dotted-notation 0.41.0__tar.gz → 0.41.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dotted_notation-0.41.0/dotted_notation.egg-info → dotted_notation-0.41.2}/PKG-INFO +39 -5
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/README.md +38 -4
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/base.py +8 -1
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/engine.py +1 -1
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/grammar.py +16 -4
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/matchers.py +36 -34
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/wrappers.py +16 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2/dotted_notation.egg-info}/PKG-INFO +39 -5
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted_notation.egg-info/SOURCES.txt +1 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/setup.py +1 -1
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_named_subst.py +4 -4
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_replace.py +10 -10
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_subst_escape.py +2 -2
- dotted_notation-0.41.2/tests/test_subst_transforms.py +173 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/LICENSE +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/__init__.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/__main__.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/access.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/api.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/cli/__init__.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/cli/_compat.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/cli/formats.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/cli/main.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/containers.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/filters.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/groups.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/predicates.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/recursive.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/results.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/transforms.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/utils.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted/utypes.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted_notation.egg-info/dependency_links.txt +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted_notation.egg-info/entry_points.txt +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted_notation.egg-info/requires.txt +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/dotted_notation.egg-info/top_level.txt +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/setup.cfg +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/__init__.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_api.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_appender.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_assemble.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_attrs.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_cli.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_container_filter.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_cut.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_empty.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_filter_keyvalue.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_get.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_guard_transforms.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_invert.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_keys_values.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_match.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_negation.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_nop.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_numeric.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_opgroup.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_pluck.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_predicates.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_quote_idempotent.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_recursive.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_reference.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_slice.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_softcut.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_strict.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_string_glob.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_transforms.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_translate.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_type_restriction.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_unpack.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_update.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_update_if.py +0 -0
- {dotted_notation-0.41.0 → dotted_notation-0.41.2}/tests/test_value_guard.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotted_notation
|
|
3
|
-
Version: 0.41.
|
|
3
|
+
Version: 0.41.2
|
|
4
4
|
Summary: Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms
|
|
5
5
|
Home-page: https://github.com/freywaid/dotted
|
|
6
6
|
Author: Frey Waid
|
|
@@ -111,6 +111,7 @@ Or pick only what you need:
|
|
|
111
111
|
- [Slicing vs Patterns](#slicing-vs-patterns)
|
|
112
112
|
- [Substitutions and References](#substitutions-and-references)
|
|
113
113
|
- [Substitution](#substitution)
|
|
114
|
+
- [Substitution transforms](#substitution-transforms)
|
|
114
115
|
- [References](#references)
|
|
115
116
|
- [Relative References](#relative-references)
|
|
116
117
|
- [Escaping](#escaping)
|
|
@@ -462,7 +463,7 @@ excluding literals:
|
|
|
462
463
|
Substitute placeholders in a template path with bound values. Template paths
|
|
463
464
|
are validated at parse time — structural errors are caught immediately, not at runtime.
|
|
464
465
|
|
|
465
|
-
Positional (`$N`) placeholders resolve against a tuple:
|
|
466
|
+
Positional (`$N`) placeholders resolve against a list or tuple:
|
|
466
467
|
|
|
467
468
|
>>> import dotted
|
|
468
469
|
>>> dotted.replace('people.$1.$2', ('users', 'alice', 'age'))
|
|
@@ -473,6 +474,11 @@ Named (`$(name)`) placeholders resolve against a dict:
|
|
|
473
474
|
>>> dotted.replace('$(table).$(key)', {'table': 'users', 'key': 'alice'})
|
|
474
475
|
'users.alice'
|
|
475
476
|
|
|
477
|
+
Placeholders support transforms inside the parenthesized form:
|
|
478
|
+
|
|
479
|
+
>>> dotted.replace('$(0|uppercase)', ['hello'])
|
|
480
|
+
'HELLO'
|
|
481
|
+
|
|
476
482
|
Combine with `match` to remap paths — capture groups from one pattern and substitute
|
|
477
483
|
into another:
|
|
478
484
|
|
|
@@ -1167,8 +1173,10 @@ at replace time) and **references** (resolved during traversal).
|
|
|
1167
1173
|
|
|
1168
1174
|
| Syntax | Type | Resolved against |
|
|
1169
1175
|
|---|---|---|
|
|
1170
|
-
| `$0`, `$1` | Positional substitution | `replace()` bindings |
|
|
1171
|
-
| `$(name)` | Named substitution | `replace()` bindings |
|
|
1176
|
+
| `$0`, `$1` | Positional substitution | `replace()` bindings (list/tuple) |
|
|
1177
|
+
| `$(name)` | Named substitution | `replace()` bindings (dict) |
|
|
1178
|
+
| `$(0)`, `$(name)` | Substitution with parens | `replace()` bindings via `__getitem__` |
|
|
1179
|
+
| `$(name\|int)` | Substitution with transform | `replace()` bindings, then transform |
|
|
1172
1180
|
| `$$(path)` | Reference | Root object during traversal |
|
|
1173
1181
|
| `$$(^path)` | Relative reference | Current node during traversal |
|
|
1174
1182
|
| `$$(^^path)` | Relative reference | Parent node during traversal |
|
|
@@ -1178,7 +1186,7 @@ at replace time) and **references** (resolved during traversal).
|
|
|
1178
1186
|
|
|
1179
1187
|
Substitution references turn a path into a **template**. There are two forms:
|
|
1180
1188
|
|
|
1181
|
-
- **Positional** (`$0`, `$1`, …) — resolved against a
|
|
1189
|
+
- **Positional** (`$0`, `$1`, …) — resolved against a list or tuple
|
|
1182
1190
|
- **Named** (`$(name)`, `$(key)`, …) — resolved against a dict
|
|
1183
1191
|
|
|
1184
1192
|
The `replace` function resolves them:
|
|
@@ -1188,6 +1196,13 @@ The `replace` function resolves them:
|
|
|
1188
1196
|
>>> dotted.replace('$(table).$(field)', {'table': 'users', 'field': 'email'})
|
|
1189
1197
|
'users.email'
|
|
1190
1198
|
|
|
1199
|
+
The parenthesized form `$(N)` adapts to the binding type — it uses `__getitem__`,
|
|
1200
|
+
so `$(0)` works as a positional index against a list or as a numeric key against
|
|
1201
|
+
a dict:
|
|
1202
|
+
|
|
1203
|
+
>>> dotted.replace('$(0)', {0: 'zero'})
|
|
1204
|
+
'zero'
|
|
1205
|
+
|
|
1191
1206
|
Use `is_template` to test whether a path contains substitution references:
|
|
1192
1207
|
|
|
1193
1208
|
>>> dotted.is_template('a.$0')
|
|
@@ -1197,6 +1212,25 @@ Use `is_template` to test whether a path contains substitution references:
|
|
|
1197
1212
|
>>> dotted.is_template('a.b')
|
|
1198
1213
|
False
|
|
1199
1214
|
|
|
1215
|
+
#### Substitution transforms
|
|
1216
|
+
|
|
1217
|
+
Substitutions support per-substitution transforms using the `|` separator inside
|
|
1218
|
+
the parenthesized form. The transform is applied to the resolved value before it
|
|
1219
|
+
is spliced into the path:
|
|
1220
|
+
|
|
1221
|
+
>>> dotted.replace('$(name|uppercase)', {'name': 'hello'})
|
|
1222
|
+
'HELLO'
|
|
1223
|
+
>>> dotted.replace('$(0|str)', [42])
|
|
1224
|
+
'42'
|
|
1225
|
+
|
|
1226
|
+
Multiple transforms chain left to right:
|
|
1227
|
+
|
|
1228
|
+
>>> dotted.replace('$(name|strip|lowercase)', {'name': ' HELLO '})
|
|
1229
|
+
'hello'
|
|
1230
|
+
|
|
1231
|
+
All [built-in transforms](#built-in-transforms) are available. The bare `$N` form
|
|
1232
|
+
does not support transforms — use `$(N|transform)` instead.
|
|
1233
|
+
|
|
1200
1234
|
See [Replace](#replace) and [Translate](#translate) for full API details.
|
|
1201
1235
|
|
|
1202
1236
|
<a id="references"></a>
|
|
@@ -74,6 +74,7 @@ Or pick only what you need:
|
|
|
74
74
|
- [Slicing vs Patterns](#slicing-vs-patterns)
|
|
75
75
|
- [Substitutions and References](#substitutions-and-references)
|
|
76
76
|
- [Substitution](#substitution)
|
|
77
|
+
- [Substitution transforms](#substitution-transforms)
|
|
77
78
|
- [References](#references)
|
|
78
79
|
- [Relative References](#relative-references)
|
|
79
80
|
- [Escaping](#escaping)
|
|
@@ -425,7 +426,7 @@ excluding literals:
|
|
|
425
426
|
Substitute placeholders in a template path with bound values. Template paths
|
|
426
427
|
are validated at parse time — structural errors are caught immediately, not at runtime.
|
|
427
428
|
|
|
428
|
-
Positional (`$N`) placeholders resolve against a tuple:
|
|
429
|
+
Positional (`$N`) placeholders resolve against a list or tuple:
|
|
429
430
|
|
|
430
431
|
>>> import dotted
|
|
431
432
|
>>> dotted.replace('people.$1.$2', ('users', 'alice', 'age'))
|
|
@@ -436,6 +437,11 @@ Named (`$(name)`) placeholders resolve against a dict:
|
|
|
436
437
|
>>> dotted.replace('$(table).$(key)', {'table': 'users', 'key': 'alice'})
|
|
437
438
|
'users.alice'
|
|
438
439
|
|
|
440
|
+
Placeholders support transforms inside the parenthesized form:
|
|
441
|
+
|
|
442
|
+
>>> dotted.replace('$(0|uppercase)', ['hello'])
|
|
443
|
+
'HELLO'
|
|
444
|
+
|
|
439
445
|
Combine with `match` to remap paths — capture groups from one pattern and substitute
|
|
440
446
|
into another:
|
|
441
447
|
|
|
@@ -1130,8 +1136,10 @@ at replace time) and **references** (resolved during traversal).
|
|
|
1130
1136
|
|
|
1131
1137
|
| Syntax | Type | Resolved against |
|
|
1132
1138
|
|---|---|---|
|
|
1133
|
-
| `$0`, `$1` | Positional substitution | `replace()` bindings |
|
|
1134
|
-
| `$(name)` | Named substitution | `replace()` bindings |
|
|
1139
|
+
| `$0`, `$1` | Positional substitution | `replace()` bindings (list/tuple) |
|
|
1140
|
+
| `$(name)` | Named substitution | `replace()` bindings (dict) |
|
|
1141
|
+
| `$(0)`, `$(name)` | Substitution with parens | `replace()` bindings via `__getitem__` |
|
|
1142
|
+
| `$(name\|int)` | Substitution with transform | `replace()` bindings, then transform |
|
|
1135
1143
|
| `$$(path)` | Reference | Root object during traversal |
|
|
1136
1144
|
| `$$(^path)` | Relative reference | Current node during traversal |
|
|
1137
1145
|
| `$$(^^path)` | Relative reference | Parent node during traversal |
|
|
@@ -1141,7 +1149,7 @@ at replace time) and **references** (resolved during traversal).
|
|
|
1141
1149
|
|
|
1142
1150
|
Substitution references turn a path into a **template**. There are two forms:
|
|
1143
1151
|
|
|
1144
|
-
- **Positional** (`$0`, `$1`, …) — resolved against a
|
|
1152
|
+
- **Positional** (`$0`, `$1`, …) — resolved against a list or tuple
|
|
1145
1153
|
- **Named** (`$(name)`, `$(key)`, …) — resolved against a dict
|
|
1146
1154
|
|
|
1147
1155
|
The `replace` function resolves them:
|
|
@@ -1151,6 +1159,13 @@ The `replace` function resolves them:
|
|
|
1151
1159
|
>>> dotted.replace('$(table).$(field)', {'table': 'users', 'field': 'email'})
|
|
1152
1160
|
'users.email'
|
|
1153
1161
|
|
|
1162
|
+
The parenthesized form `$(N)` adapts to the binding type — it uses `__getitem__`,
|
|
1163
|
+
so `$(0)` works as a positional index against a list or as a numeric key against
|
|
1164
|
+
a dict:
|
|
1165
|
+
|
|
1166
|
+
>>> dotted.replace('$(0)', {0: 'zero'})
|
|
1167
|
+
'zero'
|
|
1168
|
+
|
|
1154
1169
|
Use `is_template` to test whether a path contains substitution references:
|
|
1155
1170
|
|
|
1156
1171
|
>>> dotted.is_template('a.$0')
|
|
@@ -1160,6 +1175,25 @@ Use `is_template` to test whether a path contains substitution references:
|
|
|
1160
1175
|
>>> dotted.is_template('a.b')
|
|
1161
1176
|
False
|
|
1162
1177
|
|
|
1178
|
+
#### Substitution transforms
|
|
1179
|
+
|
|
1180
|
+
Substitutions support per-substitution transforms using the `|` separator inside
|
|
1181
|
+
the parenthesized form. The transform is applied to the resolved value before it
|
|
1182
|
+
is spliced into the path:
|
|
1183
|
+
|
|
1184
|
+
>>> dotted.replace('$(name|uppercase)', {'name': 'hello'})
|
|
1185
|
+
'HELLO'
|
|
1186
|
+
>>> dotted.replace('$(0|str)', [42])
|
|
1187
|
+
'42'
|
|
1188
|
+
|
|
1189
|
+
Multiple transforms chain left to right:
|
|
1190
|
+
|
|
1191
|
+
>>> dotted.replace('$(name|strip|lowercase)', {'name': ' HELLO '})
|
|
1192
|
+
'hello'
|
|
1193
|
+
|
|
1194
|
+
All [built-in transforms](#built-in-transforms) are available. The bare `$N` form
|
|
1195
|
+
does not support transforms — use `$(N|transform)` instead.
|
|
1196
|
+
|
|
1163
1197
|
See [Replace](#replace) and [Translate](#translate) for full API details.
|
|
1164
1198
|
|
|
1165
1199
|
<a id="references"></a>
|
|
@@ -65,7 +65,7 @@ class Op:
|
|
|
65
65
|
return self.__class__ == op.__class__ and self.args == op.args
|
|
66
66
|
def resolve(self, bindings, partial=False):
|
|
67
67
|
"""
|
|
68
|
-
Return a new op with all
|
|
68
|
+
Return a new op with all substitutions resolved.
|
|
69
69
|
Default: return self (no substitutions).
|
|
70
70
|
"""
|
|
71
71
|
return self
|
|
@@ -149,6 +149,13 @@ class TraversalOp(Op):
|
|
|
149
149
|
Base class for ops that participate in stack-based traversal.
|
|
150
150
|
Subclasses must implement push_children(stack, frame, paths).
|
|
151
151
|
"""
|
|
152
|
+
@property
|
|
153
|
+
def most_inner(self):
|
|
154
|
+
"""
|
|
155
|
+
Return self — no wrapping to unwrap.
|
|
156
|
+
"""
|
|
157
|
+
return self
|
|
158
|
+
|
|
152
159
|
def to_branches(self):
|
|
153
160
|
return [tuple([self])]
|
|
154
161
|
|
|
@@ -18,7 +18,7 @@ def _needs_parents(ops):
|
|
|
18
18
|
(parent or higher), requiring _parents tracking during traversal.
|
|
19
19
|
"""
|
|
20
20
|
for op in ops:
|
|
21
|
-
inner = op.
|
|
21
|
+
inner = op.most_inner
|
|
22
22
|
if (hasattr(inner, 'is_reference') and inner.is_reference()
|
|
23
23
|
and inner.op.depth >= 2):
|
|
24
24
|
return True
|
|
@@ -84,11 +84,23 @@ _escaped_dollar = pp.Regex(r'\\\$\${0,2}(\([^)]*\)|[0-9]*)').set_parse_action(
|
|
|
84
84
|
lambda t: matchers.Word(t[0][1:]))
|
|
85
85
|
_reference = pp.Regex(r'\$\$\([^)]+\)').set_parse_action(
|
|
86
86
|
lambda t: matchers.Reference(t[0][3:-1]))
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
def _paren_subst_action(t):
|
|
88
|
+
"""
|
|
89
|
+
Parse $(content) where content is name_or_index[|transform1|transform2...].
|
|
90
|
+
"""
|
|
91
|
+
content = t[0][2:-1] # strip $( and )
|
|
92
|
+
parts = content.split('|')
|
|
93
|
+
name = parts[0]
|
|
94
|
+
xforms = tuple(base.Transform(p) for p in parts[1:]) if len(parts) > 1 else ()
|
|
95
|
+
try:
|
|
96
|
+
return matchers.Subst(int(name), transforms=xforms)
|
|
97
|
+
except ValueError:
|
|
98
|
+
return matchers.Subst(name, transforms=xforms)
|
|
99
|
+
|
|
100
|
+
_paren_subst = pp.Regex(r'\$\([^)]+\)').set_parse_action(_paren_subst_action)
|
|
89
101
|
_raw_subst = pp.Regex(r'\$[0-9]+').set_parse_action(
|
|
90
|
-
lambda t: matchers.
|
|
91
|
-
subst = _escaped_dollar | _reference |
|
|
102
|
+
lambda t: matchers.Subst(int(t[0][1:])))
|
|
103
|
+
subst = _escaped_dollar | _reference | _paren_subst | _raw_subst
|
|
92
104
|
slice = pp.Optional(integer | plus) + ':' + pp.Optional(integer | plus) \
|
|
93
105
|
+ pp.Optional(':') + pp.Optional(integer | plus)
|
|
94
106
|
|
|
@@ -147,8 +147,15 @@ class Pattern(MatchOp):
|
|
|
147
147
|
|
|
148
148
|
class Subst(Pattern):
|
|
149
149
|
"""
|
|
150
|
-
|
|
150
|
+
Substitution op: $0, $(name), $(0|transform), $(name|int), etc.
|
|
151
|
+
Resolves against bindings via __getitem__: list for positional,
|
|
152
|
+
dict for named or numeric keys. Optional transforms applied
|
|
153
|
+
after lookup.
|
|
151
154
|
"""
|
|
155
|
+
def __init__(self, *args, transforms=(), **kwargs):
|
|
156
|
+
super().__init__(*args, **kwargs)
|
|
157
|
+
self.transforms = tuple(transforms)
|
|
158
|
+
|
|
152
159
|
@property
|
|
153
160
|
def value(self):
|
|
154
161
|
return self.args[0]
|
|
@@ -162,47 +169,42 @@ class Subst(Pattern):
|
|
|
162
169
|
def matchable(self, op, specials=False):
|
|
163
170
|
return False
|
|
164
171
|
|
|
165
|
-
|
|
166
|
-
class PositionalSubst(Subst):
|
|
167
|
-
"""
|
|
168
|
-
Substitution op for captured match groups: $0, $1, $2, etc.
|
|
169
|
-
"""
|
|
170
|
-
def resolve(self, bindings, partial=False):
|
|
172
|
+
def _apply_transforms(self, val):
|
|
171
173
|
"""
|
|
172
|
-
|
|
173
|
-
Returns ResolvedValue on success, self if partial and out of range,
|
|
174
|
-
or raises IndexError if not partial and out of range.
|
|
174
|
+
Apply this op's transforms to a resolved value.
|
|
175
175
|
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return self
|
|
181
|
-
raise IndexError(
|
|
182
|
-
f'${idx} out of range ({len(bindings)} bindings)')
|
|
183
|
-
def __repr__(self):
|
|
184
|
-
return f'${self.args[0]}'
|
|
176
|
+
if not self.transforms:
|
|
177
|
+
return val
|
|
178
|
+
from .results import apply_transforms
|
|
179
|
+
return apply_transforms(val, self.transforms)
|
|
185
180
|
|
|
181
|
+
def _transform_suffix(self):
|
|
182
|
+
"""
|
|
183
|
+
Render |transform1|transform2 suffix for quote/repr.
|
|
184
|
+
"""
|
|
185
|
+
if not self.transforms:
|
|
186
|
+
return ''
|
|
187
|
+
return ''.join(f'|{t.operator()}' for t in self.transforms)
|
|
186
188
|
|
|
187
|
-
class NamedSubst(Subst):
|
|
188
|
-
"""
|
|
189
|
-
Substitution op for named bindings: $(name), $(key), etc.
|
|
190
|
-
"""
|
|
191
189
|
def resolve(self, bindings, partial=False):
|
|
192
190
|
"""
|
|
193
|
-
Resolve this substitution against
|
|
194
|
-
Returns ResolvedValue on success, self if partial and missing,
|
|
195
|
-
or raises KeyError if not partial and missing.
|
|
191
|
+
Resolve this substitution against bindings.
|
|
196
192
|
"""
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return ResolvedValue(str(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
193
|
+
try:
|
|
194
|
+
val = self._apply_transforms(bindings[self.value])
|
|
195
|
+
return ResolvedValue(str(val))
|
|
196
|
+
except (KeyError, IndexError, TypeError):
|
|
197
|
+
if partial:
|
|
198
|
+
return self
|
|
199
|
+
raise
|
|
200
|
+
|
|
204
201
|
def __repr__(self):
|
|
205
|
-
|
|
202
|
+
suffix = self._transform_suffix()
|
|
203
|
+
v = self.args[0]
|
|
204
|
+
if isinstance(v, int) and not suffix:
|
|
205
|
+
return f'${v}'
|
|
206
|
+
return f'$({v}{suffix})'
|
|
207
|
+
|
|
206
208
|
|
|
207
209
|
|
|
208
210
|
class Reference(MatchOp):
|
|
@@ -21,6 +21,16 @@ class Wrap(base.TraversalOp):
|
|
|
21
21
|
|
|
22
22
|
inner = None # subclasses set in __init__
|
|
23
23
|
|
|
24
|
+
@property
|
|
25
|
+
def most_inner(self):
|
|
26
|
+
"""
|
|
27
|
+
Unwrap through nested Wraps to the innermost op.
|
|
28
|
+
"""
|
|
29
|
+
op = self.inner
|
|
30
|
+
while isinstance(op, Wrap):
|
|
31
|
+
op = op.inner
|
|
32
|
+
return op
|
|
33
|
+
|
|
24
34
|
def is_pattern(self):
|
|
25
35
|
return self.inner.is_pattern() if hasattr(self.inner, 'is_pattern') else False
|
|
26
36
|
|
|
@@ -30,6 +40,12 @@ class Wrap(base.TraversalOp):
|
|
|
30
40
|
"""
|
|
31
41
|
return self.inner.is_template() if hasattr(self.inner, 'is_template') else False
|
|
32
42
|
|
|
43
|
+
def is_reference(self):
|
|
44
|
+
"""
|
|
45
|
+
Delegate to inner op.
|
|
46
|
+
"""
|
|
47
|
+
return self.inner.is_reference() if hasattr(self.inner, 'is_reference') else False
|
|
48
|
+
|
|
33
49
|
def default(self):
|
|
34
50
|
return self.inner.default() if hasattr(self.inner, 'default') else {}
|
|
35
51
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotted_notation
|
|
3
|
-
Version: 0.41.
|
|
3
|
+
Version: 0.41.2
|
|
4
4
|
Summary: Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms
|
|
5
5
|
Home-page: https://github.com/freywaid/dotted
|
|
6
6
|
Author: Frey Waid
|
|
@@ -111,6 +111,7 @@ Or pick only what you need:
|
|
|
111
111
|
- [Slicing vs Patterns](#slicing-vs-patterns)
|
|
112
112
|
- [Substitutions and References](#substitutions-and-references)
|
|
113
113
|
- [Substitution](#substitution)
|
|
114
|
+
- [Substitution transforms](#substitution-transforms)
|
|
114
115
|
- [References](#references)
|
|
115
116
|
- [Relative References](#relative-references)
|
|
116
117
|
- [Escaping](#escaping)
|
|
@@ -462,7 +463,7 @@ excluding literals:
|
|
|
462
463
|
Substitute placeholders in a template path with bound values. Template paths
|
|
463
464
|
are validated at parse time — structural errors are caught immediately, not at runtime.
|
|
464
465
|
|
|
465
|
-
Positional (`$N`) placeholders resolve against a tuple:
|
|
466
|
+
Positional (`$N`) placeholders resolve against a list or tuple:
|
|
466
467
|
|
|
467
468
|
>>> import dotted
|
|
468
469
|
>>> dotted.replace('people.$1.$2', ('users', 'alice', 'age'))
|
|
@@ -473,6 +474,11 @@ Named (`$(name)`) placeholders resolve against a dict:
|
|
|
473
474
|
>>> dotted.replace('$(table).$(key)', {'table': 'users', 'key': 'alice'})
|
|
474
475
|
'users.alice'
|
|
475
476
|
|
|
477
|
+
Placeholders support transforms inside the parenthesized form:
|
|
478
|
+
|
|
479
|
+
>>> dotted.replace('$(0|uppercase)', ['hello'])
|
|
480
|
+
'HELLO'
|
|
481
|
+
|
|
476
482
|
Combine with `match` to remap paths — capture groups from one pattern and substitute
|
|
477
483
|
into another:
|
|
478
484
|
|
|
@@ -1167,8 +1173,10 @@ at replace time) and **references** (resolved during traversal).
|
|
|
1167
1173
|
|
|
1168
1174
|
| Syntax | Type | Resolved against |
|
|
1169
1175
|
|---|---|---|
|
|
1170
|
-
| `$0`, `$1` | Positional substitution | `replace()` bindings |
|
|
1171
|
-
| `$(name)` | Named substitution | `replace()` bindings |
|
|
1176
|
+
| `$0`, `$1` | Positional substitution | `replace()` bindings (list/tuple) |
|
|
1177
|
+
| `$(name)` | Named substitution | `replace()` bindings (dict) |
|
|
1178
|
+
| `$(0)`, `$(name)` | Substitution with parens | `replace()` bindings via `__getitem__` |
|
|
1179
|
+
| `$(name\|int)` | Substitution with transform | `replace()` bindings, then transform |
|
|
1172
1180
|
| `$$(path)` | Reference | Root object during traversal |
|
|
1173
1181
|
| `$$(^path)` | Relative reference | Current node during traversal |
|
|
1174
1182
|
| `$$(^^path)` | Relative reference | Parent node during traversal |
|
|
@@ -1178,7 +1186,7 @@ at replace time) and **references** (resolved during traversal).
|
|
|
1178
1186
|
|
|
1179
1187
|
Substitution references turn a path into a **template**. There are two forms:
|
|
1180
1188
|
|
|
1181
|
-
- **Positional** (`$0`, `$1`, …) — resolved against a
|
|
1189
|
+
- **Positional** (`$0`, `$1`, …) — resolved against a list or tuple
|
|
1182
1190
|
- **Named** (`$(name)`, `$(key)`, …) — resolved against a dict
|
|
1183
1191
|
|
|
1184
1192
|
The `replace` function resolves them:
|
|
@@ -1188,6 +1196,13 @@ The `replace` function resolves them:
|
|
|
1188
1196
|
>>> dotted.replace('$(table).$(field)', {'table': 'users', 'field': 'email'})
|
|
1189
1197
|
'users.email'
|
|
1190
1198
|
|
|
1199
|
+
The parenthesized form `$(N)` adapts to the binding type — it uses `__getitem__`,
|
|
1200
|
+
so `$(0)` works as a positional index against a list or as a numeric key against
|
|
1201
|
+
a dict:
|
|
1202
|
+
|
|
1203
|
+
>>> dotted.replace('$(0)', {0: 'zero'})
|
|
1204
|
+
'zero'
|
|
1205
|
+
|
|
1191
1206
|
Use `is_template` to test whether a path contains substitution references:
|
|
1192
1207
|
|
|
1193
1208
|
>>> dotted.is_template('a.$0')
|
|
@@ -1197,6 +1212,25 @@ Use `is_template` to test whether a path contains substitution references:
|
|
|
1197
1212
|
>>> dotted.is_template('a.b')
|
|
1198
1213
|
False
|
|
1199
1214
|
|
|
1215
|
+
#### Substitution transforms
|
|
1216
|
+
|
|
1217
|
+
Substitutions support per-substitution transforms using the `|` separator inside
|
|
1218
|
+
the parenthesized form. The transform is applied to the resolved value before it
|
|
1219
|
+
is spliced into the path:
|
|
1220
|
+
|
|
1221
|
+
>>> dotted.replace('$(name|uppercase)', {'name': 'hello'})
|
|
1222
|
+
'HELLO'
|
|
1223
|
+
>>> dotted.replace('$(0|str)', [42])
|
|
1224
|
+
'42'
|
|
1225
|
+
|
|
1226
|
+
Multiple transforms chain left to right:
|
|
1227
|
+
|
|
1228
|
+
>>> dotted.replace('$(name|strip|lowercase)', {'name': ' HELLO '})
|
|
1229
|
+
'hello'
|
|
1230
|
+
|
|
1231
|
+
All [built-in transforms](#built-in-transforms) are available. The bare `$N` form
|
|
1232
|
+
does not support transforms — use `$(N|transform)` instead.
|
|
1233
|
+
|
|
1200
1234
|
See [Replace](#replace) and [Translate](#translate) for full API details.
|
|
1201
1235
|
|
|
1202
1236
|
<a id="references"></a>
|
|
@@ -5,7 +5,7 @@ with open("README.md", "rt") as f:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="dotted_notation",
|
|
8
|
-
version="0.41.
|
|
8
|
+
version="0.41.2",
|
|
9
9
|
author="Frey Waid",
|
|
10
10
|
author_email="logophage1@gmail.com",
|
|
11
11
|
description="Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms",
|
|
@@ -4,27 +4,27 @@ Tests for $(name) named substitutions.
|
|
|
4
4
|
import pytest
|
|
5
5
|
import dotted
|
|
6
6
|
from dotted.api import parse, assemble, replace, quote
|
|
7
|
-
from dotted.matchers import
|
|
7
|
+
from dotted.matchers import Subst, Word
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
# ---- parsing ----
|
|
11
11
|
|
|
12
12
|
def test_parse_named_subst():
|
|
13
13
|
ops = parse('$(name)')
|
|
14
|
-
assert isinstance(ops[0].op,
|
|
14
|
+
assert isinstance(ops[0].op, Subst)
|
|
15
15
|
assert ops[0].op.value == 'name'
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def test_parse_named_subst_underscore():
|
|
19
19
|
ops = parse('$(my_key)')
|
|
20
|
-
assert isinstance(ops[0].op,
|
|
20
|
+
assert isinstance(ops[0].op, Subst)
|
|
21
21
|
assert ops[0].op.value == 'my_key'
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def test_parse_named_subst_nested():
|
|
25
25
|
ops = parse('a.$(key).b')
|
|
26
26
|
assert len(ops) == 3
|
|
27
|
-
assert isinstance(ops[1].op,
|
|
27
|
+
assert isinstance(ops[1].op, Subst)
|
|
28
28
|
assert ops[1].op.value == 'key'
|
|
29
29
|
|
|
30
30
|
|
|
@@ -5,7 +5,7 @@ import pytest
|
|
|
5
5
|
import dotted
|
|
6
6
|
from dotted.api import parse
|
|
7
7
|
from dotted.access import Key, Attr, Slot
|
|
8
|
-
from dotted.matchers import
|
|
8
|
+
from dotted.matchers import Subst
|
|
9
9
|
from dotted.results import assemble
|
|
10
10
|
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ def test_parse_subst_key_position():
|
|
|
17
17
|
ops = parse('a.$0')
|
|
18
18
|
assert len(ops) == 2
|
|
19
19
|
assert isinstance(ops[1], Key)
|
|
20
|
-
assert isinstance(ops[1].op,
|
|
20
|
+
assert isinstance(ops[1].op, Subst)
|
|
21
21
|
assert ops[1].op.value == 0
|
|
22
22
|
|
|
23
23
|
|
|
@@ -25,7 +25,7 @@ def test_parse_subst_slot_position():
|
|
|
25
25
|
ops = parse('a[$0]')
|
|
26
26
|
assert len(ops) == 2
|
|
27
27
|
assert isinstance(ops[1], Slot)
|
|
28
|
-
assert isinstance(ops[1].op,
|
|
28
|
+
assert isinstance(ops[1].op, Subst)
|
|
29
29
|
assert ops[1].op.value == 0
|
|
30
30
|
|
|
31
31
|
|
|
@@ -33,15 +33,15 @@ def test_parse_subst_attr_position():
|
|
|
33
33
|
ops = parse('$0@$1')
|
|
34
34
|
assert len(ops) == 2
|
|
35
35
|
assert isinstance(ops[0], Key)
|
|
36
|
-
assert isinstance(ops[0].op,
|
|
36
|
+
assert isinstance(ops[0].op, Subst)
|
|
37
37
|
assert isinstance(ops[1], Attr)
|
|
38
|
-
assert isinstance(ops[1].op,
|
|
38
|
+
assert isinstance(ops[1].op, Subst)
|
|
39
39
|
assert ops[1].op.value == 1
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def test_parse_subst_multi_digit():
|
|
43
43
|
ops = parse('$10')
|
|
44
|
-
assert isinstance(ops[0].op,
|
|
44
|
+
assert isinstance(ops[0].op, Subst)
|
|
45
45
|
assert ops[0].op.value == 10
|
|
46
46
|
|
|
47
47
|
|
|
@@ -99,22 +99,22 @@ def test_parse_subst_container_value():
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
# ---------------------------------------------------------------------------
|
|
102
|
-
#
|
|
102
|
+
# Subst.resolve
|
|
103
103
|
# ---------------------------------------------------------------------------
|
|
104
104
|
|
|
105
105
|
def test_resolve_in_range():
|
|
106
|
-
s =
|
|
106
|
+
s = Subst(2)
|
|
107
107
|
r = s.resolve(('a', 'b', 'c'))
|
|
108
108
|
assert r.value == 'c'
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
def test_resolve_out_of_range():
|
|
112
|
-
s =
|
|
112
|
+
s = Subst(5)
|
|
113
113
|
assert s.resolve(('a', 'b'), partial=True) is s
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
def test_resolve_out_of_range_strict():
|
|
117
|
-
s =
|
|
117
|
+
s = Subst(5)
|
|
118
118
|
with pytest.raises(IndexError):
|
|
119
119
|
s.resolve(('a', 'b'))
|
|
120
120
|
|
|
@@ -3,7 +3,7 @@ Tests for $N substitution escaping and is_template API.
|
|
|
3
3
|
"""
|
|
4
4
|
import dotted
|
|
5
5
|
from dotted.api import parse, assemble, quote
|
|
6
|
-
from dotted.matchers import Word,
|
|
6
|
+
from dotted.matchers import Word, Subst
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
# ---- escaping: \$ produces literal dollar-sign keys ----
|
|
@@ -28,7 +28,7 @@ def test_parse_escaped_dollar_multi_digit():
|
|
|
28
28
|
|
|
29
29
|
def test_parse_raw_subst_still_works():
|
|
30
30
|
ops = parse('$0')
|
|
31
|
-
assert isinstance(ops[0].op,
|
|
31
|
+
assert isinstance(ops[0].op, Subst)
|
|
32
32
|
assert ops[0].op.value == 0
|
|
33
33
|
|
|
34
34
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for substitution transforms: $(name|transform), $(0|transform).
|
|
3
|
+
"""
|
|
4
|
+
from dotted.api import replace, is_template, parse
|
|
5
|
+
from dotted.matchers import Subst
|
|
6
|
+
from dotted.results import assemble
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ---- parsing ----
|
|
10
|
+
|
|
11
|
+
def test_parse_named_with_transform():
|
|
12
|
+
"""
|
|
13
|
+
$(name|int) parses as Subst with int transform.
|
|
14
|
+
"""
|
|
15
|
+
ops = parse('$(name|int)')
|
|
16
|
+
assert isinstance(ops[0].op, Subst)
|
|
17
|
+
assert ops[0].op.value == 'name'
|
|
18
|
+
assert len(ops[0].op.transforms) == 1
|
|
19
|
+
assert ops[0].op.transforms[0].name == 'int'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_parse_named_no_transform():
|
|
23
|
+
"""
|
|
24
|
+
$(name) still parses as Subst with no transforms.
|
|
25
|
+
"""
|
|
26
|
+
ops = parse('$(name)')
|
|
27
|
+
assert isinstance(ops[0].op, Subst)
|
|
28
|
+
assert ops[0].op.value == 'name'
|
|
29
|
+
assert ops[0].op.transforms == ()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_parse_numeric_paren_with_transform():
|
|
33
|
+
"""
|
|
34
|
+
$(0|str) parses as Subst with str transform.
|
|
35
|
+
"""
|
|
36
|
+
ops = parse('$(0|str)')
|
|
37
|
+
assert isinstance(ops[0].op, Subst)
|
|
38
|
+
assert ops[0].op.value == 0
|
|
39
|
+
assert len(ops[0].op.transforms) == 1
|
|
40
|
+
assert ops[0].op.transforms[0].name == 'str'
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_parse_numeric_paren_no_transform():
|
|
44
|
+
"""
|
|
45
|
+
$(0) parses as Subst with no transforms.
|
|
46
|
+
"""
|
|
47
|
+
ops = parse('$(0)')
|
|
48
|
+
assert isinstance(ops[0].op, Subst)
|
|
49
|
+
assert ops[0].op.value == 0
|
|
50
|
+
assert ops[0].op.transforms == ()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_parse_raw_positional():
|
|
54
|
+
"""
|
|
55
|
+
$0 still parses as Subst with no transforms.
|
|
56
|
+
"""
|
|
57
|
+
ops = parse('$0')
|
|
58
|
+
assert isinstance(ops[0].op, Subst)
|
|
59
|
+
assert ops[0].op.value == 0
|
|
60
|
+
assert ops[0].op.transforms == ()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_parse_multiple_transforms():
|
|
64
|
+
"""
|
|
65
|
+
$(name|strip|lowercase) parses with two transforms.
|
|
66
|
+
"""
|
|
67
|
+
ops = parse('$(name|strip|lowercase)')
|
|
68
|
+
assert isinstance(ops[0].op, Subst)
|
|
69
|
+
assert len(ops[0].op.transforms) == 2
|
|
70
|
+
assert ops[0].op.transforms[0].name == 'strip'
|
|
71
|
+
assert ops[0].op.transforms[1].name == 'lowercase'
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---- resolve with transforms ----
|
|
75
|
+
|
|
76
|
+
def test_resolve_named_with_int_transform():
|
|
77
|
+
"""
|
|
78
|
+
$(name|int) resolves and applies int transform.
|
|
79
|
+
"""
|
|
80
|
+
result = replace('$(name|int)', {'name': '42'})
|
|
81
|
+
assert result == '42'
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_resolve_named_with_str_transform():
|
|
85
|
+
"""
|
|
86
|
+
$(name|str) resolves and applies str transform.
|
|
87
|
+
"""
|
|
88
|
+
result = replace('$(name|str)', {'name': 123})
|
|
89
|
+
assert result == '123'
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_resolve_numeric_with_transform():
|
|
93
|
+
"""
|
|
94
|
+
$(0|uppercase) resolves positional and applies transform.
|
|
95
|
+
"""
|
|
96
|
+
result = replace('$(0|uppercase)', ['hello'])
|
|
97
|
+
assert result == 'HELLO'
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_resolve_raw_positional_no_transform():
|
|
101
|
+
"""
|
|
102
|
+
$0 resolves against list bindings as before.
|
|
103
|
+
"""
|
|
104
|
+
result = replace('prefix.$0', ['world'])
|
|
105
|
+
assert result == 'prefix.world'
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ---- Subst against dict bindings ----
|
|
109
|
+
|
|
110
|
+
def test_numeric_subst_dict_bindings():
|
|
111
|
+
"""
|
|
112
|
+
$(0) against dict bindings looks up numeric key 0.
|
|
113
|
+
"""
|
|
114
|
+
result = replace('$(0)', {0: 'zero'})
|
|
115
|
+
assert result == 'zero'
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---- round-trip ----
|
|
119
|
+
|
|
120
|
+
def test_repr_named_with_transform():
|
|
121
|
+
"""
|
|
122
|
+
Subst with transforms repr includes the suffix.
|
|
123
|
+
"""
|
|
124
|
+
ops = parse('$(name|int)')
|
|
125
|
+
assert repr(ops[0].op) == '$(name|int)'
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_repr_numeric_with_transform():
|
|
129
|
+
"""
|
|
130
|
+
Subst with transforms uses paren form.
|
|
131
|
+
"""
|
|
132
|
+
ops = parse('$(0|str)')
|
|
133
|
+
assert repr(ops[0].op) == '$(0|str)'
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_repr_numeric_no_transform():
|
|
137
|
+
"""
|
|
138
|
+
Subst without transforms uses bare $N form.
|
|
139
|
+
"""
|
|
140
|
+
ops = parse('$0')
|
|
141
|
+
assert repr(ops[0].op) == '$0'
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_assemble_named_with_transform():
|
|
145
|
+
"""
|
|
146
|
+
Assembling a path with $(name|int) round-trips.
|
|
147
|
+
"""
|
|
148
|
+
ops = parse('a.$(name|int).b')
|
|
149
|
+
assert assemble(ops) == 'a.$(name|int).b'
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_assemble_numeric_with_transform():
|
|
153
|
+
"""
|
|
154
|
+
Assembling a path with $(0|str) round-trips.
|
|
155
|
+
"""
|
|
156
|
+
ops = parse('a.$(0|str).b')
|
|
157
|
+
assert assemble(ops) == 'a.$(0|str).b'
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ---- is_template ----
|
|
161
|
+
|
|
162
|
+
def test_is_template_with_transform():
|
|
163
|
+
"""
|
|
164
|
+
$(name|int) is still a template.
|
|
165
|
+
"""
|
|
166
|
+
assert is_template('$(name|int)')
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_is_template_numeric_with_transform():
|
|
170
|
+
"""
|
|
171
|
+
$(0|str) is still a template.
|
|
172
|
+
"""
|
|
173
|
+
assert is_template('$(0|str)')
|
|
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.41.0 → dotted_notation-0.41.2}/dotted_notation.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|