I have historically made many plugin-architecture software [*] using
setuptools entry points. I don't fundamentally believe that the
packaging system (i.e. setuptools) is the appropriate place for such
things. However its there and its convenient. I've asked in #python
on freenode a few times and besides "don't use setuptools" and "what
problem are you trying to solve?" I haven't gotten much response.
The problem I am trying to solve is this:
1. I tend to write software with either swappable components (which
backend model do you want; SQL, couch, flat files, etc) or software
that you can hang an ordered list of extensions on an entry point.
These shouldn't be hardcoded and in fact must have a string key for
driving via a CLI or a a configuration file
2. There should be the ability to add functionality (change these
plugins) -- easily -- without having to modify the core software.
To give an example, see
https://github.com/k0s/toolbox/blob/master/toolbox/dispatcher.py#L21 .
There are two model classes keyed on a string. For completely
self-contained software, as it currently is, a dict is fine: string
keys -> plugins. However, for a large class of problems I want to be
able to add extensions without having to modify the upstream software
(including monkey-patching). I hope that's clear. Whether or not its
necessary for this particular example is debatable, though I do have
other cases in the same module (e.g. handlers) that should be
pluggable if the software is going to truly be useful and extensible
software instead of a particular implementation.
I see our own Tarek has done http://pypi.python.org/pypi/extensions/ .
Is this the recommended course of action?
I'd like to find recipes/patterns/standards that can be (easily) used
by the Mozilla community. In other words, this isn't a "how do I solve
this problem?" question, as I can think of a few ways of doing that,
but rather "how do I move towards a plugin software architecture
generally useful to e.g. Mozilla?".
Jeff
[*] I don't necessarily believe that plugin architectures are a
particularly great architecture even at their best. I prefer more a
contract-based approach, such as zope.interface. But such approaches
are often difficult to actualize, and zope.interface isn't wonderful
(or maybe I just haven't mastered it). And plugin architectures are
easy to write, understand, and maintain. So I use them.
The problem of Plugins is two-fold
1/ how to declare a plugin for an application
2/ how to configure the application so that is uses the plugins
Setuptools' principle, where you declare extensions points (1), is
fine per se, but the way it's used out there (2) is often wrong:
applications tend to loop over entry points and use the ones that
correspond to a group *automatically*
IOW if I create a Python project that exposes an entry point, it will
be implicitly activated in an application that uses these entry
points.
and there will be potential issues:
- what if I want to install application A but not activate one of its
entry point ?
- what if two entries points are conflicting with each others ? which
one overrides the other ?
- ...
The important thing imo is to let the end-user explicitly manage
plugins configuration for an application via a config file, like
Mercurial does.
To resume: Implicitly activated plugins are not a good idea, But
discoverability is still a nice feature to have.
So, here's what I am planning for Packaging:
- Metadata in general will be extensible via X-Fields in setup.cfg, so
one project can define a new field. Let's say "X-Entry-Point".
- The pkgutil module in the stdlib will project discoverabilty for
metadata, thus for entry points
- Applications should use a configuration file to explicitly activate
plugins - we might provide helpers atop pkgutil
But we're not there yet. The goal of the "extensions" project I've
started is to provide a backport of the pkgutil APIs that will also be
backward compatible with today's Setuptools entry points, so people
can use it w/o a setuptools dependency.
In the meantime, what I would highly suggest is to go the Mercurial way:
1/ add in the application a config file with a section for plugins
2/ every plugin installed by a third party project, can be explicit
activated via its fully qualified name
for example:
[extensions]
foo = here.is.the.plugin
where plugin is a class or a module or whatever the app takes, and
here.is.the, the package/module where it lives.
>
>
> I'd like to find recipes/patterns/standards that can be (easily) used
> by the Mozilla community. In other words, this isn't a "how do I solve
> this problem?" question, as I can think of a few ways of doing that,
> but rather "how do I move towards a plugin software architecture
> generally useful to e.g. Mozilla?".
>
> Jeff
>
> [*] I don't necessarily believe that plugin architectures are a
> particularly great architecture even at their best. I prefer more a
> contract-based approach, such as zope.interface. But such approaches
> are often difficult to actualize, and zope.interface isn't wonderful
> (or maybe I just haven't mastered it). And plugin architectures are
> easy to write, understand, and maintain. So I use them.
Yeah agreed. what I did is defined interfaces but without
zope.interface. I use ABCs for this. Some people find this feature
overkill, but I find it quite nice to have to validate plugins.
That's what I do for now in Services, I have a plugin registry that
validates and registers plugins that are explicitly declared. See:
https://hg.mozilla.org/services/server-core/file/ce11a1c69122/services/pluginreg.py
And I control plugins via config files, and define them in my packages. see:
- the interface definition:
https://hg.mozilla.org/services/server-core/file/ce11a1c69122/services/auth/__init__.py#l47
- a concrete plugin :
https://hg.mozilla.org/services/server-core/file/ce11a1c69122/services/auth/sql.py#l79
Notice that like the ZCA, the plugin does not inherit from a base
plugin class, which avoid any class mixin hell. It just have to
implement some methods defined in the interface, and the plugin
registry controls this.
Cheers
Tarek
--
Tarek Ziadé | http://ziade.org
> And I control plugins via config files, and define them in my packages.
+1 on config files. If your plugins need the concept of ordering, it's basically the only way to do it.
I personally prefer Python as a config file syntax, because why not? You don't end up reinventing the wheel to do factoring or conditionals (like ZCML did), humans don't need to learn yet another format, and you save a language boundary. Non-Python is a good choice if you need non-Python tools (like aptitude) to read the files or if you need to read untrusted config files (like in Packaging).
Erik
I used to prefer Python as the config file of choice but lately i've
been moving towards JSON. It allows the same complexity (if needed)
and the config data is consumable by non-python environments.
--
Bear
be...@xmpp.org (email)
bea...@gmail.com (xmpp, email)
be...@code-bear.com (xmpp, email)
http://code-bear.com/bearlog (weblog)
PGP Fingerprint = 9996 719F 973D B11B E111 D770 9331 E822 40B3 CD29
On my side, I prefer ini-like files for the sake of simplicity. If
you don't have a strong policy, Python-based configuration end up
complicated and easy to break. And if you start adding conditionals in
your config, something's wrong. IOW the complexity can be in the
application side, where the configuration is read at initialization,
and the configuration file itself can stay a simple, flat ini-file.
Semi-replated: forcing developers to think hard about very simple
options to configure the app is a good practice imho, as it becomes
almost self-documented.
A side benefit is that Ops can tweak ini-like files without risking to
break your app code, as long as the boundaries for every value is
documented and/or checked at initialization. For the same reason, I
also dislike json or yaml: a missing parenthesis can break the file,
whereas a ini-like file is pretty robust on typos etc.
that's exactly why I prefer JSON - if the config is wrong it *should* break IMO.
INI files just have too much "wiggle room". But +1 on the reasons to
avoid Python.
>
> Cheers
> Tarek
>
> --
> Tarek Ziadé | http://ziade.org
>
--
$0.02
Jeff
hehe - it's wiggle is on the parsing/structure side - not the value
side I guess :)
To round out the pro/con list, I've seen case after case where attempting to enforce layer separation or layer simplicity with language boundaries backfires. I've seen people embed business logic in Clearsilver templates, and we all know the overengineered mess that peak.config turned into, subsuming (but spelling differently) a whole raft of Python syntax. A good Python config file should look like this:
foo = 'value'
bar = 'other value'
baz = u'ünicøde'
things = [1, 4, 5] # No need to invent a new syntax for a simple structure
Django's settings.py is a good example.
Erik
My statement was from a real production case where we forgot the
ending "}" and it took people some time to find it, because all
variables values looked perfectly fine.
What I meant to say was that JSON has extra syntax-thingies compared
to ini-like file to express the same thing, and the nesting JSON
offers is often not necessary for the configuration.
But that's a bit of bikesheding I guess ;)
YAML! XML! ;)
Here's my attempt on this:
https://wiki.mozilla.org/Services/Sync/Server/GlobalConfFile
implementation:
https://hg.mozilla.org/services/server-core/file/ce11a1c69122/services/config.py
my .2 euros
Cheers