labfreed 0.0.20__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 CHANGED
@@ -19,12 +19,13 @@ from ..validation import ValidationMessage, LabFREEDValidationError
19
19
 
20
20
 
21
21
  class PACID_With_Extensions(BaseModelWithValidationMessages):
22
- pac_id: PACID
22
+ pac_id: PACID = Field(serialization_alias='pac')
23
23
  extensions: list[Extension] = Field(default_factory=list)
24
24
 
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
 
@@ -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 labfreed.validation import BaseModelWithValidationMessages
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
 
@@ -14,7 +18,7 @@ class PAC_CAT(PACID):
14
18
  '''
15
19
  Extends a PAC-ID with interpretation of the identifier as categories
16
20
  '''
17
- categories:list[Category] = Field(default_factory=list())
21
+ categories:list[Category] = Field(default_factory=list)
18
22
 
19
23
  @property
20
24
  def identifier(self) -> list[IDSegment]:
@@ -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
- type="Error",
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\n----------'
153
+ title = Text('Main Category', style='bold')
146
154
  else:
147
- title = 'Category\n------ '
155
+ title = Text('Category', style='bold')
156
+
157
+ table.add_row(title)
148
158
 
149
- s += f'{title}\n'
150
- s += str(c)
151
- s += '\n'
152
- print(s)
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
@@ -175,7 +192,7 @@ class Category(BaseModelWithValidationMessages):
175
192
  "populate_by_name": True
176
193
  }
177
194
  key:str
178
- additional_segments: list[IDSegment] = Field(default_factory=list)
195
+ additional_segments: list[IDSegment] = Field(default_factory=list, exclude=True)
179
196
 
180
197
  @computed_field
181
198
  @property
@@ -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.add_validation_message(
206
- source=f"Category {self.key}",
207
- type="Warning",
208
- msg=f'Category key {self.key} is not a well known key. It is recommended to use well known keys only',
209
- highlight_pattern = f"{self.key}"
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
- type="Error",
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
- type="Error",
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
- type="Error",
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
- type="Error",
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
- type="Error",
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
  )
@@ -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
- type="Error",
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
- type="Error",
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
- type="Recommendation",
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
- type="Recommendation",
59
- msg=f"{key} is not a well known segment key.",
60
- recommendation = "RECOMMENDED to be a well-known id segment key.",
61
- highlight_pattern = key
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
- type="Recommendation",
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
- type="Recommendation",
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
- type="Recommendation",
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
  )
@@ -96,7 +92,7 @@ class IDSegment(BaseModelWithValidationMessages):
96
92
 
97
93
  class PACID(BaseModelWithValidationMessages):
98
94
  issuer:str
99
- identifier: conlist(IDSegment, min_length=1) = Field(..., default_factory=list()) # type: ignore # exclude=True prevents this from being serialized by Pydantic
95
+ identifier: conlist(IDSegment, min_length=1) = Field(..., default_factory=list) # type: ignore # exclude=True prevents this from being serialized by Pydantic
100
96
 
101
97
 
102
98
  @model_validator(mode='after')
@@ -113,9 +109,8 @@ class PACID(BaseModelWithValidationMessages):
113
109
  if l > 256:
114
110
  self.add_validation_message(
115
111
  source=f"identifier",
116
- type="Error",
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
- type="Error",
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
- type="Recommendation",
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
- type="Recommendation",
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
  )
