dotted-notation 0.43.2__tar.gz → 0.43.4__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.43.2/dotted_notation.egg-info → dotted_notation-0.43.4}/PKG-INFO +9 -1
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/README.md +8 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/access.py +8 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/filters.py +44 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/grammar.py +64 -18
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/matchers.py +5 -1
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/wrappers.py +25 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4/dotted_notation.egg-info}/PKG-INFO +9 -1
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/setup.py +1 -1
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_named_subst.py +74 -0
- dotted_notation-0.43.4/tests/test_numeric.py +199 -0
- dotted_notation-0.43.2/tests/test_numeric.py +0 -103
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/LICENSE +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/__init__.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/__main__.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/api.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/base.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/cli/__init__.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/cli/_compat.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/cli/formats.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/cli/main.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/containers.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/engine.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/groups.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/predicates.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/recursive.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/results.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/sqlize.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/transforms.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/utils.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted/utypes.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted_notation.egg-info/SOURCES.txt +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted_notation.egg-info/dependency_links.txt +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted_notation.egg-info/entry_points.txt +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted_notation.egg-info/requires.txt +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/dotted_notation.egg-info/top_level.txt +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/setup.cfg +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/__init__.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_api.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_appender.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_assemble.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_attrs.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_bindings.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_cli.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_concat.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_container_filter.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_cut.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_empty.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_filter_keyvalue.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_get.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_guard_transforms.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_invert.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_json_sentinels.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_keys_values.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_match.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_matchable.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_negation.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_nop.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_opgroup.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_pluck.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_predicates.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_quote_idempotent.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_recursive.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_reference.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_replace.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_slice.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_softcut.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_sqlize.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_strict.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_string_glob.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_subst_escape.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_subst_transforms.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_threading.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_transforms.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_translate.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_type_restriction.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_unpack.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_update.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/tests/test_update_if.py +0 -0
- {dotted_notation-0.43.2 → dotted_notation-0.43.4}/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.43.
|
|
3
|
+
Version: 0.43.4
|
|
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
|
|
@@ -244,6 +244,14 @@ Several Python libraries handle nested data access. Here's how dotted compares:
|
|
|
244
244
|
<a id="breaking-changes"></a>
|
|
245
245
|
## Breaking Changes
|
|
246
246
|
|
|
247
|
+
### v0.43.4
|
|
248
|
+
- **`is_pattern` no longer returns `True` for substitutions**. `Subst` previously
|
|
249
|
+
inherited from `Pattern` in the class hierarchy, so any path containing a
|
|
250
|
+
substitution in an access position (e.g. `a.$(x)`, `a.$0`) reported as a
|
|
251
|
+
pattern. Substitutions resolve to a single key, not a set — that was a
|
|
252
|
+
misclassification. Use `is_template(path)` to check "contains a
|
|
253
|
+
substitution."
|
|
254
|
+
|
|
247
255
|
### v0.40.0
|
|
248
256
|
- **`unpack()` now returns a `dict`**: Previously returned a tuple of
|
|
249
257
|
`(path, value)` pairs. Now returns `{path: value, ...}` directly.
|
|
@@ -207,6 +207,14 @@ Several Python libraries handle nested data access. Here's how dotted compares:
|
|
|
207
207
|
<a id="breaking-changes"></a>
|
|
208
208
|
## Breaking Changes
|
|
209
209
|
|
|
210
|
+
### v0.43.4
|
|
211
|
+
- **`is_pattern` no longer returns `True` for substitutions**. `Subst` previously
|
|
212
|
+
inherited from `Pattern` in the class hierarchy, so any path containing a
|
|
213
|
+
substitution in an access position (e.g. `a.$(x)`, `a.$0`) reported as a
|
|
214
|
+
pattern. Substitutions resolve to a single key, not a set — that was a
|
|
215
|
+
misclassification. Use `is_template(path)` to check "contains a
|
|
216
|
+
substitution."
|
|
217
|
+
|
|
210
218
|
### v0.40.0
|
|
211
219
|
- **`unpack()` now returns a `dict`**: Previously returned a tuple of
|
|
212
220
|
`(path, value)` pairs. Now returns `{path: value, ...}` directly.
|
|
@@ -862,6 +862,14 @@ class SliceFilter(BaseOp):
|
|
|
862
862
|
def is_pattern(self):
|
|
863
863
|
return False
|
|
864
864
|
|
|
865
|
+
def is_template(self):
|
|
866
|
+
"""
|
|
867
|
+
True if any attached filter contains a substitution reference.
|
|
868
|
+
"""
|
|
869
|
+
return any(
|
|
870
|
+
hasattr(f, 'is_template') and f.is_template()
|
|
871
|
+
for f in self.filters)
|
|
872
|
+
|
|
865
873
|
def is_empty(self, node):
|
|
866
874
|
return not node
|
|
867
875
|
|
|
@@ -6,6 +6,13 @@ from . import predicates
|
|
|
6
6
|
from .access import Slot, Slice
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
def _any_template(items):
|
|
10
|
+
"""
|
|
11
|
+
True if any item in an iterable has is_template() returning True.
|
|
12
|
+
"""
|
|
13
|
+
return any(hasattr(x, 'is_template') and x.is_template() for x in items)
|
|
14
|
+
|
|
15
|
+
|
|
9
16
|
class FilterOp(base.MatchOp):
|
|
10
17
|
def is_pattern(self):
|
|
11
18
|
return False
|
|
@@ -50,6 +57,12 @@ class FilterKey(base.MatchOp):
|
|
|
50
57
|
def is_dotted(self):
|
|
51
58
|
return len(self.parts) > 1
|
|
52
59
|
|
|
60
|
+
def is_template(self):
|
|
61
|
+
"""
|
|
62
|
+
True if any part of the filter key contains a substitution reference.
|
|
63
|
+
"""
|
|
64
|
+
return _any_template(self.parts)
|
|
65
|
+
|
|
53
66
|
def get_values(self, node):
|
|
54
67
|
"""
|
|
55
68
|
Get all values from node matching this key, traversing dotted path if needed.
|
|
@@ -188,6 +201,16 @@ class FilterKeyValue(FilterOp):
|
|
|
188
201
|
t_str = ''.join('|' + t.operator() for t in self.transforms) if self.transforms else ''
|
|
189
202
|
return f'{self.key}{t_str}{self._eq_str}{self.val}'
|
|
190
203
|
|
|
204
|
+
def is_template(self):
|
|
205
|
+
"""
|
|
206
|
+
True if the key, value, or any transform contains a substitution.
|
|
207
|
+
"""
|
|
208
|
+
if hasattr(self.key, 'is_template') and self.key.is_template():
|
|
209
|
+
return True
|
|
210
|
+
if hasattr(self.val, 'is_template') and self.val.is_template():
|
|
211
|
+
return True
|
|
212
|
+
return _any_template(self.transforms)
|
|
213
|
+
|
|
191
214
|
def _eq_match(self, node):
|
|
192
215
|
"""
|
|
193
216
|
True if any value from self.key in node matches self.val (after transforms).
|
|
@@ -351,6 +374,11 @@ class FilterGroup(FilterOp):
|
|
|
351
374
|
return None
|
|
352
375
|
return self.inner.match(op.inner)
|
|
353
376
|
|
|
377
|
+
def is_template(self):
|
|
378
|
+
return (self.inner is not None
|
|
379
|
+
and hasattr(self.inner, 'is_template')
|
|
380
|
+
and self.inner.is_template())
|
|
381
|
+
|
|
354
382
|
def resolve(self, bindings, partial=False):
|
|
355
383
|
"""
|
|
356
384
|
Resolve $N in inner filter.
|
|
@@ -403,6 +431,9 @@ class FilterAnd(FilterOp):
|
|
|
403
431
|
results.append(m)
|
|
404
432
|
return FilterAnd(*results)
|
|
405
433
|
|
|
434
|
+
def is_template(self):
|
|
435
|
+
return _any_template(self.filters)
|
|
436
|
+
|
|
406
437
|
def resolve(self, bindings, partial=False):
|
|
407
438
|
"""
|
|
408
439
|
Resolve $N in each filter.
|
|
@@ -443,6 +474,9 @@ class FilterOr(FilterOp):
|
|
|
443
474
|
seen.add(item_id)
|
|
444
475
|
yield item
|
|
445
476
|
|
|
477
|
+
def is_template(self):
|
|
478
|
+
return _any_template(self.filters)
|
|
479
|
+
|
|
446
480
|
def resolve(self, bindings, partial=False):
|
|
447
481
|
"""
|
|
448
482
|
Resolve $N in each filter.
|
|
@@ -488,6 +522,11 @@ class FilterKeyValueFirst(FilterOp):
|
|
|
488
522
|
return self.inner.match(op.inner)
|
|
489
523
|
return None
|
|
490
524
|
|
|
525
|
+
def is_template(self):
|
|
526
|
+
return (self.inner is not None
|
|
527
|
+
and hasattr(self.inner, 'is_template')
|
|
528
|
+
and self.inner.is_template())
|
|
529
|
+
|
|
491
530
|
def resolve(self, bindings, partial=False):
|
|
492
531
|
"""
|
|
493
532
|
Resolve $N in inner filter.
|
|
@@ -541,6 +580,11 @@ class FilterNot(FilterOp):
|
|
|
541
580
|
return None
|
|
542
581
|
return self.inner.match(op.inner)
|
|
543
582
|
|
|
583
|
+
def is_template(self):
|
|
584
|
+
return (self.inner is not None
|
|
585
|
+
and hasattr(self.inner, 'is_template')
|
|
586
|
+
and self.inner.is_template())
|
|
587
|
+
|
|
544
588
|
def resolve(self, bindings, partial=False):
|
|
545
589
|
"""
|
|
546
590
|
Resolve $N in inner filter.
|
|
@@ -147,8 +147,9 @@ _ccomma = pp.Optional(pp.White()).suppress() + comma + pp.Optional(pp.White()).s
|
|
|
147
147
|
# Forward for recursive container values
|
|
148
148
|
container_value = pp.Forward()
|
|
149
149
|
|
|
150
|
-
# Scalar atoms usable inside containers (same as value atoms minus containers)
|
|
151
|
-
|
|
150
|
+
# Scalar atoms usable inside containers (same as value atoms minus containers).
|
|
151
|
+
# Uses numeric_slot (ppc.number) so floats parse in value position.
|
|
152
|
+
_val_atoms = bytes_literal | string | wildcard | var | regex | numeric_quoted | none | true | false | numeric_extended | numeric_slot
|
|
152
153
|
|
|
153
154
|
# Glob inside containers: ... with optional /regex/ then optional count
|
|
154
155
|
# Regex pattern for glob (unsuppressed slashes handled inline)
|
|
@@ -385,7 +386,7 @@ concrete_value <<= (
|
|
|
385
386
|
value = pp.Forward()
|
|
386
387
|
_value_group_inner = value + OM(_ccomma + value)
|
|
387
388
|
value_group = (S('(') + _value_group_inner + S(')')).set_parse_action(lambda t: [ct.ValueGroup(*t)])
|
|
388
|
-
value <<= value_group | container_value | string_glob | bytes_literal | string | wildcard | var | regex | numeric_quoted | none | true | false | numeric_extended |
|
|
389
|
+
value <<= value_group | container_value | string_glob | bytes_literal | string | wildcard | var | regex | numeric_quoted | none | true | false | numeric_extended | numeric_slot
|
|
389
390
|
# ---------------------------------------------------------------------------
|
|
390
391
|
# Concat: part+part for key construction using Python's native + operator.
|
|
391
392
|
# Each part can carry per-part transforms: part|xform+part|xform
|
|
@@ -859,9 +860,11 @@ rec_dstar = _dstar_body().set_parse_action(
|
|
|
859
860
|
rec_pat = _pat_body().set_parse_action(
|
|
860
861
|
lambda t: _make_recursive(t))
|
|
861
862
|
|
|
862
|
-
|
|
863
|
-
_rec_dstar_guarded_all | _rec_pat_guarded_all |
|
|
863
|
+
recursive_op_nonguarded = (
|
|
864
864
|
rec_dstar_first | rec_dstar | rec_pat_first | rec_pat)
|
|
865
|
+
recursive_op_guarded = (
|
|
866
|
+
_rec_dstar_guarded_all | _rec_pat_guarded_all)
|
|
867
|
+
recursive_op = recursive_op_guarded | recursive_op_nonguarded
|
|
865
868
|
|
|
866
869
|
empty = pp.Empty().set_parse_action(access.Empty)
|
|
867
870
|
|
|
@@ -877,15 +880,23 @@ _dot_keycmd = (dot + keycmd).set_parse_action(lambda t: t[0])
|
|
|
877
880
|
# op_seq_item uses _nop_wrap (defined later); Forward for circular ref
|
|
878
881
|
op_seq_item = pp.Forward() # first item: allows bare (a,b)
|
|
879
882
|
_op_seq_cont = pp.Forward() # continuation: requires explicit ops
|
|
880
|
-
|
|
883
|
+
_op_seq_guarded = pp.Forward() # guarded terminator (resolved below)
|
|
884
|
+
# op_seq: guarded op is always terminal — either the sole op (alt 1), or
|
|
885
|
+
# optionally trailing a non-guarded path (alt 2).
|
|
886
|
+
op_seq = pp.Group(
|
|
887
|
+
_op_seq_guarded
|
|
888
|
+
| (op_seq_item + ZM(_op_seq_cont) + Opt(_op_seq_guarded))
|
|
889
|
+
)
|
|
881
890
|
|
|
882
891
|
# Continuation items (absorbed from former multi grammar):
|
|
883
892
|
# .~ and ~. with grouped expressions — prefix shorthand requires bare keys inside
|
|
884
893
|
_dot_nop_grouped = ((dot + tilde) | (tilde + dot)) + (_inner_grouped_bare_first | _inner_grouped_bare)
|
|
885
894
|
_dot_nop_grouped = _dot_nop_grouped.set_parse_action(lambda t: wrappers.NopWrap(t[-1]))
|
|
886
895
|
_dot_plain_grouped = (dot + (_inner_grouped_bare_first | _inner_grouped_bare)).set_parse_action(lambda t: t[0])
|
|
887
|
-
# .recursive
|
|
888
|
-
|
|
896
|
+
# .recursive (split into guarded / non-guarded)
|
|
897
|
+
_dot_recursive_nonguarded = (dot + recursive_op_nonguarded).set_parse_action(lambda t: t[0])
|
|
898
|
+
_dot_recursive_guarded = (dot + recursive_op_guarded).set_parse_action(lambda t: t[0])
|
|
899
|
+
_dot_recursive = _dot_recursive_guarded | _dot_recursive_nonguarded
|
|
889
900
|
# @(group) — attribute group access: @(a,b) == (@a,@b), prefix requires bare keys
|
|
890
901
|
_at_plain_grouped = (at + (_inner_grouped_bare_first | _inner_grouped_bare)).set_parse_action(
|
|
891
902
|
lambda t: groups.as_attrs_opgroup(t[0])
|
|
@@ -916,28 +927,63 @@ _explicit_nop_inner = (
|
|
|
916
927
|
)
|
|
917
928
|
_explicit_nop_wrap = (tilde + _explicit_nop_inner).set_parse_action(lambda t: wrappers.NopWrap(t[1]))
|
|
918
929
|
|
|
919
|
-
#
|
|
920
|
-
#
|
|
921
|
-
#
|
|
930
|
+
# Guarded variants — terminal within op_seq. After a `=value`, no more ops
|
|
931
|
+
# may follow (guards have no right-side delimiter, so continuations are
|
|
932
|
+
# ambiguous/meaningless — `a=1.b` is not a legal path).
|
|
933
|
+
_op_seq_guarded << (
|
|
934
|
+
_keycmd_guarded_all |
|
|
935
|
+
_dot_keycmd_guarded_nop | _dot_keycmd_guarded_plain |
|
|
936
|
+
_slotcmd_guarded_all |
|
|
937
|
+
recursive_op_guarded |
|
|
938
|
+
_dot_recursive_guarded
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
# Lookahead: a non-guarded continuation must NOT be followed by a guarded
|
|
942
|
+
# suffix (`|transform* + pred_op`). If it is, the whole thing belongs to
|
|
943
|
+
# the trailing `_op_seq_guarded` terminator, not to this continuation.
|
|
944
|
+
# A bare `|transform` (without pred_op) is fine — that's a template-level
|
|
945
|
+
# transform applied after the path resolves.
|
|
946
|
+
_guard_la = pp.NotAny(ZM(pipe + transform) + _pred_op_re)
|
|
947
|
+
|
|
948
|
+
# Continuation items for the ZM middle of op_seq — guarded variants excluded.
|
|
949
|
+
# Items that could be the LHS of a guard get a `_guard_la` lookahead so
|
|
950
|
+
# they don't greedily eat what belongs to the trailing guarded terminator.
|
|
922
951
|
_op_seq_cont_items = (
|
|
923
952
|
_nop_wrap |
|
|
924
953
|
_inner_grouped_explicit_first | _inner_grouped_explicit |
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
_dot_keycmd_nop | _dot_keycmd |
|
|
954
|
+
(recursive_op_nonguarded + _guard_la) |
|
|
955
|
+
(keycmd + _guard_la) |
|
|
956
|
+
_dot_recursive_nonguarded |
|
|
957
|
+
((_dot_keycmd_nop | _dot_keycmd) + _guard_la) |
|
|
930
958
|
_dot_nop_grouped | _dot_plain_grouped |
|
|
931
959
|
_at_nop_grouped | _at_plain_grouped |
|
|
932
960
|
attrcmd |
|
|
933
961
|
slotgroup_first | slotgroup |
|
|
934
|
-
|
|
962
|
+
(slotcmd + _guard_la) |
|
|
935
963
|
slotspecial | slicefilter | slicecmd
|
|
936
964
|
)
|
|
937
965
|
_op_seq_cont << _op_seq_cont_items
|
|
966
|
+
|
|
967
|
+
# First-item items — same set but WITHOUT the lookahead: the first position
|
|
968
|
+
# doesn't need it because alt 1 (_op_seq_guarded) handles sole-guard cases
|
|
969
|
+
# like `first=7` before we fall into this branch.
|
|
970
|
+
_op_seq_first_items = (
|
|
971
|
+
_nop_wrap |
|
|
972
|
+
_inner_grouped_explicit_first | _inner_grouped_explicit |
|
|
973
|
+
recursive_op_nonguarded |
|
|
974
|
+
keycmd |
|
|
975
|
+
_dot_recursive_nonguarded |
|
|
976
|
+
_dot_keycmd_nop | _dot_keycmd |
|
|
977
|
+
_dot_nop_grouped | _dot_plain_grouped |
|
|
978
|
+
_at_nop_grouped | _at_plain_grouped |
|
|
979
|
+
attrcmd |
|
|
980
|
+
slotgroup_first | slotgroup |
|
|
981
|
+
slotcmd |
|
|
982
|
+
slotspecial | slicefilter | slicecmd
|
|
983
|
+
)
|
|
938
984
|
# First item also allows bare grouped expressions — (a,b) with bare keys.
|
|
939
985
|
# This is valid at the start of a path (top-level inference).
|
|
940
|
-
op_seq_item << (inner_grouped_first | inner_grouped |
|
|
986
|
+
op_seq_item << (inner_grouped_first | inner_grouped | _op_seq_first_items)
|
|
941
987
|
|
|
942
988
|
# Precedence tower (atoms are op_seqs)
|
|
943
989
|
inner_atom = op_seq
|
|
@@ -150,12 +150,16 @@ class Pattern(MatchOp):
|
|
|
150
150
|
return True
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
class Subst(
|
|
153
|
+
class Subst(MatchOp):
|
|
154
154
|
"""
|
|
155
155
|
Substitution op: $0, $(name), $(0|transform), $(name|int), etc.
|
|
156
156
|
Resolves against bindings via __getitem__: list for positional,
|
|
157
157
|
dict for named or numeric keys. Optional transforms applied
|
|
158
158
|
after lookup.
|
|
159
|
+
|
|
160
|
+
Not a Pattern subclass — substitutions resolve to a single key, not
|
|
161
|
+
a set of keys. is_template() is the right check for "contains a
|
|
162
|
+
substitution".
|
|
159
163
|
"""
|
|
160
164
|
def __init__(self, *args, transforms=(), **kwargs):
|
|
161
165
|
super().__init__(*args, **kwargs)
|
|
@@ -202,6 +202,20 @@ class ValueGuard(Wrap):
|
|
|
202
202
|
def is_pattern(self):
|
|
203
203
|
return self.inner.is_pattern()
|
|
204
204
|
|
|
205
|
+
def is_template(self):
|
|
206
|
+
"""
|
|
207
|
+
True if the inner op, the guard value, or any transform contains
|
|
208
|
+
a substitution reference.
|
|
209
|
+
"""
|
|
210
|
+
if self.inner.is_template():
|
|
211
|
+
return True
|
|
212
|
+
if hasattr(self.guard, 'is_template') and self.guard.is_template():
|
|
213
|
+
return True
|
|
214
|
+
for t in self.transforms:
|
|
215
|
+
if hasattr(t, 'is_template') and t.is_template():
|
|
216
|
+
return True
|
|
217
|
+
return False
|
|
218
|
+
|
|
205
219
|
def is_recursive(self):
|
|
206
220
|
return self.inner.is_recursive()
|
|
207
221
|
|
|
@@ -419,6 +433,17 @@ class FilterWrap(Wrap):
|
|
|
419
433
|
return s[:-1] + filter_str + ']'
|
|
420
434
|
return s + filter_str
|
|
421
435
|
|
|
436
|
+
def is_template(self):
|
|
437
|
+
"""
|
|
438
|
+
True if the inner op or any attached filter contains a
|
|
439
|
+
substitution reference.
|
|
440
|
+
"""
|
|
441
|
+
if self.inner.is_template():
|
|
442
|
+
return True
|
|
443
|
+
return any(
|
|
444
|
+
hasattr(f, 'is_template') and f.is_template()
|
|
445
|
+
for f in self.filters)
|
|
446
|
+
|
|
422
447
|
def default(self):
|
|
423
448
|
"""
|
|
424
449
|
Filters imply the value has structure to check against,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotted_notation
|
|
3
|
-
Version: 0.43.
|
|
3
|
+
Version: 0.43.4
|
|
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
|
|
@@ -244,6 +244,14 @@ Several Python libraries handle nested data access. Here's how dotted compares:
|
|
|
244
244
|
<a id="breaking-changes"></a>
|
|
245
245
|
## Breaking Changes
|
|
246
246
|
|
|
247
|
+
### v0.43.4
|
|
248
|
+
- **`is_pattern` no longer returns `True` for substitutions**. `Subst` previously
|
|
249
|
+
inherited from `Pattern` in the class hierarchy, so any path containing a
|
|
250
|
+
substitution in an access position (e.g. `a.$(x)`, `a.$0`) reported as a
|
|
251
|
+
pattern. Substitutions resolve to a single key, not a set — that was a
|
|
252
|
+
misclassification. Use `is_template(path)` to check "contains a
|
|
253
|
+
substitution."
|
|
254
|
+
|
|
247
255
|
### v0.40.0
|
|
248
256
|
- **`unpack()` now returns a `dict`**: Previously returned a tuple of
|
|
249
257
|
`(path, value)` pairs. Now returns `{path: value, ...}` directly.
|
|
@@ -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.43.
|
|
8
|
+
version="0.43.4",
|
|
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",
|
|
@@ -106,6 +106,80 @@ def test_is_template_named_escaped():
|
|
|
106
106
|
assert dotted.is_template('\\$(name)') is False
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
# ---- is_template catches substs in all positions ----
|
|
110
|
+
|
|
111
|
+
def test_is_template_guard_value():
|
|
112
|
+
# Subst as guard RHS (previously missed)
|
|
113
|
+
assert dotted.is_template('a=$(x)') is True
|
|
114
|
+
assert dotted.is_template('a>=$(x)') is True
|
|
115
|
+
assert dotted.is_template('a!=$(x)') is True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_is_template_guard_transform():
|
|
119
|
+
# Subst combined with guard cast
|
|
120
|
+
assert dotted.is_template('a|int=$(x)') is True
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_is_template_filter_wrap_value():
|
|
124
|
+
# Subst inside filter-wrap predicate value
|
|
125
|
+
assert dotted.is_template('a[*&x=$(y)]') is True
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_is_template_filter_wrap_key():
|
|
129
|
+
# Subst as filter key
|
|
130
|
+
assert dotted.is_template('a[$(k)=1]') is True
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_is_template_filter_wrap_transform():
|
|
134
|
+
# Subst with filter transform
|
|
135
|
+
assert dotted.is_template('a[*&x|int=$(y)]') is True
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_is_template_filter_negation():
|
|
139
|
+
# Subst under filter negation
|
|
140
|
+
assert dotted.is_template('a[!x=$(y)]') is True
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_is_template_filter_group():
|
|
144
|
+
# Subst inside filter group (AND/OR of predicates)
|
|
145
|
+
assert dotted.is_template('a[*&(b=$(x) & c=1)]') is True
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_is_template_slice_filter():
|
|
149
|
+
# SliceFilter (compact [filter] form) carrying a subst
|
|
150
|
+
assert dotted.is_template('a[$(k)=1]') is True
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_is_template_group_branch():
|
|
154
|
+
# Subst in an access-op group branch
|
|
155
|
+
assert dotted.is_template('(a=$(x) & b=1)') is True
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_is_template_false_for_refs():
|
|
159
|
+
# References are not templates (resolve against data, not bindings)
|
|
160
|
+
assert dotted.is_template('a.$$(b)') is False
|
|
161
|
+
assert dotted.is_template('a=$$(b)') is False
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_is_template_false_for_patterns():
|
|
165
|
+
# Plain patterns are not templates
|
|
166
|
+
assert dotted.is_template('a.*.b') is False
|
|
167
|
+
assert dotted.is_template('a=/re/') is False
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ---- is_pattern: substitutions are NOT patterns (single-yield) ----
|
|
171
|
+
|
|
172
|
+
def test_is_pattern_false_for_subst_in_access():
|
|
173
|
+
# $(x) as an access-position matcher is not a pattern (yields one key).
|
|
174
|
+
assert dotted.is_pattern('a.$(x)') is False
|
|
175
|
+
assert dotted.is_pattern('a.$0') is False
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_is_pattern_false_for_subst_in_guard():
|
|
179
|
+
# Concrete access with subst guard is still not a pattern
|
|
180
|
+
assert dotted.is_pattern('a=$(x)') is False
|
|
181
|
+
|
|
182
|
+
|
|
109
183
|
# ---- quote ----
|
|
110
184
|
|
|
111
185
|
def test_quote_named_subst_literal():
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import dotted
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_numeric_get_key():
|
|
5
|
+
T = {111: {'stuff': 'hi'}}
|
|
6
|
+
|
|
7
|
+
m = dotted.get(T, '111.stuff')
|
|
8
|
+
assert m == 'hi'
|
|
9
|
+
|
|
10
|
+
m = dotted.get(T, '111.0.stuff')
|
|
11
|
+
assert m != 'hi'
|
|
12
|
+
|
|
13
|
+
# 111.0 == 111
|
|
14
|
+
m = dotted.get(T, '#"111.0".stuff')
|
|
15
|
+
assert m == 'hi'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_numeric_get_slot():
|
|
19
|
+
T = {111: {'stuff': 'hi'}}
|
|
20
|
+
|
|
21
|
+
m = dotted.get(T, '[111].stuff')
|
|
22
|
+
assert m == 'hi'
|
|
23
|
+
|
|
24
|
+
# 111.0 == 111
|
|
25
|
+
m = dotted.get(T, '[111.0].stuff')
|
|
26
|
+
assert m == 'hi'
|
|
27
|
+
|
|
28
|
+
# 111.0 == 111
|
|
29
|
+
m = dotted.get(T, '[#"111.0"].stuff')
|
|
30
|
+
assert m == 'hi'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_numeric_expand_int():
|
|
34
|
+
T = {111: {'stuff': 'hi'}}
|
|
35
|
+
|
|
36
|
+
m = dotted.expand(T, '*.*')
|
|
37
|
+
assert m == ('111.stuff',)
|
|
38
|
+
|
|
39
|
+
m = dotted.expand(T, '[*].*')
|
|
40
|
+
assert m == ('[111].stuff',)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_numeric_expand_float():
|
|
44
|
+
T = {111.0: {'stuff': 'hi'}}
|
|
45
|
+
|
|
46
|
+
m = dotted.expand(T, '*.*')
|
|
47
|
+
assert m == ("#'111.0'.stuff",)
|
|
48
|
+
|
|
49
|
+
m = dotted.expand(T, '[*].*')
|
|
50
|
+
assert m == ('[111.0].stuff',)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_numeric_update():
|
|
54
|
+
m = dotted.update({}, '07a', 8)
|
|
55
|
+
assert m == {'07a': 8}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Extended numeric literals
|
|
59
|
+
|
|
60
|
+
def test_numeric_extended_scientific():
|
|
61
|
+
lst = list(range(11))
|
|
62
|
+
assert dotted.get(lst, '[1e1]') == 10
|
|
63
|
+
d = {10000000000: 'big'}
|
|
64
|
+
assert dotted.get(d, '1e10') == 'big'
|
|
65
|
+
assert dotted.get(d, '1e+10') == 'big'
|
|
66
|
+
# negative exponent — float key
|
|
67
|
+
d2 = {1e-12: 'tiny'}
|
|
68
|
+
assert dotted.get(d2, '1e-12') == 'tiny'
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_numeric_extended_underscore():
|
|
72
|
+
lst = list(range(1001))
|
|
73
|
+
assert dotted.get(lst, '[1_000]') == 1000
|
|
74
|
+
d = {1000: 'thousand'}
|
|
75
|
+
assert dotted.get(d, '1_000') == 'thousand'
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_numeric_extended_hex():
|
|
79
|
+
lst = list(range(32))
|
|
80
|
+
assert dotted.get(lst, '[0x1F]') == 31
|
|
81
|
+
d = {31: 'hex'}
|
|
82
|
+
assert dotted.get(d, '0x1F') == 'hex'
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_numeric_extended_octal():
|
|
86
|
+
lst = list(range(16))
|
|
87
|
+
assert dotted.get(lst, '[0o17]') == 15
|
|
88
|
+
d = {15: 'octal'}
|
|
89
|
+
assert dotted.get(d, '0o17') == 'octal'
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_numeric_extended_binary():
|
|
93
|
+
lst = list(range(11))
|
|
94
|
+
assert dotted.get(lst, '[0b1010]') == 10
|
|
95
|
+
d = {10: 'binary'}
|
|
96
|
+
assert dotted.get(d, '0b1010') == 'binary'
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_numeric_extended_negative_slot():
|
|
100
|
+
lst = [None] * 5 + ['neg_sci']
|
|
101
|
+
assert dotted.get(lst, '[-1e1]') is None # -10, out of range for len 6
|
|
102
|
+
d = {-100000: 'neg_sci'}
|
|
103
|
+
assert dotted.get(d, '[-1e5]') == 'neg_sci'
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# --- Floats in RHS (value) positions ---
|
|
107
|
+
|
|
108
|
+
def test_rhs_float_guard():
|
|
109
|
+
"""
|
|
110
|
+
a=0.9 parses as a float guard, not (a=0, .9).
|
|
111
|
+
"""
|
|
112
|
+
p = dotted.parse('a=0.9')
|
|
113
|
+
assert dotted.assemble(p) == 'a=0.9'
|
|
114
|
+
assert dotted.get({'a': 0.9}, 'a=0.9') == 0.9
|
|
115
|
+
assert dotted.get({'a': 1}, 'a=0.9') is None
|
|
116
|
+
|
|
117
|
+
def test_rhs_float_dotted_keycmd_guard():
|
|
118
|
+
"""
|
|
119
|
+
a.b=7.1 must parse as (a, b=7.1), not (a, b=7, 1).
|
|
120
|
+
"""
|
|
121
|
+
p = dotted.parse('a.b=7.1')
|
|
122
|
+
assert dotted.assemble(p) == 'a.b=7.1'
|
|
123
|
+
assert dotted.get({'a': {'b': 7.1}}, 'a.b=7.1') == 7.1
|
|
124
|
+
|
|
125
|
+
def test_rhs_float_negative():
|
|
126
|
+
assert dotted.get({'a': -0.9}, 'a=-0.9') == -0.9
|
|
127
|
+
|
|
128
|
+
def test_rhs_float_scientific():
|
|
129
|
+
assert dotted.get({'a': 150.0}, 'a=1.5e2') == 150.0
|
|
130
|
+
|
|
131
|
+
def test_rhs_float_in_filter():
|
|
132
|
+
data = [{'x': 0.9}, {'x': 1.1}]
|
|
133
|
+
assert dotted.get(data, '[x=0.9]') == [{'x': 0.9}]
|
|
134
|
+
|
|
135
|
+
def test_rhs_float_in_slot_guard():
|
|
136
|
+
assert dotted.get([0.1, 0.9, 0.5], '[*]=0.9') == (0.9,)
|
|
137
|
+
|
|
138
|
+
def test_rhs_float_in_container():
|
|
139
|
+
p = dotted.parse('a=[0.1, 0.2]')
|
|
140
|
+
assert dotted.assemble(p) == 'a=[0.1, 0.2]'
|
|
141
|
+
assert dotted.get({'a': [0.1, 0.2]}, 'a=[0.1, 0.2]') == [0.1, 0.2]
|
|
142
|
+
|
|
143
|
+
def test_rhs_float_in_transform_arg():
|
|
144
|
+
"""
|
|
145
|
+
Transform arguments accept floats.
|
|
146
|
+
"""
|
|
147
|
+
assert dotted.get({'a': 0.5}, 'a|round:0') == 0
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# --- Guarded ops are terminal within op_seq ---
|
|
151
|
+
|
|
152
|
+
def test_guard_terminal_keycmd():
|
|
153
|
+
"""
|
|
154
|
+
a=1.b: guard `a=1` cannot have a continuation `.b` after it.
|
|
155
|
+
"""
|
|
156
|
+
import pytest
|
|
157
|
+
from dotted.api import ParseError
|
|
158
|
+
with pytest.raises(ParseError):
|
|
159
|
+
dotted.parse('a=1.b')
|
|
160
|
+
with pytest.raises(ParseError):
|
|
161
|
+
dotted.parse('a=1.0b')
|
|
162
|
+
|
|
163
|
+
def test_guard_terminal_dotted_keycmd():
|
|
164
|
+
"""
|
|
165
|
+
a.b=7.c: guard `b=7` is already terminal; adding `.c` after must fail.
|
|
166
|
+
"""
|
|
167
|
+
import pytest
|
|
168
|
+
from dotted.api import ParseError
|
|
169
|
+
with pytest.raises(ParseError):
|
|
170
|
+
dotted.parse('a.b=7.c')
|
|
171
|
+
|
|
172
|
+
def test_guard_terminal_slot():
|
|
173
|
+
"""
|
|
174
|
+
a[0]=7.b: slot guard `[0]=7` must be terminal.
|
|
175
|
+
"""
|
|
176
|
+
import pytest
|
|
177
|
+
from dotted.api import ParseError
|
|
178
|
+
with pytest.raises(ParseError):
|
|
179
|
+
dotted.parse('a[0]=7.b')
|
|
180
|
+
with pytest.raises(ParseError):
|
|
181
|
+
dotted.parse('[0]=7.a')
|
|
182
|
+
|
|
183
|
+
def test_guard_terminal_recursive_dstar():
|
|
184
|
+
"""
|
|
185
|
+
**=7.a: recursive wildcard guard must be terminal.
|
|
186
|
+
"""
|
|
187
|
+
import pytest
|
|
188
|
+
from dotted.api import ParseError
|
|
189
|
+
with pytest.raises(ParseError):
|
|
190
|
+
dotted.parse('**=7.a')
|
|
191
|
+
|
|
192
|
+
def test_guard_terminal_recursive_pattern():
|
|
193
|
+
"""
|
|
194
|
+
*x=7.a: recursive pattern guard must be terminal.
|
|
195
|
+
"""
|
|
196
|
+
import pytest
|
|
197
|
+
from dotted.api import ParseError
|
|
198
|
+
with pytest.raises(ParseError):
|
|
199
|
+
dotted.parse('*x=7.a')
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import dotted
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def test_numeric_get_key():
|
|
5
|
-
T = {111: {'stuff': 'hi'}}
|
|
6
|
-
|
|
7
|
-
m = dotted.get(T, '111.stuff')
|
|
8
|
-
assert m == 'hi'
|
|
9
|
-
|
|
10
|
-
m = dotted.get(T, '111.0.stuff')
|
|
11
|
-
assert m != 'hi'
|
|
12
|
-
|
|
13
|
-
# 111.0 == 111
|
|
14
|
-
m = dotted.get(T, '#"111.0".stuff')
|
|
15
|
-
assert m == 'hi'
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_numeric_get_slot():
|
|
19
|
-
T = {111: {'stuff': 'hi'}}
|
|
20
|
-
|
|
21
|
-
m = dotted.get(T, '[111].stuff')
|
|
22
|
-
assert m == 'hi'
|
|
23
|
-
|
|
24
|
-
# 111.0 == 111
|
|
25
|
-
m = dotted.get(T, '[111.0].stuff')
|
|
26
|
-
assert m == 'hi'
|
|
27
|
-
|
|
28
|
-
# 111.0 == 111
|
|
29
|
-
m = dotted.get(T, '[#"111.0"].stuff')
|
|
30
|
-
assert m == 'hi'
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def test_numeric_expand_int():
|
|
34
|
-
T = {111: {'stuff': 'hi'}}
|
|
35
|
-
|
|
36
|
-
m = dotted.expand(T, '*.*')
|
|
37
|
-
assert m == ('111.stuff',)
|
|
38
|
-
|
|
39
|
-
m = dotted.expand(T, '[*].*')
|
|
40
|
-
assert m == ('[111].stuff',)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_numeric_expand_float():
|
|
44
|
-
T = {111.0: {'stuff': 'hi'}}
|
|
45
|
-
|
|
46
|
-
m = dotted.expand(T, '*.*')
|
|
47
|
-
assert m == ("#'111.0'.stuff",)
|
|
48
|
-
|
|
49
|
-
m = dotted.expand(T, '[*].*')
|
|
50
|
-
assert m == ('[111.0].stuff',)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def test_numeric_update():
|
|
54
|
-
m = dotted.update({}, '07a', 8)
|
|
55
|
-
assert m == {'07a': 8}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# Extended numeric literals
|
|
59
|
-
|
|
60
|
-
def test_numeric_extended_scientific():
|
|
61
|
-
lst = list(range(11))
|
|
62
|
-
assert dotted.get(lst, '[1e1]') == 10
|
|
63
|
-
d = {10000000000: 'big'}
|
|
64
|
-
assert dotted.get(d, '1e10') == 'big'
|
|
65
|
-
assert dotted.get(d, '1e+10') == 'big'
|
|
66
|
-
# negative exponent — float key
|
|
67
|
-
d2 = {1e-12: 'tiny'}
|
|
68
|
-
assert dotted.get(d2, '1e-12') == 'tiny'
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_numeric_extended_underscore():
|
|
72
|
-
lst = list(range(1001))
|
|
73
|
-
assert dotted.get(lst, '[1_000]') == 1000
|
|
74
|
-
d = {1000: 'thousand'}
|
|
75
|
-
assert dotted.get(d, '1_000') == 'thousand'
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def test_numeric_extended_hex():
|
|
79
|
-
lst = list(range(32))
|
|
80
|
-
assert dotted.get(lst, '[0x1F]') == 31
|
|
81
|
-
d = {31: 'hex'}
|
|
82
|
-
assert dotted.get(d, '0x1F') == 'hex'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def test_numeric_extended_octal():
|
|
86
|
-
lst = list(range(16))
|
|
87
|
-
assert dotted.get(lst, '[0o17]') == 15
|
|
88
|
-
d = {15: 'octal'}
|
|
89
|
-
assert dotted.get(d, '0o17') == 'octal'
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def test_numeric_extended_binary():
|
|
93
|
-
lst = list(range(11))
|
|
94
|
-
assert dotted.get(lst, '[0b1010]') == 10
|
|
95
|
-
d = {10: 'binary'}
|
|
96
|
-
assert dotted.get(d, '0b1010') == 'binary'
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def test_numeric_extended_negative_slot():
|
|
100
|
-
lst = [None] * 5 + ['neg_sci']
|
|
101
|
-
assert dotted.get(lst, '[-1e1]') is None # -10, out of range for len 6
|
|
102
|
-
d = {-100000: 'neg_sci'}
|
|
103
|
-
assert dotted.get(d, '[-1e5]') == 'neg_sci'
|
|
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.43.2 → dotted_notation-0.43.4}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|