0
# Utilities
1
2
Helper functions and classes for data manipulation, parameter transformation, and path-based access. These utilities support common operations needed when working with FHIR data structures.
3
4
## Capabilities
5
6
### Dictionary and List Extensions
7
8
Enhanced dictionary and list classes with path-based access capabilities.
9
10
```python { .api }
11
class AttrDict(dict):
12
"""Dictionary with attribute-style access and path-based operations."""
13
14
def __init__(self, *args, **kwargs):
15
"""Initialize AttrDict from dict or keyword arguments."""
16
17
def get_by_path(self, path: str, default=None):
18
"""
19
Get value using dot-separated path.
20
21
Parameters:
22
- path: Dot-separated path string (e.g., 'address.0.city')
23
- default: Value to return if path not found
24
25
Returns:
26
Value at path or default
27
"""
28
29
class SearchList(list):
30
"""List with path-based access capabilities."""
31
32
def get_by_path(self, path: str, default=None):
33
"""
34
Get value using dot-separated path.
35
36
Parameters:
37
- path: Dot-separated path string
38
- default: Value to return if path not found
39
40
Returns:
41
Value at path or default
42
"""
43
```
44
45
### Collection Utilities
46
47
Functions for working with collections and data processing.
48
49
```python { .api }
50
def chunks(lst: list, n: int) -> Generator:
51
"""
52
Split list into chunks of specified size.
53
54
Parameters:
55
- lst: List to split
56
- n: Chunk size
57
58
Yields:
59
Sublists of size n (last chunk may be smaller)
60
"""
61
62
def unique_everseen(seq: list) -> list:
63
"""
64
Get unique items from sequence, preserving order.
65
66
Parameters:
67
- seq: Input sequence
68
69
Returns:
70
List with unique items in original order
71
"""
72
```
73
74
### Path Operations
75
76
Functions for parsing and manipulating dot-separated paths.
77
78
```python { .api }
79
def parse_path(path: str) -> list:
80
"""
81
Parse dot-separated path into list of keys.
82
83
Parameters:
84
- path: Dot-separated path string
85
86
Returns:
87
List of path components
88
"""
89
90
def get_by_path(obj, path: list, default=None):
91
"""
92
Get value from nested object using path list.
93
94
Parameters:
95
- obj: Object to traverse
96
- path: List of keys/indices
97
- default: Value to return if path not found
98
99
Returns:
100
Value at path or default
101
"""
102
103
def set_by_path(obj, path: str, value):
104
"""
105
Set value in nested object using dot-separated path.
106
107
Parameters:
108
- obj: Object to modify
109
- path: Dot-separated path string
110
- value: Value to set
111
"""
112
113
def convert_values(data, fn):
114
"""
115
Recursively convert data values using provided function.
116
117
Parameters:
118
- data: Data structure to convert (dict, list, or other)
119
- fn: Function that takes a value and returns (converted_value, stop_flag)
120
121
Returns:
122
Converted data structure with fn applied recursively
123
"""
124
```
125
126
### URL and Parameter Encoding
127
128
Functions for encoding parameters and working with URLs.
129
130
```python { .api }
131
def encode_params(params: dict) -> str:
132
"""
133
Encode parameters for URL query string.
134
135
Parameters:
136
- params: Dictionary of parameters
137
138
Returns:
139
URL-encoded query string
140
"""
141
```
142
143
### FHIR Date and Value Formatting
144
145
Functions for formatting dates and values according to FHIR specifications.
146
147
```python { .api }
148
def format_date_time(date: datetime) -> str:
149
"""
150
Format datetime for FHIR (ISO 8601 format).
151
152
Parameters:
153
- date: Python datetime object
154
155
Returns:
156
FHIR-formatted datetime string (YYYY-MM-DDTHH:MM:SSZ)
157
"""
158
159
def format_date(date: date) -> str:
160
"""
161
Format date for FHIR.
162
163
Parameters:
164
- date: Python date object
165
166
Returns:
167
FHIR-formatted date string (YYYY-MM-DD)
168
"""
169
170
def transform_param(param: str) -> str:
171
"""
172
Transform parameter name for FHIR (underscore to dash).
173
174
Parameters:
175
- param: Parameter name with underscores
176
177
Returns:
178
Parameter name with dashes (preserves leading underscore/dot)
179
"""
180
181
def transform_value(value) -> str:
182
"""
183
Transform value for FHIR search parameters.
184
185
Handles:
186
- datetime objects → ISO format strings
187
- date objects → YYYY-MM-DD strings
188
- bool → 'true'/'false' strings
189
- BaseResource/BaseReference → reference strings
190
191
Parameters:
192
- value: Value to transform
193
194
Returns:
195
String representation suitable for FHIR
196
"""
197
```
198
199
## Usage Examples
200
201
### Path-Based Data Access
202
203
```python
204
from fhirpy.base.utils import AttrDict, get_by_path, set_by_path, parse_path
205
206
def path_access_examples():
207
# AttrDict with attribute access
208
patient_data = AttrDict({
209
'name': [
210
{'family': 'Doe', 'given': ['John', 'William']},
211
{'family': 'Smith', 'given': ['Johnny'], 'use': 'nickname'}
212
],
213
'address': [{
214
'line': ['123 Main St', 'Apt 4B'],
215
'city': 'Springfield',
216
'postalCode': '12345'
217
}],
218
'telecom': [
219
{'system': 'phone', 'value': '555-1234'},
220
{'system': 'email', 'value': 'john@example.com'}
221
]
222
})
223
224
# Access using paths
225
family_name = patient_data.get_by_path('name.0.family') # 'Doe'
226
first_given = patient_data.get_by_path('name.0.given.0') # 'John'
227
street = patient_data.get_by_path('address.0.line.0') # '123 Main St'
228
phone = patient_data.get_by_path('telecom.0.value') # '555-1234'
229
230
# Safe access with defaults
231
fax = patient_data.get_by_path('telecom.2.value', 'No fax') # 'No fax'
232
233
print(f"Patient: {first_given} {family_name}")
234
print(f"Address: {street}, {patient_data.get_by_path('address.0.city')}")
235
236
# Attribute-style access
237
print(f"Name array: {patient_data.name}")
238
print(f"City: {patient_data.address[0]['city']}")
239
```
240
241
### Data Manipulation Utilities
242
243
```python
244
from fhirpy.base.utils import chunks, unique_everseen, set_by_path
245
246
def data_manipulation_examples():
247
# Split large datasets into manageable chunks
248
patient_ids = [f"patient-{i}" for i in range(1, 101)] # 100 patient IDs
249
250
# Process in chunks of 20
251
for chunk in chunks(patient_ids, 20):
252
print(f"Processing batch of {len(chunk)} patients")
253
# Process chunk...
254
255
# Remove duplicates while preserving order
256
search_params = ['name', 'birthdate', 'gender', 'name', 'active', 'gender']
257
unique_params = unique_everseen(search_params)
258
print(f"Unique parameters: {unique_params}") # ['name', 'birthdate', 'gender', 'active']
259
260
# Modify nested data structures
261
patient_data = {'name': [{'family': 'Doe'}]}
262
set_by_path(patient_data, 'name.0.given', ['John'])
263
set_by_path(patient_data, 'active', True)
264
set_by_path(patient_data, 'address.0.city', 'Springfield') # Creates nested structure
265
266
print(f"Modified patient: {patient_data}")
267
```
268
269
### FHIR Parameter Transformation
270
271
```python
272
import datetime
273
from fhirpy.base.utils import transform_param, transform_value, format_date_time, format_date
274
275
def parameter_transformation_examples():
276
# Parameter name transformation
277
fhir_param = transform_param('general_practitioner') # 'general-practitioner'
278
preserved = transform_param('_id') # '_id' (unchanged)
279
dot_param = transform_param('.effectiveDate') # '.effectiveDate' (unchanged)
280
281
print(f"Transformed: {fhir_param}")
282
283
# Value transformation for search parameters
284
now = datetime.datetime(2024, 1, 15, 10, 30, 0, tzinfo=datetime.timezone.utc)
285
today = datetime.date(2024, 1, 15)
286
287
datetime_str = transform_value(now) # '2024-01-15T10:30:00Z'
288
date_str = transform_value(today) # '2024-01-15'
289
bool_str = transform_value(True) # 'true'
290
false_str = transform_value(False) # 'false'
291
regular_str = transform_value('text') # 'text'
292
293
print(f"DateTime: {datetime_str}")
294
print(f"Date: {date_str}")
295
print(f"Boolean: {bool_str}")
296
297
# Direct date formatting
298
fhir_datetime = format_date_time(now) # '2024-01-15T10:30:00Z'
299
fhir_date = format_date(today) # '2024-01-15'
300
```
301
302
### URL Parameter Encoding
303
304
```python
305
from fhirpy.base.utils import encode_params
306
307
def parameter_encoding_examples():
308
# Simple parameters
309
params1 = {'name': ['Smith'], 'active': ['true']}
310
query1 = encode_params(params1) # 'name=Smith&active=true'
311
312
# Multiple values for same parameter
313
params2 = {'status:not': ['active', 'entered-in-error']}
314
query2 = encode_params(params2) # 'status:not=active&status:not=entered-in-error'
315
316
# Complex parameters with modifiers
317
params3 = {
318
'name:contains': ['John'],
319
'birthdate:ge': ['1990-01-01'],
320
'address-city': ['Springfield', 'Boston']
321
}
322
query3 = encode_params(params3)
323
324
print(f"Simple: {query1}")
325
print(f"Multiple: {query2}")
326
print(f"Complex: {query3}")
327
```
328
329
### Working with Search Results
330
331
```python
332
from fhirpy.base.utils import AttrDict, SearchList
333
import json
334
335
async def search_results_processing():
336
# Simulate FHIR Bundle response
337
bundle_data = {
338
'resourceType': 'Bundle',
339
'total': 3,
340
'entry': [
341
{
342
'resource': {
343
'resourceType': 'Patient',
344
'id': '1',
345
'name': [{'family': 'Doe', 'given': ['John']}],
346
'address': [{'city': 'Springfield'}]
347
}
348
},
349
{
350
'resource': {
351
'resourceType': 'Patient',
352
'id': '2',
353
'name': [{'family': 'Smith', 'given': ['Jane']}],
354
'address': [{'city': 'Boston'}]
355
}
356
}
357
]
358
}
359
360
# Convert to AttrDict for easier access
361
bundle = AttrDict(bundle_data)
362
363
# Extract patient data with path operations
364
patients = SearchList()
365
for entry in bundle.entry:
366
patient = AttrDict(entry.resource)
367
patients.append(patient)
368
369
# Process patients using path-based access
370
for patient in patients:
371
name = patient.get_by_path('name.0.family')
372
given = patient.get_by_path('name.0.given.0')
373
city = patient.get_by_path('address.0.city', 'Unknown')
374
375
print(f"Patient {patient.id}: {given} {name} from {city}")
376
377
# Aggregate data
378
cities = [p.get_by_path('address.0.city') for p in patients if p.get_by_path('address.0.city')]
379
unique_cities = unique_everseen(cities)
380
print(f"Cities represented: {unique_cities}")
381
```
382
383
### Utility Integration with FHIR Operations
384
385
```python
386
from fhirpy import AsyncFHIRClient
387
from fhirpy.base.utils import chunks, transform_value
388
import datetime
389
390
async def utility_integration_example():
391
client = AsyncFHIRClient('https://hapi.fhir.org/baseR4')
392
393
# Batch process large number of patients
394
patient_ids = [f"patient-{i}" for i in range(1, 101)]
395
396
all_patients = []
397
for id_chunk in chunks(patient_ids, 10): # Process 10 at a time
398
# Build search for this chunk
399
id_query = '|'.join(id_chunk) # FHIR OR syntax
400
401
patients = await (client.resources('Patient')
402
.search(_id=id_query)
403
.fetch())
404
all_patients.extend(patients)
405
406
print(f"Processed {len(all_patients)} patients")
407
408
# Use date transformation for search
409
cutoff_date = datetime.date(2020, 1, 1)
410
recent_patients = await (client.resources('Patient')
411
.search(birthdate__ge=transform_value(cutoff_date))
412
.fetch())
413
414
print(f"Found {len(recent_patients)} patients born after {cutoff_date}")
415
416
# Process results with path operations
417
for patient in recent_patients:
418
birth_date = patient.get_by_path('birthDate')
419
name = patient.get_by_path('name.0.family', 'Unknown')
420
print(f"{name}: {birth_date}")
421
```