[Distutils] installing .py plugins to an alternate directory

David Cournapeau david at ar.media.kyoto-u.ac.jp
Thu Dec 3 04:46:53 CET 2009

Andrew Dalke wrote:
> Hi all,
>   I'm working with the Akara project. It contains a web server. The server loads extensions from a special directory (let's say "$AKARA" for now). An extension can register handlers for URLs. An example extension might look like:
> installs to $AKARA/spam_extension.py
> (note: only .py files are supported; not even .pyc files)
> =========================
> from akara.services import simple_service
> import my_spam # This is part of the distribution, and gets put in site-packages
> @simple_service("GET", "http://vikings.protocol.id/")
> def vikings(say=my_spam.DEFAULT_TEXT):
>     return my_spam.vikings(say)
> =========================
> We want people to be able to distribute Akara plugins and install via setup.py. Ideally I would like to say:
> from distutils.core import setup
> from akara.distutils ... I'm not sure what here ...
> setup(name="Spam services",
>       package="my_spam",
>       akara_extensions=["spam_extension.py"]
> )
> To clarify, the development/distribution package looks like:
>   $PACKAGE/setup.py
>   $PACKAGE/spam_extensions.py
>   $PACKAGE/my_spam/__init__.py
>   $PACKAGE/my_spam/dramatis_personae.py
>   $PACKAGE/my_spam/cafe.py
> and $PACKAGE/spam_extensions.py goes to $AKARA/spam_extensions.py while $PACKAGE/my_spam is copied to site-packages.
> The installation does not need to byte-compile spam_extension.py.
> It should also include spam_extension.py in any distribution that it makes.
> I looked through the documentation and searched for existing examples, but found nothing which does this. The plugins I found used entry_points, and that's an architecture change which I don't think is appropriate for us.

I won't comment on entry_points, as I have never used them. The way I
would do it is by having akara distutils extensions, which define in
particular a setup function and associated classes.

Here is the code:

import shutil

from distutils.core import setup as _setup
from distutils.core import Command
from distutils import log

# XXX: you will need to handle setuptools (and distribute, and....)
# monkey-patching for the below commands
from distutils.command.install import install as old_install
from distutils.dist import Distribution as _Distribution

# Where to install akara extensions
AKARA_SITE = '/tmp'

def setup(**kw):
    new_kw = kw.copy()

    # Handle commands overriding
    cmdclass = new_kw.get('cmdclass', {})
    if 'install' in cmdclass:
        install = cmdclass['install']
        install = old_install

    class my_install(install):
        sub_commands = install.sub_commands + [
                ('install_akara_extensions', lambda x: True)

    class install_akara_extensions(Command):
        description = "Command to install akara extensions"
        user_options = [
            ('akara-site=', None, 'Akara plugin directory install'),

        def initialize_options(self):
            self.install_dir = None
            self.akara_site = None
            self.outfiles = []

        def finalize_options(self):
            if self.akara_site is None:
                self.akara_site = AKARA_SITE

        def run (self):
            dist = self.distribution
            for a in  dist.akara_extensions:
                log.info("Installing akara extension %s in %s" % (a,
                shutil.copy(a, self.akara_site)

    new_cmdclass = {}

    new_cmdclass['install'] = my_install
    new_cmdclass['install_akara_extensions'] = install_akara_extensions
    new_kw['cmdclass'] = new_cmdclass
    # Handle overriden distclass
    if not 'distclass' in new_kw:
        Distribution = new_kw['distclass']
        Distribution = _Distribution

    class MyDistribution(Distribution):
        def __init__(self, attrs=None):
            assert not hasattr(self, 'akara_extensions')

            if 'akara_extensions' in attrs:
                self.akara_extensions = attrs['akara_extensions']
                self.akara_extensions = []
            Distribution.__init__(self, attrs)

    new_kw['distclass'] = MyDistribution

    return _setup(**new_kw)

setup(name="Spam services",

This is the minimal code to support this semi correctly. Note that many
things are not handled correctly:
    - get_output for install_akara_extensions should be handled
    - most likely it will break for semi-random distutils extensions,
including setuptools and distribute: you will need to detect
monkey-patching to decide which Distribution and install commands to
override. I have not done it because it requires the extensions to be in
a separate package (to conditionally import things depending on detected
monkey patching).

If you think this is insane, you are not alone :)


More information about the Distutils-SIG mailing list