dotted-notation 0.51.0__tar.gz → 0.51.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/CHANGELOG.md +20 -0
  2. {dotted_notation-0.51.0/dotted_notation.egg-info → dotted_notation-0.51.2}/PKG-INFO +21 -8
  3. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/README.md +20 -7
  4. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/__init__.py +2 -2
  5. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/api.py +43 -1
  6. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/base.py +4 -3
  7. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/grammar.py +5 -2
  8. {dotted_notation-0.51.0 → dotted_notation-0.51.2/dotted_notation.egg-info}/PKG-INFO +21 -8
  9. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/pyproject.toml +1 -1
  10. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/LICENSE +0 -0
  11. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/MANIFEST.in +0 -0
  12. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/__main__.py +0 -0
  13. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/access.pxd +0 -0
  14. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/access.py +0 -0
  15. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/base.pxd +0 -0
  16. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/cli/__init__.py +0 -0
  17. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/cli/_compat.py +0 -0
  18. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/cli/formats.py +0 -0
  19. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/cli/main.py +0 -0
  20. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/containers.py +0 -0
  21. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/engine.pxd +0 -0
  22. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/engine.py +0 -0
  23. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/filters.pxd +0 -0
  24. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/filters.py +0 -0
  25. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/groups.pxd +0 -0
  26. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/groups.py +0 -0
  27. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/matchers.pxd +0 -0
  28. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/matchers.py +0 -0
  29. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/native.py +0 -0
  30. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/predicates.py +0 -0
  31. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/recursive.pxd +0 -0
  32. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/recursive.py +0 -0
  33. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/results.py +0 -0
  34. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/sql/__init__.py +0 -0
  35. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/sql/core.py +0 -0
  36. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/sql/pg.py +0 -0
  37. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/transforms.py +0 -0
  38. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/utils.py +0 -0
  39. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/utypes.py +0 -0
  40. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/wrappers.pxd +0 -0
  41. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted/wrappers.py +0 -0
  42. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted_notation.egg-info/SOURCES.txt +0 -0
  43. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted_notation.egg-info/dependency_links.txt +0 -0
  44. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted_notation.egg-info/entry_points.txt +0 -0
  45. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted_notation.egg-info/requires.txt +0 -0
  46. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/dotted_notation.egg-info/top_level.txt +0 -0
  47. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/setup.cfg +0 -0
  48. {dotted_notation-0.51.0 → dotted_notation-0.51.2}/setup.py +0 -0
@@ -3,6 +3,26 @@
3
3
  All notable changes to `dotted` are recorded here. Versions prior to
4
4
  the ones listed are omitted — browse git history for earlier entries.
5
5
 
6
+ ## [0.51.2]
7
+
8
+ ### Added
9
+ - `getter(path, ...)`: a read of `path` as a callable `obj -> value`,
10
+ returning what `get(obj, path, ...)` would, with the parse, the
11
+ resolver's `lex`, `fold` and `literals` and the fast-path decision
12
+ settled once when it is made, so repeated reads skip `get`'s setup.
13
+
14
+ ### Fixed
15
+ - On `get`'s fast path, a resolver answering a reference in a trailing
16
+ transform's argument saw `settings.strict` as `False` whatever the
17
+ call's `strict`.
18
+
19
+ ## [0.51.1]
20
+
21
+ ### Added
22
+ - `ReferenceResolver.lex` is offered a bare transform argument too,
23
+ `|sub:taken`, in the position `'transforms'`. Answering `None` leaves
24
+ it the plain string it was.
25
+
6
26
  ## [0.51.0]
7
27
 
8
28
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotted_notation
3
- Version: 0.51.0
3
+ Version: 0.51.2
4
4
  Summary: Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms
5
5
  Author-email: Frey Waid <logophage1@gmail.com>
6
6
  License-Expression: MIT
@@ -437,6 +437,17 @@ Get the value at a dotted path. See the Paths, Patterns, and Operators sections
437
437
  >>> dotted.get({'a': {'b': {'c': {'d': 'nested'}}}}, 'a.b.c.d')
