Skip to content

Commit 6f8a558

Browse files
committed
Implement merge types as an enum
Additional type hints
1 parent 16f511a commit 6f8a558

4 files changed

Lines changed: 35 additions & 28 deletions

File tree

README.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,14 +279,13 @@ does.
279279
Merge source into destination. Like dict.update() but performs
280280
deep merging.
281281
282-
flags is an OR'ed combination of MERGE_ADDITIVE, MERGE_REPLACE
283-
MERGE_TYPESAFE.
284-
* MERGE_ADDITIVE : List objects are combined onto one long
282+
flags is an OR'ed combination of MergeType enum members.
283+
* ADDITIVE : List objects are combined onto one long
285284
list (NOT a set). This is the default flag.
286-
* MERGE_REPLACE : Instead of combining list objects, when
285+
* REPLACE : Instead of combining list objects, when
287286
2 list objects are at an equal depth of merge, replace
288287
the destination with the source.
289-
* MERGE_TYPESAFE : When 2 keys at equal levels are of different
288+
* TYPESAFE : When 2 keys at equal levels are of different
290289
types, raise a TypeError exception. By default, the source
291290
replaces the destination in this situation.
292291

dpath/util.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
from collections.abc import MutableMapping, MutableSequence
2+
from enum import Flag, IntFlag, auto
3+
from typing import Union, List, Any, Dict
24

35
import dpath.segments
46
from dpath import options
57
from dpath.exceptions import InvalidKeyName
68

79
_DEFAULT_SENTINEL = object()
8-
MERGE_REPLACE = (1 << 1)
9-
MERGE_ADDITIVE = (1 << 2)
10-
MERGE_TYPESAFE = (1 << 3)
1110

1211

