dotted-notation 0.49.0__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.
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/CHANGELOG.md +24 -0
- {dotted_notation-0.49.0/dotted_notation.egg-info → dotted_notation-0.49.2}/PKG-INFO +37 -8
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/README.md +36 -7
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/access.py +3 -2
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/api.py +5 -4
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/base.py +60 -6
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/filters.py +1 -1
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/results.py +8 -7
- {dotted_notation-0.49.0 → dotted_notation-0.49.2/dotted_notation.egg-info}/PKG-INFO +37 -8
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/pyproject.toml +1 -1
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/setup.py +15 -1
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/LICENSE +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/MANIFEST.in +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/__init__.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/__main__.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/access.pxd +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/base.pxd +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/cli/__init__.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/cli/_compat.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/cli/formats.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/cli/main.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/containers.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/engine.pxd +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/engine.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/filters.pxd +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/grammar.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/groups.pxd +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/groups.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/matchers.pxd +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/matchers.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/native.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/predicates.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/recursive.pxd +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/recursive.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/sql/__init__.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/sql/core.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/sql/pg.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/transforms.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/utils.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/utypes.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/wrappers.pxd +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted/wrappers.py +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted_notation.egg-info/SOURCES.txt +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted_notation.egg-info/dependency_links.txt +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted_notation.egg-info/entry_points.txt +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted_notation.egg-info/requires.txt +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted_notation.egg-info/top_level.txt +0 -0
- {dotted_notation-0.49.0 → dotted_notation-0.49.2}/setup.cfg +0 -0
|
@@ -3,6 +3,30 @@
|
|
|
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
|
+
|
|
21
|
+
## [0.49.1]
|
|
22
|
+
|
|
23
|
+
### Documentation
|
|
24
|
+
- The README describes a reference, `$$(expr)`, as an inline evaluation
|
|
25
|
+
expression: the dotted expression inside, transforms included, is
|
|
26
|
+
evaluated during traversal and its value stands where it is written. In a
|
|
27
|
+
transform argument, guard or filter value that is the value itself; in an
|
|
28
|
+
access position it is the key looked up.
|
|
29
|
+
|
|
6
30
|
## [0.49.0]
|
|
7
31
|
|
|
8
32
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotted_notation
|
|
3
|
-
Version: 0.49.
|
|
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
|
|
@@ -1396,7 +1396,8 @@ To chain through the items, use a pattern instead:
|
|
|
1396
1396
|
## Substitutions and References
|
|
1397
1397
|
|
|
1398
1398
|
All `$`-prefixed syntax falls into two categories: **substitutions** (resolved
|
|
1399
|
-
at replace time) and **references
|
|
1399
|
+
at replace time) and **references**, inline expressions evaluated during
|
|
1400
|
+
traversal.
|
|
1400
1401
|
|
|
1401
1402
|
| Syntax | Type | Resolved against |
|
|
1402
1403
|
|---|---|---|
|
|
@@ -1502,14 +1503,22 @@ the parsed result, so a path used many times can be compiled once:
|
|
|
1502
1503
|
<a id="references"></a>
|
|
1503
1504
|
### References
|
|
1504
1505
|
|
|
1505
|
-
A **reference
|
|
1506
|
-
|
|
1506
|
+
A **reference**, `$$(expr)`, is an inline evaluation expression. The dotted
|
|
1507
|
+
expression inside is evaluated during traversal, as `get` would evaluate it
|
|
1508
|
+
against the root, and its value stands where the reference is written. The
|
|
1509
|
+
expression is a full dotted path, so it may carry patterns and transforms:
|
|
1510
|
+
`$$(config.offset|mul:10)` is the value of `config.offset`, times ten.
|
|
1511
|
+
|
|
1512
|
+
Where the reference stands decides what its value does. In a transform
|
|
1513
|
+
argument, a guard value or a filter value it is the value itself (see
|
|
1514
|
+
[References in transforms and guards](#references-in-transforms-and-guards)).
|
|
1515
|
+
In an access position, as a key or slot, it is the key to look up:
|
|
1507
1516
|
|
|
1508
1517
|
>>> data = {'config': {'field': 'name'}, 'name': 'Alice'}
|
|
1509
1518
|
>>> dotted.get(data, '$$(config.field)')
|
|
1510
1519
|
'Alice'
|
|
1511
1520
|
|
|
1512
|
-
Here `$$(config.field)`
|
|
1521
|
+
Here `$$(config.field)` evaluates to `'name'`, then looks up `data['name']`.
|
|
1513
1522
|
|
|
1514
1523
|
References work with all access types and in any position:
|
|
1515
1524
|
|
|
@@ -1606,6 +1615,12 @@ and so on:
|
|
|
1606
1615
|
>>> dotted.get(data, 'n|add:$$(config.offset)')
|
|
1607
1616
|
15
|
|
1608
1617
|
|
|
1618
|
+
The expression inside may chain transforms of its own; they apply to its value
|
|
1619
|
+
before it is passed:
|
|
1620
|
+
|
|
1621
|
+
>>> dotted.get(data, 'n|add:$$(config.offset|mul:10)')
|
|
1622
|
+
105
|
|
1623
|
+
|
|
1609
1624
|
>>> data = {'items': [{'price': 100, 'rate': 1.5}, {'price': 20, 'rate': 2}]}
|
|
1610
1625
|
>>> dotted.get(data, 'items[*].price|mul:$$(^^rate)')
|
|
1611
1626
|
(150.0, 40)
|
|
@@ -1662,14 +1677,16 @@ claim some references and leave the rest:
|
|
|
1662
1677
|
>>> dotted.get({'amount': 10}, 'amount|sub:$$(lots.taken)', resolver=Store(store))
|
|
1663
1678
|
7
|
|
1664
1679
|
|
|
1665
|
-
A resolver
|
|
1666
|
-
those positions, so a host sigil such as `$sel` can be a variable.
|
|
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
|
|
1667
1683
|
`ref.value` to leave a token the literal it is; a key the resolver answers
|
|
1668
1684
|
is not offered again:
|
|
1669
1685
|
|
|
1670
1686
|
>>> class Sigils(dotted.ReferenceResolver):
|
|
1671
1687
|
... literals = True
|
|
1672
|
-
... def __init__(self, names):
|
|
1688
|
+
... def __init__(self, names, literals=None):
|
|
1689
|
+
... super().__init__(literals)
|
|
1673
1690
|
... self.names = names
|
|
1674
1691
|
... def resolve(self, ref, settings, node=None, parents=()):
|
|
1675
1692
|
... if ref.is_reference():
|
|
@@ -1681,6 +1698,18 @@ is not offered again:
|
|
|
1681
1698
|
>>> dotted.get(users, 'users[*&name=$sel].name', resolver=Sigils({'$sel': 'bob'}))
|
|
1682
1699
|
('bob',)
|
|
1683
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
|
+
|
|
1684
1713
|
A host variable may span several segments, `$amount.value`, which dotted has
|
|
1685
1714
|
parsed as two. The path-level hook `fold(segments)` is called once with a
|
|
1686
1715
|
path's segment strings, `('$amount', 'value')`, and returns the runs it
|
|
@@ -1349,7 +1349,8 @@ To chain through the items, use a pattern instead:
|
|
|
1349
1349
|
## Substitutions and References
|
|
1350
1350
|
|
|
1351
1351
|
All `$`-prefixed syntax falls into two categories: **substitutions** (resolved
|
|
1352
|
-
at replace time) and **references
|
|
1352
|
+
at replace time) and **references**, inline expressions evaluated during
|
|
1353
|
+
traversal.
|
|
1353
1354
|
|
|
1354
1355
|
| Syntax | Type | Resolved against |
|
|
1355
1356
|
|---|---|---|
|
|
@@ -1455,14 +1456,22 @@ the parsed result, so a path used many times can be compiled once:
|
|
|
1455
1456
|
<a id="references"></a>
|
|
1456
1457
|
### References
|
|
1457
1458
|
|
|
1458
|
-
A **reference
|
|
1459
|
-
|
|
1459
|
+
A **reference**, `$$(expr)`, is an inline evaluation expression. The dotted
|
|
1460
|
+
expression inside is evaluated during traversal, as `get` would evaluate it
|
|
1461
|
+
against the root, and its value stands where the reference is written. The
|
|
1462
|
+
expression is a full dotted path, so it may carry patterns and transforms:
|
|
1463
|
+
`$$(config.offset|mul:10)` is the value of `config.offset`, times ten.
|
|
1464
|
+
|
|
1465
|
+
Where the reference stands decides what its value does. In a transform
|
|
1466
|
+
argument, a guard value or a filter value it is the value itself (see
|
|
1467
|
+
[References in transforms and guards](#references-in-transforms-and-guards)).
|
|
1468
|
+
In an access position, as a key or slot, it is the key to look up:
|
|
1460
1469
|
|
|
1461
1470
|
>>> data = {'config': {'field': 'name'}, 'name': 'Alice'}
|
|
1462
1471
|
>>> dotted.get(data, '$$(config.field)')
|
|
1463
1472
|
'Alice'
|
|
1464
1473
|
|
|
1465
|
-
Here `$$(config.field)`
|
|
1474
|
+
Here `$$(config.field)` evaluates to `'name'`, then looks up `data['name']`.
|
|
1466
1475
|
|
|
1467
1476
|
References work with all access types and in any position:
|
|
1468
1477
|
|
|
@@ -1559,6 +1568,12 @@ and so on:
|
|
|
1559
1568
|
>>> dotted.get(data, 'n|add:$$(config.offset)')
|
|
1560
1569
|
15
|
|
1561
1570
|
|
|
1571
|
+
The expression inside may chain transforms of its own; they apply to its value
|
|
1572
|
+
before it is passed:
|
|
1573
|
+
|
|
1574
|
+
>>> dotted.get(data, 'n|add:$$(config.offset|mul:10)')
|
|
1575
|
+
105
|
|
1576
|
+
|
|
1562
1577
|
>>> data = {'items': [{'price': 100, 'rate': 1.5}, {'price': 20, 'rate': 2}]}
|
|
1563
1578
|
>>> dotted.get(data, 'items[*].price|mul:$$(^^rate)')
|
|
1564
1579
|
(150.0, 40)
|
|
@@ -1615,14 +1630,16 @@ claim some references and leave the rest:
|
|
|
1615
1630
|
>>> dotted.get({'amount': 10}, 'amount|sub:$$(lots.taken)', resolver=Store(store))
|
|
1616
1631
|
7
|
|
1617
1632
|
|
|
1618
|
-
A resolver
|
|
1619
|
-
those positions, so a host sigil such as `$sel` can be a variable.
|
|
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
|
|
1620
1636
|
`ref.value` to leave a token the literal it is; a key the resolver answers
|
|
1621
1637
|
is not offered again:
|
|
1622
1638
|
|
|
1623
1639
|
>>> class Sigils(dotted.ReferenceResolver):
|
|
1624
1640
|
... literals = True
|
|
1625
|
-
... def __init__(self, names):
|
|
1641
|
+
... def __init__(self, names, literals=None):
|
|
1642
|
+
... super().__init__(literals)
|
|
1626
1643
|
... self.names = names
|
|
1627
1644
|
... def resolve(self, ref, settings, node=None, parents=()):
|
|
1628
1645
|
... if ref.is_reference():
|
|
@@ -1634,6 +1651,18 @@ is not offered again:
|
|
|
1634
1651
|
>>> dotted.get(users, 'users[*&name=$sel].name', resolver=Sigils({'$sel': 'bob'}))
|
|
1635
1652
|
('bob',)
|
|
1636
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
|
+
|
|
1637
1666
|
A host variable may span several segments, `$amount.value`, which dotted has
|
|
1638
1667
|
parsed as two. The path-level hook `fold(segments)` is called once with a
|
|
1639
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
|
|
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)
|
|
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.
|
|
165
|
-
#
|
|
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
|
|
300
|
+
True if the resolver in force wants plain tokens at position, one
|
|
301
|
+
of LITERALS, offered too.
|
|
248
302
|
"""
|
|
249
|
-
return self.resolving()
|
|
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
|
|
265
|
-
|
|
266
|
-
|
|
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.
|
|
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
|
|
@@ -1396,7 +1396,8 @@ To chain through the items, use a pattern instead:
|
|
|
1396
1396
|
## Substitutions and References
|
|
1397
1397
|
|
|
1398
1398
|
All `$`-prefixed syntax falls into two categories: **substitutions** (resolved
|
|
1399
|
-
at replace time) and **references
|
|
1399
|
+
at replace time) and **references**, inline expressions evaluated during
|
|
1400
|
+
traversal.
|
|
1400
1401
|
|
|
1401
1402
|
| Syntax | Type | Resolved against |
|
|
1402
1403
|
|---|---|---|
|
|
@@ -1502,14 +1503,22 @@ the parsed result, so a path used many times can be compiled once:
|
|
|
1502
1503
|
<a id="references"></a>
|
|
1503
1504
|
### References
|
|
1504
1505
|
|
|
1505
|
-
A **reference
|
|
1506
|
-
|
|
1506
|
+
A **reference**, `$$(expr)`, is an inline evaluation expression. The dotted
|
|
1507
|
+
expression inside is evaluated during traversal, as `get` would evaluate it
|
|
1508
|
+
against the root, and its value stands where the reference is written. The
|
|
1509
|
+
expression is a full dotted path, so it may carry patterns and transforms:
|
|
1510
|
+
`$$(config.offset|mul:10)` is the value of `config.offset`, times ten.
|
|
1511
|
+
|
|
1512
|
+
Where the reference stands decides what its value does. In a transform
|
|
1513
|
+
argument, a guard value or a filter value it is the value itself (see
|
|
1514
|
+
[References in transforms and guards](#references-in-transforms-and-guards)).
|
|
1515
|
+
In an access position, as a key or slot, it is the key to look up:
|
|
1507
1516
|
|
|
1508
1517
|
>>> data = {'config': {'field': 'name'}, 'name': 'Alice'}
|
|
1509
1518
|
>>> dotted.get(data, '$$(config.field)')
|
|
1510
1519
|
'Alice'
|
|
1511
1520
|
|
|
1512
|
-
Here `$$(config.field)`
|
|
1521
|
+
Here `$$(config.field)` evaluates to `'name'`, then looks up `data['name']`.
|
|
1513
1522
|
|
|
1514
1523
|
References work with all access types and in any position:
|
|
1515
1524
|
|
|
@@ -1606,6 +1615,12 @@ and so on:
|
|
|
1606
1615
|
>>> dotted.get(data, 'n|add:$$(config.offset)')
|
|
1607
1616
|
15
|
|
1608
1617
|
|
|
1618
|
+
The expression inside may chain transforms of its own; they apply to its value
|
|
1619
|
+
before it is passed:
|
|
1620
|
+
|
|
1621
|
+
>>> dotted.get(data, 'n|add:$$(config.offset|mul:10)')
|
|
1622
|
+
105
|
|
1623
|
+
|
|
1609
1624
|
>>> data = {'items': [{'price': 100, 'rate': 1.5}, {'price': 20, 'rate': 2}]}
|
|
1610
1625
|
>>> dotted.get(data, 'items[*].price|mul:$$(^^rate)')
|
|
1611
1626
|
(150.0, 40)
|
|
@@ -1662,14 +1677,16 @@ claim some references and leave the rest:
|
|
|
1662
1677
|
>>> dotted.get({'amount': 10}, 'amount|sub:$$(lots.taken)', resolver=Store(store))
|
|
1663
1678
|
7
|
|
1664
1679
|
|
|
1665
|
-
A resolver
|
|
1666
|
-
those positions, so a host sigil such as `$sel` can be a variable.
|
|
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
|
|
1667
1683
|
`ref.value` to leave a token the literal it is; a key the resolver answers
|
|
1668
1684
|
is not offered again:
|
|
1669
1685
|
|
|
1670
1686
|
>>> class Sigils(dotted.ReferenceResolver):
|
|
1671
1687
|
... literals = True
|
|
1672
|
-
... def __init__(self, names):
|
|
1688
|
+
... def __init__(self, names, literals=None):
|
|
1689
|
+
... super().__init__(literals)
|
|
1673
1690
|
... self.names = names
|
|
1674
1691
|
... def resolve(self, ref, settings, node=None, parents=()):
|
|
1675
1692
|
... if ref.is_reference():
|
|
@@ -1681,6 +1698,18 @@ is not offered again:
|
|
|
1681
1698
|
>>> dotted.get(users, 'users[*&name=$sel].name', resolver=Sigils({'$sel': 'bob'}))
|
|
1682
1699
|
('bob',)
|
|
1683
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
|
+
|
|
1684
1713
|
A host variable may span several segments, `$amount.value`, which dotted has
|
|
1685
1714
|
parsed as two. The path-level hook `fold(segments)` is called once with a
|
|
1686
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.
|
|
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"
|
|
@@ -11,6 +11,7 @@ same files run as Python.
|
|
|
11
11
|
import os
|
|
12
12
|
|
|
13
13
|
from setuptools import Extension, setup
|
|
14
|
+
from setuptools.command.build_ext import build_ext as _build_ext
|
|
14
15
|
|
|
15
16
|
# The engine and the ops it runs. grammar.py, the parser, is left out on
|
|
16
17
|
# purpose: pyparsing inspects its parse actions as Python functions.
|
|
@@ -47,7 +48,20 @@ def extensions():
|
|
|
47
48
|
return cythonize(
|
|
48
49
|
[Extension('dotted.' + name, ['dotted/' + name + '.py'], optional=True) for name in MODULES],
|
|
49
50
|
compiler_directives={'language_level': 3},
|
|
51
|
+
nthreads=os.cpu_count() or 1,
|
|
50
52
|
)
|
|
51
53
|
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
class build_ext(_build_ext):
|
|
56
|
+
"""
|
|
57
|
+
Compile the extensions one per core. setuptools builds them one after
|
|
58
|
+
another unless -j is given; the modules are independent, so a release
|
|
59
|
+
wheel compiles in a fraction of the time on a multi-core runner.
|
|
60
|
+
"""
|
|
61
|
+
def finalize_options(self):
|
|
62
|
+
super().finalize_options()
|
|
63
|
+
if self.parallel is None:
|
|
64
|
+
self.parallel = os.cpu_count() or 1
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
setup(ext_modules=extensions(), cmdclass={'build_ext': build_ext})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dotted_notation-0.49.0 → dotted_notation-0.49.2}/dotted_notation.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|