diff options
author | ?ukasz Langa <lukasz@langa.pl> | 2013-05-25 23:49:37 +0200 |
---|---|---|
committer | ?ukasz Langa <lukasz@langa.pl> | 2013-05-25 23:49:37 +0200 |
commit | ffa12b2ae9b9b4ae1e2519414cf784e896832424 (patch) | |
tree | f59809c11ea9d5b3beafb0a5b7c0d2f960ecac9f | |
parent | 608f97f22cb57c6f96efa9c19e27e400cb6b289e (diff) | |
parent | 166a442bd7fc983ecb5cb49aa68dce2a17855d1b (diff) | |
download | singledispatch-ffa12b2ae9b9b4ae1e2519414cf784e896832424.tar.gz |
merge with upstream
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | singledispatch.py | 4 | ||||
-rw-r--r-- | singledispatch_helpers.py | 150 | ||||
-rw-r--r-- | test_singledispatch.py | 7 | ||||
-rw-r--r-- | tox.ini | 1 |
5 files changed, 156 insertions, 8 deletions
@@ -54,7 +54,7 @@ setup ( 'genericfunctions decorator backport', platforms = ['any'], license = 'MIT', - py_modules = ('singledispatch',), + py_modules = ('singledispatch', 'singledispatch_helpers'), zip_safe = True, install_requires = [ 'six', diff --git a/singledispatch.py b/singledispatch.py index e0de4ca..5093904 100644 --- a/singledispatch.py +++ b/singledispatch.py @@ -8,11 +8,9 @@ from __future__ import unicode_literals __all__ = ['singledispatch'] -from abc import get_cache_token from functools import update_wrapper -from types import MappingProxyType from weakref import WeakKeyDictionary - +from singledispatch_helpers import MappingProxyType, get_cache_token ################################################################################ ### singledispatch() - single-dispatch generic function decorator diff --git a/singledispatch_helpers.py b/singledispatch_helpers.py new file mode 100644 index 0000000..5c4aa8b --- /dev/null +++ b/singledispatch_helpers.py @@ -0,0 +1,150 @@ +#!/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, UserDict +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): + super(MappingProxyType, self).__init__() + self.data = data + + +def get_cache_token(): + return ABCMeta._abc_invalidation_counter diff --git a/test_singledispatch.py b/test_singledispatch.py index 2d562cd..0234de3 100644 --- a/test_singledispatch.py +++ b/test_singledispatch.py @@ -10,6 +10,7 @@ import collections import decimal from itertools import permutations import singledispatch as functools +from singledispatch_helpers import ChainMap import unittest @@ -102,8 +103,8 @@ class TestSingleDispatch(unittest.TestCase): self.assertEqual(m, [dict, c.MutableMapping, c.Mapping, object]) bases = [c.Container, c.Mapping, c.MutableMapping, c.OrderedDict] for haystack in permutations(bases): - m = mro(c.ChainMap, haystack) - self.assertEqual(m, [c.ChainMap, c.MutableMapping, c.Mapping, + m = mro(ChainMap, haystack) + self.assertEqual(m, [ChainMap, c.MutableMapping, c.Mapping, c.Sized, c.Iterable, c.Container, object]) # Note: The MRO order below depends on haystack ordering. m = mro(c.defaultdict, [c.Sized, c.Container, str]) @@ -138,7 +139,7 @@ class TestSingleDispatch(unittest.TestCase): self.assertEqual(g(s), "sized") self.assertEqual(g(f), "sized") self.assertEqual(g(t), "sized") - g.register(c.ChainMap, lambda obj: "chainmap") + g.register(ChainMap, lambda obj: "chainmap") self.assertEqual(g(d), "mutablemapping") # irrelevant ABCs registered self.assertEqual(g(l), "sized") self.assertEqual(g(s), "sized") @@ -2,7 +2,6 @@ envlist = py26,py27,py32,py33 [testenv] -changedir = test commands = {envbindir}/python test_singledispatch.py |