dotted-notation 0.49.1__tar.gz → 0.49.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.49.1 → dotted_notation-0.49.2}/CHANGELOG.md +15 -0
  2. {dotted_notation-0.49.1/dotted_notation.egg-info → dotted_notation-0.49.2}/PKG-INFO +18 -4
  3. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/README.md +17 -3
  4. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/access.py +3 -2
  5. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/api.py +5 -4
  6. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/base.py +60 -6
  7. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/filters.py +1 -1
  8. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/results.py +8 -7
  9. {dotted_notation-0.49.1 → dotted_notation-0.49.2/dotted_notation.egg-info}/PKG-INFO +18 -4
  10. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/pyproject.toml +1 -1
  11. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/LICENSE +0 -0
  12. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/MANIFEST.in +0 -0
  13. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/__init__.py +0 -0
  14. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/__main__.py +0 -0
  15. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/access.pxd +0 -0
  16. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/base.pxd +0 -0
  17. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/cli/__init__.py +0 -0
  18. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/cli/_compat.py +0 -0
  19. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/cli/formats.py +0 -0
  20. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/cli/main.py +0 -0
  21. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/containers.py +0 -0
  22. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/engine.pxd +0 -0
  23. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/engine.py +0 -0
  24. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/filters.pxd +0 -0
  25. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/grammar.py +0 -0
  26. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/groups.pxd +0 -0
  27. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/groups.py +0 -0
  28. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/matchers.pxd +0 -0
  29. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/matchers.py +0 -0
  30. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/native.py +0 -0
  31. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/predicates.py +0 -0
  32. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/recursive.pxd +0 -0
  33. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/recursive.py +0 -0
  34. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/sql/__init__.py +0 -0
  35. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/sql/core.py +0 -0
  36. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/sql/pg.py +0 -0
  37. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/transforms.py +0 -0
  38. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/utils.py +0 -0
  39. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/utypes.py +0 -0
  40. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/wrappers.pxd +0 -0
  41. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted/wrappers.py +0 -0
  42. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted_notation.egg-info/SOURCES.txt +0 -0
  43. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted_notation.egg-info/dependency_links.txt +0 -0
  44. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted_notation.egg-info/entry_points.txt +0 -0
  45. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted_notation.egg-info/requires.txt +0 -0
  46. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/dotted_notation.egg-info/top_level.txt +0 -0
  47. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/setup.cfg +0 -0
  48. {dotted_notation-0.49.1 → dotted_notation-0.49.2}/setup.py +0 -0
