dotted-notation 0.46.1__tar.gz → 0.47.1__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.46.1 → dotted_notation-0.47.1}/CHANGELOG.md +60 -0
- {dotted_notation-0.46.1/dotted_notation.egg-info → dotted_notation-0.47.1}/PKG-INFO +171 -35
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/README.md +170 -34
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/access.pxd +3 -3
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/access.py +25 -18
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/api.py +23 -21
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/base.pxd +6 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/base.py +47 -3
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/engine.pxd +3 -3
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/engine.py +13 -11
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/filters.pxd +4 -4
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/filters.py +68 -64
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/grammar.py +140 -183
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/groups.pxd +1 -1
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/groups.py +51 -1
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/matchers.py +8 -6
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/recursive.py +3 -3
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/results.py +93 -59
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/sql/pg.py +14 -5
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/transforms.py +18 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/wrappers.pxd +5 -5
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/wrappers.py +255 -75
- {dotted_notation-0.46.1 → dotted_notation-0.47.1/dotted_notation.egg-info}/PKG-INFO +171 -35
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/pyproject.toml +1 -1
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/LICENSE +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/MANIFEST.in +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/__init__.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/__main__.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/cli/__init__.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/cli/_compat.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/cli/formats.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/cli/main.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/containers.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/matchers.pxd +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/native.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/predicates.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/recursive.pxd +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/sql/__init__.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/sql/core.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/utils.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted/utypes.py +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted_notation.egg-info/SOURCES.txt +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted_notation.egg-info/dependency_links.txt +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted_notation.egg-info/entry_points.txt +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted_notation.egg-info/requires.txt +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/dotted_notation.egg-info/top_level.txt +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/setup.cfg +0 -0
- {dotted_notation-0.46.1 → dotted_notation-0.47.1}/setup.py +0 -0
|
@@ -3,6 +3,66 @@
|
|
|
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.1]
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- The `coalesce` transform: `n|coalesce:0` is `n`, or 0 when `n` is
|
|
10
|
+
`None`. Several fallbacks take the first that is not `None`, so a
|
|
11
|
+
reference may stand in first: `n|coalesce:$$(defaults.n):0`.
|
|
12
|
+
|
|
13
|
+
## [0.47.0]
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- References resolve in transform arguments, guard values and filter
|
|
17
|
+
values: `n|add:$$(config.offset)`, `*>$$(limit)`,
|
|
18
|
+
`users[*&name=$$(selected)]`. `$$(path)` is the root as before;
|
|
19
|
+
`$$(^path)` is the value being transformed or compared, `$$(^^path)`
|
|
20
|
+
the node holding it, and so on up. A reference that does not resolve
|
|
21
|
+
matches nothing, as in an access position.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- A `$(name)` substitution in a transform argument resolved through
|
|
25
|
+
`bindings=` is handed to the transform as its value; it was handed as
|
|
26
|
+
the parsed wrapper, so the transform failed and returned the value
|
|
27
|
+
unchanged. `null`, `true` and `false` arguments are likewise unwrapped.
|
|
28
|
+
- A reference as a guard or filter value raised `AttributeError`.
|
|
29
|
+
- A value guard after an attribute access, `o@name=5`, or after a group,
|
|
30
|
+
`(a,b)=5`, raised `ValueError` when parsed.
|
|
31
|
+
- A value guard under `~` did not hold for writes: `~a=1` raised
|
|
32
|
+
`TypeError` on `update` and `remove`, and `[~0]=1` mutated.
|
|
33
|
+
- `!a.b` yielded the complement of `a` and dropped the `.b`; it now
|
|
34
|
+
continues into `b` from each key other than `a`.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- Precedence, tightest first: access ops and `:type`; `!`; then guards,
|
|
38
|
+
filters and transforms; then `&`; then `,`. A guard, filter or
|
|
39
|
+
transform applies to the whole expression on its left. After a
|
|
40
|
+
parenthesized `,`, `&` or `?` group it is shorthand for writing it on
|
|
41
|
+
every branch: `(a,b)=5` is `(a=5,b=5)`, `(first&last)>5` is
|
|
42
|
+
`(first>5&last>5)`, `(a,b)|int` is `(a|int,b|int)`, which is also how
|
|
43
|
+
`assemble` writes them. After `!` it applies to what the negation
|
|
44
|
+
yields: `!a=5` is `(!a)=5`, the non-`a` values equal to 5, where it was
|
|
45
|
+
`!(a=5)`; that reading now needs the parentheses. `a,b|int` transforms
|
|
46
|
+
only `b`. `@attr=value` is a value guard like `key=value`, honored by
|
|
47
|
+
`update` and `remove`.
|
|
48
|
+
- `:type` tests the value a path yields, not the node it is applied to:
|
|
49
|
+
`x:int` is `x`, when its value is an `int`, a type guard beside `=value`
|
|
50
|
+
at the same level, so `(a|int):int` types a transformed value and
|
|
51
|
+
`*:dict` is the values that are dicts. It tested the container, so
|
|
52
|
+
`x:int` on `{'x': 3}` was `None`. A restriction on the container moves
|
|
53
|
+
one segment earlier: old `a.b:dict` is new `a:dict.b`, and
|
|
54
|
+
`*[*]:!(str, bytes)` is `*:!(str, bytes)[*]`.
|
|
55
|
+
- Every op owns its transforms: `a.b|int` carries `int` on `b`, so a
|
|
56
|
+
transform can sit inside a group and `expand`, `pluck` and `unpack`
|
|
57
|
+
write it back on each concrete path. `Dotted.transforms` is the
|
|
58
|
+
trailing op's, read-only. The match-only transforms of a guard or
|
|
59
|
+
filter, `a|int=5`, are its `guard_transforms`.
|
|
60
|
+
- For development: the `.pxd` files cimport `dotted.base` by its full
|
|
61
|
+
name, which the compiler's dependency scanner follows, so a change to
|
|
62
|
+
`base.pxd` rebuilds every compiled module that depends on it. Written
|
|
63
|
+
as `from . cimport base`, the dependency went unseen and stale modules
|
|
64
|
+
warned about a changed `DepthStack` size.
|
|
65
|
+
|
|
6
66
|
## [0.46.1]
|
|
7
67
|
|
|
8
68
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotted_notation
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.47.1
|
|
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
|
|
@@ -144,6 +144,7 @@ For faster `mutable=False` updates and removes, install the
|
|
|
144
144
|
- [Template bindings](#template-bindings)
|
|
145
145
|
- [References](#references)
|
|
146
146
|
- [Relative References](#relative-references)
|
|
147
|
+
- [References in transforms and guards](#references-in-transforms-and-guards)
|
|
147
148
|
- [Escaping](#escaping)
|
|
148
149
|
- [Type Restrictions](#type-restrictions)
|
|
149
150
|
- [Positive restrictions](#positive-restrictions)
|
|
@@ -272,6 +273,32 @@ Several Python libraries handle nested data access. Here's how dotted compares:
|
|
|
272
273
|
<a id="breaking-changes"></a>
|
|
273
274
|
## Breaking Changes
|
|
274
275
|
|
|
276
|
+
### v0.47.0
|
|
277
|
+
- **`!` binds tighter than guards, filters and transforms.** `!a=5` is now
|
|
278
|
+
`(!a)=5`, the values other than `a`'s that equal 5; the old meaning,
|
|
279
|
+
everything but an `a` equal to 5, is written `!(a=5)`. Likewise `!a&k=v`
|
|
280
|
+
filters what `!a` yields and `!a|int` transforms it. See
|
|
281
|
+
[Precedence](#precedence).
|
|
282
|
+
- **A guard, filter or transform after a group distributes to every branch.**
|
|
283
|
+
`(a,b)=5` is `(a=5,b=5)`, `(first&last)>5` is `(first>5&last>5)`,
|
|
284
|
+
`(a,b)|int` is `(a|int,b|int)`, and `assemble` writes the distributed form.
|
|
285
|
+
These used to fail to parse.
|
|
286
|
+
- **`a,b|int` transforms only `b`.** A transform binds to the expression on
|
|
287
|
+
its left before `,` and `&`; `(a,b)|int` transforms both.
|
|
288
|
+
- **`:type` tests the value a path yields, not the node it is applied to.**
|
|
289
|
+
`x:int` on `{'x': 3}` is `3`; it was `None`. Restrict a container one
|
|
290
|
+
segment earlier: `a.b:dict` → `a:dict.b`, and
|
|
291
|
+
`*[*]:!(str, bytes)` → `*:!(str, bytes)[*]`. `**:!(str, bytes)` and
|
|
292
|
+
`*(*, [*]):!(str, bytes)` now drop string values themselves, not only
|
|
293
|
+
their decomposition.
|
|
294
|
+
- **`!a.b` continues into `b`.** It yielded the complement of `a` and
|
|
295
|
+
ignored `.b`; it now yields `.b` of every key other than `a`, as
|
|
296
|
+
`match` always read it.
|
|
297
|
+
- **Ops own their transforms.** `Dotted.transforms` is read-only and
|
|
298
|
+
reflects the trailing op; `parse('a.b|int')` reprs as
|
|
299
|
+
`Dotted([a, b|int])`. A guard's or filter's match-only transforms,
|
|
300
|
+
`a|int=5`, are `guard_transforms`, not `transforms`.
|
|
301
|
+
|
|
275
302
|
### v0.44.0
|
|
276
303
|
- **Parameter renamed from `key` to `path`** across the public API to align with
|
|
277
304
|
the terminology used throughout the docs (a _path_ is the whole dotted
|
|
@@ -1374,6 +1401,7 @@ at replace time) and **references** (resolved during traversal).
|
|
|
1374
1401
|
| `$$(path)` | Reference | Root object during traversal |
|
|
1375
1402
|
| `$$(^path)` | Relative reference | Current node during traversal |
|
|
1376
1403
|
| `$$(^^path)` | Relative reference | Parent node during traversal |
|
|
1404
|
+
| `$$(^path)` in a transform, guard or filter | Relative reference | The value under test; `^^` the node holding it |
|
|
1377
1405
|
|
|
1378
1406
|
<a id="substitution"></a>
|
|
1379
1407
|
### Substitution
|
|
@@ -1559,6 +1587,50 @@ Relative references combine with patterns:
|
|
|
1559
1587
|
>>> dotted.get(data, 'a.$$(^config.*.field)')
|
|
1560
1588
|
('Alice', 30)
|
|
1561
1589
|
|
|
1590
|
+
<a id="references-in-transforms-and-guards"></a>
|
|
1591
|
+
### References in transforms and guards
|
|
1592
|
+
|
|
1593
|
+
A reference can also stand in for a transform argument, a guard value or a
|
|
1594
|
+
filter value. It resolves when the transform or comparison runs, against the
|
|
1595
|
+
value under test: `$$(path)` is the root as always, `$$(^path)` is the value
|
|
1596
|
+
itself, `$$(^^path)` the node holding it, `$$(^^^path)` that node's parent,
|
|
1597
|
+
and so on:
|
|
1598
|
+
|
|
1599
|
+
>>> data = {'config': {'offset': 10}, 'n': 5}
|
|
1600
|
+
>>> dotted.get(data, 'n|add:$$(config.offset)')
|
|
1601
|
+
15
|
|
1602
|
+
|
|
1603
|
+
>>> data = {'items': [{'price': 100, 'rate': 1.5}, {'price': 20, 'rate': 2}]}
|
|
1604
|
+
>>> dotted.get(data, 'items[*].price|mul:$$(^^rate)')
|
|
1605
|
+
(150.0, 40)
|
|
1606
|
+
|
|
1607
|
+
Each `price` is the value being transformed, so `^^rate` is the `rate` beside
|
|
1608
|
+
it. Guard and filter values resolve the same way:
|
|
1609
|
+
|
|
1610
|
+
>>> data = {'limit': 10, 'a': 5, 'b': 15}
|
|
1611
|
+
>>> dotted.get(data, '*>$$(limit)')
|
|
1612
|
+
(15,)
|
|
1613
|
+
>>> data = {'selected': 'bob', 'users': [{'name': 'alice'}, {'name': 'bob'}]}
|
|
1614
|
+
>>> dotted.get(data, 'users[*&name=$$(selected)].name')
|
|
1615
|
+
('bob',)
|
|
1616
|
+
|
|
1617
|
+
as do the transforms before a guard:
|
|
1618
|
+
|
|
1619
|
+
>>> data = {'scale': 10, 'a': 1, 'b': 2}
|
|
1620
|
+
>>> dotted.get(data, '*|mul:$$(scale)=20')
|
|
1621
|
+
(2,)
|
|
1622
|
+
|
|
1623
|
+
With `update`, the transforms apply to the value being set, so `^` is that
|
|
1624
|
+
value:
|
|
1625
|
+
|
|
1626
|
+
>>> dotted.update({'n': 5, 'inc': 3}, 'n|add:$$(inc)', 10)
|
|
1627
|
+
{'n': 13, 'inc': 3}
|
|
1628
|
+
|
|
1629
|
+
A reference that does not resolve matches nothing, as in an access position:
|
|
1630
|
+
|
|
1631
|
+
>>> dotted.get({'a': 1}, 'a|add:$$(missing)', default='fallback')
|
|
1632
|
+
'fallback'
|
|
1633
|
+
|
|
1562
1634
|
<a id="escaping"></a>
|
|
1563
1635
|
### Escaping
|
|
1564
1636
|
|
|
@@ -1586,17 +1658,19 @@ To use a literal `$$` as a key, escape with backslash (`\$$`) or quote it
|
|
|
1586
1658
|
<a id="type-restrictions"></a>
|
|
1587
1659
|
## Type Restrictions
|
|
1588
1660
|
|
|
1589
|
-
A **type restriction**
|
|
1590
|
-
|
|
1591
|
-
|
|
1661
|
+
A **type restriction** keeps a value only if it is of a given type. Append
|
|
1662
|
+
`:type` to a path: `x:int` is `x`, when its value is an `int`, so it reads
|
|
1663
|
+
like a guard, `x=5`, with a type in place of a value:
|
|
1592
1664
|
|
|
1593
1665
|
>>> import dotted
|
|
1594
1666
|
>>> d = {'a': {'b': 1}, 'c': [1, 2], 'd': 'hello'}
|
|
1667
|
+
>>> dotted.get(d, '*:dict')
|
|
1668
|
+
({'b': 1},)
|
|
1595
1669
|
>>> dotted.get(d, '*:dict.*')
|
|
1596
1670
|
(1,)
|
|
1597
1671
|
|
|
1598
|
-
Here `*:dict`
|
|
1599
|
-
The list and string are
|
|
1672
|
+
Here `*:dict` keeps the values that are dicts, and `.*` continues into them.
|
|
1673
|
+
The list and string are dropped.
|
|
1600
1674
|
|
|
1601
1675
|
Supported types: `str`, `bytes`, `int`, `float`, `dict`, `list`, `tuple`,
|
|
1602
1676
|
`set`, `frozenset`, `bool`.
|
|
@@ -1626,17 +1700,16 @@ Supported types: `str`, `bytes`, `int`, `float`, `dict`, `list`, `tuple`,
|
|
|
1626
1700
|
|
|
1627
1701
|
Type restrictions work on slot and attr accessors too:
|
|
1628
1702
|
|
|
1629
|
-
>>> dotted.get([10, 20], '[
|
|
1630
|
-
10
|
|
1631
|
-
>>> dotted.get(
|
|
1703
|
+
>>> dotted.get([10, 'x', 20], '[*]:int')
|
|
1704
|
+
(10, 20)
|
|
1705
|
+
>>> dotted.get([10, 20], '[0]:list', 'missing')
|
|
1632
1706
|
'missing'
|
|
1633
|
-
>>> dotted.get((10, 20), '[0]:tuple')
|
|
1634
|
-
10
|
|
1635
1707
|
|
|
1636
|
-
|
|
1708
|
+
A string or bytes value decomposes under `[*]`; to keep that from happening,
|
|
1709
|
+
restrict the value before the slot:
|
|
1637
1710
|
|
|
1638
1711
|
>>> d = {'s': 'abc', 'b': b'xyz', 'l': [1, 2]}
|
|
1639
|
-
>>> dotted.get(d, '
|
|
1712
|
+
>>> dotted.get(d, '*:!(str, bytes)[*]')
|
|
1640
1713
|
(1, 2)
|
|
1641
1714
|
|
|
1642
1715
|
<a id="combining-with-filters"></a>
|
|
@@ -1648,11 +1721,23 @@ Type restrictions compose with filters. The restriction goes before the filter:
|
|
|
1648
1721
|
>>> dotted.get(d, '*:dict&x=1.*')
|
|
1649
1722
|
(1, 2)
|
|
1650
1723
|
|
|
1724
|
+
<a id="typing-a-transformed-value"></a>
|
|
1725
|
+
### Typing a transformed value
|
|
1726
|
+
|
|
1727
|
+
A restriction after a parenthesized group applies to what the group yields,
|
|
1728
|
+
transforms included. A `:` directly after a transform name is a parameter,
|
|
1729
|
+
`a|int:16`, so the parentheses are what make it a type:
|
|
1730
|
+
|
|
1731
|
+
>>> dotted.get({'a': '5', 'b': 'x'}, '(*|int):int')
|
|
1732
|
+
(5,)
|
|
1733
|
+
|
|
1734
|
+
Here `|int` leaves `'x'` as it was, and `:int` drops it.
|
|
1735
|
+
|
|
1651
1736
|
<a id="recursive-traversal-with-type-restrictions"></a>
|
|
1652
1737
|
### Recursive traversal with type restrictions
|
|
1653
1738
|
|
|
1654
|
-
Type restrictions are especially useful in recursive accessor groups to
|
|
1655
|
-
|
|
1739
|
+
Type restrictions are especially useful in recursive accessor groups to keep
|
|
1740
|
+
the traversal to values of certain types:
|
|
1656
1741
|
|
|
1657
1742
|
>>> d = {'a': {'b': [1, 2]}, 'c': 'hello'}
|
|
1658
1743
|
>>> dotted.get(d, '*(*#, [*]:!(str, bytes))')
|
|
@@ -1662,11 +1747,12 @@ Without `:!(str, bytes)`, the `[*]` slot accessor would decompose `'hello'`
|
|
|
1662
1747
|
into individual characters.
|
|
1663
1748
|
|
|
1664
1749
|
A type restriction can also be applied to an entire group, which distributes it
|
|
1665
|
-
to every accessor in the group
|
|
1750
|
+
to every accessor in the group. Here it drops the string `'hello'` as well,
|
|
1751
|
+
since the `*` accessor yields it too:
|
|
1666
1752
|
|
|
1667
1753
|
>>> d = {'a': {'b': [1, 2]}, 'c': 'hello'}
|
|
1668
1754
|
>>> dotted.get(d, '*(*#, [*]):!(str, bytes)')
|
|
1669
|
-
({'b': [1, 2]}, [1, 2], 1, 2
|
|
1755
|
+
({'b': [1, 2]}, [1, 2], 1, 2)
|
|
1670
1756
|
|
|
1671
1757
|
The `**` shorthand accepts a type restriction too. It can be combined with a
|
|
1672
1758
|
depth slice — the type restriction comes first:
|
|
@@ -2017,27 +2103,45 @@ Updates and removes apply to all non-matching keys:
|
|
|
2017
2103
|
<a id="precedence"></a>
|
|
2018
2104
|
### Precedence
|
|
2019
2105
|
|
|
2020
|
-
|
|
2021
|
-
|
|
2106
|
+
Tightest first:
|
|
2107
|
+
|
|
2108
|
+
| Level | Operators | Example |
|
|
2109
|
+
|-------|-----------|---------|
|
|
2110
|
+
| 1 | access ops `.` `@` `[]`, parentheses | `!a.b` is `!(a.b)` |
|
|
2111
|
+
| 2 | `!` negation | `!a=5` is `(!a)=5` |
|
|
2112
|
+
| 3 | `:type`, `\|transform`, guard `=value`, filter `&key=value` | `!a\|int=5` tests `int` of each `(!a)` value |
|
|
2113
|
+
| 4 | `&` conjunction | `!a=5&b` is `((!a)=5)&b` |
|
|
2114
|
+
| 5 | `,` disjunction | `a,b\|int` is `a,(b\|int)` |
|
|
2022
2115
|
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
| loosest | `,` (disjunction) | `!a&b,c` = `((!a)&b),c` |
|
|
2116
|
+
A guard, filter or transform applies to the whole expression on its left:
|
|
2117
|
+
an access path (on its last segment), a `!` expression (on what it
|
|
2118
|
+
yields), or a parenthesized group, where it is shorthand for writing it on
|
|
2119
|
+
every branch: `(a,b)=5` is `(a=5,b=5)` and `(a,b)|int` is `(a|int,b|int)`,
|
|
2120
|
+
which is also how `assemble` writes them.
|
|
2029
2121
|
|
|
2030
2122
|
>>> d = {'a': 1, 'b': 2, 'c': 3}
|
|
2031
|
-
>>> dotted.get(d, '!a&b,c') #
|
|
2123
|
+
>>> dotted.get(d, '!a&b,c') # ((!a)&b), c
|
|
2032
2124
|
(2, 3, 2, 3)
|
|
2125
|
+
>>> dotted.get(d, '!a=3') # (!a)=3: the values other than a's equal to 3
|
|
2126
|
+
(3,)
|
|
2127
|
+
>>> dotted.get(d, '!(a=3)') # everything but an a equal to 3
|
|
2128
|
+
(1, 2, 3)
|
|
2129
|
+
>>> dotted.get({'a': '1', 'b': '2'}, '(a,b)|int')
|
|
2130
|
+
(1, 2)
|
|
2131
|
+
>>> dotted.get({'a': '1', 'b': '2'}, 'a,b|int')
|
|
2132
|
+
('1', 2)
|
|
2033
2133
|
|
|
2034
|
-
Since
|
|
2134
|
+
Since access ops bind tightest, `!a.b` negates `a` and continues into `b`
|
|
2135
|
+
from every other key, and `!a.b&x` conjoins that with `x`:
|
|
2035
2136
|
|
|
2036
|
-
>>> d = {'a': {'b': 1}, 'c': 2, 'x': 3}
|
|
2037
|
-
>>> dotted.get(d, '!a.b
|
|
2038
|
-
(2,
|
|
2039
|
-
>>> dotted.get(d, '!
|
|
2040
|
-
(3
|
|
2137
|
+
>>> d = {'a': {'b': 1}, 'c': {'b': 2}, 'x': 3}
|
|
2138
|
+
>>> dotted.get(d, '!a.b')
|
|
2139
|
+
(2,)
|
|
2140
|
+
>>> dotted.get(d, '!a.b&x')
|
|
2141
|
+
(2, 3)
|
|
2142
|
+
|
|
2143
|
+
A `:` after a transform name is always a parameter, `a|int:16`; to type a
|
|
2144
|
+
transformed value, parenthesize: `(a|int):int`.
|
|
2041
2145
|
|
|
2042
2146
|
<a id="operators"></a>
|
|
2043
2147
|
## Operators
|
|
@@ -2416,8 +2520,8 @@ Filters support `True`, `False`, and `None` as values:
|
|
|
2416
2520
|
### Value guard
|
|
2417
2521
|
|
|
2418
2522
|
A **value guard** tests the value at a path and yields it only if it matches.
|
|
2419
|
-
Use `key=value
|
|
2420
|
-
operators (`=`, `!=`, `<`, `>`, `<=`, `>=`) are supported:
|
|
2523
|
+
Use `key=value`, `[slot]=value` or `@attr=value` after accessing a field. All
|
|
2524
|
+
comparison operators (`=`, `!=`, `<`, `>`, `<=`, `>=`) are supported:
|
|
2421
2525
|
|
|
2422
2526
|
>>> d = {'first': 7, 'last': 3}
|
|
2423
2527
|
>>> dotted.get(d, 'first=7')
|
|
@@ -2436,6 +2540,19 @@ For lists of primitive values, use `[*]=value`:
|
|
|
2436
2540
|
>>> dotted.get(data, '[0]=1')
|
|
2437
2541
|
1
|
|
2438
2542
|
|
|
2543
|
+
A guard after a parenthesized group is shorthand for a guard on every
|
|
2544
|
+
branch: `(first,last)>5` is `(first>5,last>5)`, and `(first&last)>5` is
|
|
2545
|
+
`(first>5&last>5)`, which yields nothing unless both pass. After `!` it
|
|
2546
|
+
tests what the negation yields, so `!first=3` is the non-`first` values
|
|
2547
|
+
equal to 3; see [Precedence](#precedence).
|
|
2548
|
+
|
|
2549
|
+
>>> dotted.get(d, '(first,last)>5')
|
|
2550
|
+
(7,)
|
|
2551
|
+
>>> dotted.get(d, '(first&last)>5')
|
|
2552
|
+
()
|
|
2553
|
+
>>> dotted.get(d, '!first=3')
|
|
2554
|
+
(3,)
|
|
2555
|
+
|
|
2439
2556
|
Guards support all value types: numbers, `None`, `True`/`False`, strings, regex, `*`,
|
|
2440
2557
|
and [container patterns](#container-filter-values) (`[1, ...]`, `{"a": 1, ...: *}`, `{1, 2, ...}`):
|
|
2441
2558
|
|
|
@@ -2523,6 +2640,9 @@ Guard transforms compose with `update`, `remove`, and `has`:
|
|
|
2523
2640
|
>>> dotted.has({'val': '7'}, 'val|int=7')
|
|
2524
2641
|
True
|
|
2525
2642
|
|
|
2643
|
+
Both the transform arguments and the value compared with may be
|
|
2644
|
+
[references](#references-in-transforms-and-guards).
|
|
2645
|
+
|
|
2526
2646
|
<a id="container-filter-values"></a>
|
|
2527
2647
|
### Container filter values
|
|
2528
2648
|
|
|
@@ -2838,7 +2958,9 @@ Filter keys can include slice notation so the comparison applies to a slice of t
|
|
|
2838
2958
|
You can optionally add transforms to the end of dotted notation. These will
|
|
2839
2959
|
be applied on `get` and `update`. Transforms are separated by the `|` operator
|
|
2840
2960
|
and multiple may be chained together. Transforms may be parameterized using
|
|
2841
|
-
the `:` operator
|
|
2961
|
+
the `:` operator; a parameter may be a `$$(reference)`, resolved when the
|
|
2962
|
+
transform runs (see
|
|
2963
|
+
[References in transforms and guards](#references-in-transforms-and-guards)).
|
|
2842
2964
|
|
|
2843
2965
|
>>> import dotted
|
|
2844
2966
|
>>> d = [1, '2', 3]
|
|
@@ -2862,6 +2984,7 @@ decorator.
|
|
|
2862
2984
|
| `float` | `raises` | Convert to float |
|
|
2863
2985
|
| `decimal` | `raises` | Convert to `Decimal` |
|
|
2864
2986
|
| `none` | values... | Return `None` if falsy or matches values: `\|none::null:empty` |
|
|
2987
|
+
| `coalesce` | fallbacks... | The first of the value and the fallbacks that is not `None`: `\|coalesce:0` |
|
|
2865
2988
|
| `strip` | `chars`, `raises` | Strip whitespace or specified chars |
|
|
2866
2989
|
| `len` | `default` | Get length. Optional default if not sized: `\|len:0` |
|
|
2867
2990
|
| `lowercase` | `raises` | Convert string to lowercase |
|
|
@@ -2916,6 +3039,19 @@ Comparison and membership transforms return `True`/`False`:
|
|
|
2916
3039
|
>>> dotted.get({'n': 2}, 'n|in:[1, 2, 3]')
|
|
2917
3040
|
True
|
|
2918
3041
|
|
|
3042
|
+
`coalesce` stands in a fallback for `None`, as SQL's does; with several
|
|
3043
|
+
fallbacks the first that is not `None` wins, which matters when a fallback is
|
|
3044
|
+
a [reference](#references-in-transforms-and-guards) that may itself be `None`:
|
|
3045
|
+
|
|
3046
|
+
>>> d = {'n': None, 'defaults': {'n': 7}}
|
|
3047
|
+
>>> dotted.get(d, 'n|coalesce:0')
|
|
3048
|
+
0
|
|
3049
|
+
>>> dotted.get(d, 'n|coalesce:$$(defaults.n):0')
|
|
3050
|
+
7
|
|
3051
|
+
|
|
3052
|
+
A transform only runs on a value the path yields, so `coalesce` does not fill
|
|
3053
|
+
a missing key; `get(..., default=)` does that.
|
|
3054
|
+
|
|
2919
3055
|
<a id="container-transform-arguments"></a>
|
|
2920
3056
|
### Container transform arguments
|
|
2921
3057
|
|