labfreed 0.2.5a21__py3-none-any.whl → 0.2.6__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.

Files changed (44) hide show
  1. labfreed/__init__.py +11 -11
  2. labfreed/labfreed_infrastructure.py +258 -256
  3. labfreed/pac_cat/__init__.py +19 -19
  4. labfreed/pac_cat/category_base.py +51 -51
  5. labfreed/pac_cat/pac_cat.py +150 -150
  6. labfreed/pac_cat/predefined_categories.py +200 -200
  7. labfreed/pac_id/__init__.py +19 -19
  8. labfreed/pac_id/extension.py +48 -48
  9. labfreed/pac_id/id_segment.py +89 -89
  10. labfreed/pac_id/pac_id.py +140 -140
  11. labfreed/pac_id/url_parser.py +155 -153
  12. labfreed/pac_id/url_serializer.py +84 -80
  13. labfreed/pac_id_resolver/__init__.py +2 -2
  14. labfreed/pac_id_resolver/cit_common.py +82 -0
  15. labfreed/pac_id_resolver/cit_v1.py +245 -149
  16. labfreed/pac_id_resolver/cit_v2.py +313 -303
  17. labfreed/pac_id_resolver/resolver.py +97 -81
  18. labfreed/pac_id_resolver/services.py +79 -79
  19. labfreed/qr/__init__.py +1 -1
  20. labfreed/qr/generate_qr.py +422 -422
  21. labfreed/trex/__init__.py +16 -16
  22. labfreed/trex/python_convenience/__init__.py +3 -3
  23. labfreed/trex/python_convenience/data_table.py +87 -87
  24. labfreed/trex/python_convenience/pyTREX.py +248 -248
  25. labfreed/trex/python_convenience/quantity.py +66 -66
  26. labfreed/trex/table_segment.py +245 -245
  27. labfreed/trex/trex.py +69 -69
  28. labfreed/trex/trex_base_models.py +209 -209
  29. labfreed/trex/value_segments.py +99 -99
  30. labfreed/utilities/base36.py +82 -82
  31. labfreed/well_known_extensions/__init__.py +4 -4
  32. labfreed/well_known_extensions/default_extension_interpreters.py +6 -6
  33. labfreed/well_known_extensions/display_name_extension.py +40 -40
  34. labfreed/well_known_extensions/trex_extension.py +30 -30
  35. labfreed/well_known_keys/gs1/__init__.py +5 -5
  36. labfreed/well_known_keys/gs1/gs1.py +3 -3
  37. labfreed/well_known_keys/labfreed/well_known_keys.py +15 -15
  38. labfreed/well_known_keys/unece/__init__.py +3 -3
  39. labfreed/well_known_keys/unece/unece_units.py +67 -67
  40. {labfreed-0.2.5a21.dist-info → labfreed-0.2.6.dist-info}/METADATA +37 -21
  41. labfreed-0.2.6.dist-info/RECORD +45 -0
  42. {labfreed-0.2.5a21.dist-info → labfreed-0.2.6.dist-info}/licenses/LICENSE +21 -21
  43. labfreed-0.2.5a21.dist-info/RECORD +0 -44
  44. {labfreed-0.2.5a21.dist-info → labfreed-0.2.6.dist-info}/WHEEL +0 -0
