Skip to content

Extension Conventions and Best Practices

Alice Zoë Bevan–McGregor edited this page Sep 26, 2016 · 1 revision

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.

Metadata

Extensions may define metadata describing the interactions and dependencies the extension may provide or require.

Dependency Graphing Metadata

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.

Plugin Registration

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.

Defining New Callbacks

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'}
	...

Configuration

List of Choices

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