labfreed 1.0.0a5__py3-none-any.whl → 1.0.0a7__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/__init__.py +1 -1
- labfreed/labfreed_extended/app/app_infrastructure.py +2 -2
- labfreed/labfreed_extended/app/pac_info/pac_info.py +5 -5
- labfreed/pac_attributes/api_data_models/response.py +54 -32
- labfreed/pac_attributes/client/attribute_cache.py +8 -21
- labfreed/pac_attributes/client/client.py +4 -2
- labfreed/pac_attributes/pythonic/excel_attribute_data_source.py +1 -2
- labfreed/pac_attributes/pythonic/py_attributes.py +82 -50
- labfreed/pac_attributes/server/attribute_data_sources.py +2 -9
- labfreed/pac_attributes/server/server.py +8 -1
- labfreed/pac_cat/category_base.py +1 -1
- labfreed/pac_cat/predefined_categories.py +0 -1
- {labfreed-1.0.0a5.dist-info → labfreed-1.0.0a7.dist-info}/METADATA +1 -1
- {labfreed-1.0.0a5.dist-info → labfreed-1.0.0a7.dist-info}/RECORD +16 -16
- {labfreed-1.0.0a5.dist-info → labfreed-1.0.0a7.dist-info}/WHEEL +0 -0
- {labfreed-1.0.0a5.dist-info → labfreed-1.0.0a7.dist-info}/licenses/LICENSE +0 -0
labfreed/__init__.py
CHANGED
|
@@ -32,7 +32,7 @@ class Labfreed_App_Infrastructure():
|
|
|
32
32
|
self._http_client= http_client
|
|
33
33
|
callback = http_attribute_request_default_callback_factory(http_client)
|
|
34
34
|
|
|
35
|
-
self._attribute_client = AttributeClient(http_post_callback=callback, cache_store=MemoryAttributeCache())
|
|
35
|
+
self._attribute_client = AttributeClient(http_post_callback=callback, cache_store=MemoryAttributeCache(), always_use_cached_value_for_minutes=1)
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def add_cit(self, cit:str):
|
|
@@ -71,7 +71,7 @@ class Labfreed_App_Infrastructure():
|
|
|
71
71
|
ags = {ag.key: pyAttributeGroup.from_attribute_group(ag) for ag in self._attribute_client.get_attributes(url, pac_id=pac.to_url(include_extensions=False), language_preferences=self._language_preferences)}
|
|
72
72
|
if ags:
|
|
73
73
|
attribute_groups.update(ags)
|
|
74
|
-
pac_info.
|
|
74
|
+
pac_info.attributes_groups = attribute_groups
|
|
75
75
|
|
|
76
76
|
return pac_info
|
|
77
77
|
|
|
@@ -20,7 +20,7 @@ class PacInfo(BaseModel):
|
|
|
20
20
|
"""A convenient collection of information about a PAC-ID"""
|
|
21
21
|
pac_id:PAC_ID
|
|
22
22
|
user_handovers: list[ServiceGroup] = Field(default_factory=list)
|
|
23
|
-
|
|
23
|
+
attributes_groups:dict[str, pyAttributeGroup] = Field(default_factory=dict)
|
|
24
24
|
|
|
25
25
|
@property
|
|
26
26
|
def pac_url(self):
|
|
@@ -44,7 +44,7 @@ class PacInfo(BaseModel):
|
|
|
44
44
|
|
|
45
45
|
@property
|
|
46
46
|
def image_url(self) -> str:
|
|
47
|
-
if meta := self.
|
|
47
|
+
if meta := self.attributes_groups.get(MetaAttributeKeys.GROUPKEY.value):
|
|
48
48
|
image_attr = meta.attributes.get(MetaAttributeKeys.IMAGE.value)
|
|
49
49
|
return image_attr.value
|
|
50
50
|
|
|
@@ -73,7 +73,7 @@ class PacInfo(BaseModel):
|
|
|
73
73
|
@cached_property
|
|
74
74
|
def _all_attributes(self) -> dict[str, pyAttribute]:
|
|
75
75
|
out = {}
|
|
76
|
-
for ag in self.
|
|
76
|
+
for ag in self.attributes_groups.values():
|
|
77
77
|
out.update(ag.attributes)
|
|
78
78
|
return out
|
|
79
79
|
|
|
@@ -105,12 +105,12 @@ class PacInfo(BaseModel):
|
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
printout.title1("Attributes")
|
|
108
|
-
for ag in self.
|
|
108
|
+
for ag in self.attributes_groups.values():
|
|
109
109
|
printout.title2(f'{ag.label} (from {ag.origin})')
|
|
110
110
|
for v in ag.attributes.values():
|
|
111
111
|
v:pyAttribute
|
|
112
112
|
#print(f'{k}: ({v.label}) :: {v.value} ')
|
|
113
|
-
printout.key_value(v.label, v.
|
|
113
|
+
printout.key_value(v.label, ', '.join([str(e) for e in v.value_list]))
|
|
114
114
|
|
|
115
115
|
out = printout.getvalue()
|
|
116
116
|
|
|
@@ -3,6 +3,8 @@ from abc import ABC
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
import re
|
|
5
5
|
from typing import Annotated, Any, Literal, Union, get_args
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
6
8
|
from labfreed.utilities.ensure_utc_time import ensure_utc
|
|
7
9
|
from labfreed.labfreed_infrastructure import LabFREED_BaseModel, ValidationMsgLevel, _quote_texts
|
|
8
10
|
from pydantic import Field, field_validator, model_validator
|
|
@@ -12,22 +14,13 @@ class AttributeBase(LabFREED_BaseModel, ABC):
|
|
|
12
14
|
key: str
|
|
13
15
|
value: Any
|
|
14
16
|
label: str = ""
|
|
15
|
-
|
|
16
|
-
observed_at: datetime | None = None
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
def __init__(self, **data):
|
|
19
19
|
# Automatically inject the Literal value for `type`
|
|
20
20
|
discriminator_value = self._get_discriminator_value()
|
|
21
21
|
data["type"] = discriminator_value
|
|
22
22
|
super().__init__(**data)
|
|
23
23
|
|
|
24
|
-
@field_validator('observed_at', mode='before')
|
|
25
|
-
def set_utc_observed_at_if_naive(cls, value):
|
|
26
|
-
if isinstance(value, datetime):
|
|
27
|
-
return ensure_utc(value)
|
|
28
|
-
else:
|
|
29
|
-
return value
|
|
30
|
-
|
|
31
24
|
@classmethod
|
|
32
25
|
def _get_discriminator_value(cls) -> str:
|
|
33
26
|
"""Extract the Literal value from the 'type' annotation."""
|
|
@@ -40,16 +33,10 @@ class AttributeBase(LabFREED_BaseModel, ABC):
|
|
|
40
33
|
f"{cls.__name__} must define `type: Literal[<value>]` annotation"
|
|
41
34
|
) from e
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class ReferenceAttribute(AttributeBase):
|
|
47
|
-
type: Literal["reference"]
|
|
48
|
-
value: str
|
|
49
36
|
|
|
50
37
|
class DateTimeAttribute(AttributeBase):
|
|
51
38
|
type: Literal["datetime"]
|
|
52
|
-
value: datetime
|
|
39
|
+
value: datetime | list[datetime]
|
|
53
40
|
|
|
54
41
|
@field_validator('value', mode='before')
|
|
55
42
|
def set_utc__if_naive(cls, value):
|
|
@@ -60,15 +47,59 @@ class DateTimeAttribute(AttributeBase):
|
|
|
60
47
|
|
|
61
48
|
class BoolAttribute(AttributeBase):
|
|
62
49
|
type: Literal["bool"]
|
|
63
|
-
value: bool
|
|
50
|
+
value: bool | list[bool]
|
|
64
51
|
|
|
65
52
|
class TextAttribute(AttributeBase):
|
|
66
53
|
type: Literal["text"]
|
|
67
|
-
value: str
|
|
54
|
+
value: str | list[str]
|
|
55
|
+
|
|
56
|
+
@model_validator(mode='after')
|
|
57
|
+
def _validate_value(self):
|
|
58
|
+
l = [self.value] if isinstance(self.value, str) else self.value
|
|
59
|
+
for v in l:
|
|
60
|
+
if len(v) > 5000:
|
|
61
|
+
self._add_validation_message(
|
|
62
|
+
source="Text Attribute",
|
|
63
|
+
level=ValidationMsgLevel.WARNING, # noqa: F821
|
|
64
|
+
msg=f"Text attribute {v} exceeds 5000 characters. It is recommended to stay below",
|
|
65
|
+
highlight_pattern = f'{v}'
|
|
66
|
+
)
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ReferenceAttribute(AttributeBase):
|
|
71
|
+
type: Literal["reference"]
|
|
72
|
+
value: str | list[str]
|
|
68
73
|
|
|
74
|
+
|
|
75
|
+
class ResourceAttribute(AttributeBase):
|
|
76
|
+
type: Literal["resource"]
|
|
77
|
+
value: str | list[str]
|
|
69
78
|
|
|
79
|
+
@model_validator(mode='after')
|
|
80
|
+
def _validate_value(self):
|
|
81
|
+
value_list = self.value if isinstance(self.value, list) else [self.value]
|
|
82
|
+
for v in value_list:
|
|
83
|
+
r = urlparse(v)
|
|
84
|
+
if not all([r.scheme, r.netloc]):
|
|
85
|
+
self._add_validation_message(
|
|
86
|
+
source="Resource Attribute",
|
|
87
|
+
level=ValidationMsgLevel.ERROR, # noqa: F821
|
|
88
|
+
msg=f"Must be a valid url",
|
|
89
|
+
highlight_pattern = f'{v}'
|
|
90
|
+
)
|
|
91
|
+
pattern = re.compile(r"\.\w{1,3}$", re.IGNORECASE)
|
|
92
|
+
if not bool(pattern.search(v)):
|
|
93
|
+
self._add_validation_message(
|
|
94
|
+
source="Resource Attribute",
|
|
95
|
+
level=ValidationMsgLevel.WARNING, # noqa: F821
|
|
96
|
+
msg=f"It is RECOMMENDED resource links end with a file extension",
|
|
97
|
+
highlight_pattern = f'{v}'
|
|
98
|
+
)
|
|
99
|
+
return self
|
|
70
100
|
|
|
71
101
|
|
|
102
|
+
|
|
72
103
|
class NumericValue(LabFREED_BaseModel):
|
|
73
104
|
numerical_value: str
|
|
74
105
|
unit: str
|
|
@@ -120,11 +151,11 @@ class NumericValue(LabFREED_BaseModel):
|
|
|
120
151
|
|
|
121
152
|
class NumericAttribute(AttributeBase):
|
|
122
153
|
type: Literal["numeric"]
|
|
123
|
-
value: NumericValue
|
|
154
|
+
value: NumericValue | list[NumericValue]
|
|
124
155
|
|
|
125
156
|
class ObjectAttribute(AttributeBase):
|
|
126
157
|
type: Literal["object"]
|
|
127
|
-
value: dict[str, Any]
|
|
158
|
+
value: dict[str, Any] |list[dict[str, Any]]
|
|
128
159
|
|
|
129
160
|
|
|
130
161
|
|
|
@@ -136,27 +167,18 @@ Attribute = Annotated[
|
|
|
136
167
|
BoolAttribute,
|
|
137
168
|
TextAttribute,
|
|
138
169
|
NumericAttribute,
|
|
170
|
+
ResourceAttribute,
|
|
139
171
|
ObjectAttribute
|
|
140
172
|
],
|
|
141
173
|
Field(discriminator="type")
|
|
142
174
|
]
|
|
143
175
|
|
|
144
|
-
VALID_FOREVER = "forever"
|
|
145
176
|
|
|
146
177
|
class AttributeGroup(LabFREED_BaseModel):
|
|
147
178
|
key: str
|
|
148
179
|
label: str = ""
|
|
149
180
|
attributes: list[Attribute]
|
|
150
|
-
|
|
151
|
-
state_of: datetime
|
|
152
|
-
valid_until: datetime | Literal["forever"] | None = None
|
|
153
|
-
|
|
154
|
-
@field_validator('valid_until', mode='before')
|
|
155
|
-
def set_utc_valid_until_if_naive(cls, value):
|
|
156
|
-
if isinstance(value, datetime):
|
|
157
|
-
return ensure_utc(value)
|
|
158
|
-
else:
|
|
159
|
-
return value
|
|
181
|
+
|
|
160
182
|
|
|
161
183
|
|
|
162
184
|
class AttributesOfPACID(LabFREED_BaseModel):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
4
|
from typing import Literal, Protocol
|
|
5
5
|
|
|
6
6
|
|
|
@@ -12,28 +12,15 @@ from labfreed.pac_id.pac_id import PAC_ID
|
|
|
12
12
|
class CacheableAttributeGroup(AttributeGroup):
|
|
13
13
|
origin:str
|
|
14
14
|
language:str
|
|
15
|
-
|
|
15
|
+
value_from: datetime | None = None
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# vals = [a.valid_until for a in self.attributes]
|
|
20
|
-
# if all(e == 'forever' for e in vals):
|
|
21
|
-
# self.valid_until = 'forever'
|
|
22
|
-
# elif any(e is None for e in vals):
|
|
23
|
-
# self.valid_until = None
|
|
24
|
-
# else:
|
|
25
|
-
# self.valid_until = min(v for v in vals if isinstance(v, datetime))
|
|
26
|
-
# return self
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@property
|
|
30
|
-
def still_valid(self):
|
|
31
|
-
if self.valid_until is None:
|
|
17
|
+
def still_valid(self, accept_cache_for_minutes):
|
|
18
|
+
if self.value_from is None:
|
|
32
19
|
return False
|
|
33
|
-
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
else:
|
|
21
|
+
return ( datetime.now() - timedelta(min=accept_cache_for_minutes)) > self.value_from
|
|
22
|
+
|
|
23
|
+
|
|
37
24
|
|
|
38
25
|
|
|
39
26
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
4
5
|
from typing import Protocol, runtime_checkable
|
|
5
6
|
|
|
6
7
|
import requests
|
|
@@ -85,6 +86,7 @@ class AttributeClient():
|
|
|
85
86
|
|
|
86
87
|
http_post_callback:AttributeRequestCallback
|
|
87
88
|
cache_store:AttributeCache
|
|
89
|
+
always_use_cached_value_for_minutes:int
|
|
88
90
|
|
|
89
91
|
def get_attributes(self,
|
|
90
92
|
server_url:str,
|
|
@@ -120,7 +122,7 @@ class AttributeClient():
|
|
|
120
122
|
else:
|
|
121
123
|
attribute_groups = self.cache_store.get_all(server_url, pac_id)
|
|
122
124
|
|
|
123
|
-
if attribute_groups and all([ag.still_valid for ag in attribute_groups]):
|
|
125
|
+
if attribute_groups and all([ag.still_valid(accept_cache_for_minutes=self.always_use_cached_value_for_minutes) for ag in attribute_groups]):
|
|
124
126
|
return attribute_groups
|
|
125
127
|
|
|
126
128
|
# no valid data found in cache > request to server
|
|
@@ -154,7 +156,7 @@ class AttributeClient():
|
|
|
154
156
|
origin=server_url,
|
|
155
157
|
language=r.language,
|
|
156
158
|
label=ag.label,
|
|
157
|
-
|
|
159
|
+
value_from=datetime.now(tz=datetime.UTC))
|
|
158
160
|
for ag in ag_for_pac.attribute_groups
|
|
159
161
|
]
|
|
160
162
|
self.cache_store.update(server_url, pac_from_response, ags)
|
|
@@ -113,8 +113,7 @@ class _BaseExcelAttributeDataSource(AttributeGroupDataSource):
|
|
|
113
113
|
attributes = [pyAttribute(key=k, value=v) for k, v in d.items()]
|
|
114
114
|
return AttributeGroup(
|
|
115
115
|
key=self._attribute_group_key,
|
|
116
|
-
attributes=pyAttributes(attributes).to_payload_attributes()
|
|
117
|
-
state_of=last_changed,
|
|
116
|
+
attributes=pyAttributes(attributes).to_payload_attributes()
|
|
118
117
|
)
|
|
119
118
|
|
|
120
119
|
|
|
@@ -3,34 +3,58 @@ from datetime import date, datetime, time
|
|
|
3
3
|
import json
|
|
4
4
|
from typing import Literal
|
|
5
5
|
import warnings
|
|
6
|
-
from pydantic import RootModel
|
|
6
|
+
from pydantic import RootModel, field_validator
|
|
7
7
|
|
|
8
8
|
from labfreed.labfreed_infrastructure import LabFREED_BaseModel
|
|
9
|
-
from labfreed.pac_attributes.api_data_models.response import AttributeBase, AttributeGroup, BoolAttribute, DateTimeAttribute, NumericAttribute, NumericValue, ObjectAttribute, ReferenceAttribute, TextAttribute
|
|
9
|
+
from labfreed.pac_attributes.api_data_models.response import AttributeBase, AttributeGroup, BoolAttribute, DateTimeAttribute, NumericAttribute, NumericValue, ObjectAttribute, ReferenceAttribute, ResourceAttribute, TextAttribute
|
|
10
10
|
from labfreed.pac_attributes.client.attribute_cache import CacheableAttributeGroup
|
|
11
11
|
from labfreed.pac_id.pac_id import PAC_ID
|
|
12
12
|
from labfreed.trex.pythonic.quantity import Quantity
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class pyReference(RootModel[str]):
|
|
16
|
-
pass
|
|
17
16
|
|
|
18
17
|
def __str__(self):
|
|
19
18
|
return str(self.root)
|
|
19
|
+
|
|
20
|
+
class pyResource(RootModel[str]):
|
|
21
|
+
|
|
22
|
+
def __str__(self):
|
|
23
|
+
return str(self.root)
|
|
24
|
+
|
|
20
25
|
|
|
26
|
+
# the allowed scalar types
|
|
27
|
+
AllowedValue = str | bool | datetime | pyReference | pyResource | Quantity | int | float | dict | object
|
|
28
|
+
# homogeneous list of those
|
|
29
|
+
AllowedList = list[AllowedValue]
|
|
21
30
|
|
|
22
31
|
class pyAttribute(LabFREED_BaseModel):
|
|
23
32
|
key:str
|
|
24
33
|
label:str = ""
|
|
25
|
-
value:
|
|
26
|
-
valid_until: datetime | Literal["forever"] | None = None
|
|
27
|
-
observed_at: datetime | None = None
|
|
34
|
+
value: AllowedValue | AllowedList
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
@property
|
|
37
|
+
def value_list(self):
|
|
38
|
+
'''helper function to more conveniently iterate over value elements, even if it's scalar'''
|
|
39
|
+
return self.value if isinstance(self.value, list) else [self.value]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@field_validator('value', mode='before')
|
|
43
|
+
def handle_one_element_list(v):
|
|
44
|
+
if isinstance(v, list) and len(v)==1:
|
|
45
|
+
return v[0]
|
|
46
|
+
else:
|
|
47
|
+
return v
|
|
30
48
|
|
|
31
49
|
class pyAttributes(RootModel[list[pyAttribute]]):
|
|
32
50
|
def to_payload_attributes(self) -> list[AttributeBase]:
|
|
33
|
-
|
|
51
|
+
out = []
|
|
52
|
+
for e in self.root:
|
|
53
|
+
apt = self._attribute_to_attribute_payload_type(e)
|
|
54
|
+
if isinstance(apt.value, list) and len(apt.value) ==1:
|
|
55
|
+
apt.value = apt.value[0]
|
|
56
|
+
out.append(apt)
|
|
57
|
+
return out
|
|
34
58
|
|
|
35
59
|
|
|
36
60
|
@staticmethod
|
|
@@ -38,85 +62,93 @@ class pyAttributes(RootModel[list[pyAttribute]]):
|
|
|
38
62
|
common_args = {
|
|
39
63
|
"key": attribute.key,
|
|
40
64
|
"label": attribute.label,
|
|
41
|
-
"observed_at": attribute.observed_at
|
|
42
65
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if isinstance(
|
|
46
|
-
return
|
|
66
|
+
value_list = attribute.value_list
|
|
67
|
+
first_value = value_list[0]
|
|
68
|
+
if isinstance(first_value, bool):
|
|
69
|
+
return BoolAttribute(value=value_list, **common_args)
|
|
47
70
|
|
|
48
|
-
elif isinstance(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
elif isinstance(first_value, datetime | date | time):
|
|
72
|
+
for v in value_list:
|
|
73
|
+
if not v.tzinfo:
|
|
74
|
+
warnings.warn(f'No timezone given for {v}. Assuming it is in UTC.')
|
|
75
|
+
return DateTimeAttribute(value=value_list, **common_args)
|
|
52
76
|
# return DateTimeAttribute(value =_date_value_from_python_type(value).value, **common_args)
|
|
53
77
|
|
|
54
|
-
|
|
55
|
-
elif isinstance(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
|
|
79
|
+
elif isinstance(first_value, Quantity|int|float):
|
|
80
|
+
values = []
|
|
81
|
+
for v in value_list:
|
|
82
|
+
if not isinstance(v, Quantity):
|
|
83
|
+
v = Quantity(value=v, unit='dimensionless')
|
|
84
|
+
values.append(NumericValue(numerical_value=v.value_as_str(),
|
|
85
|
+
unit = v.unit))
|
|
86
|
+
num_attribute = NumericAttribute(value = values, **common_args)
|
|
61
87
|
num_attribute.print_validation_messages()
|
|
62
88
|
return num_attribute
|
|
63
89
|
|
|
64
|
-
elif isinstance(
|
|
90
|
+
elif isinstance(first_value, str):
|
|
65
91
|
# capture quantities in the form of "100.0e5 g/L"
|
|
66
|
-
if
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
92
|
+
if Quantity.from_str_with_unit(first_value):
|
|
93
|
+
values = []
|
|
94
|
+
for v in value_list:
|
|
95
|
+
q = Quantity.from_str_with_unit(v)
|
|
96
|
+
values.append( NumericValue(numerical_value=q.value_as_str(), unit = q.unit) )
|
|
97
|
+
return NumericAttribute(value = values,
|
|
98
|
+
**common_args)
|
|
99
|
+
|
|
70
100
|
else:
|
|
71
|
-
return TextAttribute(value =
|
|
101
|
+
return TextAttribute(value = value_list, **common_args)
|
|
72
102
|
|
|
73
|
-
elif isinstance(
|
|
74
|
-
return ReferenceAttribute(value =
|
|
75
|
-
|
|
76
|
-
elif isinstance(value, PAC_ID):
|
|
77
|
-
return ReferenceAttribute(value = value.to_url(include_extensions=False), **common_args)
|
|
78
|
-
|
|
103
|
+
elif isinstance(first_value, pyReference):
|
|
104
|
+
return ReferenceAttribute(value = [v.root for v in value_list], **common_args)
|
|
79
105
|
|
|
106
|
+
elif isinstance(first_value, pyResource):
|
|
107
|
+
return ResourceAttribute(value = [v.root for v in value_list], **common_args)
|
|
108
|
+
|
|
109
|
+
elif isinstance(first_value, PAC_ID):
|
|
110
|
+
return ReferenceAttribute(value = [v.to_url(include_extensions=False) for v in value_list], **common_args)
|
|
80
111
|
|
|
81
112
|
else: #this covers the last resort case of arbitrary objects. Must be json serializable.
|
|
82
113
|
try :
|
|
83
|
-
|
|
84
|
-
return ObjectAttribute(value=
|
|
114
|
+
values = [json.loads(json.dumps(v)) for v in value_list]
|
|
115
|
+
return ObjectAttribute(value=values, **common_args)
|
|
85
116
|
except TypeError as e: # noqa: F841
|
|
86
|
-
raise ValueError(f'Invalid Type: {type(
|
|
117
|
+
raise ValueError(f'Invalid Type: {type(first_value)} cannot be converted to attribute. You may want to use ObjectAttribute, but would have to implement the conversion from your python type yourself.')
|
|
118
|
+
|
|
87
119
|
|
|
88
120
|
|
|
89
121
|
@staticmethod
|
|
90
122
|
def from_payload_attributes(attributes:list[AttributeBase]) -> 'pyAttributes':
|
|
91
123
|
out = list()
|
|
92
124
|
for a in attributes:
|
|
125
|
+
value_list = a.value if isinstance(a.value, list) else [a.value]
|
|
93
126
|
match a:
|
|
94
|
-
|
|
95
127
|
case ReferenceAttribute():
|
|
96
|
-
|
|
128
|
+
values = [pyReference(v) for v in value_list]
|
|
129
|
+
|
|
130
|
+
case ResourceAttribute():
|
|
131
|
+
values = [pyResource(v) for v in value_list]
|
|
97
132
|
|
|
98
133
|
case NumericAttribute():
|
|
99
|
-
|
|
134
|
+
values = [ Quantity.from_str_value(value=v.numerical_value, unit=v.unit) for v in value_list]
|
|
100
135
|
|
|
101
136
|
case BoolAttribute():
|
|
102
|
-
|
|
137
|
+
values = value_list
|
|
103
138
|
|
|
104
139
|
case TextAttribute():
|
|
105
|
-
|
|
140
|
+
values = value_list
|
|
106
141
|
|
|
107
142
|
case DateTimeAttribute():
|
|
108
|
-
|
|
143
|
+
values = value_list
|
|
109
144
|
|
|
110
145
|
case ObjectAttribute():
|
|
111
|
-
|
|
146
|
+
values = value_list
|
|
112
147
|
|
|
113
148
|
|
|
114
149
|
attr = pyAttribute(key=a.key,
|
|
115
150
|
label=a.label,
|
|
116
|
-
value=
|
|
117
|
-
observed_at=a.observed_at
|
|
118
|
-
# valid_until=datetime(**_parse_date_time_str(a.valid_until)),
|
|
119
|
-
# observed_at=datetime(**_parse_date_time_str(a.value))
|
|
151
|
+
value=values
|
|
120
152
|
)
|
|
121
153
|
out.append(attr )
|
|
122
154
|
return out
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod, abstractproperty
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
|
-
from labfreed.pac_attributes.api_data_models.response import
|
|
3
|
+
from labfreed.pac_attributes.api_data_models.response import AttributeBase, AttributeGroup
|
|
4
4
|
from labfreed.pac_cat.pac_cat import PAC_CAT
|
|
5
5
|
from labfreed.pac_id.pac_id import PAC_ID
|
|
6
6
|
|
|
@@ -36,7 +36,6 @@ class Dict_DataSource(AttributeGroupDataSource):
|
|
|
36
36
|
raise ValueError('Invalid data')
|
|
37
37
|
|
|
38
38
|
self._data = data
|
|
39
|
-
self._state_of = datetime.now(tz=timezone.utc)
|
|
40
39
|
self.uses_pac_cat_short_form = uses_pac_cat_short_form
|
|
41
40
|
|
|
42
41
|
super().__init__(*args, **kwargs)
|
|
@@ -58,14 +57,8 @@ class Dict_DataSource(AttributeGroupDataSource):
|
|
|
58
57
|
if not attributes:
|
|
59
58
|
return None
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
valid_until = VALID_FOREVER if self._is_static else None
|
|
63
|
-
|
|
64
|
-
|
|
65
60
|
return AttributeGroup(key=self._attribute_group_key,
|
|
66
|
-
attributes=attributes
|
|
67
|
-
state_of=self._state_of,
|
|
68
|
-
valid_until=valid_until)
|
|
61
|
+
attributes=attributes)
|
|
69
62
|
|
|
70
63
|
|
|
71
64
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import string
|
|
1
3
|
import traceback
|
|
2
4
|
import warnings
|
|
3
5
|
|
|
@@ -132,7 +134,12 @@ class AttributeServerRequestHandler():
|
|
|
132
134
|
if dn := self._get_display_name_for_key(ag.key, language):
|
|
133
135
|
ag.label = dn
|
|
134
136
|
else:
|
|
135
|
-
ag.label =
|
|
137
|
+
ag.label = string.capwords(
|
|
138
|
+
re.sub(r'([a-z])([A-Z])', r'\1 \2',
|
|
139
|
+
re.sub('-_', '',
|
|
140
|
+
ag.key.split('/')[-1])
|
|
141
|
+
)
|
|
142
|
+
)
|
|
136
143
|
rich.print(f"[yellow]WARNING:[/yellow] No translation for '{ag.key}' in '{language}'. Falling back to '{ag.label}'")
|
|
137
144
|
for a in ag.attributes:
|
|
138
145
|
if dn := self._get_display_name_for_key(a.key, language):
|
|
@@ -202,7 +202,6 @@ class Processor_Abstract(PredefinedCategory, ABC):
|
|
|
202
202
|
key: str
|
|
203
203
|
processor_instance:str|None = Field( alias='21')
|
|
204
204
|
processor_code:str|None = Field( alias='240')
|
|
205
|
-
|
|
206
205
|
additional_segments: list[IDSegment] = Field(default_factory=list, exclude=True)
|
|
207
206
|
''' Category segments, which are not defined in the specification'''
|
|
208
207
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
labfreed/__init__.py,sha256=
|
|
1
|
+
labfreed/__init__.py,sha256=2M8GLQGnuUWBRUGzOt9MJDSVqxWBbFRcTDuQhOuTu6M,338
|
|
2
2
|
labfreed/labfreed_infrastructure.py,sha256=YZmU-kgopyB1tvpTR_k_uIt1Q2ezexMrWvu-HaP65IE,10104
|
|
3
|
-
labfreed/labfreed_extended/app/app_infrastructure.py,sha256=
|
|
3
|
+
labfreed/labfreed_extended/app/app_infrastructure.py,sha256=0dO1dXu1DZykRH3gnta34vz5MzEEXUfK1D-tsjNqCaE,4036
|
|
4
4
|
labfreed/labfreed_extended/app/formatted_print.py,sha256=DcwWP0ix1e_wYNIdceIp6cETkJdG2DqpU8Gs3aZAL40,1930
|
|
5
|
-
labfreed/labfreed_extended/app/pac_info/pac_info.py,sha256=
|
|
5
|
+
labfreed/labfreed_extended/app/pac_info/pac_info.py,sha256=rb5mraJNsPsm7ujhk6WJmm9tXByhv0E8QTOCcgSaO_U,6159
|
|
6
6
|
labfreed/labfreed_extended/app/pac_info/html_renderer/external-link.svg,sha256=H5z9s4VvHq09UnHdqfrYNsx-Whljc0gE4qKJ6-3kfgQ,1158
|
|
7
7
|
labfreed/labfreed_extended/app/pac_info/html_renderer/macros.jinja.html,sha256=1S-dxibPwJshtdelsmyA4LpgOm84L6RTXPNO93gmPfg,5964
|
|
8
8
|
labfreed/labfreed_extended/app/pac_info/html_renderer/pac-info-style.css,sha256=C5pyD956fd6pJgUBjGxvxgL0Wbgq0v7ZLY4Vr-sJZ7A,4169
|
|
@@ -11,23 +11,23 @@ labfreed/labfreed_extended/app/pac_info/html_renderer/pac_info_card.jinja.html,s
|
|
|
11
11
|
labfreed/pac_attributes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
labfreed/pac_attributes/well_knonw_attribute_keys.py,sha256=axE81MeJ3G_Wy1PbmNAXH6SfPtl96NXvQJMyrvK10t4,324
|
|
13
13
|
labfreed/pac_attributes/api_data_models/request.py,sha256=-CI3rU_Bzw2DZGSS06Jl4zajrxMkfPGhKHWmIfnmWlk,1868
|
|
14
|
-
labfreed/pac_attributes/api_data_models/response.py,sha256=
|
|
14
|
+
labfreed/pac_attributes/api_data_models/response.py,sha256=eGh474ILEcBC1ijhs1ZZfdhNWRxiPeccGS8aw0zzt0U,6934
|
|
15
15
|
labfreed/pac_attributes/api_data_models/server_capabilities_response.py,sha256=ypDm4f8xZZl036fp8PuIe6lJHNW5Zg1fItgUlnV75V0,178
|
|
16
16
|
labfreed/pac_attributes/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
labfreed/pac_attributes/client/attribute_cache.py,sha256=
|
|
18
|
-
labfreed/pac_attributes/client/client.py,sha256=
|
|
17
|
+
labfreed/pac_attributes/client/attribute_cache.py,sha256=DbVk6MTOmzNhaf1IDVhzmnlBXiSMPX9Gur43tlBOSdM,2207
|
|
18
|
+
labfreed/pac_attributes/client/client.py,sha256=psOvlYsnK4zPp2jFHHgTPpwjRo8-uJOGLEESXyi-e_I,7510
|
|
19
19
|
labfreed/pac_attributes/pythonic/attribute_server_factory.py,sha256=_wasafjBlwvzOaM6-uPgqPethsDQHEpaXoiRW7w9aV0,5759
|
|
20
|
-
labfreed/pac_attributes/pythonic/excel_attribute_data_source.py,sha256=
|
|
21
|
-
labfreed/pac_attributes/pythonic/py_attributes.py,sha256=
|
|
20
|
+
labfreed/pac_attributes/pythonic/excel_attribute_data_source.py,sha256=oP4OHj0DTlH4dD7OlL1qxtX4y9KcuDTCd9Bi_FruP6A,7276
|
|
21
|
+
labfreed/pac_attributes/pythonic/py_attributes.py,sha256=FXSp9_P0o-GuZSDvXtD2fU4g82lglMu9f_-8KPMkEP0,6821
|
|
22
22
|
labfreed/pac_attributes/pythonic/py_dict_data_source.py,sha256=nAz6GA7Xx_0IORPPpt_Wl3sFJa1Q5Fnq5vdf1uQiJF8,531
|
|
23
23
|
labfreed/pac_attributes/server/__init__.py,sha256=JvQ2kpQx62OUwP18bGhOWYU9an_nQW59Y8Lh7HyfVxY,301
|
|
24
|
-
labfreed/pac_attributes/server/attribute_data_sources.py,sha256=
|
|
25
|
-
labfreed/pac_attributes/server/server.py,sha256=
|
|
24
|
+
labfreed/pac_attributes/server/attribute_data_sources.py,sha256=CAPoEc6OQycfc9jU66aL7K4K90ZDrEGG8fXfbVfDKao,2175
|
|
25
|
+
labfreed/pac_attributes/server/server.py,sha256=HKfy5XbDM4AOpfeGrkxqGcfMtaMCoWP_qkJ-etBRROg,9077
|
|
26
26
|
labfreed/pac_attributes/server/translation_data_sources.py,sha256=axALOqfP840sOSdVCRYtrens97mm-hpfONMUyuVlCrY,2145
|
|
27
27
|
labfreed/pac_cat/__init__.py,sha256=KNPtQzBD1XVohvG_ucOs7RJj-oi6biUTGB1k-T2o6pk,568
|
|
28
|
-
labfreed/pac_cat/category_base.py,sha256=
|
|
28
|
+
labfreed/pac_cat/category_base.py,sha256=D7BzsdF0-JIgag5L2XZJRF4T2LOH5RLh1MMszflkmV8,2526
|
|
29
29
|
labfreed/pac_cat/pac_cat.py,sha256=wcb_fhvgjS2xmqTsxS8_Oibvr1nsQt5zr8aUajLfK1E,5578
|
|
30
|
-
labfreed/pac_cat/predefined_categories.py,sha256=
|
|
30
|
+
labfreed/pac_cat/predefined_categories.py,sha256=U1Phgy-q4VKbMv3ruoa8uKKTlxkpZOKnWPqtHLzYUqU,11105
|
|
31
31
|
labfreed/pac_id/__init__.py,sha256=NGMbzkwQ4txKeT5pxdIZordwHO8J3_q84jzPanjKoHg,675
|
|
32
32
|
labfreed/pac_id/extension.py,sha256=NgLexs1LbRMMm4ETrn5m4EY2iWoMDgOTb0UV556jatQ,2227
|
|
33
33
|
labfreed/pac_id/id_segment.py,sha256=r5JU1SJuRXhZJJxy5T3xjrb598wIDTLpivSJhIUAzjQ,4526
|
|
@@ -64,7 +64,7 @@ labfreed/well_known_keys/labfreed/well_known_keys.py,sha256=p-hXwEEIs7p2SKn9DQeL
|
|
|
64
64
|
labfreed/well_known_keys/unece/UneceUnits.json,sha256=kwfQSp_nTuWbADfBBgqTWrvPl6XtM5SedEVLbMJrM7M,898953
|
|
65
65
|
labfreed/well_known_keys/unece/__init__.py,sha256=MSP9lmjg9_D9iqG9Yq2_ajYfQSNS9wIT7FXA1c--59M,122
|
|
66
66
|
labfreed/well_known_keys/unece/unece_units.py,sha256=J20d64H69qKDE3XlGdJoXIIh0G-d0jKoiIDsg9an5pk,1655
|
|
67
|
-
labfreed-1.0.
|
|
68
|
-
labfreed-1.0.
|
|
69
|
-
labfreed-1.0.
|
|
70
|
-
labfreed-1.0.
|
|
67
|
+
labfreed-1.0.0a7.dist-info/licenses/LICENSE,sha256=gHFOv9FRKHxO8cInP3YXyPoJnuNeqrvcHjaE_wPSsQ8,1100
|
|
68
|
+
labfreed-1.0.0a7.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
69
|
+
labfreed-1.0.0a7.dist-info/METADATA,sha256=D-1dE3dyCXPqcuD860PyNHInj0MAu0b3eezsHvy2pV0,19740
|
|
70
|
+
labfreed-1.0.0a7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|