dotted-notation 0.47.0__tar.gz → 0.47.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.47.0 → dotted_notation-0.47.2}/CHANGELOG.md +14 -0
  2. {dotted_notation-0.47.0/dotted_notation.egg-info → dotted_notation-0.47.2}/PKG-INFO +48 -1
  3. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/README.md +47 -0
  4. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/grammar.py +4 -1
  5. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/transforms.py +18 -0
  6. {dotted_notation-0.47.0 → dotted_notation-0.47.2/dotted_notation.egg-info}/PKG-INFO +48 -1
  7. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/pyproject.toml +1 -1
  8. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/LICENSE +0 -0
  9. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/MANIFEST.in +0 -0
  10. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/__init__.py +0 -0
  11. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/__main__.py +0 -0
  12. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/access.pxd +0 -0
  13. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/access.py +0 -0
  14. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/api.py +0 -0
  15. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/base.pxd +0 -0
  16. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/base.py +0 -0
  17. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/cli/__init__.py +0 -0
  18. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/cli/_compat.py +0 -0
  19. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/cli/formats.py +0 -0
  20. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/cli/main.py +0 -0
  21. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/containers.py +0 -0
  22. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/engine.pxd +0 -0
  23. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/engine.py +0 -0
  24. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/filters.pxd +0 -0
  25. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/filters.py +0 -0
  26. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/groups.pxd +0 -0
  27. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/groups.py +0 -0
  28. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/matchers.pxd +0 -0
  29. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/matchers.py +0 -0
  30. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/native.py +0 -0
  31. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/predicates.py +0 -0
  32. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/recursive.pxd +0 -0
  33. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/recursive.py +0 -0
  34. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/results.py +0 -0
  35. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/sql/__init__.py +0 -0
  36. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/sql/core.py +0 -0
  37. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/sql/pg.py +0 -0
  38. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/utils.py +0 -0
  39. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/utypes.py +0 -0
  40. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/wrappers.pxd +0 -0
  41. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted/wrappers.py +0 -0
  42. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted_notation.egg-info/SOURCES.txt +0 -0
  43. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted_notation.egg-info/dependency_links.txt +0 -0
  44. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted_notation.egg-info/entry_points.txt +0 -0
  45. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted_notation.egg-info/requires.txt +0 -0
  46. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/dotted_notation.egg-info/top_level.txt +0 -0
  47. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/setup.cfg +0 -0
  48. {dotted_notation-0.47.0 → dotted_notation-0.47.2}/setup.py +0 -0
@@ -3,6 +3,20 @@
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.2]
7
+
8
+ ### Added
9
+ - `$str`, a `$` before a word, is a plain string wherever a key, guard
10
+ value or transform argument may stand, as `^str` already was. It was a
11
+ `ParseError`. `$0`, `$(name)` and `$$(path)` are unchanged.
12
+
13
+ ## [0.47.1]
14
+
15
+ ### Added
16
+ - The `coalesce` transform: `n|coalesce:0` is `n`, or 0 when `n` is
17
+ `None`. Several fallbacks take the first that is not `None`, so a
18
+ reference may stand in first: `n|coalesce:$$(defaults.n):0`.
19
+
6
20
  ## [0.47.0]
7
21
 
8
22
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotted_notation
3
- Version: 0.47.0
3
+ Version: 0.47.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
@@ -273,6 +273,32 @@ Several Python libraries handle nested data access. Here's how dotted compares:
273
273
  <a id="breaking-changes"></a>
274
274
  ## Breaking Changes
275
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
+
276
302
  ### v0.44.0
277
303
  - **Parameter renamed from `key` to `path`** across the public API to align with
