pathling 9.8.0.dev0__tar.gz → 9.9.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.8.0.dev0 → pathling-9.9.0.dev0}/LICENSE +8 -2
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/PKG-INFO +1 -1
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/__init__.py +7 -1
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/_version.py +2 -2
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/__init__.py +2 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/config.py +215 -3
- pathling-9.9.0.dev0/pathling/cli/console.py +115 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/convert.py +21 -2
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/departition.py +11 -4
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/errors.py +53 -11
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/export.py +33 -22
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/fhirpath.py +48 -26
- pathling-9.9.0.dev0/pathling/cli/import_terminology.py +175 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/io.py +10 -2
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/main.py +50 -14
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/render.py +222 -20
- pathling-9.9.0.dev0/pathling/cli/ruff.toml +17 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/run.py +45 -16
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/session.py +56 -4
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/sparkconf.py +5 -3
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/terminology.py +371 -172
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/view.py +24 -14
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/context.py +137 -4
- pathling-9.8.0.dev0/pathling/cli/console.py +0 -82
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/.gitignore +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/README.md +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/bulk.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/data/bundles/Bennett146_Swaniawski813_704c9750-f6e6-473b-ee83-fbd48e07fe3f.json +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/data/bundles/Dino214_Parisian75_40d82b80-b682-cd8b-da6d-396809878641.json +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/data/csv/conditions.csv +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/data/resources/Condition.ndjson +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/data/resources/Patient.ndjson +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/designation.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/display.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/encode_bundles.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/encode_resources.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/fhir_search.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/fhir_view.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/member_of.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/property_of.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/subsumes.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/examples/translate.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/_spark_defaults.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/bulk.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/cli/resources/quiet-log4j2.properties +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/coding.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/core.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/datasink.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/datasource.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/fhir.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/functions.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/spark.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pathling/udfs.py +0 -0
- {pathling-9.8.0.dev0 → pathling-9.9.0.dev0}/pyproject.toml +0 -0
|
@@ -183,24 +183,28 @@ agree to comply with the licence terms for these components as part of
|
|
|
183
183
|
accessing the Software. Other third party software may also be identified in
|
|
184
184
|
separate files distributed with the Software.
|
|
185
185
|
|
|
186
|
-
* (Apache License, Version 2.0) FHIR Bulk Client (au.csiro.fhir:bulk-export:1.0
|
|
186
|
+
* (Apache License, Version 2.0) FHIR Bulk Client (au.csiro.fhir:bulk-export:1.1.0 - https://github.com/aehrc/fhir-bulk-java)
|
|
187
187
|
* (Apache License, Version 2.0) FHIR Auth Client for Java (au.csiro.fhir:fhir-auth:1.0.0 - https://github.com/aehrc/fhir-auth-java)
|
|
188
188
|
* (Apache License, Version 2.0) HAPI FHIR - Core Library (ca.uhn.hapi.fhir:hapi-fhir-base:8.10.0 - https://hapifhir.io/)
|
|
189
189
|
* (Apache License, Version 2.0) HAPI FHIR - Client Framework (ca.uhn.hapi.fhir:hapi-fhir-client:8.10.0 - https://hapifhir.io/hapi-deployable-pom/hapi-fhir-client)
|
|
190
190
|
* (Apache License, Version 2.0) HAPI FHIR Structures - FHIR R4 (ca.uhn.hapi.fhir:hapi-fhir-structures-r4:8.10.0 - https://hapifhir.io/hapi-deployable-pom/hapi-fhir-structures-r4)
|
|
191
|
-
* (
|
|
191
|
+
* (EPL-2.0) (LGPL-2.1-only) Logback Classic Module (ch.qos.logback:logback-classic:1.5.38 - http://logback.qos.ch/logback-classic)
|
|
192
|
+
* (Apache License, Version 2.0) Jackson-core (com.fasterxml.jackson.core:jackson-core:2.22.1 - https://github.com/FasterXML/jackson-core)
|
|
192
193
|
* (Apache License, Version 2.0) WireMock (com.github.tomakehurst:wiremock-jre8-standalone:2.35.2 - http://wiremock.org)
|
|
193
194
|
* (Apache License, Version 2.0) FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/)
|
|
194
195
|
* (Apache License, Version 2.0) Gson (com.google.code.gson:gson:2.10 - https://github.com/google/gson/gson)
|
|
195
196
|
* (Apache License, Version 2.0) Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.11.0 - https://commons.apache.org/proper/commons-beanutils)
|
|
196
197
|
* (Apache License, Version 2.0) Apache Commons IO (commons-io:commons-io:2.16.1 - https://commons.apache.org/proper/commons-io/)
|
|
197
198
|
* (Apache License, Version 2.0) Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/)
|
|
199
|
+
* (Apache License, Version 2.0) delta-kernel-api (io.delta:delta-kernel-api:4.0.0 - https://delta.io/)
|
|
200
|
+
* (Apache License, Version 2.0) delta-kernel-defaults (io.delta:delta-kernel-defaults:4.0.0 - https://delta.io/)
|
|
198
201
|
* (Apache License, Version 2.0) delta-spark (io.delta:delta-spark_2.13:4.0.0 - https://delta.io/)
|
|
199
202
|
* (Apache License, Version 2.0) ucumate-core (io.github.fhnaumann:ucumate-core:1.0.8 - https://github.com/fhnaumann/ucumate)
|
|
200
203
|
* (EPL 2.0) (GPL2 w/ CPE) Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:5.0.0 - https://projects.eclipse.org/projects/ee4j.servlet)
|
|
201
204
|
* (Apache License, Version 2.0) Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:3.0.2 - https://beanvalidation.org)
|
|
202
205
|
* (Apache License, Version 2.0) Joda-Time (joda-time:joda-time:2.12.7 - https://www.joda.org/joda-time/)
|
|
203
206
|
* (BSD License) ANTLR 4 Tool (org.antlr:antlr4:4.13.1 - http://www.antlr.org)
|
|
207
|
+
* (BSD License) ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.13.1 - https://www.antlr.org/antlr4-runtime/)
|
|
204
208
|
* (Apache License, Version 2.0) Apache Commons Lang (org.apache.commons:commons-lang3:3.18.0 - https://commons.apache.org/proper/commons-lang/)
|
|
205
209
|
* (Apache License, Version 2.0) Apache Derby Tools (org.apache.derby:derbytools:10.16.1.1 - http://db.apache.org/derby/)
|
|
206
210
|
* (Apache License, Version 2.0) Apache Hadoop Client API (org.apache.hadoop:hadoop-client-api:3.4.1 - no url defined)
|
|
@@ -222,7 +226,9 @@ separate files distributed with the Software.
|
|
|
222
226
|
* (MIT License) mockito-core (org.mockito:mockito-core:5.18.0 - https://github.com/mockito/mockito)
|
|
223
227
|
* (GNU General Public License (GPL), version 2, with the Classpath exception) JMH Core (org.openjdk.jmh:jmh-core:1.37 - http://openjdk.java.net/projects/code-tools/jmh/jmh-core/)
|
|
224
228
|
* (GNU General Public License (GPL), version 2, with the Classpath exception) JMH Generators: Annotation Processors (org.openjdk.jmh:jmh-generator-annprocess:1.37 - http://openjdk.java.net/projects/code-tools/jmh/jmh-generator-annprocess/)
|
|
229
|
+
* (GNU General Public License (GPL), version 2, with the Classpath exception) Java Object Layout: Core (org.openjdk.jol:jol-core:0.17 - http://maven.apache.org)
|
|
225
230
|
* (MIT License) Project Lombok (org.projectlombok:lombok:1.18.38 - https://projectlombok.org)
|
|
231
|
+
* (Apache License, Version 2.0) org.roaringbitmap:RoaringBitmap (org.roaringbitmap:RoaringBitmap:1.3.0 - https://github.com/RoaringBitmap/RoaringBitmap)
|
|
226
232
|
* (Apache License, Version 2.0) Scala Library (org.scala-lang:scala-library:2.13.16 - https://www.scala-lang.org/)
|
|
227
233
|
* (Apache License, Version 2.0) JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert)
|
|
228
234
|
* (Apache License, Version 2.0) JSONassert (org.skyscreamer:jsonassert:1.5.3 - https://github.com/skyscreamer/JSONassert)
|
|
@@ -39,7 +39,12 @@ if TYPE_CHECKING:
|
|
|
39
39
|
from .core import Expression, VariableExpression
|
|
40
40
|
from .datasource import DataSource, DataSources
|
|
41
41
|
from .fhir import MimeType, Version
|
|
42
|
-
from .functions import
|
|
42
|
+
from .functions import (
|
|
43
|
+
to_coding,
|
|
44
|
+
to_ecl_value_set,
|
|
45
|
+
to_loinc_coding,
|
|
46
|
+
to_snomed_coding,
|
|
47
|
+
)
|
|
43
48
|
from .udfs import (
|
|
44
49
|
Equivalence,
|
|
45
50
|
PropertyType,
|
|
@@ -65,6 +70,7 @@ _LAZY_EXPORTS = {
|
|
|
65
70
|
"Version": "pathling.fhir",
|
|
66
71
|
"to_coding": "pathling.functions",
|
|
67
72
|
"to_snomed_coding": "pathling.functions",
|
|
73
|
+
"to_loinc_coding": "pathling.functions",
|
|
68
74
|
"to_ecl_value_set": "pathling.functions",
|
|
69
75
|
"member_of": "pathling.udfs",
|
|
70
76
|
"translate": "pathling.udfs",
|
|
@@ -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.9.0.dev0"
|
|
6
|
+
__java_version__="9.9.0-SNAPSHOT"
|
|
7
7
|
__scala_version__="2.13"
|
|
8
8
|
__delta_version__="4.0.0"
|
|
9
9
|
__hadoop_version__="3.4.1"
|
|
@@ -30,6 +30,8 @@ reference, or via an environment variable.
|
|
|
30
30
|
Author: John Grimes.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
33
35
|
import os
|
|
34
36
|
import sys
|
|
35
37
|
from dataclasses import dataclass, field
|
|
@@ -53,7 +55,7 @@ PROJECT_CONFIG_FILENAME = "pathling.toml"
|
|
|
53
55
|
|
|
54
56
|
# Valid top-level keys in the config file.
|
|
55
57
|
VALID_CONFIG_KEYS = frozenset(
|
|
56
|
-
{"tx-server", "fhir-version", "terminology-auth", "bulk-auth", "spark"}
|
|
58
|
+
{"tx-server", "fhir-version", "terminology-auth", "bulk-auth", "spark", "tx-store"}
|
|
57
59
|
)
|
|
58
60
|
|
|
59
61
|
# Valid keys within the [terminology-auth] and [bulk-auth] tables.
|
|
@@ -61,6 +63,17 @@ VALID_AUTH_KEYS = frozenset(
|
|
|
61
63
|
{"client-id", "client-secret", "private-key-jwk", "token-endpoint", "scope"}
|
|
62
64
|
)
|
|
63
65
|
|
|
66
|
+
# Valid keys within the [tx-store] table.
|
|
67
|
+
VALID_TX_STORE_KEYS = frozenset(
|
|
68
|
+
{
|
|
69
|
+
"path",
|
|
70
|
+
"default-snomed-edition",
|
|
71
|
+
"expansion-cache-size",
|
|
72
|
+
"dialect-aliases",
|
|
73
|
+
"default-dialect",
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
64
77
|
|
|
65
78
|
@dataclass
|
|
66
79
|
class TxAuth:
|
|
@@ -116,6 +129,37 @@ class BulkAuth:
|
|
|
116
129
|
return "no credential"
|
|
117
130
|
|
|
118
131
|
|
|
132
|
+
@dataclass
|
|
133
|
+
class TxStore:
|
|
134
|
+
"""Resolved local terminology store settings for a single invocation.
|
|
135
|
+
|
|
136
|
+
A ``TxStore`` exists only when a store path was supplied (via the
|
|
137
|
+
``--tx-store`` flag or the ``tx-store.path`` config key); its presence on
|
|
138
|
+
:class:`CliConfig` is what selects local terminology mode. Tuning fields
|
|
139
|
+
left unset fall through to the library defaults.
|
|
140
|
+
|
|
141
|
+
:param path: the store location; a filesystem path or a URI. Not checked for
|
|
142
|
+
existence.
|
|
143
|
+
:param default_snomed_edition: the SNOMED CT module identifier used to
|
|
144
|
+
disambiguate an ambiguous edition, or None to use the library default.
|
|
145
|
+
:param expansion_cache_size: the per-executor value set expansion cache size,
|
|
146
|
+
or None to use the library default.
|
|
147
|
+
:param dialect_aliases: additional dialect tags recognised when a display or
|
|
148
|
+
designation is requested in a particular language, mapping a language
|
|
149
|
+
tag to a SNOMED CT language reference set identifier, or None.
|
|
150
|
+
:param default_dialect: the dialect whose preferred synonyms become the
|
|
151
|
+
stored display when ``import-snomed`` runs without a
|
|
152
|
+
``--default-dialect`` flag: a tag such as ``en-AU``, or a language
|
|
153
|
+
reference set identifier. None when unset.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
path: str
|
|
157
|
+
default_snomed_edition: Optional[str] = None
|
|
158
|
+
expansion_cache_size: Optional[int] = None
|
|
159
|
+
dialect_aliases: Optional[dict] = None
|
|
160
|
+
default_dialect: Optional[str] = None
|
|
161
|
+
|
|
162
|
+
|
|
119
163
|
@dataclass
|
|
120
164
|
class CliConfig:
|
|
121
165
|
"""Resolved global configuration for a single invocation.
|
|
@@ -131,6 +175,12 @@ class CliConfig:
|
|
|
131
175
|
config file, or None when absent. Carried so ``export`` resolves bulk
|
|
132
176
|
credentials from the already-loaded config rather than re-reading a
|
|
133
177
|
file.
|
|
178
|
+
:param tx_store: the resolved local terminology store settings, or None. When
|
|
179
|
+
present, terminology evaluation uses the local store rather than a
|
|
180
|
+
remote server.
|
|
181
|
+
:param tx_server_explicit: whether the terminology server URL was set
|
|
182
|
+
explicitly (via flag or config key) rather than falling back to the
|
|
183
|
+
built-in default. Drives the store-wins conflict warning.
|
|
134
184
|
"""
|
|
135
185
|
|
|
136
186
|
tx_server: str = DEFAULT_TX_SERVER
|
|
@@ -140,6 +190,8 @@ class CliConfig:
|
|
|
140
190
|
config_path: Optional[Path] = None
|
|
141
191
|
spark_conf: dict = field(default_factory=dict)
|
|
142
192
|
bulk_auth_table: Optional[dict] = None
|
|
193
|
+
tx_store: Optional[TxStore] = None
|
|
194
|
+
tx_server_explicit: bool = False
|
|
143
195
|
|
|
144
196
|
|
|
145
197
|
def _load_toml(path: Path) -> dict:
|
|
@@ -256,6 +308,23 @@ def load_config_file(
|
|
|
256
308
|
f"Ignoring unknown config key '{table_name}.{key}' in "
|
|
257
309
|
f"{path}. Valid keys are: {valid_auth}."
|
|
258
310
|
)
|
|
311
|
+
tx_store_table = data.get("tx-store")
|
|
312
|
+
if isinstance(tx_store_table, dict):
|
|
313
|
+
valid_tx_store = ", ".join(sorted(VALID_TX_STORE_KEYS))
|
|
314
|
+
for key in tx_store_table:
|
|
315
|
+
if key not in VALID_TX_STORE_KEYS:
|
|
316
|
+
warn(
|
|
317
|
+
f"Ignoring unknown config key 'tx-store.{key}' in {path}. "
|
|
318
|
+
f"Valid keys are: {valid_tx_store}."
|
|
319
|
+
)
|
|
320
|
+
elif tx_store_table is not None:
|
|
321
|
+
# A scalar tx-store value cannot configure local mode; warn rather than
|
|
322
|
+
# silently ignoring it (a common mistake is 'tx-store = \"/path\"'
|
|
323
|
+
# instead of a [tx-store] table with a 'path' key).
|
|
324
|
+
warn(
|
|
325
|
+
f"Ignoring 'tx-store' in {path}; it must be a [tx-store] table with "
|
|
326
|
+
"a 'path' key, not a single value."
|
|
327
|
+
)
|
|
259
328
|
return data
|
|
260
329
|
|
|
261
330
|
|
|
@@ -385,6 +454,117 @@ def resolve_bulk_auth(
|
|
|
385
454
|
)
|
|
386
455
|
|
|
387
456
|
|
|
457
|
+
def _resolve_tx_store(
|
|
458
|
+
file_data: dict,
|
|
459
|
+
tx_store_flag: Optional[str],
|
|
460
|
+
on_warning: Optional[Callable[[str], None]],
|
|
461
|
+
) -> Optional[TxStore]:
|
|
462
|
+
"""Resolves and validates the local terminology store settings.
|
|
463
|
+
|
|
464
|
+
The store path follows the precedence ``--tx-store`` flag >
|
|
465
|
+
``tx-store.path`` config key; the tuning keys come from the config table
|
|
466
|
+
only. All type and range checks happen here, before any Spark session, so a
|
|
467
|
+
configuration mistake fails fast (FR-008).
|
|
468
|
+
|
|
469
|
+
:param file_data: the parsed config file contents.
|
|
470
|
+
:param tx_store_flag: the ``--tx-store`` flag value, or None.
|
|
471
|
+
:param on_warning: the warning callback for the path-less-table notice.
|
|
472
|
+
:return: a populated :class:`TxStore`, or None when no store is configured.
|
|
473
|
+
:raises CliError: with EXIT_USAGE for an empty flag or an invalid table value.
|
|
474
|
+
"""
|
|
475
|
+
from pathling.cli.errors import EXIT_USAGE, CliError
|
|
476
|
+
|
|
477
|
+
# An empty flag value is a usage error rather than a silent no-op (FR-008).
|
|
478
|
+
if tx_store_flag is not None and not tx_store_flag:
|
|
479
|
+
raise CliError(
|
|
480
|
+
"The --tx-store value must not be empty. Provide a store path or URI.",
|
|
481
|
+
exit_code=EXIT_USAGE,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
table = file_data.get("tx-store")
|
|
485
|
+
table = table if isinstance(table, dict) else {}
|
|
486
|
+
|
|
487
|
+
config_path = table.get("path")
|
|
488
|
+
if config_path is not None and not isinstance(config_path, str):
|
|
489
|
+
raise CliError(
|
|
490
|
+
"The tx-store.path config value must be a string.",
|
|
491
|
+
exit_code=EXIT_USAGE,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
resolved_path = tx_store_flag or config_path
|
|
495
|
+
|
|
496
|
+
edition = table.get("default-snomed-edition")
|
|
497
|
+
if edition is not None and not isinstance(edition, str):
|
|
498
|
+
raise CliError(
|
|
499
|
+
"The tx-store.default-snomed-edition config value must be a string.",
|
|
500
|
+
exit_code=EXIT_USAGE,
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
cache_size = table.get("expansion-cache-size")
|
|
504
|
+
if cache_size is not None:
|
|
505
|
+
# A bool is an int in Python; reject it so the value is an honest count.
|
|
506
|
+
if isinstance(cache_size, bool) or not isinstance(cache_size, int):
|
|
507
|
+
raise CliError(
|
|
508
|
+
"The tx-store.expansion-cache-size config value must be an integer.",
|
|
509
|
+
exit_code=EXIT_USAGE,
|
|
510
|
+
)
|
|
511
|
+
if cache_size < 1:
|
|
512
|
+
raise CliError(
|
|
513
|
+
"The tx-store.expansion-cache-size config value must be a "
|
|
514
|
+
"positive integer.",
|
|
515
|
+
exit_code=EXIT_USAGE,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
dialect = table.get("default-dialect")
|
|
519
|
+
if dialect is not None and not isinstance(dialect, str):
|
|
520
|
+
raise CliError(
|
|
521
|
+
"The tx-store.default-dialect config value must be a string.",
|
|
522
|
+
exit_code=EXIT_USAGE,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
aliases = table.get("dialect-aliases")
|
|
526
|
+
if aliases is not None and not _is_string_table(aliases):
|
|
527
|
+
# A malformed alias table is a mistake worth reporting, but it cannot make
|
|
528
|
+
# a lookup wrong, so it is warned about and dropped rather than fatal.
|
|
529
|
+
notify = on_warning or (lambda message: print(message, file=sys.stderr))
|
|
530
|
+
notify(
|
|
531
|
+
"Ignoring 'tx-store.dialect-aliases'; it must be a table mapping a "
|
|
532
|
+
"language tag to a SNOMED CT language reference set identifier, for "
|
|
533
|
+
'example [tx-store.dialect-aliases] with en-NZ = "271000210107".'
|
|
534
|
+
)
|
|
535
|
+
aliases = None
|
|
536
|
+
|
|
537
|
+
# Without a path, tuning keys cannot activate local mode; warn that the
|
|
538
|
+
# table is inert rather than silently ignoring it (FR-009).
|
|
539
|
+
if resolved_path is None:
|
|
540
|
+
if table:
|
|
541
|
+
notify = on_warning or (lambda message: print(message, file=sys.stderr))
|
|
542
|
+
notify(
|
|
543
|
+
"The [tx-store] config table has no effect without a 'path' key "
|
|
544
|
+
"(or the --tx-store flag); remote terminology mode will be used."
|
|
545
|
+
)
|
|
546
|
+
return None
|
|
547
|
+
|
|
548
|
+
return TxStore(
|
|
549
|
+
path=resolved_path,
|
|
550
|
+
default_snomed_edition=edition,
|
|
551
|
+
expansion_cache_size=cache_size,
|
|
552
|
+
dialect_aliases=aliases,
|
|
553
|
+
default_dialect=dialect,
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _is_string_table(value: object) -> bool:
|
|
558
|
+
"""Reports whether a config value is a table whose keys and values are strings.
|
|
559
|
+
|
|
560
|
+
:param value: the value read from the config file.
|
|
561
|
+
:return: True when the value is a dict of string to string.
|
|
562
|
+
"""
|
|
563
|
+
return isinstance(value, dict) and all(
|
|
564
|
+
isinstance(key, str) and isinstance(entry, str) for key, entry in value.items()
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
|
|
388
568
|
def resolve_config_source(
|
|
389
569
|
config_path: Optional[Path],
|
|
390
570
|
cwd: Path,
|
|
@@ -425,6 +605,7 @@ def resolve_config(
|
|
|
425
605
|
config_path: Optional[Path] = None,
|
|
426
606
|
cwd: Optional[Path] = None,
|
|
427
607
|
env: Optional[dict] = None,
|
|
608
|
+
tx_store: Optional[str] = None,
|
|
428
609
|
on_warning: Optional[Callable[[str], None]] = None,
|
|
429
610
|
on_notice: Optional[Callable[[str], None]] = None,
|
|
430
611
|
) -> CliConfig:
|
|
@@ -449,6 +630,8 @@ def resolve_config(
|
|
|
449
630
|
:param cwd: the directory searched for a project-local ``pathling.toml``;
|
|
450
631
|
defaults to the current working directory.
|
|
451
632
|
:param env: the environment mapping for secret resolution.
|
|
633
|
+
:param tx_store: the ``--tx-store`` flag value selecting a local terminology
|
|
634
|
+
store, or None.
|
|
452
635
|
:param on_warning: an optional warning callback passed to the file loader and
|
|
453
636
|
used to surface the managed Spark-package version-override warning;
|
|
454
637
|
defaults to writing to stderr so warnings appear even in quiet mode.
|
|
@@ -485,6 +668,10 @@ def resolve_config(
|
|
|
485
668
|
else:
|
|
486
669
|
on_notice(f"Using project config {path}.")
|
|
487
670
|
|
|
671
|
+
# Record whether the server URL was set explicitly (flag or config key) as
|
|
672
|
+
# opposed to falling back to the built-in default; the store-wins conflict
|
|
673
|
+
# warning fires only for an explicit server (FR-004).
|
|
674
|
+
tx_server_explicit = bool(tx_server or file_data.get("tx-server"))
|
|
488
675
|
resolved_tx_server = tx_server or file_data.get("tx-server") or DEFAULT_TX_SERVER
|
|
489
676
|
resolved_fhir_version = (
|
|
490
677
|
fhir_version or file_data.get("fhir-version") or DEFAULT_FHIR_VERSION
|
|
@@ -508,10 +695,15 @@ def resolve_config(
|
|
|
508
695
|
env,
|
|
509
696
|
)
|
|
510
697
|
|
|
698
|
+
# Resolve the local terminology store; its presence selects local mode.
|
|
699
|
+
resolved_tx_store = _resolve_tx_store(file_data, tx_store, on_warning)
|
|
700
|
+
|
|
511
701
|
# Some terminology auth input was supplied but it is insufficient to
|
|
512
702
|
# authenticate (a client ID and a token endpoint are both required); tell the
|
|
513
|
-
# user rather than silently disabling it (FR-005).
|
|
514
|
-
|
|
703
|
+
# user rather than silently disabling it (FR-005). In local mode the store
|
|
704
|
+
# wins and auth is ignored entirely, so this incomplete-auth warning is
|
|
705
|
+
# suppressed in favour of the store-wins warning below.
|
|
706
|
+
if resolved_tx_store is None and tx_auth is not None and not tx_auth.enabled:
|
|
515
707
|
notify = on_warning or (lambda message: print(message, file=sys.stderr))
|
|
516
708
|
notify(
|
|
517
709
|
"Terminology authentication is incomplete and will be disabled: a "
|
|
@@ -531,6 +723,24 @@ def resolve_config(
|
|
|
531
723
|
resolved_spark = resolve_spark_conf(spark_table, spark_conf_flags, env)
|
|
532
724
|
spark_conf = merge_spark_conf(resolved_spark, on_warning=spark_warn)
|
|
533
725
|
|
|
726
|
+
# When a store is selected it wins over any server or authentication
|
|
727
|
+
# settings; warn about the ones that were explicitly configured so the
|
|
728
|
+
# override is never a silent surprise (FR-004, FR-005). An explicitly set
|
|
729
|
+
# authentication is also disabled so it can never reach the session.
|
|
730
|
+
if resolved_tx_store is not None:
|
|
731
|
+
conflict_warn = on_warning or (lambda message: print(message, file=sys.stderr))
|
|
732
|
+
if tx_server_explicit:
|
|
733
|
+
conflict_warn(
|
|
734
|
+
"A local terminology store is configured, so the terminology "
|
|
735
|
+
"server setting is ignored."
|
|
736
|
+
)
|
|
737
|
+
if tx_auth is not None:
|
|
738
|
+
conflict_warn(
|
|
739
|
+
"A local terminology store is configured, so the terminology "
|
|
740
|
+
"authentication settings are ignored."
|
|
741
|
+
)
|
|
742
|
+
tx_auth = None
|
|
743
|
+
|
|
534
744
|
return CliConfig(
|
|
535
745
|
tx_server=resolved_tx_server,
|
|
536
746
|
tx_auth=tx_auth,
|
|
@@ -539,4 +749,6 @@ def resolve_config(
|
|
|
539
749
|
config_path=path if path.exists() else None,
|
|
540
750
|
spark_conf=spark_conf,
|
|
541
751
|
bulk_auth_table=file_data.get("bulk-auth"),
|
|
752
|
+
tx_store=resolved_tx_store,
|
|
753
|
+
tx_server_explicit=tx_server_explicit,
|
|
542
754
|
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright © 2018-2026 Commonwealth Scientific and Industrial Research
|
|
3
|
+
# Organisation (CSIRO) ABN 41 687 119 230.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
"""The ``pathling console`` command.
|
|
19
|
+
|
|
20
|
+
Opens an interactive IPython session with ``spark`` (the Spark session), ``pc``
|
|
21
|
+
(the configured Pathling context), and the Pathling package's public API
|
|
22
|
+
pre-imported into the user namespace, after a banner identifying the version and
|
|
23
|
+
the variables in scope. The ``pathling`` name is bound to the package module
|
|
24
|
+
itself, so typing ``import pathling`` at the prompt cannot clobber the context.
|
|
25
|
+
The terminology display function is bound as ``tx_display`` rather than
|
|
26
|
+
``display`` so that IPython's built-in ``display`` remains reachable at the
|
|
27
|
+
prompt. IPython is imported inside the command body so that ``--help`` stays
|
|
28
|
+
fast.
|
|
29
|
+
|
|
30
|
+
Author: John Grimes.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import platform
|
|
36
|
+
from typing import TYPE_CHECKING
|
|
37
|
+
|
|
38
|
+
import click
|
|
39
|
+
|
|
40
|
+
from pathling._version import __version__
|
|
41
|
+
from pathling.cli import session
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from pathling.cli.main import CliContext
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_banner() -> str:
|
|
48
|
+
"""Builds the banner shown before the console's first prompt.
|
|
49
|
+
|
|
50
|
+
The banner identifies the Pathling and Python versions, lists the
|
|
51
|
+
variables in scope - naming the context ``pc``, the name a user copies into
|
|
52
|
+
their next command - notes that the Pathling public functions (e.g.
|
|
53
|
+
``member_of``, ``translate``, ``to_coding``) are pre-imported, points to
|
|
54
|
+
the Python API reference for the full list, notes that the terminology
|
|
55
|
+
display is available as ``tx_display`` rather than ``display``, and
|
|
56
|
+
explains how to exit.
|
|
57
|
+
|
|
58
|
+
:return: the banner text.
|
|
59
|
+
"""
|
|
60
|
+
return (
|
|
61
|
+
f"Pathling console (version {__version__}, "
|
|
62
|
+
f"Python {platform.python_version()})\n"
|
|
63
|
+
"Variables in scope: spark (SparkSession), pc (PathlingContext)\n"
|
|
64
|
+
"Pathling public functions are pre-imported (member_of, translate, "
|
|
65
|
+
"to_coding, ...).\n"
|
|
66
|
+
"See https://pathling.csiro.au/docs/python/pathling.html#module-pathling "
|
|
67
|
+
"for the full list.\n"
|
|
68
|
+
"The terminology display function is available as tx_display "
|
|
69
|
+
"(display is IPython's built-in).\n"
|
|
70
|
+
"Type exit or press Ctrl-D to leave.\n"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@click.command(name="console")
|
|
75
|
+
@click.pass_obj
|
|
76
|
+
def console(obj: CliContext) -> None:
|
|
77
|
+
"""Open an interactive console with the Pathling environment ready.
|
|
78
|
+
|
|
79
|
+
Starts an IPython session with spark (the Spark session) and pc
|
|
80
|
+
(the configured Pathling context) in scope. The Pathling public functions
|
|
81
|
+
(member_of, translate, to_coding, and so on) are pre-imported, so no
|
|
82
|
+
"from pathling import ..." is needed; the terminology display is available
|
|
83
|
+
as tx_display, leaving IPython's built-in display unchanged. Exit with
|
|
84
|
+
'exit' or Ctrl-D.
|
|
85
|
+
|
|
86
|
+
\b
|
|
87
|
+
See the Pathling Python API reference:
|
|
88
|
+
https://pathling.csiro.au/docs/python/pathling.html
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
|
|
92
|
+
pathling console
|
|
93
|
+
|
|
94
|
+
pathling --tx-server https://tx.example.org/fhir console
|
|
95
|
+
"""
|
|
96
|
+
pc = session.create_context(obj.config, obj.console)
|
|
97
|
+
|
|
98
|
+
# Pre-import the public API surface, but expose the terminology display as
|
|
99
|
+
# tx_display only: IPython installs its own `display` into the interpreter's
|
|
100
|
+
# built-ins, so binding Pathling's display here would silently shadow it.
|
|
101
|
+
user_ns = session.public_namespace()
|
|
102
|
+
user_ns["tx_display"] = user_ns.pop("display")
|
|
103
|
+
user_ns["spark"] = pc.spark
|
|
104
|
+
user_ns["pc"] = pc
|
|
105
|
+
|
|
106
|
+
import IPython
|
|
107
|
+
from traitlets.config import Config
|
|
108
|
+
|
|
109
|
+
config = Config()
|
|
110
|
+
config.TerminalInteractiveShell.banner1 = build_banner()
|
|
111
|
+
IPython.start_ipython(
|
|
112
|
+
argv=[],
|
|
113
|
+
user_ns=user_ns,
|
|
114
|
+
config=config,
|
|
115
|
+
)
|
|
@@ -24,9 +24,13 @@ counts written.
|
|
|
24
24
|
Author: John Grimes.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
27
29
|
from pathlib import Path
|
|
30
|
+
from typing import TYPE_CHECKING, Optional, Tuple
|
|
28
31
|
|
|
29
32
|
import click
|
|
33
|
+
from rich.console import Console
|
|
30
34
|
from rich.table import Table
|
|
31
35
|
|
|
32
36
|
from pathling.cli import session
|
|
@@ -34,6 +38,10 @@ from pathling.cli.errors import EXIT_USAGE, CliError
|
|
|
34
38
|
from pathling.cli.io import FROM_CHOICES, read_source, resolve_source
|
|
35
39
|
from pathling.cli.render import check_overwrite, progress_status
|
|
36
40
|
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from pathling.cli.main import CliContext
|
|
43
|
+
from pathling.datasource import DataSource
|
|
44
|
+
|
|
37
45
|
# The output formats convert can write.
|
|
38
46
|
TO_CHOICES = ("ndjson", "parquet", "delta")
|
|
39
47
|
|
|
@@ -83,7 +91,16 @@ MODE_CHOICES = ("overwrite", "error", "append", "merge")
|
|
|
83
91
|
help="Replace an existing output path (equivalent to --mode overwrite).",
|
|
84
92
|
)
|
|
85
93
|
@click.pass_obj
|
|
86
|
-
def convert(
|
|
94
|
+
def convert(
|
|
95
|
+
obj: CliContext,
|
|
96
|
+
source: str,
|
|
97
|
+
from_format: Optional[str],
|
|
98
|
+
to_format: str,
|
|
99
|
+
output: str,
|
|
100
|
+
mode: str,
|
|
101
|
+
types: Tuple[str, ...],
|
|
102
|
+
overwrite: bool,
|
|
103
|
+
) -> None:
|
|
87
104
|
"""Convert FHIR data between formats.
|
|
88
105
|
|
|
89
106
|
Examples:
|
|
@@ -134,7 +151,9 @@ def convert(obj, source, from_format, to_format, output, mode, types, overwrite)
|
|
|
134
151
|
_print_summary(console, data_source, output_path)
|
|
135
152
|
|
|
136
153
|
|
|
137
|
-
def _print_summary(
|
|
154
|
+
def _print_summary(
|
|
155
|
+
console: Console, data_source: DataSource, output_path: Path
|
|
156
|
+
) -> None:
|
|
138
157
|
"""Prints a table of the resource types written and the output location.
|
|
139
158
|
|
|
140
159
|
Row counts are deliberately omitted: counting each type would trigger an
|
|
@@ -28,15 +28,22 @@ keeping the move on a single filesystem.
|
|
|
28
28
|
Author: John Grimes.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
31
33
|
from pathlib import Path
|
|
32
|
-
from typing import Union
|
|
34
|
+
from typing import TYPE_CHECKING, Tuple, Union
|
|
33
35
|
|
|
34
36
|
from py4j.java_gateway import is_instance_of
|
|
35
37
|
|
|
36
38
|
from pathling.cli.errors import CliError
|
|
37
39
|
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from pyspark.sql import SparkSession
|
|
42
|
+
|
|
38
43
|
|
|
39
|
-
def _filesystem(
|
|
44
|
+
def _filesystem(
|
|
45
|
+
spark: SparkSession, reference_path: Union[str, Path]
|
|
46
|
+
) -> Tuple[object, object]:
|
|
40
47
|
"""Resolves the Hadoop ``FileSystem`` for a path, unwrapping checksums.
|
|
41
48
|
|
|
42
49
|
The filesystem is resolved from ``reference_path`` so that operations stay
|
|
@@ -63,7 +70,7 @@ def _filesystem(spark, reference_path: Union[str, Path]):
|
|
|
63
70
|
return fs, path_class
|
|
64
71
|
|
|
65
72
|
|
|
66
|
-
def remove_path(spark, path: Union[str, Path]) -> None:
|
|
73
|
+
def remove_path(spark: SparkSession, path: Union[str, Path]) -> None:
|
|
67
74
|
"""Removes a file or directory over its Hadoop ``FileSystem``, if it exists.
|
|
68
75
|
|
|
69
76
|
Used to clear an existing target before an overwrite and to clean up the
|
|
@@ -81,7 +88,7 @@ def remove_path(spark, path: Union[str, Path]) -> None:
|
|
|
81
88
|
|
|
82
89
|
|
|
83
90
|
def departition(
|
|
84
|
-
spark,
|
|
91
|
+
spark: SparkSession,
|
|
85
92
|
source_dir: Union[str, Path],
|
|
86
93
|
target_path: Union[str, Path],
|
|
87
94
|
part_extension: str,
|