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 +5 -4
- labfreed/PAC_CAT/data_model.py +41 -21
- labfreed/PAC_ID/data_model.py +21 -26
- labfreed/PAC_ID_Resolver/cit.yaml +92 -0
- labfreed/PAC_ID_Resolver/data_types.py +85 -0
- labfreed/PAC_ID_Resolver/resolver.py +221 -0
- labfreed/TREX/data_model.py +14 -14
- labfreed/__init__.py +1 -1
- labfreed/validation.py +126 -38
- labfreed-0.1.1.dist-info/METADATA +279 -0
- labfreed-0.1.1.dist-info/RECORD +24 -0
- labfreed-0.0.20.dist-info/METADATA +0 -230
- labfreed-0.0.20.dist-info/RECORD +0 -21
- {labfreed-0.0.20.dist-info → labfreed-0.1.1.dist-info}/WHEEL +0 -0
- {labfreed-0.0.20.dist-info → labfreed-0.1.1.dist-info}/licenses/LICENSE +0 -0
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
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
|
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
|
)
|
|
@@ -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
|
|
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
|
-
|
|
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
|
)
|
|
@@ -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)
|