278
304
  the terminology used throughout the docs (a _path_ is the whole dotted
@@ -1629,6 +1655,13 @@ Quoting also works — a quoted string is always literal:
1629
1655
  To use a literal `$$` as a key, escape with backslash (`\$$`) or quote it
1630
1656
  (`'$$(…)'`).
1631
1657
 
1658
+ A `$` before a word is not a substitution at all: `$str` is the plain string
1659
+ `$str`, in a segment, a guard value or a transform argument alike, just as
1660
+ `^str` is. Only `$0`, `$(name)` and `$$(path)` have meaning:
1661
+
1662
+ >>> dotted.get({'$amount': {'value': 2}}, '$amount.value')
1663
+ 2
1664
+
1632
1665
  <a id="type-restrictions"></a>
1633
1666
  ## Type Restrictions
1634
1667
 
@@ -2958,6 +2991,7 @@ decorator.
2958
2991
  | `float` | `raises` | Convert to float |
2959
2992
  | `decimal` | `raises` | Convert to `Decimal` |
2960
2993
  | `none` | values... | Return `None` if falsy or matches values: `\|none::null:empty` |
2994
+ | `coalesce` | fallbacks... | The first of the value and the fallbacks that is not `None`: `\|coalesce:0` |
2961
2995
  | `strip` | `chars`, `raises` | Strip whitespace or specified chars |
2962
2996
  | `len` | `default` | Get length. Optional default if not sized: `\|len:0` |
2963
2997
  | `lowercase` | `raises` | Convert string to lowercase |
@@ -3012,6 +3046,19 @@ Comparison and membership transforms return `True`/`False`:
3012
3046
  >>> dotted.get({'n': 2}, 'n|in:[1, 2, 3]')
3013
3047
  True
3014
3048
 
3049
+ `coalesce` stands in a fallback for `None`, as SQL's does; with several
3050
+ fallbacks the first that is not `None` wins, which matters when a fallback is
3051
+ a [reference](#references-in-transforms-and-guards) that may itself be `None`:
3052
+
3053
+ >>> d = {'n': None, 'defaults': {'n': 7}}
3054
+ >>> dotted.get(d, 'n|coalesce:0')
3055
+ 0
3056
+ >>> dotted.get(d, 'n|coalesce:$$(defaults.n):0')
3057
+ 7
3058
+
3059
+ A transform only runs on a value the path yields, so `coalesce` does not fill
3060
+ a missing key; `get(..., default=)` does that.
3061
+
3015
3062
  <a id="container-transform-arguments"></a>
3016
3063
  ### Container transform arguments
3017
3064
 
@@ -226,6 +226,32 @@ Several Python libraries handle nested data access. Here's how dotted compares:
226
226
  <a id="breaking-changes"></a>
227
227
  ## Breaking Changes
228
228
 
229
+ ### v0.47.0
230
+ - **`!` binds tighter than guards, filters and transforms.** `!a=5` is now
231
+ `(!a)=5`, the values other than `a`'s that equal 5; the old meaning,
232
+ everything but an `a` equal to 5, is written `!(a=5)`. Likewise `!a&k=v`
233
+ filters what `!a` yields and `!a|int` transforms it. See
234
+ [Precedence](#precedence).
235
+ - **A guard, filter or transform after a group distributes to every branch.**
236
+ `(a,b)=5` is `(a=5,b=5)`, `(first&last)>5` is `(first>5&last>5)`,
237
+ `(a,b)|int` is `(a|int,b|int)`, and `assemble` writes the distributed form.
238
+ These used to fail to parse.
239
+ - **`a,b|int` transforms only `b`.** A transform binds to the expression on
240
+ its left before `,` and `&`; `(a,b)|int` transforms both.
241
+ - **`:type` tests the value a path yields, not the node it is applied to.**
242
+ `x:int` on `{'x': 3}` is `3`; it was `None`. Restrict a container one
243
+ segment earlier: `a.b:dict` → `a:dict.b`, and
244
+ `*[*]:!(str, bytes)` → `*:!(str, bytes)[*]`. `**:!(str, bytes)` and
245
+ `*(*, [*]):!(str, bytes)` now drop string values themselves, not only
246
+ their decomposition.
247
+ - **`!a.b` continues into `b`.** It yielded the complement of `a` and
248
+ ignored `.b`; it now yields `.b` of every key other than `a`, as
249
+ `match` always read it.
250
+ - **Ops own their transforms.** `Dotted.transforms` is read-only and
251
+ reflects the trailing op; `parse('a.b|int')` reprs as
252
+ `Dotted([a, b|int])`. A guard's or filter's match-only transforms,
253
+ `a|int=5`, are `guard_transforms`, not `transforms`.
254
+
229
255
  ### v0.44.0
230
256
  - **Parameter renamed from `key` to `path`** across the public API to align with
231
257
  the terminology used throughout the docs (a _path_ is the whole dotted
@@ -1582,6 +1608,13 @@ Quoting also works — a quoted string is always literal:
1582
1608
  To use a literal `$$` as a key, escape with backslash (`\$$`) or quote it
1583
1609
  (`'$$(…)'`).
1584
1610
 
1611
+ A `$` before a word is not a substitution at all: `$str` is the plain string
1612
+ `$str`, in a segment, a guard value or a transform argument alike, just as
1613
+ `^str` is. Only `$0`, `$(name)` and `$$(path)` have meaning:
1614
+
1615
+ >>> dotted.get({'$amount': {'value': 2}}, '$amount.value')
1616
+ 2
1617
+
1585
1618
  <a id="type-restrictions"></a>
1586
1619
  ## Type Restrictions
1587
1620
 
@@ -2911,6 +2944,7 @@ decorator.
2911
2944
  | `float` | `raises` | Convert to float |
2912
2945
  | `decimal` | `raises` | Convert to `Decimal` |
2913
2946
  | `none` | values... | Return `None` if falsy or matches values: `\|none::null:empty` |
2947
+ | `coalesce` | fallbacks... | The first of the value and the fallbacks that is not `None`: `\|coalesce:0` |
2914
2948
  | `strip` | `chars`, `raises` | Strip whitespace or specified chars |
2915
2949
  | `len` | `default` | Get length. Optional default if not sized: `\|len:0` |
2916
2950
  | `lowercase` | `raises` | Convert string to lowercase |
@@ -2965,6 +2999,19 @@ Comparison and membership transforms return `True`/`False`:
2965
2999
  >>> dotted.get({'n': 2}, 'n|in:[1, 2, 3]')
2966
3000
  True
2967
3001
 
3002
+ `coalesce` stands in a fallback for `None`, as SQL's does; with several
3003
+ fallbacks the first that is not `None` wins, which matters when a fallback is
3004
+ a [reference](#references-in-transforms-and-guards) that may itself be `None`:
3005
+
3006
+ >>> d = {'n': None, 'defaults': {'n': 7}}
3007
+ >>> dotted.get(d, 'n|coalesce:0')
3008
+ 0
3009
+ >>> dotted.get(d, 'n|coalesce:$$(defaults.n):0')
3010
+ 7
3011
+
3012
+ A transform only runs on a value the path yields, so `coalesce` does not fill
3013
+ a missing key; `get(..., default=)` does that.
3014
+
2968
3015
  <a id="container-transform-arguments"></a>
2969
3016
  ### Container transform arguments
2970
3017
 
@@ -1,6 +1,7 @@
1
1
  """
2
2
  """
3
3
  import decimal
4
+ import re
4
5
  import pyparsing as pp
5
6
  from pyparsing import pyparsing_common as ppc
6
7
 
@@ -275,7 +276,9 @@ def _paren_subst_action(t):
275
276
  _paren_subst = pp.Regex(r'\$\([^)]+\)').set_parse_action(_paren_subst_action)
276
277
  _raw_subst = pp.Regex(r'\$[0-9]+').set_parse_action(
277
278
  lambda t: matchers.Subst(int(t[0][1:])))
278
- var = _escaped_dollar | _reference | _paren_subst | _raw_subst
279
+ # $str, a $ before a word: a plain string, as ^str is. Not $0, $(name) or $$(path).
280
+ _dollar_word = pp.Regex(r'\$(?![0-9($])[^' + re.escape(reserved) + r' \t\n\r]+').set_parse_action(matchers.Word)
281
+ var = _escaped_dollar | _reference | _paren_subst | _raw_subst | _dollar_word
279
282
  slice = pp.Optional(integer | plus) + ':' + pp.Optional(integer | plus) \
280
283
  + pp.Optional(':') + pp.Optional(integer | plus)
281
284
 
@@ -104,6 +104,24 @@ def transform_none(val, *none_vals):
104
104
  return None if val in none_vals else val
105
105
 
106
106
 
107
+ @transform('coalesce')
108
+ def transform_coalesce(val, *fallbacks):
109
+ """
110
+ The first of val and the fallbacks that is not None
111
+ <dotted>|coalesce:<fallback> fallback if val is None else val
112
+ <dotted>|coalesce:<f1>:<f2> the first of val, f1, f2 not None
113
+ >>> import dotted
114
+ >>> dotted.get({'n': None}, 'n|coalesce:0')
115
+ 0
116
+ >>> dotted.get({'n': 5}, 'n|coalesce:0')
117
+ 5
118
+ """
119
+ for v in (val, *fallbacks):
120
+ if v is not None:
121
+ return v
122
+ return None
123
+
124
+
107
125
  @transform('strip')
108
126
  def transform_strip(val, chars=None, *modes):
109
127
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotted_notation
3
- Version: 0.47.0
3
+ Version: 0.47.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
@@ -273,6 +273,32 @@ Several Python libraries handle nested data access. Here's how dotted compares:
273
273
  <a id="breaking-changes"></a>
274
274
  ## Breaking Changes
275
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
+
276
302
  ### v0.44.0
277
303
  - **Parameter renamed from `key` to `path`** across the public API to align with
278
304
  the terminology used throughout the docs (a _path_ is the whole dotted
@@ -1629,6 +1655,13 @@ Quoting also works — a quoted string is always literal:
1629
1655
  To use a literal `$$` as a key, escape with backslash (`\$$`) or quote it
1630
1656
  (`'$$(…)'`).
1631
1657
 
1658
+ A `$` before a word is not a substitution at all: `$str` is the plain string
1659
+ `$str`, in a segment, a guard value or a transform argument alike, just as
1660
+ `^str` is. Only `$0`, `$(name)` and `$$(path)` have meaning:
1661
+
1662
+ >>> dotted.get({'$amount': {'value': 2}}, '$amount.value')
1663
+ 2
1664
+
1632
1665
  <a id="type-restrictions"></a>
1633
1666
  ## Type Restrictions
1634
1667
 
@@ -2958,6 +2991,7 @@ decorator.
2958
2991
  | `float` | `raises` | Convert to float |
2959
2992
  | `decimal` | `raises` | Convert to `Decimal` |
2960
2993
  | `none` | values... | Return `None` if falsy or matches values: `\|none::null:empty` |
2994
+ | `coalesce` | fallbacks... | The first of the value and the fallbacks that is not `None`: `\|coalesce:0` |
2961
2995
  | `strip` | `chars`, `raises` | Strip whitespace or specified chars |
2962
2996
  | `len` | `default` | Get length. Optional default if not sized: `\|len:0` |
2963
2997
  | `lowercase` | `raises` | Convert string to lowercase |
@@ -3012,6 +3046,19 @@ Comparison and membership transforms return `True`/`False`:
3012
3046
  >>> dotted.get({'n': 2}, 'n|in:[1, 2, 3]')
3013
3047
  True
3014
3048
 
3049
+ `coalesce` stands in a fallback for `None`, as SQL's does; with several
3050
+ fallbacks the first that is not `None` wins, which matters when a fallback is
3051
+ a [reference](#references-in-transforms-and-guards) that may itself be `None`:
3052
+
3053
+ >>> d = {'n': None, 'defaults': {'n': 7}}
3054
+ >>> dotted.get(d, 'n|coalesce:0')
3055
+ 0
3056
+ >>> dotted.get(d, 'n|coalesce:$$(defaults.n):0')
3057
+ 7
3058
+
3059
+ A transform only runs on a value the path yields, so `coalesce` does not fill
3060
+ a missing key; `get(..., default=)` does that.
3061
+
3015
3062
  <a id="container-transform-arguments"></a>
3016
3063
  ### Container transform arguments
3017
3064
 
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "dotted_notation"
9
- version = "0.47.0"
9
+ version = "0.47.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"