labfreed 0.2.8__py3-none-any.whl → 0.2.9__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/__init__.py +11 -11
- labfreed/labfreed_infrastructure.py +258 -258
- labfreed/pac_cat/__init__.py +19 -19
- labfreed/pac_cat/category_base.py +51 -51
- labfreed/pac_cat/pac_cat.py +150 -150
- labfreed/pac_cat/predefined_categories.py +200 -200
- labfreed/pac_id/__init__.py +19 -19
- labfreed/pac_id/extension.py +48 -48
- labfreed/pac_id/id_segment.py +89 -89
- labfreed/pac_id/pac_id.py +140 -140
- labfreed/pac_id/url_parser.py +155 -155
- labfreed/pac_id/url_serializer.py +85 -84
- labfreed/pac_id_resolver/__init__.py +2 -2
- labfreed/pac_id_resolver/cit_common.py +81 -81
- labfreed/pac_id_resolver/cit_v1.py +244 -244
- labfreed/pac_id_resolver/cit_v2.py +313 -313
- labfreed/pac_id_resolver/resolver.py +97 -97
- labfreed/pac_id_resolver/services.py +82 -82
- labfreed/qr/__init__.py +1 -1
- labfreed/qr/generate_qr.py +422 -422
- labfreed/trex/__init__.py +16 -16
- labfreed/trex/python_convenience/__init__.py +3 -3
- labfreed/trex/python_convenience/data_table.py +87 -87
- labfreed/trex/python_convenience/pyTREX.py +248 -248
- labfreed/trex/python_convenience/quantity.py +66 -66
- labfreed/trex/table_segment.py +245 -245
- labfreed/trex/trex.py +69 -69
- labfreed/trex/trex_base_models.py +209 -209
- labfreed/trex/value_segments.py +99 -99
- labfreed/utilities/base36.py +82 -82
- labfreed/well_known_extensions/__init__.py +4 -4
- labfreed/well_known_extensions/default_extension_interpreters.py +6 -6
- labfreed/well_known_extensions/display_name_extension.py +40 -40
- labfreed/well_known_extensions/trex_extension.py +30 -30
- labfreed/well_known_keys/gs1/__init__.py +5 -5
- labfreed/well_known_keys/gs1/gs1.py +3 -3
- labfreed/well_known_keys/labfreed/well_known_keys.py +15 -15
- labfreed/well_known_keys/unece/__init__.py +3 -3
- labfreed/well_known_keys/unece/unece_units.py +67 -67
- {labfreed-0.2.8.dist-info → labfreed-0.2.9.dist-info}/METADATA +11 -8
- labfreed-0.2.9.dist-info/RECORD +45 -0
- {labfreed-0.2.8.dist-info → labfreed-0.2.9.dist-info}/licenses/LICENSE +21 -21
- labfreed-0.2.8.dist-info/RECORD +0 -45
- {labfreed-0.2.8.dist-info → labfreed-0.2.9.dist-info}/WHEEL +0 -0
|
@@ -1,249 +1,249 @@
|
|
|
1
|
-
|
|
2
|
-
from datetime import date, datetime, time
|
|
3
|
-
import logging
|
|
4
|
-
import re
|
|
5
|
-
from typing import Self
|
|
6
|
-
|
|
7
|
-
from pydantic import RootModel
|
|
8
|
-
from labfreed.well_known_keys.unece.unece_units import unece_unit
|
|
9
|
-
from labfreed.trex.python_convenience.data_table import DataTable
|
|
10
|
-
from labfreed.utilities.base36 import from_base36, base36, to_base36
|
|
11
|
-
|
|
12
|
-
from labfreed.trex.python_convenience.quantity import Quantity, unece_unit_code_from_quantity
|
|
13
|
-
from labfreed.trex.table_segment import ColumnHeader, TableSegment
|
|
14
|
-
from labfreed.trex.trex import TREX
|
|
15
|
-
from labfreed.trex.trex_base_models import AlphanumericValue, BinaryValue, BoolValue, DateValue, ErrorValue, NumericValue, TextValue
|
|
16
|
-
from labfreed.trex.value_segments import BoolSegment, ErrorSegment, TextSegment, NumericSegment, AlphanumericSegment, DateSegment, ValueSegment
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class pyTREX(RootModel[dict[str, Quantity | datetime | time | date | bool | str | base36 | DataTable]]):
|
|
20
|
-
''' A wrapper around dict, which knows how to convert to and from TREX.
|
|
21
|
-
It restricts the types allowed as values. Keys must be str.
|
|
22
|
-
'''
|
|
23
|
-
model_config = {'arbitrary_types_allowed':True} # needed to allow Quantity and DataTable w/o implementing the pydantic schema
|
|
24
|
-
'''@private'''
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@classmethod
|
|
28
|
-
def from_trex(cls, trex:TREX) -> Self:
|
|
29
|
-
'''Creates a pyTREX from a TREX'''
|
|
30
|
-
return {seg.key: _trex_segment_to_python_type(seg) for seg in trex.segments}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def to_trex(self) -> TREX:
|
|
34
|
-
'''Creates a TREX'''
|
|
35
|
-
segments = list()
|
|
36
|
-
for k, v in self.root.items():
|
|
37
|
-
if v is None:
|
|
38
|
-
value = _error_value_from_python_type(v)
|
|
39
|
-
segments.append(ErrorSegment(key=k, value=value.value))
|
|
40
|
-
elif isinstance(v, bool):
|
|
41
|
-
value = _bool_value_from_python_type(v)
|
|
42
|
-
segments.append(BoolSegment(key=k, value=value.value))
|
|
43
|
-
elif isinstance(v, Quantity):
|
|
44
|
-
unece_code = unece_unit_code_from_quantity(v)
|
|
45
|
-
value = _numeric_value_from_python_type(v.value)
|
|
46
|
-
segments.append(NumericSegment(key=k, value=value.value, type=unece_code))
|
|
47
|
-
elif isinstance(v, (int, float)):
|
|
48
|
-
value = _numeric_value_from_python_type(v)
|
|
49
|
-
segments.append(NumericSegment(key=k, value=value.value, type='C63')) # unitless
|
|
50
|
-
elif isinstance(v, (datetime, time, date)):
|
|
51
|
-
value = _date_value_from_python_type(v)
|
|
52
|
-
segments.append(DateSegment(key=k, value=value.value))
|
|
53
|
-
elif isinstance(v, str):
|
|
54
|
-
if re.fullmatch(r'[A-Z0-9\-\.]*', v):
|
|
55
|
-
value = _alphanumeric_value_from_python_type(v)
|
|
56
|
-
segments.append(AlphanumericSegment(key=k, value=value.value))
|
|
57
|
-
else:
|
|
58
|
-
v = to_base36(v)
|
|
59
|
-
value = _text_value_from_python_type(v)
|
|
60
|
-
segments.append(TextSegment(key=k, value=value.value))
|
|
61
|
-
elif isinstance(v, base36):
|
|
62
|
-
value = _text_value_from_python_type(v)
|
|
63
|
-
segments.append(TextSegment(key=k, value=value.value))
|
|
64
|
-
|
|
65
|
-
elif isinstance(v, DataTable):
|
|
66
|
-
v:DataTable = v
|
|
67
|
-
headers = list()
|
|
68
|
-
for nm, rt in zip(v.col_names, v.row_template):
|
|
69
|
-
if isinstance(rt, bool): # must come first otherwise int matches the bool
|
|
70
|
-
t = 'T.B'
|
|
71
|
-
elif isinstance(rt, Quantity):
|
|
72
|
-
unece_code = unece_unit_code_from_quantity(rt)
|
|
73
|
-
t = unece_code
|
|
74
|
-
elif isinstance(rt, (datetime, time, date)):
|
|
75
|
-
t = 'T.D'
|
|
76
|
-
elif isinstance(rt, str):
|
|
77
|
-
if re.fullmatch(r'[A-Z0-9\-\.]*', rt):
|
|
78
|
-
t = 'T.A'
|
|
79
|
-
else:
|
|
80
|
-
t = 'T.T'
|
|
81
|
-
elif isinstance(rt, base36):
|
|
82
|
-
t = 'T.T'
|
|
83
|
-
|
|
84
|
-
headers.append(ColumnHeader(key=nm, type=t))
|
|
85
|
-
data = []
|
|
86
|
-
for row in v.data:
|
|
87
|
-
r = []
|
|
88
|
-
for e in row:
|
|
89
|
-
if e is None:
|
|
90
|
-
r.append(_error_value_from_python_type(e))
|
|
91
|
-
elif isinstance(e, bool): # must come first otherwise int matches the bool
|
|
92
|
-
r.append(_bool_value_from_python_type(e))
|
|
93
|
-
elif isinstance(e, Quantity):
|
|
94
|
-
r.append(_numeric_value_from_python_type(e.value))
|
|
95
|
-
elif isinstance(e, (int, float)):
|
|
96
|
-
r.append(_numeric_value_from_python_type(e))
|
|
97
|
-
elif isinstance(e, (datetime, time, date)):
|
|
98
|
-
r.append(_date_value_from_python_type(e))
|
|
99
|
-
elif isinstance(e, str):
|
|
100
|
-
if re.fullmatch(r'[A-Z0-9\-\.]*', e):
|
|
101
|
-
r.append(_alphanumeric_value_from_python_type(e))
|
|
102
|
-
else:
|
|
103
|
-
e = to_base36(e)
|
|
104
|
-
r.append(_text_value_from_python_type(e))
|
|
105
|
-
elif isinstance(e, base36):
|
|
106
|
-
r.append(_text_value_from_python_type(e))
|
|
107
|
-
data.append(r)
|
|
108
|
-
segments.append(TableSegment(key=k, column_headers=headers, data=data))
|
|
109
|
-
return TREX(segments=segments)
|
|
110
|
-
|
|
111
|
-
# make the usual dict methods available, for convenience
|
|
112
|
-
def __getitem__(self, key): return self.root[key]
|
|
113
|
-
def __setitem__(self, key, value): self.root[key] = value
|
|
114
|
-
def update(self, *args, **kwargs):
|
|
115
|
-
return self.root.update(*args, **kwargs)
|
|
116
|
-
def keys(self): return self.root.keys()
|
|
117
|
-
def values(self): return self.root.values()
|
|
118
|
-
def items(self): return self.root.items()
|
|
119
|
-
def __contains__(self, key): return key in self.root
|
|
120
|
-
def __iter__(self): return iter(self.root)
|
|
121
|
-
def __len__(self): return len(self.root)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
# Helper functions to convert python types to TREX types
|
|
126
|
-
|
|
127
|
-
def _numeric_value_from_python_type(v:int|float):
|
|
128
|
-
return NumericValue(value = str(v))
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _date_value_from_python_type(v:date|time|datetime):
|
|
132
|
-
sd = ""
|
|
133
|
-
st = ""
|
|
134
|
-
if isinstance(v, date) or isinstance(v, datetime):
|
|
135
|
-
sd = v.strftime('%Y%m%d')
|
|
136
|
-
if isinstance(v, time) or isinstance(v, datetime):
|
|
137
|
-
if v.microsecond:
|
|
138
|
-
st = v.strftime("T%H%M%S.") + f"{v.microsecond // 1000:03d}"
|
|
139
|
-
elif v.second:
|
|
140
|
-
st = v.strftime("T%H%M%S")
|
|
141
|
-
else:
|
|
142
|
-
st = v.strftime("T%H%M")
|
|
143
|
-
|
|
144
|
-
return DateValue(value = sd + st)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def _bool_value_from_python_type(v:bool):
|
|
148
|
-
return BoolValue(value = 'T' if v else 'F')
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _alphanumeric_value_from_python_type(v:str):
|
|
152
|
-
return AlphanumericValue(value = v)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def _text_value_from_python_type(v:base36|str):
|
|
156
|
-
if isinstance(v, str):
|
|
157
|
-
logging.info('Got str for text value > converting to base36')
|
|
158
|
-
out = to_base36(v).root
|
|
159
|
-
else:
|
|
160
|
-
out = v.root
|
|
161
|
-
return TextValue(value = out)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def _binary_value_from_python_type(v:base36|str):
|
|
165
|
-
if isinstance(v, str):
|
|
166
|
-
out = v
|
|
167
|
-
else:
|
|
168
|
-
out = v.root
|
|
169
|
-
return BinaryValue(value = out)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def _error_value_from_python_type(v:str):
|
|
173
|
-
if v is None:
|
|
174
|
-
v = '-'
|
|
175
|
-
return ErrorValue(value = v)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
# Helper functions to convert from TREX types to python types
|
|
180
|
-
def _trex_segment_to_python_type(v):
|
|
181
|
-
'''Converts a TREX segment to a python value. Note the segment key must be handles outside.'''
|
|
182
|
-
if isinstance(v, NumericSegment):
|
|
183
|
-
num_val = _trex_value_to_python_type(v)
|
|
184
|
-
u = unece_unit(v.type)
|
|
185
|
-
unit = u.get('symbol')
|
|
186
|
-
return Quantity(value=num_val, unit=unit)
|
|
187
|
-
|
|
188
|
-
# value segments are derived from their respective value type
|
|
189
|
-
elif isinstance(v, ValueSegment):
|
|
190
|
-
return _trex_value_to_python_type(v)
|
|
191
|
-
|
|
192
|
-
elif isinstance(v, TableSegment):
|
|
193
|
-
table = DataTable(col_names=[ch.key for ch in v.column_headers])
|
|
194
|
-
for row in v.data:
|
|
195
|
-
r = []
|
|
196
|
-
for e, h in zip(row, v.column_headers):
|
|
197
|
-
if isinstance(e, NumericValue):
|
|
198
|
-
u = unece_unit(h.type)
|
|
199
|
-
unit = u.get('symbol')
|
|
200
|
-
r.append(Quantity(value=e.value, unit=unit))
|
|
201
|
-
else:
|
|
202
|
-
r.append(_trex_value_to_python_type(e))
|
|
203
|
-
table.append(r)
|
|
204
|
-
return table
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def _trex_value_to_python_type(v):
|
|
209
|
-
'''Converts a TREX value to the corresponding python type'''
|
|
210
|
-
if isinstance(v, NumericValue):
|
|
211
|
-
if '.' not in v.value and 'E' not in v.value:
|
|
212
|
-
return int(v.value)
|
|
213
|
-
else:
|
|
214
|
-
return float(v.value)
|
|
215
|
-
|
|
216
|
-
elif isinstance(v,DateValue):
|
|
217
|
-
d = v._date_time_dict
|
|
218
|
-
if d.get('year') and d.get('hour'): # input is only a time
|
|
219
|
-
return datetime(**d)
|
|
220
|
-
elif d.get('year'):
|
|
221
|
-
return date(**d)
|
|
222
|
-
else:
|
|
223
|
-
return time(**d)
|
|
224
|
-
|
|
225
|
-
elif isinstance(v, BoolValue):
|
|
226
|
-
if v.value == 'T':
|
|
227
|
-
return True
|
|
228
|
-
elif v.value == 'F':
|
|
229
|
-
return False
|
|
230
|
-
else:
|
|
231
|
-
Exception(f'{v} is not valid boolean. That really should not have been possible -- Contact the maintainers of the library')
|
|
232
|
-
|
|
233
|
-
elif isinstance(v, AlphanumericValue):
|
|
234
|
-
return v.value
|
|
235
|
-
|
|
236
|
-
elif isinstance(v, TextValue):
|
|
237
|
-
decoded = from_base36(v.value)
|
|
238
|
-
return decoded
|
|
239
|
-
|
|
240
|
-
elif isinstance(v, BinaryValue):
|
|
241
|
-
decoded = bytes(from_base36(v.value), encoding='utf-8')
|
|
242
|
-
return decoded
|
|
243
|
-
|
|
244
|
-
elif isinstance(v, ErrorValue):
|
|
245
|
-
return v.value
|
|
246
|
-
|
|
247
|
-
else:
|
|
248
|
-
raise (TypeError(f'Invalid type {type(v)} of segment'))
|
|
1
|
+
|
|
2
|
+
from datetime import date, datetime, time
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from typing import Self
|
|
6
|
+
|
|
7
|
+
from pydantic import RootModel
|
|
8
|
+
from labfreed.well_known_keys.unece.unece_units import unece_unit
|
|
9
|
+
from labfreed.trex.python_convenience.data_table import DataTable
|
|
10
|
+
from labfreed.utilities.base36 import from_base36, base36, to_base36
|
|
11
|
+
|
|
12
|
+
from labfreed.trex.python_convenience.quantity import Quantity, unece_unit_code_from_quantity
|
|
13
|
+
from labfreed.trex.table_segment import ColumnHeader, TableSegment
|
|
14
|
+
from labfreed.trex.trex import TREX
|
|
15
|
+
from labfreed.trex.trex_base_models import AlphanumericValue, BinaryValue, BoolValue, DateValue, ErrorValue, NumericValue, TextValue
|
|
16
|
+
from labfreed.trex.value_segments import BoolSegment, ErrorSegment, TextSegment, NumericSegment, AlphanumericSegment, DateSegment, ValueSegment
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class pyTREX(RootModel[dict[str, Quantity | datetime | time | date | bool | str | base36 | DataTable]]):
|
|
20
|
+
''' A wrapper around dict, which knows how to convert to and from TREX.
|
|
21
|
+
It restricts the types allowed as values. Keys must be str.
|
|
22
|
+
'''
|
|
23
|
+
model_config = {'arbitrary_types_allowed':True} # needed to allow Quantity and DataTable w/o implementing the pydantic schema
|
|
24
|
+
'''@private'''
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_trex(cls, trex:TREX) -> Self:
|
|
29
|
+
'''Creates a pyTREX from a TREX'''
|
|
30
|
+
return {seg.key: _trex_segment_to_python_type(seg) for seg in trex.segments}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def to_trex(self) -> TREX:
|
|
34
|
+
'''Creates a TREX'''
|
|
35
|
+
segments = list()
|
|
36
|
+
for k, v in self.root.items():
|
|
37
|
+
if v is None:
|
|
38
|
+
value = _error_value_from_python_type(v)
|
|
39
|
+
segments.append(ErrorSegment(key=k, value=value.value))
|
|
40
|
+
elif isinstance(v, bool):
|
|
41
|
+
value = _bool_value_from_python_type(v)
|
|
42
|
+
segments.append(BoolSegment(key=k, value=value.value))
|
|
43
|
+
elif isinstance(v, Quantity):
|
|
44
|
+
unece_code = unece_unit_code_from_quantity(v)
|
|
45
|
+
value = _numeric_value_from_python_type(v.value)
|
|
46
|
+
segments.append(NumericSegment(key=k, value=value.value, type=unece_code))
|
|
47
|
+
elif isinstance(v, (int, float)):
|
|
48
|
+
value = _numeric_value_from_python_type(v)
|
|
49
|
+
segments.append(NumericSegment(key=k, value=value.value, type='C63')) # unitless
|
|
50
|
+
elif isinstance(v, (datetime, time, date)):
|
|
51
|
+
value = _date_value_from_python_type(v)
|
|
52
|
+
segments.append(DateSegment(key=k, value=value.value))
|
|
53
|
+
elif isinstance(v, str):
|
|
54
|
+
if re.fullmatch(r'[A-Z0-9\-\.]*', v):
|
|
55
|
+
value = _alphanumeric_value_from_python_type(v)
|
|
56
|
+
segments.append(AlphanumericSegment(key=k, value=value.value))
|
|
57
|
+
else:
|
|
58
|
+
v = to_base36(v)
|
|
59
|
+
value = _text_value_from_python_type(v)
|
|
60
|
+
segments.append(TextSegment(key=k, value=value.value))
|
|
61
|
+
elif isinstance(v, base36):
|
|
62
|
+
value = _text_value_from_python_type(v)
|
|
63
|
+
segments.append(TextSegment(key=k, value=value.value))
|
|
64
|
+
|
|
65
|
+
elif isinstance(v, DataTable):
|
|
66
|
+
v:DataTable = v
|
|
67
|
+
headers = list()
|
|
68
|
+
for nm, rt in zip(v.col_names, v.row_template):
|
|
69
|
+
if isinstance(rt, bool): # must come first otherwise int matches the bool
|
|
70
|
+
t = 'T.B'
|
|
71
|
+
elif isinstance(rt, Quantity):
|
|
72
|
+
unece_code = unece_unit_code_from_quantity(rt)
|
|
73
|
+
t = unece_code
|
|
74
|
+
elif isinstance(rt, (datetime, time, date)):
|
|
75
|
+
t = 'T.D'
|
|
76
|
+
elif isinstance(rt, str):
|
|
77
|
+
if re.fullmatch(r'[A-Z0-9\-\.]*', rt):
|
|
78
|
+
t = 'T.A'
|
|
79
|
+
else:
|
|
80
|
+
t = 'T.T'
|
|
81
|
+
elif isinstance(rt, base36):
|
|
82
|
+
t = 'T.T'
|
|
83
|
+
|
|
84
|
+
headers.append(ColumnHeader(key=nm, type=t))
|
|
85
|
+
data = []
|
|
86
|
+
for row in v.data:
|
|
87
|
+
r = []
|
|
88
|
+
for e in row:
|
|
89
|
+
if e is None:
|
|
90
|
+
r.append(_error_value_from_python_type(e))
|
|
91
|
+
elif isinstance(e, bool): # must come first otherwise int matches the bool
|
|
92
|
+
r.append(_bool_value_from_python_type(e))
|
|
93
|
+
elif isinstance(e, Quantity):
|
|
94
|
+
r.append(_numeric_value_from_python_type(e.value))
|
|
95
|
+
elif isinstance(e, (int, float)):
|
|
96
|
+
r.append(_numeric_value_from_python_type(e))
|
|
97
|
+
elif isinstance(e, (datetime, time, date)):
|
|
98
|
+
r.append(_date_value_from_python_type(e))
|
|
99
|
+
elif isinstance(e, str):
|
|
100
|
+
if re.fullmatch(r'[A-Z0-9\-\.]*', e):
|
|
101
|
+
r.append(_alphanumeric_value_from_python_type(e))
|
|
102
|
+
else:
|
|
103
|
+
e = to_base36(e)
|
|
104
|
+
r.append(_text_value_from_python_type(e))
|
|
105
|
+
elif isinstance(e, base36):
|
|
106
|
+
r.append(_text_value_from_python_type(e))
|
|
107
|
+
data.append(r)
|
|
108
|
+
segments.append(TableSegment(key=k, column_headers=headers, data=data))
|
|
109
|
+
return TREX(segments=segments)
|
|
110
|
+
|
|
111
|
+
# make the usual dict methods available, for convenience
|
|
112
|
+
def __getitem__(self, key): return self.root[key]
|
|
113
|
+
def __setitem__(self, key, value): self.root[key] = value
|
|
114
|
+
def update(self, *args, **kwargs):
|
|
115
|
+
return self.root.update(*args, **kwargs)
|
|
116
|
+
def keys(self): return self.root.keys()
|
|
117
|
+
def values(self): return self.root.values()
|
|
118
|
+
def items(self): return self.root.items()
|
|
119
|
+
def __contains__(self, key): return key in self.root
|
|
120
|
+
def __iter__(self): return iter(self.root)
|
|
121
|
+
def __len__(self): return len(self.root)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Helper functions to convert python types to TREX types
|
|
126
|
+
|
|
127
|
+
def _numeric_value_from_python_type(v:int|float):
|
|
128
|
+
return NumericValue(value = str(v))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _date_value_from_python_type(v:date|time|datetime):
|
|
132
|
+
sd = ""
|
|
133
|
+
st = ""
|
|
134
|
+
if isinstance(v, date) or isinstance(v, datetime):
|
|
135
|
+
sd = v.strftime('%Y%m%d')
|
|
136
|
+
if isinstance(v, time) or isinstance(v, datetime):
|
|
137
|
+
if v.microsecond:
|
|
138
|
+
st = v.strftime("T%H%M%S.") + f"{v.microsecond // 1000:03d}"
|
|
139
|
+
elif v.second:
|
|
140
|
+
st = v.strftime("T%H%M%S")
|
|
141
|
+
else:
|
|
142
|
+
st = v.strftime("T%H%M")
|
|
143
|
+
|
|
144
|
+
return DateValue(value = sd + st)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _bool_value_from_python_type(v:bool):
|
|
148
|
+
return BoolValue(value = 'T' if v else 'F')
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _alphanumeric_value_from_python_type(v:str):
|
|
152
|
+
return AlphanumericValue(value = v)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _text_value_from_python_type(v:base36|str):
|
|
156
|
+
if isinstance(v, str):
|
|
157
|
+
logging.info('Got str for text value > converting to base36')
|
|
158
|
+
out = to_base36(v).root
|
|
159
|
+
else:
|
|
160
|
+
out = v.root
|
|
161
|
+
return TextValue(value = out)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _binary_value_from_python_type(v:base36|str):
|
|
165
|
+
if isinstance(v, str):
|
|
166
|
+
out = v
|
|
167
|
+
else:
|
|
168
|
+
out = v.root
|
|
169
|
+
return BinaryValue(value = out)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _error_value_from_python_type(v:str):
|
|
173
|
+
if v is None:
|
|
174
|
+
v = '-'
|
|
175
|
+
return ErrorValue(value = v)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# Helper functions to convert from TREX types to python types
|
|
180
|
+
def _trex_segment_to_python_type(v):
|
|
181
|
+
'''Converts a TREX segment to a python value. Note the segment key must be handles outside.'''
|
|
182
|
+
if isinstance(v, NumericSegment):
|
|
183
|
+
num_val = _trex_value_to_python_type(v)
|
|
184
|
+
u = unece_unit(v.type)
|
|
185
|
+
unit = u.get('symbol')
|
|
186
|
+
return Quantity(value=num_val, unit=unit)
|
|
187
|
+
|
|
188
|
+
# value segments are derived from their respective value type
|
|
189
|
+
elif isinstance(v, ValueSegment):
|
|
190
|
+
return _trex_value_to_python_type(v)
|
|
191
|
+
|
|
192
|
+
elif isinstance(v, TableSegment):
|
|
193
|
+
table = DataTable(col_names=[ch.key for ch in v.column_headers])
|
|
194
|
+
for row in v.data:
|
|
195
|
+
r = []
|
|
196
|
+
for e, h in zip(row, v.column_headers):
|
|
197
|
+
if isinstance(e, NumericValue):
|
|
198
|
+
u = unece_unit(h.type)
|
|
199
|
+
unit = u.get('symbol')
|
|
200
|
+
r.append(Quantity(value=e.value, unit=unit))
|
|
201
|
+
else:
|
|
202
|
+
r.append(_trex_value_to_python_type(e))
|
|
203
|
+
table.append(r)
|
|
204
|
+
return table
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _trex_value_to_python_type(v):
|
|
209
|
+
'''Converts a TREX value to the corresponding python type'''
|
|
210
|
+
if isinstance(v, NumericValue):
|
|
211
|
+
if '.' not in v.value and 'E' not in v.value:
|
|
212
|
+
return int(v.value)
|
|
213
|
+
else:
|
|
214
|
+
return float(v.value)
|
|
215
|
+
|
|
216
|
+
elif isinstance(v,DateValue):
|
|
217
|
+
d = v._date_time_dict
|
|
218
|
+
if d.get('year') and d.get('hour'): # input is only a time
|
|
219
|
+
return datetime(**d)
|
|
220
|
+
elif d.get('year'):
|
|
221
|
+
return date(**d)
|
|
222
|
+
else:
|
|
223
|
+
return time(**d)
|
|
224
|
+
|
|
225
|
+
elif isinstance(v, BoolValue):
|
|
226
|
+
if v.value == 'T':
|
|
227
|
+
return True
|
|
228
|
+
elif v.value == 'F':
|
|
229
|
+
return False
|
|
230
|
+
else:
|
|
231
|
+
Exception(f'{v} is not valid boolean. That really should not have been possible -- Contact the maintainers of the library')
|
|
232
|
+
|
|
233
|
+
elif isinstance(v, AlphanumericValue):
|
|
234
|
+
return v.value
|
|
235
|
+
|
|
236
|
+
elif isinstance(v, TextValue):
|
|
237
|
+
decoded = from_base36(v.value)
|
|
238
|
+
return decoded
|
|
239
|
+
|
|
240
|
+
elif isinstance(v, BinaryValue):
|
|
241
|
+
decoded = bytes(from_base36(v.value), encoding='utf-8')
|
|
242
|
+
return decoded
|
|
243
|
+
|
|
244
|
+
elif isinstance(v, ErrorValue):
|
|
245
|
+
return v.value
|
|
246
|
+
|
|
247
|
+
else:
|
|
248
|
+
raise (TypeError(f'Invalid type {type(v)} of segment'))
|
|
249
249
|
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
from pydantic import BaseModel, model_validator
|
|
2
|
-
from labfreed.well_known_keys.unece.unece_units import unece_units
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Quantity(BaseModel):
|
|
6
|
-
''' Represents a quantity'''
|
|
7
|
-
value: float|int
|
|
8
|
-
unit: str | None
|
|
9
|
-
'''unit. Use SI symbols. Set to None of the Quantity is dimensionless'''
|
|
10
|
-
log_least_significant_digit: int|None = None
|
|
11
|
-
|
|
12
|
-
@model_validator(mode='before')
|
|
13
|
-
@classmethod
|
|
14
|
-
def transform_inputs(cls, d:dict):
|
|
15
|
-
if not isinstance(d, dict):
|
|
16
|
-
return d
|
|
17
|
-
# decimals_to_log_significant_digits
|
|
18
|
-
if decimals:= d.pop('decimals', None):
|
|
19
|
-
d['log_least_significant_digit'] = - decimals
|
|
20
|
-
|
|
21
|
-
#dimensionless_unit
|
|
22
|
-
unit= d.get('unit')
|
|
23
|
-
if unit and unit in ['1', '', 'dimensionless']:
|
|
24
|
-
d['unit'] = None
|
|
25
|
-
return d
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@model_validator(mode='after')
|
|
29
|
-
def significat_digits_for_int(self):
|
|
30
|
-
if isinstance(self.value, int):
|
|
31
|
-
self.log_least_significant_digit = 0
|
|
32
|
-
return self
|
|
33
|
-
|
|
34
|
-
@property
|
|
35
|
-
def float(self) -> float:
|
|
36
|
-
''' for clarity returns the value'''
|
|
37
|
-
return self.value
|
|
38
|
-
|
|
39
|
-
def __str__(self):
|
|
40
|
-
unit_symbol = self.unit
|
|
41
|
-
if self.unit == "dimensionless" or not self.unit:
|
|
42
|
-
unit_symbol = ""
|
|
43
|
-
if self.log_least_significant_digit is not None:
|
|
44
|
-
val = f"{self.value:.{self.log_least_significant_digit}f}"
|
|
45
|
-
else:
|
|
46
|
-
val = str(self.value)
|
|
47
|
-
return f"{val} {unit_symbol}"
|
|
48
|
-
|
|
49
|
-
def __repr__(self):
|
|
50
|
-
return f'Quantity: {self.__repr__()}'
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def unece_unit_code_from_quantity(q:Quantity):
|
|
56
|
-
if not q.unit:
|
|
57
|
-
return 'C62' # dimensionless
|
|
58
|
-
by_name = [ u['commonCode'] for u in unece_units() if u.get('name','') == q.unit]
|
|
59
|
-
by_symbol = [ u['commonCode'] for u in unece_units() if u.get('symbol','') == q.unit]
|
|
60
|
-
by_code = [ u['commonCode'] for u in unece_units() if u.get('commonCode','') == q.unit]
|
|
61
|
-
code = list(set(by_name) | set(by_symbol) | set(by_code))
|
|
62
|
-
if len(code) != 1:
|
|
63
|
-
raise ValueError(f'No UNECE unit code found for Quantity {q}' )
|
|
64
|
-
return code[0]
|
|
65
|
-
|
|
66
|
-
|
|
1
|
+
from pydantic import BaseModel, model_validator
|
|
2
|
+
from labfreed.well_known_keys.unece.unece_units import unece_units
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Quantity(BaseModel):
|
|
6
|
+
''' Represents a quantity'''
|
|
7
|
+
value: float|int
|
|
8
|
+
unit: str | None
|
|
9
|
+
'''unit. Use SI symbols. Set to None of the Quantity is dimensionless'''
|
|
10
|
+
log_least_significant_digit: int|None = None
|
|
11
|
+
|
|
12
|
+
@model_validator(mode='before')
|
|
13
|
+
@classmethod
|
|
14
|
+
def transform_inputs(cls, d:dict):
|
|
15
|
+
if not isinstance(d, dict):
|
|
16
|
+
return d
|
|
17
|
+
# decimals_to_log_significant_digits
|
|
18
|
+
if decimals:= d.pop('decimals', None):
|
|
19
|
+
d['log_least_significant_digit'] = - decimals
|
|
20
|
+
|
|
21
|
+
#dimensionless_unit
|
|
22
|
+
unit= d.get('unit')
|
|
23
|
+
if unit and unit in ['1', '', 'dimensionless']:
|
|
24
|
+
d['unit'] = None
|
|
25
|
+
return d
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@model_validator(mode='after')
|
|
29
|
+
def significat_digits_for_int(self):
|
|
30
|
+
if isinstance(self.value, int):
|
|
31
|
+
self.log_least_significant_digit = 0
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def float(self) -> float:
|
|
36
|
+
''' for clarity returns the value'''
|
|
37
|
+
return self.value
|
|
38
|
+
|
|
39
|
+
def __str__(self):
|
|
40
|
+
unit_symbol = self.unit
|
|
41
|
+
if self.unit == "dimensionless" or not self.unit:
|
|
42
|
+
unit_symbol = ""
|
|
43
|
+
if self.log_least_significant_digit is not None:
|
|
44
|
+
val = f"{self.value:.{self.log_least_significant_digit}f}"
|
|
45
|
+
else:
|
|
46
|
+
val = str(self.value)
|
|
47
|
+
return f"{val} {unit_symbol}"
|
|
48
|
+
|
|
49
|
+
def __repr__(self):
|
|
50
|
+
return f'Quantity: {self.__repr__()}'
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def unece_unit_code_from_quantity(q:Quantity):
|
|
56
|
+
if not q.unit:
|
|
57
|
+
return 'C62' # dimensionless
|
|
58
|
+
by_name = [ u['commonCode'] for u in unece_units() if u.get('name','') == q.unit]
|
|
59
|
+
by_symbol = [ u['commonCode'] for u in unece_units() if u.get('symbol','') == q.unit]
|
|
60
|
+
by_code = [ u['commonCode'] for u in unece_units() if u.get('commonCode','') == q.unit]
|
|
61
|
+
code = list(set(by_name) | set(by_symbol) | set(by_code))
|
|
62
|
+
if len(code) != 1:
|
|
63
|
+
raise ValueError(f'No UNECE unit code found for Quantity {q}' )
|
|
64
|
+
return code[0]
|
|
65
|
+
|
|
66
|
+
|