labfreed 0.2.11__tar.gz → 0.3.0a0__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.
Potentially problematic release.
This version of labfreed might be problematic. Click here for more details.
- {labfreed-0.2.11 → labfreed-0.3.0a0}/CHANGELOG.md +3 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/PKG-INFO +43 -29
- {labfreed-0.2.11 → labfreed-0.3.0a0}/README.md +39 -28
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/__init__.py +1 -1
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/labfreed_infrastructure.py +3 -3
- labfreed-0.3.0a0/labfreed/pac_attributes/api_data_models/request.py +56 -0
- labfreed-0.3.0a0/labfreed/pac_attributes/api_data_models/response.py +184 -0
- labfreed-0.3.0a0/labfreed/pac_attributes/api_data_models/server_capabilities_response.py +7 -0
- labfreed-0.3.0a0/labfreed/pac_attributes/client/__init__.py +1 -0
- labfreed-0.3.0a0/labfreed/pac_attributes/client/attribute_cache.py +78 -0
- labfreed-0.3.0a0/labfreed/pac_attributes/client/client.py +148 -0
- labfreed-0.3.0a0/labfreed/pac_attributes/server/attribute_data_sources.py +69 -0
- labfreed-0.3.0a0/labfreed/pac_attributes/server/server.py +237 -0
- labfreed-0.3.0a0/labfreed/pac_attributes/server/translation_data_sources.py +60 -0
- labfreed-0.3.0a0/labfreed/pac_attributes/well_knonw_attribute_keys.py +11 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_cat/category_base.py +19 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_cat/pac_cat.py +8 -1
- labfreed-0.3.0a0/labfreed/pac_id/extension.py +73 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id/pac_id.py +2 -2
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id/url_parser.py +4 -4
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id/url_serializer.py +11 -5
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id_resolver/cit_common.py +1 -1
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id_resolver/cit_v1.py +0 -3
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id_resolver/cit_v2.py +1 -6
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id_resolver/resolver.py +12 -7
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id_resolver/services.py +0 -1
- labfreed-0.3.0a0/labfreed/trex/python_convenience/quantity.py +147 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/trex/value_segments.py +1 -1
- labfreed-0.3.0a0/labfreed/utilities/ensure_utc_time.py +6 -0
- labfreed-0.3.0a0/labfreed/utilities/translations.py +60 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/well_known_extensions/display_name_extension.py +3 -3
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/well_known_keys/gs1/gs1_ai_enum_sorted.py +4 -0
- labfreed-0.3.0a0/labfreed/well_known_keys/labfreed/well_known_keys.py +28 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/well_known_keys/unece/unece_units.py +2 -1
- labfreed-0.3.0a0/labfreed_extended/app/app_infrastructure.py +104 -0
- labfreed-0.3.0a0/labfreed_extended/app/pac_info.py +79 -0
- labfreed-0.3.0a0/labfreed_extended/pac_attributes/py_attributes.py +123 -0
- labfreed-0.3.0a0/labfreed_extended/pac_attributes/server/attribute_server_factory.py +91 -0
- labfreed-0.3.0a0/labfreed_extended/pac_attributes/server/excel_attribute_data_source.py +128 -0
- labfreed-0.3.0a0/labfreed_extended/utilities/formatted_print.py +64 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/pyproject.toml +5 -0
- labfreed-0.2.11/labfreed/pac_id/extension.py +0 -48
- labfreed-0.2.11/labfreed/trex/python_convenience/quantity.py +0 -66
- labfreed-0.2.11/labfreed/well_known_keys/gs1/gs1.py +0 -4
- labfreed-0.2.11/labfreed/well_known_keys/labfreed/well_known_keys.py +0 -18
- {labfreed-0.2.11 → labfreed-0.3.0a0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/.github/workflows/pypi-publish.yml +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/.github/workflows/run-tests.yml +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/LICENSE +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_cat/__init__.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_cat/predefined_categories.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id/__init__.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id/id_segment.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/pac_id_resolver/__init__.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/qr/__init__.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/qr/generate_qr.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/trex/__init__.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/trex/python_convenience/__init__.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/trex/python_convenience/data_table.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/trex/python_convenience/pyTREX.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/trex/table_segment.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/trex/trex.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/trex/trex_base_models.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/utilities/base36.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/well_known_extensions/__init__.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/well_known_extensions/default_extension_interpreters.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/well_known_extensions/trex_extension.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/well_known_keys/gs1/__init__.py +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/well_known_keys/unece/UneceUnits.json +0 -0
- {labfreed-0.2.11 → labfreed-0.3.0a0}/labfreed/well_known_keys/unece/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labfreed
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0a0
|
|
4
4
|
Summary: Python implementation of LabFREED building blocks
|
|
5
5
|
Author-email: Reto Thürer <thuerer.r@buchi.com>
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -28,16 +28,23 @@ Requires-Dist: pytest>=8.3.5 ; extra == "dev"
|
|
|
28
28
|
Requires-Dist: pdoc>=15.0.1 ; extra == "dev"
|
|
29
29
|
Requires-Dist: flit>=3.12.0 ; extra == "dev"
|
|
30
30
|
Requires-Dist: ruff>=0.11.5 ; extra == "dev"
|
|
31
|
+
Requires-Dist: Flask>=3.1.1 ; extra == "extended"
|
|
32
|
+
Requires-Dist: openpyxl>=3.1.5 ; extra == "extended"
|
|
31
33
|
Project-URL: Documentation, https://github.com/retothuerer/LabFREED?tab=readme-ov-file#readme
|
|
32
34
|
Project-URL: Homepage, https://github.com/retothuerer/LabFREED
|
|
33
35
|
Project-URL: Source, https://github.com/retothuerer/LabFREED
|
|
34
36
|
Project-URL: Tracker, https://github.com/retothuerer/LabFREED/issues
|
|
35
37
|
Provides-Extra: dev
|
|
38
|
+
Provides-Extra: extended
|
|
36
39
|
|
|
37
40
|
# LabFREED for Python
|
|
38
41
|
|
|
39
42
|
[](https://pypi.org/project/labfreed/)  [](https://github.com/retothuerer/LabFREED/actions/workflows/run-tests.yml) [](LICENSE)
|
|
40
43
|
|
|
44
|
+
<!--
|
|
45
|
+
[](https://github.com/astral-sh/ruff)
|
|
46
|
+
-->
|
|
47
|
+
|
|
41
48
|
|
|
42
49
|
This is a Python implementation of [LabFREED](https://labfreed.org/) building blocks.
|
|
43
50
|
|
|
@@ -103,28 +110,32 @@ There is a nice function to highlight problems
|
|
|
103
110
|
pac.print_validation_messages()
|
|
104
111
|
```
|
|
105
112
|
```text
|
|
106
|
-
>> Validation Results
|
|
107
|
-
>>
|
|
108
|
-
>> │ **RECOMMENDATION** in id segment value bal500
|
|
109
|
-
>> │ Characters '
|
|
110
|
-
>> │
|
|
111
|
-
>> │
|
|
112
|
-
>>
|
|
113
|
-
>>
|
|
114
|
-
>> │
|
|
115
|
-
>> │
|
|
116
|
-
>> │
|
|
117
|
-
>>
|
|
118
|
-
>> │
|
|
119
|
-
>>
|
|
120
|
-
>> │
|
|
121
|
-
>> │
|
|
122
|
-
>>
|
|
123
|
-
>> │
|
|
124
|
-
>> │
|
|
125
|
-
>>
|
|
126
|
-
>> │
|
|
127
|
-
>>
|
|
113
|
+
>> Validation Results
|
|
114
|
+
>> ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
115
|
+
>> │ **RECOMMENDATION** in id segment value bal500 │
|
|
116
|
+
>> │ Characters 'a','l','b' should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers │
|
|
117
|
+
>> │ (0-9), '-' and '+' │
|
|
118
|
+
>> │ │
|
|
119
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/👉bal👈500/@1234 │
|
|
120
|
+
>> ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
121
|
+
>> │ **RECOMMENDATION** in id segment value @1234 │
|
|
122
|
+
>> │ Characters '@' should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers (0-9), '-' │
|
|
123
|
+
>> │ and '+' │
|
|
124
|
+
>> │ │
|
|
125
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/bal500/👉@👈1234 │
|
|
126
|
+
>> ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
127
|
+
>> │ **RECOMMENDATION** in id segment value bal500 │
|
|
128
|
+
>> │ Characters 'a','l','b' should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers │
|
|
129
|
+
>> │ (0-9), '-' and '+' │
|
|
130
|
+
>> │ │
|
|
131
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/👉bal👈500/@1234 │
|
|
132
|
+
>> ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
133
|
+
>> │ **RECOMMENDATION** in id segment value @1234 │
|
|
134
|
+
>> │ Characters '@' should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers (0-9), '-' │
|
|
135
|
+
>> │ and '+' │
|
|
136
|
+
>> │ │
|
|
137
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/bal500/👉@👈1234 │
|
|
138
|
+
>> └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
128
139
|
```
|
|
129
140
|
### Save as QR Code
|
|
130
141
|
|
|
@@ -249,7 +260,7 @@ trex.print_validation_messages()
|
|
|
249
260
|
>> Validation Results
|
|
250
261
|
>> ┌────────────────────────────────────────────────────────────┐
|
|
251
262
|
>> │ **ERROR** in TREX table column Date │
|
|
252
|
-
>> │ Column header key contains invalid characters: '
|
|
263
|
+
>> │ Column header key contains invalid characters: 'a','t','e' │
|
|
253
264
|
>> │ │
|
|
254
265
|
>> │ STOP$T.D:20240505T1306 │
|
|
255
266
|
>> │ +TEMP$KEL:10.15 │
|
|
@@ -257,9 +268,9 @@ trex.print_validation_messages()
|
|
|
257
268
|
>> │ +COMMENT$T.A:FOO │
|
|
258
269
|
>> │ +COMMENT2$T.T:12G3 │
|
|
259
270
|
>> │ +TABLE$$DURATION$HUR:D👉ate👈$T.D:OK$T.B:COMMENT$T.A:: │
|
|
260
|
-
>> │ 1:
|
|
261
|
-
>> │ 1.1:
|
|
262
|
-
>> │ 1.3:
|
|
271
|
+
>> │ 1:20250522T180101.575:T:FOO:: │
|
|
272
|
+
>> │ 1.1:20250522T180101.575:T:BAR:: │
|
|
273
|
+
>> │ 1.3:20250522T180101.575:F:BLUBB │
|
|
263
274
|
>> └────────────────────────────────────────────────────────────┘
|
|
264
275
|
```
|
|
265
276
|
#### Combine PAC-ID and TREX and serialize
|
|
@@ -271,7 +282,7 @@ pac_str = pac.to_url()
|
|
|
271
282
|
print(pac_str)
|
|
272
283
|
```
|
|
273
284
|
```text
|
|
274
|
-
>> HTTPS://PAC.METTORIUS.COM/21:1234*MYTREX$TREX/STOP$T.D:20240505T1306+TEMP$KEL:10.15+OK$T.B:F+COMMENT$T.A:FOO+COMMENT2$T.T:12G3+TABLE$$DURATION$HUR:Date$T.D:OK$T.B:COMMENT$T.A::1:
|
|
285
|
+
>> HTTPS://PAC.METTORIUS.COM/21:1234*MYTREX$TREX/STOP$T.D:20240505T1306+TEMP$KEL:10.15+OK$T.B:F+COMMENT$T.A:FOO+COMMENT2$T.T:12G3+TABLE$$DURATION$HUR:Date$T.D:OK$T.B:COMMENT$T.A::1:20250522T180101.575:T:FOO::1.1:20250522T180101.575:T:BAR::1.3:20250522T180101.575:F:BLUBB
|
|
275
286
|
```
|
|
276
287
|
## PAC-ID Resolver
|
|
277
288
|
|
|
@@ -332,9 +343,12 @@ for sg in service_groups:
|
|
|
332
343
|
|
|
333
344
|
<!-- BEGIN CHANGELOG -->
|
|
334
345
|
## Change Log
|
|
346
|
+
### v0.2.12
|
|
347
|
+
- bugfix:no warning message if PAC-CAT has same segment key in two segments
|
|
348
|
+
|
|
335
349
|
### v0.2.11
|
|
336
350
|
- bugfix:added missing well known segment key '250'
|
|
337
|
-
|
|
351
|
+
|
|
338
352
|
### v0.2.10
|
|
339
353
|
- bugfix:added missing well known segment key '20'
|
|
340
354
|
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/labfreed/)  [](https://github.com/retothuerer/LabFREED/actions/workflows/run-tests.yml) [](LICENSE)
|
|
4
4
|
|
|
5
|
+
<!--
|
|
6
|
+
[](https://github.com/astral-sh/ruff)
|
|
7
|
+
-->
|
|
8
|
+
|
|
5
9
|
|
|
6
10
|
This is a Python implementation of [LabFREED](https://labfreed.org/) building blocks.
|
|
7
11
|
|
|
@@ -67,28 +71,32 @@ There is a nice function to highlight problems
|
|
|
67
71
|
pac.print_validation_messages()
|
|
68
72
|
```
|
|
69
73
|
```text
|
|
70
|
-
>> Validation Results
|
|
71
|
-
>>
|
|
72
|
-
>> │ **RECOMMENDATION** in id segment value bal500
|
|
73
|
-
>> │ Characters '
|
|
74
|
-
>> │
|
|
75
|
-
>> │
|
|
76
|
-
>>
|
|
77
|
-
>>
|
|
78
|
-
>> │
|
|
79
|
-
>> │
|
|
80
|
-
>> │
|
|
81
|
-
>>
|
|
82
|
-
>> │
|
|
83
|
-
>>
|
|
84
|
-
>> │
|
|
85
|
-
>> │
|
|
86
|
-
>>
|
|
87
|
-
>> │
|
|
88
|
-
>> │
|
|
89
|
-
>>
|
|
90
|
-
>> │
|
|
91
|
-
>>
|
|
74
|
+
>> Validation Results
|
|
75
|
+
>> ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
76
|
+
>> │ **RECOMMENDATION** in id segment value bal500 │
|
|
77
|
+
>> │ Characters 'a','l','b' should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers │
|
|
78
|
+
>> │ (0-9), '-' and '+' │
|
|
79
|
+
>> │ │
|
|
80
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/👉bal👈500/@1234 │
|
|
81
|
+
>> ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
82
|
+
>> │ **RECOMMENDATION** in id segment value @1234 │
|
|
83
|
+
>> │ Characters '@' should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers (0-9), '-' │
|
|
84
|
+
>> │ and '+' │
|
|
85
|
+
>> │ │
|
|
86
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/bal500/👉@👈1234 │
|
|
87
|
+
>> ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
88
|
+
>> │ **RECOMMENDATION** in id segment value bal500 │
|
|
89
|
+
>> │ Characters 'a','l','b' should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers │
|
|
90
|
+
>> │ (0-9), '-' and '+' │
|
|
91
|
+
>> │ │
|
|
92
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/👉bal👈500/@1234 │
|
|
93
|
+
>> ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
94
|
+
>> │ **RECOMMENDATION** in id segment value @1234 │
|
|
95
|
+
>> │ Characters '@' should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers (0-9), '-' │
|
|
96
|
+
>> │ and '+' │
|
|
97
|
+
>> │ │
|
|
98
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/bal500/👉@👈1234 │
|
|
99
|
+
>> └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
92
100
|
```
|
|
93
101
|
### Save as QR Code
|
|
94
102
|
|
|
@@ -213,7 +221,7 @@ trex.print_validation_messages()
|
|
|
213
221
|
>> Validation Results
|
|
214
222
|
>> ┌────────────────────────────────────────────────────────────┐
|
|
215
223
|
>> │ **ERROR** in TREX table column Date │
|
|
216
|
-
>> │ Column header key contains invalid characters: '
|
|
224
|
+
>> │ Column header key contains invalid characters: 'a','t','e' │
|
|
217
225
|
>> │ │
|
|
218
226
|
>> │ STOP$T.D:20240505T1306 │
|
|
219
227
|
>> │ +TEMP$KEL:10.15 │
|
|
@@ -221,9 +229,9 @@ trex.print_validation_messages()
|
|
|
221
229
|
>> │ +COMMENT$T.A:FOO │
|
|
222
230
|
>> │ +COMMENT2$T.T:12G3 │
|
|
223
231
|
>> │ +TABLE$$DURATION$HUR:D👉ate👈$T.D:OK$T.B:COMMENT$T.A:: │
|
|
224
|
-
>> │ 1:
|
|
225
|
-
>> │ 1.1:
|
|
226
|
-
>> │ 1.3:
|
|
232
|
+
>> │ 1:20250522T180101.575:T:FOO:: │
|
|
233
|
+
>> │ 1.1:20250522T180101.575:T:BAR:: │
|
|
234
|
+
>> │ 1.3:20250522T180101.575:F:BLUBB │
|
|
227
235
|
>> └────────────────────────────────────────────────────────────┘
|
|
228
236
|
```
|
|
229
237
|
#### Combine PAC-ID and TREX and serialize
|
|
@@ -235,7 +243,7 @@ pac_str = pac.to_url()
|
|
|
235
243
|
print(pac_str)
|
|
236
244
|
```
|
|
237
245
|
```text
|
|
238
|
-
>> HTTPS://PAC.METTORIUS.COM/21:1234*MYTREX$TREX/STOP$T.D:20240505T1306+TEMP$KEL:10.15+OK$T.B:F+COMMENT$T.A:FOO+COMMENT2$T.T:12G3+TABLE$$DURATION$HUR:Date$T.D:OK$T.B:COMMENT$T.A::1:
|
|
246
|
+
>> HTTPS://PAC.METTORIUS.COM/21:1234*MYTREX$TREX/STOP$T.D:20240505T1306+TEMP$KEL:10.15+OK$T.B:F+COMMENT$T.A:FOO+COMMENT2$T.T:12G3+TABLE$$DURATION$HUR:Date$T.D:OK$T.B:COMMENT$T.A::1:20250522T180101.575:T:FOO::1.1:20250522T180101.575:T:BAR::1.3:20250522T180101.575:F:BLUBB
|
|
239
247
|
```
|
|
240
248
|
## PAC-ID Resolver
|
|
241
249
|
|
|
@@ -296,9 +304,12 @@ for sg in service_groups:
|
|
|
296
304
|
|
|
297
305
|
<!-- BEGIN CHANGELOG -->
|
|
298
306
|
## Change Log
|
|
307
|
+
### v0.2.12
|
|
308
|
+
- bugfix:no warning message if PAC-CAT has same segment key in two segments
|
|
309
|
+
|
|
299
310
|
### v0.2.11
|
|
300
311
|
- bugfix:added missing well known segment key '250'
|
|
301
|
-
|
|
312
|
+
|
|
302
313
|
### v0.2.10
|
|
303
314
|
- bugfix:added missing well known segment key '20'
|
|
304
315
|
|
|
@@ -108,7 +108,7 @@ class LabFREED_BaseModel(PDOC_Workaround_Base):
|
|
|
108
108
|
self._validation_messages.append(w)
|
|
109
109
|
|
|
110
110
|
# Function to extract warnings from a model and its nested models
|
|
111
|
-
def _get_nested_validation_messages(self, parent_name: str = "", visited: Set[int] = None) -> List['ValidationMessage']:
|
|
111
|
+
def _get_nested_validation_messages(self, parent_name: str = "", visited: Set[int]|None = None) -> List['ValidationMessage']:
|
|
112
112
|
"""
|
|
113
113
|
Recursively extract warnings from a Pydantic model and its nested fields, including computed fields.
|
|
114
114
|
|
|
@@ -140,7 +140,8 @@ class LabFREED_BaseModel(PDOC_Workaround_Base):
|
|
|
140
140
|
warnings_list.extend(item._get_nested_validation_messages(list_path, visited))
|
|
141
141
|
|
|
142
142
|
# Traverse computed fields
|
|
143
|
-
|
|
143
|
+
mdl:BaseModel = getattr(self, '__pydantic_decorators__', {})
|
|
144
|
+
computed_fields = mdl.computed_fields or {}
|
|
144
145
|
for field_name in computed_fields:
|
|
145
146
|
full_path = f"{parent_name}.{field_name}" if parent_name else field_name
|
|
146
147
|
try:
|
|
@@ -255,4 +256,3 @@ def _filter_warnings(val_msg:list[ValidationMessage]) -> list[ValidationMessage]
|
|
|
255
256
|
def _quote_texts(texts:list[str]):
|
|
256
257
|
return ','.join([f"'{t}'" for t in texts])
|
|
257
258
|
|
|
258
|
-
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
from pydantic import ConfigDict, model_validator
|
|
3
|
+
from labfreed.labfreed_infrastructure import LabFREED_BaseModel, LabFREED_ValidationError, ValidationMsgLevel
|
|
4
|
+
from labfreed.pac_id.pac_id import PAC_ID
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AttributeRequestPayload(LabFREED_BaseModel):
|
|
8
|
+
model_config = ConfigDict(frozen=True)
|
|
9
|
+
|
|
10
|
+
pac_urls: list[str]
|
|
11
|
+
language_preferences: list[str]
|
|
12
|
+
restrict_to_attribute_groups: list[str]|None = None
|
|
13
|
+
suppress_forward_lookup: bool = False
|
|
14
|
+
|
|
15
|
+
def as_json(self):
|
|
16
|
+
return self.model_dump_json()
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_json(cls, json):
|
|
20
|
+
return cls.model_validate_json(json)
|
|
21
|
+
|
|
22
|
+
@model_validator(mode="before")
|
|
23
|
+
@classmethod
|
|
24
|
+
def _handle_single_pac_url(cls, data):
|
|
25
|
+
p = data.get('pac_urls')
|
|
26
|
+
if isinstance(p, str):
|
|
27
|
+
data['pac_urls'] = [p]
|
|
28
|
+
return data
|
|
29
|
+
|
|
30
|
+
@model_validator(mode="after")
|
|
31
|
+
def _validate_pacs(self) -> Self:
|
|
32
|
+
if len(self.pac_urls) > 100:
|
|
33
|
+
self._add_validation_message(
|
|
34
|
+
source="pacs",
|
|
35
|
+
level = ValidationMsgLevel.ERROR,
|
|
36
|
+
msg='The number of pac-ids must be limited to 100'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
for pac_url in self.pac_urls:
|
|
40
|
+
try:
|
|
41
|
+
PAC_ID.from_url(pac_url)
|
|
42
|
+
except LabFREED_ValidationError:
|
|
43
|
+
self._add_validation_message(
|
|
44
|
+
source="pacs",
|
|
45
|
+
level = ValidationMsgLevel.ERROR,
|
|
46
|
+
msg='{pac_url} is not a valid PAC-ID'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if not self.is_valid:
|
|
50
|
+
raise LabFREED_ValidationError(message='Invalid request', validation_msgs=self.validation_messages())
|
|
51
|
+
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import re
|
|
5
|
+
from typing import Annotated, Any, Literal, Union, get_args
|
|
6
|
+
from labfreed.utilities.ensure_utc_time import ensure_utc
|
|
7
|
+
from labfreed.labfreed_infrastructure import LabFREED_BaseModel, ValidationMsgLevel, _quote_texts
|
|
8
|
+
from pydantic import Field, field_validator, model_validator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AttributeBase(LabFREED_BaseModel, ABC):
|
|
12
|
+
key: str
|
|
13
|
+
value: Any
|
|
14
|
+
label: str = ""
|
|
15
|
+
|
|
16
|
+
observed_at: datetime | None = None
|
|
17
|
+
|
|
18
|
+
def __init__(self, **data):
|
|
19
|
+
# Automatically inject the Literal value for `type`
|
|
20
|
+
discriminator_value = self._get_discriminator_value()
|
|
21
|
+
data["type"] = discriminator_value
|
|
22
|
+
super().__init__(**data)
|
|
23
|
+
|
|
24
|
+
@field_validator('observed_at', mode='before')
|
|
25
|
+
def set_utc_observed_at_if_naive(cls, value):
|
|
26
|
+
if isinstance(value, datetime):
|
|
27
|
+
return ensure_utc(value)
|
|
28
|
+
else:
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def _get_discriminator_value(cls) -> str:
|
|
33
|
+
"""Extract the Literal value from the 'type' annotation."""
|
|
34
|
+
try:
|
|
35
|
+
type_annotation = cls.__annotations__["type"]
|
|
36
|
+
literal_value = get_args(type_annotation)[0]
|
|
37
|
+
return literal_value
|
|
38
|
+
except Exception as e:
|
|
39
|
+
raise TypeError(
|
|
40
|
+
f"{cls.__name__} must define `type: Literal[<value>]` annotation"
|
|
41
|
+
) from e
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ReferenceAttribute(AttributeBase):
|
|
47
|
+
type: Literal["reference"]
|
|
48
|
+
value: str
|
|
49
|
+
|
|
50
|
+
class DateTimeAttribute(AttributeBase):
|
|
51
|
+
type: Literal["datetime"]
|
|
52
|
+
value: datetime
|
|
53
|
+
|
|
54
|
+
@field_validator('value', mode='before')
|
|
55
|
+
def set_utc__if_naive(cls, value):
|
|
56
|
+
if isinstance(value, datetime):
|
|
57
|
+
return ensure_utc(value)
|
|
58
|
+
else:
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
class BoolAttribute(AttributeBase):
|
|
62
|
+
type: Literal["bool"]
|
|
63
|
+
value: bool
|
|
64
|
+
|
|
65
|
+
class TextAttribute(AttributeBase):
|
|
66
|
+
type: Literal["text"]
|
|
67
|
+
value: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class NumericValue(LabFREED_BaseModel):
|
|
73
|
+
magnitude: str
|
|
74
|
+
unit: str
|
|
75
|
+
|
|
76
|
+
@model_validator(mode='after')
|
|
77
|
+
def _validate_value(self):
|
|
78
|
+
value = self.magnitude
|
|
79
|
+
if not_allowed_chars := set(re.sub(r'[0-9\.\-\+Ee]', '', value)):
|
|
80
|
+
self._add_validation_message(
|
|
81
|
+
source="Numeric Attribute",
|
|
82
|
+
level=ValidationMsgLevel.ERROR, # noqa: F821
|
|
83
|
+
msg=f"Characters {_quote_texts(not_allowed_chars)} are not allowed in quantity segment. Must be a number.",
|
|
84
|
+
highlight_pattern = f'{value}',
|
|
85
|
+
highlight_sub=not_allowed_chars
|
|
86
|
+
)
|
|
87
|
+
if not re.fullmatch(r'-?\d+(\.\d+)?([Ee][\+-]?\d+)?', value):
|
|
88
|
+
self._add_validation_message(
|
|
89
|
+
source="Numeric Attribute",
|
|
90
|
+
level=ValidationMsgLevel.ERROR,
|
|
91
|
+
msg=f"{value} cannot be converted to number",
|
|
92
|
+
highlight_pattern = f'{value}'
|
|
93
|
+
)
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
@model_validator(mode="after")
|
|
97
|
+
def _validate_units(self):
|
|
98
|
+
'''A sanity check on unit complying with UCUM. NOTE: It is not a complete validation
|
|
99
|
+
- I check for blankspaces and ^, which are often used for units, but are invalid.
|
|
100
|
+
- the general structure of a ucum unit is validated, but 1)parentheses are not matched 2) units are not validated 3)prefixes are not checked
|
|
101
|
+
'''
|
|
102
|
+
if ' ' in self.unit or '^' in self.unit:
|
|
103
|
+
self._add_validation_message(
|
|
104
|
+
source="Numeric Attribute",
|
|
105
|
+
level= ValidationMsgLevel.ERROR,
|
|
106
|
+
msg=f"Unit {self.unit} is invalid. Must not contain blankspace or '^'.",
|
|
107
|
+
highlight_pattern = self.unit
|
|
108
|
+
)
|
|
109
|
+
elif not re.fullmatch(r"^(((?P<unit>[\w\[\]]+?)(?P<exponent>\-?\d+)?|(?P<annotation>)\{\w+?\})(?P<operator>[\./]?)?)+", self.unit):
|
|
110
|
+
self._add_validation_message(
|
|
111
|
+
source="Numeric Attribute",
|
|
112
|
+
level= ValidationMsgLevel.WARNING,
|
|
113
|
+
msg=f"Unit {self.unit} is probably invalid. Ensure it complies with UCUM specifications.",
|
|
114
|
+
highlight_pattern = self.unit
|
|
115
|
+
)
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class NumericAttribute(AttributeBase):
|
|
122
|
+
type: Literal["numeric"]
|
|
123
|
+
value: NumericValue
|
|
124
|
+
|
|
125
|
+
class ObjectAttribute(AttributeBase):
|
|
126
|
+
type: Literal["object"]
|
|
127
|
+
value: dict[str, Any]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
Attribute = Annotated[
|
|
133
|
+
Union[
|
|
134
|
+
ReferenceAttribute,
|
|
135
|
+
DateTimeAttribute,
|
|
136
|
+
BoolAttribute,
|
|
137
|
+
TextAttribute,
|
|
138
|
+
NumericAttribute,
|
|
139
|
+
ObjectAttribute
|
|
140
|
+
],
|
|
141
|
+
Field(discriminator="type")
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
VALID_FOREVER = "forever"
|
|
145
|
+
|
|
146
|
+
class AttributeGroup(LabFREED_BaseModel):
|
|
147
|
+
key: str
|
|
148
|
+
label: str = ""
|
|
149
|
+
attributes: list[Attribute]
|
|
150
|
+
|
|
151
|
+
state_of: datetime
|
|
152
|
+
valid_until: datetime | Literal["forever"] | None = None
|
|
153
|
+
|
|
154
|
+
@field_validator('valid_until', mode='before')
|
|
155
|
+
def set_utc_valid_until_if_naive(cls, value):
|
|
156
|
+
if isinstance(value, datetime):
|
|
157
|
+
return ensure_utc(value)
|
|
158
|
+
else:
|
|
159
|
+
return value
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class AttributesOfPACID(LabFREED_BaseModel):
|
|
163
|
+
pac_url: str
|
|
164
|
+
attribute_groups: list[AttributeGroup]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class AttributeResponsePayload(LabFREED_BaseModel):
|
|
169
|
+
schema_version: str = Field(default='1.0')
|
|
170
|
+
language:str
|
|
171
|
+
pac_attributes: list[AttributesOfPACID]
|
|
172
|
+
|
|
173
|
+
def to_json(self):
|
|
174
|
+
return self.model_dump_json(exclude_none=True)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import labfreed.utilities.translations
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Literal, Protocol
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from labfreed.pac_attributes.api_data_models.response import AttributeGroup
|
|
8
|
+
from labfreed.pac_id.pac_id import PAC_ID
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CacheableAttributeGroup(AttributeGroup):
|
|
13
|
+
origin:str
|
|
14
|
+
language:str
|
|
15
|
+
valid_until: Literal['forever'] | datetime | None = None
|
|
16
|
+
|
|
17
|
+
# @model_validator(mode='after')
|
|
18
|
+
# def set_valid_until(self) -> 'CacheableAttributeGroup':
|
|
19
|
+
# vals = [a.valid_until for a in self.attributes]
|
|
20
|
+
# if all(e == 'forever' for e in vals):
|
|
21
|
+
# self.valid_until = 'forever'
|
|
22
|
+
# elif any(e is None for e in vals):
|
|
23
|
+
# self.valid_until = None
|
|
24
|
+
# else:
|
|
25
|
+
# self.valid_until = min(v for v in vals if isinstance(v, datetime))
|
|
26
|
+
# return self
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def still_valid(self):
|
|
31
|
+
if self.valid_until is None:
|
|
32
|
+
return False
|
|
33
|
+
if self.valid_until == 'forever':
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
return self.valid_until > datetime.now()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AttributeCache(Protocol):
|
|
42
|
+
def get_all(self, service_url:str, pac:PAC_ID) -> list[CacheableAttributeGroup]:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def get_attribute_groups(self, service_url:str, pac:PAC_ID, attribute_groups:list[str]):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def update(self, service_url:str, pac:PAC_ID, attribute_groups:list[CacheableAttributeGroup]):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MemoryAttributeCache(AttributeCache):
|
|
54
|
+
'''simple in-memory implementation of AttributeCache'''
|
|
55
|
+
def __init__(self):
|
|
56
|
+
self._store = dict()
|
|
57
|
+
|
|
58
|
+
def get_all(self, service_url:str, pac:PAC_ID|str) -> list[CacheableAttributeGroup]:
|
|
59
|
+
if isinstance(pac, str):
|
|
60
|
+
pac = PAC_ID.from_url(pac)
|
|
61
|
+
k = self._generate_dict_key(service_url=service_url, pac=pac)
|
|
62
|
+
|
|
63
|
+
ags = [CacheableAttributeGroup.model_validate(e) for e in self._store.get(k, [])]
|
|
64
|
+
return ags
|
|
65
|
+
|
|
66
|
+
def get_attribute_groups(self, service_url:str, pac:PAC_ID, attribute_groups:list[str]):
|
|
67
|
+
all_ags = self.get_all(service_url=service_url, pac=pac)
|
|
68
|
+
selected_ags = [ag for ag in all_ags if ag.key in attribute_groups]
|
|
69
|
+
return selected_ags
|
|
70
|
+
|
|
71
|
+
def update(self, service_url:str, pac:PAC_ID, attribute_groups: list[CacheableAttributeGroup] ):
|
|
72
|
+
k = self._generate_dict_key(service_url=service_url, pac=pac)
|
|
73
|
+
self._store.update({k: [e.model_dump() for e in attribute_groups]})
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _generate_dict_key(service_url:str, pac:PAC_ID):
|
|
77
|
+
key = service_url +";"+ pac.to_url(include_extensions=False)
|
|
78
|
+
return key
|