dotted-notation 0.43.2__tar.gz → 0.43.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. {dotted_notation-0.43.2/dotted_notation.egg-info → dotted_notation-0.43.3}/PKG-INFO +1 -1
  2. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/grammar.py +64 -18
  3. {dotted_notation-0.43.2 → dotted_notation-0.43.3/dotted_notation.egg-info}/PKG-INFO +1 -1
  4. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/setup.py +1 -1
  5. dotted_notation-0.43.3/tests/test_numeric.py +199 -0
  6. dotted_notation-0.43.2/tests/test_numeric.py +0 -103
  7. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/LICENSE +0 -0
  8. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/README.md +0 -0
  9. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/__init__.py +0 -0
  10. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/__main__.py +0 -0
  11. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/access.py +0 -0
  12. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/api.py +0 -0
  13. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/base.py +0 -0
  14. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/cli/__init__.py +0 -0
  15. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/cli/_compat.py +0 -0
  16. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/cli/formats.py +0 -0
  17. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/cli/main.py +0 -0
  18. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/containers.py +0 -0
  19. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/engine.py +0 -0
  20. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/filters.py +0 -0
  21. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/groups.py +0 -0
  22. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/matchers.py +0 -0
  23. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/predicates.py +0 -0
  24. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/recursive.py +0 -0
  25. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/results.py +0 -0
  26. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/sqlize.py +0 -0
  27. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/transforms.py +0 -0
  28. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/utils.py +0 -0
  29. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/utypes.py +0 -0
  30. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted/wrappers.py +0 -0
  31. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted_notation.egg-info/SOURCES.txt +0 -0
  32. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted_notation.egg-info/dependency_links.txt +0 -0
  33. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted_notation.egg-info/entry_points.txt +0 -0
  34. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted_notation.egg-info/requires.txt +0 -0
  35. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/dotted_notation.egg-info/top_level.txt +0 -0
  36. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/setup.cfg +0 -0
  37. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/__init__.py +0 -0
  38. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_api.py +0 -0
  39. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_appender.py +0 -0
  40. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_assemble.py +0 -0
  41. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_attrs.py +0 -0
  42. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_bindings.py +0 -0
  43. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_cli.py +0 -0
  44. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_concat.py +0 -0
  45. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_container_filter.py +0 -0
  46. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_cut.py +0 -0
  47. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_empty.py +0 -0
  48. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_filter_keyvalue.py +0 -0
  49. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_get.py +0 -0
  50. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_guard_transforms.py +0 -0
  51. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_invert.py +0 -0
  52. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_json_sentinels.py +0 -0
  53. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_keys_values.py +0 -0
  54. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_match.py +0 -0
  55. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_matchable.py +0 -0
  56. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_named_subst.py +0 -0
  57. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_negation.py +0 -0
  58. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_nop.py +0 -0
  59. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_opgroup.py +0 -0
  60. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_pluck.py +0 -0
  61. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_predicates.py +0 -0
  62. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_quote_idempotent.py +0 -0
  63. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_recursive.py +0 -0
  64. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_reference.py +0 -0
  65. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_replace.py +0 -0
  66. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_slice.py +0 -0
  67. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_softcut.py +0 -0
  68. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_sqlize.py +0 -0
  69. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_strict.py +0 -0
  70. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_string_glob.py +0 -0
  71. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_subst_escape.py +0 -0
  72. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_subst_transforms.py +0 -0
  73. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_threading.py +0 -0
  74. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_transforms.py +0 -0
  75. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_translate.py +0 -0
  76. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_type_restriction.py +0 -0
  77. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_unpack.py +0 -0
  78. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_update.py +0 -0
  79. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/tests/test_update_if.py +0 -0
  80. {dotted_notation-0.43.2 → dotted_notation-0.43.3}/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.2
3
+ Version: 0.43.3
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
@@ -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
- _val_atoms = bytes_literal | string | wildcard | var | regex | numeric_quoted | none | true | false | numeric_extended | numeric_key
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 | numeric_key
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
- recursive_op = (
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
- op_seq = pp.Group(op_seq_item + ZM(_op_seq_cont))
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
- _dot_recursive = (dot + recursive_op).set_parse_action(lambda t: t[0])
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
- # Resolve forward: op_seq_item is the atom of op_seq, used in the precedence tower
920
- # Continuation items: everything that can appear after the first item in an op_seq.
921
- # Bare (a,b) is NOT allowed here — only explicit-op groups like (.a,.b).
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
- recursive_op |
926
- _keycmd_guarded_all | keycmd |
927
- _dot_keycmd_guarded_nop | _dot_keycmd_guarded_plain |
928
- _dot_recursive |
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
- _slotcmd_guarded_all | slotcmd |
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 | _op_seq_cont_items)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotted_notation
3
- Version: 0.43.2
3
+ Version: 0.43.3
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
@@ -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.2",
8
+ version="0.43.3",
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",
@@ -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'