mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-01-26 12:53:04 -08:00
6bb4ae56bd
Dependencies: * backports.functools-lru-cache 1.2.1 * jaraco.classes 1.3 * jaraco.collections 1.3.2 * jaraco.functools 1.11 * jaraco.structures 1.0 * jaraco.text 1.7 * jaraco.ui 1.4 * jaraco.windows 3.6 * more-itertools 2.2 * path.py 8.2.1 * six 1.10.0
774 lines
17 KiB
Python
774 lines
17 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import absolute_import, unicode_literals, division
|
|
|
|
import re
|
|
import operator
|
|
import collections
|
|
import itertools
|
|
import copy
|
|
|
|
import six
|
|
from jaraco.classes.properties import NonDataProperty
|
|
import jaraco.text
|
|
|
|
|
|
class DictFilter(object):
|
|
"""
|
|
Takes a dict, and simulates a sub-dict based on the keys.
|
|
|
|
>>> sample = {'a': 1, 'b': 2, 'c': 3}
|
|
>>> filtered = DictFilter(sample, ['a', 'c'])
|
|
>>> filtered == {'a': 1, 'c': 3}
|
|
True
|
|
|
|
One can also filter by a regular expression pattern
|
|
|
|
>>> sample['d'] = 4
|
|
>>> sample['ef'] = 5
|
|
|
|
Here we filter for only single-character keys
|
|
|
|
>>> filtered = DictFilter(sample, include_pattern='.$')
|
|
>>> filtered == {'a': 1, 'b': 2, 'c': 3, 'd': 4}
|
|
True
|
|
|
|
Also note that DictFilter keeps a reference to the original dict, so
|
|
if you modify the original dict, that could modify the filtered dict.
|
|
|
|
>>> del sample['d']
|
|
>>> del sample['a']
|
|
>>> filtered == {'b': 2, 'c': 3}
|
|
True
|
|
|
|
"""
|
|
def __init__(self, dict, include_keys=[], include_pattern=None):
|
|
self.dict = dict
|
|
self.specified_keys = set(include_keys)
|
|
if include_pattern is not None:
|
|
self.include_pattern = re.compile(include_pattern)
|
|
else:
|
|
# for performance, replace the pattern_keys property
|
|
self.pattern_keys = set()
|
|
|
|
def get_pattern_keys(self):
|
|
#key_matches = lambda k, v: self.include_pattern.match(k)
|
|
keys = filter(self.include_pattern.match, self.dict.keys())
|
|
return set(keys)
|
|
pattern_keys = NonDataProperty(get_pattern_keys)
|
|
|
|
@property
|
|
def include_keys(self):
|
|
return self.specified_keys.union(self.pattern_keys)
|
|
|
|
def keys(self):
|
|
return self.include_keys.intersection(self.dict.keys())
|
|
|
|
def values(self):
|
|
keys = self.keys()
|
|
values = map(self.dict.get, keys)
|
|
return values
|
|
|
|
def __getitem__(self, i):
|
|
if not i in self.include_keys:
|
|
return KeyError, i
|
|
return self.dict[i]
|
|
|
|
def items(self):
|
|
keys = self.keys()
|
|
values = map(self.dict.get, keys)
|
|
return zip(keys, values)
|
|
|
|
def __eq__(self, other):
|
|
return dict(self) == other
|
|
|
|
def __ne__(self, other):
|
|
return dict(self) != other
|
|
|
|
|
|
def dict_map(function, dictionary):
|
|
"""
|
|
dict_map is much like the built-in function map. It takes a dictionary
|
|
and applys a function to the values of that dictionary, returning a
|
|
new dictionary with the mapped values in the original keys.
|
|
|
|
>>> d = dict_map(lambda x:x+1, dict(a=1, b=2))
|
|
>>> d == dict(a=2,b=3)
|
|
True
|
|
"""
|
|
return dict((key, function(value)) for key, value in dictionary.items())
|
|
|
|
|
|
class RangeMap(dict):
|
|
"""
|
|
A dictionary-like object that uses the keys as bounds for a range.
|
|
Inclusion of the value for that range is determined by the
|
|
key_match_comparator, which defaults to less-than-or-equal.
|
|
A value is returned for a key if it is the first key that matches in
|
|
the sorted list of keys.
|
|
|
|
One may supply keyword parameters to be passed to the sort function used
|
|
to sort keys (i.e. cmp [python 2 only], keys, reverse) as sort_params.
|
|
|
|
Let's create a map that maps 1-3 -> 'a', 4-6 -> 'b'
|
|
|
|
>>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy
|
|
>>> r[1], r[2], r[3], r[4], r[5], r[6]
|
|
('a', 'a', 'a', 'b', 'b', 'b')
|
|
|
|
Even float values should work so long as the comparison operator
|
|
supports it.
|
|
|
|
>>> r[4.5]
|
|
'b'
|
|
|
|
But you'll notice that the way rangemap is defined, it must be open-ended
|
|
on one side.
|
|
|
|
>>> r[0]
|
|
'a'
|
|
>>> r[-1]
|
|
'a'
|
|
|
|
One can close the open-end of the RangeMap by using undefined_value
|
|
|
|
>>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'})
|
|
>>> r[0]
|
|
Traceback (most recent call last):
|
|
...
|
|
KeyError: 0
|
|
|
|
One can get the first or last elements in the range by using RangeMap.Item
|
|
|
|
>>> last_item = RangeMap.Item(-1)
|
|
>>> r[last_item]
|
|
'b'
|
|
|
|
.last_item is a shortcut for Item(-1)
|
|
|
|
>>> r[RangeMap.last_item]
|
|
'b'
|
|
|
|
Sometimes it's useful to find the bounds for a RangeMap
|
|
|
|
>>> r.bounds()
|
|
(0, 6)
|
|
|
|
RangeMap supports .get(key, default)
|
|
|
|
>>> r.get(0, 'not found')
|
|
'not found'
|
|
|
|
>>> r.get(7, 'not found')
|
|
'not found'
|
|
"""
|
|
def __init__(self, source, sort_params = {}, key_match_comparator = operator.le):
|
|
dict.__init__(self, source)
|
|
self.sort_params = sort_params
|
|
self.match = key_match_comparator
|
|
|
|
def __getitem__(self, item):
|
|
sorted_keys = sorted(self.keys(), **self.sort_params)
|
|
if isinstance(item, RangeMap.Item):
|
|
result = self.__getitem__(sorted_keys[item])
|
|
else:
|
|
key = self._find_first_match_(sorted_keys, item)
|
|
result = dict.__getitem__(self, key)
|
|
if result is RangeMap.undefined_value:
|
|
raise KeyError(key)
|
|
return result
|
|
|
|
def get(self, key, default=None):
|
|
"""
|
|
Return the value for key if key is in the dictionary, else default.
|
|
If default is not given, it defaults to None, so that this method
|
|
never raises a KeyError.
|
|
"""
|
|
try:
|
|
return self[key]
|
|
except KeyError:
|
|
return default
|
|
|
|
def _find_first_match_(self, keys, item):
|
|
is_match = lambda k: self.match(item, k)
|
|
matches = list(filter(is_match, keys))
|
|
if matches:
|
|
return matches[0]
|
|
raise KeyError(item)
|
|
|
|
def bounds(self):
|
|
sorted_keys = sorted(self.keys(), **self.sort_params)
|
|
return (
|
|
sorted_keys[RangeMap.first_item],
|
|
sorted_keys[RangeMap.last_item],
|
|
)
|
|
|
|
# some special values for the RangeMap
|
|
undefined_value = type(str('RangeValueUndefined'), (object,), {})()
|
|
class Item(int): pass
|
|
first_item = Item(0)
|
|
last_item = Item(-1)
|
|
|
|
|
|
__identity = lambda x: x
|
|
|
|
|
|
def sorted_items(d, key=__identity, reverse=False):
|
|
"""
|
|
Return the items of the dictionary sorted by the keys
|
|
|
|
>>> sample = dict(foo=20, bar=42, baz=10)
|
|
>>> tuple(sorted_items(sample))
|
|
(('bar', 42), ('baz', 10), ('foo', 20))
|
|
|
|
>>> reverse_string = lambda s: ''.join(reversed(s))
|
|
>>> tuple(sorted_items(sample, key=reverse_string))
|
|
(('foo', 20), ('bar', 42), ('baz', 10))
|
|
|
|
>>> tuple(sorted_items(sample, reverse=True))
|
|
(('foo', 20), ('baz', 10), ('bar', 42))
|
|
"""
|
|
# wrap the key func so it operates on the first element of each item
|
|
pairkey_key = lambda item: key(item[0])
|
|
return sorted(d.items(), key=pairkey_key, reverse=reverse)
|
|
|
|
|
|
class KeyTransformingDict(dict):
|
|
"""
|
|
A dict subclass that transforms the keys before they're used.
|
|
Subclasses may override the default transform_key to customize behavior.
|
|
"""
|
|
@staticmethod
|
|
def transform_key(key):
|
|
return key
|
|
|
|
def __init__(self, *args, **kargs):
|
|
super(KeyTransformingDict, self).__init__()
|
|
# build a dictionary using the default constructs
|
|
d = dict(*args, **kargs)
|
|
# build this dictionary using transformed keys.
|
|
for item in d.items():
|
|
self.__setitem__(*item)
|
|
|
|
def __setitem__(self, key, val):
|
|
key = self.transform_key(key)
|
|
super(KeyTransformingDict, self).__setitem__(key, val)
|
|
|
|
def __getitem__(self, key):
|
|
key = self.transform_key(key)
|
|
return super(KeyTransformingDict, self).__getitem__(key)
|
|
|
|
def __contains__(self, key):
|
|
key = self.transform_key(key)
|
|
return super(KeyTransformingDict, self).__contains__(key)
|
|
|
|
def __delitem__(self, key):
|
|
key = self.transform_key(key)
|
|
return super(KeyTransformingDict, self).__delitem__(key)
|
|
|
|
def get(self, key, *args, **kwargs):
|
|
key = self.transform_key(key)
|
|
return super(KeyTransformingDict, self).get(key, *args, **kwargs)
|
|
|
|
def setdefault(self, key, *args, **kwargs):
|
|
key = self.transform_key(key)
|
|
return super(KeyTransformingDict, self).setdefault(key, *args, **kwargs)
|
|
|
|
def pop(self, key, *args, **kwargs):
|
|
key = self.transform_key(key)
|
|
return super(KeyTransformingDict, self).pop(key, *args, **kwargs)
|
|
|
|
def matching_key_for(self, key):
|
|
"""
|
|
Given a key, return the actual key stored in self that matches.
|
|
Raise KeyError if the key isn't found.
|
|
"""
|
|
try:
|
|
return next(e_key for e_key in self.keys() if e_key == key)
|
|
except StopIteration:
|
|
raise KeyError(key)
|
|
|
|
|
|
class FoldedCaseKeyedDict(KeyTransformingDict):
|
|
"""
|
|
A case-insensitive dictionary (keys are compared as insensitive
|
|
if they are strings).
|
|
|
|
>>> d = FoldedCaseKeyedDict()
|
|
>>> d['heLlo'] = 'world'
|
|
>>> list(d.keys()) == ['heLlo']
|
|
True
|
|
>>> list(d.values()) == ['world']
|
|
True
|
|
>>> d['hello'] == 'world'
|
|
True
|
|
>>> 'hello' in d
|
|
True
|
|
>>> 'HELLO' in d
|
|
True
|
|
>>> print(repr(FoldedCaseKeyedDict({'heLlo': 'world'})).replace("u'", "'"))
|
|
{'heLlo': 'world'}
|
|
>>> d = FoldedCaseKeyedDict({'heLlo': 'world'})
|
|
>>> print(d['hello'])
|
|
world
|
|
>>> print(d['Hello'])
|
|
world
|
|
>>> list(d.keys())
|
|
['heLlo']
|
|
>>> d = FoldedCaseKeyedDict({'heLlo': 'world', 'Hello': 'world'})
|
|
>>> list(d.values())
|
|
['world']
|
|
>>> key, = d.keys()
|
|
>>> key in ['heLlo', 'Hello']
|
|
True
|
|
>>> del d['HELLO']
|
|
>>> d
|
|
{}
|
|
|
|
get should work
|
|
|
|
>>> d['Sumthin'] = 'else'
|
|
>>> d.get('SUMTHIN')
|
|
'else'
|
|
>>> d.get('OTHER', 'thing')
|
|
'thing'
|
|
>>> del d['sumthin']
|
|
|
|
setdefault should also work
|
|
|
|
>>> d['This'] = 'that'
|
|
>>> print(d.setdefault('this', 'other'))
|
|
that
|
|
>>> len(d)
|
|
1
|
|
>>> print(d['this'])
|
|
that
|
|
>>> print(d.setdefault('That', 'other'))
|
|
other
|
|
>>> print(d['THAT'])
|
|
other
|
|
|
|
Make it pop!
|
|
|
|
>>> print(d.pop('THAT'))
|
|
other
|
|
|
|
To retrieve the key in its originally-supplied form, use matching_key_for
|
|
|
|
>>> print(d.matching_key_for('this'))
|
|
This
|
|
"""
|
|
@staticmethod
|
|
def transform_key(key):
|
|
return jaraco.text.FoldedCase(key)
|
|
|
|
|
|
class DictAdapter(object):
|
|
"""
|
|
Provide a getitem interface for attributes of an object.
|
|
|
|
Let's say you want to get at the string.lowercase property in a formatted
|
|
string. It's easy with DictAdapter.
|
|
|
|
>>> import string
|
|
>>> print("lowercase is %(ascii_lowercase)s" % DictAdapter(string))
|
|
lowercase is abcdefghijklmnopqrstuvwxyz
|
|
"""
|
|
def __init__(self, wrapped_ob):
|
|
self.object = wrapped_ob
|
|
|
|
def __getitem__(self, name):
|
|
return getattr(self.object, name)
|
|
|
|
|
|
class ItemsAsAttributes(object):
|
|
"""
|
|
Mix-in class to enable a mapping object to provide items as
|
|
attributes.
|
|
|
|
>>> C = type(str('C'), (dict, ItemsAsAttributes), dict())
|
|
>>> i = C()
|
|
>>> i['foo'] = 'bar'
|
|
>>> i.foo
|
|
'bar'
|
|
|
|
Natural attribute access takes precedence
|
|
|
|
>>> i.foo = 'henry'
|
|
>>> i.foo
|
|
'henry'
|
|
|
|
But as you might expect, the mapping functionality is preserved.
|
|
|
|
>>> i['foo']
|
|
'bar'
|
|
|
|
A normal attribute error should be raised if an attribute is
|
|
requested that doesn't exist.
|
|
|
|
>>> i.missing
|
|
Traceback (most recent call last):
|
|
...
|
|
AttributeError: 'C' object has no attribute 'missing'
|
|
|
|
It also works on dicts that customize __getitem__
|
|
|
|
>>> missing_func = lambda self, key: 'missing item'
|
|
>>> C = type(str('C'), (dict, ItemsAsAttributes), dict(__missing__ = missing_func))
|
|
>>> i = C()
|
|
>>> i.missing
|
|
'missing item'
|
|
>>> i.foo
|
|
'missing item'
|
|
"""
|
|
def __getattr__(self, key):
|
|
try:
|
|
return getattr(super(ItemsAsAttributes, self), key)
|
|
except AttributeError as e:
|
|
# attempt to get the value from the mapping (return self[key])
|
|
# but be careful not to lose the original exception context.
|
|
noval = object()
|
|
def _safe_getitem(cont, key, missing_result):
|
|
try:
|
|
return cont[key]
|
|
except KeyError:
|
|
return missing_result
|
|
result = _safe_getitem(self, key, noval)
|
|
if result is not noval:
|
|
return result
|
|
# raise the original exception, but use the original class
|
|
# name, not 'super'.
|
|
message, = e.args
|
|
message = message.replace('super', self.__class__.__name__, 1)
|
|
e.args = message,
|
|
raise
|
|
|
|
|
|
def invert_map(map):
|
|
"""
|
|
Given a dictionary, return another dictionary with keys and values
|
|
switched. If any of the values resolve to the same key, raises
|
|
a ValueError.
|
|
|
|
>>> numbers = dict(a=1, b=2, c=3)
|
|
>>> letters = invert_map(numbers)
|
|
>>> letters[1]
|
|
'a'
|
|
>>> numbers['d'] = 3
|
|
>>> invert_map(numbers)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: Key conflict in inverted mapping
|
|
"""
|
|
res = dict((v,k) for k, v in map.items())
|
|
if not len(res) == len(map):
|
|
raise ValueError('Key conflict in inverted mapping')
|
|
return res
|
|
|
|
|
|
class IdentityOverrideMap(dict):
|
|
"""
|
|
A dictionary that by default maps each key to itself, but otherwise
|
|
acts like a normal dictionary.
|
|
|
|
>>> d = IdentityOverrideMap()
|
|
>>> d[42]
|
|
42
|
|
>>> d['speed'] = 'speedo'
|
|
>>> print(d['speed'])
|
|
speedo
|
|
"""
|
|
|
|
def __missing__(self, key):
|
|
return key
|
|
|
|
|
|
class DictStack(list, collections.Mapping):
|
|
"""
|
|
A stack of dictionaries that behaves as a view on those dictionaries,
|
|
giving preference to the last.
|
|
|
|
>>> stack = DictStack([dict(a=1, c=2), dict(b=2, a=2)])
|
|
>>> stack['a']
|
|
2
|
|
>>> stack['b']
|
|
2
|
|
>>> stack['c']
|
|
2
|
|
>>> stack.push(dict(a=3))
|
|
>>> stack['a']
|
|
3
|
|
>>> set(stack.keys()) == set(['a', 'b', 'c'])
|
|
True
|
|
>>> d = stack.pop()
|
|
>>> stack['a']
|
|
2
|
|
>>> d = stack.pop()
|
|
>>> stack['a']
|
|
1
|
|
"""
|
|
|
|
def keys(self):
|
|
return list(set(itertools.chain.from_iterable(c.keys() for c in self)))
|
|
|
|
def __getitem__(self, key):
|
|
for scope in reversed(self):
|
|
if key in scope: return scope[key]
|
|
raise KeyError(key)
|
|
|
|
push = list.append
|
|
|
|
|
|
class BijectiveMap(dict):
|
|
"""
|
|
A Bijective Map (two-way mapping).
|
|
|
|
Implemented as a simple dictionary of 2x the size, mapping values back
|
|
to keys.
|
|
|
|
Note, this implementation may be incomplete. If there's not a test for
|
|
your use case below, it's likely to fail, so please test and send pull
|
|
requests or patches for additional functionality needed.
|
|
|
|
|
|
>>> m = BijectiveMap()
|
|
>>> m['a'] = 'b'
|
|
>>> m == {'a': 'b', 'b': 'a'}
|
|
True
|
|
>>> print(m['b'])
|
|
a
|
|
|
|
>>> m['c'] = 'd'
|
|
>>> len(m)
|
|
2
|
|
|
|
Some weird things happen if you map an item to itself or overwrite a
|
|
single key of a pair, so it's disallowed.
|
|
|
|
>>> m['e'] = 'e'
|
|
Traceback (most recent call last):
|
|
ValueError: Key cannot map to itself
|
|
|
|
>>> m['d'] = 'e'
|
|
Traceback (most recent call last):
|
|
ValueError: Key/Value pairs may not overlap
|
|
|
|
>>> print(m.pop('d'))
|
|
c
|
|
|
|
>>> 'c' in m
|
|
False
|
|
|
|
>>> m = BijectiveMap(dict(a='b'))
|
|
>>> len(m)
|
|
1
|
|
>>> print(m['b'])
|
|
a
|
|
|
|
>>> m = BijectiveMap()
|
|
>>> m.update(a='b')
|
|
>>> m['b']
|
|
'a'
|
|
|
|
>>> del m['b']
|
|
>>> len(m)
|
|
0
|
|
>>> 'a' in m
|
|
False
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
super(BijectiveMap, self).__init__()
|
|
self.update(*args, **kwargs)
|
|
|
|
def __setitem__(self, item, value):
|
|
if item == value:
|
|
raise ValueError("Key cannot map to itself")
|
|
if (value in self or item in self) and self[item] != value:
|
|
raise ValueError("Key/Value pairs may not overlap")
|
|
super(BijectiveMap, self).__setitem__(item, value)
|
|
super(BijectiveMap, self).__setitem__(value, item)
|
|
|
|
def __delitem__(self, item):
|
|
self.pop(item)
|
|
|
|
def __len__(self):
|
|
return super(BijectiveMap, self).__len__() // 2
|
|
|
|
def pop(self, key, *args, **kwargs):
|
|
mirror = self[key]
|
|
super(BijectiveMap, self).__delitem__(mirror)
|
|
return super(BijectiveMap, self).pop(key, *args, **kwargs)
|
|
|
|
def update(self, *args, **kwargs):
|
|
# build a dictionary using the default constructs
|
|
d = dict(*args, **kwargs)
|
|
# build this dictionary using transformed keys.
|
|
for item in d.items():
|
|
self.__setitem__(*item)
|
|
|
|
|
|
class FrozenDict(collections.Mapping, collections.Hashable):
|
|
"""
|
|
An immutable mapping.
|
|
|
|
>>> a = FrozenDict(a=1, b=2)
|
|
>>> b = FrozenDict(a=1, b=2)
|
|
>>> a == b
|
|
True
|
|
|
|
>>> a == dict(a=1, b=2)
|
|
True
|
|
>>> dict(a=1, b=2) == a
|
|
True
|
|
|
|
>>> a['c'] = 3
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: 'FrozenDict' object does not support item assignment
|
|
|
|
>>> a.update(y=3)
|
|
Traceback (most recent call last):
|
|
...
|
|
AttributeError: 'FrozenDict' object has no attribute 'update'
|
|
|
|
Copies should compare equal
|
|
|
|
>>> copy.copy(a) == a
|
|
True
|
|
|
|
Copies should be the same type
|
|
|
|
>>> isinstance(copy.copy(a), FrozenDict)
|
|
True
|
|
|
|
FrozenDict supplies .copy(), even though collections.Mapping doesn't
|
|
demand it.
|
|
|
|
>>> a.copy() == a
|
|
True
|
|
>>> a.copy() is not a
|
|
True
|
|
"""
|
|
__slots__ = ['__data']
|
|
|
|
def __new__(cls, *args, **kwargs):
|
|
self = super(FrozenDict, cls).__new__(cls)
|
|
self.__data = dict(*args, **kwargs)
|
|
return self
|
|
|
|
# Container
|
|
def __contains__(self, key):
|
|
return key in self.__data
|
|
|
|
# Hashable
|
|
def __hash__(self):
|
|
return hash(tuple(sorted(self.__data.iteritems())))
|
|
|
|
# Mapping
|
|
def __iter__(self):
|
|
return iter(self.__data)
|
|
|
|
def __len__(self):
|
|
return len(self.__data)
|
|
|
|
def __getitem__(self, key):
|
|
return self.__data[key]
|
|
|
|
# override get for efficiency provided by dict
|
|
def get(self, *args, **kwargs):
|
|
return self.__data.get(*args, **kwargs)
|
|
|
|
# override eq to recognize underlying implementation
|
|
def __eq__(self, other):
|
|
if isinstance(other, FrozenDict):
|
|
other = other.__data
|
|
return self.__data.__eq__(other)
|
|
|
|
def copy(self):
|
|
"Return a shallow copy of self"
|
|
return copy.copy(self)
|
|
|
|
|
|
class Enumeration(ItemsAsAttributes, BijectiveMap):
|
|
"""
|
|
A convenient way to provide enumerated values
|
|
|
|
>>> e = Enumeration('a b c')
|
|
>>> e['a']
|
|
0
|
|
|
|
>>> e.a
|
|
0
|
|
|
|
>>> e[1]
|
|
'b'
|
|
|
|
>>> set(e.names) == set('abc')
|
|
True
|
|
|
|
>>> set(e.codes) == set(range(3))
|
|
True
|
|
|
|
>>> e.get('d') is None
|
|
True
|
|
|
|
Codes need not start with 0
|
|
|
|
>>> e = Enumeration('a b c', range(1, 4))
|
|
>>> e['a']
|
|
1
|
|
|
|
>>> e[3]
|
|
'c'
|
|
"""
|
|
def __init__(self, names, codes=None):
|
|
if isinstance(names, six.string_types):
|
|
names = names.split()
|
|
if codes is None:
|
|
codes = itertools.count()
|
|
super(Enumeration, self).__init__(zip(names, codes))
|
|
|
|
@property
|
|
def names(self):
|
|
return (key for key in self if isinstance(key, six.string_types))
|
|
|
|
@property
|
|
def codes(self):
|
|
return (self[name] for name in self.names)
|
|
|
|
|
|
class Everything(object):
|
|
"""
|
|
A collection "containing" every possible thing.
|
|
|
|
>>> 'foo' in Everything()
|
|
True
|
|
|
|
>>> import random
|
|
>>> random.randint(1, 999) in Everything()
|
|
True
|
|
"""
|
|
def __contains__(self, other):
|
|
return True
|
|
|
|
|
|
class InstrumentedDict(six.moves.UserDict):
|
|
"""
|
|
Instrument an existing dictionary with additional
|
|
functionality, but always reference and mutate
|
|
the original dictionary.
|
|
|
|
>>> orig = {'a': 1, 'b': 2}
|
|
>>> inst = InstrumentedDict(orig)
|
|
>>> inst['a']
|
|
1
|
|
>>> inst['c'] = 3
|
|
>>> orig['c']
|
|
3
|
|
>>> inst.keys() == orig.keys()
|
|
True
|
|
"""
|
|
def __init__(self, data):
|
|
six.moves.UserDict.__init__(self)
|
|
self.data = data
|