438
438
  'nested'
439
439
 
440
+ A path read many times can be made into a **getter** once: a callable
441
+ `obj -> value` that returns what `get` would, with the parse, the resolver's
442
+ hooks and the fast-path decision settled when it is made, against the
443
+ resolver in force then. It takes `get`'s arguments, the object aside.
444
+
445
+ >>> d_of = dotted.getter('a.b.c.d', default='none')
446
+ >>> d_of({'a': {'b': {'c': {'d': 'nested'}}}})
447
+ 'nested'
448
+ >>> d_of({})
449
+ 'none'
450
+
440
451
  <a id="update"></a>
441
452
  ### Update
442
453
 
@@ -1771,12 +1782,14 @@ path's segment values, and a slice of a parsed path, `ops[1:]`, is a path.
1771
1782
 
1772
1783
  A host's own names may stand where dotted has no reading for a bare token:
1773
1784
  a slot, `merged[k]`, a slice bound, `items[:n]`, a filter value,
1774
- `[*&cur=want]`, or a guard value, `a=k`. The parse-time hook
1775
- `lex(text, position)` is offered each such token as written, a dotted name
1776
- whole, with its position, one of `'slots'`, `'slices'`, `'filters'` or
1777
- `'guards'`. It answers with dotted text to read in the token's place, `None`
1778
- to leave the `ParseError`, or raises to fail the parse at the token. The
1779
- answer must be one token dotted reads there, and is not offered again.
1785
+ `[*&cur=want]`, a guard value, `a=k`, or a transform argument,
1786
+ `|sub:taken`. The parse-time hook `lex(text, position)` is offered each such
1787
+ token as written, a dotted name whole, with its position, one of `'slots'`,
1788
+ `'slices'`, `'filters'`, `'guards'` or `'transforms'`. It answers with dotted
1789
+ text to read in the token's place, `None` to leave the `ParseError` (a
1790
+ transform argument stays the plain string it was), or raises to fail the
1791
+ parse at the token. The answer must be one token dotted reads there, and is
1792
+ not offered again.
1780
1793
 
1781
1794
  >>> class Names(dotted.ReferenceResolver):
1782
1795
  ... def lex(self, text, position):
@@ -1787,7 +1800,7 @@ answer must be one token dotted reads there, and is not offered again.
1787
1800
  >>> dotted.get(d, 'merged[c.account]', resolver=Names())
1788
1801
  2
1789
1802
 
1790
- A token dotted reads, `m[$x]` or `|sub:taken`, is never offered to `lex()`;
1803
+ A token dotted reads, `m[$x]` or `|sub:$taken`, is never offered to `lex()`;
1791
1804
  a host that means something else by one remaps it at run time, through
1792
1805
  `literals`. `parse` takes `resolver=` too, and a path is cached per resolver
1793
1806
  that lexes.
@@ -389,6 +389,17 @@ Get the value at a dotted path. See the Paths, Patterns, and Operators sections
389
389
  >>> dotted.get({'a': {'b': {'c': {'d': 'nested'}}}}, 'a.b.c.d')
390
390
  'nested'
391
391
 
392
+ A path read many times can be made into a **getter** once: a callable
393
+ `obj -> value` that returns what `get` would, with the parse, the resolver's
394
+ hooks and the fast-path decision settled when it is made, against the
395
+ resolver in force then. It takes `get`'s arguments, the object aside.
396
+
397
+ >>> d_of = dotted.getter('a.b.c.d', default='none')
398
+ >>> d_of({'a': {'b': {'c': {'d': 'nested'}}}})
399
+ 'nested'
400
+ >>> d_of({})
401
+ 'none'
402
+
392
403
  <a id="update"></a>
393
404
  ### Update
394
405
 
@@ -1723,12 +1734,14 @@ path's segment values, and a slice of a parsed path, `ops[1:]`, is a path.
1723
1734
 
1724
1735
  A host's own names may stand where dotted has no reading for a bare token:
1725
1736
  a slot, `merged[k]`, a slice bound, `items[:n]`, a filter value,
