pathling 9.9.0.dev0__tar.gz → 9.10.0.dev0__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.
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/PKG-INFO +1 -1
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/_version.py +2 -2
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/config.py +15 -1
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/import_terminology.py +147 -6
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/context.py +33 -5
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/.gitignore +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/LICENSE +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/README.md +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/bulk.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/data/bundles/Bennett146_Swaniawski813_704c9750-f6e6-473b-ee83-fbd48e07fe3f.json +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/data/bundles/Dino214_Parisian75_40d82b80-b682-cd8b-da6d-396809878641.json +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/data/csv/conditions.csv +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/data/resources/Condition.ndjson +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/data/resources/Patient.ndjson +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/designation.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/display.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/encode_bundles.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/encode_resources.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/fhir_search.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/fhir_view.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/member_of.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/property_of.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/subsumes.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/examples/translate.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/__init__.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/_spark_defaults.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/bulk.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/__init__.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/console.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/convert.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/departition.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/errors.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/export.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/fhirpath.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/io.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/main.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/render.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/resources/quiet-log4j2.properties +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/ruff.toml +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/run.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/session.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/sparkconf.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/terminology.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/cli/view.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/coding.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/core.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/datasink.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/datasource.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/fhir.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/functions.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/spark.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pathling/udfs.py +0 -0
- {pathling-9.9.0.dev0 → pathling-9.10.0.dev0}/pyproject.toml +0 -0
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
# Auto generated from POM project version.
|
|
3
3
|
# Please do not modify.
|
|
4
4
|
#
|
|
5
|
-
__version__="9.
|
|
6
|
-
__java_version__="9.
|
|
5
|
+
__version__="9.10.0.dev0"
|
|
6
|
+
__java_version__="9.10.0-SNAPSHOT"
|
|
7
7
|
__scala_version__="2.13"
|
|
8
8
|
__delta_version__="4.0.0"
|
|
9
9
|
__hadoop_version__="3.4.1"
|
|
@@ -55,7 +55,15 @@ PROJECT_CONFIG_FILENAME = "pathling.toml"
|
|
|
55
55
|
|
|
56
56
|
# Valid top-level keys in the config file.
|
|
57
57
|
VALID_CONFIG_KEYS = frozenset(
|
|
58
|
-
{
|
|
58
|
+
{
|
|
59
|
+
"tx-server",
|
|
60
|
+
"fhir-version",
|
|
61
|
+
"terminology-auth",
|
|
62
|
+
"bulk-auth",
|
|
63
|
+
"spark",
|
|
64
|
+
"tx-store",
|
|
65
|
+
"package-registry",
|
|
66
|
+
}
|
|
59
67
|
)
|
|
60
68
|
|
|
61
69
|
# Valid keys within the [terminology-auth] and [bulk-auth] tables.
|
|
@@ -181,6 +189,10 @@ class CliConfig:
|
|
|
181
189
|
:param tx_server_explicit: whether the terminology server URL was set
|
|
182
190
|
explicitly (via flag or config key) rather than falling back to the
|
|
183
191
|
built-in default. Drives the store-wins conflict warning.
|
|
192
|
+
:param package_registry: the FHIR package registry that a package import
|
|
193
|
+
checks a package against, or None to leave the choice to the library
|
|
194
|
+
default. Concerns package distribution rather than the store, so it
|
|
195
|
+
is a top-level key rather than part of ``[tx-store]``.
|
|
184
196
|
"""
|
|
185
197
|
|
|
186
198
|
tx_server: str = DEFAULT_TX_SERVER
|
|
@@ -192,6 +204,7 @@ class CliConfig:
|
|
|
192
204
|
bulk_auth_table: Optional[dict] = None
|
|
193
205
|
tx_store: Optional[TxStore] = None
|
|
194
206
|
tx_server_explicit: bool = False
|
|
207
|
+
package_registry: Optional[str] = None
|
|
195
208
|
|
|
196
209
|
|
|
197
210
|
def _load_toml(path: Path) -> dict:
|
|
@@ -751,4 +764,5 @@ def resolve_config(
|
|
|
751
764
|
bulk_auth_table=file_data.get("bulk-auth"),
|
|
752
765
|
tx_store=resolved_tx_store,
|
|
753
766
|
tx_server_explicit=tx_server_explicit,
|
|
767
|
+
package_registry=file_data.get("package-registry"),
|
|
754
768
|
)
|
|
@@ -35,13 +35,26 @@ from rich.console import Console
|
|
|
35
35
|
|
|
36
36
|
from pathling.cli import session
|
|
37
37
|
from pathling.cli.config import CliConfig
|
|
38
|
-
from pathling.cli.errors import EXIT_USAGE, CliError
|
|
38
|
+
from pathling.cli.errors import EXIT_USAGE, CliError, unwrap_java_exception
|
|
39
39
|
from pathling.cli.render import progress_status
|
|
40
40
|
|
|
41
41
|
if TYPE_CHECKING:
|
|
42
|
+
from pyspark.sql import Row, SparkSession
|
|
43
|
+
|
|
42
44
|
from pathling import PathlingContext
|
|
43
45
|
from pathling.cli.main import CliContext
|
|
44
46
|
|
|
47
|
+
# The name of the manifest table within a terminology store.
|
|
48
|
+
MANIFEST_TABLE = "manifest"
|
|
49
|
+
|
|
50
|
+
# The fixed phrase the library uses when a package does not match the checksum
|
|
51
|
+
# its registry publishes. The library must not name a CLI flag, so the hint
|
|
52
|
+
# naming --no-verify is added here, keyed off this phrase.
|
|
53
|
+
MISMATCH_PHRASE = "does not match the registry checksum"
|
|
54
|
+
|
|
55
|
+
# The hint appended to a checksum mismatch failure.
|
|
56
|
+
MISMATCH_HINT = "Re-run with --no-verify to import it anyway."
|
|
57
|
+
|
|
45
58
|
|
|
46
59
|
def _import_context(config: CliConfig, console: Console) -> PathlingContext:
|
|
47
60
|
"""Creates a context for importing into a store.
|
|
@@ -84,6 +97,91 @@ def _resolve_storage_path(config: CliConfig, storage_path: Optional[str]) -> str
|
|
|
84
97
|
)
|
|
85
98
|
|
|
86
99
|
|
|
100
|
+
def read_latest_import_row(
|
|
101
|
+
spark: SparkSession, store: str, source: str
|
|
102
|
+
) -> Optional[Row]:
|
|
103
|
+
"""Reads the most recent manifest row recorded for a source.
|
|
104
|
+
|
|
105
|
+
The store manifest carries the provenance of each import: the source hash,
|
|
106
|
+
the package identity, and the outcome of the registry check. The latest row
|
|
107
|
+
for this source describes the import that has just completed.
|
|
108
|
+
|
|
109
|
+
A failure to read the manifest yields None rather than an error: the import
|
|
110
|
+
has already committed, and reporting less detail is better than failing a
|
|
111
|
+
command whose work succeeded.
|
|
112
|
+
|
|
113
|
+
:param spark: the Spark session to read with.
|
|
114
|
+
:param store: the terminology store path.
|
|
115
|
+
:param source: the import source, matched against the manifest ``source``
|
|
116
|
+
column.
|
|
117
|
+
:return: the latest manifest row for the source, or None when there is none
|
|
118
|
+
or the manifest cannot be read.
|
|
119
|
+
"""
|
|
120
|
+
from pyspark.sql import functions as F
|
|
121
|
+
|
|
122
|
+
manifest_path = f"{store.rstrip('/')}/{MANIFEST_TABLE}"
|
|
123
|
+
try:
|
|
124
|
+
rows = (
|
|
125
|
+
spark.read.format("delta")
|
|
126
|
+
.load(manifest_path)
|
|
127
|
+
.filter(F.col("source") == source)
|
|
128
|
+
.orderBy(F.col("imported_at").desc())
|
|
129
|
+
.take(1)
|
|
130
|
+
)
|
|
131
|
+
except Exception: # noqa: BLE001 - detail only; the import already committed.
|
|
132
|
+
return None
|
|
133
|
+
return rows[0] if rows else None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def format_import_summary(
|
|
137
|
+
command_noun: str, source: str, store: str, row: Optional[Row]
|
|
138
|
+
) -> str:
|
|
139
|
+
"""Renders the completion line for a finished import.
|
|
140
|
+
|
|
141
|
+
The line always names what was imported, from where, and into where. Where
|
|
142
|
+
the manifest records provenance it is reported in parentheses: the source
|
|
143
|
+
hash, and for a package the identity and the outcome of the registry check.
|
|
144
|
+
|
|
145
|
+
:param command_noun: what was imported, such as ``"SNOMED CT"``.
|
|
146
|
+
:param source: the import source as given on the command line.
|
|
147
|
+
:param store: the resolved store path.
|
|
148
|
+
:param row: the manifest row describing the import, or None when it could
|
|
149
|
+
not be read.
|
|
150
|
+
:return: the completion line.
|
|
151
|
+
"""
|
|
152
|
+
summary = f"Imported {command_noun} from {source} into {store}"
|
|
153
|
+
if row is None:
|
|
154
|
+
return summary
|
|
155
|
+
values = row.asDict() if hasattr(row, "asDict") else dict(row)
|
|
156
|
+
sha256 = values.get("source_sha256")
|
|
157
|
+
if sha256 is None:
|
|
158
|
+
# A directory source has no single set of bytes to fingerprint.
|
|
159
|
+
return summary
|
|
160
|
+
status = values.get("package_verification")
|
|
161
|
+
name = values.get("package_name")
|
|
162
|
+
version = values.get("package_version")
|
|
163
|
+
identity = f"{name} {version}" if name and version else None
|
|
164
|
+
if status == "verified":
|
|
165
|
+
registry = values.get("package_registry")
|
|
166
|
+
detail = f"verified {identity} against {registry}"
|
|
167
|
+
elif status == "unverified":
|
|
168
|
+
subject = identity if identity else "package"
|
|
169
|
+
detail = (
|
|
170
|
+
f"{subject} not verified against a registry; re-run with --verbose "
|
|
171
|
+
"for the reason"
|
|
172
|
+
)
|
|
173
|
+
elif status == "skipped":
|
|
174
|
+
detail = (
|
|
175
|
+
f"{identity}, verification skipped"
|
|
176
|
+
if identity
|
|
177
|
+
else "package verification skipped"
|
|
178
|
+
)
|
|
179
|
+
else:
|
|
180
|
+
# Not a package: the hash alone is the provenance.
|
|
181
|
+
return f"{summary} (sha256 {sha256})"
|
|
182
|
+
return f"{summary} ({detail}; sha256 {sha256})"
|
|
183
|
+
|
|
184
|
+
|
|
87
185
|
@click.command(name="import-snomed")
|
|
88
186
|
@click.argument("source")
|
|
89
187
|
@click.argument("storage_path", required=False)
|
|
@@ -142,20 +240,46 @@ def import_snomed(
|
|
|
142
240
|
pc.import_snomed(
|
|
143
241
|
source, resolved_path, edition_uri, dense_id_order, resolved_dialect
|
|
144
242
|
)
|
|
145
|
-
|
|
243
|
+
row = read_latest_import_row(pc.spark, resolved_path, source)
|
|
244
|
+
click.echo(format_import_summary("SNOMED CT", source, resolved_path, row))
|
|
146
245
|
|
|
147
246
|
|
|
148
247
|
@click.command(name="import-fhir-terminology")
|
|
149
248
|
@click.argument("source")
|
|
150
249
|
@click.argument("storage_path", required=False)
|
|
250
|
+
@click.option(
|
|
251
|
+
"--no-verify",
|
|
252
|
+
"no_verify",
|
|
253
|
+
is_flag=True,
|
|
254
|
+
help=(
|
|
255
|
+
"Do not check a FHIR NPM package against its registry's published "
|
|
256
|
+
"checksum. Use for offline imports or packages not published to a "
|
|
257
|
+
"registry."
|
|
258
|
+
),
|
|
259
|
+
)
|
|
260
|
+
@click.option(
|
|
261
|
+
"--package-registry",
|
|
262
|
+
"package_registry",
|
|
263
|
+
metavar="URL",
|
|
264
|
+
help=(
|
|
265
|
+
"The FHIR package registry to check a package against. Falls back to "
|
|
266
|
+
"the 'package-registry' config key, then https://packages.fhir.org."
|
|
267
|
+
),
|
|
268
|
+
)
|
|
151
269
|
@click.pass_obj
|
|
152
270
|
def import_fhir_terminology(
|
|
153
|
-
obj: CliContext,
|
|
271
|
+
obj: CliContext,
|
|
272
|
+
source: str,
|
|
273
|
+
storage_path: Optional[str],
|
|
274
|
+
no_verify: bool,
|
|
275
|
+
package_registry: Optional[str],
|
|
154
276
|
) -> None:
|
|
155
277
|
"""Import FHIR CodeSystem, ValueSet, and ConceptMap resources into a store.
|
|
156
278
|
|
|
157
279
|
The source may be a JSON file, a directory of JSON files, or a FHIR NPM
|
|
158
|
-
package (.tgz).
|
|
280
|
+
package (.tgz). A package is checked against the checksum its registry
|
|
281
|
+
publishes; a package that does not match fails the import and leaves the
|
|
282
|
+
store unchanged. STORAGE_PATH may be omitted when 'tx-store.path' (or
|
|
159
283
|
--tx-store) is set.
|
|
160
284
|
|
|
161
285
|
Example:
|
|
@@ -165,10 +289,27 @@ def import_fhir_terminology(
|
|
|
165
289
|
config = obj.config
|
|
166
290
|
console = obj.console
|
|
167
291
|
resolved_path = _resolve_storage_path(config, storage_path)
|
|
292
|
+
# The flag wins over the config key; with neither set no registry is named
|
|
293
|
+
# and the library default applies, so the CLI never holds that URL.
|
|
294
|
+
resolved_registry = (
|
|
295
|
+
package_registry if package_registry is not None else config.package_registry
|
|
296
|
+
)
|
|
168
297
|
pc = _import_context(config, console)
|
|
169
298
|
with progress_status(console, "Importing FHIR terminology...", config.verbose):
|
|
170
|
-
|
|
171
|
-
|
|
299
|
+
try:
|
|
300
|
+
pc.import_fhir_terminology(
|
|
301
|
+
source,
|
|
302
|
+
resolved_path,
|
|
303
|
+
verify_package=not no_verify,
|
|
304
|
+
package_registry=resolved_registry,
|
|
305
|
+
)
|
|
306
|
+
except Exception as exc: # noqa: BLE001 - enrich a mismatch failure.
|
|
307
|
+
root_message = unwrap_java_exception(exc)
|
|
308
|
+
if MISMATCH_PHRASE not in root_message:
|
|
309
|
+
raise
|
|
310
|
+
raise CliError(f"{root_message} {MISMATCH_HINT}") from exc
|
|
311
|
+
row = read_latest_import_row(pc.spark, resolved_path, source)
|
|
312
|
+
click.echo(format_import_summary("FHIR terminology", source, resolved_path, row))
|
|
172
313
|
|
|
173
314
|
|
|
174
315
|
#: The terminology import commands registered by the root command group.
|
|
@@ -254,8 +254,8 @@ class PathlingContext:
|
|
|
254
254
|
unversioned SNOMED reference when the local store holds multiple editions.
|
|
255
255
|
:param expansion_cache_size: the maximum number of value set expansions cached per executor
|
|
256
256
|
in local mode.
|
|
257
|
-
:param dialect_aliases: additional dialect tags recognised in local mode when a display
|
|
258
|
-
|
|
257
|
+
:param dialect_aliases: additional dialect tags recognised in local mode when a display
|
|
258
|
+
is requested in a particular language, mapping a language tag to the
|
|
259
259
|
identifier of the SNOMED CT language reference set that serves it (for example
|
|
260
260
|
``{"en-NZ": "271000210107"}``). An entry for a tag that is already recognised
|
|
261
261
|
replaces the built-in mapping for it.
|
|
@@ -473,18 +473,46 @@ class PathlingContext:
|
|
|
473
473
|
options = builder.build()
|
|
474
474
|
self._jpc.importSnomed(source, storage_path, options)
|
|
475
475
|
|
|
476
|
-
def import_fhir_terminology(
|
|
476
|
+
def import_fhir_terminology(
|
|
477
|
+
self,
|
|
478
|
+
source: str,
|
|
479
|
+
storage_path: str,
|
|
480
|
+
verify_package: bool = True,
|
|
481
|
+
package_registry: Optional[str] = None,
|
|
482
|
+
) -> None:
|
|
477
483
|
"""
|
|
478
484
|
Imports FHIR R4 CodeSystem, ValueSet, and ConceptMap resources into a local terminology
|
|
479
485
|
store.
|
|
480
486
|
|
|
487
|
+
A FHIR NPM package (``.tgz``) is checked against the checksum its registry publishes before
|
|
488
|
+
anything is written: a package that does not match fails the import and leaves the store
|
|
489
|
+
untouched. A package the registry does not describe, or a registry that cannot be reached,
|
|
490
|
+
is imported and recorded as unverified. For an offline import, or a package that was never
|
|
491
|
+
published to a registry, pass ``verify_package=False``; the import is then recorded as
|
|
492
|
+
having skipped verification.
|
|
493
|
+
|
|
481
494
|
:param source: the path to a JSON file, a directory of JSON files, or a FHIR NPM package
|
|
482
495
|
(``.tgz``), on any filesystem accessible through the Hadoop FileSystem API
|
|
483
496
|
:param storage_path: the terminology store location, created if absent
|
|
497
|
+
:param verify_package: whether a package source is checked against its registry's published
|
|
498
|
+
checksum; ``True`` by default
|
|
499
|
+
:param package_registry: the FHIR package registry to check a package against; when omitted
|
|
500
|
+
the library default (``https://packages.fhir.org``) is used
|
|
484
501
|
:raises: the mapped JVM ``TerminologyImportException`` if the source contains no importable
|
|
485
|
-
resources or an invalid resource
|
|
502
|
+
resources or an invalid resource, or the package does not match the registry
|
|
503
|
+
checksum; the store is left unmodified
|
|
486
504
|
"""
|
|
487
|
-
|
|
505
|
+
options = None
|
|
506
|
+
if not verify_package or package_registry is not None:
|
|
507
|
+
jvm = self._spark._jvm
|
|
508
|
+
builder = (
|
|
509
|
+
jvm.au.csiro.pathling.library.terminology.FhirImportOptions.builder()
|
|
510
|
+
)
|
|
511
|
+
builder = builder.verifyPackage(verify_package)
|
|
512
|
+
if package_registry is not None:
|
|
513
|
+
builder = builder.packageRegistry(package_registry)
|
|
514
|
+
options = builder.build()
|
|
515
|
+
self._jpc.importFhirTerminology(source, storage_path, options)
|
|
488
516
|
|
|
489
517
|
def encode(
|
|
490
518
|
self,
|
|
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
|
|
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
|