pax_global_header00006660000000000000000000000064140565103520014513gustar00rootroot0000000000000052 comment=6733c0b338a978904f8feca3bbe851f54a8ca983 orderedmultidict-1.0.1/000077500000000000000000000000001405651035200150555ustar00rootroot00000000000000orderedmultidict-1.0.1/.gitignore000066400000000000000000000001151405651035200170420ustar00rootroot00000000000000*~ .#* \#* .tox/ dist/ .eggs/ build/ *.pyc *.pyo *.egg *.egg-info .mypy_cacheorderedmultidict-1.0.1/.travis.yml000066400000000000000000000007671405651035200172000ustar00rootroot00000000000000language: python sudo: false matrix: include: - env: TOXENV=codestyle - python: 2.6 env: TOXENV=py26 dist: trusty - python: 2.7 env: TOXENV=py27 - python: 3.4 env: TOXENV=py34 - python: 3.5 env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 - python: 3.7 env: TOXENV=py37 sudo: true dist: xenial - python: pypy env: TOXENV=pypy install: travis_retry pip install tox script: tox notifications: email: false orderedmultidict-1.0.1/API.md000066400000000000000000000316311405651035200160140ustar00rootroot00000000000000# omdict API ### Nomenclature Many of omdict's methods contain the word __list__ or __all__. __list__ in a method\ name indicates that method interacts with a list of values instead of a\ single value. __all__ in a method name indicates that method interacts with\ the ordered list of all items, including multiple items with the same key. Here's an example illustrating __getlist(key, default=[])__, a __list__ method, and\ __allitems()__, an __all__ method. ```python >>> from orderedmultidict import omdict >>> omd = omdict([(1,1), (2,2), (1,11)]) >>> omd.items() [(1, 1), (2, 2)] >>> omd.allitems() [(1, 1), (2, 2), (1, 11)] >>> omd.get(1) 1 >>> omd.getlist(1) [1, 11] ``` So __list__ denotes a list of values, and __all__ denotes all items. Simple. ### Method parity with dict All [dict](http://docs.python.org/library/stdtypes.html#dict) methods behave identically on omdict objects (__pop()__,\ __setdefault()__, __clear()__, etc) ### Initialization and Updates omdict objects can be initialized from a dictionary or a list of key:value\ items. ```python >>> omd = omdict() >>> omd.allitems() [] >>> omd = omdict({1:1, 2:2, 3:3}) >>> omd.allitems() [(1, 1), (2, 2), (3, 3)] >>> omd = omdict([(1,1), (2,2), (3,3), (1,1)]) >>> omd.allitems() [(1, 1), (2, 2), (3, 3), (1, 1)] ``` __load(mapping)__ can be used at any time to reinitialize an omdict. ```python >>> omd.load({4:4, 5:5}) >>> omd.allitems() [(4, 4), (5, 5)] >>> omd = omdict([(1,1), (2,2), (3,3)]) >>> omd.allitems() [(1, 1), (2, 2), (3, 3)] >>> omd.load([(6,6), (6,6)]) >>> omd.allitems() [(6, 6), (6, 6)] ``` __update([mapping])__ updates the dictionary with items from __mapping__, one\ item per key like [dict.update([mapping])](http://docs.python.org/library/stdtypes.html#dict.update). __updateall([mapping])__ updates\ the dictionary with all items from __mapping__. Key order is preserved -\ existing keys are updated with values from __mapping__ before any new\ items are added. ```python >>> omd = omdict() >>> omd.update([(1,1), (2,2), (1,11), (2,22)]) >>> omd.items() [(1, 11), (2, 22)] >>> omd.allitems() [(1, 11), (2, 22)] >>> omd.updateall([(2,'replaced'), (1,'replaced'), (2,'added'), (1,'added')]) >>> omd.allitems() [(1, 'replaced'), (2, 'replaced'), (2, 'added'), (1, 'added')] ``` ### Getters, Setters, and Adders __omd[key]__ behaves identically to [dict[key]](http://docs.python.org/library/stdtypes.html#dict). If __key__ has multiple values, only\ its first value is returned. ```python >>> omd = omdict([(1,1), (1,'not me')]) >>> omd[1] 1 ``` __omd[key] = value__ behaves identically to [dict[key] = value](http://docs.python.org/library/stdtypes.html#dict). If __key__ has\ multiple values, they will all be deleted and replaced with __value__. ```python >>> omd = omdict([(1,'deleted'), (1,'deleted')]) >>> omd[1] = 1 >>> omd[1] 1 ``` __del omd[key]__ behaves identically to [del dict[key]](http://docs.python.org/library/stdtypes.html#dict). If __key__ has multiple\ values, all of them will be deleted. ```python >>> omd = omdict([(1,1), (1,11)]) >>> del omd[1] >>> omd.allitems() [] ``` __get(key, default=None)__ behaves identically to\ [dict.get(key, default=None)](http://docs.python.org/library/stdtypes.html#dict.get). If __key__ has multiple values, only its first value\ is returned. ```python >>> omd = omdict([(1,1), (1,2)]) >>> omd.get(1) 1 >>> omd.get(404, 'sup') 'sup' ``` __getlist(key, default=[])__ is like __get(key, default=None)__ except it returns the\ list of values assocaited with __key__. ```python >>> omd = omdict([(1,1), (1,11), (2,2)]) >>> omd.getlist(1) [1, 11] >>> omd.getlist(2) [2] >>> omd.getlist(404, 'sup') 'sup' ``` __set(key, value=None)__ sets __key__'s value to __value__. Identical in function to\ `omd[key] = value`. Returns the omdict object for method chaining. ```python >>> omd = omdict([(1,1), (1,11), (1,111)]) >>> omd.set(1, 1) >>> omd.getlist(1) [1] >>> omd.set(1, 11).set(2, 2) >>> omd.allitems() [(1, 11), (2, 2)] ``` __setlist(key, values=[])__ sets __key__'s list of values to __values__. Returns the\ omdict object for method chaining. ```python >>> omd = omdict([(1,1), (2,2)]) >>> omd.setlist(1, ['replaced', 'appended']) >>> omd.allitems() [(1, 'replaced'), (2, 2), (1, 'appended')] >>> omd.setlist(1, ['onlyme']) >>> omd.allitems() [(1, 'onlyme'), (2, 2)] ``` __setdefault(key, default=None)__ behaves identically to\ [dict.setdefault(key, default=None)](http://docs.python.org/library/stdtypes.html#dict.setdefault). ```python >>> omd = omdict([(1,1)]) >>> omd.setdefault(1) 1 >>> omd.setdefault(2, None) >>> omd.allitems() [(1, 1), (2, None)] ``` __setdefaultlist(key, defaultlist=[None])__ is like\ __setdefault(key, default=None)__ except a list of values for __key__ is adopted. If\ __defaultlist__ isn't provided, __key__'s value becomes None. ```python >>> omd = omdict([(1,1)]) >>> omd.setdefaultlist(1) [1] >>> omd.setdefaultlist(2, [2, 22]) [2, 22] >>> omd.allitems() [(1, 1), (2, 2), (2, 22)] >>> omd.setdefaultlist(3) [None] >>> print omd[3] None ``` __add(key, value=None)__ adds __value__ to the list of values for __key__. Returns\ the omdict object for method chaining. ```python >>> omd = omdict() >>> omd.add(1, 1) >>> omd.allitems() [(1, 1)] >>> omd.add(1, 11).add(2, 2) >>> omd.allitems() [(1, 1), (1, 11), (2, 2)] ``` __addlist(key, valuelist=[])__ adds the values in __valuelist__ to the list of values\ for __key__. Returns the omdict object for method chaining. ```python >>> omd = omdict([(1,1)]) >>> omd.addlist(1, [11, 111]) >>> omd.allitems() [(1, 1), (1, 11), (1, 111)] >>> omd.addlist(2, [2]).addlist(3, [3, 33]) >>> omd.allitems() [(1, 1), (1, 11), (1, 111), (2, 2), (3, 3), (3, 33)] ``` ### Groups and Group Iteration __items([key])__ behaves identically to [dict.items()](http://docs.python.org/library/stdtypes.html#dict.items) except an optional __key__\ parameter has been added. If __key__ is provided, only items with key __key__\ are returned. __iteritems([key])__ returns an iterator over `items(key)`. KeyError\ is raised if __key__ is provided but not in the dictionary. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.items() [(1, 1), (2, 2), (3, 3)] >>> omd.items(1) [(1, 1), (1, 11), (1, 111)] ``` __keys()__ behaves identically to [dict.keys()](http://docs.python.org/library/stdtypes.html#dict.keys). __iterkeys()__ returns an iterator\ over keys(). __values([key])__ behaves identically to [dict.values()](http://docs.python.org/library/stdtypes.html#dict.values) except an optional __key__\ parameter has been added. If __key__ is provided, only the values for __key__ are\ returned. __itervalues([key])__ returns an iterator over `values(key)`. KeyError\ is raised if __key__ is provided but not in the dictionary. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.values() [1, 2, 3] >>> omd.values(1) [1, 11, 111] ``` __lists()__ returns a list comprised of the lists of values associated with each\ dictionary key. __iterlists()__ returns an iterator over __lists()__. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.lists() [[1, 11, 111], [2], [3]] ``` __listitems()__ returns a list of key:valuelist items. __iterlistitems()__ returns an\ iterator over __listitems()__. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3), (2,22)]) >>> omd.listitems() [(1, [1, 11, 111]), (2, [2, 22]), (3, [3])] ``` __allitems([key])__ returns a list of every item in the dictionary, including\ multiple items with the same key. If __key__ is provided and in the dictionary,\ only items with key __key__ are returned . KeyError is raised if __key__ is\ provided and not in the dictionary. __iterallitems([key])__ returns an iterator\ over __allitems(key)__. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.allitems() [(1, 1), (1, 11), (1, 111), (2, 2), (3, 3)] ``` __allkeys()__ returns a list of the keys of every item in the dictionary.\ __iterallkeys()__ returns an iterator over __allkeys()__. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.allkeys() [1, 1, 1, 2, 3] ``` __allvalues()__ returns a list of the values of every item in the dictionary.\ __iterallvalues()__ returns an iterator over __allvalues()__. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.allvalues() [1, 11, 111, 2, 3] ``` ### Pops __pop(key[, default])__ behaves identically to [dict.pop(key[, default])](http://docs.python.org/library/stdtypes.html#dict.pop). If __key__\ has multiple values, the first value is returned but all items with key __key__\ are popped. KeyError is raised if __default__ isn't provided and __key__ isn't in\ the dictionary. ```python >>> omd = omdict([(1,1), (2,2), (1,11)]) >>> omd.pop(1) 1 >>> omd.allitems() [(2, 2)] ``` __poplist(key[, default])__ is like `pop(key[, default])` except it returns the list of\ values for __key__. KeyError is raised if __default__ isn't provided and __key__ isn't in\ the dictionary. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.poplist(1) [1, 11, 111] >>> omd.allitems() [(2, 2), (3, 3)] >>> omd.poplist(2) [2] >>> omd.allitems() [(3, 3)] >>> omd.poplist('nonexistent key', 'sup') 'sup' ``` __popvalue(key[, value, default], last=True)__ pops a value for __key__. If __value__ is not provided, the first or last value for __key__ is popped and\ returned. If __value__ is provided, the first or last (__key__,__value__) item is popped and __value__\ is returned. If __key__ no longer has any values after a __popvalue()__ call, __key__ is removed\ from the dictionary. __default__ is returned if provided and __key__ isn't in the\ dictionary. KeyError is raised if __default__ isn't provided and __key__ isn't in the\ dictionary. ValueError is raised if __value__ is provided but isn't a value for\ __key__. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3), (2,22)]) >>> omd.popvalue(1) 111 >>> omd.allitems() [(1, 1), (1, 11), (2, 2), (3, 3), (2, 22)] >>> omd.popvalue(1, last=False) 1 >>> omd.allitems() [(1, 11), (2, 2), (3, 3), (2, 22)] >>> omd.popvalue(2, 2) 2 >>> omd.allitems() [(1, 11), (3, 3), (2, 22)] >>> omd.popvalue(1, 11) 11 >>> omd.allitems() [(3, 3), (2, 22)] >>> omd.popvalue('not a key', default='sup') 'sup' ``` __popitem(fromall=False, last=True)__ pops and returns a key:value item. If __fromall__ is False, `items()[0]` is popped if __last__ is False or `items()[-1]` is\ popped if __last__ is True. All remaining items with the same key are\ removed. If __fromall__ is True, `allitems()[0]` is popped if __last__ is False or `allitems()[-1]` is\ popped if __last__ is True. No other remaining items are removed, even if\ they have the same key. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.popitem() (3, 3) >>> omd.popitem(fromall=False, last=False) (1, 1) >>> omd.popitem(fromall=False, last=False) (2, 2) >>> omd.allitems() [] >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.popitem(fromall=True, last=False) (1, 1) >>> omd.popitem(fromall=True, last=False) (1, 11) >>> omd.popitem(fromall=True, last=True) (3, 3) >>> omd.popitem(fromall=True, last=False) (1, 111) ``` __poplistitem([key], last=True)__ pops and returns a key:valuelist item\ comprised of a key and that key's list of values. If __last__ is False, a\ key:valuelist item comprised of `keys()[0]` and its list of values is popped\ and returned. If __last__ is True, a key:valuelist item comprised of `keys()[-1]`\ and its list of values is popped and returned. KeyError is raised if the\ dictionary is empty or if __key__ is provided and not in the dictionary. ```python >>> omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) >>> omd.poplistitem(last=True) (3, [3]) >>> omd.poplistitem(last=False) (1, [1, 11, 111]) ``` ### Miscellaneous __copy()__ returns a shallow copy of the dictionary. ```python >>> omd = omdict([(1,1), (1,11), (2,2), (3,3)]) >>> copy = omd.copy() >>> omd == copy True >>> isinstance(copy, omdict) True ``` __clear()__ clears all items. ```python >>> omd = omdict([(1,1), (1,11), (2,2), (3,3)]) >>> omd.clear() >>> omd.allitems() [] ``` __len(omd)__ returns the number of keys in the dictionary, identical to\ [len(dict)](http://docs.python.org/library/stdtypes.html#dict). ```python >>> omd = omdict([(1, 1), (2, 2), (1, 11)]) >>> len(omd) 2 ``` __size()__ returns the total number of items in the dictionary. ```python >>> omd = omdict([(1, 1), (1, 11), (2, 2), (1, 111)]) >>> omd.size() 4 ``` __reverse()__ reverses the order of all items in the dictionary and returns the\ omdict object for method chaining. ```python >>> omd = omdict([(1, 1), (2, 2), (3, 3)]) >>> omd.allitems() [(1, 1), (2, 2), (3, 3)] >>> omd.reverse() >>> omd.allitems() [(3, 3), (2, 2), (1, 1)] ``` __fromkeys(keys[, value])__ behaves identically to [dict.fromkeys(key[, value])](http://docs.python.org/library/stdtypes.html#dict.fromkeys). __has_key(key)__ behaves identically to [dict.has_key(key)](http://docs.python.org/library/stdtypes.html#dict.has_key), but use\ `key in omd` instead of `omd.has_key(key)` where possible. orderedmultidict-1.0.1/LICENSE.md000066400000000000000000000023671405651035200164710ustar00rootroot00000000000000Build Amazing Things. *** ### Unlicense This is free and unencumbered software released into the public\ domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute\ this software, either in source code form or as a compiled binary, for any\ purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of\ this software dedicate any and all copyright interest in the software to the\ public domain. We make this dedication for the benefit of the public at\ large and to the detriment of our heirs and successors. We intend this\ dedication to be an overt act of relinquishment in perpetuity of all\ present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF\ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\ TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\ SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT\ OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\ SOFTWARE. For more information, please refer to orderedmultidict-1.0.1/MANIFEST.in000066400000000000000000000000431405651035200166100ustar00rootroot00000000000000include LICENSE.md README.md API.mdorderedmultidict-1.0.1/README.md000066400000000000000000000044021405651035200163340ustar00rootroot00000000000000

