Resolving Conflicts

Added in version 5.6.0.

Entry point names do not have to be unique within a namespace. This is intentional and is what makes the hooks pattern (Patterns for Loading) possible, where several entry points share a name so that they can all be invoked for a single event. For the drivers pattern, however, a name is expected to identify exactly one implementation, and for the extensions pattern each name is used to look up a single extension. When more than one entry point in a namespace registers under the same name, stevedore has to decide which one to use. This is a conflict, and the mechanism that decides what to do about it is the conflict resolver.

Conflicts most commonly arise when two packages are installed that both provide a plugin under the same name in the same namespace. For example, two libraries that each register a sqlite driver in the myapp.database namespace.

The conflict_resolver parameter

The following managers accept a conflict_resolver keyword argument:

A conflict resolver is any callable matching ConflictResolverT. It is called with three arguments - the namespace, the conflicting name, and the list of Extension objects that share that name - and must return the single Extension to use (or raise an exception to abort):

def resolver(namespace, name, entrypoints):
    # return one of the entries in ``entrypoints``
    ...

The resolver is only consulted for name-based lookups. For the ExtensionManager and NamedExtensionManager this happens lazily, the first time an extension is retrieved by name. For the DriverManager, which always loads a single named driver, it is consulted when the manager is constructed.

Built-in resolvers

stevedore ships with two resolvers, both in the stevedore.extension module.

ignore_conflicts()

Logs a warning listing the conflicting implementations and returns the last entry point found, so a single plugin is selected and no error is raised. This is the default for ExtensionManager and NamedExtensionManager.

error_on_conflict()

Raises MultipleMatches, refusing to resolve the conflict silently. This is the default for DriverManager.

Changed in version 5.9.1: Prior to 5.9.1, DriverManager unconditionally raised MultipleMatches and ignored any conflict_resolver that was passed. Since 5.9.1 this parameter is honoured and defaults to error_on_conflict() to preserve the historical “raise on duplicate” behavior.

Usage

To have a DriverManager pick a driver instead of raising when duplicates are present, pass ignore_conflicts():

from stevedore import driver
from stevedore import extension

mgr = driver.DriverManager(
    namespace='myapp.database',
    name='sqlite',
    invoke_on_load=True,
    conflict_resolver=extension.ignore_conflicts,
)

Alternatively, to make an ExtensionManager or NamedExtensionManager raise on duplicates rather than silently choosing one, pass error_on_conflict():

from stevedore import extension

mgr = extension.ExtensionManager(
    namespace='myapp.formatters',
    conflict_resolver=extension.error_on_conflict,
)

Writing a custom resolver

When neither built-in resolver expresses the policy you need, you can provide your own callable. This can be useful if you want to prefer an implementation from a particular package, for example. The example below prefers the first entry point discovered:

from stevedore import driver

def prefer_first(namespace, name, entrypoints):
    return entrypoints[0]

mgr = driver.DriverManager(
    namespace='myapp.database',
    name='sqlite',
    invoke_on_load=True,
    conflict_resolver=prefer_first,
)

A custom resolver may also raise an exception to reject the conflict, just as error_on_conflict() does.