4 changed files with 593 additions and 0 deletions
@ -0,0 +1,202 @@ |
|||
""" |
|||
Patch recently added ABCs into the standard lib module |
|||
``collections.abc`` (Py3) or ``collections`` (Py2). |
|||
|
|||
Usage:: |
|||
|
|||
import backports_abc |
|||
backports_abc.patch() |
|||
|
|||
or:: |
|||
|
|||
try: |
|||
from collections.abc import Generator |
|||
except ImportError: |
|||
from backports_abc import Generator |
|||
""" |
|||
|
|||
try: |
|||
import collections.abc as _collections_abc |
|||
except ImportError: |
|||
import collections as _collections_abc |
|||
|
|||
|
|||
def mk_gen(): |
|||
from abc import abstractmethod |
|||
|
|||
required_methods = ( |
|||
'__iter__', '__next__' if hasattr(iter(()), '__next__') else 'next', |
|||
'send', 'throw', 'close') |
|||
|
|||
class Generator(_collections_abc.Iterator): |
|||
__slots__ = () |
|||
|
|||
if '__next__' in required_methods: |
|||
def __next__(self): |
|||
return self.send(None) |
|||
else: |
|||
def next(self): |
|||
return self.send(None) |
|||
|
|||
@abstractmethod |
|||
def send(self, value): |
|||
raise StopIteration |
|||
|
|||
@abstractmethod |
|||
def throw(self, typ, val=None, tb=None): |
|||
if val is None: |
|||
if tb is None: |
|||
raise typ |
|||
val = typ() |
|||
if tb is not None: |
|||
val = val.with_traceback(tb) |
|||
raise val |
|||
|
|||
def close(self): |
|||
try: |
|||
self.throw(GeneratorExit) |
|||
except (GeneratorExit, StopIteration): |
|||
pass |
|||
else: |
|||
raise RuntimeError('generator ignored GeneratorExit') |
|||
|
|||
@classmethod |
|||
def __subclasshook__(cls, C): |
|||
if cls is Generator: |
|||
mro = C.__mro__ |
|||
for method in required_methods: |
|||
for base in mro: |
|||
if method in base.__dict__: |
|||
break |
|||
else: |
|||
return NotImplemented |
|||
return True |
|||
return NotImplemented |
|||
|
|||
generator = type((lambda: (yield))()) |
|||
Generator.register(generator) |
|||
return Generator |
|||
|
|||
|
|||
def mk_awaitable(): |
|||
from abc import abstractmethod, ABCMeta |
|||
|
|||
@abstractmethod |
|||
def __await__(self): |
|||
yield |
|||
|
|||
@classmethod |
|||
def __subclasshook__(cls, C): |
|||
if cls is Awaitable: |
|||
for B in C.__mro__: |
|||
if '__await__' in B.__dict__: |
|||
if B.__dict__['__await__']: |
|||
return True |
|||
break |
|||
return NotImplemented |
|||
|
|||
# calling metaclass directly as syntax differs in Py2/Py3 |
|||
Awaitable = ABCMeta('Awaitable', (), { |
|||
'__slots__': (), |
|||
'__await__': __await__, |
|||
'__subclasshook__': __subclasshook__, |
|||
}) |
|||
|
|||
return Awaitable |
|||
|
|||
|
|||
def mk_coroutine(): |
|||
from abc import abstractmethod |
|||
|
|||
class Coroutine(Awaitable): |
|||
__slots__ = () |
|||
|
|||
@abstractmethod |
|||
def send(self, value): |
|||
"""Send a value into the coroutine. |
|||
Return next yielded value or raise StopIteration. |
|||
""" |
|||
raise StopIteration |
|||
|
|||
@abstractmethod |
|||
def throw(self, typ, val=None, tb=None): |
|||
"""Raise an exception in the coroutine. |
|||
Return next yielded value or raise StopIteration. |
|||
""" |
|||
if val is None: |
|||
if tb is None: |
|||
raise typ |
|||
val = typ() |
|||
if tb is not None: |
|||
val = val.with_traceback(tb) |
|||
raise val |
|||
|
|||
def close(self): |
|||
"""Raise GeneratorExit inside coroutine. |
|||
""" |
|||
try: |
|||
self.throw(GeneratorExit) |
|||
except (GeneratorExit, StopIteration): |
|||
pass |
|||
else: |
|||
raise RuntimeError('coroutine ignored GeneratorExit') |
|||
|
|||
@classmethod |
|||
def __subclasshook__(cls, C): |
|||
if cls is Coroutine: |
|||
mro = C.__mro__ |
|||
for method in ('__await__', 'send', 'throw', 'close'): |
|||
for base in mro: |
|||
if method in base.__dict__: |
|||
break |
|||
else: |
|||
return NotImplemented |
|||
return True |
|||
return NotImplemented |
|||
|
|||
return Coroutine |
|||
|
|||
|
|||
### |
|||
# make all ABCs available in this module |
|||
|
|||
try: |
|||
Generator = _collections_abc.Generator |
|||
except AttributeError: |
|||
Generator = mk_gen() |
|||
|
|||
try: |
|||
Awaitable = _collections_abc.Awaitable |
|||
except AttributeError: |
|||
Awaitable = mk_awaitable() |
|||
|
|||
try: |
|||
Coroutine = _collections_abc.Coroutine |
|||
except AttributeError: |
|||
Coroutine = mk_coroutine() |
|||
|
|||
try: |
|||
from inspect import isawaitable |
|||
except ImportError: |
|||
def isawaitable(obj): |
|||
return isinstance(obj, Awaitable) |
|||
|
|||
|
|||
### |
|||
# allow patching the stdlib |
|||
|
|||
PATCHED = {} |
|||
|
|||
|
|||
def patch(patch_inspect=True): |
|||
""" |
|||
Main entry point for patching the ``collections.abc`` and ``inspect`` |
|||
standard library modules. |
|||
""" |
|||
PATCHED['collections.abc.Generator'] = _collections_abc.Generator = Generator |
|||
PATCHED['collections.abc.Coroutine'] = _collections_abc.Coroutine = Coroutine |
|||
PATCHED['collections.abc.Awaitable'] = _collections_abc.Awaitable = Awaitable |
|||
|
|||
if patch_inspect: |
|||
import inspect |
|||
PATCHED['inspect.isawaitable'] = inspect.isawaitable = isawaitable |
@ -0,0 +1,219 @@ |
|||
#!/usr/bin/env python |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
from __future__ import absolute_import |
|||
from __future__ import division |
|||
from __future__ import print_function |
|||
from __future__ import unicode_literals |
|||
|
|||
__all__ = ['singledispatch'] |
|||
|
|||
from functools import update_wrapper |
|||
from weakref import WeakKeyDictionary |
|||
from singledispatch_helpers import MappingProxyType, get_cache_token |
|||
|
|||
################################################################################ |
|||
### singledispatch() - single-dispatch generic function decorator |
|||
################################################################################ |
|||
|
|||
def _c3_merge(sequences): |
|||
"""Merges MROs in *sequences* to a single MRO using the C3 algorithm. |
|||
|
|||
Adapted from http://www.python.org/download/releases/2.3/mro/. |
|||
|
|||
""" |
|||
result = [] |
|||
while True: |
|||
sequences = [s for s in sequences if s] # purge empty sequences |
|||
if not sequences: |
|||
return result |
|||
for s1 in sequences: # find merge candidates among seq heads |
|||
candidate = s1[0] |
|||
for s2 in sequences: |
|||
if candidate in s2[1:]: |
|||
candidate = None |
|||
break # reject the current head, it appears later |
|||
else: |
|||
break |
|||
if not candidate: |
|||
raise RuntimeError("Inconsistent hierarchy") |
|||
result.append(candidate) |
|||
# remove the chosen candidate |
|||
for seq in sequences: |
|||
if seq[0] == candidate: |
|||
del seq[0] |
|||
|
|||
def _c3_mro(cls, abcs=None): |
|||
"""Computes the method resolution order using extended C3 linearization. |
|||
|
|||
If no *abcs* are given, the algorithm works exactly like the built-in C3 |
|||
linearization used for method resolution. |
|||
|
|||
If given, *abcs* is a list of abstract base classes that should be inserted |
|||
into the resulting MRO. Unrelated ABCs are ignored and don't end up in the |
|||
result. The algorithm inserts ABCs where their functionality is introduced, |
|||
i.e. issubclass(cls, abc) returns True for the class itself but returns |
|||
False for all its direct base classes. Implicit ABCs for a given class |
|||
(either registered or inferred from the presence of a special method like |
|||
__len__) are inserted directly after the last ABC explicitly listed in the |
|||
MRO of said class. If two implicit ABCs end up next to each other in the |
|||
resulting MRO, their ordering depends on the order of types in *abcs*. |
|||
|
|||
""" |
|||
for i, base in enumerate(reversed(cls.__bases__)): |
|||
if hasattr(base, '__abstractmethods__'): |
|||
boundary = len(cls.__bases__) - i |
|||
break # Bases up to the last explicit ABC are considered first. |
|||
else: |
|||
boundary = 0 |
|||
abcs = list(abcs) if abcs else [] |
|||
explicit_bases = list(cls.__bases__[:boundary]) |
|||
abstract_bases = [] |
|||
other_bases = list(cls.__bases__[boundary:]) |
|||
for base in abcs: |
|||
if issubclass(cls, base) and not any( |
|||
issubclass(b, base) for b in cls.__bases__ |
|||
): |
|||
# If *cls* is the class that introduces behaviour described by |
|||
# an ABC *base*, insert said ABC to its MRO. |
|||
abstract_bases.append(base) |
|||
for base in abstract_bases: |
|||
abcs.remove(base) |
|||
explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases] |
|||
abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases] |
|||
other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases] |
|||
return _c3_merge( |
|||
[[cls]] + |
|||
explicit_c3_mros + abstract_c3_mros + other_c3_mros + |
|||
[explicit_bases] + [abstract_bases] + [other_bases] |
|||
) |
|||
|
|||
def _compose_mro(cls, types): |
|||
"""Calculates the method resolution order for a given class *cls*. |
|||
|
|||
Includes relevant abstract base classes (with their respective bases) from |
|||
the *types* iterable. Uses a modified C3 linearization algorithm. |
|||
|
|||
""" |
|||
bases = set(cls.__mro__) |
|||
# Remove entries which are already present in the __mro__ or unrelated. |
|||
def is_related(typ): |
|||
return (typ not in bases and hasattr(typ, '__mro__') |
|||
and issubclass(cls, typ)) |
|||
types = [n for n in types if is_related(n)] |
|||
# Remove entries which are strict bases of other entries (they will end up |
|||
# in the MRO anyway. |
|||
def is_strict_base(typ): |
|||
for other in types: |
|||
if typ != other and typ in other.__mro__: |
|||
return True |
|||
return False |
|||
types = [n for n in types if not is_strict_base(n)] |
|||
# Subclasses of the ABCs in *types* which are also implemented by |
|||
# *cls* can be used to stabilize ABC ordering. |
|||
type_set = set(types) |
|||
mro = [] |
|||
for typ in types: |
|||
found = [] |
|||
for sub in typ.__subclasses__(): |
|||
if sub not in bases and issubclass(cls, sub): |
|||
found.append([s for s in sub.__mro__ if s in type_set]) |
|||
if not found: |
|||
mro.append(typ) |
|||
continue |
|||
# Favor subclasses with the biggest number of useful bases |
|||
found.sort(key=len, reverse=True) |
|||
for sub in found: |
|||
for subcls in sub: |
|||
if subcls not in mro: |
|||
mro.append(subcls) |
|||
return _c3_mro(cls, abcs=mro) |
|||
|
|||
def _find_impl(cls, registry): |
|||
"""Returns the best matching implementation from *registry* for type *cls*. |
|||
|
|||
Where there is no registered implementation for a specific type, its method |
|||
resolution order is used to find a more generic implementation. |
|||
|
|||
Note: if *registry* does not contain an implementation for the base |
|||
*object* type, this function may return None. |
|||
|
|||
""" |
|||
mro = _compose_mro(cls, registry.keys()) |
|||
match = None |
|||
for t in mro: |
|||
if match is not None: |
|||
# If *match* is an implicit ABC but there is another unrelated, |
|||
# equally matching implicit ABC, refuse the temptation to guess. |
|||
if (t in registry and t not in cls.__mro__ |
|||
and match not in cls.__mro__ |
|||
and not issubclass(match, t)): |
|||
raise RuntimeError("Ambiguous dispatch: {0} or {1}".format( |
|||
match, t)) |
|||
break |
|||
if t in registry: |
|||
match = t |
|||
return registry.get(match) |
|||
|
|||
def singledispatch(func): |
|||
"""Single-dispatch generic function decorator. |
|||
|
|||
Transforms a function into a generic function, which can have different |
|||
behaviours depending upon the type of its first argument. The decorated |
|||
function acts as the default implementation, and additional |
|||
implementations can be registered using the register() attribute of the |
|||
generic function. |
|||
|
|||
""" |
|||
registry = {} |
|||
dispatch_cache = WeakKeyDictionary() |
|||
def ns(): pass |
|||
ns.cache_token = None |
|||
|
|||
def dispatch(cls): |
|||
"""generic_func.dispatch(cls) -> <function implementation> |
|||
|
|||
Runs the dispatch algorithm to return the best available implementation |
|||
for the given *cls* registered on *generic_func*. |
|||
|
|||
""" |
|||
if ns.cache_token is not None: |
|||
current_token = get_cache_token() |
|||
if ns.cache_token != current_token: |
|||
dispatch_cache.clear() |
|||
ns.cache_token = current_token |
|||
try: |
|||
impl = dispatch_cache[cls] |
|||
except KeyError: |
|||
try: |
|||
impl = registry[cls] |
|||
except KeyError: |
|||
impl = _find_impl(cls, registry) |
|||
dispatch_cache[cls] = impl |
|||
return impl |
|||
|
|||
def register(cls, func=None): |
|||
"""generic_func.register(cls, func) -> func |
|||
|
|||
Registers a new implementation for the given *cls* on a *generic_func*. |
|||
|
|||
""" |
|||
if func is None: |
|||
return lambda f: register(cls, f) |
|||
registry[cls] = func |
|||
if ns.cache_token is None and hasattr(cls, '__abstractmethods__'): |
|||
ns.cache_token = get_cache_token() |
|||
dispatch_cache.clear() |
|||
return func |
|||
|
|||
def wrapper(*args, **kw): |
|||
return dispatch(args[0].__class__)(*args, **kw) |
|||
|
|||
registry[object] = func |
|||
wrapper.register = register |
|||
wrapper.dispatch = dispatch |
|||
wrapper.registry = MappingProxyType(registry) |
|||
wrapper._clear_cache = dispatch_cache.clear |
|||
update_wrapper(wrapper, func) |
|||
return wrapper |
|||
|
@ -0,0 +1,170 @@ |
|||
#!/usr/bin/env python |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
from __future__ import absolute_import |
|||
from __future__ import division |
|||
from __future__ import print_function |
|||
from __future__ import unicode_literals |
|||
|
|||
from abc import ABCMeta |
|||
from collections import MutableMapping |
|||
import sys |
|||
try: |
|||
from collections import UserDict |
|||
except ImportError: |
|||
from UserDict import UserDict |
|||
try: |
|||
from collections import OrderedDict |
|||
except ImportError: |
|||
from ordereddict import OrderedDict |
|||
try: |
|||
from thread import get_ident |
|||
except ImportError: |
|||
try: |
|||
from _thread import get_ident |
|||
except ImportError: |
|||
from _dummy_thread import get_ident |
|||
|
|||
|
|||
def recursive_repr(fillvalue='...'): |
|||
'Decorator to make a repr function return fillvalue for a recursive call' |
|||
|
|||
def decorating_function(user_function): |
|||
repr_running = set() |
|||
|
|||
def wrapper(self): |
|||
key = id(self), get_ident() |
|||
if key in repr_running: |
|||
return fillvalue |
|||
repr_running.add(key) |
|||
try: |
|||
result = user_function(self) |
|||
finally: |
|||
repr_running.discard(key) |
|||
return result |
|||
|
|||
# Can't use functools.wraps() here because of bootstrap issues |
|||
wrapper.__module__ = getattr(user_function, '__module__') |
|||
wrapper.__doc__ = getattr(user_function, '__doc__') |
|||
wrapper.__name__ = getattr(user_function, '__name__') |
|||
wrapper.__annotations__ = getattr(user_function, '__annotations__', {}) |
|||
return wrapper |
|||
|
|||
return decorating_function |
|||
|
|||
|
|||
class ChainMap(MutableMapping): |
|||
''' A ChainMap groups multiple dicts (or other mappings) together |
|||
to create a single, updateable view. |
|||
|
|||
The underlying mappings are stored in a list. That list is public and can |
|||
accessed or updated using the *maps* attribute. There is no other state. |
|||
|
|||
Lookups search the underlying mappings successively until a key is found. |
|||
In contrast, writes, updates, and deletions only operate on the first |
|||
mapping. |
|||
|
|||
''' |
|||
|
|||
def __init__(self, *maps): |
|||
'''Initialize a ChainMap by setting *maps* to the given mappings. |
|||
If no mappings are provided, a single empty dictionary is used. |
|||
|
|||
''' |
|||
self.maps = list(maps) or [{}] # always at least one map |
|||
|
|||
def __missing__(self, key): |
|||
raise KeyError(key) |
|||
|
|||
def __getitem__(self, key): |
|||
for mapping in self.maps: |
|||
try: |
|||
return mapping[key] # can't use 'key in mapping' with defaultdict |
|||
except KeyError: |
|||
pass |
|||
return self.__missing__(key) # support subclasses that define __missing__ |
|||
|
|||
def get(self, key, default=None): |
|||
return self[key] if key in self else default |
|||
|
|||
def __len__(self): |
|||
return len(set().union(*self.maps)) # reuses stored hash values if possible |
|||
|
|||
def __iter__(self): |
|||
return iter(set().union(*self.maps)) |
|||
|
|||
def __contains__(self, key): |
|||
return any(key in m for m in self.maps) |
|||
|
|||
@recursive_repr() |
|||
def __repr__(self): |
|||
return '{0.__class__.__name__}({1})'.format( |
|||
self, ', '.join(map(repr, self.maps))) |
|||
|
|||
@classmethod |
|||
def fromkeys(cls, iterable, *args): |
|||
'Create a ChainMap with a single dict created from the iterable.' |
|||
return cls(dict.fromkeys(iterable, *args)) |
|||
|
|||
def copy(self): |
|||
'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' |
|||
return self.__class__(self.maps[0].copy(), *self.maps[1:]) |
|||
|
|||
__copy__ = copy |
|||
|
|||
def new_child(self): # like Django's Context.push() |
|||
'New ChainMap with a new dict followed by all previous maps.' |
|||
return self.__class__({}, *self.maps) |
|||
|
|||
@property |
|||
def parents(self): # like Django's Context.pop() |
|||
'New ChainMap from maps[1:].' |
|||
return self.__class__(*self.maps[1:]) |
|||
|
|||
def __setitem__(self, key, value): |
|||
self.maps[0][key] = value |
|||
|
|||
def __delitem__(self, key): |
|||
try: |
|||
del self.maps[0][key] |
|||
except KeyError: |
|||
raise KeyError('Key not found in the first mapping: {!r}'.format(key)) |
|||
|
|||
def popitem(self): |
|||
'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' |
|||
try: |
|||
return self.maps[0].popitem() |
|||
except KeyError: |
|||
raise KeyError('No keys found in the first mapping.') |
|||
|
|||
def pop(self, key, *args): |
|||
'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' |
|||
try: |
|||
return self.maps[0].pop(key, *args) |
|||
except KeyError: |
|||
raise KeyError('Key not found in the first mapping: {!r}'.format(key)) |
|||
|
|||
def clear(self): |
|||
'Clear maps[0], leaving maps[1:] intact.' |
|||
self.maps[0].clear() |
|||
|
|||
|
|||
class MappingProxyType(UserDict): |
|||
def __init__(self, data): |
|||
UserDict.__init__(self) |
|||
self.data = data |
|||
|
|||
|
|||
def get_cache_token(): |
|||
return ABCMeta._abc_invalidation_counter |
|||
|
|||
|
|||
|
|||
class Support(object): |
|||
def dummy(self): |
|||
pass |
|||
|
|||
def cpython_only(self, func): |
|||
if 'PyPy' in sys.version: |
|||
return self.dummy |
|||
return func |
Loading…
Reference in new issue