-
Notifications
You must be signed in to change notification settings - Fork 9
Extension Conventions and Best Practices
This document aims to cover a number of common Extension practices. Extensions should:
- Utilize as little code as possible to solve focused problems. Do not write one extension to rule them all.
- Leverage existing third-party "best of breed" packages wherever possible. There is no need to reinvent the wheel unless it's square.
- Allow for configuration-free use. Provide a sensible default configuration, where possible.
Extensions may define metadata describing the interactions and dependencies the extension may provide or require.
The most immediately useful metadata attributes to define are:
provides
: The set of feature flags (sometimes referred to as "use flags") this extension provides if enabled.
uses
: Feature flags that this extension will utilize (and thus must be sorted after) but does not explicitly depend on.
needs
: Feature flags the extension depends on.
always
: Identify that this extension should always be enabled.
never
: Identify that this extension should never be enabled.
first
: Identify if this extension should be automatically made a dependency (via uses
) of all other non-first extensions.
last
: Identify if this extension should automatically depend on (via uses
) all other non-last extensions.
All extensions should be registered using entry_points
plugin references under the web.extension
namespace. Register each extension against each feature flag it defines in its provides
metadata.
Extension authors may define a set property named extensions
that lists other entry_point
extension namespaces utilized by the extension. This is primarily useful for improved auto-generated documentation and error reporting.
Extensions are allowed to declare new extension callbacks by defining a property named signals
as a set of string signal names. Strings prefixed with a hyphen (-
) will have their callback list order reversed.
The transaction extension, for example, declares the following to allow other extensions to participate in the transactional API:
class TransactionExtension(object):
signals = {'begin', '-vote', '-finish', '-abort'}
...
A common pattern for extensions is to provide a set of things managed by the extension. In order to interoperate with YAML (or JSON) configuration in the most flexible way, it is recommend to model this pattern using Python 3 "keyword only arguments", like this:
class MyExtension:
def __init__(self, *_choices, default=None, choices=None):
self.choices = _choices
if choices:
self.choices += tuple(choices)
if not self.choices and default:
self.choices = (default, )
This will allow for the broadest choice of configuration mechanism. For example, all of the following are valid:
MyExtension("foo", "bar")
MyExtension(default="foo")
MyExtension(choices=["foo"])
MyExtension(default="foo", choices=["bar"])
In YAML format, the above can be expressed:
application:
extensions:
my_extension:
- foo
- bar
---
application:
extensions:
my_extension:
default: foo
---
application:
extensions:
my_extension:
choices:
- foo
---
application:
extensions:
my_extension:
default: foo
choices:
- bar