Patching launcher

Each package implementing a compat-patcher for a specific framework/library should expose a patch() function at the top level, responsible for the whole process of retrieving configuration, instantiating miscellaneous utilities, selecting relevant fixers, and applying them.

These building blocks are exposed to ease the process fo creating this launcher.

compat_patcher_core.generic_patch_software(settings, patching_registry, patching_utilities_class=<class 'compat_patcher_core.utilities.PatchingUtilities'>, patching_runner_class=<class 'compat_patcher_core.runner.PatchingRunner'>, warnings_proxy=None)[source]

Load all dependencies, and apply relevant fixers from the patching_registry, according to the settings of the provided settings.

You can provide custom classes to be instantiated instead of default ones, and/or an existing WarningsProxy which will be updated with the new settings as soon as possible.


This decorator makes a patching launcher thread-safe with a recursive lock.

Other checks and misc. features might be added in the future, so packages using this patching framework should always decorate their main “patch()” entrypoint with this utility.

Patching registry

class compat_patcher_core.PatchingRegistry(family_prefix, populate_callable=None, current_software_version=None)[source]

This registry is used to store and select a set of fixers related to some specific software.

family_prefix will be used to constructe family names, along with the software reference version provided by the fixer.

populate_callable, if provided, is a callable taking the registry as first argument, and which will be called by populate().

current_software_version may be a version tuple or a string. If it’s None, then an override value will have to be provided when calling get_relevant_fixers.


Return the list of all fixers (as dicts) known by this registry.


Return the fixer having this (unqualified) ID, or raise KeyError.

get_relevant_fixer_ids(qualified=False, **kwargs)[source]

“Same as get_relevant_fixers, but only returns IDs of selected fixers.

If qualified is True, returns a fixers IDs dot-prefixed with the family name.

get_relevant_fixers(include_fixer_ids='*', include_fixer_families=None, exclude_fixer_ids=None, exclude_fixer_families=None, current_software_version=None, log=None)[source]

Return the list of fixers (as dicts) to be applied for the target software version, based on the metadata of fixers, as well as inclusion/exclusion lists provided as arguments.

For inclusion/exclusion filters, a special “*” value means “all fixers”, else a list of strings is expected.

An output callable log may be provided, expecting a string as argument, to debug the reasons why some fixers weren’t selected.

This method forces a populate() on the registry.


Trigger the registration of potential lazy fixers, which might be in other submodules, or waiting in factory functions.

register_compatibility_fixer(fixer_reference_version, fixer_applied_from_version=None, fixer_applied_upto_version=None, feature_supported_from_version=None, feature_supported_upto_version=None, fixer_tags=None)[source]

Register a compatibility fixer, which will be activated only if current software version is >= fixer_applied_from_version and < fixer_applied_upto_version (let them be None to have no limit).

The “fixer_reference_version” parameters identifies the software version where the breaking change was introduced (for backwards compatibility fixers), or where the new feature was introduced (for forwards compatibility fixers). It is not related to the appearance of corresponding DeprecationWarnings in the software. It is also used to sort fixers when applying them, and to generate the name of the family of fixers concerned.

feature_supported_from_version (included) and feature_supported_upto_version (excluded) may be used to limit the range of software versions for which related unit-tests are expected to work (i.e versions for which the feature is available, either as a monkey-paching or as standard code).

Version identifiers must be dotted strings, eg. “1.9.1”. None means “no limit” here.

fixer_tags is a list of strings, which can be used to differentiate fixers which will be applied at different moments of software startup.

class compat_patcher_core.MultiPatchingRegistry(registries)[source]

This patching registry wraps a list of other registries, each having its own fixers and current software version.

It concatenates and returns selected fixers on demand, assuming that they are compatible with each other.

get_all_fixers(*args, **kwargs)[source]

Return the concatenation of all fixers of underlying registries.

get_fixer_by_id(fixer_id, *args, **kwargs)[source]

In case of duplicate fixers having the same ID, just return the first one.

get_relevant_fixer_ids(qualified=False, **kwargs)

“Same as get_relevant_fixers, but only returns IDs of selected fixers.

If qualified is True, returns a fixers IDs dot-prefixed with the family name.

get_relevant_fixers(*args, **kwargs)[source]

Populate underlying registries, and return the concatenation of their selected fixers.

Forcing a current_software_version as parameter of this method is still possible, but beware that underlying registries all deal with the same software stack, in this case.