@@ -1,149 +1,245 @@
1
-
2
- from enum import Enum
3
- import logging
4
- import re
5
-
6
- from pydantic import Field
7
- from labfreed.labfreed_infrastructure import LabFREED_BaseModel, ValidationMessage, ValidationMsgLevel
8
- from labfreed.pac_id.pac_id import PAC_ID
9
- from labfreed.pac_id_resolver.services import Service, ServiceGroup
10
-
11
- class ServiceType(Enum):
12
- USER_HANDOVER_GENERIC = 'userhandover-generic'
13
- ATTRIBUTE_SERVICE_GENERIC = 'attributes-generic'
14
-
15
-
16
- class CITEntry_v1(LabFREED_BaseModel):
17
- applicable_if: str = Field(..., min_length=1)
18
- service_name: str = Field(..., min_length=1)
19
- application_intent:str = Field(..., min_length=1)
20
- service_type:ServiceType
21
- template_url:str = Field(..., min_length=1)
22
-
23
-
24
- class CIT_v1(LabFREED_BaseModel):
25
- origin:str = ''
26
- entries:list[CITEntry_v1]
27
-
28
-
29
- @classmethod
30
- def from_csv(cls, csv:str, origin=''):
31
- lines = csv.splitlines()
32
- entries = list()
33
- errors = list()
34
- for line in lines:
35
- if not line: # empty line
36
- continue
37
- if line.strip()[0] == '#': #comment line
38
- continue
39
- if 'Service Name' in line.strip() : #header line
40
- continue
41
-
42
- cols = [c.strip() for c in line.split('\t')]
43
- try:
44
- entry = CITEntry_v1(
45
- service_name = cols[0],
46
- application_intent = cols[1],
47
- service_type = ServiceType(cols[2]),
48
- applicable_if = cols[3],
49
- template_url = cols[4]
50
- )
51
- except ValueError:
52
- logging.error(f'invalid line {line}')
53
- msg = ValidationMessage(
54
- level=ValidationMsgLevel.WARNING,
55
- source='CIT line',
56
- source_id=0,
57
- msg='Invalid line in CIT. Line was ignored. Remaining CIT is functional.',
58
- highlight_sub_patterns=line
59
- )
60
- errors.append(msg)
61
-
62
- entries.append(entry)
63
- cit = CIT_v1(origin=origin, entries=entries)
64
- cit._validation_messages.extend(errors)
65
- cit._csv_original = csv
66
- return cit
67
-
68
- def evaluate_pac_id(self, pac):
69
- cit_evaluated = ServiceGroup(origin=self.origin)
70
- for e in self.entries:
71
- conditions = e.applicable_if.split(';')
72
- conditions_evaluated = list()
73
- for c in conditions:
74
- if '=' in c:
75
- query, expected = c.split('=')
76
- value = self._find_in_pac(query.strip(), pac)
77
- conditions_evaluated.append(value == expected.strip())
78
- else:
79
- query = c.strip()
80
- found = self._find_in_pac(query, pac)
81
- conditions_evaluated.append(found)
82
- is_applicable = all(conditions_evaluated)
83
-
84
- if not is_applicable:
85
- continue
86
-
87
- url = re.sub(r"\{([^}]+)\}", lambda v: self._find_in_pac(v.group(0), pac), e.template_url)
88
- cit_evaluated.services.append(Service(
89
- service_name=e.service_name,
90
- application_intents= [ e.application_intent ],
91
- service_type=e.service_type,
92
- url = url
93
- )
94
- )
95
- return cit_evaluated
96
-
97
- def _find_in_pac(self, value, pac:PAC_ID):
98
- pac_url =pac.to_url()
99
- if value == '{isu}':
100
- return pac.issuer
101
-
102
- elif value == '{pac}':
103
- return pac_url.split('*')[0]
104
-
105
- elif value == '{id}':
106
- m = re.match(r'^HTTPS://.+?/(.+?)(\*.*)*$', pac_url)
107
- return m.group(1) if m else None
108
-
109
- elif m := re.match(r'\{idSeg(\d+)\}', value):
110
- i = int(m.group(1)) - 1 # CIT is 1 based
111
- seg = pac.identifier[i] if i < len(pac.identifier) else None
112
- if seg:
113
- return f"{(seg.key + ':') if seg.key else ''}{seg.value}"
114
-
115
- elif m := re.match(r'\{idVal(\w+)\}', value):
116
- k = m.group(1)
117
- seg = [s for s in pac.identifier if s.key and s.key == k]
118
- if seg:
119
- seg = seg[0]
120
- return seg.value
121
- else:
122
- return None
123
-
124
- elif value == '{ext}':
125
- m = re.match(r'^.*?(\*.*)*$', pac_url)
126
- ext_str = m.group(1) if m else None
127
- return m.group(1)[1:] if ext_str else None
128
-
129
- elif m := re.match(r'\{ext(\d+)\}', value):
130
- i = int(m.group(1)) - 1 # CIT is 1 based
131
- extensions = pac_url.split('*')
132
- extensions.pop(0)# first element is not extension
133
- return extensions[i] if i < len(extensions) else None
134
- else:
135
- return None
136
-
137
-
138
-
139
-
140
-
141
- def __str__(self):
142
- if csv:=self._csv_original:
143
- return csv
144
-
145
- s = "# coupling information table version: 1.0\n"
146
- s += "Service Name\tApplication Intent\tService Type\tApplicable If\tTemplate Url\n"
147
- for e in self.entries:
148
- s += '\t'.join([e.service_name, e.application_intent, e.service_type.value, e.applicable_if, e.template_url]) + '\n'
149
- return s
1
+
2
+ from enum import Enum
3
+ import logging
4
+ import re
5
+ import traceback
6
+
7
+ from pydantic import Field, model_validator
8
+ from labfreed.labfreed_infrastructure import LabFREED_BaseModel, ValidationMessage, ValidationMsgLevel
9
+ from labfreed.pac_id.pac_id import PAC_ID
10
+ from labfreed.pac_id_resolver.services import Service, ServiceGroup
11
+ from labfreed.pac_id_resolver.cit_common import ( _add_msg_to_cit_entry_model,
12
+ _validate_service_name,
13
+ _validate_application_intent,
14
+ _validate_service_type,
15
+ ServiceType)
16
+
17
+
18
+
19
+ class CITEntry_v1(LabFREED_BaseModel):
20
+ applicable_if: str = Field(..., min_length=1)
21
+ service_name: str = Field(..., min_length=1)
22
+ application_intent:str = Field(..., min_length=1)
23
+ service_type:ServiceType|str
24
+ template_url:str = Field(..., min_length=1)
25
+
26
+
27
+ @model_validator(mode='after')
28
+ def _validate_model(self):
29
+ if self.applicable_if:
30
+ conditions = self.applicable_if.split(';')
31
+ for c in conditions:
32
+ if '=' in c:
33
+ query, expected = c.split('=')
34
+ query = query.strip()
35
+ else:
36
+ query = c.strip()
37
+
38
+ try:
39
+ # use this function to check if the pattern is valid. it returns a PatternError if not
40
+ _find_pattern_in_pac(query, '')
41
+ except PatternError:
42
+ self._add_validation_message(
43
+ level=ValidationMsgLevel.ERROR,
44
+ source=f'Service {self.service_name}',
45
+ msg=f'Applicable if contains invalid pattern {query}',
46
+ highlight_sub=query
47
+ )
48
+ except Exception:
49
+ pass # if no PatternError everything is fine
50
+ return self
51
+
52
+ @model_validator(mode='after')
53
+ def _validate_service_name(self):
54
+ msg_dict= _validate_service_name(self.service_name)
55
+ return _add_msg_to_cit_entry_model(msg_dict, self)
56
+
57
+
58
+ @model_validator(mode='after')
59
+ def _validate_application_intent(self):
60
+ msg_dict= _validate_application_intent(self.application_intent)
61
+ return _add_msg_to_cit_entry_model(msg_dict, self)
62
+
63
+
64
+ @model_validator(mode='after')
65
+ def _validate_service_type(self):
66
+ msg_dict= _validate_service_type(self.service_type)
67
+ return _add_msg_to_cit_entry_model(msg_dict, self)
68
+
69
+
70
+
71
+
72
+
73
+
74
+ class CIT_v1(LabFREED_BaseModel):
75
+ origin:str = ''
76
+ entries:list[CITEntry_v1]
77
+
78
+
79
+ @classmethod
80
+ def from_csv(cls, csv:str, origin=''):
81
+ lines = csv.splitlines()
82
+ entries = list()
83
+ errors = list()
84
+ for line in lines:
85
+ if not line: # empty line
86
+ continue
87
+ if line.strip()[0] == '#': #comment line
88
+ continue
89
+ if 'Service Name' in line.strip() : #header line
90
+ continue
91
+
92
+ cols = [c.strip() for c in line.split('\t')]
93
+ if len(cols) < 5:
94
+ msg = ValidationMessage(
95
+ level=ValidationMsgLevel.ERROR,
96
+ source='CIT line',
97
+ source_id=0,
98
+ msg=f'Invalid line in CIT. There are {5 - len(cols)} columns missing.',
99
+ highlight_sub_patterns=line
100
+ )
101
+ errors.append(msg)
102
+ continue
103
+ if len(cols) > 5:
104
+ msg = ValidationMessage(
105
+ level=ValidationMsgLevel.ERROR,
106
+ source='CIT line',
107
+ source_id=0,
108
+ msg=f'Invalid line in CIT. There are {len(cols) -5} too many columns',
109
+ highlight_sub_patterns=line
110
+ )
111
+ errors.append(msg)
112
+ continue
113
+ try:
114
+
115
+ entry = CITEntry_v1(
116
+ service_name = cols[0],
117
+ application_intent = cols[1],
118
+ service_type = cols[2],
119
+ applicable_if = cols[3],
120
+ template_url = cols[4]
121
+ )
122
+ entries.append(entry)
123
+ except ValueError:
124
+ msg = ValidationMessage(
125
+ level=ValidationMsgLevel.ERROR,
126
+ source='CIT line',
127
+ source_id=0,
128
+ msg='Invalid line in CIT.',
129
+ highlight_sub_patterns=line
130
+ )
131
+ errors.append(msg)
132
+
133
+ cit = CIT_v1(origin=origin, entries=entries)
134
+ if not cit.is_valid:
135
+ errors.insert(0,
136
+ ValidationMessage(
137
+ level=ValidationMsgLevel.WARNING,
138
+ source='CIT ',
139
+ source_id=0,
140
+ msg='Invalid lines in CIT. The lines were ignored. The rest of the CIT is still functional',
141
+ highlight_sub_patterns=''
142
+ )
143
+ )
144
+ cit._validation_messages.extend(errors)
145
+ cit._csv_original = csv
146
+ return cit
147
+
148
+ def evaluate_pac_id(self, pac:PAC_ID):
149
+ if type(pac) is not PAC_ID:
150
+ raise ValueError('CIT v1 does only handle PAC-IDs. PAC-CAT it does not know what to do')
151
+ cit_evaluated = ServiceGroup(origin=self.origin)
152
+ for e in self.entries:
153
+ if e.errors():
154
+ continue #make this stable against errors in the cit
155
+
156
+ conditions = e.applicable_if.split(';')
157
+ conditions_evaluated = list()
158
+ for c in conditions:
159
+ if '=' in c:
160
+ query, expected = c.split('=')
161
+ value = _find_pattern_in_pac(query.strip(), pac)
162
+ conditions_evaluated.append(value == expected.strip())
163
+ else:
164
+ query = c.strip()
165
+ found = _find_pattern_in_pac(query, pac)
166
+ conditions_evaluated.append(found)
167
+ is_applicable = all(conditions_evaluated)
168
+
169
+ if not is_applicable:
170
+ continue
171
+
172
+ url = re.sub(r"\{([^}]+)\}", lambda v: _find_pattern_in_pac(v.group(0), pac), e.template_url)
173
+ cit_evaluated.services.append(Service(
174
+ service_name=e.service_name,
175
+ application_intents= [ e.application_intent ],
176
+ service_type=e.service_type,
177
+ url = url
178
+ )
179
+ )
180
+ return cit_evaluated
181
+
182
+
183
+
184
+ def __str__(self):
185
+ if csv:=self._csv_original:
186
+ return csv
187
+
188
+ s = "# coupling information table version: 1.0\n"
189
+ s += "Service Name\tApplication Intent\tService Type\tApplicable If\tTemplate Url\n"
190
+ for e in self.entries:
191
+ s += '\t'.join([e.service_name, e.application_intent, e.service_type.value, e.applicable_if, e.template_url]) + '\n'
192
+ return s
193
+
194
+
195
+
196
+ def _find_pattern_in_pac(value, pac:PAC_ID|str):
197
+ if not isinstance(pac, str):
198
+ pac_url =pac.to_url()
199
+ else:
200
+ pac_url = pac
201
+
202
+ if value == '{isu}':
203
+ return pac.issuer
204
+
205
+ elif value == '{pac}':
206
+ return pac_url.split('*')[0]
207
+
208
+ elif value == '{id}':
209
+ m = re.match(r'^HTTPS://.+?/(.+?)(\*.*)*$', pac_url)
210
+ return m.group(1) if m else None
211
+
212
+ elif m := re.match(r'\{idSeg(\d+)\}', value):
213
+ i = int(m.group(1)) - 1 # CIT is 1 based
214
+ seg = pac.identifier[i] if i < len(pac.identifier) else None
215
+ if seg:
216
+ return f"{(seg.key + ':') if seg.key else ''}{seg.value}"
217
+
218
+ elif m := re.match(r'\{idVal(\w+)\}', value):
219
+ k = m.group(1)
220
+ seg = [s for s in pac.identifier if s.key and s.key == k]
221
+ if seg:
222
+ seg = seg[0]
223
+ return seg.value
224
+ else:
225
+ return None
226
+
227
+ elif value == '{ext}':
228
+ m = re.match(r'^.*?(\*.*)*$', pac_url)
229
+ ext_str = m.group(1) if m else None
230
+ return m.group(1)[1:] if ext_str else None
231
+
232
+ elif m := re.match(r'\{ext(\d+)\}', value):
233
+ i = int(m.group(1)) - 1 # CIT is 1 based
234
+ extensions = pac_url.split('*')
235
+ extensions.pop(0)# first element is not extension
236
+ return extensions[i] if i < len(extensions) else None
237
+ else:
238
+ raise PatternError(f'{value} is not a recognized pattern for applicable if')
239
+
240
+ class PatternError(ValueError):
241
+ pass
242
+
243
+
244
+
245
+