zs-yaml 0.10.1.dev2__tar.gz → 0.10.2.dev3__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.
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/CHANGELOG.md +5 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/PKG-INFO +1 -1
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/team/test_all_conversions.py +100 -1
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml/_version.py +3 -3
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml/convert.py +38 -14
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml/main.py +42 -5
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml.egg-info/PKG-INFO +1 -1
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/.github/workflows/ci.yml +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/.gitignore +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/LICENSE +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/README.md +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/api_docs_review.md +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/doc/zs-yaml.png +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/doc/zs-yaml.svg +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/extern_compression/extern_compression.zs +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/extern_compression/payload.yaml +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/extern_compression/test_compression.py +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/perf/.gitignore +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/perf/benchmark.py +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/perf/generate_perf_yaml.py +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/perf/perf.zs +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/perf/perf_reference.bin +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/perf/perf_reference_dict.sha256 +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/perf/test_fast_path.py +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/team/addresses.yaml +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/team/custom_transformations.py +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/team/hobbies.yaml +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/team/skills.yaml +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/team/team.zs +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/team/team1.yaml +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/team/test_pyobj_to_yaml.py +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/team/work_experience.yaml +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/pyproject.toml +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/setup.cfg +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml/__init__.py +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml/built_in_transformations.py +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml/yaml_transformer.py +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml.egg-info/SOURCES.txt +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml.egg-info/dependency_links.txt +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml.egg-info/entry_points.txt +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml.egg-info/requires.txt +0 -0
- {zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/zs_yaml.egg-info/top_level.txt +0 -0
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.11.0] - 2026-06-09
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `bin -> yaml` can now take the zserio type directly via `--type <module.TypeName>` (and optional `--init-args`), so the target YAML no longer has to be pre-created with a `_meta` block. Fully backward compatible: when `--type` is omitted, the existing read-target-`_meta` behavior (including any `transformation_module`) is unchanged. `bin_to_yaml(...)` gains matching optional `schema_module`/`schema_type`/`init_args` parameters. Fixes #30
|
|
14
|
+
|
|
10
15
|
## [0.10.1] - 2026-05-07
|
|
11
16
|
|
|
12
17
|
### Fixed
|
|
@@ -217,6 +217,53 @@ def test_bin_to_yaml():
|
|
|
217
217
|
raise
|
|
218
218
|
|
|
219
219
|
|
|
220
|
+
def test_bin_to_yaml_with_type_arg():
|
|
221
|
+
"""Test bin_to_yaml with the type passed directly, no pre-existing target.
|
|
222
|
+
|
|
223
|
+
Regression for ndsev/zs-yaml#30: schema_module/schema_type (CLI --type) allow
|
|
224
|
+
binary -> YAML without a pre-created target file containing _meta.
|
|
225
|
+
"""
|
|
226
|
+
print("\nTesting bin_to_yaml with --type (no pre-existing target)...")
|
|
227
|
+
|
|
228
|
+
result = subprocess.run(['zs-yaml', 'team1.yaml', 'test_bin_to_yaml_type.bin'],
|
|
229
|
+
capture_output=True, text=True)
|
|
230
|
+
if result.returncode != 0:
|
|
231
|
+
raise Exception(f"Failed to create binary file: {result.stderr}")
|
|
232
|
+
|
|
233
|
+
out_path = 'test_bin_to_yaml_type_out.yaml'
|
|
234
|
+
cli_out = 'test_bin_to_yaml_type_cli.yaml'
|
|
235
|
+
for p in (out_path, cli_out):
|
|
236
|
+
if os.path.exists(p):
|
|
237
|
+
os.unlink(p)
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
# API: pass the type directly; the target file does not exist yet.
|
|
241
|
+
bin_to_yaml('test_bin_to_yaml_type.bin', out_path,
|
|
242
|
+
schema_module='team.api', schema_type='Team')
|
|
243
|
+
with open(out_path, 'r') as f:
|
|
244
|
+
output_data = yaml.safe_load(f)
|
|
245
|
+
assert output_data['_meta']['schema_module'] == 'team.api'
|
|
246
|
+
assert output_data['_meta']['schema_type'] == 'Team'
|
|
247
|
+
assert output_data['name'] == "Dream Team", "Team name doesn't match"
|
|
248
|
+
assert 'members' in output_data, "members field missing"
|
|
249
|
+
|
|
250
|
+
# CLI: end-to-end --type path.
|
|
251
|
+
r = subprocess.run(
|
|
252
|
+
['zs-yaml', 'test_bin_to_yaml_type.bin', cli_out, '--type', 'team.api.Team'],
|
|
253
|
+
capture_output=True, text=True)
|
|
254
|
+
assert r.returncode == 0, f"CLI --type failed: {r.stderr}"
|
|
255
|
+
with open(cli_out, 'r') as f:
|
|
256
|
+
cli_data = yaml.safe_load(f)
|
|
257
|
+
assert cli_data['name'] == "Dream Team"
|
|
258
|
+
|
|
259
|
+
print(" ✓ bin -> yaml via --type successful")
|
|
260
|
+
return True
|
|
261
|
+
finally:
|
|
262
|
+
for p in ('test_bin_to_yaml_type.bin', out_path, cli_out):
|
|
263
|
+
if os.path.exists(p):
|
|
264
|
+
os.unlink(p)
|
|
265
|
+
|
|
266
|
+
|
|
220
267
|
def test_yaml_to_yaml_with_template_args():
|
|
221
268
|
"""Test yaml_to_yaml with template argument substitution"""
|
|
222
269
|
print("\nTesting yaml_to_yaml with template arguments...")
|
|
@@ -279,6 +326,56 @@ members:
|
|
|
279
326
|
raise
|
|
280
327
|
|
|
281
328
|
|
|
329
|
+
def test_descriptor_cache_keyed_on_object_not_id():
|
|
330
|
+
"""Regression: _COMPOUND_CACHE must not alias distinct TypeInfos by id().
|
|
331
|
+
|
|
332
|
+
Previously the cache keyed on ``id(type_info)`` while only storing the
|
|
333
|
+
descriptor. Each call to a generated ``type_info()`` returns a fresh
|
|
334
|
+
``TypeInfo`` instance, so after GC reclaims one, a later call can land at
|
|
335
|
+
the same memory address and the cache returns a stale descriptor —
|
|
336
|
+
surfacing as ``'X' object has no attribute 'y'`` errors during
|
|
337
|
+
``bin_to_dict`` / ``bin_to_yaml`` in long-running processes that load
|
|
338
|
+
multiple schemas.
|
|
339
|
+
|
|
340
|
+
We simulate the collision by seeding the cache with an int key matching
|
|
341
|
+
``id(type_info)`` whose value is an unrelated descriptor, then verify the
|
|
342
|
+
lookup still returns the correct descriptor for that ``type_info``.
|
|
343
|
+
"""
|
|
344
|
+
from team.api import Team, Person
|
|
345
|
+
from zs_yaml import convert
|
|
346
|
+
|
|
347
|
+
print("Testing _COMPOUND_CACHE id-reuse regression...")
|
|
348
|
+
|
|
349
|
+
ti_team = Team.type_info()
|
|
350
|
+
ti_person = Person.type_info()
|
|
351
|
+
|
|
352
|
+
convert._COMPOUND_CACHE.clear()
|
|
353
|
+
desc_team = convert._compound_descriptor(ti_team)
|
|
354
|
+
desc_person = convert._compound_descriptor(ti_person)
|
|
355
|
+
assert desc_team is not desc_person, "Distinct TypeInfos must produce distinct descriptors"
|
|
356
|
+
|
|
357
|
+
# Simulate the previous bug: an int key matching id(ti_person) carrying
|
|
358
|
+
# an unrelated descriptor (as if a freed TypeInfo had occupied that
|
|
359
|
+
# address before GC reclaimed it). A correctly-keyed cache (keyed on the
|
|
360
|
+
# TypeInfo object) ignores the stale int-keyed entry; a buggy id-keyed
|
|
361
|
+
# cache would return desc_team for ti_person.
|
|
362
|
+
convert._COMPOUND_CACHE[id(ti_person)] = desc_team
|
|
363
|
+
desc_person_again = convert._compound_descriptor(ti_person)
|
|
364
|
+
|
|
365
|
+
if desc_person_again is not desc_person:
|
|
366
|
+
team_fields = [f.schema_name for f in desc_team.fields]
|
|
367
|
+
person_fields_expected = [f.schema_name for f in desc_person.fields]
|
|
368
|
+
person_fields_actual = [f.schema_name for f in desc_person_again.fields]
|
|
369
|
+
raise AssertionError(
|
|
370
|
+
"_COMPOUND_CACHE returned a stale descriptor — id() reuse regression. "
|
|
371
|
+
f"Expected fields={person_fields_expected}, got={person_fields_actual} "
|
|
372
|
+
f"(matches Team fields={team_fields})."
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
print(" ✓ cache returns correct descriptor under simulated id() collision")
|
|
376
|
+
return True
|
|
377
|
+
|
|
378
|
+
|
|
282
379
|
if __name__ == "__main__":
|
|
283
380
|
try:
|
|
284
381
|
# Run all tests
|
|
@@ -286,8 +383,10 @@ if __name__ == "__main__":
|
|
|
286
383
|
json_path = test_yaml_to_json()
|
|
287
384
|
test_json_to_yaml(json_path)
|
|
288
385
|
test_bin_to_yaml()
|
|
386
|
+
test_bin_to_yaml_with_type_arg()
|
|
289
387
|
test_yaml_to_yaml_with_template_args()
|
|
290
|
-
|
|
388
|
+
test_descriptor_cache_keyed_on_object_not_id()
|
|
389
|
+
|
|
291
390
|
print("\n✅ All conversion tests passed!")
|
|
292
391
|
sys.exit(0)
|
|
293
392
|
except Exception as e:
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.10.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 10,
|
|
21
|
+
__version__ = version = '0.10.2.dev3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 10, 2, 'dev3')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g02dbf3d05'
|
|
@@ -540,34 +540,58 @@ def bin_to_dict(bin_input, schema_module, schema_type, init_args=None):
|
|
|
540
540
|
)
|
|
541
541
|
|
|
542
542
|
|
|
543
|
-
def bin_to_yaml(bin_input_path, yaml_output_path):
|
|
543
|
+
def bin_to_yaml(bin_input_path, yaml_output_path, schema_module=None, schema_type=None, init_args=None):
|
|
544
544
|
"""
|
|
545
545
|
Converts a binary file to a YAML file using Zserio deserialization.
|
|
546
546
|
|
|
547
|
+
The deserialization type can be supplied in two ways:
|
|
548
|
+
|
|
549
|
+
1. By passing ``schema_module`` and ``schema_type`` directly. In this case the
|
|
550
|
+
output YAML file does not need to exist beforehand; a minimal ``_meta``
|
|
551
|
+
block is synthesized from the provided type.
|
|
552
|
+
2. By leaving them as ``None`` (the default). Then the target YAML file must
|
|
553
|
+
already exist and contain a ``_meta`` block with ``schema_module`` and
|
|
554
|
+
``schema_type`` (the original behavior, preserved for backward compatibility).
|
|
555
|
+
Any extra ``_meta`` keys (e.g. ``transformation_module``) are kept.
|
|
556
|
+
|
|
547
557
|
Args:
|
|
548
558
|
bin_input_path (str): Path to the input binary file.
|
|
549
559
|
yaml_output_path (str): Path to the output YAML file.
|
|
560
|
+
schema_module (str, optional): Fully-qualified schema module name.
|
|
561
|
+
schema_type (str, optional): Schema type name.
|
|
562
|
+
init_args (list, optional): Initialization arguments for deserialization.
|
|
550
563
|
|
|
551
564
|
Raises:
|
|
552
|
-
ValueError: If
|
|
553
|
-
|
|
554
|
-
in the module.
|
|
565
|
+
ValueError: If the type cannot be determined (neither passed directly nor
|
|
566
|
+
available in the target YAML file's ``_meta`` section).
|
|
555
567
|
"""
|
|
556
568
|
try:
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
569
|
+
if schema_module and schema_type:
|
|
570
|
+
# Type provided directly: no pre-existing target file required.
|
|
571
|
+
meta_block = {'schema_module': schema_module, 'schema_type': schema_type}
|
|
572
|
+
if init_args:
|
|
573
|
+
meta_block['initialization_args'] = init_args
|
|
574
|
+
else:
|
|
575
|
+
# Backward-compatible: read the type from the existing target's _meta.
|
|
576
|
+
with open(yaml_output_path, 'r') as yaml_file:
|
|
577
|
+
meta = yaml.safe_load(yaml_file)
|
|
578
|
+
|
|
579
|
+
meta_block = (meta or {}).get('_meta', {})
|
|
580
|
+
schema_module = meta_block.get('schema_module')
|
|
581
|
+
schema_type = meta_block.get('schema_type')
|
|
582
|
+
init_args = meta_block.get('initialization_args', [])
|
|
583
|
+
|
|
584
|
+
if not schema_module or not schema_type:
|
|
585
|
+
raise ValueError(
|
|
586
|
+
"Error: schema_module and schema_type must be specified in the _meta "
|
|
587
|
+
"section of the target YAML file, or passed via schema_module/schema_type "
|
|
588
|
+
"(CLI: --type)"
|
|
589
|
+
)
|
|
566
590
|
|
|
567
591
|
data, metadata = bin_to_dict(bin_input_path, schema_module, schema_type, init_args)
|
|
568
592
|
|
|
569
593
|
# Create a new dictionary to ensure _meta comes first
|
|
570
|
-
final_data = {'_meta':
|
|
594
|
+
final_data = {'_meta': meta_block}
|
|
571
595
|
final_data.update(data)
|
|
572
596
|
|
|
573
597
|
with open(yaml_output_path, 'w') as yaml_file:
|
|
@@ -19,8 +19,9 @@ def parse_arguments():
|
|
|
19
19
|
description=
|
|
20
20
|
f'%(prog)s {get_version_info()}\n\n'
|
|
21
21
|
'Converts between YAML, JSON, and binary formats. '
|
|
22
|
-
'To convert from binary to YAML, the
|
|
23
|
-
'
|
|
22
|
+
'To convert from binary to YAML, identify the type either by passing '
|
|
23
|
+
'--type <module.TypeName>, or via a pre-existing target YAML file that '
|
|
24
|
+
'already contains the metadata.\n\n'
|
|
24
25
|
'The minimal metadata content in the target YAML file should be:\n'
|
|
25
26
|
' _meta:\n'
|
|
26
27
|
' schema_module: <module_name>\n'
|
|
@@ -36,6 +37,17 @@ def parse_arguments():
|
|
|
36
37
|
)
|
|
37
38
|
parser.add_argument('input_path', type=str, help='Path to the input file (YAML, JSON, or binary)')
|
|
38
39
|
parser.add_argument('output_path', type=str, nargs='?', help='Path to the output file (YAML, JSON, or binary)')
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
'--type', dest='qualified_type', type=str, default=None,
|
|
42
|
+
help='Fully-qualified zserio type for binary -> YAML, e.g. '
|
|
43
|
+
'pkg.module.TypeName. Alternative to a pre-existing target file '
|
|
44
|
+
'with _meta; when given, the target need not exist beforehand.'
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
'--init-args', dest='init_args', nargs='*', default=None,
|
|
48
|
+
help='Initialization arguments for the zserio type (binary -> YAML). '
|
|
49
|
+
'Integers (incl. 0x..) are parsed as ints, everything else as strings.'
|
|
50
|
+
)
|
|
39
51
|
parser.add_argument('--version', action='version', version=f'%(prog)s {get_version_info()}')
|
|
40
52
|
|
|
41
53
|
if len(sys.argv) < 2:
|
|
@@ -62,10 +74,33 @@ def process_yaml_input(input_path, output_path):
|
|
|
62
74
|
|
|
63
75
|
return output_path
|
|
64
76
|
|
|
65
|
-
def
|
|
77
|
+
def _split_qualified_type(qualified_type):
|
|
78
|
+
"""Split 'pkg.module.TypeName' into (schema_module, schema_type)."""
|
|
79
|
+
if '.' not in qualified_type:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"--type must be a fully-qualified type 'module.TypeName', got '{qualified_type}'"
|
|
82
|
+
)
|
|
83
|
+
schema_module, schema_type = qualified_type.rsplit('.', 1)
|
|
84
|
+
return schema_module, schema_type
|
|
85
|
+
|
|
86
|
+
def _parse_init_args(raw_args):
|
|
87
|
+
"""Parse CLI init-args: ints (incl. 0x..) as ints, otherwise as strings."""
|
|
88
|
+
parsed = []
|
|
89
|
+
for a in raw_args:
|
|
90
|
+
try:
|
|
91
|
+
parsed.append(int(a, 0))
|
|
92
|
+
except ValueError:
|
|
93
|
+
parsed.append(a)
|
|
94
|
+
return parsed
|
|
95
|
+
|
|
96
|
+
def process_binary_input(input_path, output_path, qualified_type=None, init_args=None):
|
|
66
97
|
if not output_path:
|
|
67
98
|
raise ValueError("Output path must be specified for binary input")
|
|
68
|
-
|
|
99
|
+
schema_module = schema_type = None
|
|
100
|
+
if qualified_type:
|
|
101
|
+
schema_module, schema_type = _split_qualified_type(qualified_type)
|
|
102
|
+
parsed_init_args = _parse_init_args(init_args) if init_args else None
|
|
103
|
+
bin_to_yaml(input_path, output_path, schema_module, schema_type, parsed_init_args)
|
|
69
104
|
return output_path
|
|
70
105
|
|
|
71
106
|
def process_json_input(input_path, output_path):
|
|
@@ -102,7 +137,9 @@ def main():
|
|
|
102
137
|
if input_extension == '.yaml':
|
|
103
138
|
output_path = process_yaml_input(args.input_path, args.output_path)
|
|
104
139
|
elif input_extension == '.bin' or input_extension == '':
|
|
105
|
-
output_path = process_binary_input(
|
|
140
|
+
output_path = process_binary_input(
|
|
141
|
+
args.input_path, args.output_path, args.qualified_type, args.init_args
|
|
142
|
+
)
|
|
106
143
|
elif input_extension == '.json':
|
|
107
144
|
output_path = process_json_input(args.input_path, args.output_path)
|
|
108
145
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zs_yaml-0.10.1.dev2 → zs_yaml-0.10.2.dev3}/examples/extern_compression/extern_compression.zs
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
|