Skip to content

Commit 6a21463

Browse files
committed
fixing tests
1 parent b971eac commit 6a21463

File tree

7 files changed

+109
-18
lines changed

7 files changed

+109
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
DeepDiff Change log
22

3+
- v5-2-2: Fixed Delta serialization when None type is present.
34
- v5-2-0: Removed Murmur3 as the preferred hashing method. Using SHA256 by default now. Added commandline for deepdiff. Added group_by. Added math_epsilon. Improved ignoring of NoneType.
45
- v5-0-2: Bug Fix NoneType in ignore type groups https://github.com/seperman/deepdiff/issues/207
56
- v5-0-1: Bug fix to not apply format to non numbers.

deepdiff/commands.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def cli():
4848
@click.option('--significant-digits', required=False, default=None, type=int, show_default=True)
4949
@click.option('--truncate-datetime', required=False, type=click.Choice(['second', 'minute', 'hour', 'day'], case_sensitive=True), show_default=True, default=None)
5050
@click.option('--verbose-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True)
51+
@click.option('--debug', is_flag=True, show_default=False)
5152
def diff(
5253
*args, **kwargs
5354
):
@@ -59,6 +60,7 @@ def diff(
5960
6061
T1 and T2 are the path to the files to be compared with each other.
6162
"""
63+
debug = kwargs.pop('debug')
6264
kwargs['ignore_private_variables'] = not kwargs.pop('include_private_variables')
6365
kwargs['progress_logger'] = logger.info if kwargs['progress_logger'] == 'info' else logger.error
6466
create_patch = kwargs.pop('create_patch')
@@ -71,7 +73,10 @@ def diff(
7173
try:
7274
kwargs[name] = load_path_content(t_path, file_type=t_extension)
7375
except Exception as e: # pragma: no cover.
74-
sys.exit(str(f"Error when loading {name}: {e}")) # pragma: no cover.
76+
if debug: # pragma: no cover.
77+
raise # pragma: no cover.
78+
else: # pragma: no cover.
79+
sys.exit(str(f"Error when loading {name}: {e}")) # pragma: no cover.
7580

7681
# if (t1_extension != t2_extension):
7782
if t1_extension in {'csv', 'tsv'}:
@@ -92,7 +97,10 @@ def diff(
9297
try:
9398
delta = Delta(diff)
9499
except Exception as e: # pragma: no cover.
95-
sys.exit(f"Error when loading the patch (aka delta): {e}") # pragma: no cover.
100+
if debug: # pragma: no cover.
101+
raise # pragma: no cover.
102+
else: # pragma: no cover.
103+
sys.exit(f"Error when loading the patch (aka delta): {e}") # pragma: no cover.
96104

97105
# printing into stdout
98106
sys.stdout.buffer.write(delta.dumps())
@@ -105,8 +113,9 @@ def diff(
105113
@click.argument('delta_path', type=click.Path(exists=True, resolve_path=True))
106114
@click.option('--backup', '-b', is_flag=True, show_default=True)
107115
@click.option('--raise-errors', is_flag=True, show_default=True)
116+
@click.option('--debug', is_flag=True, show_default=False)
108117
def patch(
109-
path, delta_path, backup, raise_errors
118+
path, delta_path, backup, raise_errors, debug
110119
):
111120
"""
112121
Deep Patch Commandline
@@ -123,7 +132,10 @@ def patch(
123132
try:
124133
delta = Delta(delta_path=delta_path, raise_errors=raise_errors)
125134
except Exception as e: # pragma: no cover.
126-
sys.exit(str(f"Error when loading the patch (aka delta) {delta_path}: {e}")) # pragma: no cover.
135+
if debug: # pragma: no cover.
136+
raise # pragma: no cover.
137+
else: # pragma: no cover.
138+
sys.exit(str(f"Error when loading the patch (aka delta) {delta_path}: {e}")) # pragma: no cover.
127139

128140
extension = path.split('.')[-1]
129141

@@ -137,7 +149,10 @@ def patch(
137149
try:
138150
save_content_to_path(result, path, file_type=extension, keep_backup=backup)
139151
except Exception as e: # pragma: no cover.
140-
sys.exit(str(f"Error when saving {path}: {e}")) # pragma: no cover.
152+
if debug: # pragma: no cover.
153+
raise # pragma: no cover.
154+
else: # pragma: no cover.
155+
sys.exit(str(f"Error when saving {path}: {e}")) # pragma: no cover.
141156

142157

143158
@cli.command()
@@ -148,7 +163,8 @@ def patch(
148163
@click.option('--exclude-paths', required=False, type=str, show_default=False, multiple=True)
149164
@click.option('--exclude-regex-paths', required=False, type=str, show_default=False, multiple=True)
150165
@click.option('--verbose-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True)
151-
def grep(item, path, **kwargs):
166+
@click.option('--debug', is_flag=True, show_default=False)
167+
def grep(item, path, debug, **kwargs):
152168
"""
153169
Deep Grep Commandline
154170
@@ -162,19 +178,26 @@ def grep(item, path, **kwargs):
162178
try:
163179
content = load_path_content(path)
164180
except Exception as e: # pragma: no cover.
165-
sys.exit(str(f"Error when loading {path}: {e}")) # pragma: no cover.
181+
if debug: # pragma: no cover.
182+
raise # pragma: no cover.
183+
else: # pragma: no cover.
184+
sys.exit(str(f"Error when loading {path}: {e}")) # pragma: no cover.
166185

167186
try:
168187
result = DeepSearch(content, item, **kwargs)
169188
except Exception as e: # pragma: no cover.
170-
sys.exit(str(f"Error when running deep search on {path}: {e}")) # pragma: no cover.
189+
if debug: # pragma: no cover.
190+
raise # pragma: no cover.
191+
else: # pragma: no cover.
192+
sys.exit(str(f"Error when running deep search on {path}: {e}")) # pragma: no cover.
171193
pprint(result, indent=2)
172194

173195

174196
@cli.command()
175197
@click.argument('path_inside', required=True, type=str)
176198
@click.argument('path', type=click.Path(exists=True, resolve_path=True))
177-
def extract(path_inside, path):
199+
@click.option('--debug', is_flag=True, show_default=False)
200+
def extract(path_inside, path, debug):
178201
"""
179202
Deep Extract Commandline
180203
@@ -185,10 +208,16 @@ def extract(path_inside, path):
185208
try:
186209
content = load_path_content(path)
187210
except Exception as e: # pragma: no cover.
188-
sys.exit(str(f"Error when loading {path}: {e}")) # pragma: no cover.
211+
if debug: # pragma: no cover.
212+
raise # pragma: no cover.
213+
else: # pragma: no cover.
214+
sys.exit(str(f"Error when loading {path}: {e}")) # pragma: no cover.
189215

190216
try:
191217
result = deep_extract(content, path_inside)
192218
except Exception as e: # pragma: no cover.
193-
sys.exit(str(f"Error when running deep search on {path}: {e}")) # pragma: no cover.
219+
if debug: # pragma: no cover.
220+
raise # pragma: no cover.
221+
else: # pragma: no cover.
222+
sys.exit(str(f"Error when running deep search on {path}: {e}")) # pragma: no cover.
194223
pprint(result, indent=2)

deepdiff/delta.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,16 @@ def dump(self, file):
512512
"""
513513
Dump into file object
514514
"""
515-
file.write(self.dumps())
515+
# Small optimization: Our internal pickle serializer can just take a file object
516+
# and directly write to it. However if a user defined serializer is passed
517+
# we want to make it compatible with the expectation that self.serializer(self.diff)
518+
# will give the user the serialization and then it can be written to
519+
# a file object when using the dump(file) function.
520+
param_names_of_serializer = set(self.serializer.__code__.co_varnames)
521+
if 'file_obj' in param_names_of_serializer:
522+
self.serializer(self.diff, file_obj=file)
523+
else:
524+
file.write(self.dumps())
516525

517526
def dumps(self):
518527
"""

deepdiff/serialization.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class UnsupportedFormatErr(TypeError):
3939
pass
4040

4141

42+
NONE_TYPE = type(None)
43+
4244
CSV_HEADER_MAX_CHUNK_SIZE = 2048 # The chunk needs to be big enough that covers a couple of rows of data.
4345

4446

@@ -254,10 +256,40 @@ def find_class(self, module, name):
254256
# Forbid everything else.
255257
raise ForbiddenModule(FORBIDDEN_MODULE_MSG.format(module_dot_class)) from None
256258

259+
def persistent_load(self, persistent_id):
260+
if persistent_id == "<<NoneType>>":
261+
return type(None)
262+
263+
264+
class _RestrictedPickler(pickle.Pickler):
265+
def persistent_id(self, obj):
266+
if obj is NONE_TYPE: # NOQA
267+
return "<<NoneType>>"
268+
return None
269+
257270

258-
def pickle_dump(obj):
271+
def pickle_dump(obj, file_obj=None):
272+
"""
273+
**pickle_dump**
274+
Dumps the obj into pickled content.
275+
276+
**Parameters**
277+
278+
obj : Any python object
279+
280+
file_obj : (Optional) A file object to dump the contents into
281+
282+
**Returns**
283+
284+
If file_obj is passed the return value will be None. It will write the object's pickle contents into the file.
285+
However if no file_obj is passed, then it will return the pickle serialization of the obj in the form of bytes.
286+
"""
287+
file_obj_passed = bool(file_obj)
288+
file_obj = file_obj or io.BytesIO()
259289
# We expect at least python 3.5 so protocol 4 is good.
260-
return pickle.dumps(obj, protocol=4, fix_imports=False)
290+
_RestrictedPickler(file_obj, protocol=4, fix_imports=False).dump(obj)
291+
if not file_obj_passed:
292+
return file_obj.getvalue()
261293

262294

263295
def pickle_load(content, safe_to_import=None):
@@ -406,8 +438,7 @@ def _save_content(content, path, file_type, keep_backup=True):
406438
content = toml.dump(content, the_file)
407439
elif file_type == 'pickle':
408440
with open(path, 'wb') as the_file:
409-
content = pickle_dump(content)
410-
the_file.write(content)
441+
content = pickle_dump(content, file_obj=the_file)
411442
elif file_type in {'csv', 'tsv'}:
412443
if clevercsv is None: # pragma: no cover.
413444
raise ImportError('CleverCSV needs to be installed.') # pragma: no cover.

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Changelog
55

66
DeepDiff Changelog
77

8+
- v5-2-2: Fixed Delta serialization when None type is present.
89
- v5-2-0: Removed Murmur3 as the preferred hashing method. Using SHA256 by default now. Added commandline for deepdiff. Added group_by. Added math_epsilon. Improved ignoring of NoneType.
910
- v5-0-2: Bug Fix NoneType in ignore type groups https://github.com/seperman/deepdiff/issues/207
1011
- v5-0-1: Bug fix to not apply format to non numbers.

docs/delta.rst

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,21 @@ At the time of writing this document, this list consists of:
331331

332332
If you want to pass any other argument to safe_to_import, you will need to put the full path to the type as it appears in the sys.modules
333333

334-
For example let's say you have a package call mypackage and has a module called mymodule. If you check the sys.modules, the address to this module must be mypackage.mymodule. In order for Delta to be able to serialize this object, first of all it has to be `picklable <https://docs.python.org/3/library/pickle.html#object.__reduce__>`_. Then you can pass:
334+
For example let's say you have a package call mypackage and has a module called mymodule. If you check the sys.modules, the address to this module must be mypackage.mymodule. In order for Delta to be able to serialize this object via pickle, first of all it has to be `picklable <https://docs.python.org/3/library/pickle.html#object.__reduce__>`_.
335335

336-
>>> delta = Delta(t1, t2, safe_to_import={'mypackage.mymodule'})
336+
>>> diff = DeepDiff(t1, t2)
337+
>>> delta = Delta(diff)
338+
>>> dump = delta.dumps()
339+
340+
The dump at this point is serialized via Pickle and can be written to disc if needed.
341+
342+
Later when you want to load this dump, by default Delta will block you from importing anything that is NOT in deepdiff.serialization.SAFE_TO_IMPORT . In fact it will show you this error message when trying to load this dump:
343+
344+
deepdiff.serialization.ForbiddenModule: Module 'builtins.type' is forbidden. You need to explicitly pass it by passing a safe_to_import parameter
345+
346+
In order to let Delta know that this specific module is safe to import, you will need to pass it to Delta during loading of this dump:
347+
348+
>>> delta = Delta(dump, safe_to_import={'mypackage.mymodule'})
337349

338350
.. _delta_verify_symmetry_label:
339351

tests/test_delta.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,3 +1268,11 @@ def test_ignore_order_but_not_report_repetition(self):
12681268
Delta(DeepDiff(t1, t2, ignore_order=True))
12691269

12701270
assert DELTA_IGNORE_ORDER_NEEDS_REPETITION_REPORT == str(excinfo.value)
1271+
1272+
def test_none_in_delta_object(self):
1273+
t1 = {"a": None}
1274+
t2 = {"a": 1}
1275+
1276+
dump = Delta(DeepDiff(t1, t2)).dumps()
1277+
delta = Delta(dump)
1278+
assert t2 == delta + t1

0 commit comments

Comments
 (0)