@@ -3,6 +3,21 @@
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.49.2]
7
+
8
+ ### Added
9
+ - `ReferenceResolver.literals` may name the positions a resolver is
10
+ offered plain tokens in, as a list or tuple of `'segments'`,
11
+ `'transforms'`, `'guards'` and `'filters'`. A host whose variables are
12
+ sigiled arguments, `|add:^fee`, leaves `'segments'` out and no longer
13
+ pays a call per key (#63); `get` then keeps its fast path for a plain
14
+ chain of keys. An unknown name is a `ValueError`. `True` and `False`
15
+ are unchanged.
16
+ - `ReferenceResolver(literals=...)` sets `literals` per instance,
17
+ overriding the class's, so a resolver need not be subclassed only to
18
+ change it. A subclass with its own `__init__` passes it on with
19
+ `super().__init__(literals)`.
20
+
6
21
  ## [0.49.1]
7
22
 
8
23
  ### Documentation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotted_notation
3
- Version: 0.49.1
3
+ Version: 0.49.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
@@ -1677,14 +1677,16 @@ claim some references and leave the rest:
1677
1677
  >>> dotted.get({'amount': 10}, 'amount|sub:$$(lots.taken)', resolver=Store(store))
1678
1678
  7
1679
1679
 
1680
- A resolver that sets `literals = True` is also offered every plain token in
1681
- those positions, so a host sigil such as `$sel` can be a variable. Return
1680
+ A resolver with `literals = True` is also offered every plain token in
1681
+ those positions, so a host sigil such as `$sel` can be a variable. Set it on
1682
+ the class, or per instance with `literals=` on the constructor. Return
1682
1683
  `ref.value` to leave a token the literal it is; a key the resolver answers
1683
1684
  is not offered again:
1684
1685
 
1685
1686
  >>> class Sigils(dotted.ReferenceResolver):
1686
1687
  ... literals = True
1687
- ... def __init__(self, names):
1688
+ ... def __init__(self, names, literals=None):
1689
+ ... super().__init__(literals)
1688
1690
  ... self.names = names
1689
1691
  ... def resolve(self, ref, settings, node=None, parents=()):
1690
1692
  ... if ref.is_reference():
@@ -1696,6 +1698,18 @@ is not offered again:
1696
1698
  >>> dotted.get(users, 'users[*&name=$sel].name', resolver=Sigils({'$sel': 'bob'}))
1697
1699
  ('bob',)
1698
1700
 
1701
+ `literals` may instead name the positions it wants, as a list or tuple of
1702
+ `'segments'` (key, slot and attr segments), `'transforms'` (transform
1703
+ arguments), `'guards'` (guard values) and `'filters'` (filter values). A host
1704
+ whose variables only ever stand as arguments leaves `'segments'` out, so the
1705
+ keys of a path cost no call:
1706
+
1707
+ >>> r = Sigils({'$inc': 3}, literals=('transforms', 'guards', 'filters'))
1708
+ >>> dotted.get({'n': 5}, 'n|add:$inc', resolver=r)
1709
+ 8
1710
+
1711
+ An unknown name is a `ValueError`, on the class or the constructor.
1712
+
1699
1713
  A host variable may span several segments, `$amount.value`, which dotted has
1700
1714
  parsed as two. The path-level hook `fold(segments)` is called once with a
1701
1715
  path's segment strings, `('$amount', 'value')`, and returns the runs it
@@ -1630,14 +1630,16 @@ claim some references and leave the rest:
1630
1630
  >>> dotted.get({'amount': 10}, 'amount|sub:$$(lots.taken)', resolver=Store(store))
1631
1631
  7
1632
1632
 
1633
- A resolver that sets `literals = True` is also offered every plain token in
1634
- those positions, so a host sigil such as `$sel` can be a variable. Return
1633
+ A resolver with `literals = True` is also offered every plain token in
1634
+ those positions, so a host sigil such as `$sel` can be a variable. Set it on
1635
+ the class, or per instance with `literals=` on the constructor. Return
1635
1636
  `ref.value` to leave a token the literal it is; a key the resolver answers
1636
1637
  is not offered again:
1637
1638
 
1638
1639
  >>> class Sigils(dotted.ReferenceResolver):
1639
1640
  ... literals = True
1640
- ... def __init__(self, names):
1641
+ ... def __init__(self, names, literals=None):
1642
+ ... super().__init__(literals)
1641
1643
  ... self.names = names
1642
1644
  ... def resolve(self, ref, settings, node=None, parents=()):
1643
1645
  ... if ref.is_reference():
@@ -1649,6 +1651,18 @@ is not offered again:
1649
1651
  >>> dotted.get(users, 'users[*&name=$sel].name', resolver=Sigils({'$sel': 'bob'}))
1650
1652
  ('bob',)
1651
1653
 
1654
+ `literals` may instead name the positions it wants, as a list or tuple of
1655
+ `'segments'` (key, slot and attr segments), `'transforms'` (transform
1656
+ arguments), `'guards'` (guard values) and `'filters'` (filter values). A host
1657
+ whose variables only ever stand as arguments leaves `'segments'` out, so the
1658
+ keys of a path cost no call:
1659
+
1660
+ >>> r = Sigils({'$inc': 3}, literals=('transforms', 'guards', 'filters'))
1661
+ >>> dotted.get({'n': 5}, 'n|add:$inc', resolver=r)
1662
+ 8
1663
+
1664
+ An unknown name is a `ValueError`, on the class or the constructor.
1665
+
1652
1666
  A host variable may span several segments, `$amount.value`, which dotted has
1653
1667
  parsed as two. The path-level hook `fold(segments)` is called once with a
1654
1668
  path's segment strings, `('$amount', 'value')`, and returns the runs it
@@ -251,11 +251,12 @@ class AccessOp(SimpleOp):
251
251
  def _offered(self, settings):
252
252
  """
253
253
  True if the match op goes to the resolver: a reference, or a
254
- plain constant when the resolver in force takes literals.
254
+ plain constant when the resolver in force takes literals in path
255
+ segments.
255
256
  """
256
257
  if self.op.is_reference():
257
258
  return True
258
- return (settings.literals() and isinstance(self.op, matchers.Const)
259
+ return (settings.literals('segments') and isinstance(self.op, matchers.Const)
259
260
  and not isinstance(self.op, matchers.ResolvedValue))
260
261
 
261
262
  def resolve(self, bindings, partial=False):
@@ -448,9 +448,11 @@ def build(obj, path, strict=False, bindings=None, resolver=None):
448
448
  def _literals(resolver):
449
449
  """
450
450
  True if the resolver a call will run under, its own or the process
451
- default, takes plain tokens: the simple fast path must then step aside.
451
+ default, takes plain tokens in path segments: the simple fast path must
452
+ then step aside. Other positions it can serve, as its transforms
453
+ resolve through Settings.
452
454
  """
453
- return (base.RESOLVER if resolver is None else resolver).literals
455
+ return base.takes_literals(base.RESOLVER if resolver is None else resolver, 'segments')
454
456
 
455
457
 
456
458
  def _prepare(ops, resolver):
@@ -1365,12 +1367,11 @@ def set_resolver(resolver=None):
1365
1367
  restores dotted's own (see Resolver).
1366
1368
  >>> import dotted
1367
1369
  >>> class Upper(dotted.ReferenceResolver):
1368
- ... literals = True
1369
1370
  ... def resolve(self, ref, settings, node=None, parents=()):
1370
1371
  ... if ref.is_reference():
1371
1372
  ... return super().resolve(ref, settings, node, parents)
1372
1373
  ... return ref.value.upper()
1373
- >>> dotted.set_resolver(Upper())
1374
+ >>> dotted.set_resolver(Upper(literals=True))
1374
1375
  >>> dotted.get({'A': 1}, 'a')
1375
1376
  1
1376
1377
  >>> dotted.set_resolver()
@@ -149,6 +149,12 @@ class NOP(metaclass=MetaNOP):
149
149
  return False
150
150
 
151
151
 
152
+ # The positions a resolver may be offered plain tokens in, named in its
153
+ # literals: key, slot and attr segments, transform arguments, guard values
154
+ # and filter values.
155
+ LITERALS = ('segments', 'transforms', 'guards', 'filters')
156
+
157
+
152
158
  class Resolver:
153
159
  """
154
160
  What a reference means at run time. Every $$(path) a traversal meets,
@@ -161,10 +167,30 @@ class Resolver:
161
167
 
162
168
  # True to be offered every plain token in a var-permitting position as
163
169
  # well: key, slot and attr segments, transform arguments, guard and
164
- # filter values. The host decides which are its variables; returning
165
- # ref.value leaves a token the literal it is.
170
+ # filter values. A list or tuple of LITERALS names to be offered them in
171
+ # those positions only, ('transforms', 'guards', 'filters') to leave
172
+ # segments out. The host decides which are its variables; returning
173
+ # ref.value leaves a token the literal it is. Set here, on a subclass,
174
+ # or per instance with literals=.
166
175
  literals = False
167
176
 
177
+ def __init_subclass__(cls, **kwargs):
178
+ """
179
+ Check a subclass's literals (see check_literals).
180
+ """
181
+ super().__init_subclass__(**kwargs)
182
+ check_literals(cls.literals)
183
+
184
+ def __init__(self, literals=None):
185
+ """
186
+ literals, when given, overrides the class's for this instance (see
187
+ check_literals).
188
+ """
189
+ if literals is None:
190
+ return
191
+ check_literals(literals)
192
+ self.literals = literals
193
+
168
194
  def fold(self, segments):
169
195
  """
170
196
  The path-level hook, called once with a path's segment strings
@@ -211,6 +237,33 @@ class Resolver:
211
237
  RESOLVER = Resolver()
212
238
 
213
239
 
240
+ def check_literals(literals):
241
+ """
242
+ Raise unless literals is True, False, or a list or tuple of LITERALS
243
+ names.
244
+ """
245
+ if isinstance(literals, bool):
246
+ return
247
+ if not isinstance(literals, (list, tuple)):
248
+ raise TypeError(f'literals is True, False or a list or tuple of {LITERALS}, not {literals!r}')
249
+ unknown = [name for name in literals if name not in LITERALS]
250
+ if unknown:
251
+ raise ValueError(f'literals names {unknown}, not among {LITERALS}')
252
+
253
+
254
+ def takes_literals(resolver, position):
255
+ """
256
+ True if resolver takes plain tokens at position, one of LITERALS: its
257
+ literals is True, or names it.
258
+ """
259
+ lit = resolver.literals
260
+ if lit is True:
261
+ return True
262
+ if not lit:
263
+ return False
264
+ return position in lit
265
+
266
+
214
267
  class Settings:
215
268
  """
216
269
  What holds for the whole of one traversal: the root node, the chain of
@@ -242,11 +295,12 @@ class Settings:
242
295
  """
243
296
  return RESOLVER if self.resolver is None else self.resolver
244
297
 
245
- def literals(self):
298
+ def literals(self, position):
246
299
  """
247
- True if the resolver in force wants plain tokens offered too.
300
+ True if the resolver in force wants plain tokens at position, one
301
+ of LITERALS, offered too.
248
302
  """
249
- return self.resolving().literals
303
+ return takes_literals(self.resolving(), position)
250
304
 
251
305
  def resolve(self, ref, node=None, parents=()):
252
306
  """
@@ -551,7 +605,7 @@ class Transform(Op):
551
605
  return ()
552
606
  if settings is None:
553
607
  settings = SETTINGS
554
- literals = settings.literals()
608
+ literals = settings.literals('transforms')
555
609
  args = []
556
610
  for p in self.params:
557
611
  if isinstance(p, Op) and (p.is_reference() or (literals and isinstance(p, matchers.Const))):
@@ -285,7 +285,7 @@ class FilterKeyValue(FilterOp):
285
285
  val = results.apply_transforms(val, self.guard_transforms, settings, parents)
286
286
  except base.UnresolvedReference:
287
287
  continue
288
- if results.guard_passes(pred_op, val, self.val, settings, val, parents):
288
+ if results.guard_passes(pred_op, val, self.val, settings, val, parents, 'filters'):
289
289
  return True
290
290
  return False
291
291
 
@@ -256,18 +256,19 @@ def apply_transforms(val, transforms, settings=base.SETTINGS, parents=()):
256
256
  return val
257
257
 
258
258
 
259
- def guard_matcher(guard, settings=base.SETTINGS, node=None, parents=()):
259
+ def guard_matcher(guard, settings=base.SETTINGS, node=None, parents=(), position='guards'):
260
260
  """
261
261
  The match op a guard or filter value compares with: the value as
262
262
  parsed, or a reference resolved through the traversal's resolver
263
263
  against node and parents, node being the value under test (so
264
- $$(^x) looks x up in it). A reference path that is a pattern becomes
265
- a value group of what it resolves to. Raises UnresolvedReference when
266
- the reference does not resolve.
264
+ $$(^x) looks x up in it). A plain value is resolved too when the
265
+ resolver takes literals at position, 'guards' or 'filters'. A reference
266
+ path that is a pattern becomes a value group of what it resolves to.
267
+ Raises UnresolvedReference when the reference does not resolve.
267
268
  """
268
269
  # container values (ContainerList and kin) are ops without is_reference
269
270
  reference = hasattr(guard, 'is_reference') and guard.is_reference()
270
- if not (reference or (settings.literals() and isinstance(guard, Const))):
271
+ if not (reference or (settings.literals(position) and isinstance(guard, Const))):
271
272
  return guard
272
273
  val = settings.resolve(guard, node, parents)
273
274
  if not guard.is_pattern():
@@ -276,13 +277,13 @@ def guard_matcher(guard, settings=base.SETTINGS, node=None, parents=()):
276
277
  return containers.ValueGroup(*(Const(v) for v in val))
277
278
 
278
279
 
279
- def guard_passes(pred_op, val, guard, settings=base.SETTINGS, node=None, parents=()):
280
+ def guard_passes(pred_op, val, guard, settings=base.SETTINGS, node=None, parents=(), position='guards'):
280
281
  """
