labfreed 0.0.8__py2.py3-none-any.whl → 0.0.10__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.
- labfreed/DisplayNameExtension/DisplayNameExtension.py +6 -3
- labfreed/PAC_CAT/data_model copy.py +232 -0
- labfreed/PAC_CAT/data_model.py +319 -59
- labfreed/PAC_ID/data_model.py +89 -127
- labfreed/PAC_ID/extensions.py +55 -0
- labfreed/TREX/UneceUnits.json +33730 -0
- labfreed/TREX/data_model.py +789 -0
- labfreed/{TREXExtension → TREX}/parse.py +23 -16
- labfreed/TREX/unece_units.py +106 -0
- labfreed/__init__.py +1 -1
- labfreed/parse_pac.py +189 -0
- labfreed/{DisplayNameExtension → utilities}/base36.py +29 -13
- labfreed/utilities/extension_intertpreters.py +4 -0
- labfreed/utilities/utility_types.py +103 -0
- labfreed/{PAC_ID/well_known_segment_keys.py → utilities/well_known_keys.py} +1 -1
- labfreed/validation.py +117 -39
- {labfreed-0.0.8.dist-info → labfreed-0.0.10.dist-info}/METADATA +1 -1
- labfreed-0.0.10.dist-info/RECORD +22 -0
- labfreed/PAC_ID/parse.py +0 -142
- labfreed/PAC_ID/serialize.py +0 -60
- labfreed/TREXExtension/data_model.py +0 -239
- labfreed/TREXExtension/uncertainty.py +0 -32
- labfreed/TREXExtension/unit_utilities.py +0 -143
- labfreed-0.0.8.dist-info/RECORD +0 -19
- {labfreed-0.0.8.dist-info → labfreed-0.0.10.dist-info}/WHEEL +0 -0
- {labfreed-0.0.8.dist-info → labfreed-0.0.10.dist-info}/licenses/LICENSE +0 -0
labfreed/PAC_ID/data_model.py
CHANGED
|
@@ -2,30 +2,47 @@ import re
|
|
|
2
2
|
from typing import Optional
|
|
3
3
|
from typing_extensions import Self
|
|
4
4
|
from pydantic import Field, ValidationInfo, computed_field, conlist, model_validator, field_validator
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
from abc import ABC, abstractproperty, abstractstaticmethod
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
from ..utilities.well_known_keys import WellKnownKeys
|
|
9
|
+
from labfreed.validation import BaseModelWithValidationMessages, ValidationMessage, hsegment_pattern, domain_name_pattern
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
class IDSegment(
|
|
12
|
+
class IDSegment(BaseModelWithValidationMessages):
|
|
11
13
|
key:str|None = None
|
|
12
14
|
value:str
|
|
15
|
+
|
|
13
16
|
@model_validator(mode="after")
|
|
14
|
-
def validate_segment(
|
|
15
|
-
key =
|
|
16
|
-
value =
|
|
17
|
+
def validate_segment(self):
|
|
18
|
+
key = self.key or ""
|
|
19
|
+
value = self.value
|
|
17
20
|
|
|
18
21
|
# MUST be a valid hsegment according to RFC 1738, but without * (see PAC-ID Extension)
|
|
19
22
|
# This means it must be true for both, key and value
|
|
20
23
|
if not_allowed_chars := set(re.sub(hsegment_pattern, '', key)):
|
|
21
|
-
|
|
24
|
+
self.add_validation_message(
|
|
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",
|
|
29
|
+
highlight_pattern = key,
|
|
30
|
+
highlight_sub = not_allowed_chars
|
|
31
|
+
)
|
|
22
32
|
|
|
23
33
|
if not_allowed_chars := set(re.sub(hsegment_pattern, '', value)):
|
|
24
|
-
|
|
34
|
+
self.add_validation_message(
|
|
35
|
+
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",
|
|
39
|
+
highlight_pattern = value,
|
|
40
|
+
highlight_sub = not_allowed_chars
|
|
41
|
+
)
|
|
25
42
|
|
|
26
43
|
# Segment key SHOULD be limited to A-Z, 0-9, and -+..
|
|
27
44
|
if not_recommended_chars := set(re.sub(r'[A-Z0-9-:+]', '', key)):
|
|
28
|
-
|
|
45
|
+
self.add_validation_message(
|
|
29
46
|
source=f"id segment key {key}",
|
|
30
47
|
type="Recommendation",
|
|
31
48
|
msg=f"{' '.join(not_recommended_chars)} should not be used.",
|
|
@@ -35,8 +52,8 @@ class IDSegment(BaseModelWithWarnings):
|
|
|
35
52
|
)
|
|
36
53
|
|
|
37
54
|
# Segment key should be in Well know keys
|
|
38
|
-
if key and key not in [k.value for k in
|
|
39
|
-
|
|
55
|
+
if key and key not in [k.value for k in WellKnownKeys]:
|
|
56
|
+
self.add_validation_message(
|
|
40
57
|
source=f"id segment key {key}",
|
|
41
58
|
type="Recommendation",
|
|
42
59
|
msg=f"{key} is not a well known segment key.",
|
|
@@ -47,7 +64,7 @@ class IDSegment(BaseModelWithWarnings):
|
|
|
47
64
|
|
|
48
65
|
# Segment value SHOULD be limited to A-Z, 0-9, and -+..
|
|
49
66
|
if not_recommended_chars := set(re.sub(r'[A-Z0-9-:+]', '', value)):
|
|
50
|
-
|
|
67
|
+
self.add_validation_message(
|
|
51
68
|
source=f"id segment value {value}",
|
|
52
69
|
type="Recommendation",
|
|
53
70
|
msg=f"Characters {' '.join(not_recommended_chars)} should not be used.",
|
|
@@ -59,157 +76,102 @@ class IDSegment(BaseModelWithWarnings):
|
|
|
59
76
|
# Segment value SHOULD be limited to A-Z, 0-9, and :-+ for new designs.
|
|
60
77
|
# this means that ":" in key or value is problematic
|
|
61
78
|
if ':' in key:
|
|
62
|
-
|
|
79
|
+
self.add_validation_message(
|
|
63
80
|
source=f"id segment key {key}",
|
|
64
81
|
type="Recommendation",
|
|
65
82
|
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.",
|
|
66
83
|
highlight_pattern = key
|
|
67
84
|
)
|
|
68
85
|
if ':' in value:
|
|
69
|
-
|
|
86
|
+
self.add_validation_message(
|
|
70
87
|
source=f"id segment value {value}",
|
|
71
88
|
type="Recommendation",
|
|
72
89
|
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.",
|
|
73
90
|
highlight_pattern = value
|
|
74
91
|
)
|
|
75
92
|
|
|
76
|
-
return
|
|
77
|
-
|
|
93
|
+
return self
|
|
94
|
+
|
|
78
95
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class Category(BaseModelWithWarnings):
|
|
82
|
-
key:str|None = None
|
|
83
|
-
segments: list[IDSegment]
|
|
84
96
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
class PACID(BaseModelWithValidationMessages):
|
|
98
|
+
issuer:str
|
|
99
|
+
identifier: conlist(IDSegment, min_length=1) = Field(..., default_factory=list()) # type: ignore # exclude=True prevents this from being serialized by Pydantic
|
|
88
100
|
|
|
89
|
-
@computed_field
|
|
90
|
-
@property
|
|
91
|
-
def categories(self) -> list[Category]:
|
|
92
|
-
categories = list()
|
|
93
|
-
c = Category(segments=[])
|
|
94
|
-
categories.append(c)
|
|
95
|
-
for s in self.segments:
|
|
96
|
-
# new category starts with "-"
|
|
97
|
-
if s.value[0] == '-':
|
|
98
|
-
cat_key = s.value
|
|
99
|
-
c = Category(key=cat_key, segments=[])
|
|
100
|
-
categories.append(c)
|
|
101
|
-
else:
|
|
102
|
-
c.segments.append(s)
|
|
103
|
-
|
|
104
|
-
# the first category might have no segments. remove categories without segments
|
|
105
|
-
if not categories[0].segments:
|
|
106
|
-
categories = categories[1:]
|
|
107
|
-
|
|
108
|
-
return categories
|
|
109
|
-
|
|
110
|
-
@model_validator(mode='after')
|
|
111
|
-
def check_keys_are_unique_in_each_category(self) -> Self:
|
|
112
|
-
for c in self.categories:
|
|
113
|
-
keys = [s.key for s in c.segments if s.key]
|
|
114
|
-
duplicate_keys = [k for k in set(keys) if keys.count(k) > 1]
|
|
115
|
-
if duplicate_keys:
|
|
116
|
-
raise ValueError(f'Duplicate keys {",".join(duplicate_keys)} in category {c.key}')
|
|
117
|
-
return self
|
|
118
101
|
|
|
119
102
|
@model_validator(mode='after')
|
|
120
103
|
def check_length(self) -> Self:
|
|
121
104
|
l = 0
|
|
122
|
-
for s in self.
|
|
105
|
+
for s in self.identifier:
|
|
106
|
+
s:IDSegment = s
|
|
123
107
|
if s.key:
|
|
124
108
|
l += len(s.key)
|
|
125
109
|
l += 1 # for ":"
|
|
126
110
|
l += len(s.value)
|
|
127
|
-
l += len(self.
|
|
111
|
+
l += len(self.identifier) - 1 # account for "/" separating the segments
|
|
128
112
|
|
|
129
113
|
if l > 256:
|
|
130
|
-
|
|
114
|
+
self.add_validation_message(
|
|
115
|
+
source=f"identifier",
|
|
116
|
+
type="Error",
|
|
117
|
+
msg=f'Identifier is {l} characters long, Identifier must not exceed 256 characters.',
|
|
118
|
+
highlight_pattern = ""
|
|
119
|
+
)
|
|
131
120
|
return self
|
|
132
121
|
|
|
133
|
-
@staticmethod
|
|
134
|
-
def from_categories(categories:list[Category]) :
|
|
135
|
-
segments = list()
|
|
136
|
-
for c in categories:
|
|
137
|
-
if c.key:
|
|
138
|
-
segments.append(IDSegment(value=c.key))
|
|
139
|
-
segments.extend(c.segments)
|
|
140
|
-
return Identifier(segments=segments)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class Extension(ABC, BaseModelWithWarnings):
|
|
145
|
-
|
|
146
|
-
@abstractproperty
|
|
147
|
-
def name(self)->str:
|
|
148
|
-
pass
|
|
149
|
-
|
|
150
|
-
@abstractproperty
|
|
151
|
-
def type(self)->str:
|
|
152
|
-
pass
|
|
153
|
-
|
|
154
|
-
@abstractproperty
|
|
155
|
-
def data(self)->str:
|
|
156
|
-
pass
|
|
157
|
-
|
|
158
|
-
@abstractstaticmethod
|
|
159
|
-
def from_spec_fields(name, type, data):
|
|
160
|
-
pass
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class UnknownExtension(Extension):
|
|
164
|
-
name_:str
|
|
165
|
-
type_:str
|
|
166
|
-
data_:str
|
|
167
|
-
|
|
168
|
-
@property
|
|
169
|
-
def name(self)->str:
|
|
170
|
-
return self.name_
|
|
171
|
-
|
|
172
|
-
@property
|
|
173
|
-
def type(self)->str:
|
|
174
|
-
return self.type_
|
|
175
|
-
|
|
176
|
-
@property
|
|
177
|
-
def data(self)->str:
|
|
178
|
-
return self.data_
|
|
179
|
-
|
|
180
|
-
@staticmethod
|
|
181
|
-
def from_spec_fields(name, type, data):
|
|
182
|
-
return UnknownExtension(name_=name, type_=type, data_=data)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
class PACID(BaseModelWithWarnings):
|
|
187
|
-
issuer:str
|
|
188
|
-
identifier: Identifier
|
|
189
122
|
|
|
190
123
|
@model_validator(mode="after")
|
|
191
|
-
def validate_issuer(
|
|
192
|
-
if not re.fullmatch(domain_name_pattern,
|
|
193
|
-
|
|
194
|
-
|
|
124
|
+
def validate_issuer(self):
|
|
125
|
+
if not re.fullmatch(domain_name_pattern, self.issuer):
|
|
126
|
+
self.add_validation_message(
|
|
127
|
+
source="PAC-ID",
|
|
128
|
+
type="Error",
|
|
129
|
+
highlight_pattern=self.issuer,
|
|
130
|
+
msg=f"Issuer must be a valid domain name. "
|
|
131
|
+
)
|
|
195
132
|
|
|
196
133
|
# recommendation that A-Z, 0-9, -, and . should be used
|
|
197
|
-
if not_recommended_chars := set(re.sub(r'[A-Z0-9\.-]', '',
|
|
198
|
-
|
|
134
|
+
if not_recommended_chars := set(re.sub(r'[A-Z0-9\.-]', '', self.issuer)):
|
|
135
|
+
self.add_validation_message(
|
|
199
136
|
source="PAC-ID",
|
|
200
137
|
type="Recommendation",
|
|
201
|
-
highlight_pattern=
|
|
138
|
+
highlight_pattern=self.issuer,
|
|
202
139
|
highlight_sub=not_recommended_chars,
|
|
203
140
|
msg=f"Characters {' '.join(not_recommended_chars)} should not be used. Issuer SHOULD contain only the characters A-Z, 0-9, -, and . "
|
|
204
141
|
)
|
|
205
|
-
return
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@model_validator(mode='after')
|
|
146
|
+
def check_identifier_segment_keys_are_unique(self) -> Self:
|
|
147
|
+
keys = [s.key for s in self.identifier if s.key]
|
|
148
|
+
duplicate_keys = [k for k in set(keys) if keys.count(k) > 1]
|
|
149
|
+
if duplicate_keys:
|
|
150
|
+
for k in duplicate_keys:
|
|
151
|
+
self.add_validation_message(
|
|
152
|
+
source=f"identifier {k}",
|
|
153
|
+
type="Recommendation",
|
|
154
|
+
msg=f"Duplicate segment key {k}. This will probably lead to undefined behaviour",
|
|
155
|
+
highlight_pattern = k
|
|
156
|
+
)
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def __str__(self):
|
|
161
|
+
id_segments = ''
|
|
162
|
+
for s in self.identifier:
|
|
163
|
+
s:IDSegment = s
|
|
164
|
+
if s.key:
|
|
165
|
+
id_segments += f'/{s.key}:{s.value}'
|
|
166
|
+
else:
|
|
167
|
+
id_segments += f'/{s.value}'
|
|
168
|
+
|
|
169
|
+
out = f"HTTPS://PAC.{self.issuer}{id_segments}"
|
|
170
|
+
return out
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def serialize(self):
|
|
174
|
+
return str(self)
|
|
206
175
|
|
|
207
176
|
|
|
208
|
-
class PACID_With_Extensions(BaseModelWithWarnings):
|
|
209
|
-
pac_id: PACID
|
|
210
|
-
extensions: list[Extension] = Field(default_factory=list)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
177
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
|
|
2
|
+
from abc import ABC, abstractproperty, abstractstaticmethod
|
|
3
|
+
from types import MappingProxyType
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from labfreed.validation import BaseModelWithValidationMessages
|
|
8
|
+
from labfreed.PAC_ID.data_model import PACID
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Extension(ABC, BaseModelWithValidationMessages):
|
|
14
|
+
|
|
15
|
+
@abstractproperty
|
|
16
|
+
def name(self)->str:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
@abstractproperty
|
|
20
|
+
def type(self)->str:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@abstractproperty
|
|
24
|
+
def data(self)->str:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
@abstractstaticmethod
|
|
28
|
+
def from_spec_fields(*, name, type, data):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return f'{self.name}${self.type}/{self.data}'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class UnknownExtension(Extension):
|
|
37
|
+
name_:str
|
|
38
|
+
type_:str
|
|
39
|
+
data_:str
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def name(self)->str:
|
|
43
|
+
return self.name_
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def type(self)->str:
|
|
47
|
+
return self.type_
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def data(self)->str:
|
|
51
|
+
return self.data_
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def from_spec_fields(*, name, type, data):
|
|
55
|
+
return UnknownExtension(name_=name, type_=type, data_=data)
|