orderedmultidict
orderedmultidict

### omdict is an ordered multivalue dictionary that retains
method parity with Python's [dict](http://docs.python.org/library/stdtypes.html#dict) and helps power [furl](https://github.com/gruns/furl). A multivalue dictionary is a dictionary that can store multiple values per\ key. An ordered multivalue dictionary is a multivalue dictionary that\ retains the order of insertions and deletions. orderedmultidict is well tested, [Unlicensed](http://unlicense.org/) in the public domain,\ supports Python 2, Python 3, PyPy2, and PyPy3. Code time: omdict can store multiple values per key. ```python >>> from orderedmultidict import omdict >>> omd = omdict() >>> omd[1] = 1 >>> omd[1] 1 >>> omd.add(1, 11) >>> omd.getlist(1) [1, 11] >>> omd.addlist(1, [111, 1111]) >>> omd.getlist(1) [1, 11, 111, 1111] >>> omd.allitems() [(1, 1), (1, 11), (1, 111), (1, 1111)] ``` omdict retains insertion and deletion order. ```python >>> omd = omdict() >>> omd[2] = 2 >>> omd[1] = 1 >>> omd.items() [(2, 2), (1, 1)] >>> omd[2] = 'sup' >>> omd.items() [(2, 'sup'), (1, 1)] ``` Method parity with dict is retained; omdict can be a drop-in replacement. ```python >>> d, omd = dict(), omdict() >>> d.update([(1,1), (1,11), (2,2), (2,22)]) >>> omd.update([(1,1), (1,11), (2,2), (2,22)]) >>> d[1], omd[1] (11, 11) >>> d[3] = 3 >>> omd[3] = 3 >>> d.get(3), omd.get(3) (3, 3) >>> d.items() == omd.items() True ``` ### API See all of omdict's methods, with examples, in omdict's API document,\ [API.md](https://github.com/gruns/orderedmultidict/blob/master/API.md). ### Installation Installing orderedmultidict with pip is easy. ``` $ pip install orderedmultidict ``` orderedmultidict-1.0.1/logo.svg000066400000000000000000000124141405651035200165400ustar00rootroot00000000000000 image/svg+xml orderedmultidict-1.0.1/mypy.ini000066400000000000000000000001361405651035200165540ustar00rootroot00000000000000[mypy] check_untyped_defs = True no_implicit_optional = True [mypy-setup] ignore_errors = Trueorderedmultidict-1.0.1/orderedmultidict/000077500000000000000000000000001405651035200204205ustar00rootroot00000000000000orderedmultidict-1.0.1/orderedmultidict/__init__.py000066400000000000000000000011551405651035200225330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # omdict - Ordered Multivalue Dictionary. # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) # import sys from os.path import dirname, join as pjoin # There's no typing module until 3.5 if sys.version_info >= (3, 5): from typing import Dict from .orderedmultidict import * # noqa # Import all variables in __version__.py without explicit imports. meta = {} # type: Dict with open(pjoin(dirname(__file__), '__version__.py')) as f: exec(f.read(), meta) globals().update(dict((k, v) for k, v in meta.items() if k not in globals())) orderedmultidict-1.0.1/orderedmultidict/__version__.py000066400000000000000000000006471405651035200232620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # omdict - Ordered Multivalue Dictionary. # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) # __title__ = 'orderedmultidict' __version__ = '1.0.1' __license__ = 'Unlicense' __author__ = 'Ansgar Grunseid' __contact__ = 'grunseid@gmail.com' __description__ = 'Ordered Multivalue Dictionary' __url__ = 'https://github.com/gruns/orderedmultidict' orderedmultidict-1.0.1/orderedmultidict/itemlist.py000066400000000000000000000106031405651035200226240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # omdict - Ordered Multivalue Dictionary. # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) # from __future__ import absolute_import from six.moves import zip_longest _absent = object() # Marker that means no parameter was provided. class itemnode(object): """ Dictionary key:value items wrapped in a node to be members of itemlist, the doubly linked list defined below. """ def __init__(self, prev=None, next=None, key=_absent, value=_absent): self.prev = prev self.next = next self.key = key self.value = value class itemlist(object): """ Doubly linked list of itemnodes. This class is used as the key:value item storage of orderedmultidict. Methods below were only added as needed for use with orderedmultidict, so some otherwise common list methods may be missing. """ def __init__(self, items=[]): self.root = itemnode() self.root.next = self.root.prev = self.root self.size = 0 for key, value in items: self.append(key, value) def append(self, key, value): tail = self.root.prev if self.root.prev is not self.root else self.root node = itemnode(tail, self.root, key=key, value=value) tail.next = node self.root.prev = node self.size += 1 return node def removenode(self, node): node.prev.next = node.next node.next.prev = node.prev self.size -= 1 return self def clear(self): for node, key, value in self: self.removenode(node) return self def items(self): return list(self.iteritems()) def keys(self): return list(self.iterkeys()) def values(self): return list(self.itervalues()) def iteritems(self): for node, key, value in self: yield key, value def iterkeys(self): for node, key, value in self: yield key def itervalues(self): for node, key, value in self: yield value def reverse(self): for node, key, value in self: node.prev, node.next = node.next, node.prev self.root.prev, self.root.next = self.root.next, self.root.prev return self def __len__(self): return self.size def __iter__(self): current = self.root.next while current and current is not self.root: # Record current.next here in case current.next changes after the # yield and before we return for the next iteration. For example, # methods like reverse() will change current.next() before yield # gets executed again. nextnode = current.next yield current, current.key, current.value current = nextnode def __contains__(self, item): """ Params: item: Can either be a (key,value) tuple or an itemnode reference. """ node = key = value = _absent if hasattr(item, '__len__') and callable(item.__len__): if len(item) == 2: key, value = item elif len(item) == 3: node, key, value = item else: node = item if node is not _absent or _absent not in [key, value]: for selfnode, selfkey, selfvalue in self: if ((node is _absent and key == selfkey and value == selfvalue) or (node is not _absent and node == selfnode)): return True return False def __getitem__(self, index): # Only support direct access to the first or last element, as this is # all orderedmultidict needs for now. if index == 0 and self.root.next is not self.root: return self.root.next elif index == -1 and self.root.prev is not self.root: return self.root.prev raise IndexError(index) def __delitem__(self, index): self.removenode(self[index]) def __eq__(self, other): for (n1, key1, value1), (n2, key2, value2) in zip_longest(self, other): if key1 != key2 or value1 != value2: return False return True def __ne__(self, other): return not self.__eq__(other) def __nonzero__(self): return self.size > 0 def __str__(self): return '[%s]' % self.items() orderedmultidict-1.0.1/orderedmultidict/orderedmultidict.py000066400000000000000000000722071405651035200243450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # omdict - Ordered Multivalue Dictionary. # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) # from __future__ import absolute_import import sys from itertools import chain import six from six.moves import map, zip_longest from .itemlist import itemlist # There's no typing module until 3.5 if sys.version_info >= (3, 5): from typing import Iterable, Tuple, Any, List, Dict if six.PY2: from collections import MutableMapping else: from collections.abc import MutableMapping try: # Python 2.7 and later. from collections import OrderedDict as odict # type: ignore except ImportError: # Python 2.6 and earlier. from ordereddict import OrderedDict as odict # type: ignore _absent = object() # Marker that means no parameter was provided. _items_attr = 'items' if sys.version_info[0] >= 3 else 'iteritems' def callable_attr(obj, attr): return hasattr(obj, attr) and callable(getattr(obj, attr)) # # TODO(grun): Create a subclass of list that values(), getlist(), allitems(), # etc return that the user can manipulate directly to control the omdict() # object. # # For example, users should be able to do things like # # omd = omdict([(1,1), (1,11)]) # omd.values(1).append('sup') # omd.allitems() == [(1,1), (1,11), (1,'sup')] # omd.values(1).remove(11) # omd.allitems() == [(1,1), (1,'sup')] # omd.values(1).extend(['two', 'more']) # omd.allitems() == [(1,1), (1,'sup'), (1,'two'), (1,'more')] # # or # # omd = omdict([(1,1), (1,11)]) # omd.allitems().extend([(2,2), (2,22)]) # omd.allitems() == [(1,1), (1,11), (2,2), (2,22)]) # # or # # omd = omdict() # omd.values(1) = [1, 11] # omd.allitems() == [(1,1), (1,11)] # omd.values(1) = list(map(lambda i: i * -10, omd.values(1))) # omd.allitems() == [(1,-10), (1,-110)] # omd.allitems() = filter(lambda (k,v): v > -100, omd.allitems()) # omd.allitems() == [(1,-10)] # # etc. # # To accomplish this, subclass list in such a manner that each list element is # really a two tuple, where the first tuple value is the actual value and the # second tuple value is a reference to the itemlist node for that value. Users # only interact with the first tuple values, the actual values, but behind the # scenes when an element is modified, deleted, inserted, etc, the according # itemlist nodes are modified, deleted, inserted, etc accordingly. In this # manner, users can manipulate omdict objects directly through direct list # manipulation. # # Once accomplished, some methods become redundant and should be removed in # favor of the more intuitive direct value list manipulation. Such redundant # methods include getlist() (removed in favor of values()?), addlist(), and # setlist(). # # With the removal of many of the 'list' methods, think about renaming all # remaining 'list' methods to 'values' methods, like poplist() -> popvalues(), # poplistitem() -> popvaluesitem(), etc. This would be an easy switch for most # methods, but wouldn't fit others so well. For example, iterlists() would # become itervalues(), a name extremely similar to iterallvalues() but quite # different in function. # class omdict(MutableMapping): """ Ordered Multivalue Dictionary. A multivalue dictionary is a dictionary that can store multiple values per key. An ordered multivalue dictionary is a multivalue dictionary that retains the order of insertions and deletions. Internally, items are stored in a doubly linked list, self._items. A dictionary, self._map, is also maintained and stores an ordered list of linked list node references, one for each value associated with that key. Standard dict methods interact with the first value associated with a given key. This means that omdict retains method parity with dict, and a dict object can be replaced with an omdict object and all interaction will behave identically. All dict methods that retain parity with omdict are: get(), setdefault(), pop(), popitem(), clear(), copy(), update(), fromkeys(), len() __getitem__(), __setitem__(), __delitem__(), __contains__(), items(), keys(), values(), iteritems(), iterkeys(), itervalues(), Optional parameters have been added to some dict methods, but because the added parameters are optional, existing use remains unaffected. An optional parameter has been added to these methods: items(), values(), iteritems(), itervalues() New methods have also been added to omdict. Methods with 'list' in their name interact with lists of values, and methods with 'all' in their name interact with all items in the dictionary, including multiple items with the same key. The new omdict methods are: load(), size(), reverse(), getlist(), add(), addlist(), set(), setlist(), setdefaultlist(), poplist(), popvalue(), popvalues(), popitem(), poplistitem(), allitems(), allkeys(), allvalues(), lists(), listitems(), iterallitems(), iterallkeys(), iterallvalues(), iterlists(), iterlistitems() Explanations and examples of the new methods above can be found in the function comments below and online at https://github.com/gruns/orderedmultidict Additional omdict information and documentation can also be found at the above url. """ def __init__(self, *args, **kwargs): # Doubly linked list of itemnodes. Each itemnode stores a key:value # item. self._items = itemlist() # Ordered dictionary of keys and itemnode references. Each itemnode # reference points to one of that keys values. self._map = odict() self.load(*args, **kwargs) def load(self, *args, **kwargs): """ Clear all existing key:value items and import all key:value items from . If multiple values exist for the same key in , they are all be imported. Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.load([(4,4), (4,44), (5,5)]) omd.allitems() == [(4,4), (4,44), (5,5)] Returns: . """ self.clear() self.updateall(*args, **kwargs) return self def copy(self): return self.__class__(self.allitems()) def clear(self): self._map.clear() self._items.clear() def size(self): """ Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.size() == 5 Returns: Total number of items, including multiple items with the same key. """ return len(self._items) @classmethod def fromkeys(cls, iterable, value=None): return cls([(key, value) for key in iterable]) def has_key(self, key): return key in self def update(self, *args, **kwargs): self._update_updateall(True, *args, **kwargs) def updateall(self, *args, **kwargs): """ Update this dictionary with the items from , replacing existing key:value items with shared keys before adding new key:value items. Example: omd = omdict([(1,1), (2,2)]) omd.updateall([(2,'two'), (1,'one'), (2,222), (1,111)]) omd.allitems() == [(1, 'one'), (2, 'two'), (2, 222), (1, 111)] Returns: . """ self._update_updateall(False, *args, **kwargs) return self def _update_updateall(self, replace_at_most_one, *args, **kwargs): # Bin the items in and into or # . Items in are new values to replace old # values for a given key, and items in are new items to be # added. replacements, leftovers = dict(), [] # type: Tuple[Dict, List] for mapping in chain(args, [kwargs]): self._bin_update_items( self._items_iterator(mapping), replace_at_most_one, replacements, leftovers) # First, replace existing values for each key. for key, values in six.iteritems(replacements): self.setlist(key, values) # Then, add the leftover items to the end of the list of all items. for key, value in leftovers: self.add(key, value) def _bin_update_items(self, items, replace_at_most_one, replacements, leftovers): """ are modified directly, ala pass by reference. """ for key, value in items: # If there are existing items with key that have yet to be # marked for replacement, mark that item's value to be replaced by # by appending it to . if key in self and key not in replacements: replacements[key] = [value] elif (key in self and not replace_at_most_one and len(replacements[key]) < len(self.values(key))): replacements[key].append(value) else: if replace_at_most_one: replacements[key] = [value] else: leftovers.append((key, value)) def _items_iterator(self, container): cont = container iterator = iter(cont) if callable_attr(cont, 'iterallitems'): iterator = cont.iterallitems() elif callable_attr(cont, 'allitems'): iterator = iter(cont.allitems()) elif callable_attr(cont, 'iteritems'): iterator = cont.iteritems() elif callable_attr(cont, 'items'): iterator = iter(cont.items()) return iterator def get(self, key, default=None): if key in self: return self._map[key][0].value return default def getlist(self, key, default=[]): """ Returns: The list of values for if is in the dictionary, else . If is not provided, an empty list is returned. """ if key in self: return [node.value for node in self._map[key]] return default def setdefault(self, key, default=None): if key in self: return self[key] self.add(key, default) return default def setdefaultlist(self, key, defaultlist=[None]): """ Similar to setdefault() except is a list of values to set for . If already exists, its existing list of values is returned. If isn't a key and is an empty list, [], no values are added for and will not be added as a key. Returns: List of 's values if exists in the dictionary, otherwise . """ if key in self: return self.getlist(key) self.addlist(key, defaultlist) return defaultlist def add(self, key, value=None): """ Add to the list of values for . If is not in the dictionary, then is added as the sole value for . Example: omd = omdict() omd.add(1, 1) # omd.allitems() == [(1,1)] omd.add(1, 11) # omd.allitems() == [(1,1), (1,11)] omd.add(2, 2) # omd.allitems() == [(1,1), (1,11), (2,2)] Returns: . """ self._map.setdefault(key, []) node = self._items.append(key, value) self._map[key].append(node) return self def addlist(self, key, valuelist=[]): """ Add the values in to the list of values for . If is not in the dictionary, the values in become the values for . Example: omd = omdict([(1,1)]) omd.addlist(1, [11, 111]) omd.allitems() == [(1, 1), (1, 11), (1, 111)] omd.addlist(2, [2]) omd.allitems() == [(1, 1), (1, 11), (1, 111), (2, 2)] Returns: . """ for value in valuelist: self.add(key, value) return self def set(self, key, value=None): """ Sets 's value to . Identical in function to __setitem__(). Returns: . """ self[key] = value return self def setlist(self, key, values): """ Sets 's list of values to . Existing items with key are first replaced with new values from . Any remaining old items that haven't been replaced with new values are deleted, and any new values from that don't have corresponding items with to replace are appended to the end of the list of all items. If values is an empty list, [], is deleted, equivalent in action to del self[]. Example: omd = omdict([(1,1), (2,2)]) omd.setlist(1, [11, 111]) omd.allitems() == [(1,11), (2,2), (1,111)] omd = omdict([(1,1), (1,11), (2,2), (1,111)]) omd.setlist(1, [None]) omd.allitems() == [(1,None), (2,2)] omd = omdict([(1,1), (1,11), (2,2), (1,111)]) omd.setlist(1, []) omd.allitems() == [(2,2)] Returns: . """ if not values and key in self: self.pop(key) else: it = zip_longest( list(self._map.get(key, [])), values, fillvalue=_absent) for node, value in it: if node is not _absent and value is not _absent: node.value = value elif node is _absent: self.add(key, value) elif value is _absent: self._map[key].remove(node) self._items.removenode(node) return self def removevalues(self, key, values): """ Removes all from the values of . If has no remaining values after removevalues(), the key is popped. Example: omd = omdict([(1, 1), (1, 11), (1, 1), (1, 111)]) omd.removevalues(1, [1, 111]) omd.allitems() == [(1, 11)] Returns: . """ self.setlist(key, [v for v in self.getlist(key) if v not in values]) return self def pop(self, key, default=_absent): if key in self: return self.poplist(key)[0] elif key not in self._map and default is not _absent: return default raise KeyError(key) def poplist(self, key, default=_absent): """ If is in the dictionary, pop it and return its list of values. If is not in the dictionary, return . KeyError is raised if is not provided and is not in the dictionary. Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.poplist(1) == [1, 11, 111] omd.allitems() == [(2,2), (3,3)] omd.poplist(2) == [2] omd.allitems() == [(3,3)] Raises: KeyError if isn't in the dictionary and isn't provided. Returns: List of 's values. """ if key in self: values = self.getlist(key) del self._map[key] for node, nodekey, nodevalue in self._items: if nodekey == key: self._items.removenode(node) return values elif key not in self._map and default is not _absent: return default raise KeyError(key) def popvalue(self, key, value=_absent, default=_absent, last=True): """ If is provided, pops the first or last (key,value) item in the dictionary if is in the dictionary. If is not provided, pops the first or last value for if is in the dictionary. If no longer has any values after a popvalue() call, is removed from the dictionary. If isn't in the dictionary and was provided, return default. KeyError is raised if is not provided and is not in the dictionary. ValueError is raised if is provided but isn't a value for . Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3), (2,22)]) omd.popvalue(1) == 111 omd.allitems() == [(1,11), (1,111), (2,2), (3,3), (2,22)] omd.popvalue(1, last=False) == 1 omd.allitems() == [(1,11), (2,2), (3,3), (2,22)] omd.popvalue(2, 2) == 2 omd.allitems() == [(1,11), (3,3), (2,22)] omd.popvalue(1, 11) == 11 omd.allitems() == [(3,3), (2,22)] omd.popvalue('not a key', default='sup') == 'sup' Params: last: Boolean whether to return 's first value ( is False) or last value ( is True). Raises: KeyError if isn't in the dictionary and isn't provided. ValueError if isn't a value for . Returns: The first or last of 's values. """ def pop_node_with_index(key, index): node = self._map[key].pop(index) if not self._map[key]: del self._map[key] self._items.removenode(node) return node if key in self: if value is not _absent: if last: pos = self.values(key)[::-1].index(value) else: pos = self.values(key).index(value) if pos == -1: raise ValueError(value) else: index = (len(self.values(key)) - 1 - pos) if last else pos return pop_node_with_index(key, index).value else: return pop_node_with_index(key, -1 if last else 0).value elif key not in self._map and default is not _absent: return default raise KeyError(key) def popitem(self, fromall=False, last=True): """ Pop and return a key:value item. If is False, items()[0] is popped if is False or items()[-1] is popped if is True. All remaining items with the same key are removed. If is True, allitems()[0] is popped if is False or allitems()[-1] is popped if is True. Any remaining items with the same key remain. Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.popitem() == (3,3) omd.popitem(fromall=False, last=False) == (1,1) omd.popitem(fromall=False, last=False) == (2,2) omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.popitem(fromall=True, last=False) == (1,1) omd.popitem(fromall=True, last=False) == (1,11) omd.popitem(fromall=True, last=True) == (3,3) omd.popitem(fromall=True, last=False) == (1,111) Params: fromall: Whether to pop an item from items() ( is True) or allitems() ( is False). last: Boolean whether to pop the first item or last item of items() or allitems(). Raises: KeyError if the dictionary is empty. Returns: The first or last item from item() or allitem(). """ if not self._items: raise KeyError('popitem(): %s is empty' % self.__class__.__name__) if fromall: node = self._items[-1 if last else 0] key = node.key return key, self.popvalue(key, last=last) else: key = list(self._map.keys())[-1 if last else 0] return key, self.pop(key) def poplistitem(self, last=True): """ Pop and return a key:valuelist item comprised of a key and that key's list of values. If is False, a key:valuelist item comprised of keys()[0] and its list of values is popped and returned. If is True, a key:valuelist item comprised of keys()[-1] and its list of values is popped and returned. Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.poplistitem(last=True) == (3,[3]) omd.poplistitem(last=False) == (1,[1,11,111]) Params: last: Boolean whether to pop the first or last key and its associated list of values. Raises: KeyError if the dictionary is empty. Returns: A two-tuple comprised of the first or last key and its associated list of values. """ if not self._items: s = 'poplistitem(): %s is empty' % self.__class__.__name__ raise KeyError(s) key = self.keys()[-1 if last else 0] return key, self.poplist(key) def items(self, key=_absent): """ Raises: KeyError if is provided and not in the dictionary. Returns: List created from iteritems(). Only items with key are returned if is provided and is a dictionary key. """ return list(self.iteritems(key)) def keys(self): return list(self.iterkeys()) def values(self, key=_absent): """ Raises: KeyError if is provided and not in the dictionary. Returns: List created from itervalues().If is provided and is a dictionary key, only values of items with key are returned. """ if key is not _absent and key in self._map: return self.getlist(key) return list(self.itervalues()) def lists(self): """ Returns: List created from iterlists(). """ return list(self.iterlists()) def listitems(self): """ Returns: List created from iterlistitems(). """ return list(self.iterlistitems()) def iteritems(self, key=_absent): """ Parity with dict.iteritems() except the optional parameter has been added. If is provided, only items with the provided key are iterated over. KeyError is raised if is provided and not in the dictionary. Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.iteritems(1) -> (1,1) -> (1,11) -> (1,111) omd.iteritems() -> (1,1) -> (2,2) -> (3,3) Raises: KeyError if is provided and not in the dictionary. Returns: An iterator over the items() of the dictionary, or only items with the key if is provided. """ if key is not _absent: if key in self: items = [ (node.key, node.value) for node in self._map[key] ] # type: Iterable[Tuple[Any, Any]] return iter(items) raise KeyError(key) items = six.iteritems(self._map) return iter((key, nodes[0].value) for (key, nodes) in items) def iterkeys(self): return six.iterkeys(self._map) def itervalues(self, key=_absent): """ Parity with dict.itervalues() except the optional parameter has been added. If is provided, only values from items with the provided key are iterated over. KeyError is raised if is provided and not in the dictionary. Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.itervalues(1) -> 1 -> 11 -> 111 omd.itervalues() -> 1 -> 11 -> 111 -> 2 -> 3 Raises: KeyError if is provided and isn't in the dictionary. Returns: An iterator over the values() of the dictionary, or only the values of key if is provided. """ if key is not _absent: if key in self: return iter([node.value for node in self._map[key]]) raise KeyError(key) return iter([nodes[0].value for nodes in six.itervalues(self._map)]) def allitems(self, key=_absent): ''' Raises: KeyError if is provided and not in the dictionary. Returns: List created from iterallitems(). ''' return list(self.iterallitems(key)) def allkeys(self): ''' Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.allkeys() == [1,1,1,2,3] Returns: List created from iterallkeys(). ''' return list(self.iterallkeys()) def allvalues(self, key=_absent): ''' Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.allvalues() == [1,11,111,2,3] omd.allvalues(1) == [1,11,111] Raises: KeyError if is provided and not in the dictionary. Returns: List created from iterallvalues(). ''' return list(self.iterallvalues(key)) def iterallitems(self, key=_absent): ''' Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.iterallitems() == (1,1) -> (1,11) -> (1,111) -> (2,2) -> (3,3) omd.iterallitems(1) == (1,1) -> (1,11) -> (1,111) Raises: KeyError if is provided and not in the dictionary. Returns: An iterator over every item in the diciontary. If is provided, only items with the key are iterated over. ''' if key is not _absent: # Raises KeyError if is not in self._map. return self.iteritems(key) return self._items.iteritems() def iterallkeys(self): ''' Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.iterallkeys() == 1 -> 1 -> 1 -> 2 -> 3 Returns: An iterator over the keys of every item in the dictionary. ''' return self._items.iterkeys() def iterallvalues(self, key=_absent): ''' Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.iterallvalues() == 1 -> 11 -> 111 -> 2 -> 3 Returns: An iterator over the values of every item in the dictionary. ''' if key is not _absent: if key in self: return iter(self.getlist(key)) raise KeyError(key) return self._items.itervalues() def iterlists(self): ''' Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.iterlists() -> [1,11,111] -> [2] -> [3] Returns: An iterator over the list comprised of the lists of values for each key. ''' return map(lambda key: self.getlist(key), self) def iterlistitems(self): """ Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.iterlistitems() -> (1,[1,11,111]) -> (2,[2]) -> (3,[3]) Returns: An iterator over the list of key:valuelist items. """ return map(lambda key: (key, self.getlist(key)), self) def reverse(self): """ Reverse the order of all items in the dictionary. Example: omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)]) omd.reverse() omd.allitems() == [(3,3), (2,2), (1,111), (1,11), (1,1)] Returns: . """ for key in six.iterkeys(self._map): self._map[key].reverse() self._items.reverse() return self def __eq__(self, other): if callable_attr(other, 'iterallitems'): myiter, otheriter = self.iterallitems(), other.iterallitems() for i1, i2 in zip_longest(myiter, otheriter, fillvalue=_absent): if i1 != i2 or i1 is _absent or i2 is _absent: return False elif not hasattr(other, '__len__') or not hasattr(other, _items_attr): return False # Ignore order so we can compare ordered omdicts with unordered dicts. else: if len(self) != len(other): return False for key, value in six.iteritems(other): if self.get(key, _absent) != value: return False return True def __ne__(self, other): return not self.__eq__(other) def __len__(self): return len(self._map) def __iter__(self): for key in self.iterkeys(): yield key def __contains__(self, key): return key in self._map def __getitem__(self, key): if key in self: return self.get(key) raise KeyError(key) def __setitem__(self, key, value): self.setlist(key, [value]) def __delitem__(self, key): return self.pop(key) def __nonzero__(self): return bool(self._map) def __str__(self): return '{%s}' % ', '.join( map(lambda p: '%r: %r' % (p[0], p[1]), self.iterallitems())) def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self.allitems()) def __or__(self, other): return self.__class__(chain(_get_items(self), _get_items(other))) def __ior__(self, other): for k, v in _get_items(other): self.add(k, value=v) return self def _get_items(mapping): """Find item iterator for an object.""" names = ('iterallitems', 'allitems', 'iteritems', 'items') exist = (n for n in names if callable_attr(mapping, n)) for a in exist: return getattr(mapping, a)() raise TypeError( "Object {} has no compatible items interface.".format(mapping)) orderedmultidict-1.0.1/orderedmultidict/py.typed000066400000000000000000000000001405651035200221050ustar00rootroot00000000000000orderedmultidict-1.0.1/setup.cfg000066400000000000000000000001021405651035200166670ustar00rootroot00000000000000[bdist_wheel] universal = 1 [metadata] license_file = LICENSE.md orderedmultidict-1.0.1/setup.py000066400000000000000000000071151405651035200165730ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # omdict - Ordered Multivalue Dictionary. # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) import os import sys from os.path import dirname, join as pjoin from setuptools import setup, find_packages, Command from setuptools.command.test import test as TestCommand meta = {} with open(pjoin('orderedmultidict', '__version__.py')) as f: exec(f.read(), meta) class Publish(Command): """Publish to PyPI with twine.""" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): os.system('python setup.py sdist bdist_wheel') base = 'dist/orderedmultidict' sdist = '%s-%s.tar.gz' % (base, meta['__version__']) wheel = '%s-%s-py2.py3-none-any.whl' % (base, meta['__version__']) rc = os.system('twine upload "%s" "%s"' % (sdist, wheel)) sys.exit(rc) class RunTests(TestCommand): """ Run the unit tests. To test all supported Python versions (as specified in tox.ini) in parallel, run $ tox -p By default, `python setup.py test` fails if tests/ isn't a Python module (that is, if the tests/ directory doesn't contain an __init__.py file). But the tests/ directory shouldn't contain an __init__.py file and tests/ shouldn't be a Python module. See http://doc.pytest.org/en/latest/goodpractices.html Running the unit tests manually here enables `python setup.py test` without tests/ being a Python module. """ def run_tests(self): from unittest import TestLoader, TextTestRunner tests_dir = pjoin(dirname(__file__), 'tests') suite = TestLoader().discover(tests_dir) result = TextTestRunner().run(suite) sys.exit(0 if result.wasSuccessful() else -1) long_description = (''' A multivalue dictionary is a dictionary that can store multiple values for the same key. An ordered multivalue dictionary is a multivalue dictionary that retains the order of insertions and deletions. omdict retains method parity with dict. Information and documentation at https://github.com/gruns/orderedmultidict.''') required = ['six>=1.8.0'] if sys.version_info < (2, 7): required.append('ordereddict') tests_require = ['flake8'] if sys.version_info[:2] < (2, 7): tests_require += ['unittest2'] setup( name=meta['__title__'], version=meta['__version__'], author=meta['__author__'], author_email=meta['__contact__'], url=meta['__url__'], license=meta['__license__'], description=meta['__description__'], long_description=long_description, packages=find_packages(), include_package_data=True, package_data={ "orderedmultidict": ["py.typed"], }, platforms=['any'], classifiers=[ 'Topic :: Software Development :: Libraries', 'Natural Language :: English', 'License :: Freely Distributable', 'Intended Audience :: Developers', 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: PyPy', ], install_requires=required, cmdclass={ 'test': RunTests, 'publish': Publish, }, tests_require=tests_require, ) orderedmultidict-1.0.1/tests/000077500000000000000000000000001405651035200162175ustar00rootroot00000000000000orderedmultidict-1.0.1/tests/test_itemlist.py000066400000000000000000000073471405651035200214750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # omdict - Ordered Multivalue Dictionary. # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) # from __future__ import absolute_import import unittest from six.moves import zip from orderedmultidict.itemlist import itemlist _unique = object() class TestItemList(unittest.TestCase): def setUp(self): self.inits = [ [], [(0, 0)], [(0, 0), (0, 0), (None, None)], [ (0, 0), (1, 1), (2, 2)], [(True, False)], [(False, True)], [ (object(), object()), (object(), object())], [('p', 'pumps'), ('d', 'dumps')], ] self.appends = [ (0, 0), (1, 1), (None, None), (True, False), (object(), object())] def test_init(self): for init in self.inits: il = itemlist(init) assert il.items() == init def test_append(self): for init in self.inits: il = itemlist(init) for key, value in self.appends: oldsize = len(il) newnode = il.append(key, value) assert len(il) == oldsize + 1 assert il[-1] == newnode def test_removenode(self): for init in self.inits: il = itemlist(init) for node, key, value in il: oldsize = len(il) assert node in il assert il.removenode(node) == il assert len(il) == oldsize - 1 assert node not in il def test_clear(self): for init in self.inits: il = itemlist(init) if len(init) > 0: assert bool(il) assert il.clear() == il assert not il def test_items_keys_values_iteritems_iterkeys_itervalues(self): for init in self.inits: il = itemlist(init) iterator = zip(zip(il.items(), il.keys(), il.values()), zip(il.iteritems(), il.iterkeys(), il.itervalues())) for (item1, key1, value1), (item2, key2, value2) in iterator: assert item1 == item2 and key1 == key2 and value1 == value2 def test_reverse(self): for init in self.inits: il = itemlist(init) items = il.items() items.reverse() assert il.reverse() == il assert items == il.items() def test_len(self): for init in self.inits: il = itemlist(init) assert len(il) == len(init) for key, value in self.appends: oldsize = len(il) il.append(key, value) assert len(il) == oldsize + 1 def test_contains(self): for init in self.inits: il = itemlist(init) for node, key, value in il: assert node in il assert (key, value) in il assert None not in il assert _unique not in il assert (19283091823, 102893091820) not in il def test_iter(self): for init in self.inits: il = itemlist(init) for node, key, value in il: assert node in il assert (key, value) in il def test_delitem(self): for init in self.inits: for index in [0, -1]: il = itemlist(init) while il: node = il[index] assert node in il del il[index] assert node not in il def test_nonzero(self): for init in self.inits: il = itemlist(init) if init: assert il il.clear() assert not il else: assert not il orderedmultidict-1.0.1/tests/test_orderedmultidict.py000066400000000000000000001070661405651035200232050ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # omdict - Ordered Multivalue Dictionary. # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) # from __future__ import absolute_import import unittest from itertools import product, repeat import six from six.moves import map, zip, zip_longest from orderedmultidict.orderedmultidict import omdict try: from collections import OrderedDict as odict # Python 2.7+. except ImportError: from ordereddict import OrderedDict as odict # Python 2.4-2.6. _unique = object() def callable_attr(o, attr): return hasattr(o, attr) and callable(getattr(o, attr)) def is_iterator(i): return callable_attr(i, 'next') or callable_attr(i, '__next__') # Utility list subclass to expose items() and iteritems() methods on basic # lists. This provides a common iteration interface for lists and dictionaries # for looping through their items without having to test for and maintain two # separate bodies, one for lists and one for dictionaries. # # So instead of requiring two bodies, one for lists and one for dicts # # lists = [[(1,1),(2,2)]] # dicts = [{1:1,2:2}] # for lst in lists: # lst == ... # for dic in dicts: # dic.items() == ... # # list and dictionary looping bodies can be merged with itemlist # # itemlist = [itemlist([(1,1),(2,2)]), {1:1,2:2}] # for ilist in itemlist: # ilist.items() == ... # class itemlist(list): def items(self): return self def iteritems(self): return iter(self) class TestOmdict(unittest.TestCase): def setUp(self): self.inits = [ {}, {1: 1}, {1: 1, 2: 2, 3: 3}, {None: None}, { None: None, 1: 1, 2: 2}, {False: False}, ] self.inits += list(map(itemlist, [ [], [(1, 1)], [(1, 1), (2, 2)], [(1, 1), (2, 2), (1, 1)], [(1, 1), (1, 1), (1, 1)], [(None, None), (None, None)], [(False, False)], [(None, 1), (1, None), (None, None), (None, 1), (1, None)], ])) # Updates to test update() and updateall(). self.updates = [ {}, {7: 7}, {7: 7, 8: 8, 9: 9}, {None: None}, {1: 1, 2: 2}] self.updates += list(map(itemlist, [ [], [(7, 7)], [(7, 7), (8, 8), (9, 9)], [(None, 'none')], [(9, 9), (1, 2)], [(7, 7), (7, 7), (8, 8), (7, 77)], [(1, 11), (1, 111), (1, 1111), (2, 22), (2, 222), ('a', 'a'), ('a', 'aa')], ])) self.keyword_updates = [ {}, {'1': 1}, {'1': 1, '2': 2}, { 'sup': 'pumps', 'scewps': None}, {'aa': 'aa'}, ] # Items not initially in any of the multidict inputs self.inits. self.nonitems = [ (44, 44), (None, 44), (55, None), ('a', 'b'), (11, 11), (22, 22)] # Keys not initially in any of the multidict inputs self.inits or in # self.nonitems. self.nonkeys = [_unique, 'asdfasdosduf', 'oaisfiapsn', 'ioausopdaui'] self.valuelist = [1, 2, 3, None, 'a', 'b', object()] def test_init(self): for init in self.inits: omd = omdict(init) assert omd.allitems() == list(init.items()) omd1 = omdict(init) omd2 = omdict(omd1) assert omd1.allitems() == list(omd2.allitems()) # Support both *args and **kwargs dictionary initialization. items = [('sape', 4139), ('guido', 4127), ('jack', 4098)] assert omdict(items).allitems() == items omd_item_set = set(omdict(sape=4139, guido=4127, jack=4098).items()) assert omd_item_set == set(items) # Keyword order isn't preserved. def test_load(self): omd = omdict() for init in self.inits: assert omd.load(init) == omd assert omd.allitems() == list(init.items()) def test_copy(self): for init in self.inits: omd = omdict(init) copy = omd.copy() assert omd is not copy and omd == copy def test_clear(self): for init in self.inits: omd = omdict(init) omd.clear() assert omd.items() == [] def test_fromkeys(self): for init in self.inits: keys = [key for key, value in init.items()] allitems = omdict.fromkeys(keys, _unique).allitems() assert allitems == list(zip(keys, repeat(_unique))) def test_has_key(self): for init in self.inits: omd = omdict(init) for key, value in init.items(): assert key in omd def test_update(self): # Some manual tests. omd = omdict() omd.update([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) assert omd.allitems() == [(1, 111), (2, 22), (3, 3)] omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) omd.update({1: None, 2: None, 3: None}) assert omd.allitems() == [(1, None), (2, None), (3, None)] for init in self.inits: zipped = zip(self.updates, self.keyword_updates) for update, keyword_update in zipped: omd1, omd2, omd3 = omdict(init), omdict(init), omdict(init) oldomd = omd1.copy() # Reduce the update to just the final items that will be # present post update(), where repeated keys will be reduced to # their last occurring value. For example, [(7,7),(7,8)] would # be reduced to [(7,8)]. reduced = [ i for i in update.items() if i in odict(update).items()] # Update with a dictionary. omd1.update(update) # Update with keyword expansion. omd2.update(**keyword_update) # Update with both a dictionary and keyword expansion. omd3.update(update, **keyword_update) # Verification. if update or keyword_update: for key, value in reduced: assert key in omd1 and key in omd3 for key, value in keyword_update.items(): assert key in omd2 and key in omd3 else: assert omd1 == omd2 == omd3 == oldomd def test_updateall(self): # Some manual tests. omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) omd.updateall({1: None, 2: None, 3: None}) assert omd.allitems() == [(1, None), (2, None), (3, None)] omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) omd.updateall([(1, None), (2, None), (3, None), (1, None), (2, None)]) assert omd.allitems() == [ (1, None), (1, None), (2, None), (3, None), (2, None)] omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) omd.updateall([(1, None), (1, None), (1, None), (2, None)]) assert omd.allitems() == [ (1, None), (1, None), (2, None), (3, 3), (1, None)] for init in self.inits: zipped = zip(self.updates, self.keyword_updates) for update, keyword_update in zipped: omd1, omd2, omd3 = omdict(init), omdict(init), omdict(init) oldomd = omd1.copy() # Update with a dictionary. omd1.updateall(update) # Update with keyword expansion. omd2.updateall(**keyword_update) # Update with both a dictionary and keyword expansion. omd3.updateall(update, **keyword_update) # Verification. if update or keyword_update: for key, value in six.iteritems(update): assert key in omd1 and key in omd3 assert value in omd1.getlist( key) and value in omd3.getlist(key) for key, value in keyword_update.items(): assert key in omd2 and key in omd3 assert omd2.getlist( key) == omd3.getlist(key) == [value] else: assert omd1 == omd2 == oldomd def test_get(self): for init in self.inits: omd = omdict(init) for key in omd.iterkeys(): assert omd.get(key) == omd[key] for nonkey in self.nonkeys: assert omd.get(nonkey) is None assert omd.get(nonkey, _unique) == _unique def test_getlist(self): for init in self.inits: omd = omdict(init) for key in omd: assert omd.getlist( key) == [v for k, v in omd.allitems() if k == key] for nonkey in self.nonkeys: assert omd.getlist(nonkey) == [] assert omd.getlist(nonkey, _unique) == _unique def test_setdefault(self): for init in self.inits: omd = omdict(init) for key in omd.iterkeys(): assert omd.setdefault(key, _unique) == omd[key] for nonkey in self.nonkeys: assert omd.setdefault(nonkey) is None assert omd[nonkey] is None omd.load(init) for nonkey in self.nonkeys: assert omd.setdefault(nonkey, 123456) == 123456 assert omd[nonkey] == 123456 def test_setdefaultlist(self): for init in self.inits: omd = omdict(init) for key in omd.iterkeys(): assert omd.setdefaultlist(key, _unique) == omd.getlist(key) for nonkey in self.nonkeys: assert omd.setdefaultlist(nonkey) == [None] assert omd.getlist(nonkey) == [None] omd.load(init) for nonkey in self.nonkeys: assert omd.setdefaultlist(nonkey, [1, 2, 3]) == [1, 2, 3] assert omd.getlist(nonkey) == [1, 2, 3] # setdefaultlist() with an empty list of values does nothing. for init in self.inits: omd = omdict(init) for key in omd.iterkeys(): values = omd.getlist(key) assert key in omd assert omd.setdefaultlist(key, []) == values assert key in omd and omd.getlist(key) == values for nonkey in self.nonkeys: assert nonkey not in omd assert omd.setdefaultlist(nonkey, []) == [] assert nonkey not in omd def test_add(self): for init in self.inits: omd = omdict(init) for key, value in self.nonitems: assert (key, value) not in omd.allitems() assert omd.add(key, value) == omd assert omd.getlist(key)[-1] == value assert omd.allitems()[-1] == (key, value) # Repeat the add() calls with the same items and make sure the old # items aren't replaced. oldomd = omd.copy() for key, value in self.nonitems: assert (key, value) in omd.allitems() assert omd.add(key, value) == omd assert len(omd.getlist(key)) == len(oldomd.getlist(key)) + 1 assert omd.getlist(key)[-1] == value assert omd.allitems()[-1] == (key, value) # Assert that containers are valid values, too, not just immutables # like integers. assert omd.add(_unique, self.updates) == omd assert omd.getlist(_unique)[-1] == self.updates assert omd.allitems()[-1] == (_unique, self.updates) # Add() doesn't require a value, and when one isn't provided it # defaults to None. omd = omdict(init) assert omd.add(_unique) == omd assert _unique in omd and omd[_unique] is None def test_addlist(self): for init in self.inits: omd = omdict(init) for nonkey in self.nonkeys: assert (nonkey, self.valuelist) not in omd.allitems() assert omd.addlist(nonkey, self.valuelist) == omd assert omd.getlist(nonkey) == self.valuelist assert (omd.allitems()[-1 * len(self.valuelist):] == list(zip(repeat(nonkey), self.valuelist))) # Repeat the addlist() calls with the same items and make sure the # old items aren't replaced. oldomd = omd.copy() for nonkey in self.nonkeys: for value in self.valuelist: assert (nonkey, value) in omd.allitems() assert omd.addlist(nonkey, self.valuelist) == omd assert len(omd.getlist(nonkey)) == ( len(oldomd.getlist(nonkey)) + len(self.valuelist)) assert omd.getlist(nonkey) == oldomd.getlist( nonkey) + self.valuelist assert (omd.allitems()[-1 * len(self.valuelist):] == list(zip(repeat(nonkey), self.valuelist))) # If an empty list is provided to addlist(), nothing is added. omd = omdict(init) for nonkey in self.nonkeys: assert omd.addlist(nonkey) == omd and nonkey not in omd assert omd.addlist(nonkey, []) == omd and nonkey not in omd def test_setlist(self): for init in self.inits: omd = omdict(init) for key in (omd.keys() + self.nonkeys): if key in omd: assert omd.getlist(key) != self.valuelist assert omd.setlist(key, self.valuelist) assert key in omd and omd.getlist(key) == self.valuelist # Setting a key to an empty list is identical to deleting the key. for init in self.inits: omd = omdict(init) for nonkey in self.nonkeys: assert nonkey not in omd omd.setlist(nonkey, []) assert nonkey not in omd for key in omd.keys(): assert key in omd omd.setlist(key, []) assert key not in omd assert not omd def test_removevalues(self): for init in self.inits: omd = omdict(init) removevals = omd.removevalues # Shorten to linewrap for PEP 8. for nonkey in self.nonkeys: obj = object() values = [1, 1.1, '1.1', (), [], {}, obj, 5.5, '1.1'] assert removevals(nonkey, []).getlist(nonkey) == [] assert removevals(nonkey, values).getlist(nonkey) == [] omd.addlist(nonkey, values).removevalues(nonkey, []) assert omd.getlist(nonkey) == values assert removevals(nonkey, values).getlist(nonkey) == [] omd.addlist(nonkey, values) assert (removevals(nonkey, [1]).getlist(nonkey) == [1.1, '1.1', (), [], {}, obj, 5.5, '1.1']) assert (removevals(nonkey, ['1.1', obj]).getlist(nonkey) == [1.1, (), [], {}, 5.5]) assert (removevals(nonkey, [[], 5.5, ()]).getlist(nonkey) == [1.1, {}]) assert removevals(nonkey, [{}]).getlist(nonkey) == [1.1] assert removevals(nonkey, [1.1]).getlist(nonkey) == [] assert removevals( nonkey, [9, 9.9, 'nope']).getlist(nonkey) == [] def test_pop(self): self._test_pop_poplist(lambda omd, key: omd.get(key) == omd.pop(key)) def test_poplist(self): self._test_pop_poplist( lambda omd, key: omd.getlist(key) == omd.poplist(key)) def _test_pop_poplist(self, assert_lambda): for init in self.inits: omd = omdict(init) items = omd.items() for key in list(omd.keys()): assert assert_lambda(omd, key) newitems = [item for item in items if item[0] != key] assert omd.items() == newitems items = newitems omd.load(init) for nonkey in self.nonkeys: self.assertRaises(KeyError, omd.pop, nonkey) assert omd.pop(nonkey, _unique) == _unique self.assertRaises(KeyError, omd.poplist, nonkey) assert omd.poplist(nonkey, _unique) == _unique def test_popvalue(self): # popvalue() with no value provided. for init in self.inits: for last in [True, False]: omd = omdict(init) allitems = omd.allitems() while omd.keys(): for key in omd.keys(): if last: value = omd.getlist(key)[-1] _rremove(allitems, (key, value)) else: value = omd[key] allitems.remove((key, value)) assert value == omd.popvalue(key, last=last) assert omd.allitems() == allitems omd.load(init) for nonkey in self.nonkeys: self.assertRaises(KeyError, omd.popvalue, nonkey) assert omd.popvalue(nonkey, default=_unique) == _unique # popvalue() with value provided. # last = True (default). omd = omdict([(1, 1), (2, 2), (3, 3), (2, 2), (3, 3), (2, 2)]) assert omd.popvalue(2, 2) == 2 assert omd.allitems() == [(1, 1), (2, 2), (3, 3), (2, 2), (3, 3)] assert omd.popvalue(2, 2) == 2 assert omd.allitems() == [(1, 1), (2, 2), (3, 3), (3, 3)] assert omd.popvalue(2, 2) == 2 assert omd.allitems() == [(1, 1), (3, 3), (3, 3)] # last = False. omd = omdict([(3, 3), (2, 2), (3, 3), (2, 2), (3, 3), (2, 2)]) assert omd.popvalue(2, 2, last=True) == 2 assert omd.allitems() == [(3, 3), (2, 2), (3, 3), (2, 2), (3, 3)] assert omd.popvalue(2, 2, last=True) == 2 assert omd.allitems() == [(3, 3), (2, 2), (3, 3), (3, 3)] assert omd.popvalue(2, 2, last=True) == 2 assert omd.allitems() == [(3, 3), (3, 3), (3, 3)] # Invalid key. self.assertRaises(KeyError, omd.popvalue, _unique, _unique) self.assertRaises(KeyError, omd.popvalue, _unique, 2) self.assertRaises(KeyError, omd.popvalue, _unique, 22) self.assertRaises(KeyError, omd.popvalue, _unique, _unique, last=False) self.assertRaises(KeyError, omd.popvalue, _unique, 2) self.assertRaises(KeyError, omd.popvalue, _unique, 22) assert omd.popvalue(_unique, _unique, 'sup') == 'sup' assert omd.popvalue(_unique, 2, 'sup') == 'sup' assert omd.popvalue(_unique, 22, 'sup') == 'sup' # Valid key, invalid value. self.assertRaises(ValueError, omd.popvalue, 3, _unique) self.assertRaises(ValueError, omd.popvalue, 3, _unique, False) def test_popitem(self): for init in self.inits: # All permutations of booleans and . for fromall, last in product([True, False], repeat=2): omd = omdict(init) allitems = omd.allitems() while omd.allitems(): if fromall: key, value = omd.allitems()[-1 if last else 0] else: key = omd.keys()[-1 if last else 0] value = omd[key] popkey, popvalue = omd.popitem(fromall=fromall, last=last) assert popkey == key and popvalue == value if fromall: if last: _rremove(allitems, (key, value)) else: allitems.remove((key, value)) else: allitems = [(k, v) for k, v in allitems if k != key] assert omd.allitems() == allitems omd = omdict() self.assertRaises(KeyError, omd.popitem) def test_poplistitem(self): for init in self.inits: for last in [True, False]: omd, omdcopy = omdict(init), omdict(init) while omd.keys(): key, valuelist = omd.poplistitem(last=last) assert key == omdcopy.keys()[-1 if last else 0] assert valuelist == omdcopy.getlist(key) omdcopy.pop(omdcopy.keys()[-1 if last else 0]) # poplistitem() on an empty omdict. self.assertRaises(KeyError, omd.poplistitem) # Tests every non-'all' items, keys, values, lists method: items(), keys(), # values(), lists(), listitems() and their iterators iteritems(), # iterkeys(), itervalues(), iterlists(), and iterlistitems(). def test_nonall_item_key_value_lists(self): for init in self.inits: dic = odict(init.items()) omd = omdict(init.items()) # Testing items(), keys(), values(), lists(), and listitems(). assert omd.items() == list(dic.items()) assert omd.keys() == list(dic.keys()) assert omd.values() == list(dic.values()) iterator = zip(omd.keys(), omd.lists(), omd.listitems()) for key, valuelist, listitem in iterator: assert omd.values(key) == omd.getlist(key) == valuelist assert omd.items( key) == [i for i in init.items() if i[0] == key] assert listitem == (key, valuelist) # Testing iteritems(), iterkeys(), itervalues(), and iterlists(). assert is_iterator(omd.iterkeys()) for key1, key2 in zip(omd.iterkeys(), six.iterkeys(dic)): assert key1 == key2 assert is_iterator(omd.itervalues()) for val1, val2 in zip(omd.itervalues(), six.itervalues(dic)): assert val1 == val2 assert is_iterator(omd.iteritems()) for item1, item2 in zip(omd.iteritems(), six.iteritems(dic)): assert item1 == item2 assert is_iterator(omd.iterlists()) for key, values in zip(six.iterkeys(omd), omd.iterlists()): assert omd.getlist(key) == values assert is_iterator(omd.iterlistitems()) iterator = zip( omd.iterkeys(), omd.iterlists(), omd.iterlistitems()) for key, valuelist, listitem in iterator: assert listitem == (key, valuelist) # Test iteritems() and itervalues() with a key. for key in omd.iterkeys(): assert is_iterator(omd.iteritems(key)) assert list(omd.iteritems(key)) == list(zip( repeat(key), omd.getlist(key))) assert is_iterator(omd.itervalues(key)) assert list(omd.itervalues(key)) == omd.getlist(key) for nonkey in self.nonkeys: self.assertRaises(KeyError, omd.iteritems, nonkey) self.assertRaises(KeyError, omd.itervalues, nonkey) # Tests every 'all' items, keys, values method: allitems(), allkeys(), # allvalues() and their iterators iterallitems(), iterallkeys(), # iterallvalues(). def test_all_items_keys_values_iterall_items_keys_values(self): for init in self.inits: omd = omdict(init) # map(list, zip(*lst)) - doesn't work if lst is empty, lst == []. keys = [key for key, value in init.items()] values = [value for key, value in init.items()] # Test allitems(), allkeys(), allvalues(). assert omd.allitems() == list(init.items()) assert omd.allkeys() == keys assert omd.allvalues() == values # Test iterallitems(), iterallkeys(), iterallvalues(). for key1, key2 in zip(omd.iterallkeys(), keys): assert key1 == key2 for val1, val2 in zip(omd.iterallvalues(), values): assert val1 == val2 for item1, item2 in zip(omd.iterallitems(), init.items()): assert item1 == item2 # Test allitems(), allvalues(), iterallitems() and iterallvalues() # with a key. for key in omd.iterkeys(): assert (omd.allvalues(key) == list(omd.iterallvalues(key)) == omd.getlist(key)) assert (omd.allitems(key) == list(omd.iterallitems(key)) == list(zip(repeat(key), omd.getlist(key)))) for nonkey in self.nonkeys: self.assertRaises(KeyError, omd.allvalues, nonkey) self.assertRaises(KeyError, omd.allitems, nonkey) self.assertRaises(KeyError, omd.iterallvalues, nonkey) self.assertRaises(KeyError, omd.iterallitems, nonkey) def test_reverse(self): for init in self.inits: reversed = list(init.items())[::-1] assert omdict(init).reverse().allitems() == reversed def test_eq(self): for init in self.inits: d, omd = dict(init), omdict(init) assert d == omd assert omd == omd assert omd == omd.copy() def test_ne(self): diff = omdict([(_unique, _unique)]) for init in self.inits: assert omdict(init) != diff # Compare to basic types. for basic in [1, 1.1, '1.1', (), [], object()]: assert omdict(init) != basic def test_len(self): for init in self.inits: assert len(omdict(init)) == len(dict(init)) def test_size(self): for init in self.inits: assert omdict(init).size() == len(init) def test_iter(self): for init in self.inits: omd = omdict(init) for key1, key2 in zip_longest(iter(omd), omd.iterkeys()): assert key1 == key2 def test_contains(self): for init in self.inits: omd = omdict(init) for key, value in init.items(): assert key in omd def test_getitem(self): for init in self.inits: dic = dict(init) omd = omdict(init) for key in omd.iterkeys(): assert omd[key] == dic[key] omd = omdict() self.assertRaises(KeyError, omd.__getitem__, _unique) def test_set_setitem(self): for init in self.inits: omd = omdict() omd2 = omdict() for key, value in init.items(): omd[key] = value assert omd2.set(key, value) == omd2 assert omd == omd2 and omd[key] == value # Store containers as values, not just immutables like integers. omd[_unique] = self.valuelist assert omd2.set(_unique, self.valuelist) == omd2 assert omd == omd2 and omd[_unique] == self.valuelist def test_delitem(self): for init in self.inits: omd = omdict(init) for key in list(omd.keys()): assert key in omd del omd[key] assert key not in omd def test_nonzero(self): for init in self.inits: if init: assert omdict(init) else: assert not omdict(init) def test_str(self): for init in self.inits: omd = omdict(init) s = '{%s}' % ', '.join( map(lambda p: '%s: %s' % (p[0], p[1]), omd.allitems())) assert s == str(omd) def test_odict_omdict_parity(self): for init in self.inits: d = odict(init) omd = omdict(init) self._compare_odict_and_omddict(d, omd) self._compare_odict_and_omddict(d.copy(), omd.copy()) # copy(). d.clear(), omd.clear() # clear(). self._compare_odict_and_omddict(d, omd) assert dict().update(init) == omdict().update(init) # update(). dict_fromkeys = list(d.fromkeys(init).items()) omdict_fromkeys = list(omd.fromkeys(init).items()) assert dict_fromkeys == omdict_fromkeys # fromkeys() def _compare_odict_and_omddict(self, d, omd): assert len(d) == len(omd) # __len__(). # __contains__(), has_key(), get(), and setdefault(). for dkey, omdkey in zip(d, omd): assert dkey == omdkey and dkey in d and omdkey in omd assert dkey in d and omdkey in omd assert d.get(dkey) == omd.get(omdkey) d.setdefault(dkey, _unique) omd.setdefault(omdkey, _unique) assert d.get(dkey) == omd.get(omdkey) and d.get(dkey) != _unique for nonkey in self.nonkeys: assert d.get(nonkey) == omd.get(nonkey) is None d.setdefault(nonkey, _unique) omd.setdefault(nonkey, _unique) assert d.get(nonkey) == omd.get(nonkey) == _unique # items(), keys, values(), iteritems(), iterkeys, and itervalues(). iterators = [ zip(d.items(), omd.items(), d.keys(), omd.keys(), d.values(), omd.values()), zip(six.iteritems(d), six.iteritems(omd), six.iterkeys(d), six.iterkeys(omd), six.itervalues(d), six.itervalues(omd))] for iterator in iterators: for ditem, omditem, dkey, omdkey, dvalue, omdvalue in iterator: assert dkey == omdkey assert ditem == omditem assert dvalue == omdvalue # pop(). dcopy, omdcopy = d.copy(), omd.copy() while dcopy and omdcopy: dpop = dcopy.pop(list(dcopy.keys())[0]) omdpop = omdcopy.pop(list(omdcopy.keys())[0]) assert dpop == omdpop # popitem(). dcopy, omdcopy = d.copy(), omd.copy() while dcopy and omdcopy: assert dcopy.popitem() == omdcopy.popitem() # __getitem__(). for dkey, omdkey in zip(six.iterkeys(d), six.iterkeys(omd)): assert d[dkey] == omd[omdkey] # __setitem__(). for dkey, omdkey in zip(d, omd): d[dkey] = _unique omd[omdkey] = _unique assert dkey == omdkey and d[dkey] == omd[omdkey] # __delitem__(). while d and omd: dkey, omdkey = list(d.keys())[0], list(omd.keys())[0] del d[dkey] del omd[omdkey] assert dkey == omdkey and dkey not in d and omdkey not in omd def test_fundamentals(self): # Gets, sets, and pops. omd = omdict() omd[1] = 1 omd[2] = 2 assert omd.allitems() == [(1, 1), (2, 2)] omd[1] = 11 assert omd.allitems() == [(1, 11), (2, 2)] omd.add(1, 1.1) assert omd.allitems() == [(1, 11), (2, 2), (1, 1.1)] assert omd.popvalue(1) == 1.1 assert omd.allitems() == [(1, 11), (2, 2)] omd.popvalue(2) assert omd.allitems() == [(1, 11)] omd[2] = [2, 2] assert omd.allitems() == [(1, 11), (2, [2, 2])] omd[1] = None assert omd.allitems() == [(1, None), (2, [2, 2])] omd.add(2, None) assert omd.allitems() == [(1, None), (2, [2, 2]), (2, None)] del omd[2] assert omd.allitems() == [(1, None)] omd[3] = 3 assert omd.allitems() == [(1, None), (3, 3)] omd.setlist(1, [1, 11, 111]) assert omd.allitems() == [(1, 1), (3, 3), (1, 11), (1, 111)] omd.addlist(1, [1111]) omd = omdict([(1, 1), (3, 3), (1, 11), (1, 111), (1, 1111)]) assert omd.allitems() == [(1, 1), (3, 3), (1, 11), (1, 111), (1, 1111)] omd[1] = None assert omd.allitems() == [(1, None), (3, 3)] def test_pops(self): init = [(1, 1), (2, 2), (1, 1), (1, 2), (1, 3)] # pop(). omd = omdict(init) assert omd.pop(1) == 1 assert omd.allitems() == [(2, 2)] assert omd.pop(_unique, 'sup') == 'sup' # poplist(). omd = omdict(init) assert omd.poplist(1) == [1, 1, 2, 3] assert omd.allitems() == [(2, 2)] self.assertRaises(KeyError, omd.poplist, _unique) assert omd.poplist(_unique, 'sup') == 'sup' # popvalue(). omd = omdict(init) assert omd.popvalue(1) == 3 assert omd.allitems() == [(1, 1), (2, 2), (1, 1), (1, 2)] self.assertRaises(KeyError, omd.popvalue, _unique) assert omd.popvalue(_unique, default='sup') == 'sup' assert omd.popvalue(1, last=False) == 1 assert omd.allitems() == [(2, 2), (1, 1), (1, 2)] # popitem(). omd = omdict(init) assert omd.popitem() == (2, 2) assert omd.allitems() == [(1, 1), (1, 1), (1, 2), (1, 3)] assert omd.popitem() == (1, 1) assert omd.allitems() == [] omd = omdict(init) assert omd.popitem(fromall=True) == (1, 3) assert omd.allitems() == [(1, 1), (2, 2), (1, 1), (1, 2)] assert omd.popitem(fromall=True, last=False) == (1, 1) assert omd.allitems() == [(2, 2), (1, 1), (1, 2)] def test_splats(self): items = [('1', 1), ('2', 2), ('3', 3)] omd = omdict(items) def splat(*args, **kwargs): return args, set(kwargs.items()) assert splat(*omd, **omd) == (tuple(i[0] for i in items), set(items)) class TestBinaryOperators(unittest.TestCase): @property def _items(self): original = (1, ['a']), (2, 'b') one_different = (1, ['a']), (3, 'd') all_different = (1, 'c'), (3, 'd') duplicate_key = (1, ['a']), (1, 'e') empty = tuple() return original, one_different, all_different, duplicate_key, empty @property def _or_params(self): original, one_diff, all_diff, dupe_key, empty = self._items return [ # self, other, other as dict, other as omdict. (original, original, original + original, original + original), (original, one_diff, original + one_diff, original + one_diff), (original, all_diff, original + all_diff, original + all_diff), (original, dupe_key, original + ((1, 'e'),), original + dupe_key), (original, empty, original, original), ] def test_or(self): for s, t, d, o in self._or_params: assert omdict(s) | dict(t) == omdict(d) assert omdict(s) | omdict(t) == omdict(o) def test_ior(self): for s, t, d, o in self._or_params: # Test with dict. a = omdict(s) a |= dict(t) assert a == omdict(d) # Test with omdict. a = omdict(s) a |= omdict(t) assert a == omdict(o) class TestUtilities(unittest.TestCase): def test_rfind(self): tests = [([], 1, -1), ([1], 1, 0), ([1, 2], 2, 1), ([1, 2, 1, 2], 1, 2), ([1, 2, 3], 4, -1), ([1, 2, 3], 1, 0)] for lst, item, pos in tests: assert _rfind(lst, item) == pos def test_rremove(self): tests = [([1, 1], 1, [1]), ([1], 1, []), ([1, 2], 2, [1]), ([1, 2, 3], 1, [2, 3]), ([1, 2, 1, 2], 1, [1, 2, 2]), ([1, 2, 1], 1, [1, 2])] for lst, item, result in tests: _rremove(lst, item) assert lst == result nonitems = [None, 'asdf', object(), 1000000] for nonitem in nonitems: self.assertRaises(ValueError, _rremove, lst, nonitem) def _rfind(lst, item): """ Returns the index of the last occurance of in . Returns -1 if is not in . ex: _rfind([1,2,1,2], 1) == 2 """ try: return (len(lst) - 1) - lst[::-1].index(item) except ValueError: return -1 def _rremove(lst, item): """ Removes the last occurance of in , or raises a ValueError if is not in . ex: _rremove([1,2,1,2], 1) == [1,2,2] """ pos = _rfind(lst, item) if pos >= 0: lst.pop(pos) return lst raise ValueError('_rremove(list, x): x not in list') orderedmultidict-1.0.1/tox.ini000066400000000000000000000003351405651035200163710ustar00rootroot00000000000000[tox] envlist = codestyle, py27, py36, py37, py38, py39, pypy, pypy3 [testenv] deps = nose commands = nosetests --exe [] [testenv:codestyle] deps = flake8 mypy commands = mypy -p orderedmultidict flake8