labfreed 0.1.0__py2.py3-none-any.whl → 0.1.1__py2.py3-none-any.whl
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/IO/parse_pac.py +4 -3
- labfreed/PAC_CAT/data_model.py +39 -19
- labfreed/PAC_ID/data_model.py +20 -25
- labfreed/PAC_ID_Resolver/cit.yaml +1 -1
- labfreed/PAC_ID_Resolver/data_types.py +27 -1
- labfreed/TREX/data_model.py +14 -14
- labfreed/__init__.py +1 -1
- labfreed/validation.py +123 -39
- labfreed-0.1.1.dist-info/METADATA +279 -0
- {labfreed-0.1.0.dist-info → labfreed-0.1.1.dist-info}/RECORD +12 -12
- labfreed-0.1.0.dist-info/METADATA +0 -266
- {labfreed-0.1.0.dist-info → labfreed-0.1.1.dist-info}/WHEEL +0 -0
- {labfreed-0.1.0.dist-info → labfreed-0.1.1.dist-info}/licenses/LICENSE +0 -0
labfreed/IO/parse_pac.py
CHANGED
|
@@ -25,6 +25,7 @@ class PACID_With_Extensions(BaseModelWithValidationMessages):
|
|
|
25
25
|
def __str__(self):
|
|
26
26
|
out = str(self.pac_id)
|
|
27
27
|
out += '*'.join(str(e) for e in self.extensions)
|
|
28
|
+
return out
|
|
28
29
|
|
|
29
30
|
def get_extension_of_type(self, type:str) -> list[Extension]:
|
|
30
31
|
return [e for e in self.extensions if e.type == type]
|
|
@@ -87,7 +88,7 @@ class PAC_Parser():
|
|
|
87
88
|
def __init__(self, extension_interpreters:dict[str, Extension]=None):
|
|
88
89
|
self.extension_interpreters = extension_interpreters or {'TREX': TREX, 'N': DisplayNames}
|
|
89
90
|
|
|
90
|
-
def parse(self, pac_url:str) -> PACID_With_Extensions:
|
|
91
|
+
def parse(self, pac_url:str, suppress_errors=False) -> PACID_With_Extensions:
|
|
91
92
|
if '*' in pac_url:
|
|
92
93
|
id_str, ext_str = pac_url.split('*', 1)
|
|
93
94
|
else:
|
|
@@ -98,8 +99,8 @@ class PAC_Parser():
|
|
|
98
99
|
extensions = self._parse_extensions(ext_str)
|
|
99
100
|
|
|
100
101
|
pac_with_extension = PACID_With_Extensions(pac_id=pac_id, extensions=extensions)
|
|
101
|
-
if not pac_with_extension.is_valid():
|
|
102
|
-
raise LabFREEDValidationError(validation_msgs = pac_with_extension.get_nested_validation_messages())
|
|
102
|
+
if not pac_with_extension.is_valid() and not suppress_errors:
|
|
103
|
+
raise LabFREEDValidationError(validation_msgs = pac_with_extension.get_nested_validation_messages(), model=pac_with_extension)
|
|
103
104
|
|
|
104
105
|
return pac_with_extension
|
|
105
106
|
|
labfreed/PAC_CAT/data_model.py
CHANGED
|
@@ -4,7 +4,11 @@ from abc import ABC
|
|
|
4
4
|
from typing import Self
|
|
5
5
|
from pydantic import Field, computed_field, model_validator
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from rich import print
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from labfreed.validation import BaseModelWithValidationMessages, ValidationMsgLevel
|
|
8
12
|
|
|
9
13
|
from ..PAC_ID.data_model import PACID, IDSegment
|
|
10
14
|
|
|
@@ -132,24 +136,37 @@ class PAC_CAT(PACID):
|
|
|
132
136
|
for k in duplicate_keys:
|
|
133
137
|
self.add_validation_message(
|
|
134
138
|
source=f"identifier {k}",
|
|
135
|
-
|
|
139
|
+
level = ValidationMsgLevel.ERROR,
|
|
136
140
|
msg=f"Duplicate key {k} in category {c.key}",
|
|
137
141
|
highlight_pattern = k
|
|
138
142
|
)
|
|
139
143
|
return self
|
|
140
144
|
|
|
141
145
|
def print_categories(self):
|
|
146
|
+
|
|
147
|
+
table = Table(title=f'Categories in {str(self)}', show_header=False)
|
|
148
|
+
table.add_column('0')
|
|
149
|
+
table.add_column('1')
|
|
142
150
|
s = ''
|
|
143
151
|
for i, c in enumerate(self.categories):
|
|
144
152
|
if i == 0:
|
|
145
|
-
title = 'Main Category
|
|
153
|
+
title = Text('Main Category', style='bold')
|
|
146
154
|
else:
|
|
147
|
-
title = 'Category
|
|
155
|
+
title = Text('Category', style='bold')
|
|
156
|
+
|
|
157
|
+
table.add_row(title)
|
|
148
158
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
for field_name, field_info in c.model_fields.items():
|
|
160
|
+
if not getattr(c, field_name):
|
|
161
|
+
continue
|
|
162
|
+
table.add_row(f"{field_name} ({field_info.alias or ''})",
|
|
163
|
+
f" {getattr(c, field_name)}"
|
|
164
|
+
)
|
|
165
|
+
table.add_section()
|
|
166
|
+
print(table)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
153
170
|
|
|
154
171
|
|
|
155
172
|
# @classmethod
|
|
@@ -202,18 +219,21 @@ class Category(BaseModelWithValidationMessages):
|
|
|
202
219
|
@model_validator(mode='after')
|
|
203
220
|
def warn_unusual_category_key(self):
|
|
204
221
|
''' this base class is instantiated only if the key is not a known category key'''
|
|
205
|
-
self
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
222
|
+
if type(self) is Category:
|
|
223
|
+
self.add_validation_message(
|
|
224
|
+
source=f"Category {self.key}",
|
|
225
|
+
level = ValidationMsgLevel.RECOMMENDATION,
|
|
226
|
+
msg=f'Category key {self.key} is not a well known key. It is recommended to use well known keys only',
|
|
227
|
+
highlight_pattern = f"{self.key}"
|
|
228
|
+
)
|
|
211
229
|
return self
|
|
212
230
|
|
|
213
231
|
|
|
214
232
|
def __str__(self):
|
|
215
233
|
s = '\n'.join( [f'{field_name} \t ({field_info.alias or ''}): \t {getattr(self, field_name)}' for field_name, field_info in self.model_fields.items() if getattr(self, field_name)])
|
|
216
234
|
return s
|
|
235
|
+
|
|
236
|
+
|
|
217
237
|
|
|
218
238
|
|
|
219
239
|
# def to_identifier_category(self, use_short_notation=False):
|
|
@@ -278,14 +298,14 @@ class Material_Device(Category):
|
|
|
278
298
|
if not self.model_number:
|
|
279
299
|
self.add_validation_message(
|
|
280
300
|
source=f"Category {self.key}",
|
|
281
|
-
|
|
301
|
+
level = ValidationMsgLevel.ERROR,
|
|
282
302
|
msg=f'Category key {self.key} is missing mandatory field Model Number',
|
|
283
303
|
highlight_pattern = f"{self.key}"
|
|
284
304
|
)
|
|
285
305
|
if not self.serial_number:
|
|
286
306
|
self.add_validation_message(
|
|
287
307
|
source=f"Category {self.key}",
|
|
288
|
-
|
|
308
|
+
level = ValidationMsgLevel.ERROR,
|
|
289
309
|
msg=f'Category key {self.key} is missing mandatory field Serial Number',
|
|
290
310
|
highlight_pattern = f"{self.key}"
|
|
291
311
|
)
|
|
@@ -303,7 +323,7 @@ class Material_Substance(Category):
|
|
|
303
323
|
if not self.product_number:
|
|
304
324
|
self.add_validation_message(
|
|
305
325
|
source=f"Category {self.key}",
|
|
306
|
-
|
|
326
|
+
level = ValidationMsgLevel.ERROR,
|
|
307
327
|
msg=f'Category key {self.key} is missing mandatory field Product Number',
|
|
308
328
|
highlight_pattern = f"{self.key}"
|
|
309
329
|
)
|
|
@@ -321,7 +341,7 @@ class Material_Consumable(Category):
|
|
|
321
341
|
if not self.product_number:
|
|
322
342
|
self.add_validation_message(
|
|
323
343
|
source=f"Category {self.key}",
|
|
324
|
-
|
|
344
|
+
level = ValidationMsgLevel.ERROR,
|
|
325
345
|
msg=f"Category key {self.key} is missing mandatory field 'Product Number'",
|
|
326
346
|
highlight_pattern = f"{self.key}"
|
|
327
347
|
)
|
|
@@ -341,7 +361,7 @@ class Data_Abstract(Category, ABC):
|
|
|
341
361
|
if not self.id:
|
|
342
362
|
self.add_validation_message(
|
|
343
363
|
source=f"Category {self.key}",
|
|
344
|
-
|
|
364
|
+
level = ValidationMsgLevel.ERROR,
|
|
345
365
|
msg=f"Category key {self.key} is missing mandatory field 'ID'",
|
|
346
366
|
highlight_pattern = f"{self.key}"
|
|
347
367
|
)
|
labfreed/PAC_ID/data_model.py
CHANGED
|
@@ -6,7 +6,7 @@ from pydantic import Field, ValidationInfo, computed_field, conlist, model_valid
|
|
|
6
6
|
from abc import ABC, abstractproperty, abstractstaticmethod
|
|
7
7
|
|
|
8
8
|
from ..utilities.well_known_keys import WellKnownKeys
|
|
9
|
-
from labfreed.validation import BaseModelWithValidationMessages, ValidationMessage, hsegment_pattern, domain_name_pattern
|
|
9
|
+
from labfreed.validation import BaseModelWithValidationMessages, ValidationMessage, ValidationMsgLevel, hsegment_pattern, domain_name_pattern
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class IDSegment(BaseModelWithValidationMessages):
|
|
@@ -23,9 +23,8 @@ class IDSegment(BaseModelWithValidationMessages):
|
|
|
23
23
|
if not_allowed_chars := set(re.sub(hsegment_pattern, '', key)):
|
|
24
24
|
self.add_validation_message(
|
|
25
25
|
source=f"id segment key {key}",
|
|
26
|
-
|
|
27
|
-
msg=f"{' '.join(not_allowed_chars)} must not be used.",
|
|
28
|
-
recommendation = "The segment key must be a valid hsegment",
|
|
26
|
+
level = ValidationMsgLevel.ERROR,
|
|
27
|
+
msg=f"{' '.join(not_allowed_chars)} must not be used. The segment key must be a valid hsegment",
|
|
29
28
|
highlight_pattern = key,
|
|
30
29
|
highlight_sub = not_allowed_chars
|
|
31
30
|
)
|
|
@@ -33,9 +32,8 @@ class IDSegment(BaseModelWithValidationMessages):
|
|
|
33
32
|
if not_allowed_chars := set(re.sub(hsegment_pattern, '', value)):
|
|
34
33
|
self.add_validation_message(
|
|
35
34
|
source=f"id segment key {value}",
|
|
36
|
-
|
|
37
|
-
msg=f"{' '.join(not_allowed_chars)} must not be used.",
|
|
38
|
-
recommendation = "The segment key must be a valid hsegment",
|
|
35
|
+
level = ValidationMsgLevel.ERROR,
|
|
36
|
+
msg=f"{' '.join(not_allowed_chars)} must not be used. The segment key must be a valid hsegment",
|
|
39
37
|
highlight_pattern = value,
|
|
40
38
|
highlight_sub = not_allowed_chars
|
|
41
39
|
)
|
|
@@ -44,9 +42,8 @@ class IDSegment(BaseModelWithValidationMessages):
|
|
|
44
42
|
if not_recommended_chars := set(re.sub(r'[A-Z0-9-:+]', '', key)):
|
|
45
43
|
self.add_validation_message(
|
|
46
44
|
source=f"id segment key {key}",
|
|
47
|
-
|
|
48
|
-
msg=f"{' '.join(not_recommended_chars)} should not be used.",
|
|
49
|
-
recommendation = "SHOULD be limited to A-Z, 0-9, and -+",
|
|
45
|
+
level = ValidationMsgLevel.RECOMMENDATION,
|
|
46
|
+
msg=f"{' '.join(not_recommended_chars)} should not be used. Characters SHOULD be limited to upper case letters (A-Z), numbers (0-9), '-' and '+' ",
|
|
50
47
|
highlight_pattern = key,
|
|
51
48
|
highlight_sub = not_recommended_chars
|
|
52
49
|
)
|
|
@@ -55,10 +52,10 @@ class IDSegment(BaseModelWithValidationMessages):
|
|
|
55
52
|
if key and key not in [k.value for k in WellKnownKeys]:
|
|
56
53
|
self.add_validation_message(
|
|
57
54
|
source=f"id segment key {key}",
|
|
58
|
-
|
|
59
|
-
msg=f"{key} is not a well known segment key.",
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
level = ValidationMsgLevel.RECOMMENDATION,
|
|
56
|
+
msg=f"{key} is not a well known segment key. It is RECOMMENDED to use well-known keys.",
|
|
57
|
+
highlight_pattern = key,
|
|
58
|
+
highlight_sub=[key]
|
|
62
59
|
)
|
|
63
60
|
|
|
64
61
|
|
|
@@ -66,9 +63,8 @@ class IDSegment(BaseModelWithValidationMessages):
|
|
|
66
63
|
if not_recommended_chars := set(re.sub(r'[A-Z0-9-:+]', '', value)):
|
|
67
64
|
self.add_validation_message(
|
|
68
65
|
source=f"id segment value {value}",
|
|
69
|
-
|
|
70
|
-
msg=f"Characters {' '.join(not_recommended_chars)} should not be used
|
|
71
|
-
recommendation = "SHOULD be limited to A-Z, 0-9, and -+",
|
|
66
|
+
level = ValidationMsgLevel.RECOMMENDATION,
|
|
67
|
+
msg=f"Characters {' '.join(not_recommended_chars)} should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers (0-9), '-' and '+' ",
|
|
72
68
|
highlight_pattern = value,
|
|
73
69
|
highlight_sub = not_recommended_chars
|
|
74
70
|
)
|
|
@@ -78,14 +74,14 @@ class IDSegment(BaseModelWithValidationMessages):
|
|
|
78
74
|
if ':' in key:
|
|
79
75
|
self.add_validation_message(
|
|
80
76
|
source=f"id segment key {key}",
|
|
81
|
-
|
|
77
|
+
level = ValidationMsgLevel.RECOMMENDATION,
|
|
82
78
|
msg=f"Character ':' should not be used in segment key, since this character is used to separate key and value this can lead to undefined behaviour.",
|
|
83
79
|
highlight_pattern = key
|
|
84
80
|
)
|
|
85
81
|
if ':' in value:
|
|
86
82
|
self.add_validation_message(
|
|
87
83
|
source=f"id segment value {value}",
|
|
88
|
-
|
|
84
|
+
level = ValidationMsgLevel.RECOMMENDATION,
|
|
89
85
|
msg=f"Character ':' should not be used in segment value, since this character is used to separate key and value this can lead to undefined behaviour.",
|
|
90
86
|
highlight_pattern = value
|
|
91
87
|
)
|
|
@@ -113,9 +109,8 @@ class PACID(BaseModelWithValidationMessages):
|
|
|
113
109
|
if l > 256:
|
|
114
110
|
self.add_validation_message(
|
|
115
111
|
source=f"identifier",
|
|
116
|
-
|
|
117
|
-
msg=f'Identifier is {l} characters long, Identifier must not exceed 256 characters.'
|
|
118
|
-
highlight_pattern = ""
|
|
112
|
+
level = ValidationMsgLevel.ERROR,
|
|
113
|
+
msg=f'Identifier is {l} characters long, Identifier must not exceed 256 characters.'
|
|
119
114
|
)
|
|
120
115
|
return self
|
|
121
116
|
|
|
@@ -125,7 +120,7 @@ class PACID(BaseModelWithValidationMessages):
|
|
|
125
120
|
if not re.fullmatch(domain_name_pattern, self.issuer):
|
|
126
121
|
self.add_validation_message(
|
|
127
122
|
source="PAC-ID",
|
|
128
|
-
|
|
123
|
+
level = ValidationMsgLevel.ERROR,
|
|
129
124
|
highlight_pattern=self.issuer,
|
|
130
125
|
msg=f"Issuer must be a valid domain name. "
|
|
131
126
|
)
|
|
@@ -134,7 +129,7 @@ class PACID(BaseModelWithValidationMessages):
|
|
|
134
129
|
if not_recommended_chars := set(re.sub(r'[A-Z0-9\.-]', '', self.issuer)):
|
|
135
130
|
self.add_validation_message(
|
|
136
131
|
source="PAC-ID",
|
|
137
|
-
|
|
132
|
+
level = ValidationMsgLevel.RECOMMENDATION,
|
|
138
133
|
highlight_pattern=self.issuer,
|
|
139
134
|
highlight_sub=not_recommended_chars,
|
|
140
135
|
msg=f"Characters {' '.join(not_recommended_chars)} should not be used. Issuer SHOULD contain only the characters A-Z, 0-9, -, and . "
|
|
@@ -150,7 +145,7 @@ class PACID(BaseModelWithValidationMessages):
|
|
|
150
145
|
for k in duplicate_keys:
|
|
151
146
|
self.add_validation_message(
|
|
152
147
|
source=f"identifier {k}",
|
|
153
|
-
|
|
148
|
+
level = ValidationMsgLevel.RECOMMENDATION,
|
|
154
149
|
msg=f"Duplicate segment key {k}. This will probably lead to undefined behaviour",
|
|
155
150
|
highlight_pattern = k
|
|
156
151
|
)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
2
|
+
from enum import Enum, auto
|
|
1
3
|
from pydantic import BaseModel, Field, field_validator
|
|
4
|
+
from requests import RequestException, request, ConnectionError, ReadTimeout
|
|
2
5
|
from rich import print
|
|
3
6
|
from rich.table import Table
|
|
4
7
|
|
|
@@ -29,18 +32,40 @@ class CIT(BaseModelWithValidationMessages):
|
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
|
|
35
|
+
class ServiceState(Enum):
|
|
36
|
+
ACTIVE = auto()
|
|
37
|
+
INACTIVE = auto()
|
|
38
|
+
UNKNOWN = auto()
|
|
32
39
|
|
|
33
40
|
class Service(BaseModelWithValidationMessages):
|
|
34
41
|
service_name: str
|
|
35
42
|
application_intents:list[str]
|
|
36
43
|
service_type:str
|
|
37
44
|
url:str
|
|
45
|
+
active:ServiceState =ServiceState.UNKNOWN
|
|
38
46
|
|
|
47
|
+
def check_state(self):
|
|
48
|
+
try:
|
|
49
|
+
r = request('get',self.url, timeout=2)
|
|
50
|
+
if r.status_code < 400:
|
|
51
|
+
self.active = ServiceState.ACTIVE
|
|
52
|
+
else:
|
|
53
|
+
self.active = ServiceState.INACTIVE
|
|
54
|
+
except RequestException as e:
|
|
55
|
+
print(f"Request failed: {e}")
|
|
56
|
+
self.active = ServiceState.INACTIVE
|
|
57
|
+
|
|
39
58
|
|
|
40
59
|
class CITEvaluated(BaseModelWithValidationMessages):
|
|
41
60
|
origin: str = ""
|
|
42
61
|
services: list[Service] = Field(default_factory=list)
|
|
43
62
|
|
|
63
|
+
def update_states(self):
|
|
64
|
+
with ThreadPoolExecutor(max_workers=10) as executor:
|
|
65
|
+
futures = [executor.submit(s.check_state) for s in self.services]
|
|
66
|
+
for _ in as_completed(futures):
|
|
67
|
+
pass # just wait for all to finish
|
|
68
|
+
|
|
44
69
|
def __str__(self):
|
|
45
70
|
out = [f'CIT (origin {self.origin})']
|
|
46
71
|
for s in self.services:
|
|
@@ -52,8 +77,9 @@ class CITEvaluated(BaseModelWithValidationMessages):
|
|
|
52
77
|
|
|
53
78
|
table.add_column("Service Name")
|
|
54
79
|
table.add_column("URL")
|
|
80
|
+
table.add_column('Reachable')
|
|
55
81
|
|
|
56
82
|
for s in self.services:
|
|
57
|
-
table.add_row(s.service_name, s.url)
|
|
83
|
+
table.add_row(s.service_name, s.url, s.active.name)
|
|
58
84
|
|
|
59
85
|
print(table)
|
labfreed/TREX/data_model.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import Annotated, Literal
|
|
|
8
8
|
from pydantic import PrivateAttr, RootModel, ValidationError, field_validator, model_validator, Field
|
|
9
9
|
from labfreed.TREX.unece_units import unece_unit, unece_unit_codes, unece_units, unit_name, unit_symbol
|
|
10
10
|
from labfreed.utilities.utility_types import DataTable, Quantity, Unit, unece_unit_code_from_quantity
|
|
11
|
-
from labfreed.validation import BaseModelWithValidationMessages
|
|
11
|
+
from labfreed.validation import BaseModelWithValidationMessages, ValidationMsgLevel
|
|
12
12
|
from abc import ABC, abstractmethod
|
|
13
13
|
|
|
14
14
|
from labfreed.PAC_ID.extensions import Extension
|
|
@@ -180,7 +180,7 @@ class BoolValue(ValueMixin):
|
|
|
180
180
|
if not self.value in ['T', 'F']:
|
|
181
181
|
self.add_validation_message(
|
|
182
182
|
source=f"TREX boolean value {self.value}",
|
|
183
|
-
|
|
183
|
+
level= ValidationMsgLevel.ERROR,
|
|
184
184
|
msg=f'{self.value} is no valid boolean. Must be T or F',
|
|
185
185
|
highlight_pattern = f'{self.value}',
|
|
186
186
|
highlight_sub=[c for c in self.value]
|
|
@@ -207,7 +207,7 @@ class AlphanumericValue(ValueMixin):
|
|
|
207
207
|
if re.match(r'[a-z]', self.value):
|
|
208
208
|
self.add_validation_message(
|
|
209
209
|
source=f"TREX value {self.value}",
|
|
210
|
-
|
|
210
|
+
level= ValidationMsgLevel.ERROR,
|
|
211
211
|
msg=f"Lower case characters are not allowed.",
|
|
212
212
|
highlight_pattern = self.value
|
|
213
213
|
)
|
|
@@ -215,7 +215,7 @@ class AlphanumericValue(ValueMixin):
|
|
|
215
215
|
if not_allowed_chars := set(re.sub(r'[A-Z0-9\.-]', '', self.value)):
|
|
216
216
|
self.add_validation_message(
|
|
217
217
|
source=f"TREX value {self.value}",
|
|
218
|
-
|
|
218
|
+
level= ValidationMsgLevel.ERROR,
|
|
219
219
|
msg=f"Characters {','.join(not_allowed_chars)} are not allowed in alphanumeric segment",
|
|
220
220
|
highlight_pattern = self.value,
|
|
221
221
|
highlight_sub=not_allowed_chars
|
|
@@ -242,7 +242,7 @@ class TextValue(ValueMixin):
|
|
|
242
242
|
if not_allowed_chars := set(re.sub(r'[A-Z0-9]', '', self.value)):
|
|
243
243
|
self.add_validation_message(
|
|
244
244
|
source=f"TREX value {self.value}",
|
|
245
|
-
|
|
245
|
+
level= ValidationMsgLevel.ERROR,
|
|
246
246
|
msg=f"Characters {','.join(not_allowed_chars)} are not allowed in text segment. Base36 encoding only allows A-Z0-9",
|
|
247
247
|
highlight_pattern = self.value,
|
|
248
248
|
highlight_sub=not_allowed_chars
|
|
@@ -268,7 +268,7 @@ class BinaryValue(ValueMixin):
|
|
|
268
268
|
if not_allowed_chars := set(re.sub(r'[A-Z0-9]', '', self.value)):
|
|
269
269
|
self.add_validation_message(
|
|
270
270
|
source=f"TREX value {self.value}",
|
|
271
|
-
|
|
271
|
+
tlevel= ValidationMsgLevel.ERROR,
|
|
272
272
|
msg=f"Characters {','.join(not_allowed_chars)} are not allowed in text segment. Base36 encoding only allows A-Z0-9",
|
|
273
273
|
highlight_pattern = self.value,
|
|
274
274
|
highlight_sub=not_allowed_chars
|
|
@@ -286,7 +286,7 @@ class ErrorValue(ValueMixin):
|
|
|
286
286
|
if not_allowed_chars := set(re.sub(r'[A-Z0-9\.-]', '', self.value)):
|
|
287
287
|
self.add_validation_message(
|
|
288
288
|
source=f"TREX value {self.value}",
|
|
289
|
-
|
|
289
|
+
level= ValidationMsgLevel.ERROR,
|
|
290
290
|
msg=f"Characters {','.join(not_allowed_chars)} are not allowed in error segment",
|
|
291
291
|
highlight_pattern = self.value,
|
|
292
292
|
highlight_sub=not_allowed_chars
|
|
@@ -309,7 +309,7 @@ class ValueSegment(TREX_Segment, ValueMixin, ABC):
|
|
|
309
309
|
if not self.type in valid_types:
|
|
310
310
|
self.add_validation_message(
|
|
311
311
|
source=f"TREX value segment {self.key}",
|
|
312
|
-
|
|
312
|
+
level= ValidationMsgLevel.ERROR,
|
|
313
313
|
msg=f"Type {self.type} is invalid. Must be 'T.D', 'T.B', 'T.A', 'T.T', 'T.X', 'E' or a UNECE unit",
|
|
314
314
|
highlight_pattern = self.type
|
|
315
315
|
)
|
|
@@ -385,7 +385,7 @@ class ColumnHeader(BaseModelWithValidationMessages):
|
|
|
385
385
|
if not_allowed_chars := set(re.sub(r'[A-Z0-9\.-]', '', self.key)):
|
|
386
386
|
self.add_validation_message(
|
|
387
387
|
source=f"TREX table column {self.key}",
|
|
388
|
-
|
|
388
|
+
level= ValidationMsgLevel.ERROR,
|
|
389
389
|
msg=f"Column header key contains invalid characters: {','.join(not_allowed_chars)}",
|
|
390
390
|
highlight_pattern = f'{self.key}$',
|
|
391
391
|
highlight_sub=not_allowed_chars
|
|
@@ -398,7 +398,7 @@ class ColumnHeader(BaseModelWithValidationMessages):
|
|
|
398
398
|
if not self.type in valid_types:
|
|
399
399
|
self.add_validation_message(
|
|
400
400
|
source=f"TREX table column {self.key}",
|
|
401
|
-
|
|
401
|
+
level= ValidationMsgLevel.ERROR,
|
|
402
402
|
msg=f"Type '{self.type}' is invalid. Must be 'T.D', 'T.B', 'T.A', 'T.T', 'T.X', 'E' or a UNECE unit",
|
|
403
403
|
highlight_pattern = self.type
|
|
404
404
|
)
|
|
@@ -435,7 +435,7 @@ class TREX_Table(TREX_Segment):
|
|
|
435
435
|
if len(self.column_headers) != most_common_len:
|
|
436
436
|
self.add_validation_message(
|
|
437
437
|
source=f"Table {self.key}",
|
|
438
|
-
|
|
438
|
+
level= ValidationMsgLevel.ERROR,
|
|
439
439
|
msg=f"Size mismatch: Table header contains {self.column_names} keys, while most rows have {most_common_len}",
|
|
440
440
|
highlight_pattern = self.key
|
|
441
441
|
)
|
|
@@ -448,7 +448,7 @@ class TREX_Table(TREX_Segment):
|
|
|
448
448
|
if len(row) != expected_row_len:
|
|
449
449
|
self.add_validation_message(
|
|
450
450
|
source=f"Table {self.key}",
|
|
451
|
-
|
|
451
|
+
level= ValidationMsgLevel.ERROR,
|
|
452
452
|
msg=f"Size mismatch: Table row {i} contains {len(row)} elements. Expected size is {expected_row_len}",
|
|
453
453
|
highlight_pattern = row.serialize_for_trex()
|
|
454
454
|
)
|
|
@@ -480,7 +480,7 @@ class TREX_Table(TREX_Segment):
|
|
|
480
480
|
except AssertionError:
|
|
481
481
|
self.add_validation_message(
|
|
482
482
|
source=f"Table {self.key}",
|
|
483
|
-
|
|
483
|
+
level= ValidationMsgLevel.ERROR,
|
|
484
484
|
msg=f"Type mismatch: Table row {i}, column {nm} is of wrong type. According to the header it should be {t_expected}",
|
|
485
485
|
highlight_pattern = row.serialize_for_trex(),
|
|
486
486
|
highlight_sub=[c for c in e.value]
|
|
@@ -490,7 +490,7 @@ class TREX_Table(TREX_Segment):
|
|
|
490
490
|
for m in msg:
|
|
491
491
|
self.add_validation_message(
|
|
492
492
|
source=f"Table {self.key}",
|
|
493
|
-
|
|
493
|
+
level= ValidationMsgLevel.ERROR,
|
|
494
494
|
msg=m.problem_msg,
|
|
495
495
|
highlight_pattern = row.serialize_for_trex(),
|
|
496
496
|
highlight_sub=[c for c in e.value]
|
labfreed/__init__.py
CHANGED
labfreed/validation.py
CHANGED
|
@@ -1,37 +1,52 @@
|
|
|
1
|
+
from enum import Enum, auto
|
|
2
|
+
import re
|
|
1
3
|
from pydantic import BaseModel, Field, PrivateAttr
|
|
2
4
|
from typing import List, Set, Tuple
|
|
3
5
|
|
|
4
6
|
from rich import print
|
|
5
7
|
from rich.text import Text
|
|
8
|
+
from rich.table import Table
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
domain_name_pattern = r"(?!-)([A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,63}"
|
|
9
12
|
hsegment_pattern = r"[A-Za-z0-9_\-\.~!$&'()+,:;=@]|%[0-9A-Fa-f]{2}"
|
|
10
13
|
|
|
11
14
|
|
|
15
|
+
class ValidationMsgLevel(Enum):
|
|
16
|
+
ERROR = auto()
|
|
17
|
+
ERROR_AUTO_FIX = auto()
|
|
18
|
+
WARNING = auto()
|
|
19
|
+
RECOMMENDATION = auto()
|
|
20
|
+
INFO = auto()
|
|
21
|
+
|
|
12
22
|
class ValidationMessage(BaseModel):
|
|
23
|
+
source_id:int
|
|
13
24
|
source:str
|
|
14
|
-
|
|
25
|
+
level: ValidationMsgLevel
|
|
15
26
|
problem_msg:str
|
|
16
27
|
recommendation_msg: str = ""
|
|
17
28
|
highlight:str = "" #this can be used to highlight problematic parts
|
|
18
|
-
|
|
29
|
+
highlight_sub_patterns:list[str] = Field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
19
33
|
|
|
20
|
-
@property
|
|
21
|
-
def emphazised_highlight(self):
|
|
22
|
-
fmt = lambda s: f'[emph]{s}[/emph]'
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
# @property
|
|
36
|
+
# def emphazised_highlight(self):
|
|
37
|
+
# fmt = lambda s: f'[emph]{s}[/emph]'
|
|
38
|
+
|
|
39
|
+
# if not self.highlight_sub_patterns:
|
|
40
|
+
# return fmt(self.highlight)
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
# result = []
|
|
43
|
+
# for c in self.highlight:
|
|
44
|
+
# if c in self.highlight_sub_patterns:
|
|
45
|
+
# result.append(fmt(c))
|
|
46
|
+
# else:
|
|
47
|
+
# result.append(c)
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
# return ''.join(result)
|
|
35
50
|
|
|
36
51
|
|
|
37
52
|
class LabFREEDValidationError(ValueError):
|
|
@@ -51,10 +66,10 @@ class BaseModelWithValidationMessages(BaseModel):
|
|
|
51
66
|
The purpose of that is to allow only minimal validation but on top check for stricter recommendations"""
|
|
52
67
|
_validation_messages: list[ValidationMessage] = PrivateAttr(default_factory=list)
|
|
53
68
|
|
|
54
|
-
def add_validation_message(self, *, msg: str,
|
|
69
|
+
def add_validation_message(self, *, msg: str, level:ValidationMsgLevel, recommendation:str="", source:str="", highlight_pattern="", highlight_sub=None):
|
|
55
70
|
if not highlight_sub:
|
|
56
71
|
highlight_sub = []
|
|
57
|
-
w = ValidationMessage(problem_msg=msg, recommendation_msg=recommendation, source=source,
|
|
72
|
+
w = ValidationMessage(problem_msg=msg, recommendation_msg=recommendation, source=source, level=level, highlight=highlight_pattern, highlight_sub_patterns=highlight_sub, source_id=id(self))
|
|
58
73
|
|
|
59
74
|
if not w in self._validation_messages:
|
|
60
75
|
self._validation_messages.append(w)
|
|
@@ -113,47 +128,116 @@ class BaseModelWithValidationMessages(BaseModel):
|
|
|
113
128
|
return filter_warnings(self.get_nested_validation_messages())
|
|
114
129
|
|
|
115
130
|
|
|
116
|
-
def
|
|
117
|
-
if
|
|
118
|
-
|
|
131
|
+
def str_for_validation_msg(self, validation_msg:ValidationMessage):
|
|
132
|
+
if validation_msg.source_id == id(self):
|
|
133
|
+
return validation_msg.source_id
|
|
134
|
+
#return validation_msg.emphasize_in(self(str))
|
|
135
|
+
else:
|
|
136
|
+
return str(self)
|
|
137
|
+
|
|
138
|
+
def str_highlighted(self):
|
|
139
|
+
raise NotImplementedError("Subclasses must implement format_special()")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _emphasize_in(self, validation_msg, validation_node_str:str, fmt, color='black'):
|
|
144
|
+
if validation_msg.highlight_sub_patterns:
|
|
145
|
+
replacements = validation_msg.highlight_sub_patterns
|
|
146
|
+
else:
|
|
147
|
+
replacements = [validation_msg.highlight]
|
|
148
|
+
# Sort patterns by length descending to avoid subpattern clobbering
|
|
149
|
+
sorted_patterns = sorted(replacements, key=len, reverse=True)
|
|
150
|
+
# Escape the patterns for regex safety
|
|
151
|
+
escaped_patterns = [re.escape(p) for p in sorted_patterns]
|
|
152
|
+
# Create one regex pattern with alternation (longest first)
|
|
153
|
+
pattern = re.compile("|".join(escaped_patterns))
|
|
154
|
+
|
|
155
|
+
out = pattern.sub(lambda m: fmt(m.group(0)), validation_node_str)
|
|
156
|
+
return out
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def print_validation_messages(self, target='console'):
|
|
119
160
|
msgs = self.get_nested_validation_messages()
|
|
120
|
-
print('\n'.join(['\n',
|
|
121
|
-
'=======================================',
|
|
122
|
-
'Validation Results',
|
|
123
|
-
'---------------------------------------'
|
|
124
|
-
]
|
|
125
|
-
)
|
|
126
|
-
)
|
|
127
161
|
|
|
162
|
+
table = Table(title=f"Validation Results", show_header=False)
|
|
163
|
+
|
|
164
|
+
col = lambda s: table.add_column(s, vertical='top')
|
|
165
|
+
col("-")
|
|
166
|
+
|
|
167
|
+
|
|
128
168
|
if not msgs:
|
|
129
|
-
|
|
169
|
+
table.add_row('All clear!', end_section=True)
|
|
130
170
|
return
|
|
131
171
|
|
|
132
172
|
for m in msgs:
|
|
133
|
-
if m.
|
|
173
|
+
if m.level == ValidationMsgLevel.ERROR:
|
|
134
174
|
color = 'red'
|
|
135
175
|
else:
|
|
136
176
|
color = 'yellow'
|
|
137
177
|
|
|
138
|
-
text = Text.from_markup(f'\n [bold {color}]{m.type} [/bold {color}] in \t {m.source}' )
|
|
139
|
-
print(text)
|
|
140
178
|
match target:
|
|
141
179
|
case 'markdown':
|
|
142
|
-
|
|
180
|
+
fmt = lambda s: f'🔸{s}🔸'
|
|
143
181
|
case 'console':
|
|
144
|
-
|
|
182
|
+
fmt = lambda s: f'[{color} bold]{s}[/{color} bold]'
|
|
145
183
|
case 'html':
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
184
|
+
fmt = lambda s: f'<span class="val_{color}">{s}</span>'
|
|
185
|
+
case 'html_styled':
|
|
186
|
+
fmt = lambda s: f'<b style="color:{color}>{s}</b>'
|
|
187
|
+
|
|
188
|
+
serialized = str(self)
|
|
189
|
+
emphazised_highlight = self._emphasize_in(m, serialized, fmt=fmt, color=color)
|
|
190
|
+
|
|
191
|
+
txt = f'[bold {color}]{m.level.name} [/bold {color}]'
|
|
192
|
+
txt += '\n' + f'{m.problem_msg}'
|
|
193
|
+
txt += '\n' + emphazised_highlight
|
|
194
|
+
|
|
195
|
+
table.add_row( txt)
|
|
196
|
+
table.add_section()
|
|
197
|
+
|
|
198
|
+
print(table)
|
|
199
|
+
|
|
200
|
+
# def print_validation_messages_(self, str_to_highlight_in=None, target='console'):
|
|
201
|
+
# if not str_to_highlight_in:
|
|
202
|
+
# str_to_highlight_in = str(self)
|
|
203
|
+
# msgs = self.get_nested_validation_messages()
|
|
204
|
+
# print('\n'.join(['\n',
|
|
205
|
+
# '=======================================',
|
|
206
|
+
# 'Validation Results',
|
|
207
|
+
# '---------------------------------------'
|
|
208
|
+
# ]
|
|
209
|
+
# )
|
|
210
|
+
# )
|
|
211
|
+
|
|
212
|
+
# if not msgs:
|
|
213
|
+
# print('All clear!')
|
|
214
|
+
# return
|
|
215
|
+
|
|
216
|
+
# for m in msgs:
|
|
217
|
+
# if m.level.casefold() == "error":
|
|
218
|
+
# color = 'red'
|
|
219
|
+
# else:
|
|
220
|
+
# color = 'yellow'
|
|
221
|
+
|
|
222
|
+
# text = Text.from_markup(f'\n [bold {color}]{m.level} [/bold {color}] in \t {m.source}' )
|
|
223
|
+
# print(text)
|
|
224
|
+
# match target:
|
|
225
|
+
# case 'markdown':
|
|
226
|
+
# formatted_highlight = m.emphazised_highlight.replace('emph', f'🔸').replace('[/', '').replace('[', '').replace(']', '')
|
|
227
|
+
# case 'console':
|
|
228
|
+
# formatted_highlight = m.emphazised_highlight.replace('emph', f'bold {color}')
|
|
229
|
+
# case 'html':
|
|
230
|
+
# formatted_highlight = m.emphazised_highlight.replace('emph', f'b').replace('[', '<').replace(']', '>')
|
|
231
|
+
# fmtd = str_to_highlight_in.replace(m.highlight, formatted_highlight)
|
|
232
|
+
# fmtd = Text.from_markup(fmtd)
|
|
233
|
+
# print(fmtd)
|
|
234
|
+
# print(Text.from_markup(f'{m.problem_msg}'))
|
|
151
235
|
|
|
152
236
|
|
|
153
237
|
|
|
154
238
|
def filter_errors(val_msg:list[ValidationMessage]) -> list[ValidationMessage]:
|
|
155
|
-
return [ m for m in val_msg if m.
|
|
239
|
+
return [ m for m in val_msg if m.level == ValidationMsgLevel.ERROR ]
|
|
156
240
|
|
|
157
241
|
def filter_warnings(val_msg:list[ValidationMessage]) -> list[ValidationMessage]:
|
|
158
|
-
return [ m for m in val_msg if m.
|
|
242
|
+
return [ m for m in val_msg if m.level != ValidationMsgLevel.ERROR ]
|
|
159
243
|
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: labfreed
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python implementation of LabFREED building blocks
|
|
5
|
+
Author-email: Reto Thürer <thuerer.r@buchi.com>
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: numpy>=2.2.4
|
|
10
|
+
Requires-Dist: pydantic>=2.11.3
|
|
11
|
+
Requires-Dist: segno>=1.6.6
|
|
12
|
+
Requires-Dist: typer>=0.15.2
|
|
13
|
+
|
|
14
|
+
# LabFREED for Python
|
|
15
|
+
|
|
16
|
+
[](LICENSE) [](https://pypi.org/project/labfreed/) 
|
|
17
|
+
|
|
18
|
+
<!--
|
|
19
|
+
[](https://github.com/retothuerer/LabFREED/actions/workflows/ci.yml)
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
This is a Python implementation of [LabFREED](https://labfreed.wega-it.com) building blocks.
|
|
23
|
+
|
|
24
|
+
## Supported Building Blocks
|
|
25
|
+
- PAC-ID
|
|
26
|
+
- PAC-CAT
|
|
27
|
+
- TREX
|
|
28
|
+
- Display Extension
|
|
29
|
+
- PAC-ID Resolver
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
You can install LabFREED from [PyPI](https://pypi.org/project/labfreed/) using pip:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install labfreed
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Usage Examples
|
|
40
|
+
> ⚠️ **Note:** These examples are building on each other. Imports and parsing are not repeated in each example.
|
|
41
|
+
<!-- BEGIN EXAMPLES -->
|
|
42
|
+
```python
|
|
43
|
+
# import built ins
|
|
44
|
+
import os
|
|
45
|
+
```
|
|
46
|
+
### Parse a simple PAC-ID
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from labfreed.IO.parse_pac import PAC_Parser
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Parse the PAC-ID
|
|
53
|
+
pac_str = 'HTTPS://PAC.METTORIUS.COM/-MD/bal500/@1234'
|
|
54
|
+
pac_id = PAC_Parser().parse(pac_str).pac_id
|
|
55
|
+
|
|
56
|
+
# Check validity of this PAC-ID
|
|
57
|
+
is_valid = pac_id.is_valid()
|
|
58
|
+
print(f'PAC-ID is valid: {is_valid}')
|
|
59
|
+
```
|
|
60
|
+
```text
|
|
61
|
+
>> PAC-ID is valid: True
|
|
62
|
+
```
|
|
63
|
+
### Show recommendations:
|
|
64
|
+
Note that the PAC-ID -- while valid -- uses characters which are not recommended (results in larger QR code).
|
|
65
|
+
There is a nice function to highlight problems
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
pac_id.print_validation_messages(target='markdown')
|
|
69
|
+
```
|
|
70
|
+
```text
|
|
71
|
+
>> Validation Results
|
|
72
|
+
>> ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
73
|
+
>> │ RECOMMENDATION │
|
|
74
|
+
>> │ Characters l a b should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers (0-9), '-' and '+' │
|
|
75
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/🔸b🔸🔸a🔸🔸l🔸500/@1234 │
|
|
76
|
+
>> ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
77
|
+
>> │ RECOMMENDATION │
|
|
78
|
+
>> │ Characters @ should not be used., Characters SHOULD be limited to upper case letters (A-Z), numbers (0-9), '-' and '+' │
|
|
79
|
+
>> │ HTTPS://PAC.METTORIUS.COM/-MD/bal500/🔸@🔸1234 │
|
|
80
|
+
>> └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
81
|
+
```
|
|
82
|
+
### Save as QR Code
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from labfreed.IO.generate_qr import save_qr_with_markers
|
|
86
|
+
|
|
87
|
+
save_qr_with_markers(pac_str, fmt='png')
|
|
88
|
+
```
|
|
89
|
+
```text
|
|
90
|
+
>> Large QR: Provided URL is not alphanumeric!
|
|
91
|
+
>> Size: 29
|
|
92
|
+
>> Version: 3
|
|
93
|
+
>> Error Level: M
|
|
94
|
+
```
|
|
95
|
+
### PAC-CAT
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from labfreed.PAC_CAT.data_model import PAC_CAT
|
|
99
|
+
pac_str = 'HTTPS://PAC.METTORIUS.COM/-DR/XQ908756/-MD/bal500/@1234'
|
|
100
|
+
pac_id = PAC_Parser().parse(pac_str).pac_id
|
|
101
|
+
if isinstance(pac_id, PAC_CAT):
|
|
102
|
+
pac_id.print_categories()
|
|
103
|
+
```
|
|
104
|
+
```text
|
|
105
|
+
>> Categories in
|
|
106
|
+
>> HTTPS://PAC.METTORIUS.COM/-DR/XQ90
|
|
107
|
+
>> 8756/-MD/bal500/@1234
|
|
108
|
+
>> ┌────────────────────┬───────────┐
|
|
109
|
+
>> │ Main Category │ │
|
|
110
|
+
>> │ key () │ -DR │
|
|
111
|
+
>> │ id (21) │ XQ908756 │
|
|
112
|
+
>> ├────────────────────┼───────────┤
|
|
113
|
+
>> │ Category │ │
|
|
114
|
+
>> │ key () │ -MD │
|
|
115
|
+
>> │ model_number (240) │ bal500 │
|
|
116
|
+
>> │ serial_number (21) │ @1234 │
|
|
117
|
+
>> └────────────────────┴───────────┘
|
|
118
|
+
```
|
|
119
|
+
### Parse a PAC-ID with extensions
|
|
120
|
+
PAC-ID can have extensions. Here we parse a PAC-ID with attached display names and summary.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
pac_str = 'HTTPS://PAC.METTORIUS.COM/-MD/BAL500/1234*N$N/WM633OV3E5DGJW2BEG0PDM1EA7*SUM$TREX/WEIGHT$GRM:67.89'
|
|
124
|
+
pac_id = PAC_Parser().parse(pac_str)
|
|
125
|
+
|
|
126
|
+
# Display Name
|
|
127
|
+
display_names = pac_id.get_extension('N') # display name has name 'N'
|
|
128
|
+
print(display_names)
|
|
129
|
+
```
|
|
130
|
+
```text
|
|
131
|
+
>> Display names: My Balance ❤️
|
|
132
|
+
```
|
|
133
|
+
```python
|
|
134
|
+
# TREX
|
|
135
|
+
trexes = pac_id.get_extension_of_type('TREX')
|
|
136
|
+
trex = trexes[0] # there could be multiple trexes. In this example there is only one, though
|
|
137
|
+
v = trex.get_segment('WEIGHT').to_python_type()
|
|
138
|
+
print(f'WEIGHT = {v}')
|
|
139
|
+
```
|
|
140
|
+
```text
|
|
141
|
+
>> WEIGHT = 67.89 g
|
|
142
|
+
```
|
|
143
|
+
### Create a PAC-ID with Extensions
|
|
144
|
+
|
|
145
|
+
#### Create PAC-ID
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from labfreed.PAC_ID.data_model import PACID, IDSegment
|
|
149
|
+
from labfreed.utilities.well_known_keys import WellKnownKeys
|
|
150
|
+
|
|
151
|
+
pac_id = PACID(issuer='METTORIUS.COM', identifier=[IDSegment(key=WellKnownKeys.SERIAL, value='1234')])
|
|
152
|
+
pac_str = pac_id.serialize()
|
|
153
|
+
print(pac_str)
|
|
154
|
+
```
|
|
155
|
+
```text
|
|
156
|
+
>> HTTPS://PAC.METTORIUS.COM/21:1234
|
|
157
|
+
```
|
|
158
|
+
#### Create a TREX
|
|
159
|
+
TREX can conveniently be created from a python dictionary.
|
|
160
|
+
Note that utility types for Quantity (number with unit) and table are needed
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from datetime import datetime
|
|
164
|
+
from labfreed.TREX.data_model import TREX
|
|
165
|
+
from labfreed.utilities.utility_types import Quantity, DataTable, Unit
|
|
166
|
+
|
|
167
|
+
# Create TREX
|
|
168
|
+
trex = TREX(name_='DEMO')
|
|
169
|
+
# Add value segments of different type
|
|
170
|
+
trex.update(
|
|
171
|
+
{
|
|
172
|
+
'STOP': datetime(year=2024,month=5,day=5,hour=13,minute=6),
|
|
173
|
+
'TEMP': Quantity(value=10.15, unit=Unit(name='kelvin', symbol='K')),
|
|
174
|
+
'OK':False,
|
|
175
|
+
'COMMENT': 'FOO',
|
|
176
|
+
'COMMENT2':'£'
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Create a table
|
|
181
|
+
table = DataTable(['DURATION', 'Date', 'OK', 'COMMENT'])
|
|
182
|
+
table.append([Quantity(value=1, unit=Unit(symbol='h', name='hour')), datetime.now(), True, 'FOO'])
|
|
183
|
+
table.append([ 1.1, datetime.now(), True, 'BAR'])
|
|
184
|
+
table.append([ 1.3, datetime.now(), False, 'BLUBB'])
|
|
185
|
+
#add the table to the trex
|
|
186
|
+
trex.update({'TABLE': table})
|
|
187
|
+
|
|
188
|
+
# Validation also works the same way for TREX
|
|
189
|
+
trex.print_validation_messages(target='markdown')
|
|
190
|
+
```
|
|
191
|
+
```text
|
|
192
|
+
>> Validation Results
|
|
193
|
+
>> ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
194
|
+
>> │ ERROR │
|
|
195
|
+
>> │ Column header key contains invalid characters: e,a,t │
|
|
196
|
+
>> │ DEMO$TREX/STOP$T.D:20240505T1306+TEMP$KEL:10.15+OK$T.B:F+COMMENT$T.A:FOO+COMMENT2$T.T:12G3+TABLE$$DURATION$HUR:D🔸a🔸🔸t🔸🔸e🔸$T.D:OK$T.B:COMMENT$T.A::1:20250411T090300.103:T:FOO::1… │
|
|
197
|
+
>> └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
198
|
+
```
|
|
199
|
+
```python
|
|
200
|
+
# there is an error. 'Date' uses lower case. Lets fix it
|
|
201
|
+
d = trex.dict()
|
|
202
|
+
d['TABLE'].col_names[1] = 'DATE'
|
|
203
|
+
trex = TREX(name_='DEMO')
|
|
204
|
+
trex.update(d)
|
|
205
|
+
```
|
|
206
|
+
#### Combine PAC-ID and TREX and serialize
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from labfreed.IO.parse_pac import PACID_With_Extensions
|
|
210
|
+
|
|
211
|
+
pac_with_trex = PACID_With_Extensions(pac_id=pac_id, extensions=[trex])
|
|
212
|
+
pac_str = pac_with_trex.serialize()
|
|
213
|
+
print(pac_str)
|
|
214
|
+
```
|
|
215
|
+
```text
|
|
216
|
+
>> HTTPS://PAC.METTORIUS.COM/21:1234*DEMO$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:20250411T090300.103:T:FOO::1.1:20250411T090300.103:T:BAR::1.3:20250411T090300.103:F:BLUBB
|
|
217
|
+
```
|
|
218
|
+
## PAC-ID Resolver
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from labfreed.PAC_ID_Resolver.resolver import PAC_ID_Resolver, load_cit
|
|
222
|
+
# Get a CIT
|
|
223
|
+
dir = os.path.dirname(__file__)
|
|
224
|
+
p = os.path.join(dir, 'cit_mine.yaml')
|
|
225
|
+
cit = load_cit(p)
|
|
226
|
+
|
|
227
|
+
# validate the CIT
|
|
228
|
+
cit.is_valid()
|
|
229
|
+
cit.print_validation_messages()
|
|
230
|
+
```
|
|
231
|
+
```python
|
|
232
|
+
# resolve a pac id
|
|
233
|
+
service_groups = PAC_ID_Resolver(cits=[cit]).resolve(pac_with_trex)
|
|
234
|
+
for sg in service_groups:
|
|
235
|
+
sg.update_states()
|
|
236
|
+
sg.print()
|
|
237
|
+
|
|
238
|
+
5
|
|
239
|
+
```
|
|
240
|
+
```text
|
|
241
|
+
>> Request failed: HTTPSConnectionPool(host='aaaaaaaaaaaa.mettorius.com.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL:
|
|
242
|
+
>> SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1028)')))
|
|
243
|
+
>> Services from origin 'MINE
|
|
244
|
+
>> ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
|
|
245
|
+
>> ┃ Service Name ┃ URL ┃ Reachable ┃
|
|
246
|
+
>> ┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
|
|
247
|
+
>> │ AAAAAAAAAAAAAAAAAAhhhhhh │ https://AAAAAAAAAAAA.METTORIUS.COM.com/ │ INACTIVE │
|
|
248
|
+
>> └──────────────────────────┴─────────────────────────────────────────┴───────────┘
|
|
249
|
+
>> Services from origin 'PAC.METTORIUS.COM
|
|
250
|
+
>> ┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
|
|
251
|
+
>> ┃ Service Name ┃ URL ┃ Reachable ┃
|
|
252
|
+
>> ┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
|
|
253
|
+
>> │ Shop │ https://mettorius.com/shop/an= │ INACTIVE │
|
|
254
|
+
>> │ Manual │ https://mettorius.com/om/an= │ INACTIVE │
|
|
255
|
+
>> │ _3 │ https://mettorius.com/ │ ACTIVE │
|
|
256
|
+
>> └──────────────┴────────────────────────────────┴───────────┘
|
|
257
|
+
```
|
|
258
|
+
<!-- END EXAMPLES -->
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
## Change Log
|
|
263
|
+
|
|
264
|
+
### v0.1.1
|
|
265
|
+
- minor internal improvements and bugfixes
|
|
266
|
+
|
|
267
|
+
### v0.1.0
|
|
268
|
+
- DRAFT Support for PAC-ID Resolver
|
|
269
|
+
|
|
270
|
+
### v0.0.20
|
|
271
|
+
- bugfix in TREX table to dict conversion
|
|
272
|
+
- markdown compatible validation printing
|
|
273
|
+
|
|
274
|
+
### v0.0.19
|
|
275
|
+
- supports PAC-ID, PAC-CAT, TREX and DisplayName
|
|
276
|
+
- QR generation
|
|
277
|
+
- ok-ish test coverage
|
|
278
|
+
|
|
279
|
+
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
labfreed/__init__.py,sha256=
|
|
2
|
-
labfreed/validation.py,sha256=
|
|
1
|
+
labfreed/__init__.py,sha256=TzQ186YXdEkFSNsduP7q0XO3d8HEiNMNkHMZ6t6Zg-Y,87
|
|
2
|
+
labfreed/validation.py,sha256=29Ys6qrCQ4T689juc19aiNJfHEgggI-hO7gfwrrNKWI,9554
|
|
3
3
|
labfreed/DisplayNameExtension/DisplayNameExtension.py,sha256=l9JZY2eRS0V-H5h3-WXIHiiBJuljns-_e_t9Bp84_CU,1155
|
|
4
4
|
labfreed/IO/generate_qr.py,sha256=wdZSf51Id03xSY8liK2RfB7WyGAw9O4s0VhZzrkAa-g,16680
|
|
5
|
-
labfreed/IO/parse_pac.py,sha256=
|
|
5
|
+
labfreed/IO/parse_pac.py,sha256=1rV-pJ4kUgxQMDl8DJ14SUmHV37ruKYZYBqiJjPtCUw,6381
|
|
6
6
|
labfreed/PAC_CAT/__init__.py,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
7
|
-
labfreed/PAC_CAT/data_model.py,sha256=
|
|
7
|
+
labfreed/PAC_CAT/data_model.py,sha256=9cjLA7u4APtiFbZyYp9iuYhl5mOGUPS30gXwYl0G_24,15153
|
|
8
8
|
labfreed/PAC_ID/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
labfreed/PAC_ID/data_model.py,sha256=
|
|
9
|
+
labfreed/PAC_ID/data_model.py,sha256=IZB6F2p3E90mEalnYdGjPvMGcuvNWOrqqlKXGuDyJQ4,7601
|
|
10
10
|
labfreed/PAC_ID/extensions.py,sha256=YKIE-aFf1jdL4sqCqUe3txjnP8-dA2zCJrT5lBSjvuE,1092
|
|
11
|
-
labfreed/PAC_ID_Resolver/cit.yaml,sha256
|
|
12
|
-
labfreed/PAC_ID_Resolver/data_types.py,sha256=
|
|
11
|
+
labfreed/PAC_ID_Resolver/cit.yaml,sha256=G_cH1f0Z7MUGgu7YR1cVhTrETaSBHvTVC7sbTbUJ-Rc,1992
|
|
12
|
+
labfreed/PAC_ID_Resolver/data_types.py,sha256=XFUmlw-sGLbYNdHdyb-8SZMK6BwMMQWKuLfIGO-yR3g,2650
|
|
13
13
|
labfreed/PAC_ID_Resolver/resolver.py,sha256=2EKpDEzOM8Ri-Gy5cKVE6aoHsVFRqZE9fd_llG4wrFI,8178
|
|
14
14
|
labfreed/TREX/UneceUnits.json,sha256=kwfQSp_nTuWbADfBBgqTWrvPl6XtM5SedEVLbMJrM7M,898953
|
|
15
|
-
labfreed/TREX/data_model.py,sha256=
|
|
15
|
+
labfreed/TREX/data_model.py,sha256=etb4dd2kNFGDU5upQkH1YhlutRsvblMst4FdHQyuDu0,29972
|
|
16
16
|
labfreed/TREX/parse.py,sha256=86962VEJpkrTcT436iFIB5dNed5WHABzpjxRjkA3PXo,2043
|
|
17
17
|
labfreed/TREX/unece_units.py,sha256=scPKdsPzY1neAdFOhA08_tRZaR-yplM8mBhIzzDqZBk,3006
|
|
18
18
|
labfreed/utilities/base36.py,sha256=_yX8aQ1OwrK5tnJU1NUEzQSFGr9xAVnNvPObpNzCPYs,2895
|
|
19
19
|
labfreed/utilities/utility_types.py,sha256=dM0qZgshF3cHJThVzia7UIAOdkNLKukAaaduLqKSaMY,2195
|
|
20
20
|
labfreed/utilities/well_known_keys.py,sha256=nqk66kHdSwJTJfMKlP-xQbBglS8F_NoWsGkfOVITFN0,331
|
|
21
|
-
labfreed-0.1.
|
|
22
|
-
labfreed-0.1.
|
|
23
|
-
labfreed-0.1.
|
|
24
|
-
labfreed-0.1.
|
|
21
|
+
labfreed-0.1.1.dist-info/licenses/LICENSE,sha256=gHFOv9FRKHxO8cInP3YXyPoJnuNeqrvcHjaE_wPSsQ8,1100
|
|
22
|
+
labfreed-0.1.1.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
|
|
23
|
+
labfreed-0.1.1.dist-info/METADATA,sha256=uxdO6fEUnZPsWnW_sTZD34ptdj9xBPIXlUPk0mJQI6c,13103
|
|
24
|
+
labfreed-0.1.1.dist-info/RECORD,,
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: labfreed
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Python implementation of LabFREED building blocks
|
|
5
|
-
Author-email: Reto Thürer <thuerer.r@buchi.com>
|
|
6
|
-
Description-Content-Type: text/markdown
|
|
7
|
-
License-Expression: MIT
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Requires-Dist: numpy>=2.2.4
|
|
10
|
-
Requires-Dist: pydantic>=2.11.3
|
|
11
|
-
Requires-Dist: segno>=1.6.6
|
|
12
|
-
Requires-Dist: typer>=0.15.2
|
|
13
|
-
|
|
14
|
-
# LabFREED for Python
|
|
15
|
-
|
|
16
|
-
[](LICENSE) [](https://pypi.org/project/labfreed/) 
|
|
17
|
-
|
|
18
|
-
<!--
|
|
19
|
-
[](https://github.com/retothuerer/LabFREED/actions/workflows/ci.yml)
|
|
20
|
-
-->
|
|
21
|
-
|
|
22
|
-
This is a Python implementation of [LabFREED](https://labfreed.wega-it.com) building blocks.
|
|
23
|
-
|
|
24
|
-
## Supported Building Blocks
|
|
25
|
-
- PAC-ID
|
|
26
|
-
- PAC-CAT
|
|
27
|
-
- TREX
|
|
28
|
-
- Display Extension
|
|
29
|
-
|
|
30
|
-
## Installation
|
|
31
|
-
You can install LabFREED from [PyPI](https://pypi.org/project/labfreed/) using pip:
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
pip install labfreed
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
## Usage Examples
|
|
39
|
-
> ⚠️ **Note:** These examples are building on each other. Imports and parsing are not repeated in each example.
|
|
40
|
-
<!-- BEGIN EXAMPLES -->
|
|
41
|
-
```python
|
|
42
|
-
# import built ins
|
|
43
|
-
import os
|
|
44
|
-
```
|
|
45
|
-
### Parse a simple PAC-ID
|
|
46
|
-
|
|
47
|
-
```python
|
|
48
|
-
from labfreed.IO.parse_pac import PAC_Parser
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# Parse the PAC-ID
|
|
52
|
-
pac_str = 'HTTPS://PAC.METTORIUS.COM/-MD/bal500/@1234'
|
|
53
|
-
pac_id = PAC_Parser().parse(pac_str).pac_id
|
|
54
|
-
|
|
55
|
-
# Check validity of this PAC-ID
|
|
56
|
-
pac_id = PAC_Parser().parse(pac_str).pac_id
|
|
57
|
-
is_valid = pac_id.is_valid()
|
|
58
|
-
print(f'PAC-ID is valid: {is_valid}')
|
|
59
|
-
```
|
|
60
|
-
```text
|
|
61
|
-
>> PAC-ID is valid: True
|
|
62
|
-
```
|
|
63
|
-
### Show recommendations:
|
|
64
|
-
Note that the PAC-ID -- while valid -- uses characters which are not recommended (results in larger QR code).
|
|
65
|
-
There is a nice function to highlight problems
|
|
66
|
-
|
|
67
|
-
```python
|
|
68
|
-
pac_id.print_validation_messages(target='markdown')
|
|
69
|
-
```
|
|
70
|
-
```text
|
|
71
|
-
>> =======================================
|
|
72
|
-
>> Validation Results
|
|
73
|
-
>> ---------------------------------------
|
|
74
|
-
>>
|
|
75
|
-
>> Recommendation in id segment value bal500
|
|
76
|
-
>> HTTPS://PAC.METTORIUS.COM/-MD/🔸b🔸🔸a🔸🔸l🔸500/@1234
|
|
77
|
-
>> Characters a b l should not be used.
|
|
78
|
-
>>
|
|
79
|
-
>> Recommendation in id segment value @1234
|
|
80
|
-
>> HTTPS://PAC.METTORIUS.COM/-MD/bal500/🔸@🔸1234
|
|
81
|
-
>> Characters @ should not be used.
|
|
82
|
-
>>
|
|
83
|
-
>> Warning in Category -MD
|
|
84
|
-
>> HTTPS://PAC.METTORIUS.COM/🔸-MD🔸/bal500/@1234
|
|
85
|
-
>> Category key -MD is not a well known key. It is recommended to use well known keys only
|
|
86
|
-
```
|
|
87
|
-
### Save as QR Code
|
|
88
|
-
|
|
89
|
-
```python
|
|
90
|
-
from labfreed.IO.generate_qr import save_qr_with_markers
|
|
91
|
-
|
|
92
|
-
save_qr_with_markers(pac_str, fmt='png')
|
|
93
|
-
```
|
|
94
|
-
```text
|
|
95
|
-
>> Large QR: Provided URL is not alphanumeric!
|
|
96
|
-
>> Size: 29
|
|
97
|
-
>> Version: 3
|
|
98
|
-
>> Error Level: M
|
|
99
|
-
```
|
|
100
|
-
### PAC-CAT
|
|
101
|
-
|
|
102
|
-
```python
|
|
103
|
-
from labfreed.PAC_CAT.data_model import PAC_CAT
|
|
104
|
-
pac_str = 'HTTPS://PAC.METTORIUS.COM/-DR/XQ908756/-MD/bal500/@1234'
|
|
105
|
-
pac_id = PAC_Parser().parse(pac_str).pac_id
|
|
106
|
-
if isinstance(pac_id, PAC_CAT):
|
|
107
|
-
pac_id.print_categories()
|
|
108
|
-
```
|
|
109
|
-
```text
|
|
110
|
-
>> Main Category
|
|
111
|
-
>> ----------
|
|
112
|
-
>> key (): -DR
|
|
113
|
-
>> id (21): XQ908756
|
|
114
|
-
>> Category
|
|
115
|
-
>> ------
|
|
116
|
-
>> key (): -MD
|
|
117
|
-
>> model_number (240): bal500
|
|
118
|
-
>> serial_number (21): @1234
|
|
119
|
-
```
|
|
120
|
-
### Parse a PAC-ID with extensions
|
|
121
|
-
PAC-ID can have extensions. Here we parse a PAC-ID with attached display names and summary.
|
|
122
|
-
|
|
123
|
-
```python
|
|
124
|
-
pac_str = 'HTTPS://PAC.METTORIUS.COM/-MD/BAL500/1234*N$N/WM633OV3E5DGJW2BEG0PDM1EA7*SUM$TREX/WEIGHT$GRM:67.89'
|
|
125
|
-
pac_id = PAC_Parser().parse(pac_str)
|
|
126
|
-
|
|
127
|
-
# Display Name
|
|
128
|
-
display_names = pac_id.get_extension('N') # display name has name 'N'
|
|
129
|
-
print(display_names)
|
|
130
|
-
```
|
|
131
|
-
```text
|
|
132
|
-
>> Display names: My Balance ❤️
|
|
133
|
-
```
|
|
134
|
-
```python
|
|
135
|
-
# TREX
|
|
136
|
-
trexes = pac_id.get_extension_of_type('TREX')
|
|
137
|
-
trex = trexes[0] # there could be multiple trexes. In this example there is only one, though
|
|
138
|
-
v = trex.get_segment('WEIGHT').to_python_type()
|
|
139
|
-
print(f'WEIGHT = {v}')
|
|
140
|
-
```
|
|
141
|
-
```text
|
|
142
|
-
>> WEIGHT = 67.89 g
|
|
143
|
-
```
|
|
144
|
-
### Create a PAC-ID with Extensions
|
|
145
|
-
|
|
146
|
-
#### Create PAC-ID
|
|
147
|
-
|
|
148
|
-
```python
|
|
149
|
-
from labfreed.PAC_ID.data_model import PACID, IDSegment
|
|
150
|
-
from labfreed.utilities.well_known_keys import WellKnownKeys
|
|
151
|
-
|
|
152
|
-
pac_id = PACID(issuer='METTORIUS.COM', identifier=[IDSegment(key=WellKnownKeys.SERIAL, value='1234')])
|
|
153
|
-
pac_str = pac_id.serialize()
|
|
154
|
-
print(pac_str)
|
|
155
|
-
```
|
|
156
|
-
```text
|
|
157
|
-
>> HTTPS://PAC.METTORIUS.COM/21:1234
|
|
158
|
-
```
|
|
159
|
-
#### Create a TREX
|
|
160
|
-
TREX can conveniently be created from a python dictionary.
|
|
161
|
-
Note that utility types for Quantity (number with unit) and table are needed
|
|
162
|
-
|
|
163
|
-
```python
|
|
164
|
-
from datetime import datetime
|
|
165
|
-
from labfreed.TREX.data_model import TREX
|
|
166
|
-
from labfreed.utilities.utility_types import Quantity, DataTable, Unit
|
|
167
|
-
|
|
168
|
-
# Create TREX
|
|
169
|
-
trex = TREX(name_='DEMO')
|
|
170
|
-
# Add value segments of different type
|
|
171
|
-
trex.update(
|
|
172
|
-
{
|
|
173
|
-
'STOP': datetime(year=2024,month=5,day=5,hour=13,minute=6),
|
|
174
|
-
'TEMP': Quantity(value=10.15, unit=Unit(name='kelvin', symbol='K')),
|
|
175
|
-
'OK':False,
|
|
176
|
-
'COMMENT': 'FOO',
|
|
177
|
-
'COMMENT2':'£'
|
|
178
|
-
}
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
# Create a table
|
|
182
|
-
table = DataTable(['DURATION', 'Date', 'OK', 'COMMENT'])
|
|
183
|
-
table.append([Quantity(value=1, unit=Unit(symbol='h', name='hour')), datetime.now(), True, 'FOO'])
|
|
184
|
-
table.append([ 1.1, datetime.now(), True, 'BAR'])
|
|
185
|
-
table.append([ 1.3, datetime.now(), False, 'BLUBB'])
|
|
186
|
-
#add the table to the trex
|
|
187
|
-
trex.update({'TABLE': table})
|
|
188
|
-
|
|
189
|
-
# Validation also works the same way for TREX
|
|
190
|
-
trex.print_validation_messages(target='markdown')
|
|
191
|
-
```
|
|
192
|
-
```text
|
|
193
|
-
>> =======================================
|
|
194
|
-
>> Validation Results
|
|
195
|
-
>> ---------------------------------------
|
|
196
|
-
>>
|
|
197
|
-
>> Error in TREX table column Date
|
|
198
|
-
>> DEMO$TREX/STOP$T.D:20240505T1306+TEMP$KEL:10.15+OK$T.B:F+COMMENT$T.A:FOO+COMMENT2$T.T:12G3+TABLE$$DURATION$HUR:D🔸a🔸🔸t🔸🔸e🔸$T.D:OK$T.B:COMMENT$T.A::1:20250410T094130.847:T:FOO::1.1:20250410T094130.847:T:BAR::1.3
|
|
199
|
-
>> :20250410T094130.847:F:BLUBB
|
|
200
|
-
>> Column header key contains invalid characters: t,a,e
|
|
201
|
-
```
|
|
202
|
-
```python
|
|
203
|
-
# there is an error. 'Date' uses lower case. Lets fix it
|
|
204
|
-
d = trex.dict()
|
|
205
|
-
d['TABLE'].col_names[1] = 'DATE'
|
|
206
|
-
trex = TREX(name_='DEMO')
|
|
207
|
-
trex.update(d)
|
|
208
|
-
```
|
|
209
|
-
#### Combine PAC-ID and TREX and serialize
|
|
210
|
-
|
|
211
|
-
```python
|
|
212
|
-
from labfreed.IO.parse_pac import PACID_With_Extensions
|
|
213
|
-
|
|
214
|
-
pac_with_trex = PACID_With_Extensions(pac_id=pac_id, extensions=[trex])
|
|
215
|
-
pac_str = pac_with_trex.serialize()
|
|
216
|
-
print(pac_str)
|
|
217
|
-
```
|
|
218
|
-
```text
|
|
219
|
-
>> HTTPS://PAC.METTORIUS.COM/21:1234*DEMO$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:20250410T094130.847:T:FOO::1.1:20250410T094130.847:T:BAR::1.3:20250410T094130.847:F:BLUBB
|
|
220
|
-
```
|
|
221
|
-
## PAC-ID Resolver
|
|
222
|
-
|
|
223
|
-
```python
|
|
224
|
-
from labfreed.PAC_ID_Resolver.resolver import PAC_ID_Resolver, load_cit
|
|
225
|
-
# Get a CIT
|
|
226
|
-
dir = os.path.dirname(__file__)
|
|
227
|
-
p = os.path.join(dir, 'cit_mine.yaml')
|
|
228
|
-
cit = load_cit(p)
|
|
229
|
-
|
|
230
|
-
# validate the CIT
|
|
231
|
-
cit.is_valid()
|
|
232
|
-
cit.print_validation_messages()
|
|
233
|
-
```
|
|
234
|
-
```text
|
|
235
|
-
>> [Error during execution: name '__file__' is not defined]
|
|
236
|
-
```
|
|
237
|
-
```python
|
|
238
|
-
# resolve a pac id
|
|
239
|
-
service_groups = PAC_ID_Resolver(cits=[cit]).resolve(pac_with_trex)
|
|
240
|
-
for sg in service_groups:
|
|
241
|
-
sg.print()
|
|
242
|
-
|
|
243
|
-
5
|
|
244
|
-
```
|
|
245
|
-
```text
|
|
246
|
-
>> [Error during execution: name 'cit' is not defined]
|
|
247
|
-
```
|
|
248
|
-
<!-- END EXAMPLES -->
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
## Change Log
|
|
253
|
-
|
|
254
|
-
### v0.1.0
|
|
255
|
-
- DRAFT Support for PAC-ID Resolver
|
|
256
|
-
|
|
257
|
-
### v0.0.20
|
|
258
|
-
- bugfix in TREX table to dict conversion
|
|
259
|
-
- markdown compatible validation printing
|
|
260
|
-
|
|
261
|
-
### v0.0.19
|
|
262
|
-
- supports PAC-ID, PAC-CAT, TREX and DisplayName
|
|
263
|
-
- QR generation
|
|
264
|
-
- ok-ish test coverage
|
|
265
|
-
|
|
266
|
-
|
|
File without changes
|
|
File without changes
|