localstack-snapshot 0.1.1__tar.gz → 0.3.0__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.
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/PKG-INFO +13 -4
- localstack_snapshot-0.3.0/README.md +35 -0
- localstack_snapshot-0.3.0/localstack_snapshot/__init__.py +1 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/prototype.py +23 -5
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/transformer.py +133 -8
- localstack_snapshot-0.3.0/localstack_snapshot/snapshots/transformer_utility.py +137 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/PKG-INFO +13 -4
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/requires.txt +0 -1
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/pyproject.toml +1 -2
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/tests/test_snapshots.py +50 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/tests/test_transformer.py +152 -0
- localstack-snapshot-0.1.1/README.md +0 -26
- localstack-snapshot-0.1.1/localstack_snapshot/__init__.py +0 -1
- localstack-snapshot-0.1.1/localstack_snapshot/snapshots/transformer_utility.py +0 -69
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/LICENSE +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/pytest/__init__.py +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/pytest/snapshot.py +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/__init__.py +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/report.py +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/util/__init__.py +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/util/encoding.py +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/SOURCES.txt +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/dependency_links.txt +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/top_level.txt +0 -0
- {localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: localstack-snapshot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Extracted snapshot testing lib for LocalStack
|
|
5
5
|
Author-email: LocalStack Contributors <info@localstack.cloud>
|
|
6
6
|
License:
|
|
@@ -217,12 +217,12 @@ Description-Content-Type: text/markdown
|
|
|
217
217
|
License-File: LICENSE
|
|
218
218
|
Requires-Dist: jsonpath-ng>1.6
|
|
219
219
|
Requires-Dist: deepdiff
|
|
220
|
-
Requires-Dist: botocore
|
|
221
220
|
Provides-Extra: dev
|
|
222
221
|
Requires-Dist: black==23.10.0; extra == "dev"
|
|
223
222
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
224
223
|
Requires-Dist: coverage[toml]>=5.0.0; extra == "dev"
|
|
225
224
|
Requires-Dist: ruff==0.1.0; extra == "dev"
|
|
225
|
+
Dynamic: license-file
|
|
226
226
|
|
|
227
227
|
Snapshot testing for pytest
|
|
228
228
|
===============================
|
|
@@ -234,10 +234,19 @@ This project is in a very early stage and will be both restructured and renamed.
|
|
|
234
234
|
|
|
235
235
|
## Quickstart
|
|
236
236
|
|
|
237
|
-
|
|
237
|
+
To install the python and other developer requirements into a venv run:
|
|
238
238
|
|
|
239
239
|
make install
|
|
240
240
|
|
|
241
|
+
### Configuration options
|
|
242
|
+
|
|
243
|
+
There's a few env vars that can be used with this project:
|
|
244
|
+
|
|
245
|
+
* `TEST_TARGET`: Set to `AWS_CLOUD` to use an externally-deployed instance when running tests.
|
|
246
|
+
* `SNAPSHOT_LEGACY_REPORT`: By default set to `0`. Can be set to `1`. This enables the legacy reporting output style.
|
|
247
|
+
* `SNAPSHOT_UDPATE`: By default set to `0`. Can be set to `1`. This enables updating the snapshot file rather than comparing with its contents.
|
|
248
|
+
* `SNAPSHOT_RAW`: By default set to `0`. Can be set to `1`. This outputs an additional snapshot file which contains the untransformed values.
|
|
249
|
+
|
|
241
250
|
## Format code
|
|
242
251
|
|
|
243
252
|
We use black and isort as code style tools.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Snapshot testing for pytest
|
|
2
|
+
===============================
|
|
3
|
+
|
|
4
|
+
Extracted snapshot testing lib for LocalStack.
|
|
5
|
+
|
|
6
|
+
This project is in a very early stage and will be both restructured and renamed.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
To install the python and other developer requirements into a venv run:
|
|
12
|
+
|
|
13
|
+
make install
|
|
14
|
+
|
|
15
|
+
### Configuration options
|
|
16
|
+
|
|
17
|
+
There's a few env vars that can be used with this project:
|
|
18
|
+
|
|
19
|
+
* `TEST_TARGET`: Set to `AWS_CLOUD` to use an externally-deployed instance when running tests.
|
|
20
|
+
* `SNAPSHOT_LEGACY_REPORT`: By default set to `0`. Can be set to `1`. This enables the legacy reporting output style.
|
|
21
|
+
* `SNAPSHOT_UDPATE`: By default set to `0`. Can be set to `1`. This enables updating the snapshot file rather than comparing with its contents.
|
|
22
|
+
* `SNAPSHOT_RAW`: By default set to `0`. Can be set to `1`. This outputs an additional snapshot file which contains the untransformed values.
|
|
23
|
+
|
|
24
|
+
## Format code
|
|
25
|
+
|
|
26
|
+
We use black and isort as code style tools.
|
|
27
|
+
To execute them, run:
|
|
28
|
+
|
|
29
|
+
make format
|
|
30
|
+
|
|
31
|
+
## Build distribution
|
|
32
|
+
|
|
33
|
+
To build a wheel and source distribution, simply run
|
|
34
|
+
|
|
35
|
+
make dist
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/prototype.py
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import io
|
|
1
2
|
import json
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
@@ -7,7 +8,6 @@ from pathlib import Path
|
|
|
7
8
|
from re import Pattern
|
|
8
9
|
from typing import Dict, List, Optional
|
|
9
10
|
|
|
10
|
-
from botocore.response import StreamingBody
|
|
11
11
|
from deepdiff import DeepDiff
|
|
12
12
|
from jsonpath_ng import DatumInContext
|
|
13
13
|
from jsonpath_ng.ext import parse
|
|
@@ -164,6 +164,23 @@ class SnapshotSession:
|
|
|
164
164
|
def _update(self, key: str, obj_state: dict) -> None:
|
|
165
165
|
self.observed_state[key] = obj_state
|
|
166
166
|
|
|
167
|
+
def match_object(self, key: str, obj: object) -> None:
|
|
168
|
+
def _convert_object_to_dict(obj_):
|
|
169
|
+
if isinstance(obj_, dict):
|
|
170
|
+
for key in list(obj_.keys()):
|
|
171
|
+
if key.startswith("_"):
|
|
172
|
+
del obj_[key]
|
|
173
|
+
else:
|
|
174
|
+
obj_[key] = _convert_object_to_dict(obj_[key])
|
|
175
|
+
elif isinstance(obj_, list):
|
|
176
|
+
for idx, val in enumerate(obj_):
|
|
177
|
+
obj_[idx] = _convert_object_to_dict(val)
|
|
178
|
+
elif hasattr(obj_, "__dict__"):
|
|
179
|
+
return _convert_object_to_dict(obj_.__dict__)
|
|
180
|
+
return obj_
|
|
181
|
+
|
|
182
|
+
return self.match(key, _convert_object_to_dict(obj))
|
|
183
|
+
|
|
167
184
|
def match(self, key: str, obj: dict) -> None:
|
|
168
185
|
if key in self.called_keys:
|
|
169
186
|
raise Exception(
|
|
@@ -243,11 +260,10 @@ class SnapshotSession:
|
|
|
243
260
|
def _transform_dict_to_parseable_values(self, original):
|
|
244
261
|
"""recursively goes through dict and tries to resolve values to strings (& parse them as json if possible)"""
|
|
245
262
|
for k, v in original.items():
|
|
246
|
-
if isinstance(v,
|
|
263
|
+
if isinstance(v, io.IOBase):
|
|
247
264
|
# update v for json parsing below
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
) # TODO: patch boto client so this doesn't break any further read() calls
|
|
265
|
+
# TODO: patch boto client so this doesn't break any further read() calls
|
|
266
|
+
original[k] = v = v.read().decode("utf-8")
|
|
251
267
|
if isinstance(v, list) and v:
|
|
252
268
|
for item in v:
|
|
253
269
|
if isinstance(item, dict):
|
|
@@ -256,6 +272,8 @@ class SnapshotSession:
|
|
|
256
272
|
self._transform_dict_to_parseable_values(v)
|
|
257
273
|
|
|
258
274
|
if isinstance(v, str) and v.startswith("{"):
|
|
275
|
+
# Doesn't handle JSON arrays and nested JSON strings. See JsonStringTransformer.
|
|
276
|
+
# TODO for the major release consider having JSON parsing in one place only: either here or in JsonStringTransformer
|
|
259
277
|
try:
|
|
260
278
|
json_value = json.loads(v)
|
|
261
279
|
original[k] = json_value
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/transformer.py
RENAMED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import copy
|
|
2
|
+
import json
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
4
5
|
import re
|
|
5
6
|
from datetime import datetime
|
|
7
|
+
from json import JSONDecodeError
|
|
6
8
|
from re import Pattern
|
|
7
9
|
from typing import Any, Callable, Optional, Protocol
|
|
8
10
|
|
|
@@ -16,6 +18,10 @@ SNAPSHOT_LOGGER.setLevel(logging.DEBUG if os.environ.get("DEBUG_SNAPSHOT") else
|
|
|
16
18
|
GlobalReplacementFn = Callable[[str], str]
|
|
17
19
|
|
|
18
20
|
|
|
21
|
+
class TransformerException(Exception):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
19
25
|
class TransformContext:
|
|
20
26
|
_cache: dict
|
|
21
27
|
replacements: list[GlobalReplacementFn]
|
|
@@ -43,6 +49,19 @@ class TransformContext:
|
|
|
43
49
|
def _register_serialized_reference_replacement(
|
|
44
50
|
transform_context: TransformContext, *, reference_value: str, replacement: str
|
|
45
51
|
):
|
|
52
|
+
# Provide a better error message for the TypeError if the reference value is not iterable (e.g., float)
|
|
53
|
+
# Example: `TypeError: argument of type 'float' is not iterable`
|
|
54
|
+
# Using a list would throw an AttributeError because `.replace` is not applicable.
|
|
55
|
+
# The snapshot library currently only supports strings for reference replacements
|
|
56
|
+
if not isinstance(reference_value, str):
|
|
57
|
+
message = (
|
|
58
|
+
f"The reference value {reference_value} of type {type(reference_value)} is not a string."
|
|
59
|
+
f" Consider using `reference_replacement=False` in your transformer"
|
|
60
|
+
f" for the replacement {replacement} because reference replacements are only supported for strings."
|
|
61
|
+
)
|
|
62
|
+
SNAPSHOT_LOGGER.error(message)
|
|
63
|
+
raise TransformerException(message)
|
|
64
|
+
|
|
46
65
|
if '"' in reference_value:
|
|
47
66
|
reference_value = reference_value.replace('"', '\\"')
|
|
48
67
|
|
|
@@ -167,35 +186,36 @@ class RegexTransformer:
|
|
|
167
186
|
return input_data
|
|
168
187
|
|
|
169
188
|
|
|
170
|
-
class
|
|
189
|
+
class KeyValueBasedTransformerFunctionReplacement:
|
|
171
190
|
def __init__(
|
|
172
191
|
self,
|
|
173
192
|
match_fn: Callable[[str, Any], Optional[str]],
|
|
174
|
-
|
|
193
|
+
replacement_function: [Callable[[str, Any], str]],
|
|
175
194
|
replace_reference: bool = True,
|
|
176
195
|
):
|
|
177
196
|
self.match_fn = match_fn
|
|
178
|
-
self.
|
|
197
|
+
self.replacement_function = replacement_function
|
|
179
198
|
self.replace_reference = replace_reference
|
|
180
199
|
|
|
181
200
|
def transform(self, input_data: dict, *, ctx: TransformContext) -> dict:
|
|
182
201
|
for k, v in input_data.items():
|
|
183
202
|
if (match_result := self.match_fn(k, v)) is not None:
|
|
203
|
+
replacement = self.replacement_function(k, v)
|
|
184
204
|
if self.replace_reference:
|
|
185
205
|
_register_serialized_reference_replacement(
|
|
186
|
-
ctx, reference_value=match_result, replacement=
|
|
206
|
+
ctx, reference_value=match_result, replacement=replacement
|
|
187
207
|
)
|
|
188
208
|
else:
|
|
189
209
|
if isinstance(v, str):
|
|
190
210
|
SNAPSHOT_LOGGER.debug(
|
|
191
|
-
f"Replacing value for key '{k}': Match result '{match_result:.200s}' with '{
|
|
211
|
+
f"Replacing value for key '{k}': Match result '{match_result:.200s}' with '{replacement}'. (Original value: {str(v)})"
|
|
192
212
|
)
|
|
193
|
-
input_data[k] = v.replace(match_result,
|
|
213
|
+
input_data[k] = v.replace(match_result, replacement)
|
|
194
214
|
else:
|
|
195
215
|
SNAPSHOT_LOGGER.debug(
|
|
196
|
-
f"Replacing value for key '{k}' with '{
|
|
216
|
+
f"Replacing value for key '{k}' with '{replacement}'. (Original value: {str(v)})"
|
|
197
217
|
)
|
|
198
|
-
input_data[k] =
|
|
218
|
+
input_data[k] = replacement
|
|
199
219
|
elif isinstance(v, list) and len(v) > 0:
|
|
200
220
|
for i in range(0, len(v)):
|
|
201
221
|
if isinstance(v[i], dict):
|
|
@@ -206,6 +226,20 @@ class KeyValueBasedTransformer:
|
|
|
206
226
|
return input_data
|
|
207
227
|
|
|
208
228
|
|
|
229
|
+
class KeyValueBasedTransformer(KeyValueBasedTransformerFunctionReplacement):
|
|
230
|
+
def __init__(
|
|
231
|
+
self,
|
|
232
|
+
match_fn: Callable[[str, Any], Optional[str]],
|
|
233
|
+
replacement: str,
|
|
234
|
+
replace_reference: bool = True,
|
|
235
|
+
):
|
|
236
|
+
super().__init__(
|
|
237
|
+
match_fn=match_fn,
|
|
238
|
+
replacement_function=lambda k, v: replacement,
|
|
239
|
+
replace_reference=replace_reference,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
209
243
|
class GenericTransformer:
|
|
210
244
|
def __init__(self, fn: Callable[[dict, TransformContext], dict]):
|
|
211
245
|
self.fn = fn
|
|
@@ -327,3 +361,94 @@ class TimestampTransformer:
|
|
|
327
361
|
|
|
328
362
|
def _transform_list(self, input_data: list, ctx: TransformContext = None) -> list:
|
|
329
363
|
return [self._transform(e, ctx=ctx) for e in input_data]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class TextTransformer:
|
|
367
|
+
def __init__(self, text: str, replacement: str):
|
|
368
|
+
self.text = text
|
|
369
|
+
self.replacement = replacement
|
|
370
|
+
|
|
371
|
+
def transform(self, input_data: dict, *, ctx: TransformContext) -> dict:
|
|
372
|
+
def replace_val(s):
|
|
373
|
+
return s.replace(self.text, self.replacement)
|
|
374
|
+
|
|
375
|
+
ctx.register_serialized_replacement(replace_val)
|
|
376
|
+
SNAPSHOT_LOGGER.debug(
|
|
377
|
+
f"Registering text pattern '{self.text}' in snapshot with '{self.replacement}'"
|
|
378
|
+
)
|
|
379
|
+
return input_data
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class JsonStringTransformer:
|
|
383
|
+
"""
|
|
384
|
+
Parses JSON string at the specified key.
|
|
385
|
+
Additionally, attempts to parse any JSON strings inside the parsed JSON
|
|
386
|
+
|
|
387
|
+
This transformer complements the default parsing of JSON strings in
|
|
388
|
+
localstack_snapshot.snapshots.prototype.SnapshotSession._transform_dict_to_parseable_values
|
|
389
|
+
|
|
390
|
+
Shortcomings of the default parser that this transformer addresses:
|
|
391
|
+
- parsing of nested JSON strings '{"a": "{\\"b\\":42}"}'
|
|
392
|
+
- parsing of JSON arrays at the specified key, e.g. '["a", "b"]'
|
|
393
|
+
|
|
394
|
+
Such parsing allows applying transformations further to the elements of the parsed JSON - timestamps, ARNs, etc.
|
|
395
|
+
|
|
396
|
+
Such parsing is not done by default because it's not a common use case.
|
|
397
|
+
Whether to parse a JSON string or not should be decided by the user on a case by case basis.
|
|
398
|
+
Limited general parsing that we already have is preserved for backwards compatibility.
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
key: str
|
|
402
|
+
|
|
403
|
+
def __init__(self, key: str):
|
|
404
|
+
self.key = key
|
|
405
|
+
|
|
406
|
+
def transform(self, input_data: dict, *, ctx: TransformContext = None) -> dict:
|
|
407
|
+
return self._transform_dict(input_data, ctx=ctx)
|
|
408
|
+
|
|
409
|
+
def _transform(self, input_data: Any, ctx: TransformContext = None) -> Any:
|
|
410
|
+
if isinstance(input_data, dict):
|
|
411
|
+
return self._transform_dict(input_data, ctx=ctx)
|
|
412
|
+
elif isinstance(input_data, list):
|
|
413
|
+
return self._transform_list(input_data, ctx=ctx)
|
|
414
|
+
return input_data
|
|
415
|
+
|
|
416
|
+
def _transform_dict(self, input_data: dict, ctx: TransformContext = None) -> dict:
|
|
417
|
+
for k, v in input_data.items():
|
|
418
|
+
if k == self.key and isinstance(v, str) and v.strip().startswith(("{", "[")):
|
|
419
|
+
try:
|
|
420
|
+
SNAPSHOT_LOGGER.debug(f"Replacing string value of {k} with parsed JSON")
|
|
421
|
+
json_value = json.loads(v)
|
|
422
|
+
input_data[k] = self._transform_nested(json_value)
|
|
423
|
+
except JSONDecodeError:
|
|
424
|
+
SNAPSHOT_LOGGER.exception(
|
|
425
|
+
f'Value mapped to "{k}" key is not a valid JSON string and won\'t be transformed. Value: {v}'
|
|
426
|
+
)
|
|
427
|
+
else:
|
|
428
|
+
input_data[k] = self._transform(v, ctx=ctx)
|
|
429
|
+
return input_data
|
|
430
|
+
|
|
431
|
+
def _transform_list(self, input_data: list, ctx: TransformContext = None) -> list:
|
|
432
|
+
return [self._transform(item, ctx=ctx) for item in input_data]
|
|
433
|
+
|
|
434
|
+
def _transform_nested(self, input_data: Any) -> Any:
|
|
435
|
+
"""
|
|
436
|
+
Separate method from the main `_transform_dict` one because
|
|
437
|
+
it checks every string while the main one attempts to load at specified key only.
|
|
438
|
+
This one is implicit, best-effort attempt,
|
|
439
|
+
while the main one is explicit about at which key transform should happen
|
|
440
|
+
"""
|
|
441
|
+
if isinstance(input_data, list):
|
|
442
|
+
input_data = [self._transform_nested(item) for item in input_data]
|
|
443
|
+
if isinstance(input_data, dict):
|
|
444
|
+
for k, v in input_data.items():
|
|
445
|
+
input_data[k] = self._transform_nested(v)
|
|
446
|
+
if isinstance(input_data, str) and input_data.strip().startswith(("{", "[")):
|
|
447
|
+
try:
|
|
448
|
+
json_value = json.loads(input_data)
|
|
449
|
+
input_data = self._transform_nested(json_value)
|
|
450
|
+
except JSONDecodeError:
|
|
451
|
+
SNAPSHOT_LOGGER.debug(
|
|
452
|
+
f"The value is not a valid JSON string and won't be transformed. The value: {input_data}"
|
|
453
|
+
)
|
|
454
|
+
return input_data
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from re import Pattern
|
|
2
|
+
from typing import Any, Callable, Optional
|
|
3
|
+
|
|
4
|
+
from localstack_snapshot.snapshots.transformer import (
|
|
5
|
+
JsonpathTransformer,
|
|
6
|
+
JsonStringTransformer,
|
|
7
|
+
KeyValueBasedTransformer,
|
|
8
|
+
KeyValueBasedTransformerFunctionReplacement,
|
|
9
|
+
RegexTransformer,
|
|
10
|
+
SortingTransformer,
|
|
11
|
+
TextTransformer,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _replace_camel_string_with_hyphen(input_string: str):
|
|
16
|
+
return "".join(["-" + char.lower() if char.isupper() else char for char in input_string]).strip(
|
|
17
|
+
"-"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TransformerUtility:
|
|
22
|
+
@staticmethod
|
|
23
|
+
def key_value(
|
|
24
|
+
key: str, value_replacement: Optional[str] = None, reference_replacement: bool = True
|
|
25
|
+
):
|
|
26
|
+
"""Creates a new KeyValueBasedTransformer. If the key matches, the value will be replaced.
|
|
27
|
+
|
|
28
|
+
:param key: the name of the key which should be replaced
|
|
29
|
+
:param value_replacement: the value which will replace the original value.
|
|
30
|
+
By default it is the key-name in lowercase, separated with hyphen
|
|
31
|
+
:param reference_replacement: if False, only the original value for this key will be replaced.
|
|
32
|
+
If True all references of this value will be replaced (using a regex pattern), for the entire test case.
|
|
33
|
+
In this case, the replaced value will be nummerated as well.
|
|
34
|
+
Default: True
|
|
35
|
+
|
|
36
|
+
:return: KeyValueBasedTransformer
|
|
37
|
+
"""
|
|
38
|
+
return KeyValueBasedTransformer(
|
|
39
|
+
lambda k, v: v if k == key and (v is not None and v != "") else None,
|
|
40
|
+
replacement=value_replacement or _replace_camel_string_with_hyphen(key),
|
|
41
|
+
replace_reference=reference_replacement,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def key_value_replacement_function(
|
|
46
|
+
key: str,
|
|
47
|
+
replacement_function: Callable[[str, Any], str] = None,
|
|
48
|
+
reference_replacement: bool = True,
|
|
49
|
+
):
|
|
50
|
+
"""Creates a new KeyValueBasedTransformer. If the key matches, the value will be replaced.
|
|
51
|
+
|
|
52
|
+
:param key: the name of the key which should be replaced
|
|
53
|
+
:param replacement_function: The function calculating the replacement. Will be passed the key and value of the replaced pair.
|
|
54
|
+
By default it is the key-name in lowercase, separated with hyphen
|
|
55
|
+
:param reference_replacement: if False, only the original value for this key will be replaced.
|
|
56
|
+
If True all references of this value will be replaced (using a regex pattern), for the entire test case.
|
|
57
|
+
In this case, the replaced value will be nummerated as well.
|
|
58
|
+
Default: True
|
|
59
|
+
|
|
60
|
+
:return: KeyValueBasedTransformer
|
|
61
|
+
"""
|
|
62
|
+
replacement_function = replacement_function or (
|
|
63
|
+
lambda x, y: _replace_camel_string_with_hyphen(key)
|
|
64
|
+
)
|
|
65
|
+
return KeyValueBasedTransformerFunctionReplacement(
|
|
66
|
+
lambda k, v: v if k == key and (v is not None and v != "") else None,
|
|
67
|
+
replacement_function=replacement_function,
|
|
68
|
+
replace_reference=reference_replacement,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def jsonpath(jsonpath: str, value_replacement: str, reference_replacement: bool = True):
|
|
73
|
+
"""Creates a new JsonpathTransformer. If the jsonpath matches, the value will be replaced.
|
|
74
|
+
|
|
75
|
+
:param jsonpath: the jsonpath that should be matched
|
|
76
|
+
:param value_replacement: the value which will replace the original value.
|
|
77
|
+
By default it is the key-name in lowercase, separated with hyphen
|
|
78
|
+
:param reference_replacement: if False, only the original value for this key will be replaced.
|
|
79
|
+
If True all references of this value will be replaced (using a regex pattern), for the entire test case.
|
|
80
|
+
In this case, the replaced value will be nummerated as well.
|
|
81
|
+
Default: True
|
|
82
|
+
|
|
83
|
+
:return: JsonpathTransformer
|
|
84
|
+
"""
|
|
85
|
+
return JsonpathTransformer(
|
|
86
|
+
jsonpath=jsonpath,
|
|
87
|
+
replacement=value_replacement,
|
|
88
|
+
replace_reference=reference_replacement,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def regex(regex: str | Pattern[str], replacement: str):
|
|
93
|
+
"""Creates a new RegexTransformer. All matches in the string-converted dict will be replaced.
|
|
94
|
+
|
|
95
|
+
:param regex: the regex that should be matched
|
|
96
|
+
:param replacement: the value which will replace the original value.
|
|
97
|
+
|
|
98
|
+
:return: RegexTransformer
|
|
99
|
+
"""
|
|
100
|
+
return RegexTransformer(regex, replacement)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def text(text: str, replacement: str):
|
|
104
|
+
"""Creates a new TextTransformer. All occurrences in the string-converted dict will be replaced.
|
|
105
|
+
|
|
106
|
+
Useful if the text contains special characters that would confuse the RegexTransformer, like '+' or '('.
|
|
107
|
+
|
|
108
|
+
:param text: the text that should be replaced
|
|
109
|
+
:param replacement: the value which will replace the original value.
|
|
110
|
+
|
|
111
|
+
:return: TextTransformer
|
|
112
|
+
"""
|
|
113
|
+
return TextTransformer(text, replacement)
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def json_string(key: str) -> JsonStringTransformer:
|
|
117
|
+
"""Creates a new JsonStringTransformer. If there is a valid JSON text string at specified key
|
|
118
|
+
it will be loaded as a regular object or array.
|
|
119
|
+
|
|
120
|
+
:param key: key at which JSON string is expected
|
|
121
|
+
|
|
122
|
+
:return: JsonStringTransformer
|
|
123
|
+
"""
|
|
124
|
+
return JsonStringTransformer(key)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def sorting(key: str, sorting_fn: Optional[Callable[[...], Any]]) -> SortingTransformer:
|
|
128
|
+
"""Creates a new SortingTransformer.
|
|
129
|
+
|
|
130
|
+
Sorts a list at `key` with the given `sorting_fn` (argument for `sorted(list, key=sorting_fn)`)
|
|
131
|
+
|
|
132
|
+
:param key: key at which the list to sort is expected
|
|
133
|
+
:param sorting_fn: sorting function
|
|
134
|
+
|
|
135
|
+
:return: SortingTransformer
|
|
136
|
+
"""
|
|
137
|
+
return SortingTransformer(key, sorting_fn)
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: localstack-snapshot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Extracted snapshot testing lib for LocalStack
|
|
5
5
|
Author-email: LocalStack Contributors <info@localstack.cloud>
|
|
6
6
|
License:
|
|
@@ -217,12 +217,12 @@ Description-Content-Type: text/markdown
|
|
|
217
217
|
License-File: LICENSE
|
|
218
218
|
Requires-Dist: jsonpath-ng>1.6
|
|
219
219
|
Requires-Dist: deepdiff
|
|
220
|
-
Requires-Dist: botocore
|
|
221
220
|
Provides-Extra: dev
|
|
222
221
|
Requires-Dist: black==23.10.0; extra == "dev"
|
|
223
222
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
224
223
|
Requires-Dist: coverage[toml]>=5.0.0; extra == "dev"
|
|
225
224
|
Requires-Dist: ruff==0.1.0; extra == "dev"
|
|
225
|
+
Dynamic: license-file
|
|
226
226
|
|
|
227
227
|
Snapshot testing for pytest
|
|
228
228
|
===============================
|
|
@@ -234,10 +234,19 @@ This project is in a very early stage and will be both restructured and renamed.
|
|
|
234
234
|
|
|
235
235
|
## Quickstart
|
|
236
236
|
|
|
237
|
-
|
|
237
|
+
To install the python and other developer requirements into a venv run:
|
|
238
238
|
|
|
239
239
|
make install
|
|
240
240
|
|
|
241
|
+
### Configuration options
|
|
242
|
+
|
|
243
|
+
There's a few env vars that can be used with this project:
|
|
244
|
+
|
|
245
|
+
* `TEST_TARGET`: Set to `AWS_CLOUD` to use an externally-deployed instance when running tests.
|
|
246
|
+
* `SNAPSHOT_LEGACY_REPORT`: By default set to `0`. Can be set to `1`. This enables the legacy reporting output style.
|
|
247
|
+
* `SNAPSHOT_UDPATE`: By default set to `0`. Can be set to `1`. This enables updating the snapshot file rather than comparing with its contents.
|
|
248
|
+
* `SNAPSHOT_RAW`: By default set to `0`. Can be set to `1`. This outputs an additional snapshot file which contains the untransformed values.
|
|
249
|
+
|
|
241
250
|
## Format code
|
|
242
251
|
|
|
243
252
|
We use black and isort as code style tools.
|
|
@@ -7,12 +7,11 @@ name = "localstack-snapshot"
|
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "LocalStack Contributors", email = "info@localstack.cloud" }
|
|
9
9
|
]
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "0.3.0"
|
|
11
11
|
description = "Extracted snapshot testing lib for LocalStack"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"jsonpath-ng>1.6",
|
|
14
14
|
"deepdiff",
|
|
15
|
-
"botocore",
|
|
16
15
|
]
|
|
17
16
|
requires-python = ">=3.10"
|
|
18
17
|
license = {file = "LICENSE"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import io
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
|
|
3
5
|
from localstack_snapshot.snapshots import SnapshotSession
|
|
@@ -20,6 +22,12 @@ class TestSnapshotManager:
|
|
|
20
22
|
sm._assert_all()
|
|
21
23
|
ctx.match("Parity snapshot failed")
|
|
22
24
|
|
|
25
|
+
def test_diff_with_io_stream(self):
|
|
26
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
27
|
+
sm.recorded_state = {"key_a": {"a": "data"}}
|
|
28
|
+
sm.match("key_a", {"a": io.BytesIO(b"data")})
|
|
29
|
+
sm._assert_all()
|
|
30
|
+
|
|
23
31
|
def test_multiple_assertmatch_with_same_key_fail(self):
|
|
24
32
|
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
25
33
|
sm.recorded_state = {"key_a": {"a": 3}}
|
|
@@ -37,6 +45,48 @@ class TestSnapshotManager:
|
|
|
37
45
|
sm.match("key_a", {"aaa": "something", "bbb": "something hello"})
|
|
38
46
|
sm._assert_all()
|
|
39
47
|
|
|
48
|
+
def test_match_object_nochange(self):
|
|
49
|
+
class CustomObject:
|
|
50
|
+
def __init__(self, name, nested=False):
|
|
51
|
+
self.name = name
|
|
52
|
+
if nested:
|
|
53
|
+
self.nested = CustomObject(f"nested{name}")
|
|
54
|
+
self.listed = [CustomObject(f"listed{name}"), "otherobj"]
|
|
55
|
+
|
|
56
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
57
|
+
sm.recorded_state = {
|
|
58
|
+
"key_a": {
|
|
59
|
+
"name": "myname",
|
|
60
|
+
"nested": {"name": "nestedmyname"},
|
|
61
|
+
"listed": [{"name": "listedmyname"}, "otherobj"],
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
sm.match_object("key_a", CustomObject(name="myname", nested=True))
|
|
65
|
+
sm._assert_all()
|
|
66
|
+
|
|
67
|
+
def test_match_object_ignore_private_values(self):
|
|
68
|
+
class CustomObject:
|
|
69
|
+
def __init__(self, name):
|
|
70
|
+
self.name = name
|
|
71
|
+
self._internal = "n/a"
|
|
72
|
+
|
|
73
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
74
|
+
sm.recorded_state = {"key_a": {"name": "myname"}}
|
|
75
|
+
sm.match_object("key_a", CustomObject(name="myname"))
|
|
76
|
+
sm._assert_all()
|
|
77
|
+
|
|
78
|
+
def test_match_object_change(self):
|
|
79
|
+
class CustomObject:
|
|
80
|
+
def __init__(self, name):
|
|
81
|
+
self.name = name
|
|
82
|
+
|
|
83
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
84
|
+
sm.recorded_state = {"key_a": {"name": "myname"}}
|
|
85
|
+
sm.match_object("key_a", CustomObject(name="diffname"))
|
|
86
|
+
with pytest.raises(Exception) as ctx:
|
|
87
|
+
sm._assert_all()
|
|
88
|
+
ctx.match("Parity snapshot failed")
|
|
89
|
+
|
|
40
90
|
# def test_context_replacement_no_change(self):
|
|
41
91
|
# sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
42
92
|
# sm.add_transformer(TransformerUtility.key_value("name"))
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
6
|
from localstack_snapshot.snapshots.transformer import (
|
|
7
|
+
JsonStringTransformer,
|
|
7
8
|
SortingTransformer,
|
|
8
9
|
TimestampTransformer,
|
|
9
10
|
TransformContext,
|
|
@@ -50,6 +51,81 @@ class TestTransformer:
|
|
|
50
51
|
|
|
51
52
|
assert json.loads(tmp) == expected_key_value_reference
|
|
52
53
|
|
|
54
|
+
def test_key_value_replacement_custom_function(self):
|
|
55
|
+
input = {
|
|
56
|
+
"hello": "12characters",
|
|
57
|
+
"hello2": "again",
|
|
58
|
+
"path": {
|
|
59
|
+
"to": {
|
|
60
|
+
"anotherkey": "hi",
|
|
61
|
+
"twelvesymbol": {"hello": "twelvesymbol"},
|
|
62
|
+
"fifteen_symbols": {"hello": "fifteen_symbols"},
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
key_value = TransformerUtility.key_value_replacement_function(
|
|
68
|
+
"hello",
|
|
69
|
+
replacement_function=lambda k, v: f"placeholder({len(v)})",
|
|
70
|
+
reference_replacement=False,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
expected_key_value = {
|
|
74
|
+
"hello": "placeholder(12)",
|
|
75
|
+
"hello2": "again",
|
|
76
|
+
"path": {
|
|
77
|
+
"to": {
|
|
78
|
+
"anotherkey": "hi",
|
|
79
|
+
"twelvesymbol": {"hello": "placeholder(12)"},
|
|
80
|
+
"fifteen_symbols": {"hello": "placeholder(15)"},
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
ctx = TransformContext()
|
|
86
|
+
assert key_value.transform(input, ctx=ctx) == expected_key_value
|
|
87
|
+
assert ctx.serialized_replacements == []
|
|
88
|
+
|
|
89
|
+
def test_key_value_replacement_custom_function_reference_replacement(self):
|
|
90
|
+
input = {
|
|
91
|
+
"hello": "12characters",
|
|
92
|
+
"hello2": "again",
|
|
93
|
+
"path": {
|
|
94
|
+
"to": {
|
|
95
|
+
"anotherkey": "hi",
|
|
96
|
+
"twelvesymbol": {"hello": "twelvesymbol"},
|
|
97
|
+
"fifteen_symbols": {"hello": "fifteen_symbols"},
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
key_value = TransformerUtility.key_value_replacement_function(
|
|
103
|
+
"hello",
|
|
104
|
+
replacement_function=lambda k, v: f"placeholder({len(v)})",
|
|
105
|
+
reference_replacement=True,
|
|
106
|
+
)
|
|
107
|
+
# replacement counters are per replacement key, so it will start from 1 again.
|
|
108
|
+
expected_key_value_reference = {
|
|
109
|
+
"hello": "<placeholder(12):1>",
|
|
110
|
+
"hello2": "again",
|
|
111
|
+
"path": {
|
|
112
|
+
"to": {
|
|
113
|
+
"anotherkey": "hi",
|
|
114
|
+
"<placeholder(12):2>": {"hello": "<placeholder(12):2>"},
|
|
115
|
+
"<placeholder(15):1>": {"hello": "<placeholder(15):1>"},
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
ctx = TransformContext()
|
|
120
|
+
assert key_value.transform(input, ctx=ctx) == input
|
|
121
|
+
assert len(ctx.serialized_replacements) == 3
|
|
122
|
+
|
|
123
|
+
tmp = json.dumps(input, default=str)
|
|
124
|
+
for sr in ctx.serialized_replacements:
|
|
125
|
+
tmp = sr(tmp)
|
|
126
|
+
|
|
127
|
+
assert json.loads(tmp) == expected_key_value_reference
|
|
128
|
+
|
|
53
129
|
def test_key_value_replacement_with_falsy_value(self):
|
|
54
130
|
input = {
|
|
55
131
|
"hello": "world",
|
|
@@ -211,6 +287,82 @@ class TestTransformer:
|
|
|
211
287
|
output = transformer.transform(input, ctx=ctx)
|
|
212
288
|
assert output == expected
|
|
213
289
|
|
|
290
|
+
@pytest.mark.parametrize(
|
|
291
|
+
"value",
|
|
292
|
+
[
|
|
293
|
+
"a+b",
|
|
294
|
+
"question?",
|
|
295
|
+
"amount: $4.00",
|
|
296
|
+
"emoji: ^^",
|
|
297
|
+
"sentence.",
|
|
298
|
+
"others (like so)",
|
|
299
|
+
"special {char}",
|
|
300
|
+
],
|
|
301
|
+
)
|
|
302
|
+
def test_text(self, value):
|
|
303
|
+
input = {"key": f"some {value} with more text"}
|
|
304
|
+
|
|
305
|
+
expected = {"key": "some <value> with more text"}
|
|
306
|
+
|
|
307
|
+
transformer = TransformerUtility.text(value, "<value>")
|
|
308
|
+
|
|
309
|
+
ctx = TransformContext()
|
|
310
|
+
output = transformer.transform(json.dumps(input), ctx=ctx)
|
|
311
|
+
for sr in ctx.serialized_replacements:
|
|
312
|
+
output = sr(output)
|
|
313
|
+
assert json.loads(output) == expected
|
|
314
|
+
|
|
315
|
+
@pytest.mark.parametrize(
|
|
316
|
+
"input_value,transformed_value",
|
|
317
|
+
[
|
|
318
|
+
pytest.param('{"a": "b"}', {"a": "b"}, id="simple_json_object"),
|
|
319
|
+
pytest.param('{\n "a": "b"\n}', {"a": "b"}, id="formatted_json_object"),
|
|
320
|
+
pytest.param('\n {"a": "b"}', {"a": "b"}, id="json_with_whitespaces"),
|
|
321
|
+
pytest.param('{"a": 42}malformed', '{"a": 42}malformed', id="malformed_json"),
|
|
322
|
+
pytest.param('["a", "b"]', ["a", "b"], id="simple_json_list"),
|
|
323
|
+
pytest.param('{"a": "{\\"b\\":42}"}', {"a": {"b": 42}}, id="nested_json_object"),
|
|
324
|
+
pytest.param(
|
|
325
|
+
'{"a": "\\n {\\n \\"b\\":42}"}',
|
|
326
|
+
{"a": {"b": 42}},
|
|
327
|
+
id="nested_formatted_json_object_with_whitespaces",
|
|
328
|
+
),
|
|
329
|
+
pytest.param(
|
|
330
|
+
'{"a": "[{\\"b\\":\\"c\\"}]"}', {"a": [{"b": "c"}]}, id="nested_json_list"
|
|
331
|
+
),
|
|
332
|
+
pytest.param(
|
|
333
|
+
'{"a": "{\\"b\\":42malformed}"}',
|
|
334
|
+
{"a": '{"b":42malformed}'},
|
|
335
|
+
id="malformed_nested_json",
|
|
336
|
+
),
|
|
337
|
+
pytest.param("[]", [], id="empty_list"),
|
|
338
|
+
pytest.param("{}", {}, id="empty_object"),
|
|
339
|
+
pytest.param("", "", id="empty_string"),
|
|
340
|
+
],
|
|
341
|
+
)
|
|
342
|
+
def test_json_string(self, input_value, transformed_value):
|
|
343
|
+
key = "key"
|
|
344
|
+
input_data = {key: input_value}
|
|
345
|
+
expected = {key: transformed_value}
|
|
346
|
+
|
|
347
|
+
transformer = JsonStringTransformer(key)
|
|
348
|
+
|
|
349
|
+
ctx = TransformContext()
|
|
350
|
+
output = transformer.transform(input_data, ctx=ctx)
|
|
351
|
+
|
|
352
|
+
assert output == expected
|
|
353
|
+
|
|
354
|
+
def test_json_string_in_a_nested_key(self):
|
|
355
|
+
key = "nested-key-in-an-object-hidden-inside-a-list"
|
|
356
|
+
input_data = {"top-level-key": [{key: '{"a": "b"}'}]}
|
|
357
|
+
expected = {"top-level-key": [{key: {"a": "b"}}]}
|
|
358
|
+
|
|
359
|
+
transformer = JsonStringTransformer(key)
|
|
360
|
+
|
|
361
|
+
ctx = TransformContext()
|
|
362
|
+
output = transformer.transform(input_data, ctx=ctx)
|
|
363
|
+
|
|
364
|
+
assert output == expected
|
|
365
|
+
|
|
214
366
|
|
|
215
367
|
class TestTimestampTransformer:
|
|
216
368
|
def test_generic_timestamp_transformer(self):
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
Snapshot testing for pytest
|
|
2
|
-
===============================
|
|
3
|
-
|
|
4
|
-
Extracted snapshot testing lib for LocalStack.
|
|
5
|
-
|
|
6
|
-
This project is in a very early stage and will be both restructured and renamed.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Quickstart
|
|
10
|
-
|
|
11
|
-
to install the python and other developer requirements into a venv run:
|
|
12
|
-
|
|
13
|
-
make install
|
|
14
|
-
|
|
15
|
-
## Format code
|
|
16
|
-
|
|
17
|
-
We use black and isort as code style tools.
|
|
18
|
-
To execute them, run:
|
|
19
|
-
|
|
20
|
-
make format
|
|
21
|
-
|
|
22
|
-
## Build distribution
|
|
23
|
-
|
|
24
|
-
To build a wheel and source distribution, simply run
|
|
25
|
-
|
|
26
|
-
make dist
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.1"
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
from re import Pattern
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
from localstack_snapshot.snapshots.transformer import (
|
|
5
|
-
JsonpathTransformer,
|
|
6
|
-
KeyValueBasedTransformer,
|
|
7
|
-
RegexTransformer,
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _replace_camel_string_with_hyphen(input_string: str):
|
|
12
|
-
return "".join(["-" + char.lower() if char.isupper() else char for char in input_string]).strip(
|
|
13
|
-
"-"
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class TransformerUtility:
|
|
18
|
-
@staticmethod
|
|
19
|
-
def key_value(
|
|
20
|
-
key: str, value_replacement: Optional[str] = None, reference_replacement: bool = True
|
|
21
|
-
):
|
|
22
|
-
"""Creates a new KeyValueBasedTransformer. If the key matches, the value will be replaced.
|
|
23
|
-
|
|
24
|
-
:param key: the name of the key which should be replaced
|
|
25
|
-
:param value_replacement: the value which will replace the original value.
|
|
26
|
-
By default it is the key-name in lowercase, separated with hyphen
|
|
27
|
-
:param reference_replacement: if False, only the original value for this key will be replaced.
|
|
28
|
-
If True all references of this value will be replaced (using a regex pattern), for the entire test case.
|
|
29
|
-
In this case, the replaced value will be nummerated as well.
|
|
30
|
-
Default: True
|
|
31
|
-
|
|
32
|
-
:return: KeyValueBasedTransformer
|
|
33
|
-
"""
|
|
34
|
-
return KeyValueBasedTransformer(
|
|
35
|
-
lambda k, v: v if k == key and (v is not None and v != "") else None,
|
|
36
|
-
replacement=value_replacement or _replace_camel_string_with_hyphen(key),
|
|
37
|
-
replace_reference=reference_replacement,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
@staticmethod
|
|
41
|
-
def jsonpath(jsonpath: str, value_replacement: str, reference_replacement: bool = True):
|
|
42
|
-
"""Creates a new JsonpathTransformer. If the jsonpath matches, the value will be replaced.
|
|
43
|
-
|
|
44
|
-
:param jsonpath: the jsonpath that should be matched
|
|
45
|
-
:param value_replacement: the value which will replace the original value.
|
|
46
|
-
By default it is the key-name in lowercase, separated with hyphen
|
|
47
|
-
:param reference_replacement: if False, only the original value for this key will be replaced.
|
|
48
|
-
If True all references of this value will be replaced (using a regex pattern), for the entire test case.
|
|
49
|
-
In this case, the replaced value will be nummerated as well.
|
|
50
|
-
Default: True
|
|
51
|
-
|
|
52
|
-
:return: JsonpathTransformer
|
|
53
|
-
"""
|
|
54
|
-
return JsonpathTransformer(
|
|
55
|
-
jsonpath=jsonpath,
|
|
56
|
-
replacement=value_replacement,
|
|
57
|
-
replace_reference=reference_replacement,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
@staticmethod
|
|
61
|
-
def regex(regex: str | Pattern[str], replacement: str):
|
|
62
|
-
"""Creates a new RegexTransformer. All matches in the string-converted dict will be replaced.
|
|
63
|
-
|
|
64
|
-
:param regex: the regex that should be matched
|
|
65
|
-
:param replacement: the value which will replace the original value.
|
|
66
|
-
|
|
67
|
-
:return: RegexTransformer
|
|
68
|
-
"""
|
|
69
|
-
return RegexTransformer(regex, replacement)
|
|
File without changes
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/pytest/__init__.py
RENAMED
|
File without changes
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/pytest/snapshot.py
RENAMED
|
File without changes
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/__init__.py
RENAMED
|
File without changes
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/report.py
RENAMED
|
File without changes
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/util/__init__.py
RENAMED
|
File without changes
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot/util/encoding.py
RENAMED
|
File without changes
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{localstack-snapshot-0.1.1 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|