Mercurial > repos > shellac > guppy_basecaller
diff env/lib/python3.7/site-packages/humanfriendly/deprecation.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
line wrap: on
line diff
--- a/env/lib/python3.7/site-packages/humanfriendly/deprecation.py Thu May 14 16:47:39 2020 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,251 +0,0 @@ -# Human friendly input/output in Python. -# -# Author: Peter Odding <peter@peterodding.com> -# Last Change: March 2, 2020 -# URL: https://humanfriendly.readthedocs.io - -""" -Support for deprecation warnings when importing names from old locations. - -When software evolves, things tend to move around. This is usually detrimental -to backwards compatibility (in Python this primarily manifests itself as -:exc:`~exceptions.ImportError` exceptions). - -While backwards compatibility is very important, it should not get in the way -of progress. It would be great to have the agility to move things around -without breaking backwards compatibility. - -This is where the :mod:`humanfriendly.deprecation` module comes in: It enables -the definition of backwards compatible aliases that emit a deprecation warning -when they are accessed. - -The way it works is that it wraps the original module in an :class:`DeprecationProxy` -object that defines a :func:`~DeprecationProxy.__getattr__()` special method to -override attribute access of the module. -""" - -# Standard library modules. -import collections -import functools -import importlib -import inspect -import sys -import types -import warnings - -# Modules included in our package. -from humanfriendly.text import format - -# Registry of known aliases (used by humanfriendly.sphinx). -REGISTRY = collections.defaultdict(dict) - -# Public identifiers that require documentation. -__all__ = ("DeprecationProxy", "define_aliases", "deprecated_args", "get_aliases", "is_method") - - -def define_aliases(module_name, **aliases): - """ - Update a module with backwards compatible aliases. - - :param module_name: The ``__name__`` of the module (a string). - :param aliases: Each keyword argument defines an alias. The values - are expected to be "dotted paths" (strings). - - The behavior of this function depends on whether the Sphinx documentation - generator is active, because the use of :class:`DeprecationProxy` to shadow the - real module in :data:`sys.modules` has the unintended side effect of - breaking autodoc support for ``:data:`` members (module variables). - - To avoid breaking Sphinx the proxy object is omitted and instead the - aliased names are injected into the original module namespace, to make sure - that imports can be satisfied when the documentation is being rendered. - - If you run into cyclic dependencies caused by :func:`define_aliases()` when - running Sphinx, you can try moving the call to :func:`define_aliases()` to - the bottom of the Python module you're working on. - """ - module = sys.modules[module_name] - proxy = DeprecationProxy(module, aliases) - # Populate the registry of aliases. - for name, target in aliases.items(): - REGISTRY[module.__name__][name] = target - # Avoid confusing Sphinx. - if "sphinx" in sys.modules: - for name, target in aliases.items(): - setattr(module, name, proxy.resolve(target)) - else: - # Install a proxy object to raise DeprecationWarning. - sys.modules[module_name] = proxy - - -def get_aliases(module_name): - """ - Get the aliases defined by a module. - - :param module_name: The ``__name__`` of the module (a string). - :returns: A dictionary with string keys and values: - - 1. Each key gives the name of an alias - created for backwards compatibility. - - 2. Each value gives the dotted path of - the proper location of the identifier. - - An empty dictionary is returned for modules that - don't define any backwards compatible aliases. - """ - return REGISTRY.get(module_name, {}) - - -def deprecated_args(*names): - """ - Deprecate positional arguments without dropping backwards compatibility. - - :param names: - - The positional arguments to :func:`deprecated_args()` give the names of - the positional arguments that the to-be-decorated function should warn - about being deprecated and translate to keyword arguments. - - :returns: A decorator function specialized to `names`. - - The :func:`deprecated_args()` decorator function was created to make it - easy to switch from positional arguments to keyword arguments [#]_ while - preserving backwards compatibility [#]_ and informing call sites - about the change. - - .. [#] Increased flexibility is the main reason why I find myself switching - from positional arguments to (optional) keyword arguments as my code - evolves to support more use cases. - - .. [#] In my experience positional argument order implicitly becomes part - of API compatibility whether intended or not. While this makes sense - for functions that over time adopt more and more optional arguments, - at a certain point it becomes an inconvenience to code maintenance. - - Here's an example of how to use the decorator:: - - @deprecated_args('text') - def report_choice(**options): - print(options['text']) - - When the decorated function is called with positional arguments - a deprecation warning is given:: - - >>> report_choice('this will give a deprecation warning') - DeprecationWarning: report_choice has deprecated positional arguments, please switch to keyword arguments - this will give a deprecation warning - - But when the function is called with keyword arguments no deprecation - warning is emitted:: - - >>> report_choice(text='this will not give a deprecation warning') - this will not give a deprecation warning - """ - def decorator(function): - def translate(args, kw): - # Raise TypeError when too many positional arguments are passed to the decorated function. - if len(args) > len(names): - raise TypeError( - format( - "{name} expected at most {limit} arguments, got {count}", - name=function.__name__, - limit=len(names), - count=len(args), - ) - ) - # Emit a deprecation warning when positional arguments are used. - if args: - warnings.warn( - format( - "{name} has deprecated positional arguments, please switch to keyword arguments", - name=function.__name__, - ), - category=DeprecationWarning, - stacklevel=3, - ) - # Translate positional arguments to keyword arguments. - for name, value in zip(names, args): - kw[name] = value - if is_method(function): - @functools.wraps(function) - def wrapper(*args, **kw): - """Wrapper for instance methods.""" - args = list(args) - self = args.pop(0) - translate(args, kw) - return function(self, **kw) - else: - @functools.wraps(function) - def wrapper(*args, **kw): - """Wrapper for module level functions.""" - translate(args, kw) - return function(**kw) - return wrapper - return decorator - - -def is_method(function): - """Check if the expected usage of the given function is as an instance method.""" - try: - # Python 3.3 and newer. - signature = inspect.signature(function) - return "self" in signature.parameters - except AttributeError: - # Python 3.2 and older. - metadata = inspect.getargspec(function) - return "self" in metadata.args - - -class DeprecationProxy(types.ModuleType): - - """Emit deprecation warnings for imports that should be updated.""" - - def __init__(self, module, aliases): - """ - Initialize an :class:`DeprecationProxy` object. - - :param module: The original module object. - :param aliases: A dictionary of aliases. - """ - # Initialize our superclass. - super(DeprecationProxy, self).__init__(name=module.__name__) - # Store initializer arguments. - self.module = module - self.aliases = aliases - - def __getattr__(self, name): - """ - Override module attribute lookup. - - :param name: The name to look up (a string). - :returns: The attribute value. - """ - # Check if the given name is an alias. - target = self.aliases.get(name) - if target is not None: - # Emit the deprecation warning. - warnings.warn( - format("%s.%s was moved to %s, please update your imports", self.module.__name__, name, target), - category=DeprecationWarning, - stacklevel=2, - ) - # Resolve the dotted path. - return self.resolve(target) - # Look up the name in the original module namespace. - value = getattr(self.module, name, None) - if value is not None: - return value - # Fall back to the default behavior. - raise AttributeError(format("module '%s' has no attribute '%s'", self.module.__name__, name)) - - def resolve(self, target): - """ - Look up the target of an alias. - - :param target: The fully qualified dotted path (a string). - :returns: The value of the given target. - """ - module_name, _, member = target.rpartition(".") - module = importlib.import_module(module_name) - return getattr(module, member)