@@ -0,0 +1,92 @@
1
+ origin: PAC.METTORIUS.COM
2
+
3
+ macros:
4
+ shop: &shop
5
+ service_type: userhandover-generic
6
+ service_name: Shop
7
+ application_intents:
8
+ - showdevicemanual-apinilabs
9
+ template_url: https://mettorius.com/shop/an={$.pac.identifier.categories[1].segments['240'].value}
10
+
11
+ manual: &manual
12
+ service_type: userhandover-generic
13
+ service_name: Manual
14
+ application_intents:
15
+ - showdevicemanual-apinilabs
16
+ template_url: https://mettorius.com/om/an={$..*['240'].value}
17
+
18
+ CoA: &coa
19
+ service_type: userhandover-generic
20
+ service_name: CoA
21
+ application_intents:
22
+ - showCoA-apinilabs
23
+ template_url: https://mettorius.com/downloads/an={$..*['240'].value}
24
+
25
+ dummy: &dummy
26
+ service_type: userhandover-generic
27
+ application_intents:
28
+ - dummy
29
+ template_url: https://mettorius.com/
30
+
31
+
32
+ cit:
33
+ # Test
34
+ - if: mettorius.com == $.pac.issuer
35
+ entries:
36
+ - <<: *shop
37
+ - <<: *manual
38
+
39
+ # Instruments
40
+ - if: mettorius.com == $.pac.issuer AND $.pac.id.categories['-MD']
41
+ entries:
42
+ - <<: *shop
43
+ - <<: *manual
44
+
45
+
46
+ # Consumables
47
+ - if: mettorius.com == $.pac.issuer AND $.pac.id.categories['-MC']
48
+ entries:
49
+ - <<: *shop
50
+ - <<: *manual
51
+ - <<: *coa
52
+
53
+
54
+
55
+ # Showcase for logic
56
+ ##############################
57
+
58
+ # multiline string
59
+ - if: "($.pac.issuer == mettorius.com)
60
+ AND $.pac.id.categories['-MD']"
61
+ entries:
62
+ - <<: *dummy
63
+ service_name: multiline applicable_if
64
+
65
+ # folded string
66
+ - if: |
67
+ ($.pac.issuer == mettorius.com)
68
+ AND $.pac.id.categories['-MD']
69
+ entries:
70
+ - <<: *dummy
71
+ service_name: folded applicable_if
72
+
73
+ # multiline string
74
+ - if: |
75
+ NOT NOT ($.pac.issuer == mettorius.com)
76
+ AND $.pac.id.categories['-MD']
77
+ entries:
78
+ - <<: *dummy
79
+ service_name: multiline applicable_if
80
+
81
+ - if: NOT ($.pac.issuer != METTORIUS.COM)
82
+ entries:
83
+ - <<: *dummy
84
+ service_name: _3
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
@@ -0,0 +1,85 @@
1
+ from concurrent.futures import ThreadPoolExecutor, as_completed
2
+ from enum import Enum, auto
3
+ from pydantic import BaseModel, Field, field_validator
4
+ from requests import RequestException, request, ConnectionError, ReadTimeout
5
+ from rich import print
6
+ from rich.table import Table
7
+
8
+ from labfreed.validation import BaseModelWithValidationMessages
9
+
10
+
11
+ class CITEntry(BaseModelWithValidationMessages):
12
+ service_name: str
13
+ application_intents:list[str]
14
+ service_type:str
15
+ template_url:str
16
+
17
+
18
+ class CITBlock(BaseModelWithValidationMessages):
19
+ applicable_if: str = Field(default='True', alias='if')
20
+ entries: list[CITEntry]
21
+
22
+ @field_validator('applicable_if', mode='before')
23
+ @classmethod
24
+ def convert_if(cls, v):
25
+ return v if v is not None else 'True'
26
+
27
+
28
+ class CIT(BaseModelWithValidationMessages):
29
+ origin: str = ''
30
+ macros:dict = Field(default_factory=dict)
31
+ cit: list[CITBlock] = Field(default_factory=list)
32
+
33
+
34
+
35
+ class ServiceState(Enum):
36
+ ACTIVE = auto()
37
+ INACTIVE = auto()
38
+ UNKNOWN = auto()
39
+
40
+ class Service(BaseModelWithValidationMessages):
41
+ service_name: str
42
+ application_intents:list[str]
43
+ service_type:str
44
+ url:str
45
+ active:ServiceState =ServiceState.UNKNOWN
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
+
58
+
59
+ class CITEvaluated(BaseModelWithValidationMessages):
60
+ origin: str = ""
61
+ services: list[Service] = Field(default_factory=list)
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
+
69
+ def __str__(self):
70
+ out = [f'CIT (origin {self.origin})']
71
+ for s in self.services:
72
+ out.append(f'{s.service_name}\t\t\t{s.url}')
73
+ return '\n'.join(out)
74
+
75
+ def print(self):
76
+ table = Table(title=f"Services from origin '{self.origin}")
77
+
78
+ table.add_column("Service Name")
79
+ table.add_column("URL")
80
+ table.add_column('Reachable')
81
+
82
+ for s in self.services:
83
+ table.add_row(s.service_name, s.url, s.active.name)
84
+
85
+ print(table)