dic-helper-toolkit 1.0.2__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.
- dic_helper_toolkit-1.0.2/PKG-INFO +152 -0
- dic_helper_toolkit-1.0.2/README.md +126 -0
- dic_helper_toolkit-1.0.2/dic_helper_toolkit.egg-info/PKG-INFO +152 -0
- dic_helper_toolkit-1.0.2/dic_helper_toolkit.egg-info/SOURCES.txt +16 -0
- dic_helper_toolkit-1.0.2/dic_helper_toolkit.egg-info/dependency_links.txt +1 -0
- dic_helper_toolkit-1.0.2/dic_helper_toolkit.egg-info/requires.txt +19 -0
- dic_helper_toolkit-1.0.2/dic_helper_toolkit.egg-info/top_level.txt +1 -0
- dic_helper_toolkit-1.0.2/dic_helpers/__init__.py +0 -0
- dic_helper_toolkit-1.0.2/dic_helpers/directory_reader.py +80 -0
- dic_helper_toolkit-1.0.2/dic_helpers/fhir_client.py +314 -0
- dic_helper_toolkit-1.0.2/dic_helpers/fhir_file_reader.py +307 -0
- dic_helper_toolkit-1.0.2/dic_helpers/http_client.py +300 -0
- dic_helper_toolkit-1.0.2/pyproject.toml +41 -0
- dic_helper_toolkit-1.0.2/setup.cfg +4 -0
- dic_helper_toolkit-1.0.2/tests/test_directory_reader.py +199 -0
- dic_helper_toolkit-1.0.2/tests/test_fhir_client.py +516 -0
- dic_helper_toolkit-1.0.2/tests/test_fhir_file_reader.py +315 -0
- dic_helper_toolkit-1.0.2/tests/test_http_client.py +108 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dic-helper-toolkit
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Helper libraries for data integration centers' daily tasks
|
|
5
|
+
Project-URL: Repository, https://github.com/haemka/dic-helper-toolkit
|
|
6
|
+
Project-URL: Issues, https://github.com/haemka/dic-helper-toolkit/issues
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: requests~=2.34.2
|
|
10
|
+
Requires-Dist: requests-oauthlib~=2.0.0
|
|
11
|
+
Requires-Dist: oauthlib~=3.3.1
|
|
12
|
+
Requires-Dist: fhir.resources~=8.2.0
|
|
13
|
+
Requires-Dist: pytest~=9.0.3
|
|
14
|
+
Requires-Dist: certifi~=2026.5.20
|
|
15
|
+
Requires-Dist: charset-normalizer~=3.4.7
|
|
16
|
+
Requires-Dist: pydantic~=2.13.4
|
|
17
|
+
Requires-Dist: typing-extensions~=4.15.0
|
|
18
|
+
Requires-Dist: typing-inspection~=0.4.2
|
|
19
|
+
Requires-Dist: responses~=0.26.1
|
|
20
|
+
Provides-Extra: xml
|
|
21
|
+
Requires-Dist: lxml~=6.1; extra == "xml"
|
|
22
|
+
Provides-Extra: tests
|
|
23
|
+
Requires-Dist: pytest; extra == "tests"
|
|
24
|
+
Requires-Dist: responses; extra == "tests"
|
|
25
|
+
Requires-Dist: lxml~=6.1; extra == "tests"
|
|
26
|
+
|
|
27
|
+
# Python DIC Helper Toolkit
|
|
28
|
+
|
|
29
|
+
This Toolkit contains helper libraries for the data integration centers daily tasks.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```shell
|
|
34
|
+
pip install dic-helper-toolkit
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The package is published on [PyPI](https://pypi.org/project/dic-helper-toolkit/). To use the FHIR XML reader, install
|
|
38
|
+
the `xml` extra: `pip install dic-helper-toolkit[xml]`.
|
|
39
|
+
|
|
40
|
+
## Contents
|
|
41
|
+
|
|
42
|
+
### HTTP client
|
|
43
|
+
|
|
44
|
+
A wrapper around the `requests` library which allows to create an object for an endpoint holding all needed
|
|
45
|
+
configuration including authentication and certificate verification. It supports Basic Auth and OAuth 2.0.
|
|
46
|
+
|
|
47
|
+
#### Usage:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from dic_helpers.http_client import HTTPClient
|
|
51
|
+
|
|
52
|
+
## No authentication or CA certificate at all
|
|
53
|
+
client = HTTPClient(base_url="https://example.com")
|
|
54
|
+
|
|
55
|
+
## Basic Auth with CA certificate
|
|
56
|
+
#client = HTTPClient(base_url="https://example.com", username="user", password="P4SSW0RD", ca_file="MyCA.crt")
|
|
57
|
+
|
|
58
|
+
## OAuth 2.0 with username and password
|
|
59
|
+
#client = HTTPClient(base_url="https://example.com", username="user", password="P4SSW0RD",
|
|
60
|
+
# auth_url="https://keycloak.example.com", realm="MyRealm", client_id="MyClient",
|
|
61
|
+
# client_secret="00000000-00000000-00000000-00000000", ca_file="MyCA.crt")
|
|
62
|
+
|
|
63
|
+
with client.get("index.html") as result:
|
|
64
|
+
print(result)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### FHIR client
|
|
69
|
+
|
|
70
|
+
A simple FHIR client library which uses the HTTP client from above and handles resources directly as `fhir.resources`
|
|
71
|
+
(https://github.com/nazrulworld/fhir.resources) object.
|
|
72
|
+
|
|
73
|
+
#### Usage:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from dic_helpers.fhir_client import FHIRClient
|
|
77
|
+
|
|
78
|
+
client = FHIRClient(base_url="https://example.com", username="user", password="P4SSW0RD",
|
|
79
|
+
auth_url="https://keycloak.example.com", realm="MyRealm", client_id="MyClient",
|
|
80
|
+
client_secret="00000000-00000000-00000000-00000000", ca_file="MyCA.crt")
|
|
81
|
+
|
|
82
|
+
# Fetch a patient
|
|
83
|
+
patients = client.search("Patient", "identifier=0123456789&_lastUpdated=ge2025-01-01"):
|
|
84
|
+
for patient in patients:
|
|
85
|
+
print(patient)
|
|
86
|
+
```
|
|
87
|
+
*Keep in mind, that this returns an object.*
|
|
88
|
+
|
|
89
|
+
### Directory reader
|
|
90
|
+
|
|
91
|
+
Lists the files of a directory, optionally including all subdirectories, whose names match shell-style patterns.
|
|
92
|
+
|
|
93
|
+
#### Usage:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from dic_helpers.directory_reader import DirectoryReader
|
|
97
|
+
|
|
98
|
+
reader = DirectoryReader("/data/export", patterns=["*.json", "*.ndjson"], recursive=True)
|
|
99
|
+
for path in reader.list_files():
|
|
100
|
+
print(path)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- **Only regular files** are listed, sorted by path, as `pathlib.Path` objects that start with the directory as given.
|
|
104
|
+
- **`patterns`** (default `"*"`) are matched case-sensitively against the file names (`*`, `?`, `[seq]`). A single
|
|
105
|
+
pattern can be a string; a file is listed if it matches any pattern. Special characters in the directory's own name
|
|
106
|
+
don't matter.
|
|
107
|
+
- **`recursive`** (default `False`) also lists the files of all subdirectories.
|
|
108
|
+
- **`include_hidden`** (default `False`): hidden files (names starting with `.`) are skipped and hidden directories
|
|
109
|
+
are not read, unless this is `True`.
|
|
110
|
+
- **`follow_symlinks`** (default `False`): symbolic links to directories are only read if this is `True`. Every
|
|
111
|
+
directory is read once at most, so symlink loops end. Symbolic links to files are always listed, broken links never.
|
|
112
|
+
- **Errors:** a missing directory raises `FileNotFoundError`, a file instead of a directory `NotADirectoryError`, and
|
|
113
|
+
an unreadable directory `PermissionError`. An empty list therefore always means that no file matched.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
### FHIR file reader
|
|
117
|
+
|
|
118
|
+
Reads FHIR R4B resources from files as `fhir.resources` objects, together with the file and line they were read from.
|
|
119
|
+
Combined with the directory reader:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from dic_helpers.directory_reader import DirectoryReader
|
|
123
|
+
from dic_helpers.fhir_file_reader import FHIRFileReader
|
|
124
|
+
|
|
125
|
+
paths = DirectoryReader("/data/export", patterns=["*.json", "*.ndjson", "*.xml", "*.gz"], recursive=True).list_files()
|
|
126
|
+
for item in FHIRFileReader(paths).resources():
|
|
127
|
+
print(item.path, item.line, item.resource.get_resource_type(), item.resource.id)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Formats**, by file extension (case-insensitive):
|
|
131
|
+
|
|
132
|
+
| Extension | Content | `item.line` |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `.json` | one resource | `None` |
|
|
135
|
+
| `.ndjson` | one resource per line (FHIR bulk data), empty lines are skipped | line number, from 1 |
|
|
136
|
+
| `.xml` | one resource in FHIR XML, the resource type comes from the root element | `None` |
|
|
137
|
+
| `.json.gz`, `.ndjson.gz`, `.xml.gz` | the same, gzip compressed | as above |
|
|
138
|
+
|
|
139
|
+
Other extensions are an error, the format is not guessed. Text is UTF-8, a byte order mark is allowed. Reading XML
|
|
140
|
+
needs `lxml`: install `dic-helper-toolkit[xml]`. External entities are not resolved, an XML file that references one is
|
|
141
|
+
invalid.
|
|
142
|
+
|
|
143
|
+
**Parameters:**
|
|
144
|
+
- **`paths`**: the files, read in this order; a single path can be given on its own. Resources come in the order they
|
|
145
|
+
appear in each file. NDJSON files are read line by line, also compressed ones; JSON and XML files as a whole.
|
|
146
|
+
- **`unpack_bundles`** (default `True`): a Bundle gives the resources of its entries, of any Bundle type. Only the
|
|
147
|
+
outermost Bundle is unpacked, entries without a resource are left out. With `False` the Bundle is returned.
|
|
148
|
+
- **`on_error`** (default `"raise"`): an unreadable or invalid file, NDJSON line or resource raises `FHIRFileError`,
|
|
149
|
+
whose message starts with the file and line, e.g. `export/Patient.ndjson:17: invalid JSON: …` or
|
|
150
|
+
`patient.json: invalid Patient: birthDate: Date value string does not match spec regex.`. With `"skip"` the message
|
|
151
|
+
is logged as a warning, and reading continues with the next line (NDJSON) or the next file. A missing `lxml` is
|
|
152
|
+
always an error.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Python DIC Helper Toolkit
|
|
2
|
+
|
|
3
|
+
This Toolkit contains helper libraries for the data integration centers daily tasks.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
pip install dic-helper-toolkit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package is published on [PyPI](https://pypi.org/project/dic-helper-toolkit/). To use the FHIR XML reader, install
|
|
12
|
+
the `xml` extra: `pip install dic-helper-toolkit[xml]`.
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
|
|
16
|
+
### HTTP client
|
|
17
|
+
|
|
18
|
+
A wrapper around the `requests` library which allows to create an object for an endpoint holding all needed
|
|
19
|
+
configuration including authentication and certificate verification. It supports Basic Auth and OAuth 2.0.
|
|
20
|
+
|
|
21
|
+
#### Usage:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from dic_helpers.http_client import HTTPClient
|
|
25
|
+
|
|
26
|
+
## No authentication or CA certificate at all
|
|
27
|
+
client = HTTPClient(base_url="https://example.com")
|
|
28
|
+
|
|
29
|
+
## Basic Auth with CA certificate
|
|
30
|
+
#client = HTTPClient(base_url="https://example.com", username="user", password="P4SSW0RD", ca_file="MyCA.crt")
|
|
31
|
+
|
|
32
|
+
## OAuth 2.0 with username and password
|
|
33
|
+
#client = HTTPClient(base_url="https://example.com", username="user", password="P4SSW0RD",
|
|
34
|
+
# auth_url="https://keycloak.example.com", realm="MyRealm", client_id="MyClient",
|
|
35
|
+
# client_secret="00000000-00000000-00000000-00000000", ca_file="MyCA.crt")
|
|
36
|
+
|
|
37
|
+
with client.get("index.html") as result:
|
|
38
|
+
print(result)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
### FHIR client
|
|
43
|
+
|
|
44
|
+
A simple FHIR client library which uses the HTTP client from above and handles resources directly as `fhir.resources`
|
|
45
|
+
(https://github.com/nazrulworld/fhir.resources) object.
|
|
46
|
+
|
|
47
|
+
#### Usage:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from dic_helpers.fhir_client import FHIRClient
|
|
51
|
+
|
|
52
|
+
client = FHIRClient(base_url="https://example.com", username="user", password="P4SSW0RD",
|
|
53
|
+
auth_url="https://keycloak.example.com", realm="MyRealm", client_id="MyClient",
|
|
54
|
+
client_secret="00000000-00000000-00000000-00000000", ca_file="MyCA.crt")
|
|
55
|
+
|
|
56
|
+
# Fetch a patient
|
|
57
|
+
patients = client.search("Patient", "identifier=0123456789&_lastUpdated=ge2025-01-01"):
|
|
58
|
+
for patient in patients:
|
|
59
|
+
print(patient)
|
|
60
|
+
```
|
|
61
|
+
*Keep in mind, that this returns an object.*
|
|
62
|
+
|
|
63
|
+
### Directory reader
|
|
64
|
+
|
|
65
|
+
Lists the files of a directory, optionally including all subdirectories, whose names match shell-style patterns.
|
|
66
|
+
|
|
67
|
+
#### Usage:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from dic_helpers.directory_reader import DirectoryReader
|
|
71
|
+
|
|
72
|
+
reader = DirectoryReader("/data/export", patterns=["*.json", "*.ndjson"], recursive=True)
|
|
73
|
+
for path in reader.list_files():
|
|
74
|
+
print(path)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- **Only regular files** are listed, sorted by path, as `pathlib.Path` objects that start with the directory as given.
|
|
78
|
+
- **`patterns`** (default `"*"`) are matched case-sensitively against the file names (`*`, `?`, `[seq]`). A single
|
|
79
|
+
pattern can be a string; a file is listed if it matches any pattern. Special characters in the directory's own name
|
|
80
|
+
don't matter.
|
|
81
|
+
- **`recursive`** (default `False`) also lists the files of all subdirectories.
|
|
82
|
+
- **`include_hidden`** (default `False`): hidden files (names starting with `.`) are skipped and hidden directories
|
|
83
|
+
are not read, unless this is `True`.
|
|
84
|
+
- **`follow_symlinks`** (default `False`): symbolic links to directories are only read if this is `True`. Every
|
|
85
|
+
directory is read once at most, so symlink loops end. Symbolic links to files are always listed, broken links never.
|
|
86
|
+
- **Errors:** a missing directory raises `FileNotFoundError`, a file instead of a directory `NotADirectoryError`, and
|
|
87
|
+
an unreadable directory `PermissionError`. An empty list therefore always means that no file matched.
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
### FHIR file reader
|
|
91
|
+
|
|
92
|
+
Reads FHIR R4B resources from files as `fhir.resources` objects, together with the file and line they were read from.
|
|
93
|
+
Combined with the directory reader:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from dic_helpers.directory_reader import DirectoryReader
|
|
97
|
+
from dic_helpers.fhir_file_reader import FHIRFileReader
|
|
98
|
+
|
|
99
|
+
paths = DirectoryReader("/data/export", patterns=["*.json", "*.ndjson", "*.xml", "*.gz"], recursive=True).list_files()
|
|
100
|
+
for item in FHIRFileReader(paths).resources():
|
|
101
|
+
print(item.path, item.line, item.resource.get_resource_type(), item.resource.id)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Formats**, by file extension (case-insensitive):
|
|
105
|
+
|
|
106
|
+
| Extension | Content | `item.line` |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `.json` | one resource | `None` |
|
|
109
|
+
| `.ndjson` | one resource per line (FHIR bulk data), empty lines are skipped | line number, from 1 |
|
|
110
|
+
| `.xml` | one resource in FHIR XML, the resource type comes from the root element | `None` |
|
|
111
|
+
| `.json.gz`, `.ndjson.gz`, `.xml.gz` | the same, gzip compressed | as above |
|
|
112
|
+
|
|
113
|
+
Other extensions are an error, the format is not guessed. Text is UTF-8, a byte order mark is allowed. Reading XML
|
|
114
|
+
needs `lxml`: install `dic-helper-toolkit[xml]`. External entities are not resolved, an XML file that references one is
|
|
115
|
+
invalid.
|
|
116
|
+
|
|
117
|
+
**Parameters:**
|
|
118
|
+
- **`paths`**: the files, read in this order; a single path can be given on its own. Resources come in the order they
|
|
119
|
+
appear in each file. NDJSON files are read line by line, also compressed ones; JSON and XML files as a whole.
|
|
120
|
+
- **`unpack_bundles`** (default `True`): a Bundle gives the resources of its entries, of any Bundle type. Only the
|
|
121
|
+
outermost Bundle is unpacked, entries without a resource are left out. With `False` the Bundle is returned.
|
|
122
|
+
- **`on_error`** (default `"raise"`): an unreadable or invalid file, NDJSON line or resource raises `FHIRFileError`,
|
|
123
|
+
whose message starts with the file and line, e.g. `export/Patient.ndjson:17: invalid JSON: …` or
|
|
124
|
+
`patient.json: invalid Patient: birthDate: Date value string does not match spec regex.`. With `"skip"` the message
|
|
125
|
+
is logged as a warning, and reading continues with the next line (NDJSON) or the next file. A missing `lxml` is
|
|
126
|
+
always an error.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dic-helper-toolkit
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Helper libraries for data integration centers' daily tasks
|
|
5
|
+
Project-URL: Repository, https://github.com/haemka/dic-helper-toolkit
|
|
6
|
+
Project-URL: Issues, https://github.com/haemka/dic-helper-toolkit/issues
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: requests~=2.34.2
|
|
10
|
+
Requires-Dist: requests-oauthlib~=2.0.0
|
|
11
|
+
Requires-Dist: oauthlib~=3.3.1
|
|
12
|
+
Requires-Dist: fhir.resources~=8.2.0
|
|
13
|
+
Requires-Dist: pytest~=9.0.3
|
|
14
|
+
Requires-Dist: certifi~=2026.5.20
|
|
15
|
+
Requires-Dist: charset-normalizer~=3.4.7
|
|
16
|
+
Requires-Dist: pydantic~=2.13.4
|
|
17
|
+
Requires-Dist: typing-extensions~=4.15.0
|
|
18
|
+
Requires-Dist: typing-inspection~=0.4.2
|
|
19
|
+
Requires-Dist: responses~=0.26.1
|
|
20
|
+
Provides-Extra: xml
|
|
21
|
+
Requires-Dist: lxml~=6.1; extra == "xml"
|
|
22
|
+
Provides-Extra: tests
|
|
23
|
+
Requires-Dist: pytest; extra == "tests"
|
|
24
|
+
Requires-Dist: responses; extra == "tests"
|
|
25
|
+
Requires-Dist: lxml~=6.1; extra == "tests"
|
|
26
|
+
|
|
27
|
+
# Python DIC Helper Toolkit
|
|
28
|
+
|
|
29
|
+
This Toolkit contains helper libraries for the data integration centers daily tasks.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```shell
|
|
34
|
+
pip install dic-helper-toolkit
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The package is published on [PyPI](https://pypi.org/project/dic-helper-toolkit/). To use the FHIR XML reader, install
|
|
38
|
+
the `xml` extra: `pip install dic-helper-toolkit[xml]`.
|
|
39
|
+
|
|
40
|
+
## Contents
|
|
41
|
+
|
|
42
|
+
### HTTP client
|
|
43
|
+
|
|
44
|
+
A wrapper around the `requests` library which allows to create an object for an endpoint holding all needed
|
|
45
|
+
configuration including authentication and certificate verification. It supports Basic Auth and OAuth 2.0.
|
|
46
|
+
|
|
47
|
+
#### Usage:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from dic_helpers.http_client import HTTPClient
|
|
51
|
+
|
|
52
|
+
## No authentication or CA certificate at all
|
|
53
|
+
client = HTTPClient(base_url="https://example.com")
|
|
54
|
+
|
|
55
|
+
## Basic Auth with CA certificate
|
|
56
|
+
#client = HTTPClient(base_url="https://example.com", username="user", password="P4SSW0RD", ca_file="MyCA.crt")
|
|
57
|
+
|
|
58
|
+
## OAuth 2.0 with username and password
|
|
59
|
+
#client = HTTPClient(base_url="https://example.com", username="user", password="P4SSW0RD",
|
|
60
|
+
# auth_url="https://keycloak.example.com", realm="MyRealm", client_id="MyClient",
|
|
61
|
+
# client_secret="00000000-00000000-00000000-00000000", ca_file="MyCA.crt")
|
|
62
|
+
|
|
63
|
+
with client.get("index.html") as result:
|
|
64
|
+
print(result)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### FHIR client
|
|
69
|
+
|
|
70
|
+
A simple FHIR client library which uses the HTTP client from above and handles resources directly as `fhir.resources`
|
|
71
|
+
(https://github.com/nazrulworld/fhir.resources) object.
|
|
72
|
+
|
|
73
|
+
#### Usage:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from dic_helpers.fhir_client import FHIRClient
|
|
77
|
+
|
|
78
|
+
client = FHIRClient(base_url="https://example.com", username="user", password="P4SSW0RD",
|
|
79
|
+
auth_url="https://keycloak.example.com", realm="MyRealm", client_id="MyClient",
|
|
80
|
+
client_secret="00000000-00000000-00000000-00000000", ca_file="MyCA.crt")
|
|
81
|
+
|
|
82
|
+
# Fetch a patient
|
|
83
|
+
patients = client.search("Patient", "identifier=0123456789&_lastUpdated=ge2025-01-01"):
|
|
84
|
+
for patient in patients:
|
|
85
|
+
print(patient)
|
|
86
|
+
```
|
|
87
|
+
*Keep in mind, that this returns an object.*
|
|
88
|
+
|
|
89
|
+
### Directory reader
|
|
90
|
+
|
|
91
|
+
Lists the files of a directory, optionally including all subdirectories, whose names match shell-style patterns.
|
|
92
|
+
|
|
93
|
+
#### Usage:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from dic_helpers.directory_reader import DirectoryReader
|
|
97
|
+
|
|
98
|
+
reader = DirectoryReader("/data/export", patterns=["*.json", "*.ndjson"], recursive=True)
|
|
99
|
+
for path in reader.list_files():
|
|
100
|
+
print(path)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- **Only regular files** are listed, sorted by path, as `pathlib.Path` objects that start with the directory as given.
|
|
104
|
+
- **`patterns`** (default `"*"`) are matched case-sensitively against the file names (`*`, `?`, `[seq]`). A single
|
|
105
|
+
pattern can be a string; a file is listed if it matches any pattern. Special characters in the directory's own name
|
|
106
|
+
don't matter.
|
|
107
|
+
- **`recursive`** (default `False`) also lists the files of all subdirectories.
|
|
108
|
+
- **`include_hidden`** (default `False`): hidden files (names starting with `.`) are skipped and hidden directories
|
|
109
|
+
are not read, unless this is `True`.
|
|
110
|
+
- **`follow_symlinks`** (default `False`): symbolic links to directories are only read if this is `True`. Every
|
|
111
|
+
directory is read once at most, so symlink loops end. Symbolic links to files are always listed, broken links never.
|
|
112
|
+
- **Errors:** a missing directory raises `FileNotFoundError`, a file instead of a directory `NotADirectoryError`, and
|
|
113
|
+
an unreadable directory `PermissionError`. An empty list therefore always means that no file matched.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
### FHIR file reader
|
|
117
|
+
|
|
118
|
+
Reads FHIR R4B resources from files as `fhir.resources` objects, together with the file and line they were read from.
|
|
119
|
+
Combined with the directory reader:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from dic_helpers.directory_reader import DirectoryReader
|
|
123
|
+
from dic_helpers.fhir_file_reader import FHIRFileReader
|
|
124
|
+
|
|
125
|
+
paths = DirectoryReader("/data/export", patterns=["*.json", "*.ndjson", "*.xml", "*.gz"], recursive=True).list_files()
|
|
126
|
+
for item in FHIRFileReader(paths).resources():
|
|
127
|
+
print(item.path, item.line, item.resource.get_resource_type(), item.resource.id)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Formats**, by file extension (case-insensitive):
|
|
131
|
+
|
|
132
|
+
| Extension | Content | `item.line` |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `.json` | one resource | `None` |
|
|
135
|
+
| `.ndjson` | one resource per line (FHIR bulk data), empty lines are skipped | line number, from 1 |
|
|
136
|
+
| `.xml` | one resource in FHIR XML, the resource type comes from the root element | `None` |
|
|
137
|
+
| `.json.gz`, `.ndjson.gz`, `.xml.gz` | the same, gzip compressed | as above |
|
|
138
|
+
|
|
139
|
+
Other extensions are an error, the format is not guessed. Text is UTF-8, a byte order mark is allowed. Reading XML
|
|
140
|
+
needs `lxml`: install `dic-helper-toolkit[xml]`. External entities are not resolved, an XML file that references one is
|
|
141
|
+
invalid.
|
|
142
|
+
|
|
143
|
+
**Parameters:**
|
|
144
|
+
- **`paths`**: the files, read in this order; a single path can be given on its own. Resources come in the order they
|
|
145
|
+
appear in each file. NDJSON files are read line by line, also compressed ones; JSON and XML files as a whole.
|
|
146
|
+
- **`unpack_bundles`** (default `True`): a Bundle gives the resources of its entries, of any Bundle type. Only the
|
|
147
|
+
outermost Bundle is unpacked, entries without a resource are left out. With `False` the Bundle is returned.
|
|
148
|
+
- **`on_error`** (default `"raise"`): an unreadable or invalid file, NDJSON line or resource raises `FHIRFileError`,
|
|
149
|
+
whose message starts with the file and line, e.g. `export/Patient.ndjson:17: invalid JSON: …` or
|
|
150
|
+
`patient.json: invalid Patient: birthDate: Date value string does not match spec regex.`. With `"skip"` the message
|
|
151
|
+
is logged as a warning, and reading continues with the next line (NDJSON) or the next file. A missing `lxml` is
|
|
152
|
+
always an error.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
dic_helper_toolkit.egg-info/PKG-INFO
|
|
4
|
+
dic_helper_toolkit.egg-info/SOURCES.txt
|
|
5
|
+
dic_helper_toolkit.egg-info/dependency_links.txt
|
|
6
|
+
dic_helper_toolkit.egg-info/requires.txt
|
|
7
|
+
dic_helper_toolkit.egg-info/top_level.txt
|
|
8
|
+
dic_helpers/__init__.py
|
|
9
|
+
dic_helpers/directory_reader.py
|
|
10
|
+
dic_helpers/fhir_client.py
|
|
11
|
+
dic_helpers/fhir_file_reader.py
|
|
12
|
+
dic_helpers/http_client.py
|
|
13
|
+
tests/test_directory_reader.py
|
|
14
|
+
tests/test_fhir_client.py
|
|
15
|
+
tests/test_fhir_file_reader.py
|
|
16
|
+
tests/test_http_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
requests~=2.34.2
|
|
2
|
+
requests-oauthlib~=2.0.0
|
|
3
|
+
oauthlib~=3.3.1
|
|
4
|
+
fhir.resources~=8.2.0
|
|
5
|
+
pytest~=9.0.3
|
|
6
|
+
certifi~=2026.5.20
|
|
7
|
+
charset-normalizer~=3.4.7
|
|
8
|
+
pydantic~=2.13.4
|
|
9
|
+
typing-extensions~=4.15.0
|
|
10
|
+
typing-inspection~=0.4.2
|
|
11
|
+
responses~=0.26.1
|
|
12
|
+
|
|
13
|
+
[tests]
|
|
14
|
+
pytest
|
|
15
|
+
responses
|
|
16
|
+
lxml~=6.1
|
|
17
|
+
|
|
18
|
+
[xml]
|
|
19
|
+
lxml~=6.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dic_helpers
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Listing the files of a directory, optionally including its subdirectories."""
|
|
2
|
+
import fnmatch
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Iterable, List, Set, Tuple, Union
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DirectoryReader:
|
|
9
|
+
"""
|
|
10
|
+
Lists the files of a directory whose names match one of the given patterns.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, directory: Union[str, os.PathLike], patterns: Union[str, Iterable[str]] = ("*",),
|
|
14
|
+
recursive: bool = False, include_hidden: bool = False, follow_symlinks: bool = False) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Initialize the DirectoryReader. The directory is only read by list_files().
|
|
17
|
+
|
|
18
|
+
:param directory: The directory to read.
|
|
19
|
+
:type directory: Union[str, os.PathLike]
|
|
20
|
+
:param patterns: Shell-style patterns (fnmatch: *, ?, [seq]) matched case-sensitively against the file names,
|
|
21
|
+
e.g. ("*.json", "*.ndjson"). A single pattern can be given as a string. A file is listed if it matches any
|
|
22
|
+
pattern.
|
|
23
|
+
:type patterns: Union[str, Iterable[str]]
|
|
24
|
+
:param recursive: Also list the files of all subdirectories.
|
|
25
|
+
:type recursive: bool
|
|
26
|
+
:param include_hidden: Also list hidden files (names starting with '.') and read hidden subdirectories.
|
|
27
|
+
:type include_hidden: bool
|
|
28
|
+
:param follow_symlinks: Also read subdirectories that are symbolic links. Every directory is read once at most,
|
|
29
|
+
so symlink loops end. Symbolic links to files are always listed.
|
|
30
|
+
:type follow_symlinks: bool
|
|
31
|
+
"""
|
|
32
|
+
self.directory = Path(directory)
|
|
33
|
+
self.patterns: Tuple[str, ...] = (patterns,) if isinstance(patterns, str) else tuple(patterns)
|
|
34
|
+
self.recursive = recursive
|
|
35
|
+
self.include_hidden = include_hidden
|
|
36
|
+
self.follow_symlinks = follow_symlinks
|
|
37
|
+
|
|
38
|
+
def list_files(self) -> List[Path]:
|
|
39
|
+
"""
|
|
40
|
+
List the matching files.
|
|
41
|
+
|
|
42
|
+
:return: The paths of the matching regular files, sorted. Each path starts with the directory as given.
|
|
43
|
+
:rtype: List[Path]
|
|
44
|
+
:raises FileNotFoundError: If the directory doesn't exist.
|
|
45
|
+
:raises NotADirectoryError: If the path is not a directory.
|
|
46
|
+
:raises PermissionError: If the directory or one of the subdirectories to read can't be read.
|
|
47
|
+
"""
|
|
48
|
+
if not self.directory.exists():
|
|
49
|
+
raise FileNotFoundError(f"Directory not found: {self.directory}")
|
|
50
|
+
if not self.directory.is_dir():
|
|
51
|
+
raise NotADirectoryError(f"Not a directory: {self.directory}")
|
|
52
|
+
files: List[Path] = []
|
|
53
|
+
self._read(self.directory, files, visited=set())
|
|
54
|
+
return sorted(files)
|
|
55
|
+
|
|
56
|
+
def _read(self, directory: Path, files: List[Path], visited: Set[Tuple[int, int]]) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Add the matching files of a directory to files, and read its subdirectories if recursive.
|
|
59
|
+
|
|
60
|
+
:param directory: The directory to read.
|
|
61
|
+
:type directory: Path
|
|
62
|
+
:param files: The list the matching files are added to.
|
|
63
|
+
:type files: List[Path]
|
|
64
|
+
:param visited: Device and inode of the directories read so far; directories in it are not read again.
|
|
65
|
+
:type visited: Set[Tuple[int, int]]
|
|
66
|
+
"""
|
|
67
|
+
status = directory.stat()
|
|
68
|
+
visited.add((status.st_dev, status.st_ino))
|
|
69
|
+
with os.scandir(directory) as entries:
|
|
70
|
+
for entry in entries:
|
|
71
|
+
if not self.include_hidden and entry.name.startswith("."):
|
|
72
|
+
continue
|
|
73
|
+
# is_file() follows symbolic links: links to files are listed, broken links are not
|
|
74
|
+
if entry.is_file():
|
|
75
|
+
if any(fnmatch.fnmatchcase(entry.name, pattern) for pattern in self.patterns):
|
|
76
|
+
files.append(Path(entry.path))
|
|
77
|
+
elif self.recursive and entry.is_dir(follow_symlinks=self.follow_symlinks):
|
|
78
|
+
status = entry.stat()
|
|
79
|
+
if (status.st_dev, status.st_ino) not in visited:
|
|
80
|
+
self._read(Path(entry.path), files, visited)
|