labfreed 0.0.3__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.
@@ -0,0 +1,177 @@
1
+ import re
2
+ from typing import Optional
3
+ from typing_extensions import Self
4
+ from pydantic import Field, ValidationInfo, computed_field, conlist, model_validator, field_validator
5
+
6
+ from abc import ABC, abstractproperty, abstractstaticmethod
7
+
8
+ from ..utilities.well_known_keys import WellKnownKeys
9
+ from labfreed.validation import BaseModelWithValidationMessages, ValidationMessage, hsegment_pattern, domain_name_pattern
10
+
11
+
12
+ class IDSegment(BaseModelWithValidationMessages):
13
+ key:str|None = None
14
+ value:str
15
+
16
+ @model_validator(mode="after")
17
+ def validate_segment(self):
18
+ key = self.key or ""
19
+ value = self.value
20
+
21
+ # MUST be a valid hsegment according to RFC 1738, but without * (see PAC-ID Extension)
22
+ # This means it must be true for both, key and value
23
+ if not_allowed_chars := set(re.sub(hsegment_pattern, '', key)):
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
+ )
32
+
33
+ if not_allowed_chars := set(re.sub(hsegment_pattern, '', value)):
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
+ )
42
+
43
+ # Segment key SHOULD be limited to A-Z, 0-9, and -+..
44
+ if not_recommended_chars := set(re.sub(r'[A-Z0-9-:+]', '', key)):
45
+ self.add_validation_message(
46
+ 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 -+",
50
+ highlight_pattern = key,
51
+ highlight_sub = not_recommended_chars
52
+ )
53
+
54
+ # Segment key should be in Well know keys
55
+ if key and key not in [k.value for k in WellKnownKeys]:
56
+ self.add_validation_message(
57
+ 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
62
+ )
63
+
64
+
65
+ # Segment value SHOULD be limited to A-Z, 0-9, and -+..
66
+ if not_recommended_chars := set(re.sub(r'[A-Z0-9-:+]', '', value)):
67
+ self.add_validation_message(
68
+ 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 -+",
72
+ highlight_pattern = value,
73
+ highlight_sub = not_recommended_chars
74
+ )
75
+
76
+ # Segment value SHOULD be limited to A-Z, 0-9, and :-+ for new designs.
77
+ # this means that ":" in key or value is problematic
78
+ if ':' in key:
79
+ self.add_validation_message(
80
+ source=f"id segment key {key}",
81
+ type="Recommendation",
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.",
83
+ highlight_pattern = key
84
+ )
85
+ if ':' in value:
86
+ self.add_validation_message(
87
+ source=f"id segment value {value}",
88
+ type="Recommendation",
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.",
90
+ highlight_pattern = value
91
+ )
92
+
93
+ return self
94
+
95
+
96
+
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
100
+
101
+
102
+ @model_validator(mode='after')
103
+ def check_length(self) -> Self:
104
+ l = 0
105
+ for s in self.identifier:
106
+ s:IDSegment = s
107
+ if s.key:
108
+ l += len(s.key)
109
+ l += 1 # for ":"
110
+ l += len(s.value)
111
+ l += len(self.identifier) - 1 # account for "/" separating the segments
112
+
113
+ if l > 256:
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
+ )
120
+ return self
121
+
122
+
123
+ @model_validator(mode="after")
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
+ )
132
+
133
+ # recommendation that A-Z, 0-9, -, and . should be used
134
+ if not_recommended_chars := set(re.sub(r'[A-Z0-9\.-]', '', self.issuer)):
135
+ self.add_validation_message(
136
+ source="PAC-ID",
137
+ type="Recommendation",
138
+ highlight_pattern=self.issuer,
139
+ highlight_sub=not_recommended_chars,
140
+ msg=f"Characters {' '.join(not_recommended_chars)} should not be used. Issuer SHOULD contain only the characters A-Z, 0-9, -, and . "
141
+ )
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)
175
+
176
+
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)