dotted-notation 0.45.1__tar.gz → 0.47.0__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.45.1 → dotted_notation-0.47.0}/CHANGELOG.md +89 -0
- dotted_notation-0.47.0/MANIFEST.in +4 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/PKG-INFO +137 -39
- dotted_notation-0.45.1/dotted_notation.egg-info/PKG-INFO → dotted_notation-0.47.0/README.md +133 -82
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/__init__.py +6 -0
- dotted_notation-0.47.0/dotted/access.pxd +57 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/access.py +141 -107
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/api.py +25 -22
- dotted_notation-0.47.0/dotted/base.pxd +76 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/base.py +104 -9
- dotted_notation-0.47.0/dotted/engine.pxd +18 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/engine.py +45 -29
- dotted_notation-0.47.0/dotted/filters.pxd +60 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/filters.py +68 -64
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/grammar.py +287 -191
- dotted_notation-0.47.0/dotted/groups.pxd +34 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/groups.py +87 -173
- dotted_notation-0.47.0/dotted/matchers.pxd +87 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/matchers.py +14 -11
- dotted_notation-0.47.0/dotted/native.py +59 -0
- dotted_notation-0.47.0/dotted/recursive.pxd +24 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/recursive.py +32 -32
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/results.py +93 -59
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/sql/pg.py +14 -5
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/utils.py +8 -10
- dotted_notation-0.47.0/dotted/wrappers.pxd +27 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/wrappers.py +287 -109
- dotted_notation-0.45.1/README.md → dotted_notation-0.47.0/dotted_notation.egg-info/PKG-INFO +180 -36
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted_notation.egg-info/SOURCES.txt +10 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted_notation.egg-info/requires.txt +1 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/pyproject.toml +16 -6
- dotted_notation-0.47.0/setup.py +53 -0
- dotted_notation-0.45.1/MANIFEST.in +0 -2
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/LICENSE +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/__main__.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/cli/__init__.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/cli/_compat.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/cli/formats.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/cli/main.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/containers.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/predicates.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/sql/__init__.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/sql/core.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/transforms.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted/utypes.py +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted_notation.egg-info/dependency_links.txt +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted_notation.egg-info/entry_points.txt +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/dotted_notation.egg-info/top_level.txt +0 -0
- {dotted_notation-0.45.1 → dotted_notation-0.47.0}/setup.cfg +0 -0
|
@@ -3,6 +3,95 @@
|
|
|
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.47.0]
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- References resolve in transform arguments, guard values and filter
|
|
10
|
+
values: `n|add:$$(config.offset)`, `*>$$(limit)`,
|
|
11
|
+
`users[*&name=$$(selected)]`. `$$(path)` is the root as before;
|
|
12
|
+
`$$(^path)` is the value being transformed or compared, `$$(^^path)`
|
|
13
|
+
the node holding it, and so on up. A reference that does not resolve
|
|
14
|
+
matches nothing, as in an access position.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- A `$(name)` substitution in a transform argument resolved through
|
|
18
|
+
`bindings=` is handed to the transform as its value; it was handed as
|
|
19
|
+
the parsed wrapper, so the transform failed and returned the value
|
|
20
|
+
unchanged. `null`, `true` and `false` arguments are likewise unwrapped.
|
|
21
|
+
- A reference as a guard or filter value raised `AttributeError`.
|
|
22
|
+
- A value guard after an attribute access, `o@name=5`, or after a group,
|
|
23
|
+
`(a,b)=5`, raised `ValueError` when parsed.
|
|
24
|
+
- A value guard under `~` did not hold for writes: `~a=1` raised
|
|
25
|
+
`TypeError` on `update` and `remove`, and `[~0]=1` mutated.
|
|
26
|
+
- `!a.b` yielded the complement of `a` and dropped the `.b`; it now
|
|
27
|
+
continues into `b` from each key other than `a`.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- Precedence, tightest first: access ops and `:type`; `!`; then guards,
|
|
31
|
+
filters and transforms; then `&`; then `,`. A guard, filter or
|
|
32
|
+
transform applies to the whole expression on its left. After a
|
|
33
|
+
parenthesized `,`, `&` or `?` group it is shorthand for writing it on
|
|
34
|
+
every branch: `(a,b)=5` is `(a=5,b=5)`, `(first&last)>5` is
|
|
35
|
+
`(first>5&last>5)`, `(a,b)|int` is `(a|int,b|int)`, which is also how
|
|
36
|
+
`assemble` writes them. After `!` it applies to what the negation
|
|
37
|
+
yields: `!a=5` is `(!a)=5`, the non-`a` values equal to 5, where it was
|
|
38
|
+
`!(a=5)`; that reading now needs the parentheses. `a,b|int` transforms
|
|
39
|
+
only `b`. `@attr=value` is a value guard like `key=value`, honored by
|
|
40
|
+
`update` and `remove`.
|
|
41
|
+
- `:type` tests the value a path yields, not the node it is applied to:
|
|
42
|
+
`x:int` is `x`, when its value is an `int`, a type guard beside `=value`
|
|
43
|
+
at the same level, so `(a|int):int` types a transformed value and
|
|
44
|
+
`*:dict` is the values that are dicts. It tested the container, so
|
|
45
|
+
`x:int` on `{'x': 3}` was `None`. A restriction on the container moves
|
|
46
|
+
one segment earlier: old `a.b:dict` is new `a:dict.b`, and
|
|
47
|
+
`*[*]:!(str, bytes)` is `*:!(str, bytes)[*]`.
|
|
48
|
+
- Every op owns its transforms: `a.b|int` carries `int` on `b`, so a
|
|
49
|
+
transform can sit inside a group and `expand`, `pluck` and `unpack`
|
|
50
|
+
write it back on each concrete path. `Dotted.transforms` is the
|
|
51
|
+
trailing op's, read-only. The match-only transforms of a guard or
|
|
52
|
+
filter, `a|int=5`, are its `guard_transforms`.
|
|
53
|
+
- For development: the `.pxd` files cimport `dotted.base` by its full
|
|
54
|
+
name, which the compiler's dependency scanner follows, so a change to
|
|
55
|
+
`base.pxd` rebuilds every compiled module that depends on it. Written
|
|
56
|
+
as `from . cimport base`, the dependency went unseen and stale modules
|
|
57
|
+
warned about a changed `DepthStack` size.
|
|
58
|
+
|
|
59
|
+
## [0.46.1]
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
- Each compiled wheel is built and tested in its own CI job, so a
|
|
63
|
+
release takes minutes rather than the better part of an hour. The CLI
|
|
64
|
+
tests write their input files under pytest's `tmp_path`, which
|
|
65
|
+
Windows accepts.
|
|
66
|
+
|
|
67
|
+
## [0.46.0]
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
- The engine compiles to C. Wheels for CPython 3.11-3.14 on Linux
|
|
71
|
+
(glibc and musl, x86_64 and aarch64), macOS (Apple Silicon) and
|
|
72
|
+
Windows carry the compiled engine; every other Python installs the
|
|
73
|
+
same code as pure Python from the universal wheel. `DOTTED_NATIVE=0`
|
|
74
|
+
in the environment runs the Python engine regardless;
|
|
75
|
+
`dotted.native.active()` says which is loaded. Wildcard reads are
|
|
76
|
+
about 2.9x faster than 0.45.1 compiled and writes about 3.5x; pure
|
|
77
|
+
Python is faster too, writes by about a third.
|
|
78
|
+
- For development: `make test` and `make bench` run both engines,
|
|
79
|
+
`test.python`/`test.native` and `bench.python`/`bench.native` one;
|
|
80
|
+
`make test.wheel` runs the suite against a wheel built from the
|
|
81
|
+
source distribution.
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
- The `all` extra now means all of them: `formats` and `copium`. It was
|
|
85
|
+
an alias for `formats`, deprecated since 0.44.9.
|
|
86
|
+
- A concrete key present in a node is no longer looked for among the
|
|
87
|
+
node's keys to return the node's own key object: it matches as
|
|
88
|
+
written. Concrete writes and `expand`, `pluck` and `unpack` on a
|
|
89
|
+
concrete key cost the same wherever the key sits in a large dict
|
|
90
|
+
(`update` of the 996th key of 1000: 11.5us to 3.3us compiled). The
|
|
91
|
+
one visible difference: a rendered path shows the key as written
|
|
92
|
+
where the node spells it differently but equal, `users.1` for a key
|
|
93
|
+
`1.0`, `RED` for a `StrEnum` member.
|
|
94
|
+
|
|
6
95
|
## [0.45.1]
|
|
7
96
|
|
|
8
97
|
### Changed
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotted_notation
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.47.0
|
|
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
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/freywaid/dotted
|
|
8
8
|
Project-URL: Source, https://github.com/freywaid/dotted
|
|
9
9
|
Project-URL: Changelog, https://github.com/freywaid/dotted/blob/master/CHANGELOG.md
|
|
10
10
|
Keywords: dotted,nested,path,pattern-matching,json,jsonb,sql,traversal
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
13
|
Classifier: Operating System :: OS Independent
|
|
15
14
|
Classifier: Programming Language :: Python :: 3
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.6
|
|
@@ -21,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
25
|
Classifier: Topic :: Utilities
|
|
26
26
|
Requires-Python: >=3.6
|
|
@@ -39,6 +39,7 @@ Requires-Dist: PyYAML>=5.0; extra == "formats"
|
|
|
39
39
|
Requires-Dist: tomli>=1.0; extra == "formats"
|
|
40
40
|
Requires-Dist: tomli_w>=1.0; extra == "formats"
|
|
41
41
|
Provides-Extra: all
|
|
42
|
+
Requires-Dist: copium>=0.1.0; extra == "all"
|
|
42
43
|
Requires-Dist: PyYAML>=5.0; extra == "all"
|
|
43
44
|
Requires-Dist: tomli>=1.0; extra == "all"
|
|
44
45
|
Requires-Dist: tomli_w>=1.0; extra == "all"
|
|
@@ -73,8 +74,9 @@ Or pick only what you need:
|
|
|
73
74
|
|
|
74
75
|
pip install dotted-notation[yaml,toml]
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
Or everything, formats and copium (below) alike:
|
|
78
|
+
|
|
79
|
+
pip install dotted-notation[all]
|
|
78
80
|
|
|
79
81
|
For faster `mutable=False` updates and removes, install the
|
|
80
82
|
[copium](https://github.com/Bobronium/copium) extra, a C implementation of
|
|
@@ -142,6 +144,7 @@ For faster `mutable=False` updates and removes, install the
|
|
|
142
144
|
- [Template bindings](#template-bindings)
|
|
143
145
|
- [References](#references)
|
|
144
146
|
- [Relative References](#relative-references)
|
|
147
|
+
- [References in transforms and guards](#references-in-transforms-and-guards)
|
|
145
148
|
- [Escaping](#escaping)
|
|
146
149
|
- [Type Restrictions](#type-restrictions)
|
|
147
150
|
- [Positive restrictions](#positive-restrictions)
|
|
@@ -1372,6 +1375,7 @@ at replace time) and **references** (resolved during traversal).
|
|
|
1372
1375
|
| `$$(path)` | Reference | Root object during traversal |
|
|
1373
1376
|
| `$$(^path)` | Relative reference | Current node during traversal |
|
|
1374
1377
|
| `$$(^^path)` | Relative reference | Parent node during traversal |
|
|
1378
|
+
| `$$(^path)` in a transform, guard or filter | Relative reference | The value under test; `^^` the node holding it |
|
|
1375
1379
|
|
|
1376
1380
|
<a id="substitution"></a>
|
|
1377
1381
|
### Substitution
|
|
@@ -1557,6 +1561,50 @@ Relative references combine with patterns:
|
|
|
1557
1561
|
>>> dotted.get(data, 'a.$$(^config.*.field)')
|
|
1558
1562
|
('Alice', 30)
|
|
1559
1563
|
|
|
1564
|
+
<a id="references-in-transforms-and-guards"></a>
|
|
1565
|
+
### References in transforms and guards
|
|
1566
|
+
|
|
1567
|
+
A reference can also stand in for a transform argument, a guard value or a
|
|
1568
|
+
filter value. It resolves when the transform or comparison runs, against the
|
|
1569
|
+
value under test: `$$(path)` is the root as always, `$$(^path)` is the value
|
|
1570
|
+
itself, `$$(^^path)` the node holding it, `$$(^^^path)` that node's parent,
|
|
1571
|
+
and so on:
|
|
1572
|
+
|
|
1573
|
+
>>> data = {'config': {'offset': 10}, 'n': 5}
|
|
1574
|
+
>>> dotted.get(data, 'n|add:$$(config.offset)')
|
|
1575
|
+
15
|
|
1576
|
+
|
|
1577
|
+
>>> data = {'items': [{'price': 100, 'rate': 1.5}, {'price': 20, 'rate': 2}]}
|
|
1578
|
+
>>> dotted.get(data, 'items[*].price|mul:$$(^^rate)')
|
|
1579
|
+
(150.0, 40)
|
|
1580
|
+
|
|
1581
|
+
Each `price` is the value being transformed, so `^^rate` is the `rate` beside
|
|
1582
|
+
it. Guard and filter values resolve the same way:
|
|
1583
|
+
|
|
1584
|
+
>>> data = {'limit': 10, 'a': 5, 'b': 15}
|
|
1585
|
+
>>> dotted.get(data, '*>$$(limit)')
|
|
1586
|
+
(15,)
|
|
1587
|
+
>>> data = {'selected': 'bob', 'users': [{'name': 'alice'}, {'name': 'bob'}]}
|
|
1588
|
+
>>> dotted.get(data, 'users[*&name=$$(selected)].name')
|
|
1589
|
+
('bob',)
|
|
1590
|
+
|
|
1591
|
+
as do the transforms before a guard:
|
|
1592
|
+
|
|
1593
|
+
>>> data = {'scale': 10, 'a': 1, 'b': 2}
|
|
1594
|
+
>>> dotted.get(data, '*|mul:$$(scale)=20')
|
|
1595
|
+
(2,)
|
|
1596
|
+
|
|
1597
|
+
With `update`, the transforms apply to the value being set, so `^` is that
|
|
1598
|
+
value:
|
|
1599
|
+
|
|
1600
|
+
>>> dotted.update({'n': 5, 'inc': 3}, 'n|add:$$(inc)', 10)
|
|
1601
|
+
{'n': 13, 'inc': 3}
|
|
1602
|
+
|
|
1603
|
+
A reference that does not resolve matches nothing, as in an access position:
|
|
1604
|
+
|
|
1605
|
+
>>> dotted.get({'a': 1}, 'a|add:$$(missing)', default='fallback')
|
|
1606
|
+
'fallback'
|
|
1607
|
+
|
|
1560
1608
|
<a id="escaping"></a>
|
|
1561
1609
|
### Escaping
|
|
1562
1610
|
|
|
@@ -1584,17 +1632,19 @@ To use a literal `$$` as a key, escape with backslash (`\$$`) or quote it
|
|
|
1584
1632
|
<a id="type-restrictions"></a>
|
|
1585
1633
|
## Type Restrictions
|
|
1586
1634
|
|
|
1587
|
-
A **type restriction**
|
|
1588
|
-
|
|
1589
|
-
|
|
1635
|
+
A **type restriction** keeps a value only if it is of a given type. Append
|
|
1636
|
+
`:type` to a path: `x:int` is `x`, when its value is an `int`, so it reads
|
|
1637
|
+
like a guard, `x=5`, with a type in place of a value:
|
|
1590
1638
|
|
|
1591
1639
|
>>> import dotted
|
|
1592
1640
|
>>> d = {'a': {'b': 1}, 'c': [1, 2], 'd': 'hello'}
|
|
1641
|
+
>>> dotted.get(d, '*:dict')
|
|
1642
|
+
({'b': 1},)
|
|
1593
1643
|
>>> dotted.get(d, '*:dict.*')
|
|
1594
1644
|
(1,)
|
|
1595
1645
|
|
|
1596
|
-
Here `*:dict`
|
|
1597
|
-
The list and string are
|
|
1646
|
+
Here `*:dict` keeps the values that are dicts, and `.*` continues into them.
|
|
1647
|
+
The list and string are dropped.
|
|
1598
1648
|
|
|
1599
1649
|
Supported types: `str`, `bytes`, `int`, `float`, `dict`, `list`, `tuple`,
|
|
1600
1650
|
`set`, `frozenset`, `bool`.
|
|
@@ -1624,17 +1674,16 @@ Supported types: `str`, `bytes`, `int`, `float`, `dict`, `list`, `tuple`,
|
|
|
1624
1674
|
|
|
1625
1675
|
Type restrictions work on slot and attr accessors too:
|
|
1626
1676
|
|
|
1627
|
-
>>> dotted.get([10, 20], '[
|
|
1628
|
-
10
|
|
1629
|
-
>>> dotted.get(
|
|
1677
|
+
>>> dotted.get([10, 'x', 20], '[*]:int')
|
|
1678
|
+
(10, 20)
|
|
1679
|
+
>>> dotted.get([10, 20], '[0]:list', 'missing')
|
|
1630
1680
|
'missing'
|
|
1631
|
-
>>> dotted.get((10, 20), '[0]:tuple')
|
|
1632
|
-
10
|
|
1633
1681
|
|
|
1634
|
-
|
|
1682
|
+
A string or bytes value decomposes under `[*]`; to keep that from happening,
|
|
1683
|
+
restrict the value before the slot:
|
|
1635
1684
|
|
|
1636
1685
|
>>> d = {'s': 'abc', 'b': b'xyz', 'l': [1, 2]}
|
|
1637
|
-
>>> dotted.get(d, '
|
|
1686
|
+
>>> dotted.get(d, '*:!(str, bytes)[*]')
|
|
1638
1687
|
(1, 2)
|
|
1639
1688
|
|
|
1640
1689
|
<a id="combining-with-filters"></a>
|
|
@@ -1646,11 +1695,23 @@ Type restrictions compose with filters. The restriction goes before the filter:
|
|
|
1646
1695
|
>>> dotted.get(d, '*:dict&x=1.*')
|
|
1647
1696
|
(1, 2)
|
|
1648
1697
|
|
|
1698
|
+
<a id="typing-a-transformed-value"></a>
|
|
1699
|
+
### Typing a transformed value
|
|
1700
|
+
|
|
1701
|
+
A restriction after a parenthesized group applies to what the group yields,
|
|
1702
|
+
transforms included. A `:` directly after a transform name is a parameter,
|
|
1703
|
+
`a|int:16`, so the parentheses are what make it a type:
|
|
1704
|
+
|
|
1705
|
+
>>> dotted.get({'a': '5', 'b': 'x'}, '(*|int):int')
|
|
1706
|
+
(5,)
|
|
1707
|
+
|
|
1708
|
+
Here `|int` leaves `'x'` as it was, and `:int` drops it.
|
|
1709
|
+
|
|
1649
1710
|
<a id="recursive-traversal-with-type-restrictions"></a>
|
|
1650
1711
|
### Recursive traversal with type restrictions
|
|
1651
1712
|
|
|
1652
|
-
Type restrictions are especially useful in recursive accessor groups to
|
|
1653
|
-
|
|
1713
|
+
Type restrictions are especially useful in recursive accessor groups to keep
|
|
1714
|
+
the traversal to values of certain types:
|
|
1654
1715
|
|
|
1655
1716
|
>>> d = {'a': {'b': [1, 2]}, 'c': 'hello'}
|
|
1656
1717
|
>>> dotted.get(d, '*(*#, [*]:!(str, bytes))')
|
|
@@ -1660,11 +1721,12 @@ Without `:!(str, bytes)`, the `[*]` slot accessor would decompose `'hello'`
|
|
|
1660
1721
|
into individual characters.
|
|
1661
1722
|
|
|
1662
1723
|
A type restriction can also be applied to an entire group, which distributes it
|
|
1663
|
-
to every accessor in the group
|
|
1724
|
+
to every accessor in the group. Here it drops the string `'hello'` as well,
|
|
1725
|
+
since the `*` accessor yields it too:
|
|
1664
1726
|
|
|
1665
1727
|
>>> d = {'a': {'b': [1, 2]}, 'c': 'hello'}
|
|
1666
1728
|
>>> dotted.get(d, '*(*#, [*]):!(str, bytes)')
|
|
1667
|
-
({'b': [1, 2]}, [1, 2], 1, 2
|
|
1729
|
+
({'b': [1, 2]}, [1, 2], 1, 2)
|
|
1668
1730
|
|
|
1669
1731
|
The `**` shorthand accepts a type restriction too. It can be combined with a
|
|
1670
1732
|
depth slice — the type restriction comes first:
|
|
@@ -2015,27 +2077,45 @@ Updates and removes apply to all non-matching keys:
|
|
|
2015
2077
|
<a id="precedence"></a>
|
|
2016
2078
|
### Precedence
|
|
2017
2079
|
|
|
2018
|
-
|
|
2019
|
-
|
|
2080
|
+
Tightest first:
|
|
2081
|
+
|
|
2082
|
+
| Level | Operators | Example |
|
|
2083
|
+
|-------|-----------|---------|
|
|
2084
|
+
| 1 | access ops `.` `@` `[]`, parentheses | `!a.b` is `!(a.b)` |
|
|
2085
|
+
| 2 | `!` negation | `!a=5` is `(!a)=5` |
|
|
2086
|
+
| 3 | `:type`, `\|transform`, guard `=value`, filter `&key=value` | `!a\|int=5` tests `int` of each `(!a)` value |
|
|
2087
|
+
| 4 | `&` conjunction | `!a=5&b` is `((!a)=5)&b` |
|
|
2088
|
+
| 5 | `,` disjunction | `a,b\|int` is `a,(b\|int)` |
|
|
2020
2089
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
| loosest | `,` (disjunction) | `!a&b,c` = `((!a)&b),c` |
|
|
2090
|
+
A guard, filter or transform applies to the whole expression on its left:
|
|
2091
|
+
an access path (on its last segment), a `!` expression (on what it
|
|
2092
|
+
yields), or a parenthesized group, where it is shorthand for writing it on
|
|
2093
|
+
every branch: `(a,b)=5` is `(a=5,b=5)` and `(a,b)|int` is `(a|int,b|int)`,
|
|
2094
|
+
which is also how `assemble` writes them.
|
|
2027
2095
|
|
|
2028
2096
|
>>> d = {'a': 1, 'b': 2, 'c': 3}
|
|
2029
|
-
>>> dotted.get(d, '!a&b,c') #
|
|
2097
|
+
>>> dotted.get(d, '!a&b,c') # ((!a)&b), c
|
|
2030
2098
|
(2, 3, 2, 3)
|
|
2099
|
+
>>> dotted.get(d, '!a=3') # (!a)=3: the values other than a's equal to 3
|
|
2100
|
+
(3,)
|
|
2101
|
+
>>> dotted.get(d, '!(a=3)') # everything but an a equal to 3
|
|
2102
|
+
(1, 2, 3)
|
|
2103
|
+
>>> dotted.get({'a': '1', 'b': '2'}, '(a,b)|int')
|
|
2104
|
+
(1, 2)
|
|
2105
|
+
>>> dotted.get({'a': '1', 'b': '2'}, 'a,b|int')
|
|
2106
|
+
('1', 2)
|
|
2031
2107
|
|
|
2032
|
-
Since
|
|
2108
|
+
Since access ops bind tightest, `!a.b` negates `a` and continues into `b`
|
|
2109
|
+
from every other key, and `!a.b&x` conjoins that with `x`:
|
|
2033
2110
|
|
|
2034
|
-
>>> d = {'a': {'b': 1}, 'c': 2, 'x': 3}
|
|
2035
|
-
>>> dotted.get(d, '!a.b
|
|
2036
|
-
(2,
|
|
2037
|
-
>>> dotted.get(d, '!
|
|
2038
|
-
(3
|
|
2111
|
+
>>> d = {'a': {'b': 1}, 'c': {'b': 2}, 'x': 3}
|
|
2112
|
+
>>> dotted.get(d, '!a.b')
|
|
2113
|
+
(2,)
|
|
2114
|
+
>>> dotted.get(d, '!a.b&x')
|
|
2115
|
+
(2, 3)
|
|
2116
|
+
|
|
2117
|
+
A `:` after a transform name is always a parameter, `a|int:16`; to type a
|
|
2118
|
+
transformed value, parenthesize: `(a|int):int`.
|
|
2039
2119
|
|
|
2040
2120
|
<a id="operators"></a>
|
|
2041
2121
|
## Operators
|
|
@@ -2414,8 +2494,8 @@ Filters support `True`, `False`, and `None` as values:
|
|
|
2414
2494
|
### Value guard
|
|
2415
2495
|
|
|
2416
2496
|
A **value guard** tests the value at a path and yields it only if it matches.
|
|
2417
|
-
Use `key=value
|
|
2418
|
-
operators (`=`, `!=`, `<`, `>`, `<=`, `>=`) are supported:
|
|
2497
|
+
Use `key=value`, `[slot]=value` or `@attr=value` after accessing a field. All
|
|
2498
|
+
comparison operators (`=`, `!=`, `<`, `>`, `<=`, `>=`) are supported:
|
|
2419
2499
|
|
|
2420
2500
|
>>> d = {'first': 7, 'last': 3}
|
|
2421
2501
|
>>> dotted.get(d, 'first=7')
|
|
@@ -2434,6 +2514,19 @@ For lists of primitive values, use `[*]=value`:
|
|
|
2434
2514
|
>>> dotted.get(data, '[0]=1')
|
|
2435
2515
|
1
|
|
2436
2516
|
|
|
2517
|
+
A guard after a parenthesized group is shorthand for a guard on every
|
|
2518
|
+
branch: `(first,last)>5` is `(first>5,last>5)`, and `(first&last)>5` is
|
|
2519
|
+
`(first>5&last>5)`, which yields nothing unless both pass. After `!` it
|
|
2520
|
+
tests what the negation yields, so `!first=3` is the non-`first` values
|
|
2521
|
+
equal to 3; see [Precedence](#precedence).
|
|
2522
|
+
|
|
2523
|
+
>>> dotted.get(d, '(first,last)>5')
|
|
2524
|
+
(7,)
|
|
2525
|
+
>>> dotted.get(d, '(first&last)>5')
|
|
2526
|
+
()
|
|
2527
|
+
>>> dotted.get(d, '!first=3')
|
|
2528
|
+
(3,)
|
|
2529
|
+
|
|
2437
2530
|
Guards support all value types: numbers, `None`, `True`/`False`, strings, regex, `*`,
|
|
2438
2531
|
and [container patterns](#container-filter-values) (`[1, ...]`, `{"a": 1, ...: *}`, `{1, 2, ...}`):
|
|
2439
2532
|
|
|
@@ -2521,6 +2614,9 @@ Guard transforms compose with `update`, `remove`, and `has`:
|
|
|
2521
2614
|
>>> dotted.has({'val': '7'}, 'val|int=7')
|
|
2522
2615
|
True
|
|
2523
2616
|
|
|
2617
|
+
Both the transform arguments and the value compared with may be
|
|
2618
|
+
[references](#references-in-transforms-and-guards).
|
|
2619
|
+
|
|
2524
2620
|
<a id="container-filter-values"></a>
|
|
2525
2621
|
### Container filter values
|
|
2526
2622
|
|
|
@@ -2836,7 +2932,9 @@ Filter keys can include slice notation so the comparison applies to a slice of t
|
|
|
2836
2932
|
You can optionally add transforms to the end of dotted notation. These will
|
|
2837
2933
|
be applied on `get` and `update`. Transforms are separated by the `|` operator
|
|
2838
2934
|
and multiple may be chained together. Transforms may be parameterized using
|
|
2839
|
-
the `:` operator
|
|
2935
|
+
the `:` operator; a parameter may be a `$$(reference)`, resolved when the
|
|
2936
|
+
transform runs (see
|
|
2937
|
+
[References in transforms and guards](#references-in-transforms-and-guards)).
|
|
2840
2938
|
|
|
2841
2939
|
>>> import dotted
|
|
2842
2940
|
>>> d = [1, '2', 3]
|