281
282
  True if val satisfies pred_op against guard (see guard_matcher); a
282
283
  guard reference that does not resolve passes nothing.
283
284
  """
284
285
  try:
285
- matcher = guard_matcher(guard, settings, node, parents)
286
+ matcher = guard_matcher(guard, settings, node, parents, position)
286
287
  except base.UnresolvedReference:
287
288
  return False
288
289
  return any(True for _ in pred_op.matches((val,), matcher))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotted_notation
3
- Version: 0.49.1
3
+ Version: 0.49.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
@@ -1677,14 +1677,16 @@ claim some references and leave the rest:
1677
1677
  >>> dotted.get({'amount': 10}, 'amount|sub:$$(lots.taken)', resolver=Store(store))
1678
1678
  7
1679
1679
 
1680
- A resolver that sets `literals = True` is also offered every plain token in
1681
- those positions, so a host sigil such as `$sel` can be a variable. Return
1680
+ A resolver with `literals = True` is also offered every plain token in
1681
+ those positions, so a host sigil such as `$sel` can be a variable. Set it on
1682
+ the class, or per instance with `literals=` on the constructor. Return
1682
1683
  `ref.value` to leave a token the literal it is; a key the resolver answers
1683
1684
  is not offered again:
1684
1685
 
1685
1686
  >>> class Sigils(dotted.ReferenceResolver):
1686
1687
  ... literals = True
1687
- ... def __init__(self, names):
1688
+ ... def __init__(self, names, literals=None):
1689
+ ... super().__init__(literals)
1688
1690
  ... self.names = names
1689
1691
  ... def resolve(self, ref, settings, node=None, parents=()):
1690
1692
  ... if ref.is_reference():
@@ -1696,6 +1698,18 @@ is not offered again:
1696
1698
  >>> dotted.get(users, 'users[*&name=$sel].name', resolver=Sigils({'$sel': 'bob'}))
1697
1699
  ('bob',)
1698
1700
 
1701
+ `literals` may instead name the positions it wants, as a list or tuple of
1702
+ `'segments'` (key, slot and attr segments), `'transforms'` (transform
1703
+ arguments), `'guards'` (guard values) and `'filters'` (filter values). A host
1704
+ whose variables only ever stand as arguments leaves `'segments'` out, so the
1705
+ keys of a path cost no call:
1706
+
1707
+ >>> r = Sigils({'$inc': 3}, literals=('transforms', 'guards', 'filters'))
1708
+ >>> dotted.get({'n': 5}, 'n|add:$inc', resolver=r)
1709
+ 8
1710
+
1711
+ An unknown name is a `ValueError`, on the class or the constructor.
1712
+
1699
1713
  A host variable may span several segments, `$amount.value`, which dotted has
1700
1714
  parsed as two. The path-level hook `fold(segments)` is called once with a
1701
1715
  path's segment strings, `('$amount', 'value')`, and returns the runs it
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "dotted_notation"
9
- version = "0.49.1"
9
+ version = "0.49.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"