summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author?ukasz Langa <lukasz@langa.pl>2013-05-25 23:46:41 +0200
committer?ukasz Langa <lukasz@langa.pl>2013-05-25 23:46:41 +0200
commit166a442bd7fc983ecb5cb49aa68dce2a17855d1b (patch)
treef59809c11ea9d5b3beafb0a5b7c0d2f960ecac9f
parent608f97f22cb57c6f96efa9c19e27e400cb6b289e (diff)
downloadsingledispatch-166a442bd7fc983ecb5cb49aa68dce2a17855d1b.tar.gz
bootstrap updates necessary for 3.2 - 3.3 compatibility
-rw-r--r--setup.py2
-rw-r--r--singledispatch.py4
-rw-r--r--singledispatch_helpers.py150
-rw-r--r--test_singledispatch.py7
-rw-r--r--tox.ini1
5 files changed, 156 insertions, 8 deletions
diff --git a/setup.py b/setup.py
index 834e9f1..4ca442a 100644
--- a/setup.py
+++ b/setup.py
@@ -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")
diff --git a/tox.ini b/tox.ini
index c0fc179..fb6827d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,7 +2,6 @@
envlist = py26,py27,py32,py33
[testenv]
-changedir = test
commands =
{envbindir}/python test_singledispatch.py