dotted-notation 0.44.7__tar.gz → 0.44.9__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.44.7 → dotted_notation-0.44.9}/CHANGELOG.md +49 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/PKG-INFO +59 -2
- dotted_notation-0.44.7/dotted_notation.egg-info/PKG-INFO → dotted_notation-0.44.9/README.md +52 -41
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/api.py +35 -7
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/base.py +15 -2
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/engine.py +2 -2
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/groups.py +134 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/recursive.py +49 -3
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/utils.py +6 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/wrappers.py +15 -0
- dotted_notation-0.44.7/README.md → dotted_notation-0.44.9/dotted_notation.egg-info/PKG-INFO +98 -1
- dotted_notation-0.44.9/dotted_notation.egg-info/requires.txt +29 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/pyproject.toml +8 -1
- dotted_notation-0.44.7/dotted_notation.egg-info/requires.txt +0 -17
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/LICENSE +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/MANIFEST.in +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/__init__.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/__main__.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/access.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/cli/__init__.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/cli/_compat.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/cli/formats.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/cli/main.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/containers.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/filters.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/grammar.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/matchers.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/predicates.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/results.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/sql/__init__.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/sql/core.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/sql/pg.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/transforms.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted/utypes.py +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted_notation.egg-info/SOURCES.txt +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted_notation.egg-info/dependency_links.txt +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted_notation.egg-info/entry_points.txt +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/dotted_notation.egg-info/top_level.txt +0 -0
- {dotted_notation-0.44.7 → dotted_notation-0.44.9}/setup.cfg +0 -0
|
@@ -3,6 +3,55 @@
|
|
|
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.44.9]
|
|
7
|
+
|
|
8
|
+
### Performance
|
|
9
|
+
- Optional [copium](https://github.com/Bobronium/copium) support: a C
|
|
10
|
+
implementation of `copy.deepcopy` with the same semantics. When
|
|
11
|
+
installed, the copies made by `mutable=False` updates and removes and
|
|
12
|
+
by `build()` use it (roughly 5-20x faster on plain nested data);
|
|
13
|
+
otherwise dotted falls back to `copy.deepcopy`. Install with
|
|
14
|
+
`pip install dotted-notation[copium]` (CPython 3.10+).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `copium` extra, and a `formats` extra bundling YAML and TOML support.
|
|
18
|
+
|
|
19
|
+
### Deprecated
|
|
20
|
+
- The `all` extra. It is an alias for `formats` and will be removed in
|
|
21
|
+
a future release.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- When copium is installed the test suite runs every test function
|
|
25
|
+
under both deepcopy implementations.
|
|
26
|
+
|
|
27
|
+
## [0.44.8]
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- `match()` supports variadic ops on the *path* side. A group or
|
|
31
|
+
recursive op in the path used to fall through to `None` (or match by
|
|
32
|
+
accident, as `**` did); the path is now treated as the set of paths it
|
|
33
|
+
denotes, and the pattern must *subsume* it — cover every expansion.
|
|
34
|
+
Every branch of a path-side group must match, so
|
|
35
|
+
`match('references.*', 'references.(a,b)')` matches while
|
|
36
|
+
`match('*.*', '(a.b,c)')` does not (branch `c` is a segment short).
|
|
37
|
+
Groups on both sides compose the two rules: some pattern branch must
|
|
38
|
+
cover every path branch. A path-side recursive is covered only by a
|
|
39
|
+
recursive pattern that subsumes it — `match('**', '*b')` matches,
|
|
40
|
+
`match('*', '**')` does not. Conjunctions match on any branch (their
|
|
41
|
+
expansions are the intersection); negations, denoting an open set,
|
|
42
|
+
only by an identical negation or a bare wildcard.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- A path-side group captures as one segment: itself, e.g.
|
|
46
|
+
`match('references.*', 'references.(a,b)', groups=True)` gives
|
|
47
|
+
`('references.(a,b)', ('references', '(a,b)'))`. When a variadic
|
|
48
|
+
pattern consumes across the group boundary the group folds into that
|
|
49
|
+
segment's capture, as nested patterns already do.
|
|
50
|
+
- Match dispatch is now per-op on both sides: variadic path ops
|
|
51
|
+
implement `do_match_path` (the mirror of `do_match`), and segment
|
|
52
|
+
coverage moved onto ops as `covered_by`, replacing the ad-hoc value
|
|
53
|
+
extraction `Recursive.do_match` used to do.
|
|
54
|
+
|
|
6
55
|
## [0.44.7]
|
|
7
56
|
|
|
8
57
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotted_notation
|
|
3
|
-
Version: 0.44.
|
|
3
|
+
Version: 0.44.9
|
|
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: MIT
|
|
@@ -27,11 +27,17 @@ Requires-Python: >=3.6
|
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
License-File: LICENSE
|
|
29
29
|
Requires-Dist: pyparsing>=3.0
|
|
30
|
+
Provides-Extra: copium
|
|
31
|
+
Requires-Dist: copium>=0.1.0; (python_version >= "3.10" and platform_python_implementation == "CPython") and extra == "copium"
|
|
30
32
|
Provides-Extra: yaml
|
|
31
33
|
Requires-Dist: PyYAML>=5.0; extra == "yaml"
|
|
32
34
|
Provides-Extra: toml
|
|
33
35
|
Requires-Dist: tomli>=1.0; python_version < "3.11" and extra == "toml"
|
|
34
36
|
Requires-Dist: tomli_w>=1.0; extra == "toml"
|
|
37
|
+
Provides-Extra: formats
|
|
38
|
+
Requires-Dist: PyYAML>=5.0; extra == "formats"
|
|
39
|
+
Requires-Dist: tomli>=1.0; python_version < "3.11" and extra == "formats"
|
|
40
|
+
Requires-Dist: tomli_w>=1.0; extra == "formats"
|
|
35
41
|
Provides-Extra: all
|
|
36
42
|
Requires-Dist: PyYAML>=5.0; extra == "all"
|
|
37
43
|
Requires-Dist: tomli>=1.0; python_version < "3.11" and extra == "all"
|
|
@@ -61,12 +67,22 @@ Since this package includes the [**`dq`** command-line tool](#cli-dq), several d
|
|
|
61
67
|
|
|
62
68
|
To install optional format support:
|
|
63
69
|
|
|
64
|
-
pip install dotted-notation[
|
|
70
|
+
pip install dotted-notation[formats]
|
|
65
71
|
|
|
66
72
|
Or pick only what you need:
|
|
67
73
|
|
|
68
74
|
pip install dotted-notation[yaml,toml]
|
|
69
75
|
|
|
76
|
+
> **Deprecated:** the `all` extra is an alias for `formats` and will be removed
|
|
77
|
+
> in a future release. Use `formats` instead.
|
|
78
|
+
|
|
79
|
+
For faster `mutable=False` updates and removes, install the
|
|
80
|
+
[copium](https://github.com/Bobronium/copium) extra, a C implementation of
|
|
81
|
+
`copy.deepcopy` (CPython 3.10+). Dotted uses it when present and falls back to
|
|
82
|
+
`copy.deepcopy` otherwise:
|
|
83
|
+
|
|
84
|
+
pip install dotted-notation[copium]
|
|
85
|
+
|
|
70
86
|
## Table of Contents
|
|
71
87
|
|
|
72
88
|
- [Safe Traversal (Optional Chaining)](#safe-traversal-optional-chaining)
|
|
@@ -519,6 +535,47 @@ single group, and counts as one pattern position:
|
|
|
519
535
|
>>> dotted.match('x.(a.b)', 'x.a.b', groups=True)
|
|
520
536
|
('x.a.b', ('x', 'a.b'))
|
|
521
537
|
|
|
538
|
+
The path may itself be a pattern, in which case `match` asks whether the
|
|
539
|
+
pattern *subsumes* it — covers everything the path denotes:
|
|
540
|
+
|
|
541
|
+
>>> dotted.match('*', '*.*')
|
|
542
|
+
'*.*'
|
|
543
|
+
>>> dotted.match('*.*', '*')
|
|
544
|
+
|
|
545
|
+
A group on the path side denotes all of its expansions, so every branch
|
|
546
|
+
must be covered. It captures as a single segment: itself.
|
|
547
|
+
|
|
548
|
+
>>> dotted.match('references.*', 'references.(a,b)')
|
|
549
|
+
'references.(a,b)'
|
|
550
|
+
>>> dotted.match('references.*', 'references.(a,b)', groups=True)
|
|
551
|
+
('references.(a,b)', ('references', '(a,b)'))
|
|
552
|
+
|
|
553
|
+
A branch that outruns the pattern is only covered when partial matching
|
|
554
|
+
is on, and never when the pattern needs more segments than the branch
|
|
555
|
+
has:
|
|
556
|
+
|
|
557
|
+
>>> dotted.match('*', '(a.b,c)')
|
|
558
|
+
'(a.b,c)'
|
|
559
|
+
>>> dotted.match('*', '(a.b,c)', partial=False)
|
|
560
|
+
>>> dotted.match('*.*', '(a.b,c)')
|
|
561
|
+
|
|
562
|
+
Groups on both sides compose the two rules — the pattern matches if
|
|
563
|
+
*some* branch of it covers *every* branch of the path:
|
|
564
|
+
|
|
565
|
+
>>> dotted.match('(a,b)', '(a.b,b)')
|
|
566
|
+
'(a.b,b)'
|
|
567
|
+
>>> dotted.match('(a,b)', '(a.b,b)', partial=False)
|
|
568
|
+
>>> dotted.match('(a,b)', '(a.b,c)')
|
|
569
|
+
|
|
570
|
+
A recursive op on the path side denotes unboundedly many expansions, so
|
|
571
|
+
only a recursive pattern that subsumes it will match:
|
|
572
|
+
|
|
573
|
+
>>> dotted.match('**', 'a.**')
|
|
574
|
+
'a.**'
|
|
575
|
+
>>> dotted.match('**', '*b')
|
|
576
|
+
'*b'
|
|
577
|
+
>>> dotted.match('*', '**')
|
|
578
|
+
|
|
522
579
|
<a id="replace"></a>
|
|
523
580
|
### Replace
|
|
524
581
|
|
|
@@ -1,43 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: dotted_notation
|
|
3
|
-
Version: 0.44.7
|
|
4
|
-
Summary: Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms
|
|
5
|
-
Author-email: Frey Waid <logophage1@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/freywaid/dotted
|
|
8
|
-
Project-URL: Source, https://github.com/freywaid/dotted
|
|
9
|
-
Project-URL: Changelog, https://github.com/freywaid/dotted/blob/master/CHANGELOG.md
|
|
10
|
-
Keywords: dotted,nested,path,pattern-matching,json,jsonb,sql,traversal
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
-
Classifier: Topic :: Utilities
|
|
26
|
-
Requires-Python: >=3.6
|
|
27
|
-
Description-Content-Type: text/markdown
|
|
28
|
-
License-File: LICENSE
|
|
29
|
-
Requires-Dist: pyparsing>=3.0
|
|
30
|
-
Provides-Extra: yaml
|
|
31
|
-
Requires-Dist: PyYAML>=5.0; extra == "yaml"
|
|
32
|
-
Provides-Extra: toml
|
|
33
|
-
Requires-Dist: tomli>=1.0; python_version < "3.11" and extra == "toml"
|
|
34
|
-
Requires-Dist: tomli_w>=1.0; extra == "toml"
|
|
35
|
-
Provides-Extra: all
|
|
36
|
-
Requires-Dist: PyYAML>=5.0; extra == "all"
|
|
37
|
-
Requires-Dist: tomli>=1.0; python_version < "3.11" and extra == "all"
|
|
38
|
-
Requires-Dist: tomli_w>=1.0; extra == "all"
|
|
39
|
-
Dynamic: license-file
|
|
40
|
-
|
|
41
1
|
# Dotted
|
|
42
2
|
|
|
43
3
|
Sometimes you want to fetch data from a deeply nested data structure. Dotted notation
|
|
@@ -61,12 +21,22 @@ Since this package includes the [**`dq`** command-line tool](#cli-dq), several d
|
|
|
61
21
|
|
|
62
22
|
To install optional format support:
|
|
63
23
|
|
|
64
|
-
pip install dotted-notation[
|
|
24
|
+
pip install dotted-notation[formats]
|
|
65
25
|
|
|
66
26
|
Or pick only what you need:
|
|
67
27
|
|
|
68
28
|
pip install dotted-notation[yaml,toml]
|
|
69
29
|
|
|
30
|
+
> **Deprecated:** the `all` extra is an alias for `formats` and will be removed
|
|
31
|
+
> in a future release. Use `formats` instead.
|
|
32
|
+
|
|
33
|
+
For faster `mutable=False` updates and removes, install the
|
|
34
|
+
[copium](https://github.com/Bobronium/copium) extra, a C implementation of
|
|
35
|
+
`copy.deepcopy` (CPython 3.10+). Dotted uses it when present and falls back to
|
|
36
|
+
`copy.deepcopy` otherwise:
|
|
37
|
+
|
|
38
|
+
pip install dotted-notation[copium]
|
|
39
|
+
|
|
70
40
|
## Table of Contents
|
|
71
41
|
|
|
72
42
|
- [Safe Traversal (Optional Chaining)](#safe-traversal-optional-chaining)
|
|
@@ -519,6 +489,47 @@ single group, and counts as one pattern position:
|
|
|
519
489
|
>>> dotted.match('x.(a.b)', 'x.a.b', groups=True)
|
|
520
490
|
('x.a.b', ('x', 'a.b'))
|
|
521
491
|
|
|
492
|
+
The path may itself be a pattern, in which case `match` asks whether the
|
|
493
|
+
pattern *subsumes* it — covers everything the path denotes:
|
|
494
|
+
|
|
495
|
+
>>> dotted.match('*', '*.*')
|
|
496
|
+
'*.*'
|
|
497
|
+
>>> dotted.match('*.*', '*')
|
|
498
|
+
|
|
499
|
+
A group on the path side denotes all of its expansions, so every branch
|
|
500
|
+
must be covered. It captures as a single segment: itself.
|
|
501
|
+
|
|
502
|
+
>>> dotted.match('references.*', 'references.(a,b)')
|
|
503
|
+
'references.(a,b)'
|
|
504
|
+
>>> dotted.match('references.*', 'references.(a,b)', groups=True)
|
|
505
|
+
('references.(a,b)', ('references', '(a,b)'))
|
|
506
|
+
|
|
507
|
+
A branch that outruns the pattern is only covered when partial matching
|
|
508
|
+
is on, and never when the pattern needs more segments than the branch
|
|
509
|
+
has:
|
|
510
|
+
|
|
511
|
+
>>> dotted.match('*', '(a.b,c)')
|
|
512
|
+
'(a.b,c)'
|
|
513
|
+
>>> dotted.match('*', '(a.b,c)', partial=False)
|
|
514
|
+
>>> dotted.match('*.*', '(a.b,c)')
|
|
515
|
+
|
|
516
|
+
Groups on both sides compose the two rules — the pattern matches if
|
|
517
|
+
*some* branch of it covers *every* branch of the path:
|
|
518
|
+
|
|
519
|
+
>>> dotted.match('(a,b)', '(a.b,b)')
|
|
520
|
+
'(a.b,b)'
|
|
521
|
+
>>> dotted.match('(a,b)', '(a.b,b)', partial=False)
|
|
522
|
+
>>> dotted.match('(a,b)', '(a.b,c)')
|
|
523
|
+
|
|
524
|
+
A recursive op on the path side denotes unboundedly many expansions, so
|
|
525
|
+
only a recursive pattern that subsumes it will match:
|
|
526
|
+
|
|
527
|
+
>>> dotted.match('**', 'a.**')
|
|
528
|
+
'a.**'
|
|
529
|
+
>>> dotted.match('**', '*b')
|
|
530
|
+
'*b'
|
|
531
|
+
>>> dotted.match('*', '**')
|
|
532
|
+
|
|
522
533
|
<a id="replace"></a>
|
|
523
534
|
### Replace
|
|
524
535
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Main api
|
|
3
3
|
"""
|
|
4
|
-
import copy
|
|
5
4
|
import enum
|
|
6
5
|
import functools
|
|
7
6
|
import itertools
|
|
@@ -14,6 +13,7 @@ from . import transforms
|
|
|
14
13
|
from . import base
|
|
15
14
|
from . import access
|
|
16
15
|
from . import matchers
|
|
16
|
+
from . import utils
|
|
17
17
|
from . import utypes
|
|
18
18
|
|
|
19
19
|
|
|
@@ -544,7 +544,7 @@ def update_if(obj, path, val, pred=lambda val: val is not None, mutable=True, ap
|
|
|
544
544
|
if obj is AUTO:
|
|
545
545
|
obj = _auto_root_from_path(path)
|
|
546
546
|
if not mutable and _is_mutable_container(obj):
|
|
547
|
-
obj =
|
|
547
|
+
obj = utils.deepcopy(obj)
|
|
548
548
|
mutable = True
|
|
549
549
|
|
|
550
550
|
if pred is not None and not pred(val):
|
|
@@ -562,7 +562,7 @@ def update_if_multi(obj, items, pred=lambda val: val is not None, mutable=True,
|
|
|
562
562
|
{'a': 1, 'c': 3}
|
|
563
563
|
"""
|
|
564
564
|
if not mutable and _is_mutable_container(obj):
|
|
565
|
-
obj =
|
|
565
|
+
obj = utils.deepcopy(obj)
|
|
566
566
|
mutable = True
|
|
567
567
|
for item in items:
|
|
568
568
|
path, val, *rest = item
|
|
@@ -646,7 +646,7 @@ def remove_if(obj, path, pred=lambda path: path is not None, val=ANY, mutable=Tr
|
|
|
646
646
|
if obj is AUTO:
|
|
647
647
|
obj = _auto_root_from_path(path)
|
|
648
648
|
if not mutable and _is_mutable_container(obj):
|
|
649
|
-
obj =
|
|
649
|
+
obj = utils.deepcopy(obj)
|
|
650
650
|
mutable = True
|
|
651
651
|
|
|
652
652
|
if pred is not None and not pred(path):
|
|
@@ -663,7 +663,7 @@ def remove_if_multi(obj, items, paths_only=True, pred=lambda path: path is not N
|
|
|
663
663
|
{}
|
|
664
664
|
"""
|
|
665
665
|
if not mutable and _is_mutable_container(obj):
|
|
666
|
-
obj =
|
|
666
|
+
obj = utils.deepcopy(obj)
|
|
667
667
|
mutable = True
|
|
668
668
|
|
|
669
669
|
if paths_only:
|
|
@@ -775,6 +775,33 @@ def match(pattern, path, groups=False, partial=True, strict=False):
|
|
|
775
775
|
>>> match('*b', 'b.b.b')
|
|
776
776
|
'b.b.b'
|
|
777
777
|
>>> match('*b', 'a.b.c')
|
|
778
|
+
|
|
779
|
+
A group on the *path* side denotes all its expansions; the pattern
|
|
780
|
+
must cover every branch (subsumption). The group captures as one
|
|
781
|
+
segment: itself.
|
|
782
|
+
>>> match('references.*', 'references.(a,b)')
|
|
783
|
+
'references.(a,b)'
|
|
784
|
+
>>> match('references.*', 'references.(a,b)', groups=True)
|
|
785
|
+
('references.(a,b)', ('references', '(a,b)'))
|
|
786
|
+
>>> match('**.z', '(a.b,c).z', groups=True)
|
|
787
|
+
('(a.b,c).z', ('(a.b,c)', 'z'))
|
|
788
|
+
>>> match('*', '(a.b,c)')
|
|
789
|
+
'(a.b,c)'
|
|
790
|
+
>>> match('*', '(a.b,c)', partial=False)
|
|
791
|
+
>>> match('*.*', '(a.b,c)')
|
|
792
|
+
>>> match('(a,b)', '(a.b,c)')
|
|
793
|
+
>>> match('(a,b)', '(a.b,b)')
|
|
794
|
+
'(a.b,b)'
|
|
795
|
+
>>> match('**.z', '(a.b,c).z')
|
|
796
|
+
'(a.b,c).z'
|
|
797
|
+
|
|
798
|
+
A recursive op on the path side is covered only by a recursive
|
|
799
|
+
pattern that subsumes it:
|
|
800
|
+
>>> match('**', 'a.**')
|
|
801
|
+
'a.**'
|
|
802
|
+
>>> match('**', '*b')
|
|
803
|
+
'*b'
|
|
804
|
+
>>> match('*', '**')
|
|
778
805
|
"""
|
|
779
806
|
# groups can be a bool, a GroupMode member, or its string value.
|
|
780
807
|
_patterns_only = (groups == GroupMode.patterns
|
|
@@ -791,8 +818,9 @@ def match(pattern, path, groups=False, partial=True, strict=False):
|
|
|
791
818
|
path_ops = parse(path)
|
|
792
819
|
|
|
793
820
|
# Variadic ops (recursive, groups) consume variable-length path
|
|
794
|
-
# segments — use the recursive matcher
|
|
795
|
-
|
|
821
|
+
# segments — use the recursive matcher. On the path side they make
|
|
822
|
+
# the path denote multiple expansions, which likewise needs it.
|
|
823
|
+
if any(op.is_variadic() for op in pats) or any(op.is_variadic() for op in path_ops):
|
|
796
824
|
result = base.match_ops(list(pats), list(path_ops), partial)
|
|
797
825
|
if result is None:
|
|
798
826
|
return returns(None, [])
|
|
@@ -46,9 +46,13 @@ def match_ops(pats, path_ops, partial):
|
|
|
46
46
|
"""
|
|
47
47
|
Match a list of pattern ops against a list of path ops.
|
|
48
48
|
Returns a list of (value, is_pattern) capture pairs on success, None
|
|
49
|
-
on failure.
|
|
50
|
-
|
|
49
|
+
on failure. Dispatch is per-op on both sides: a variadic op at the
|
|
50
|
+
head of the path (group, recursive) decides how the pattern must
|
|
51
|
+
cover it via do_match_path; otherwise the head pattern op decides
|
|
52
|
+
how many path segments it consumes via do_match.
|
|
51
53
|
"""
|
|
54
|
+
if path_ops and path_ops[0].is_variadic():
|
|
55
|
+
return path_ops[0].do_match_path(list(pats), list(path_ops[1:]), partial)
|
|
52
56
|
if pats:
|
|
53
57
|
return pats[0].do_match(pats[1:], path_ops, partial)
|
|
54
58
|
if not path_ops:
|
|
@@ -207,6 +211,15 @@ class TraversalOp(Op):
|
|
|
207
211
|
"""
|
|
208
212
|
return False
|
|
209
213
|
|
|
214
|
+
def covered_by(self, matcher):
|
|
215
|
+
"""
|
|
216
|
+
True if *matcher* (a match op) matches every segment this op
|
|
217
|
+
denotes when it appears on the path side. Simple ops denote a
|
|
218
|
+
single concrete segment; variadic ops override.
|
|
219
|
+
"""
|
|
220
|
+
val = getattr(getattr(self, 'op', self), 'value', self)
|
|
221
|
+
return has_any(matcher.matches((val,)))
|
|
222
|
+
|
|
210
223
|
def do_match(self, rest_pats, path_ops, partial):
|
|
211
224
|
"""
|
|
212
225
|
Match this op against exactly one path segment, then continue with
|
|
@@ -3,10 +3,10 @@ Traversal engine for dotted path operations.
|
|
|
3
3
|
|
|
4
4
|
Core traversal functions (walk, gets, updates, removes, expands).
|
|
5
5
|
"""
|
|
6
|
-
import copy
|
|
7
6
|
|
|
8
7
|
from . import base
|
|
9
8
|
from . import matchers
|
|
9
|
+
from . import utils
|
|
10
10
|
from . import wrappers
|
|
11
11
|
from .access import Attr, Slot
|
|
12
12
|
from .results import Dotted
|
|
@@ -42,7 +42,7 @@ def build(ops, node, deepcopy=True, **kwargs):
|
|
|
42
42
|
built = node.__class__()
|
|
43
43
|
for k,v in cur.items(node, **kwargs):
|
|
44
44
|
if not ops:
|
|
45
|
-
built = cur.update(built, k,
|
|
45
|
+
built = cur.update(built, k, utils.deepcopy(v) if deepcopy else v)
|
|
46
46
|
else:
|
|
47
47
|
built = cur.update(built, k, build(ops, v, deepcopy=deepcopy, **kwargs))
|
|
48
48
|
return built or build_default([cur]+ops)
|
|
@@ -128,6 +128,85 @@ class OpGroup(base.TraversalOp):
|
|
|
128
128
|
return [(combined, True)] + rest
|
|
129
129
|
return None
|
|
130
130
|
|
|
131
|
+
def do_match_path(self, pats, rest_path, partial):
|
|
132
|
+
"""
|
|
133
|
+
Match when this group appears on the *path* side. The path
|
|
134
|
+
denotes every expansion of the group's branches, so the pattern
|
|
135
|
+
must cover all of them (subsumption): each branch is spliced
|
|
136
|
+
into the path in the group's place and matched; one failing
|
|
137
|
+
branch fails the whole match.
|
|
138
|
+
|
|
139
|
+
The group captures as itself — one capture — via an aligned
|
|
140
|
+
decomposition (a pattern prefix covering every branch), or
|
|
141
|
+
assembled into a variadic pattern segment's capture when the
|
|
142
|
+
pattern crosses the group boundary; only when neither
|
|
143
|
+
decomposition exists do captures fall back to the first
|
|
144
|
+
branch's expansion.
|
|
145
|
+
"""
|
|
146
|
+
fallback = self._verify_covered(pats, rest_path, partial)
|
|
147
|
+
if fallback is None:
|
|
148
|
+
return None
|
|
149
|
+
r = self._aligned_captures(pats, rest_path, partial)
|
|
150
|
+
if r is not None:
|
|
151
|
+
return r
|
|
152
|
+
if pats and pats[0].is_variadic():
|
|
153
|
+
r = pats[0].do_match(pats[1:], [self] + list(rest_path), partial)
|
|
154
|
+
if r is not None:
|
|
155
|
+
return r
|
|
156
|
+
return fallback
|
|
157
|
+
|
|
158
|
+
def _verify_covered(self, pats, rest_path, partial):
|
|
159
|
+
"""
|
|
160
|
+
Subsumption check over expansions: splice each branch into the
|
|
161
|
+
path and match. Returns the first branch's result (the capture
|
|
162
|
+
fallback) or None when any branch is uncovered.
|
|
163
|
+
"""
|
|
164
|
+
first = base.marker
|
|
165
|
+
for branch in base.branches_only(self.branches):
|
|
166
|
+
r = base.match_ops(pats, list(branch) + list(rest_path), partial)
|
|
167
|
+
if r is None:
|
|
168
|
+
return None
|
|
169
|
+
if first is base.marker:
|
|
170
|
+
first = r
|
|
171
|
+
if first is base.marker:
|
|
172
|
+
return base.match_ops(pats, list(rest_path), partial)
|
|
173
|
+
return first
|
|
174
|
+
|
|
175
|
+
def _aligned_captures(self, pats, rest_path, partial):
|
|
176
|
+
"""
|
|
177
|
+
Group-as-itself captures: find a pattern prefix that covers the
|
|
178
|
+
branch set exactly (or up to a partial tail when the pattern
|
|
179
|
+
ends inside the group); the group then captures as one segment
|
|
180
|
+
and the remaining pattern matches the remaining path.
|
|
181
|
+
"""
|
|
182
|
+
from . import results
|
|
183
|
+
for k in range(1, len(pats) + 1):
|
|
184
|
+
head = list(pats[:k])
|
|
185
|
+
tail_partial = partial and k == len(pats) and not rest_path
|
|
186
|
+
if not self._covers_branches(head, tail_partial):
|
|
187
|
+
continue
|
|
188
|
+
rest = base.match_ops(list(pats[k:]), list(rest_path), partial)
|
|
189
|
+
if rest is None:
|
|
190
|
+
continue
|
|
191
|
+
is_pat = any(p.is_pattern() for p in head)
|
|
192
|
+
return [(results.assemble([self]), is_pat)] + rest
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
def _covers_branches(self, head_pats, tail_partial=False):
|
|
196
|
+
"""
|
|
197
|
+
True if head_pats covers every branch of this group.
|
|
198
|
+
"""
|
|
199
|
+
return all(base.match_ops(head_pats, list(b), tail_partial) is not None
|
|
200
|
+
for b in base.branches_only(self.branches))
|
|
201
|
+
|
|
202
|
+
def covered_by(self, matcher):
|
|
203
|
+
"""
|
|
204
|
+
A group path op is covered when every segment of every branch is.
|
|
205
|
+
"""
|
|
206
|
+
return all(op.covered_by(matcher)
|
|
207
|
+
for branch in base.branches_only(self.branches)
|
|
208
|
+
for op in branch)
|
|
209
|
+
|
|
131
210
|
def _render(self, top=True):
|
|
132
211
|
"""
|
|
133
212
|
Render the group as a string. Subclasses override this.
|
|
@@ -429,6 +508,25 @@ class OpGroupAnd(OpGroup):
|
|
|
429
508
|
def __repr__(self):
|
|
430
509
|
return self._render(top=True)
|
|
431
510
|
|
|
511
|
+
def _verify_covered(self, pats, rest_path, partial):
|
|
512
|
+
"""
|
|
513
|
+
A conjunction's expansions are the intersection of its
|
|
514
|
+
branches', so a pattern covering any single branch covers the
|
|
515
|
+
whole (sufficient, conservatively incomplete).
|
|
516
|
+
"""
|
|
517
|
+
for branch in base.branches_only(self.branches):
|
|
518
|
+
r = base.match_ops(pats, list(branch) + list(rest_path), partial)
|
|
519
|
+
if r is not None:
|
|
520
|
+
return r
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
def _covers_branches(self, head_pats, tail_partial=False):
|
|
524
|
+
"""
|
|
525
|
+
Any single covered branch suffices for a conjunction.
|
|
526
|
+
"""
|
|
527
|
+
return any(base.match_ops(head_pats, list(b), tail_partial) is not None
|
|
528
|
+
for b in base.branches_only(self.branches))
|
|
529
|
+
|
|
432
530
|
def push_children(self, stack, frame, paths):
|
|
433
531
|
"""
|
|
434
532
|
All branches must match. Collect results per branch;
|
|
@@ -566,6 +664,42 @@ class OpGroupNot(OpGroup):
|
|
|
566
664
|
seg_val = getattr(getattr(kop, 'op', kop), 'value', kop)
|
|
567
665
|
return [(seg_val, True)] + rest
|
|
568
666
|
|
|
667
|
+
def do_match_path(self, pats, rest_path, partial):
|
|
668
|
+
"""
|
|
669
|
+
Match when this negation appears on the *path* side. It denotes
|
|
670
|
+
every segment its inner pattern excludes — an open set — so it
|
|
671
|
+
is covered only by an identical negation or by a bare wildcard
|
|
672
|
+
segment.
|
|
673
|
+
"""
|
|
674
|
+
from . import results
|
|
675
|
+
if not pats:
|
|
676
|
+
return [] if partial else None
|
|
677
|
+
head = pats[0]
|
|
678
|
+
if head != self and not self._wildcard_covers(head):
|
|
679
|
+
return None
|
|
680
|
+
rest = base.match_ops(pats[1:], rest_path, partial)
|
|
681
|
+
if rest is None:
|
|
682
|
+
return None
|
|
683
|
+
return [(results.assemble([self]), True)] + rest
|
|
684
|
+
|
|
685
|
+
def _wildcard_covers(self, head):
|
|
686
|
+
"""
|
|
687
|
+
True if pattern op *head* is a bare single-segment wildcard and
|
|
688
|
+
this negation excludes only single segments.
|
|
689
|
+
"""
|
|
690
|
+
from . import matchers
|
|
691
|
+
if any(len(b) != 1 for b in base.branches_only(self.branches)):
|
|
692
|
+
return False
|
|
693
|
+
return isinstance(getattr(head, 'op', None), matchers.Wildcard)
|
|
694
|
+
|
|
695
|
+
def covered_by(self, matcher):
|
|
696
|
+
"""
|
|
697
|
+
A negation denotes an open set of segments; only a wildcard
|
|
698
|
+
covers them all.
|
|
699
|
+
"""
|
|
700
|
+
from . import matchers
|
|
701
|
+
return isinstance(matcher, matchers.Wildcard)
|
|
702
|
+
|
|
569
703
|
def do_update(self, ops, node, val, has_defaults, _path, nop, nop_from_unwrap=False, **kwargs):
|
|
570
704
|
inner = self.inner
|
|
571
705
|
if not inner:
|
|
@@ -125,9 +125,7 @@ class Recursive(BaseOp):
|
|
|
125
125
|
"""
|
|
126
126
|
from . import results
|
|
127
127
|
for n in range(1, len(path_ops) + 1):
|
|
128
|
-
|
|
129
|
-
seg_val = getattr(getattr(kop, 'op', kop), 'value', kop)
|
|
130
|
-
if not any(True for _ in self.inner.matches((seg_val,))):
|
|
128
|
+
if not path_ops[n - 1].covered_by(self.inner):
|
|
131
129
|
return None
|
|
132
130
|
rest = base.match_ops(rest_pats, path_ops[n:], partial)
|
|
133
131
|
if rest is None:
|
|
@@ -136,6 +134,54 @@ class Recursive(BaseOp):
|
|
|
136
134
|
return [(combined, True)] + rest
|
|
137
135
|
return None
|
|
138
136
|
|
|
137
|
+
def do_match_path(self, pats, rest_path, partial):
|
|
138
|
+
"""
|
|
139
|
+
Match when this recursive op appears on the *path* side. It
|
|
140
|
+
denotes unboundedly many expansions, so only a pattern segment
|
|
141
|
+
that itself covers arbitrary depth can subsume it: a recursive
|
|
142
|
+
whose inner pattern covers this op's chain segments. The
|
|
143
|
+
subsuming pattern op is tried both consumed and retained, since
|
|
144
|
+
a recursive pattern may keep covering segments after the group.
|
|
145
|
+
"""
|
|
146
|
+
from . import results
|
|
147
|
+
if not pats:
|
|
148
|
+
return [] if partial else None
|
|
149
|
+
head = pats[0]
|
|
150
|
+
if not isinstance(head, Recursive):
|
|
151
|
+
return None
|
|
152
|
+
if not self._subsumed_by(head):
|
|
153
|
+
return None
|
|
154
|
+
rest = base.match_ops(pats[1:], rest_path, partial)
|
|
155
|
+
if rest is None:
|
|
156
|
+
rest = base.match_ops(pats, rest_path, partial)
|
|
157
|
+
if rest is None:
|
|
158
|
+
return None
|
|
159
|
+
return [(results.assemble([self]), True)] + rest
|
|
160
|
+
|
|
161
|
+
def _subsumed_by(self, pat):
|
|
162
|
+
"""
|
|
163
|
+
True if recursive pattern op *pat* covers every expansion of
|
|
164
|
+
this path op. Identical recursives cover; otherwise an
|
|
165
|
+
unsliced recursive with the same accessors whose inner matcher
|
|
166
|
+
covers every chain segment this op can produce.
|
|
167
|
+
"""
|
|
168
|
+
if pat == self:
|
|
169
|
+
return True
|
|
170
|
+
if pat.depth_start is not None or pat.depth_stop is not None or pat.depth_step is not None:
|
|
171
|
+
return False
|
|
172
|
+
if pat.accessors != self.accessors:
|
|
173
|
+
return False
|
|
174
|
+
return self.covered_by(pat.inner)
|
|
175
|
+
|
|
176
|
+
def covered_by(self, matcher):
|
|
177
|
+
"""
|
|
178
|
+
A recursive path op denotes unboundedly many segments; only a
|
|
179
|
+
wildcard, or this op's own inner matcher, covers them all.
|
|
180
|
+
"""
|
|
181
|
+
if isinstance(matcher, matchers.Wildcard):
|
|
182
|
+
return True
|
|
183
|
+
return matcher == self.inner
|
|
184
|
+
|
|
139
185
|
def _effective_branches(self):
|
|
140
186
|
"""
|
|
141
187
|
Return accessor branches that drive recursion.
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Shared type-checking helpers (duck-typing).
|
|
3
3
|
"""
|
|
4
|
+
try:
|
|
5
|
+
# C implementation of copy.deepcopy with identical semantics;
|
|
6
|
+
# installed by the [copium] extra.
|
|
7
|
+
from copium import deepcopy
|
|
8
|
+
except ImportError:
|
|
9
|
+
from copy import deepcopy
|
|
4
10
|
|
|
5
11
|
class lazyprop:
|
|
6
12
|
"""
|
|
@@ -84,6 +84,21 @@ class Wrap(base.TraversalOp):
|
|
|
84
84
|
return self.inner.do_match(rest_pats, path_ops, partial)
|
|
85
85
|
return super().do_match(rest_pats, path_ops, partial)
|
|
86
86
|
|
|
87
|
+
def do_match_path(self, pats, rest_path, partial):
|
|
88
|
+
"""
|
|
89
|
+
Delegate path-side matching to the wrapped op (only reached for
|
|
90
|
+
variadic inners, via is_variadic).
|
|
91
|
+
"""
|
|
92
|
+
return self.inner.do_match_path(pats, rest_path, partial)
|
|
93
|
+
|
|
94
|
+
def covered_by(self, matcher):
|
|
95
|
+
"""
|
|
96
|
+
Delegate segment coverage to the wrapped op.
|
|
97
|
+
"""
|
|
98
|
+
if hasattr(self.inner, 'covered_by'):
|
|
99
|
+
return self.inner.covered_by(matcher)
|
|
100
|
+
return super().covered_by(matcher)
|
|
101
|
+
|
|
87
102
|
def concrete(self, val):
|
|
88
103
|
return self.inner.concrete(val)
|
|
89
104
|
|
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dotted_notation
|
|
3
|
+
Version: 0.44.9
|
|
4
|
+
Summary: Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms
|
|
5
|
+
Author-email: Frey Waid <logophage1@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/freywaid/dotted
|
|
8
|
+
Project-URL: Source, https://github.com/freywaid/dotted
|
|
9
|
+
Project-URL: Changelog, https://github.com/freywaid/dotted/blob/master/CHANGELOG.md
|
|
10
|
+
Keywords: dotted,nested,path,pattern-matching,json,jsonb,sql,traversal
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Requires-Python: >=3.6
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: pyparsing>=3.0
|
|
30
|
+
Provides-Extra: copium
|
|
31
|
+
Requires-Dist: copium>=0.1.0; (python_version >= "3.10" and platform_python_implementation == "CPython") and extra == "copium"
|
|
32
|
+
Provides-Extra: yaml
|
|
33
|
+
Requires-Dist: PyYAML>=5.0; extra == "yaml"
|
|
34
|
+
Provides-Extra: toml
|
|
35
|
+
Requires-Dist: tomli>=1.0; python_version < "3.11" and extra == "toml"
|
|
36
|
+
Requires-Dist: tomli_w>=1.0; extra == "toml"
|
|
37
|
+
Provides-Extra: formats
|
|
38
|
+
Requires-Dist: PyYAML>=5.0; extra == "formats"
|
|
39
|
+
Requires-Dist: tomli>=1.0; python_version < "3.11" and extra == "formats"
|
|
40
|
+
Requires-Dist: tomli_w>=1.0; extra == "formats"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: PyYAML>=5.0; extra == "all"
|
|
43
|
+
Requires-Dist: tomli>=1.0; python_version < "3.11" and extra == "all"
|
|
44
|
+
Requires-Dist: tomli_w>=1.0; extra == "all"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
1
47
|
# Dotted
|
|
2
48
|
|
|
3
49
|
Sometimes you want to fetch data from a deeply nested data structure. Dotted notation
|
|
@@ -21,12 +67,22 @@ Since this package includes the [**`dq`** command-line tool](#cli-dq), several d
|
|
|
21
67
|
|
|
22
68
|
To install optional format support:
|
|
23
69
|
|
|
24
|
-
pip install dotted-notation[
|
|
70
|
+
pip install dotted-notation[formats]
|
|
25
71
|
|
|
26
72
|
Or pick only what you need:
|
|
27
73
|
|
|
28
74
|
pip install dotted-notation[yaml,toml]
|
|
29
75
|
|
|
76
|
+
> **Deprecated:** the `all` extra is an alias for `formats` and will be removed
|
|
77
|
+
> in a future release. Use `formats` instead.
|
|
78
|
+
|
|
79
|
+
For faster `mutable=False` updates and removes, install the
|
|
80
|
+
[copium](https://github.com/Bobronium/copium) extra, a C implementation of
|
|
81
|
+
`copy.deepcopy` (CPython 3.10+). Dotted uses it when present and falls back to
|
|
82
|
+
`copy.deepcopy` otherwise:
|
|
83
|
+
|
|
84
|
+
pip install dotted-notation[copium]
|
|
85
|
+
|
|
30
86
|
## Table of Contents
|
|
31
87
|
|
|
32
88
|
- [Safe Traversal (Optional Chaining)](#safe-traversal-optional-chaining)
|
|
@@ -479,6 +535,47 @@ single group, and counts as one pattern position:
|
|
|
479
535
|
>>> dotted.match('x.(a.b)', 'x.a.b', groups=True)
|
|
480
536
|
('x.a.b', ('x', 'a.b'))
|
|
481
537
|
|
|
538
|
+
The path may itself be a pattern, in which case `match` asks whether the
|
|
539
|
+
pattern *subsumes* it — covers everything the path denotes:
|
|
540
|
+
|
|
541
|
+
>>> dotted.match('*', '*.*')
|
|
542
|
+
'*.*'
|
|
543
|
+
>>> dotted.match('*.*', '*')
|
|
544
|
+
|
|
545
|
+
A group on the path side denotes all of its expansions, so every branch
|
|
546
|
+
must be covered. It captures as a single segment: itself.
|
|
547
|
+
|
|
548
|
+
>>> dotted.match('references.*', 'references.(a,b)')
|
|
549
|
+
'references.(a,b)'
|
|
550
|
+
>>> dotted.match('references.*', 'references.(a,b)', groups=True)
|
|
551
|
+
('references.(a,b)', ('references', '(a,b)'))
|
|
552
|
+
|
|
553
|
+
A branch that outruns the pattern is only covered when partial matching
|
|
554
|
+
is on, and never when the pattern needs more segments than the branch
|
|
555
|
+
has:
|
|
556
|
+
|
|
557
|
+
>>> dotted.match('*', '(a.b,c)')
|
|
558
|
+
'(a.b,c)'
|
|
559
|
+
>>> dotted.match('*', '(a.b,c)', partial=False)
|
|
560
|
+
>>> dotted.match('*.*', '(a.b,c)')
|
|
561
|
+
|
|
562
|
+
Groups on both sides compose the two rules — the pattern matches if
|
|
563
|
+
*some* branch of it covers *every* branch of the path:
|
|
564
|
+
|
|
565
|
+
>>> dotted.match('(a,b)', '(a.b,b)')
|
|
566
|
+
'(a.b,b)'
|
|
567
|
+
>>> dotted.match('(a,b)', '(a.b,b)', partial=False)
|
|
568
|
+
>>> dotted.match('(a,b)', '(a.b,c)')
|
|
569
|
+
|
|
570
|
+
A recursive op on the path side denotes unboundedly many expansions, so
|
|
571
|
+
only a recursive pattern that subsumes it will match:
|
|
572
|
+
|
|
573
|
+
>>> dotted.match('**', 'a.**')
|
|
574
|
+
'a.**'
|
|
575
|
+
>>> dotted.match('**', '*b')
|
|
576
|
+
'*b'
|
|
577
|
+
>>> dotted.match('*', '**')
|
|
578
|
+
|
|
482
579
|
<a id="replace"></a>
|
|
483
580
|
### Replace
|
|
484
581
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
pyparsing>=3.0
|
|
2
|
+
|
|
3
|
+
[all]
|
|
4
|
+
PyYAML>=5.0
|
|
5
|
+
tomli_w>=1.0
|
|
6
|
+
|
|
7
|
+
[all:python_version < "3.11"]
|
|
8
|
+
tomli>=1.0
|
|
9
|
+
|
|
10
|
+
[copium]
|
|
11
|
+
|
|
12
|
+
[copium:python_version >= "3.10" and platform_python_implementation == "CPython"]
|
|
13
|
+
copium>=0.1.0
|
|
14
|
+
|
|
15
|
+
[formats]
|
|
16
|
+
PyYAML>=5.0
|
|
17
|
+
tomli_w>=1.0
|
|
18
|
+
|
|
19
|
+
[formats:python_version < "3.11"]
|
|
20
|
+
tomli>=1.0
|
|
21
|
+
|
|
22
|
+
[toml]
|
|
23
|
+
tomli_w>=1.0
|
|
24
|
+
|
|
25
|
+
[toml:python_version < "3.11"]
|
|
26
|
+
tomli>=1.0
|
|
27
|
+
|
|
28
|
+
[yaml]
|
|
29
|
+
PyYAML>=5.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dotted_notation"
|
|
7
|
-
version = "0.44.
|
|
7
|
+
version = "0.44.9"
|
|
8
8
|
description = "Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.6"
|
|
@@ -44,8 +44,15 @@ dependencies = [
|
|
|
44
44
|
]
|
|
45
45
|
|
|
46
46
|
[project.optional-dependencies]
|
|
47
|
+
copium = ['copium>=0.1.0;python_version>="3.10" and platform_python_implementation=="CPython"']
|
|
47
48
|
yaml = ["PyYAML>=5.0"]
|
|
48
49
|
toml = ['tomli>=1.0;python_version<"3.11"', "tomli_w>=1.0"]
|
|
50
|
+
formats = [
|
|
51
|
+
"PyYAML>=5.0",
|
|
52
|
+
'tomli>=1.0;python_version<"3.11"',
|
|
53
|
+
"tomli_w>=1.0",
|
|
54
|
+
]
|
|
55
|
+
# DEPRECATED: legacy alias for formats, to be removed in a future release
|
|
49
56
|
all = [
|
|
50
57
|
"PyYAML>=5.0",
|
|
51
58
|
'tomli>=1.0;python_version<"3.11"',
|
|
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.44.7 → dotted_notation-0.44.9}/dotted_notation.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|