dol.paths¶
Module for path (and path-like) object manipulation
Examples
>>> d = {'a': {'b': {'c': 1, 'd': 2}, 'e': 3}}
>>> list(path_filter(lambda p, k, v: v == 2, d))
[('a', 'b', 'd')]
>>> path_get(d, ('a', 'b', 'd'))
2
>>> path_set(d, ('a', 'b', 'd'), 4)
>>> d
{'a': {'b': {'c': 1, 'd': 4}, 'e': 3}}
>>> path_set(d, ('a', 'b', 'new_ab_key'), 42)
>>> d
{'a': {'b': {'c': 1, 'd': 4, 'new_ab_key': 42}, 'e': 3}}
Functions
|
Make a missing key that is a prefix of existing keys return the sub-mapping of those keys (so |
Cast |
|
|
If |
|
Ensure that the path_extender is a function that takes a path and a key and returns a new path. |
|
Flatten a nested dictionary into a flat one, using key-paths as keys. |
|
Yield flattened key-value pairs from a nested dictionary. |
|
If |
|
Return |
|
A store decorator that handles prefixes. |
|
Return |
|
Transforms a string path separated by a specified separator into a tuple of keys and indices. |
|
Get a dictionary of leaf paths of a nested dictionary. |
|
|
|
Make a series of (in place) edits to a Mapping, specifying |
|
Walk a dict, yielding paths to values that pass the |
|
Get elements of a mapping through a path to be called recursively. |
|
Returns (path, values) pairs of the given paths in the given object. |
|
Wrap |
|
|
|
|
|
|
|
|
|
backwards compatibility quasi-alias (arguments are flipped) Use path_filter instead, since search_paths will be deprecated. |
|
Split a string path on |
|
Extends a given path with a new key using the specified separator. |
|
Split |
|
Make a key trans object that translates from a string _id to a dict, tuple, or namedtuple key (and back) |
|
The inverse of string.Formatter.parse |
Classes
|
dol.base.Keys implementation that gets it's keys explicitly from a collection given at initialization time. |
|
A key mapper that converts from an iterable key (default tuple) to a string (given a path-separator str) |
|
A class for parsing and generating keys based on a template. |
|
Enum of the path key forms: |
|
A collection of keys with a key_to_value function to lazy load values. |
|
A key wrap that allows one to interface with absolute paths through relative paths. |
Mixin that adds a intercepts the _id_of_key an _key_of_id methods, transforming absolute keys to relative ones. |
|
|
Key mapper adding |
- class dol.paths.ExplicitKeysWithPrefixRelativization(key_collection, _prefix=None)[source]¶
Bases:
PrefixRelativizationMixin,Storedol.base.Keys implementation that gets it’s keys explicitly from a collection given at initialization time. The key_collection must be a collections.abc.Collection (such as list, tuple, set, etc.)
>>> from dol.base import Store >>> s = ExplicitKeysWithPrefixRelativization(key_collection=['/root/of/foo', '/root/of/bar', '/root/for/alice']) >>> keys = Store(store=s) >>> 'of/foo' in keys True >>> 'not there' in keys False >>> list(keys) ['of/foo', 'of/bar', 'for/alice']
- class dol.paths.KeyPath(path_sep='/', _path_type=<class 'tuple'>, *, create_missing=False, mk_missing=None, explore_further=None, may_create=None, on_create=<function _warn_on_create>, max_created=None, max_levels=20, verify_writeback=False, writeback_lock=None)[source]¶
Bases:
objectA key mapper that converts from an iterable key (default tuple) to a string (given a path-separator str)
- Parameters:
With
'/'as a separator:>>> kp = KeyPath(path_sep='/') >>> kp._key_of_id(('a', 'b', 'c')) 'a/b/c' >>> kp._id_of_key('a/b/c') ('a', 'b', 'c')
With
'.'as a separator:>>> kp = KeyPath(path_sep='.') >>> kp._key_of_id(('a', 'b', 'c')) 'a.b.c' >>> kp._id_of_key('a.b.c') ('a', 'b', 'c') >>> kp = KeyPath(path_sep=':::', _path_type=dict.fromkeys) >>> _id = dict.fromkeys('abc') >>> _id {'a': None, 'b': None, 'c': None} >>> kp._key_of_id(_id) 'a:::b:::c' >>> kp._id_of_key('a:::b:::c') {'a': None, 'b': None, 'c': None}
Calling a
KeyPathinstance on a store wraps it so we can have path access to it.>>> s = {'a': {'b': {'c': 42}}} >>> s['a']['b']['c'] 42 >>> # Now let's wrap the store >>> s = KeyPath('.')(s) >>> s['a.b.c'] 42 >>> s['a.b.c'] = 3.14 >>> s['a.b.c'] 3.14 >>> del s['a.b.c'] >>> s {'a': {'b': {}}}
Note
By default
KeyPathreads with paths only when all the keys of the path are valid (i.e. have a value), and, just like adict, will not create intermediate nested values for you on write. Passcreate_missing=Trueto opt into write-through autovivification: missing intermediates are created on write (likecollections.defaultdict, but with an optional contextual per-levelmk_missing(ctx)factory), and the change persists correctly even through persistent / copy-semantics stores. Seemisc/docs/dol_issue16_design.md.>>> s = KeyPath('.', create_missing=True)({}) >>> s['a.b.c'] = 42 >>> s['a.b.c'] 42
- class dol.paths.KeyTemplate(template, *, field_patterns=None, to_str_funcs=None, from_str_funcs=None, simple_str_sep=', ', namedtuple_type_name='NamedTuple', dflt_pattern='.*', dflt_field_name=<built-in method format of str object>, normalize_paths=False)[source]¶
Bases:
objectA class for parsing and generating keys based on a template.
- Parameters:
template (
str) – A template string with fields to be extracted or filled in.field_patterns (
dict) – A dictionary of field names and their regex patterns.simple_str_sep (
str) – A separator string for simple strings (i.e. strings without fields).namedtuple_type_name (
str) – The name of the namedtuple type to use for namedtuple fields.dflt_pattern (
str) – The default pattern to use for fields that don’t have a pattern specified.to_str_funcs (
dict) – A dictionary of field names and their functions to convert them to strings.from_str_funcs (
dict) – A dictionary of field names and their functions to convert them from strings.
Examples
>>> st = KeyTemplate( ... 'root/{name}/v_{version}.json', ... field_patterns={'version': r'\d+'}, ... from_str_funcs={'version': int}, ... )
And now you have a template that can be used to convert between various representations of the template: You can extract fields from strings, generate strings from fields, etc.
>>> st.str_to_dict("root/dol/v_9.json") {'name': 'dol', 'version': 9} >>> st.dict_to_str({'name': 'meshed', 'version': 42}) 'root/meshed/v_42.json' >>> st.dict_to_tuple({'name': 'meshed', 'version': 42}) ('meshed', 42) >>> st.tuple_to_dict(('i2', 96)) {'name': 'i2', 'version': 96} >>> st.str_to_tuple("root/dol/v_9.json") ('dol', 9) >>> st.tuple_to_str(('front', 11)) 'root/front/v_11.json' >>> st.str_to_namedtuple("root/dol/v_9.json") NamedTuple(name='dol', version=9) >>> st.str_to_simple_str("root/dol/v_9.json") 'dol,9' >>> st_clone = st.clone(simple_str_sep='/') >>> st_clone.str_to_simple_str("root/dol/v_9.json") 'dol/9'
With
st.key_codec, you can make aKeyCodecfor the given source (decoded) and target (encoded) types. Akey_codecis a codec; it has an encoder and a decoder.>>> key_codec = st.key_codec('tuple', 'str') >>> encoder, decoder = key_codec >>> decoder('root/dol/v_9.json') ('dol', 9) >>> encoder(('dol', 9)) 'root/dol/v_9.json'
If you have a
Mapping, you can usekey_codecas a decorator to wrap the mapping with a key mappings.>>> store = { ... 'root/meshed/v_151.json': '{"downloads": 41, "type": "productivity"}', ... 'root/dol/v_9.json': '{"downloads": 132, "type": "utility"}', ... } >>> >>> accessor = key_codec(store) >>> list(accessor) [('meshed', 151), ('dol', 9)] >>> accessor['i2', 4] = '{"downloads": 274, "type": "utility"}' >>> list(store) ['root/meshed/v_151.json', 'root/dol/v_9.json', 'root/i2/v_4.json'] >>> store['root/i2/v_4.json'] '{"downloads": 274, "type": "utility"}'
Note
If your store contains keys that don’t fit the format, key_codec will raise a
ValueError. To remedy this, you can use thest.filt_iterto filter out keys that don’t fit the format, before you wrap the store withst.key_codec.>>> store = { ... 'root/meshed/v_151.json': '{"downloads": 41, "type": "productivity"}', ... 'root/dol/v_9.json': '{"downloads": 132, "type": "utility"}', ... 'root/not/the/right/format': "something else" ... } >>> accessor = st.filt_iter('str')(store) >>> list(accessor) ['root/meshed/v_151.json', 'root/dol/v_9.json'] >>> accessor = st.key_codec('tuple', 'str')(st.filt_iter('str')(store)) >>> list(accessor) [('meshed', 151), ('dol', 9)] >>> accessor['dol', 9] '{"downloads": 132, "type": "utility"}'
You can also ask any (handled) combination of field types:
>>> key_codec = st.key_codec('tuple', 'dict') >>> key_codec.encoder(('i2', 96)) {'name': 'i2', 'version': 96} >>> key_codec.decoder({'name': 'fantastic', 'version': 4}) ('fantastic', 4)
- dict_to_namedtuple(params)[source]¶
Generates a namedtuple from the dictionary values based on the template.
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> App = st.dict_to_namedtuple({'i01_': 'life', 'ver': 42}) >>> App NamedTuple(i01_='life', ver=42)
- dict_to_str(params)[source]¶
Generates a string from the dictionary values based on the template.
- Return type:
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> st.dict_to_str({'i01_': 'life', 'ver': 42}) 'root/life/v_042.json'
- dict_to_tuple(params)[source]¶
Generates a tuple from the dictionary values based on the template.
- Return type:
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> st.str_to_tuple('root/life/v_42.json') ('life', 42)
- filt_iter(field_type='str')[source]¶
Makes a store decorator that filters out keys that don’t match the template given field type.
>>> store = { ... 'root/meshed/v_151.json': '{"downloads": 41, "type": "productivity"}', ... 'root/dol/v_9.json': '{"downloads": 132, "type": "utility"}', ... 'root/not/the/right/format': "something else" ... } >>> filt = KeyTemplate('root/{pkg}/v_{version}.json') >>> filtered_store = filt.filt_iter('str')(store) >>> list(filtered_store) ['root/meshed/v_151.json', 'root/dol/v_9.json']
- key_codec(decoded='tuple', encoded='str')[source]¶
Makes a
KeyCodecfor the given source and target types.>>> st = KeyTemplate( ... 'root/{name}/v_{version}.json', ... field_patterns={'version': r'\d+'}, ... from_str_funcs={'version': int}, ... )
A
key_codecis a codec; it has an encoder and a decoder.>>> key_codec = st.key_codec('tuple', 'str') >>> encoder, decoder = key_codec >>> decoder('root/dol/v_9.json') ('dol', 9) >>> encoder(('dol', 9)) 'root/dol/v_9.json'
If you have a
Mapping, you can usekey_codecas a decorator to wrap the mapping with a key mappings.>>> store = { ... 'root/meshed/v_151.json': '{"downloads": 41, "type": "productivity"}', ... 'root/dol/v_9.json': '{"downloads": 132, "type": "utility"}', ... } >>> >>> accessor = key_codec(store) >>> list(accessor) [('meshed', 151), ('dol', 9)] >>> accessor['i2', 4] = '{"downloads": 274, "type": "utility"}' >>> list(store) ['root/meshed/v_151.json', 'root/dol/v_9.json', 'root/i2/v_4.json'] >>> store['root/i2/v_4.json'] '{"downloads": 274, "type": "utility"}'
Note
If your store contains keys that don’t fit the format, key_codec will raise a
ValueError. To remedy this, you can use thest.filt_iterto filter out keys that don’t fit the format, before you wrap the store withst.key_codec.
- match_str(s)[source]¶
Returns True iff the string matches the template.
- Return type:
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> st.match_str('root/life/v_042.json') True >>> st.match_str('this/does/not_match') False
- namedtuple_to_dict(nt)[source]¶
Converts a namedtuple to a dictionary.
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> App = st.dict_to_namedtuple({'i01_': 'life', 'ver': 42}) >>> st.namedtuple_to_dict(App) {'i01_': 'life', 'ver': 42}
- simple_str_to_str(ss)[source]¶
Converts a simple character-delimited string to a string.
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... simple_str_sep='-', ... ) >>> st.simple_str_to_str('life-042') 'root/life/v_042.json'
- simple_str_to_tuple(ss)[source]¶
Converts a simple character-delimited string to a dict.
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... simple_str_sep='-', ... ) >>> st.simple_str_to_tuple('life-042') ('life', 42)
- single_to_str(k)[source]¶
Generates a string from the single value based on the template.
- Return type:
>>> st = KeyTemplate( ... r'root/life/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> st.single_to_str(42) 'root/life/v_042.json'
- str_to_dict(s)[source]¶
Parses the input string and returns a dictionary of extracted values.
- Return type:
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', ... from_str_funcs={'ver': int}, ... ) >>> st.str_to_dict('root/life/v_30.json') {'i01_': 'life', 'ver': 30}
- str_to_namedtuple(s)[source]¶
Converts a string to a namedtuple.
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> App = st.str_to_namedtuple('root/life/v_042.json') >>> App NamedTuple(i01_='life', ver=42)
- str_to_simple_str(s)[source]¶
Converts a string to a simple string (i.e. a simple character-delimited string).
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> st.str_to_simple_str('root/life/v_042.json') 'life,042' >>> st_clone = st.clone(simple_str_sep='-') >>> st_clone.str_to_simple_str('root/life/v_042.json') 'life-042'
- str_to_single(s)[source]¶
Parses the input string and returns a single value.
- Return type:
>>> st = KeyTemplate( ... r'root/life/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> st.str_to_single('root/life/v_42.json') 42
- str_to_tuple(s)[source]¶
Parses the input string and returns a tuple of extracted values.
- Return type:
>>> st = KeyTemplate( ... r'root/{}/v_{ver:03.0f:\d+}.json', from_str_funcs={'ver': int}, ... ) >>> st.str_to_tuple('root/life/v_42.json') ('life', 42)
- class dol.paths.PathKeyTypes(*values)[source]¶
Bases:
EnumEnum of the path key forms:
str,dict,tuple,namedtuple.
- class dol.paths.PathMappedData(src, key_collection, getter=<function path_get>, *, key_to_value=None)[source]¶
Bases:
KeysReaderA collection of keys with a key_to_value function to lazy load values.
PathMappedDatais particularly useful in cases where you want to have a mapping that lazy-loads values for keys from an explicit collection.Keywords: Lazy-evaluation, Mapping
- Parameters:
data – The mapping to extract data from
paths – The paths to extract data from the mapping
Example
>>> data = { ... 'a': { ... 'b': [{'c': 1}, {'c': 2}], ... 'd': 'bar' ... } ... } >>> paths = ['a.d', 'a.b.0.c'] >>> >>> d = PathMappedData(data, paths) >>> list(d) ['a.d', 'a.b.0.c'] >>> d['a.d'] 'bar' >>> d['a.b.0.c'] 1
Now, data does contain a key path for ‘a.b.1.c’:
>>> d.getter(d.src, 'a.b.1.c') 2
But since we didn’t mention it in our paths parameter, it will raise a KeyError if we try to access it via the
PathMappedDataobject:>>> d['a.b.1.c'] Traceback (most recent call last): ... KeyError: "Key a.b.1.c was not found....key_collection attribute)"
- class dol.paths.PrefixRelativization(_prefix='')[source]¶
Bases:
PrefixRelativizationMixinA key wrap that allows one to interface with absolute paths through relative paths. The original intent was for local files. Instead of referencing files through an absolute path such as:
/A/VERY/LONG/ROOT/FOLDER/the/file/we.want
we can instead reference the file as:
the/file/we.want
But PrefixRelativization can be used, not only for local paths, but when ever a string reference is involved. In fact, not only strings, but any key object that has a __len__, __add__, and subscripting.
- class dol.paths.PrefixRelativizationMixin[source]¶
Bases:
objectMixin that adds a intercepts the _id_of_key an _key_of_id methods, transforming absolute keys to relative ones. Designed to work with string keys, where absolute and relative are relative to a _prefix attribute (assumed to exist). The cannonical use case is when keys are absolute file paths, but we want to identify data through relative paths. Instead of referencing files through an absolute path such as
/A/VERY/LONG/ROOT/FOLDER/the/file/we.wantwe can instead reference the file asthe/file/we.want.Note though, that PrefixRelativizationMixin can be used, not only for local paths, but when ever a string reference is involved. In fact, not only strings, but any key object that has a __len__, __add__, and subscripting.
When subclassed, should be placed before the class defining _id_of_key an _key_of_id. Also, assumes that a (string) _prefix attribute will be available.
>>> from dol.base import Store >>> from collections import UserDict >>> >>> class MyStore(PrefixRelativizationMixin, Store): ... def __init__(self, store, _prefix='/root/of/data/'): ... super().__init__(store) ... self._prefix = _prefix ... >>> s = MyStore(store=dict()) # using a dict as our store >>> s['foo'] = 'bar' >>> assert s['foo'] == 'bar' >>> s['too'] = 'much' >>> assert list(s.keys()) == ['foo', 'too'] >>> # Everything looks normal, but are the actual keys behind the hood? >>> s._id_of_key('foo') '/root/of/data/foo' >>> # see when iterating over s.items(), we get the interface view: >>> list(s.items()) [('foo', 'bar'), ('too', 'much')] >>> # but if we ask the store we're actually delegating the storing to, we see what the keys actually are. >>> s.store.items() dict_items([('/root/of/data/foo', 'bar'), ('/root/of/data/too', 'much')])
- class dol.paths.RelativePathKeyMapper(prefix)[source]¶
Bases:
objectKey mapper adding
prefixon the way in and removing it on the way out.
- dol.paths.add_prefix_filtering(store=None, *, relativize_prefix=False, __module__=None, __name__=None, __qualname__=None, __doc__=None, __annotations__=None, __defaults__=None, __kwdefaults__=None)[source]¶
Make a missing key that is a prefix of existing keys return the sub-mapping of those keys (so
s['a/']lists everything “under”a/).>>> d = {'a/b': 1, 'a/c': 2, 'd/e': 3, 'f': 4} >>> s = add_prefix_filtering(d) >>> assert s['a/'] == {'a/b': 1, 'a/c': 2}
Demo usage on a
Mappingtype:>>> from collections import UserDict >>> D = add_prefix_filtering(UserDict) >>> s = D(d) >>> assert s['a/'] == {'a/b': 1, 'a/c': 2}
- dol.paths.cast_to_int_if_numeric_str(k)[source]¶
Cast
ktointif it is a numeric string; return it unchanged otherwise.
- dol.paths.chain_of_getters(getters, obj=None, k=None, *, caught_errors=(<class 'Exception'>, ))[source]¶
If
kis a string, tries to getkas an attribute ofobjfirst, and if that fails, gets it asobj[k]
- dol.paths.ensure_path_extender_func(path_extender)[source]¶
Ensure that the path_extender is a function that takes a path and a key and returns a new path.
- dol.paths.flatten_dict(d, sep='.', *, parent_path=None, visit_nested=<function <lambda>>, egress=<class 'dict'>)[source]¶
Flatten a nested dictionary into a flat one, using key-paths as keys.
See also
leaf_pathsfor a related function that returns paths to leaf values.- Parameters:
d – The dictionary to flatten
sep (
Union[str,Callable[[TypeVar(Path),TypeVar(KT)],TypeVar(Path)]]) – The separator to use for joining keys, or a function that takes a path and a key and returns a new path.parent_path (
Optional[TypeVar(Path)]) – The path to the parent of the current dictvisit_nested (
Callable) – A function that returns True if a value should be visitedegress (
Callable[[Generator[tuple[TypeVar(KT),TypeVar(VT)],None,None]],Mapping]) – A function that takes a generator of key-value pairs and returns a mapping
>>> d = {'a': {'b': 2}, 'c': 3} >>> flatten_dict(d) {'a.b': 2, 'c': 3} >>> flatten_dict(d, sep='/') {'a/b': 2, 'c': 3}
- dol.paths.flattened_dict_items(d, sep='.', *, parent_path=None, visit_nested=<function <lambda>>)[source]¶
Yield flattened key-value pairs from a nested dictionary.
- dol.paths.get_attr_or_item(obj, k)[source]¶
If
kis a string, tries to getkas an attribute ofobjfirst, and if that fails, gets it asobj[k]Warning
The hardcoded priority choices of this function regarding when to try k as an item, index, or attribute, don’t apply to every case, so you may want to use an explicit value getter to be more robust!
# >>> d = {‘a’: [1, {‘items’: 2, ‘3’: 33, 3: 42}]}
>>> get_attr_or_item({'items': 2}, 'items') 2
But if “items” is not there as a key of the object, the attribute is found:
>>> get_attr_or_item({'not_items': 2}, 'items') <built-in method items of dict object...>
Both integers and string integers will work to get an item if obj is not a Mapping.
>>> get_attr_or_item([7, 21, 42], 2) 42 >>> get_attr_or_item([7, 21, 42], '2') 42
If you’re dealling with a Mapping, you can get both integer and string keys, and if you have both types in your Mapping, you’ll get the right one!
>>> get_attr_or_item({2: 'numerical key', '2': 'string key'}, 2) 'numerical key' >>> get_attr_or_item({2: 'numerical key', '2': 'string key'}, '2') 'string key'
If you don’t have the numerical version, the string version will still find your numerical key.
>>> get_attr_or_item({2: 'numerical key'}, '2') 'numerical key'
The opposite is not true though: If you ask for an integer key, it will not find a string version of it.
>>> get_attr_or_item({'2': 'string key'}, 2) # +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... KeyError: 2
- dol.paths.handle_prefixes(store=None, *, prefix=None, filter_prefix=True, relativize_prefix=True, default_prefix='', __module__=None, __name__=None, __qualname__=None, __doc__=None, __annotations__=None, __defaults__=None, __kwdefaults__=None)[source]¶
A store decorator that handles prefixes.
If aggregates several prefix-related functionalities. It will (by default)
Filter the store so that only the keys starting with given prefix are accessible.
Relativize the keys (provide a view where the prefix is removed from the keys)
- Parameters:
store – The store to wrap
prefix – The prefix to use. If None and the store is an instance (not type), will take the longest common prefix as the prefix.
filter_prefix (
bool) – Whether to filter out keys that don’t start with the prefixrelativize_prefix (
bool) – Whether to relativize the prefixdefault_prefix – The default prefix to use if no prefix is given and the store is a type (not instance)
>>> d = {'/ROOT/of/every/thing': 42, '/ROOT/of/this/too': 0} >>> dd = handle_prefixes(d, prefix='/ROOT/of/') >>> dd['foo'] = 'bar' >>> dict(dd.items()) # gives us what you would expect {'every/thing': 42, 'this/too': 0, 'foo': 'bar'} >>> dict(dd.store) # but see where the underlying store actually wrote 'bar': {'/ROOT/of/every/thing': 42, '/ROOT/of/this/too': 0, '/ROOT/of/foo': 'bar'}
- dol.paths.keys_and_indices_path(str_path, *, sep='.', index_pattern='\\[(\\d+)\\]')[source]¶
Transforms a string path separated by a specified separator into a tuple of keys and indices. Bracketed indices are extracted as integers.
This function is meant to be used in as the key_transformer argument of path_get etc.
- Parameters:
- Returns:
A tuple representation of the path, e.g., (“a21-59c”, “message”, 2, “user”).
- Return type:
Example
>>> keys_and_indices_path("a21-59c.message[2].user") ('a21-59c', 'message', 2, 'user')
- dol.paths.leaf_paths(d, sep='.', *, parent_path=None, egress=<class 'dict'>)[source]¶
Get a dictionary of leaf paths of a nested dictionary.
Given a nested dictionary, returns a similarly structured dictionary where each leaf value is replaced by its flattened path. The ‘sep’ parameter can be either a string or a callable.
Original use case: You used flatten_dict to flatten a nested dictionary, referencing your values with paths, but maybe you’d like to know what the paths that your nested dictionary is going to flatten to are. This function does that. The output is a dict with the same keys and structure as the input, but the leaf values are replaced by the paths that would be used to access them in a flat dict.
- Parameters:
d (
Mapping[TypeVar(KT),Union[TypeVar(VT),Mapping[TypeVar(KT),Union[TypeVar(VT), NestedMapping[KT, VT]]]]]) – The nested dictionary to get the leaf paths fromsep (
Union[str,Callable[[TypeVar(Path),TypeVar(KT)],TypeVar(Path)]]) – The separator to use for joining keys, or a function that takes a path and a key and returns a new path.parent_path (
Optional[TypeVar(Path)]) – The path to the parent of the current dictegress (
Callable[[Generator[tuple[TypeVar(KT),TypeVar(VT)],None,None]],Mapping]) – A function that takes a generator of key-value pairs and returns a mapping
- Return type:
Example
>>> leaf_paths({'a': {'b': 2}, 'c': 3}) {'a': {'b': 'a.b'}, 'c': 'c'}
>>> leaf_paths({'a': {'b': 2}, 'c': 3}, sep="/") {'a': {'b': 'a/b'}, 'c': 'c'}
>>> leaf_paths({'a': {'b': 2}, 'c': 3}, sep=lambda p, k: f"{p}-{k}" if p else k) {'a': {'b': 'a-b'}, 'c': 'c'}
- dol.paths.mk_relative_path_store(store_cls=None, *, name=None, with_key_validation=False, prefix_attr='_prefix', __module__=None, __name__=None, __qualname__=None, __doc__=None, __annotations__=None, __defaults__=None, __kwdefaults__=None)[source]¶
- Parameters:
store_cls – The base store to wrap (subclass)
name – The name of the new store (by default ‘RelPath’ + store_cls.__name__)
with_key_validation – Whether keys should be validated upon access (store_cls must have an is_valid_key method
- Returns:
A new class that uses relative paths (i.e. where _prefix is automatically added to incoming keys, and the len(_prefix) first characters are removed from outgoing keys.
>>> # The dynamic way (if you try this at home, be aware of the pitfalls of the dynamic way >>> # -- but don't just believe the static dogmas). >>> MyStore = mk_relative_path_store(dict) # wrap our favorite store: A dict. >>> s = MyStore() # make such a store >>> s._prefix = '/ROOT/' >>> s['foo'] = 'bar' >>> dict(s.items()) # gives us what you would expect {'foo': 'bar'} >>> # but under the hood, the dict we wrapped actually contains the '/ROOT/' prefix >>> dict(s.store) {'/ROOT/foo': 'bar'} >>> >>> # The static way: Make a class that will integrate the _prefix at construction time. >>> class MyStore(mk_relative_path_store(dict)): # Indeed, mk_relative_path_store(dict) is a class you can subclass ... def __init__(self, _prefix, *args, **kwargs): ... self._prefix = _prefix
You can choose the name you want that prefix to have as an attribute (we’ll still make a hidden ‘_prefix’ attribute for internal use, but at least you can have an attribute with the name you want.
>>> MyRelStore = mk_relative_path_store(dict, prefix_attr='rootdir') >>> s = MyRelStore() >>> s.rootdir = '/ROOT/'
>>> s['foo'] = 'bar' >>> dict(s.items()) # gives us what you would expect {'foo': 'bar'} >>> # but under the hood, the dict we wrapped actually contains the '/ROOT/' prefix >>> dict(s.store) {'/ROOT/foo': 'bar'}
- dol.paths.path_edit(d, edits=())[source]¶
Make a series of (in place) edits to a Mapping, specifying
(path, value)pairs.- Parameters:
- Returns:
The edited mapping.
- Return type:
>>> d = {'a': 1} >>> path_edit(d, [(['b', 'c'], 2), ('d.e.f', 3)]) {'a': 1, 'b': {'c': 2}, 'd': {'e': {'f': 3}}}
Changes happened also inplace (so if you don’t want that, make a deepcopy first):
>>> d {'a': 1, 'b': {'c': 2}, 'd': {'e': {'f': 3}}}
You can also pass a dict of edits.
>>> path_edit(d, {'a': 4, 'd.e.f': 5}) {'a': 4, 'b': {'c': 2}, 'd': {'e': {'f': 5}}}
- dol.paths.path_filter(pkv_filt, d, *, leafs_only=True, breadth_first=False)[source]¶
Walk a dict, yielding paths to values that pass the
pkv_filt- Parameters:
pkv_filt (
Callable[[TypeVar(PT),TypeVar(KT),TypeVar(VT)],bool]) – A function that takes a path, key, and value, and returnsTrueif the path should be yielded, andFalseotherwised (
Mapping) – TheMappingto walk (scan through)leafs_only (
bool) – Whether to yield only paths to leafs (default), or to yield paths to all values that pass thepkv_filt.breadth_first (
bool) – Whether to perform breadth-first traversal (instead of the default depth-first traversal).
- Return type:
- Returns:
An iterator of paths to values that pass the
pkv_filt
Example
>>> d = {'a': {'b': {'c': 1, 'd': 2}, 'e': 3}} >>> list(path_filter(lambda p, k, v: v == 2, d)) [('a', 'b', 'd')]
>>> mm = { ... 'a': {'b': {'c': 42}}, ... 'aa': {'bb': {'cc': 'meaning of life'}}, ... 'aaa': {'bbb': 314}, ... } >>> return_path_if_int_leaf = lambda p, k, v: (p, v) if isinstance(v, int) else None >>> paths = list(path_filter(return_path_if_int_leaf, mm)) >>> paths # only the paths to the int leaves are returned [('a', 'b', 'c'), ('aaa', 'bbb')]
The
pkv_filtargument can use path, key, and/or value to define your search query. For example, let’s extract all the paths that have depth at least 3.>>> paths = list(path_filter(lambda p, k, v: len(p) >= 3, mm)) >>> paths [('a', 'b', 'c'), ('aa', 'bb', 'cc')]
The rationale for
path_filteryielding matching paths, and not values or keys, is that if you have the paths, you can than get the keys and values with them, usingpath_get.>>> from functools import partial, reduce >>> path_get = lambda m, k: reduce(lambda m, k: m[k], k, m) >>> extract_paths = lambda m, paths: map(partial(path_get, m), paths) >>> vals = list(extract_paths(mm, paths)) >>> vals [42, 'meaning of life']
Note
pkv_filt is first to match the order of the arguments of the builtin filter function.
- dol.paths.path_get(obj, path, on_error=<function raise_on_error>, *, sep=None, key_transformer=None, get_value=<function get_attr_or_item>, caught_errors=(<class 'Exception'>, ))[source]¶
Get elements of a mapping through a path to be called recursively.
- Parameters:
obj (
Any) – The object to get the path frompath – The path to get
on_error (
Union[Callable[[dict],Any],str]) – The error handler to use (default: raise_on_error)sep (
str|Callable|None) – Determines a path is transforms into a tuple of keys. If it’s a string,lambda path: path.split(sep)is used. If not, it should be a function which takes in a path object and returns an iterable of keys.key_transformer – A function to transform the keys of the path
get_value (
Callable) – A function to get the value of a key in a mappingcaught_errors – The errors to catch (default: Exception)
It will
split a path into keys (if sep is given, or if path is a string, will use ‘.’ as a separator by default)
if key_transformer is given, apply to each key
consider string keys that are numeric as ints (convenient for lists)
get items also as attributes (attributes are checked for first for string keys)
catch all exceptions (that are subclasses of
Exception)
>>> class A: ... an_attribute = 42 >>> path_get([1, [4, 5, {'a': A}], 3], [1, 2, 'a', 'an_attribute']) 42
By default, if
pathis a string, it will be split onsep, which is'.'by default.>>> path_get([1, [4, 5, {'a': A}], 3], '1.2.a.an_attribute') 42
Note
The underlying function is
_path_get, butpath_gethas defaults and flexible input processing for more convenience.Note
path_getcontains some ready-madeOnErrorTypefunctions in its attributes. For example, see how we can makepath_gethave the same behavior asdict.getby passingpath_get.return_none_on_errorason_error:>>> dd = path_get({}, 'no.keys', on_error=path_get.return_none_on_error) >>> dd is None True
For example,
path_get.raise_on_error,path_get.return_none_on_error, andpath_get.return_empty_tuple_on_error.
- dol.paths.paths_getter(paths, obj=None, *, egress=<class 'dict'>, on_error=<function raise_on_error>, sep=None, key_transformer=None, get_value=<function get_attr_or_item>, caught_errors=(<class 'Exception'>, ))[source]¶
Returns (path, values) pairs of the given paths in the given object. This is the “fan-out” version of
path_get, specifically designed to get multiple paths, returning the (path, value) pairs in a dict (by default), or via any pairs aggregator (egress) function.Note
For reasons who’s clarity is burried in historical legacy, the order of obj and path are the opposite of path_get.
- Parameters:
paths – The paths to get
obj – The object to get the paths from
egress – The egress function to use (default: dict)
on_error (
Union[Callable[[dict],Any],str]) – The error handler to use (default: raise_on_error)sep (
str|Callable|None) – The separator to use if the path is a stringkey_transformer – A function to transform the keys of the path
get_value (
Callable) – A function to get the value of a key in a mappingcaught_errors – The errors to catch (default: Exception)
>>> obj = {'a': {'b': 1, 'c': 2}, 'd': 3} >>> paths = ['a.c', 'd'] >>> paths_getter(paths, obj=obj) {'a.c': 2, 'd': 3} >>> path_extractor = paths_getter(paths) >>> path_extractor(obj) {'a.c': 2, 'd': 3}
See that the paths are used as the keys of the returned dict. If you want to specify your own keys, you can simply specify
pathsas a dict whose keys are the keys you want, and whose values are the paths to get:>>> path_extractor_2 = paths_getter({'california': 'a.c', 'dreaming': 'd'}) >>> path_extractor_2(obj) {'california': 2, 'dreaming': 3}
- dol.paths.prefixless_view(store=None, *, prefix=None, __module__=None, __name__=None, __qualname__=None, __doc__=None, __annotations__=None, __defaults__=None, __kwdefaults__=None)[source]¶
Wrap
storeso that keys are seen withoutprefix(added back on access).
- dol.paths.rel_path_wrap(o, _prefix)[source]¶
- Parameters:
o – An object to be wrapped
_prefix – The _prefix to use for key wrapping (will remove it from outcoming keys and add to ingoing keys.
>>> # The dynamic way (if you try this at home, be aware of the pitfalls of the dynamic way >>> # -- but don't just believe the static dogmas). >>> d = {'/ROOT/of/every/thing': 42, '/ROOT/of/this/too': 0} >>> dd = rel_path_wrap(d, '/ROOT/of/') >>> dd['foo'] = 'bar' >>> dict(dd.items()) # gives us what you would expect {'every/thing': 42, 'this/too': 0, 'foo': 'bar'} >>> # but under the hood, the dict we wrapped actually contains the '/ROOT/' prefix >>> dict(dd.store) {'/ROOT/of/every/thing': 42, '/ROOT/of/this/too': 0, '/ROOT/of/foo': 'bar'} >>> >>> # The static way: Make a class that will integrate the _prefix at construction time. >>> class MyStore(mk_relative_path_store(dict)): # Indeed, mk_relative_path_store(dict) is a class you can subclass ... def __init__(self, _prefix, *args, **kwargs): ... self._prefix = _prefix
- dol.paths.search_paths(d, pkv_filt, *, leafs_only=True, breadth_first=False)[source]¶
backwards compatibility quasi-alias (arguments are flipped) Use path_filter instead, since search_paths will be deprecated.
- dol.paths.separate_keys_with_separator(obj, sep='.')[source]¶
Split a string path on
sepand cast numeric parts toint; a non-string iterable is only cast element-wise.
- dol.paths.separator_based_path_extender(path, key, sep)[source]¶
Extends a given path with a new key using the specified separator. If the path is empty, the key is returned as is.
- Return type:
TypeVar(Path)
- dol.paths.split_if_str(obj, sep='.')[source]¶
Split
objonsepif it is a string; return it unchanged otherwise.