Patching utilities

class compat_patcher_core.PatchingUtilities(settings)[source]

An instance of this class is provided as first argument to each compatibility fixer applied.

It provides handy tools to monkey-patch the software environment, and emit logs and Warnings in a controllable way.

For better forward-compatibility, please call injection utilities though keyword arguments, and not positional ones (due to python2.7 support, it’s not enforced yet).

settings_keys_used = ['logging_level', 'enable_warnings', 'patch_injected_objects']

This method can be called at runtime, mainly to alter the emission of logs and Warnings by fixers. it’s possible to provide only a subset of settings, the others remaining as is.

emit_log(message, level='INFO')[source]

A logger printing to stderr, since at some stages of patching, logging is not yet setup.

Log is only output if level is gerater or equal the current logging_level setting.

emit_warning(message, category=<class 'DeprecationWarning'>, stacklevel=1)[source]

Similar to “warnings.warn()” of the stdlib, but only emits the Warning if enable_warnings setting is True.

inject_attribute(target_object, target_attrname, attribute)[source]

Inject an attribute into an object of any type (module, class, instance…).

  • target_object – The object to patch
  • target_attrname – The name given to the new attribute in the object to patch
  • attribute – The attribute to inject, which must not be a callable
inject_callable(target_object, target_callable_name, patch_callable)[source]

Inject a simple callable (not a class) into an object of any type (module, class, instance…).

  • target_object – The object to patch
  • target_callable_name – The name given to the new callable in the object to patch
  • patch_callable – The callable to inject, which must be a callable, but not a class
inject_callable_alias(target_object, target_attrname, source_object, source_attrname)[source]

Create and inject an alias for the source callable (not a class), which also triggers a deprecation warning when called.

Returns the created alias callable.

  • target_object – The object to patch
  • target_attrname – The name of the callable on the target object
  • source_object – The object from which to get the callable
  • source_attrname – The name of the callable on the source object
inject_class(target_object, target_klassname, klass)[source]

Inject a class into an object of any type (module, class, instance…).

  • target_object – The object to patch
  • target_klassname – The name given to the new class in the object to patch
  • klass – The class to inject
inject_import_alias(alias_name, real_name)[source]

Create an import alias for the selected module.

This doesn’t directly patch sys.modules, but instead uses the imports hooks of python, so that “import <alias_name>” and “import <real_name>” both end up importing the same the (sub)module object.

When the alias is a submodule, its parent module(s) must exist too.

  • alias_name – The dotted name of the alias module
  • real_name – The dotted name of the real module
inject_module(target_module_name, module)[source]

Inject a module in sys.modules, under the selected dotted name.

When injecting a submodule, its parent module(s) must exist too.

It is good practice to, then, also inject this module object as an attribute of its immediate parents (with inject_attribute()), since this is normally done during python imports.

  • target_module_name – The dotted name of the new module in sys.modules
  • module – The module object to inject
class compat_patcher_core.WarningsProxy[source]

An instance of this class acts as a replacement for the stdlib “warnings” package, but it relies on a PatchingUtilities instance as soon as this one is provided - thus making Warnings controllable by compat patcher settings.


Coerces the version string (if not None), to a version tuple. E.g. “1.7.0” becomes (1, 7, 0). No version suffix like “alpha” is expected.


Coerces the version tuple (if not None), to a version string. E.g. (1, 7, 0) becomes “1.7.0”.

Patching runner

class compat_patcher_core.PatchingRunner(settings, patching_registry, patching_utilities)[source]

This class is in charge of fetching relevant fixers from the registry, and applying them in a proper order, while skipping those who have already been applied (i.e same family name and ID) and those which raised SkipFixerException.


Patch the software according to plans.

Return a dict with, at least field “fixers_just_applied”, the list of fixers that were successfully applied during this call.

Patching exceptions

class compat_patcher_core.SkipFixerException[source]

Exception to signal a fixer which is not applicable in that project context.

Patching configuration

The settings expected by classes above must be a dict-like object (just a __getitem__() method is enough), which raises KeyError if a setting is not found.

compat_patcher_core.DEFAULT_SETTINGS = {'enable_warnings': True, 'exclude_fixer_families': None, 'exclude_fixer_ids': None, 'include_fixer_families': None, 'include_fixer_ids': '*', 'logging_level': 'INFO', 'patch_injected_objects': True}

Example configuration to copy() and adapt