1726
- `[*&cur=want]`, or a guard value, `a=k`. The parse-time hook
1727
- `lex(text, position)` is offered each such token as written, a dotted name
1728
- whole, with its position, one of `'slots'`, `'slices'`, `'filters'` or
1729
- `'guards'`. It answers with dotted text to read in the token's place, `None`
1730
- to leave the `ParseError`, or raises to fail the parse at the token. The
1731
- answer must be one token dotted reads there, and is not offered again.
1737
+ `[*&cur=want]`, a guard value, `a=k`, or a transform argument,
1738
+ `|sub:taken`. The parse-time hook `lex(text, position)` is offered each such
1739
+ token as written, a dotted name whole, with its position, one of `'slots'`,
1740
+ `'slices'`, `'filters'`, `'guards'` or `'transforms'`. It answers with dotted
1741
+ text to read in the token's place, `None` to leave the `ParseError` (a
1742
+ transform argument stays the plain string it was), or raises to fail the
1743
+ parse at the token. The answer must be one token dotted reads there, and is
1744
+ not offered again.
1732
1745
 
1733
1746
  >>> class Names(dotted.ReferenceResolver):
1734
1747
  ... def lex(self, text, position):
@@ -1739,7 +1752,7 @@ answer must be one token dotted reads there, and is not offered again.
1739
1752
  >>> dotted.get(d, 'merged[c.account]', resolver=Names())
1740
1753
  2
1741
1754
 
1742
- A token dotted reads, `m[$x]` or `|sub:taken`, is never offered to `lex()`;
1755
+ A token dotted reads, `m[$x]` or `|sub:$taken`, is never offered to `lex()`;
1743
1756
  a host that means something else by one remaps it at run time, through
1744
1757
  `literals`. `parse` takes `resolver=` too, and a path is cached per resolver
1745
1758
  that lexes.
@@ -71,7 +71,7 @@ from .api import \
71
71
  expand, expand_multi, \
72
72
  match, match_multi, replace, overlaps, translate, translate_multi, \
73
73
  apply, apply_multi, \
74
- get, get_multi, \
74
+ get, get_multi, getter, \
75
75
  has, setdefault, setdefault_multi, \
76
76
  update, update_multi, pack, update_if, update_if_multi, \
77
77
  remove, remove_multi, remove_if, remove_if_multi, \
@@ -82,7 +82,7 @@ from .base import Resolver as ReferenceResolver, UnresolvedReference
82
82
 