13-
def __safe_path__(path, separator):
12+
class MergeType(IntFlag):
13+
REPLACE = auto()
14+
ADDITIVE = auto()
15+
TYPESAFE = auto()
16+
17+
18+
# Type alias for dict path segments where integers are explicitly casted
19+
IntAwareSegment = Union[int, Any]
20+
21+
22+
def __safe_path__(path: str, separator: str) -> Union[List[IntAwareSegment], IntAwareSegment]:
1423
"""
1524
Given a path and separator, return a tuple of segments. If path is
1625
already a non-leaf thing, return it.
@@ -146,7 +155,7 @@ def f(obj, pair, counter):
146155
return changed
147156

148157

149-
def get(obj, glob, separator='/', default=_DEFAULT_SENTINEL):
158+
def get(obj: Dict, glob: str, separator="/", default: Any = _DEFAULT_SENTINEL) -> dict:
150159
"""
151160
Given an object which contains only one possible match for the given glob,
152161
return the value for the leaf matching the given glob.
@@ -156,7 +165,7 @@ def get(obj, glob, separator='/', default=_DEFAULT_SENTINEL):
156165
If more than one leaf matches the glob, ValueError is raised. If the glob is
157166
not found and a default is not provided, KeyError is raised.
158167
"""
159-
if glob == '/':
168+
if glob == "/":
160169
return obj
161170

162171
globlist = __safe_path__(glob, separator)
@@ -233,7 +242,7 @@ def f(obj, pair, result):
233242
return dpath.segments.fold(obj, f, {})
234243

235244

236-
def merge(dst, src, separator='/', afilter=None, flags=MERGE_ADDITIVE):
245+
def merge(dst, src, separator='/', afilter=None, flags=MergeType.ADDITIVE):
237246
"""
238247
Merge source into destination. Like dict.update() but performs deep
239248
merging.
@@ -262,14 +271,13 @@ def merge(dst, src, separator='/', afilter=None, flags=MERGE_ADDITIVE):
262271
objects that you intend to merge. For further notes see
263272
https://github.com/akesterson/dpath-python/issues/58
264273
265-
flags is an OR'ed combination of MERGE_ADDITIVE, MERGE_REPLACE,
266-
MERGE_TYPESAFE.
267-
* MERGE_ADDITIVE : List objects are combined onto one long
274+
flags is an OR'ed combination of MergeType enum members.
275+
* ADDITIVE : List objects are combined onto one long
268276
list (NOT a set). This is the default flag.
269-
* MERGE_REPLACE : Instead of combining list objects, when
277+
* REPLACE : Instead of combining list objects, when
270278
2 list objects are at an equal depth of merge, replace
271279
the destination with the source.
272-
* MERGE_TYPESAFE : When 2 keys at equal levels are of different
280+
* TYPESAFE : When 2 keys at equal levels are of different
273281
types, raise a TypeError exception. By default, the source
274282
replaces the destination in this situation.
275283
"""
@@ -295,7 +303,7 @@ def merger(dst, src, _segments=()):
295303
"{}".format(segments))
296304

297305
# Validate src and dst types match.
298-
if flags & MERGE_TYPESAFE:
306+
if flags & MergeType.TYPESAFE:
299307
if dpath.segments.has(dst, segments):
300308
target = dpath.segments.get(dst, segments)
301309
tt = type(target)
@@ -332,11 +340,11 @@ def merger(dst, src, _segments=()):
332340
#
333341
# Pretend we have a sequence and account for the flags.
334342
try:
335-
if flags & MERGE_ADDITIVE:
343+
if flags & MergeType.ADDITIVE:
336344
target += found
337345
continue
338346

339-
if flags & MERGE_REPLACE:
347+
if flags & MergeType.REPLACE:
340348
try:
341349
target['']
342350
except TypeError:

tests/test_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def test_types_merge_simple_list_replace():
106106
"list": TestSequence([0, 1, 2, 3])
107107
})
108108

109-
dpath.util.merge(dst, src, flags=dpath.util.MERGE_REPLACE)
109+
dpath.util.merge(dst, src, flags=dpath.util.MergeType.REPLACE)
110110
nose.tools.eq_(dst["list"], TestSequence([7, 8, 9, 10]))
111111

112112

tests/test_util_merge.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def test_merge_typesafe_and_separator():
1919
}
2020

2121
try:
22-
dpath.util.merge(dst, src, flags=(dpath.util.MERGE_ADDITIVE | dpath.util.MERGE_TYPESAFE), separator=";")
22+
dpath.util.merge(dst, src, flags=(dpath.util.MergeType.ADDITIVE | dpath.util.MergeType.TYPESAFE), separator=";")
2323
except TypeError as e:
2424
assert(str(e).endswith("dict;integer"))
2525

@@ -59,7 +59,7 @@ def test_merge_simple_list_additive():
5959
"list": [0, 1, 2, 3],
6060
}
6161

62-
dpath.util.merge(dst, src, flags=dpath.util.MERGE_ADDITIVE)
62+
dpath.util.merge(dst, src, flags=dpath.util.MergeType.ADDITIVE)
6363
nose.tools.eq_(dst["list"], [0, 1, 2, 3, 7, 8, 9, 10])
6464

6565

@@ -71,7 +71,7 @@ def test_merge_simple_list_replace():
7171
"list": [0, 1, 2, 3],
7272
}
7373

74-
dpath.util.merge(dst, src, flags=dpath.util.MERGE_REPLACE)
74+
dpath.util.merge(dst, src, flags=dpath.util.MergeType.REPLACE)
7575
nose.tools.eq_(dst["list"], [7, 8, 9, 10])
7676

7777

@@ -123,7 +123,7 @@ def test_merge_typesafe():
123123
],
124124
}
125125

126-
dpath.util.merge(dst, src, flags=dpath.util.MERGE_TYPESAFE)
126+
dpath.util.merge(dst, src, flags=dpath.util.MergeType.TYPESAFE)
127127

128128

129129
@raises(TypeError)
@@ -156,20 +156,20 @@ class tcis(list):
156156
assert(dst['ms'][2] == 'c')
157157
assert("casserole" in dst["mm"])
158158

159-
dpath.util.merge(dst, src, flags=dpath.util.MERGE_TYPESAFE)
159+
dpath.util.merge(dst, src, flags=dpath.util.MergeType.TYPESAFE)
160160

161161

162162
def test_merge_replace_1():
163163
dct_a = {"a": {"b": [1, 2, 3]}}
164164
dct_b = {"a": {"b": [1]}}
165-
dpath.util.merge(dct_a, dct_b, flags=dpath.util.MERGE_REPLACE)
165+
dpath.util.merge(dct_a, dct_b, flags=dpath.util.MergeType.REPLACE)
166166
assert(len(dct_a['a']['b']) == 1)
167167

168168

169169
def test_merge_replace_2():
170170
d1 = {'a': [0, 1, 2]}
171171
d2 = {'a': ['a']}
172-
dpath.util.merge(d1, d2, flags=dpath.util.MERGE_REPLACE)
172+
dpath.util.merge(d1, d2, flags=dpath.util.MergeType.REPLACE)
173173
assert(len(d1['a']) == 1)
174174
assert(d1['a'][0] == 'a')
175175

0 commit comments

Comments
 (0)