{"id":13501443,"url":"https://github.com/jsbueno/extradict","last_synced_at":"2025-04-09T16:06:05.453Z","repository":{"id":8717730,"uuid":"59457481","full_name":"jsbueno/extradict","owner":"jsbueno","description":"Collection of Mappings and with different capabilites and some utilities","archived":false,"fork":false,"pushed_at":"2025-02-11T18:59:16.000Z","size":241,"stargazers_count":38,"open_issues_count":3,"forks_count":3,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-04-09T16:06:00.625Z","etag":null,"topics":["collections","dictionary","hacktoberfest","hacktoberfest2020","hacktoberfest2021","mapgetter","ordereddict","python-mapping","tuples"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jsbueno.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-05-23T06:27:23.000Z","updated_at":"2025-03-22T20:51:15.000Z","dependencies_parsed_at":"2024-05-04T15:23:46.243Z","dependency_job_id":"59dc4fdb-26dc-4650-87e3-e79b5db6e6a9","html_url":"https://github.com/jsbueno/extradict","commit_stats":{"total_commits":77,"total_committers":2,"mean_commits":38.5,"dds":"0.025974025974025983","last_synced_commit":"27c798a67721bb9c3259ef2bf5af572552f2f818"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsbueno%2Fextradict","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsbueno%2Fextradict/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsbueno%2Fextradict/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsbueno%2Fextradict/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsbueno","download_url":"https://codeload.github.com/jsbueno/extradict/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248065289,"owners_count":21041871,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["collections","dictionary","hacktoberfest","hacktoberfest2020","hacktoberfest2021","mapgetter","ordereddict","python-mapping","tuples"],"created_at":"2024-07-31T22:01:37.805Z","updated_at":"2025-04-09T16:06:05.435Z","avatar_url":"https://github.com/jsbueno.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# Extra Dictionary classes and utilities for Python\n\nSome Mapping containers and tools for daily use with Python.\nThis attempts to be a small package with no dependencies,\njust delivering its data-types as described bellow\nenough tested for production-usage.\n\n ## What is new in v. 0.7\n\n  - The Trie, PrefixTrie and NormalizedTrie classes! See bottom of file!\n\n\n## ExtraDict data structures:\n  Bellow, a list, in order of implementation, of the public classes\n  implemented in this package:\n\n## VersionDict\n\nA Python Mutable Mapping Container (dictionary :-) ) that\ncan \"remember\" previous values.\nUse it wherever you would use a dict - at each\nkey change or update, it's `version` attribute\nis increased by one.\n\n### Special and modified methods:\n\n`.get` method is modified to receive an optional\nnamed  `version` parameter that allows one to retrieve\nfor a key the value it contained at that respective version.\nNB. When using the `version` parameter, `get` will raise\na KeyError if the key does not exist for that version and\nno default value is specified.\n\n`.copy(version=None)`:  yields a copy of the current dictionary at that version, with history preserved\n(if version is not given, the current version is used)\n\n`.freeze(version=None)` yields a snapshot of the versionDict in the form of a plain dictionary for\nthe specified version\n\n\n### Implementation:\nIt works by internally keeping a list of (named)tuples with\n(version, value) for each key.\n\n\n### Example:\n\n```python\n\n\u003e\u003e\u003e from extradict import VersionDict\n\u003e\u003e\u003e a = VersionDict(b=0)\n\u003e\u003e\u003e a[\"b\"] = 1\n\u003e\u003e\u003e a[\"b\"]\n1\n\u003e\u003e\u003e a.get(\"b\", version=0)\n0\n```\n\nFor extra examples, check the \"tests\" directory\n\n## OrderedVersionDict\n\nInherits from VersionDict, but preserves and retrieves key\ninsertion order. Unlike a plain dictionary or `collections.OrderedDict`,\nhowever, whenever a key's value is updated, it is moved\nlast on the dictionary order.\n\n### Example:\n```python\n\u003e\u003e\u003e from collections import OrderedDict\n\u003e\u003e\u003e a = OrderedDict(((\"a\", 1), (\"b\", 2), (\"c\", 3)))\n\u003e\u003e\u003e list(a.keys())\n\u003e\u003e\u003e ['a', 'b', 'c']\n\u003e\u003e\u003e a[\"a\"] = 3\n\u003e\u003e\u003e list(a.keys())\n\u003e\u003e\u003e ['a', 'b', 'c']\n\n\u003e\u003e\u003e from extradict import OrderedVersionDict\n\u003e\u003e\u003e a = OrderedVersionDict(((\"a\", 1), (\"b\", 2), (\"c\", 3)))\n\u003e\u003e\u003e list(a.keys())\n['a', 'b', 'c']\n\u003e\u003e\u003e a[\"a\"] = 3\n\u003e\u003e\u003e list(a.keys())\n['b', 'c', 'a']\n```\n\n## MapGetter\nA Context manager that allows one to pick variables from inside a dictionary,\nmapping, or any Python object by using the  `from \u003cmyobject\u003e import key1, key2` statement.\n\n\n\n```python\n\u003e\u003e\u003e from extradict import MapGetter\n\u003e\u003e\u003e a = dict(b=\"test\", c=\"another test\")\n\u003e\u003e\u003e with MapGetter(a) as a:\n...     from a import b, c\n...\n\u003e\u003e\u003e print (b, c)\ntest another test\n```\n\nOr:\n```python\n\u003e\u003e\u003e from collections import namedtuple\n\u003e\u003e\u003e a = namedtuple(\"a\", \"c d\")\n\u003e\u003e\u003e b = a(2,3)\n\u003e\u003e\u003e with MapGetter(b):\n...     from b import c, d\n\u003e\u003e\u003e print(c, d)\n2, 3\n```\n\nIt works with Python \"Enum\"s - which is great as it allow one\nto use the enums by their own name, without having to prepend the Enum class\nevery time:\n```python\n\u003e\u003e\u003e from enum import Enum\n\n\u003e\u003e\u003e class Colors(tuple, Enum):\n...     red = 255, 0, 0\n...     green = 0, 255, 0\n...     blue = 0, 0, 255\n...\n\n\u003e\u003e\u003e with MapGetter(Colors):\n...    from Colors import red, green, blue\n...\n\n\u003e\u003e\u003e red\n\u003cColors.red: (255, 0, 0)\u003e\n\u003e\u003e\u003e red[0]\n255\n```\n\nMapGetter can also have a `default` value or callable which\nwill generate values for each name that one tries to \"import\" from it:\n\n```python\n\u003e\u003e\u003e with MapGetter(default=lambda x: x) as x:\n...    from x import foo, bar, baz\n...\n\n\u003e\u003e\u003e foo\n'foo'\n\u003e\u003e\u003e bar\n'bar'\n\u003e\u003e\u003e baz\n'baz'\n```\n\nIf the parameter default is not a callable, it is assigned directly to\nthe imported names. If it is a callable, MapGetter will try to call it passing\neach name as the first and only positional parameter. If that fails\nwith a type error, it calls it without parameters the way collections.defaultdict\nworks.\n\n\nThe syntax `from \u003cmydict\u003e import key1 as var1` works as well.\n\n## BijectiveDict\nThis is a bijective dictionary for which each pair key, value added\nis also added as value, key.\n\nThe explicitly inserted keys can be retrieved as the \"assigned_keys\"\nattribute - and a dictionary copy with all such keys is available\nat the \"BijectiveDict.assigned\".\nConversely, the generated keys are exposed as \"BijectiveDict.generated_keys\"\nand can be seen as a dict at \"Bijective.generated\"\n\n```python\n\u003e\u003e\u003e from extradict import BijectiveDict\n\u003e\u003e\u003e\n\u003e\u003e\u003e a = BijectiveDict(b = 1, c = 2)\n\u003e\u003e\u003e a\nBijectiveDict({'b': 1, 2: 'c', 'c': 2, 1: 'b'})\n\u003e\u003e\u003e a[2]\n'c'\n\u003e\u003e\u003e a[2] = \"d\"\n\u003e\u003e\u003e a[\"d\"]\n2\n\u003e\u003e\u003e a[\"c\"]\nTraceback (most recent call last):\n  File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n  File \"/home/gwidion/projetos/extradict/extradict/reciprocal_dict.py\", line 31, in __getitem__\n    return self._data[item]\nKeyError: 'c'\n\u003e\u003e\u003e\n```\n\n## namedtuple\nAlternate, clean room, implementation of 'namedtuple' as in stdlib's collection.namedtuple\n. This does not make use of \"eval\" at runtime - and can be up to 10 times faster to create\na namedtuple class than the stdlib version.\n\nInstead, it relies on closures to do its magic.\n\nHowever, these will be slower to instantiate than stdlib version. The \"fastnamedtuple\"\nis faster in all respects, although it holds the same API for instantiating as tuples, and\nperforms no length checking.\n\n\n## fastnamedtuple\nLike namedtuple but the class returned take an iterable for its values\nrather than positioned or named parameters. No checks are made towards the iterable\nlength, which should match the number of attributes\nIt is faster for instantiating as compared with stdlib's namedtuple\n\n\n## defaultnamedtuple\nImplementation of named-tuple using default parameters -\nEither pass a sequence of 2-tuples or any ordered Mapping (like a `dict`) as the second parameter, or\nsend in kwargs with the default parameters, after the first.\n\nThe resulting object can accept positional or named parameters to be instantiated, as a\nnormal namedtuple, however, any omitted parameters are used from the original\nmapping passed to it.\n\n\n## FallbackNormalizedDict\nDictionary meant for text only keys:\nwill normalize keys in a way that capitalization, whitespace and\npunctuation will be ignored when retrieving items.\n\nA parallel dictionary is maintained with the original keys,\nso that strings that would clash on normalization can still\nbe used as separated key/value pairs if original punctuation\nis passed in the key.\n\nPrimary use case if for keeping translation strings when the source\nfor the original strings is loose in terms of whitespace/punctuation\n(for example, in an html snippet)\n\n\n## NormalizedDict\nDictionary meant for text only keys:\nwill normalize keys in a way that capitalization, whitespace and\npunctuation will be ignored when retrieving items.\n\nUnlike FallbackNormalizedDict this does not keep the original\nversion of the keys.\n\n\n## TreeDict\nA Python mapping with an underlying auto-balancing binary tree data structure.\nAs such, it allows seeking ranges of keys - so, that\n`mytreedict[\"aa\":\"bz\"] will return a list with all values in\nthe dictionary whose keys are strings starting from \"aa\"\nup to those starting with \"by\".\n\nIt also features a `.get_closest_keys` method that will\nretrieve the closest existing keys for the required element.\n```python\n\u003e\u003e\u003e from extradict import TreeDict\n\u003e\u003e\u003e a = TreeDict()\n\u003e\u003e\u003e a[1] = \"one word\"\n\u003e\u003e\u003e a[3] = \"another word\"\n\u003e\u003e\u003e a[:]\n['one word', 'another word']\n\u003e\u003e\u003e a.get_closest_keys(2)\n(1, 3)\n```\n\nAnother feature of these dicts is that as they\ndo not rely on an object hash, any Python\nobject can be used as a key. Of course\nkey objects should be comparable with \u003c=, ==, \u003e=. If\nthey are not, errors will be raised. HOWEVER, there is\nan extra feature - when creating the TreeDict a named\nargument `key` parameter can be passed that works the\nsame as Python's `sorted` \"key\" parameter: a callable\nthat will receive the key/value pair as its sole argument\nand should return a comparable object. The returned object\nis the one used to keep the Binary Tree organized.\n\n\nIf the output of the given `key_func` ties, that is it:\nthe new pair simply overwrites whatever other key/value\nhad the same key_func output. To avoid that,\ncraft the key_funcs so that they return a tuple\nwith the original key as the second item:\n```python\n\u003e\u003e\u003e from extradict import TreeDict\n\u003e\u003e\u003e b = TreeDict(key=len)\n\u003e\u003e\u003e b[\"red\"] = 1\n\u003e\u003e\u003e b[\"blue\"] = 2\n\u003e\u003e\u003e b\nTreeDict('red'=1, 'blue'=2, key_func= \u003cbuilt-in function len\u003e)\n\n\u003e\u003e\u003e b[\"1234\"] = 5\n\u003e\u003e\u003e b\nTreeDict('red'=1, '1234'=5, key_func= \u003cbuilt-in function len\u003e)\n\n\u003e\u003e\u003e TreeDict(key=lambda k: (len(k), k))\n\u003e\u003e\u003e b[\"red\"] = 1\n\u003e\u003e\u003e b[\"blue\"] = 2\n\u003e\u003e\u003e b[\"1234\"] = 5\n\u003e\u003e\u003e b\n\u003e\u003e\u003e TreeDict('red'=1, '1234'=5, 'blue'=2, key_func= \u003cfunction \u003clambda\u003e at 0x7fbc7f462320\u003e)\n```\n\n### PlainNode and AVLNode\n\nTo support the TreeDict mapping interface, the standalone\n`PlainNode` and `AVLNode` classes are available at\nthe `extradict.binary_tree_dict` module - and can be used\nto create a lower level tree data structure, which can\nhave more capabilities. For one, the \"raw\" use allows\nrepeated values in the Nodes, all Nodes are root to\ntheir own subtrees and know nothing of their parents,\nand if one wishes, no need to work with \"key: value\" pairs:\nif a \"pair\" argument is not supplied to a Node, it\nreflects the given Key as its own value.\n\n`PlainNode` will build non-autobalancing trees,\nwhile those built with `AVLNode` will be self-balancing.\nTrying to manually mix node types in the same tree, or\nchanging the key_func in different notes,\nwill obviously wreck everything.\n\n## Grouper\n\n\nThink of it as an itertools.groupby which returns a mapping\nOr as an itertools.tee that splits the stream into filtered\nsubstreams according to the passed key-callable.\n\nGiven an iterable and a key callable,\neach element in the iterable is run through the key callable and\nmade available in an iterator under a bucket using the resulting key-value.\n\nThe source iterable need not be ordered (unlike itertools.groupby).\nIf no key function  is given, the identity function is used.\n\nThe items will be made available under the iterable-values as requested,\nin a lazy way when possible. Note that several different method calls may\nprecipatate an eager processing of all items in the source iterator:\n.keys() or len(), for example.\n\nWhenever a new key is found during input consumption, a \"Queue\" iterator,\nwhich is a thin wrapper over collections.deque is created under that key\nand can be further iterated to retrieve more elements that map to\nthe same key.\n\nIn short, this is very similar to `itertools.tee`, but with a filter\nso that each element goes to a mapped bucket.\n\nOnce created, the resulting object may obtionally be called. Doing this\nwill consume all data in the source iterator at once, and return a\na plain dictionary with all data fetched into lists.\n\nFor example, to divide a sequence of numbers from 0 to 10 in\n5 buckets, all one need to do is: `Grouper(myseq, lambda x: x // 2)`\n\nOr:\n```python\n\u003e\u003e\u003e from extradict import Grouper\n\u003e\u003e\u003e even_odd = Grouper(range(10), lambda x: \"even\" if not x % 2 else \"odd\")\n\u003e\u003e\u003e print(list(even_odd[\"even\"]))\n[0, 2, 4, 6, 8]\n\u003e\u003e\u003e print(list(even_odd[\"odd\"]))\n[1, 3, 5, 7, 9]\n\n```\n\n\n## NestedData\n\nNestable mappings and sequences data structure to facilitate field access\n\nThe idea is a single data structure that can hold \"JSON\" data, adding some\nhelper methods and functionalities.\n\nPrimarily, one can use a dotted string path to access a deply nested key, value pair,\ninstead of concatenating several dictionary \".get\" calls.\n\nExamples:\n      `person[\"address.city\"] instead of person[\"address\"][\"city\"]`\n\n      or\n\n      `persons[\"10.contacts.emails.0\"]`\n\n\nThe first tool available is the ability to merge mappings with extra keys\ninto existing nested mappings, without deleting non colidng keys:\na \"person.address\" key that would contain \"city\" but no \"street\" or \"zip-code\"\ncan be updated with:  `record[\"person\"].merge({\"address\": {\"street\": \"5th ave\", \"zip-code\": \"000000\"}})`\npreserving the \"person.address.city\" value in the process.\n\nThe \".data\" attribute stores the object contents as a tree of dicionary and lists as needed -\nthese are lazily wrapped as NestedData instances if retrieved through the class, but\ncan be freely manipulated directly.\n\n```python\n\u003e\u003e\u003e import json\n\u003e\u003e\u003e from extradict import NestedData\n\u003e\u003e\u003e a = NestedData(json.load(open(\"myfile.json\")))\n\u003e\u003e\u003e assert a[\"persons.0.address\"] == a[\"persons\"][0][\"address\"]\nTrue\n\u003e\u003e\u003e a.merge({\"city\": None}, \"persons.*.address\")  # creates a new \"city\" key in all addresses\n```\n\n## PrefixTrie\nA Trie data structure with a `set`-like interface. It should contain\nstrings - and after picking a prefix by using the square bracket notation\nfor mappings, it returns a view of the data which only contains the matching\nelements. (data is not dduplicated unferlying).\n\nUnlike, for example, running a regexp against a list\nwith several thousand words, which will have to walk through\nall entries, these Tries really only descend into the entries\nwhich match the given pattern, never touching any unmacthed\nentry.\n\nCan be used IRL for auto-completing text:\n\n```python\n\u003e\u003e\u003e from extradict import PrefixTrie\n\u003e\u003e\u003e a = PrefixTrie([\"abc\", \"abcd\", \"def\", \"defg\"])\n\u003e\u003e\u003e len(a)\n4\n\u003e\u003e\u003e list(a[\"ab\"])\n['abcd', 'abc']\n\u003e\u003e\u003e list(a[\"def\"])\n['defg', 'def']\n```\n## Trie\nAn evolution of PrefixTrie, which will match any pattern\nin the middle of the member strings, not only as a prefix:\n\n```python\n\u003e\u003e\u003e from extradict import Trie\n\u003e\u003e\u003e a = Trie([\"abc\", \"zabc\", \"def\", \"bcdef\"])\n\u003e\u003e\u003e list(a[\"bc\"])\n['zabc', 'abc', 'bcdef']\n```\n\n## NormalizedTrie\nA Trie that will match patterns in the middle,\nbut internally keeps a helper `NormalizedDict`\nstructure which will make it ignore differences\ndue to punctiation, space and accented characters -\nmaking it ideal for real world text-completion finding.\n\n\n```python\n\u003e\u003e\u003e from extradict import NormalizedTrie\n\u003e\u003e\u003e\n\u003e\u003e\u003e a = NormalizedTrie((\"a-b-c\", \"abc\", \"maca\", \"maçã\"))\n\u003e\u003e\u003e list(a[\"bc\"])\n['abc', 'a-b-c']\n\u003e\u003e\u003e list(a[\"mac\"])\n['maca', 'maçã']\n\u003e\u003e\u003e list(a[\"macã\"])\n['maca', 'maçã']\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsbueno%2Fextradict","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsbueno%2Fextradict","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsbueno%2Fextradict/lists"}