83
83
  __all__ = [
84
84
  # Core
85
- 'get', 'update', 'remove', 'has', 'setdefault',
85
+ 'get', 'getter', 'update', 'remove', 'has', 'setdefault',
86
86
  'ReferenceResolver', 'UnresolvedReference', 'set_resolver', 'get_resolver',
87
87
  # Multi
88
88
  'get_multi', 'update_multi', 'pack', 'update_if', 'update_if_multi', 'remove_multi', 'remove_if', 'remove_if_multi', 'setdefault_multi',
@@ -511,8 +511,25 @@ def get(obj, path, default=None, pattern_default=(), apply_transforms=True, stri
511
511
  >>> get({'a': {'b': 7, 'c': 3}}, '**=7')
512
512
  (7,)
513
513
  """
514
+ ops, chain = _get_setup(path, bindings, resolver)
515
+ return _get_read(obj, ops, chain, default, pattern_default, apply_transforms, strict, resolver)
516
+
517
+
518
+ def _get_setup(path, bindings, resolver):
519
+ """
520
+ What a read of path settles before it sees an object: the parsed path,
521
+ with its resolver's lex and fold, and the fast path's chain, None when
522
+ the read takes the full traversal.
523
+ """
514
524
  ops = _prepare(parse(path, bindings=bindings, partial=False, resolver=resolver), resolver)
515
525
  chain = ops.simple_chain if _SIMPLE_FASTPATH and not _literals(resolver) else None
526
+ return ops, chain
527
+
528
+
529
+ def _get_read(obj, ops, chain, default, pattern_default, apply_transforms, strict, resolver):
530
+ """
531
+ A read of obj through ops, set up by _get_setup.
532
+ """
516
533
  if chain is not None:
517
534
  val = engine.simple_get(chain, obj, strict=strict)
518
535
  if val is not engine.SIMPLE_BAIL:
@@ -521,7 +538,7 @@ def get(obj, path, default=None, pattern_default=(), apply_transforms=True, stri
521
538
  if not (apply_transforms and ops.transforms):
522
539
  return val
523
540
  try:
524
- return ops.apply(val, base.Settings(obj, resolver=resolver))
541
+ return ops.apply(val, base.Settings(obj, strict, resolver=resolver))
525
542
  except base.UnresolvedReference:
526
543
  return default
527
544
  found = tuple(engine.values(ops, obj, strict=strict, resolver=resolver, transforms=apply_transforms))
@@ -530,6 +547,31 @@ def get(obj, path, default=None, pattern_default=(), apply_transforms=True, stri
530
547
  return found if found else pattern_default
531
548
 
532
549
 
550
+ def getter(path, default=None, pattern_default=(), apply_transforms=True, strict=False, bindings=None, resolver=None):
551
+ """
552
+ A read of path as a callable obj -> value, as get(obj, path, ...)
553
+ returns it. The parse, the resolver's lex, fold and literals and the
554
+ fast-path decision are settled once, here, against the resolver in
555
+ force now, so a call is the read and nothing else.
556
+
557
+ >>> first = getter('hello.there[0]')
558
+ >>> first({'hello': {'there': [1, 2]}})
559
+ 1
560
+ >>> getter('[*].a')([{'a': 1}, {'a': 2}])
561
+ (1, 2)
562
+ """
563
+ r = base.RESOLVER if resolver is None else resolver
564
+ ops, chain = _get_setup(path, bindings, r)
565
+
566
+ def read(obj):
567
+ """
568
+ obj read through the getter's path.
569
+ """
570
+ return _get_read(obj, ops, chain, default, pattern_default, apply_transforms, strict, r)
571
+
572
+ return read
573
+
574
+
533
575
  def get_multi(obj, iterable, apply_transforms=True, strict=False, bindings=None, resolver=None):
534
576
  """
535
577
  Get all values from obj in iterable; return iterable
@@ -155,8 +155,8 @@ class NOP(metaclass=MetaNOP):
155
155
  LITERALS = ('segments', 'transforms', 'guards', 'filters')
156
156
 
157
157
  # The positions a resolver's lex() may be offered a token in: a slot, a
158
- # slice bound, a filter value and a guard value.
159
- LEXED = ('slots', 'slices', 'filters', 'guards')
158
+ # slice bound, a filter value, a guard value and a transform argument.
159
+ LEXED = ('slots', 'slices', 'filters', 'guards', 'transforms')
160
160
 
161
161
 
162
162
  class Resolver:
@@ -203,7 +203,8 @@ class Resolver:
203
203
  position it stands in, one of LEXED. A dotted name is one token,
204
204
  merged[a.b] offering 'a.b'. It answers with dotted text to read
205
205
  in the token's place, '$$(k)' making the path merged[$$(k)], or
206
- None to leave the path unparsed; raising fails the parse at the
206
+ None to leave the path unparsed (a transform argument, |sub:k,
207
+ a plain string, as it was); raising fails the parse at the
207
208
  token. The text is read in that position as one token, and is
208
209
  not offered to lex() again. A token dotted reads is never
209
210
  offered: a host that means something else by one says so at run
@@ -686,8 +686,11 @@ guard_value = (value + _ends) | Lexed('guards', value, _value_stops, end=True)
686
686
  _key_atom = _commons | numeric_extended | non_integer | numeric_key | word
687
687
  _slot_atom = _commons | numeric_extended | numeric_slot
688
688
 
689
- # Transform: |name or |name:param — defined early so filters and guards can reference it
690
- targ = concrete_value | _arg_sigil | var | quoted | ppc.number | none | true | false | pp.CharsNotIn('|:')
689
+ # Transform: |name or |name:param — defined early so filters and guards can reference it.
690
+ # A param dotted has no reading for is offered to the lexer before it is
691
+ # taken as a plain string, so a lexer answering None leaves it one.
692
+ _targ_read = concrete_value | _arg_sigil | var | quoted | ppc.number | none | true | false
693
+ targ = (_targ_read + _ends) | Lexed('transforms', _targ_read, '|:', end=True) | pp.CharsNotIn('|:')
691
694
  param = (colon + targ) | colon.copy().set_parse_action(lambda: [None])
692
695
  transform = (transform_name.copy() + ZM(param)).set_parse_action(lambda s, loc, t: base.Transform(*t))
693
696
  transforms = ZM(pipe + transform)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotted_notation
3
- Version: 0.51.0
3
+ Version: 0.51.2
4
4
  Summary: Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms
5
5
  Author-email: Frey Waid <logophage1@gmail.com>
6
6
  License-Expression: MIT
@@ -437,6 +437,17 @@ Get the value at a dotted path. See the Paths, Patterns, and Operators sections
437
437
  >>> dotted.get({'a': {'b': {'c': {'d': 'nested'}}}}, 'a.b.c.d')
438
438
  'nested'
439
439
 
440
+ A path read many times can be made into a **getter** once: a callable
441
+ `obj -> value` that returns what `get` would, with the parse, the resolver's
442
+ hooks and the fast-path decision settled when it is made, against the
443
+ resolver in force then. It takes `get`'s arguments, the object aside.
444
+
445
+ >>> d_of = dotted.getter('a.b.c.d', default='none')
446
+ >>> d_of({'a': {'b': {'c': {'d': 'nested'}}}})
447
+ 'nested'
448
+ >>> d_of({})
449
+ 'none'
450
+
440
451
  <a id="update"></a>
441
452
  ### Update
442
453
 
@@ -1771,12 +1782,14 @@ path's segment values, and a slice of a parsed path, `ops[1:]`, is a path.
1771
1782
 
1772
1783
  A host's own names may stand where dotted has no reading for a bare token:
1773
1784
  a slot, `merged[k]`, a slice bound, `items[:n]`, a filter value,
1774
- `[*&cur=want]`, or a guard value, `a=k`. The parse-time hook
1775
- `lex(text, position)` is offered each such token as written, a dotted name
1776
- whole, with its position, one of `'slots'`, `'slices'`, `'filters'` or
1777
- `'guards'`. It answers with dotted text to read in the token's place, `None`
1778
- to leave the `ParseError`, or raises to fail the parse at the token. The
1779
- answer must be one token dotted reads there, and is not offered again.
1785
+ `[*&cur=want]`, a guard value, `a=k`, or a transform argument,
1786
+ `|sub:taken`. The parse-time hook `lex(text, position)` is offered each such
1787
+ token as written, a dotted name whole, with its position, one of `'slots'`,
1788
+ `'slices'`, `'filters'`, `'guards'` or `'transforms'`. It answers with dotted
1789
+ text to read in the token's place, `None` to leave the `ParseError` (a
1790
+ transform argument stays the plain string it was), or raises to fail the
1791
+ parse at the token. The answer must be one token dotted reads there, and is
1792
+ not offered again.
1780
1793
 
1781
1794
  >>> class Names(dotted.ReferenceResolver):
1782
1795
  ... def lex(self, text, position):
@@ -1787,7 +1800,7 @@ answer must be one token dotted reads there, and is not offered again.
1787
1800
  >>> dotted.get(d, 'merged[c.account]', resolver=Names())
1788
1801
  2
1789
1802
 
1790
- A token dotted reads, `m[$x]` or `|sub:taken`, is never offered to `lex()`;
1803
+ A token dotted reads, `m[$x]` or `|sub:$taken`, is never offered to `lex()`;
1791
1804
  a host that means something else by one remaps it at run time, through
1792
1805
  `literals`. `parse` takes `resolver=` too, and a path is cached per resolver
1793
1806
  that lexes.
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "dotted_notation"
9
- version = "0.51.0"
9
+ version = "0.51.2"
10
10
  description = "Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.6"