labfreed 1.0.0a25__tar.gz → 1.0.0b27__tar.gz
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-1.0.0a25 → labfreed-1.0.0b27}/CHANGELOG.md +9 -3
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/PKG-INFO +1 -1
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/__init__.py +1 -1
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/app/app_infrastructure.py +2 -5
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/app/pac_info/pac_info.py +6 -8
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/lib/attribute.py +9 -6
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/logo.svg +44 -44
- labfreed-1.0.0b27/labfreed/pac_attributes/api_data_models/request.py +108 -0
- labfreed-1.0.0b27/labfreed/pac_attributes/api_data_models/response.py +251 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/client/client.py +30 -36
- labfreed-1.0.0b27/labfreed/pac_attributes/client/client_attribute_group.py +18 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/pythonic/attribute_server_factory.py +39 -13
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/pythonic/excel_attribute_data_source.py +1 -1
- labfreed-1.0.0b27/labfreed/pac_attributes/pythonic/py_attributes.py +186 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/server/attribute_data_sources.py +9 -8
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/server/server.py +33 -37
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id_resolver/resolver_config.py +2 -1
- labfreed-1.0.0b27/tst_attr.py +202 -0
- labfreed-1.0.0a25/labfreed/labfreed_extended/app/pac_info/html_renderer/macros.jinja.html +0 -188
- labfreed-1.0.0a25/labfreed/labfreed_extended/app/pac_info/html_renderer/pac-info-style.css +0 -176
- labfreed-1.0.0a25/labfreed/labfreed_extended/app/pac_info/html_renderer/pac_info.jinja.html +0 -46
- labfreed-1.0.0a25/labfreed/labfreed_extended/app/pac_info/html_renderer/pac_info_card.jinja.html +0 -7
- labfreed-1.0.0a25/labfreed/labfreed_extended/pac_issuer_lib/static/external-link.svg +0 -7
- labfreed-1.0.0a25/labfreed/pac_attributes/api_data_models/request.py +0 -56
- labfreed-1.0.0a25/labfreed/pac_attributes/api_data_models/response.py +0 -280
- labfreed-1.0.0a25/labfreed/pac_attributes/client/__init__.py +0 -0
- labfreed-1.0.0a25/labfreed/pac_attributes/client/attribute_cache.py +0 -65
- labfreed-1.0.0a25/labfreed/pac_attributes/pythonic/py_attributes.py +0 -208
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/.github/workflows/pypi-publish.yml +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/.github/workflows/run-tests.yml +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/LICENSE +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/README.md +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/app/formatted_print.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/app_factory.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/lib/bp_landing_page.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/lib/utils.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cancel.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cat-DC.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cat-DM.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cat-DP.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cat-DR.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cat-DS.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cat-MC.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cat-MD.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cat-MS.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/cat-MX.svg +0 -0
- {labfreed-1.0.0a25/labfreed/labfreed_extended/app/pac_info/html_renderer → labfreed-1.0.0b27/labfreed/labfreed_extended/pac_issuer_lib/static}/external-link.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/menu.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/search.svg +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/styles.css +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/styles_brand.css +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/styles_pac_info.css +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/flash_error_messages.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/footer.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/main.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/navbar.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/pac_info/base-components.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/pac_info/card-components.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/pac_info/card.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/pac_info/pac_info.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/pac_issuer_error.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/templates/pac_issuer_landing_page.jinja.html +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_infrastructure.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/api_data_models/server_capabilities_response.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/pythonic/py_dict_data_source.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/server/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/server/translation_data_sources.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_attributes/well_knonw_attribute_keys.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_cat/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_cat/category_base.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_cat/pac_cat.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_cat/predefined_categories.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id/extension.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id/id_segment.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id/pac_id.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id/url_parser.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id/url_serializer.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id_resolver/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id_resolver/cit_v1.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id_resolver/resolver.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id_resolver/resolver_config_common.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/pac_id_resolver/services.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/qr/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/qr/generate_qr.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/trex/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/trex/pythonic/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/trex/pythonic/data_table.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/trex/pythonic/pyTREX.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/trex/pythonic/quantity.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/trex/table_segment.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/trex/trex.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/trex/trex_base_models.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/trex/value_segments.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/utilities/base36.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/utilities/ensure_utc_time.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/utilities/translations.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_extensions/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_extensions/default_extension_interpreters.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_extensions/display_name_extension.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_extensions/text_base36_extension.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_extensions/trex_extension.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_keys/gs1/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_keys/gs1/gs1_ai_enum_sorted.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_keys/labfreed/well_known_keys.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_keys/unece/UneceUnits.json +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_keys/unece/__init__.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/well_known_keys/unece/unece_units.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed_experimental/pac_disco/ble_central.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed_experimental/pac_disco/ble_peripheral.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed_experimental/pac_disco/ble_uuid.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed_experimental/pac_disco/pac_disco_demo.py +0 -0
- {labfreed-1.0.0a25 → labfreed-1.0.0b27}/pyproject.toml +0 -0
|
@@ -5,12 +5,18 @@ PAC-CAT
|
|
|
5
5
|
- added new categories
|
|
6
6
|
- BREAKING: Renamed category MM to MX
|
|
7
7
|
|
|
8
|
+
PAC-ID Resolver
|
|
9
|
+
- Transition to improved resolver configuration ( replaces coupling information table )
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
|
|
12
|
+
PAC-ID Attributes (Beta)
|
|
10
13
|
- new building block
|
|
11
|
-
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
|
|
16
|
+
General
|
|
17
|
+
- Minor Bugfixes
|
|
18
|
+
- BREAKING: reorganization of module structure > some import paths have changed
|
|
19
|
+
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
### v0.2.12
|
{labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/app/app_infrastructure.py
RENAMED
|
@@ -4,7 +4,6 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
6
|
from labfreed.labfreed_extended.app.pac_info.pac_info import PacInfo
|
|
7
|
-
from labfreed.pac_attributes.client.attribute_cache import MemoryAttributeCache
|
|
8
7
|
from labfreed.pac_attributes.client.client import AttributeClient, http_attribute_request_default_callback_factory
|
|
9
8
|
from labfreed.pac_attributes.pythonic.py_attributes import pyAttributeGroup
|
|
10
9
|
|
|
@@ -14,8 +13,6 @@ from labfreed.pac_id_resolver.services import ServiceGroup
|
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
16
|
class Labfreed_App_Infrastructure():
|
|
20
17
|
def __init__(self, markup = 'rich', language_preferences:list[str]|str='en', http_client:requests.Session|None=None, use_issuer_resolver_config=True):
|
|
21
18
|
if isinstance(language_preferences, str):
|
|
@@ -30,7 +27,7 @@ class Labfreed_App_Infrastructure():
|
|
|
30
27
|
self._http_client= http_client
|
|
31
28
|
callback = http_attribute_request_default_callback_factory(http_client)
|
|
32
29
|
|
|
33
|
-
self._attribute_client = AttributeClient(http_post_callback=callback
|
|
30
|
+
self._attribute_client = AttributeClient(http_post_callback=callback)
|
|
34
31
|
|
|
35
32
|
|
|
36
33
|
def add_resolver_config(self, cit:str):
|
|
@@ -79,7 +76,7 @@ class Labfreed_App_Infrastructure():
|
|
|
79
76
|
for sg in service_groups:
|
|
80
77
|
attributes_urls = [s.url for s in sg.services if s.service_type == 'attributes-generic']
|
|
81
78
|
for url in attributes_urls:
|
|
82
|
-
ags = {ag.
|
|
79
|
+
ags = {ag.group_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)}
|
|
83
80
|
if ags:
|
|
84
81
|
attribute_groups.update(ags)
|
|
85
82
|
pac_info.attribute_groups = attribute_groups
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
from functools import cached_property
|
|
4
2
|
from pathlib import Path
|
|
5
3
|
from urllib.parse import urlparse
|
|
@@ -164,10 +162,10 @@ class PacInfo(BaseModel):
|
|
|
164
162
|
@cached_property
|
|
165
163
|
def image_url(self) -> str:
|
|
166
164
|
image_attr = self._all_attributes.get(MetaAttributeKeys.IMAGE.value)
|
|
167
|
-
if isinstance(image_attr.
|
|
168
|
-
return image_attr.
|
|
169
|
-
if isinstance(image_attr.
|
|
170
|
-
return image_attr.
|
|
165
|
+
if isinstance(image_attr.values, pyResource):
|
|
166
|
+
return image_attr.values.root
|
|
167
|
+
if isinstance(image_attr.values, str):
|
|
168
|
+
return image_attr.values
|
|
171
169
|
|
|
172
170
|
|
|
173
171
|
@cached_property
|
|
@@ -180,7 +178,7 @@ class PacInfo(BaseModel):
|
|
|
180
178
|
# there can be a display name in attributes, too
|
|
181
179
|
|
|
182
180
|
if dn_attr := self._all_attributes.get(MetaAttributeKeys.DISPLAYNAME.value):
|
|
183
|
-
dn = dn_attr.
|
|
181
|
+
dn = dn_attr.values
|
|
184
182
|
display_name = dn + f' ( aka {display_name} )' if display_name else dn
|
|
185
183
|
|
|
186
184
|
if not display_name and self.main_category:
|
|
@@ -244,7 +242,7 @@ class PacInfo(BaseModel):
|
|
|
244
242
|
|
|
245
243
|
printout.title1("Attributes")
|
|
246
244
|
for ag in self.attribute_groups.values():
|
|
247
|
-
printout.title2(f'{ag.
|
|
245
|
+
printout.title2(f'{ag.group_label} (from {ag.origin})')
|
|
248
246
|
for v in ag.attributes.values():
|
|
249
247
|
v:pyAttribute
|
|
250
248
|
#print(f'{k}: ({v.label}) :: {v.value} ')
|
{labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/lib/attribute.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import logging
|
|
3
3
|
from flask import render_template, request
|
|
4
|
+
from labfreed.pac_attributes.api_data_models.request import AttributeRequestData
|
|
4
5
|
from labfreed.pac_attributes.api_data_models.response import AttributeGroup
|
|
5
6
|
from labfreed.pac_cat.pac_cat import PAC_CAT
|
|
6
7
|
from labfreed.pac_cat.predefined_categories import Material_Device, Material_Consumable, Material_Substance
|
|
@@ -75,7 +76,7 @@ class DynamicDemoAttributeGroup(AttributeGroupDataSource):
|
|
|
75
76
|
return None
|
|
76
77
|
|
|
77
78
|
attributes = pyAttributes( [pyAttribute(key=d[0], value=d[1]) for d in self._data] ).to_payload_attributes()
|
|
78
|
-
return AttributeGroup(
|
|
79
|
+
return AttributeGroup(group_key=self._attribute_group_key,
|
|
79
80
|
attributes=attributes)
|
|
80
81
|
|
|
81
82
|
|
|
@@ -106,7 +107,7 @@ class SessionLocalDirectCall(requests.Session):
|
|
|
106
107
|
self._request_handlers = request_handlers
|
|
107
108
|
|
|
108
109
|
|
|
109
|
-
def
|
|
110
|
+
def get(self, url, *args, **kwargs):
|
|
110
111
|
|
|
111
112
|
if is_self_request(url):
|
|
112
113
|
# Case: server calls itself
|
|
@@ -122,11 +123,15 @@ class SessionLocalDirectCall(requests.Session):
|
|
|
122
123
|
# Example: directly call the Flask view function instead of HTTP
|
|
123
124
|
# You could map URLs to functions if you know your routing
|
|
124
125
|
# For now, just return a mock response
|
|
125
|
-
|
|
126
|
+
path, pac = path.strip("/").rsplit("/", 1)
|
|
127
|
+
rh = self._request_handlers.get(path)
|
|
126
128
|
|
|
127
129
|
r = Response()
|
|
128
130
|
if rh:
|
|
129
|
-
|
|
131
|
+
request_data = AttributeRequestData.from_http_request(pac_id = pac,
|
|
132
|
+
params = kwargs.get('params'),
|
|
133
|
+
headers = kwargs.get('headers'))
|
|
134
|
+
body = rh.handle_attribute_request(request_data=request_data)
|
|
130
135
|
r.status_code = 200
|
|
131
136
|
r._content = body.encode("utf-8")
|
|
132
137
|
r.encoding = "utf-8"
|
|
@@ -161,8 +166,6 @@ def is_self_request(url: str) -> bool:
|
|
|
161
166
|
target_ip = resolve_ip(parsed.hostname.lower())
|
|
162
167
|
current_ip = resolve_ip(request.host.split(":")[0].lower())
|
|
163
168
|
|
|
164
|
-
|
|
165
|
-
|
|
166
169
|
return target_ip == current_ip
|
|
167
170
|
|
|
168
171
|
|
{labfreed-1.0.0a25 → labfreed-1.0.0b27}/labfreed/labfreed_extended/pac_issuer_lib/static/logo.svg
RENAMED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<svg id="LabfreedLogo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 233" width="200" height="116.5">
|
|
3
|
-
<defs>
|
|
4
|
-
<style>
|
|
5
|
-
.cls-1 { fill: #51a1d7; }
|
|
6
|
-
.cls-2 { fill: #438ad7; }
|
|
7
|
-
.cls-3 { fill: #94d5e4; }
|
|
8
|
-
.cls-4 { fill: #4db9d2; }
|
|
9
|
-
.cls-5 { fill: #2b63bc; }
|
|
10
|
-
.brand-text {
|
|
11
|
-
font-family: Arial, sans-serif;
|
|
12
|
-
font-size: 24px;
|
|
13
|
-
font-weight: bold;
|
|
14
|
-
fill: #51a1d7;
|
|
15
|
-
}
|
|
16
|
-
</style>
|
|
17
|
-
</defs>
|
|
18
|
-
|
|
19
|
-
<!-- Half-size dove -->
|
|
20
|
-
<g transform="scale(0.5)">
|
|
21
|
-
<g>
|
|
22
|
-
<g>
|
|
23
|
-
<polygon class="cls-3" points="115.8 143.51 115.8 143.55 115.77 143.51 115.8 143.51"/>
|
|
24
|
-
<polygon class="cls-1" points="208.4 97.2 141.5 164.1 138.93 158.95 115.78 112.63 108.05 97.2 208.4 97.2"/>
|
|
25
|
-
<polygon class="cls-2" points="208.4 97.19 108.05 97.19 138.93 27.74 169.8 58.61 208.4 97.19"/>
|
|
26
|
-
<polygon class="cls-5" points="208.4 43.17 208.4 97.2 169.81 58.62 208.4 43.17"/>
|
|
27
|
-
<polygon class="cls-2" points="247 74.05 208.4 74.05 208.4 43.17 247 74.05"/>
|
|
28
|
-
</g>
|
|
29
|
-
<path class="cls-4" d="M108.05,189.82h.02v-7.72h-.02v7.72ZM108.06,143.51v7.72h.02v-7.72h-.02ZM108.06,158.93v15.45h.02v-15.45h-.02Z"/>
|
|
30
|
-
<path class="cls-2" d="M141.5,164.1l-2.57-5.15-23.15-46.32-7.72-15.43v46.3h.02v7.72h-.02v7.71h.02v15.45h-.02v7.72h.02v7.72h-.02v30.87l38.6-46.32-5.15-10.27ZM115.8,143.55l-.03-.05h.03v.05Z"/>
|
|
31
|
-
<rect class="cls-2" x="30.88" y="43.16" width="15.43" height="15.45"/>
|
|
32
|
-
<rect class="cls-2" x="15.43" y="27.73" width="15.44" height="15.44"/>
|
|
33
|
-
<rect class="cls-1" y="12.3" width="15.44" height="15.44"/>
|
|
34
|
-
<rect class="cls-2" x="61.75" y="74.04" width="15.43" height="15.45"/>
|
|
35
|
-
<rect class="cls-1" x="46.32" y="74.05" width="15.44" height="15.44"/>
|
|
36
|
-
<rect class="cls-1" x="30.87" y="89.49" width="15.44" height="15.44"/>
|
|
37
|
-
<rect class="cls-1" y="58.61" width="15.44" height="15.44"/>
|
|
38
|
-
</g>
|
|
39
|
-
<polygon class="cls-5" points="138.93 27.74 108.05 97.19 92.63 97.19 92.63 89.48 77.18 89.48 77.18 74.04 61.75 74.04 61.75 58.61 46.31 58.61 46.31 43.16 30.88 43.16 30.88 27.75 30.86 27.74 138.93 27.74"/>
|
|
40
|
-
</g>
|
|
41
|
-
|
|
42
|
-
<!-- Branding text -->
|
|
43
|
-
<text class="brand-text" x="0" y="130">Powered by LabFREED </text>
|
|
44
|
-
</svg>
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="LabfreedLogo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 233" width="200" height="116.5">
|
|
3
|
+
<defs>
|
|
4
|
+
<style>
|
|
5
|
+
.cls-1 { fill: #51a1d7; }
|
|
6
|
+
.cls-2 { fill: #438ad7; }
|
|
7
|
+
.cls-3 { fill: #94d5e4; }
|
|
8
|
+
.cls-4 { fill: #4db9d2; }
|
|
9
|
+
.cls-5 { fill: #2b63bc; }
|
|
10
|
+
.brand-text {
|
|
11
|
+
font-family: Arial, sans-serif;
|
|
12
|
+
font-size: 24px;
|
|
13
|
+
font-weight: bold;
|
|
14
|
+
fill: #51a1d7;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
</defs>
|
|
18
|
+
|
|
19
|
+
<!-- Half-size dove -->
|
|
20
|
+
<g transform="scale(0.5)">
|
|
21
|
+
<g>
|
|
22
|
+
<g>
|
|
23
|
+
<polygon class="cls-3" points="115.8 143.51 115.8 143.55 115.77 143.51 115.8 143.51"/>
|
|
24
|
+
<polygon class="cls-1" points="208.4 97.2 141.5 164.1 138.93 158.95 115.78 112.63 108.05 97.2 208.4 97.2"/>
|
|
25
|
+
<polygon class="cls-2" points="208.4 97.19 108.05 97.19 138.93 27.74 169.8 58.61 208.4 97.19"/>
|
|
26
|
+
<polygon class="cls-5" points="208.4 43.17 208.4 97.2 169.81 58.62 208.4 43.17"/>
|
|
27
|
+
<polygon class="cls-2" points="247 74.05 208.4 74.05 208.4 43.17 247 74.05"/>
|
|
28
|
+
</g>
|
|
29
|
+
<path class="cls-4" d="M108.05,189.82h.02v-7.72h-.02v7.72ZM108.06,143.51v7.72h.02v-7.72h-.02ZM108.06,158.93v15.45h.02v-15.45h-.02Z"/>
|
|
30
|
+
<path class="cls-2" d="M141.5,164.1l-2.57-5.15-23.15-46.32-7.72-15.43v46.3h.02v7.72h-.02v7.71h.02v15.45h-.02v7.72h.02v7.72h-.02v30.87l38.6-46.32-5.15-10.27ZM115.8,143.55l-.03-.05h.03v.05Z"/>
|
|
31
|
+
<rect class="cls-2" x="30.88" y="43.16" width="15.43" height="15.45"/>
|
|
32
|
+
<rect class="cls-2" x="15.43" y="27.73" width="15.44" height="15.44"/>
|
|
33
|
+
<rect class="cls-1" y="12.3" width="15.44" height="15.44"/>
|
|
34
|
+
<rect class="cls-2" x="61.75" y="74.04" width="15.43" height="15.45"/>
|
|
35
|
+
<rect class="cls-1" x="46.32" y="74.05" width="15.44" height="15.44"/>
|
|
36
|
+
<rect class="cls-1" x="30.87" y="89.49" width="15.44" height="15.44"/>
|
|
37
|
+
<rect class="cls-1" y="58.61" width="15.44" height="15.44"/>
|
|
38
|
+
</g>
|
|
39
|
+
<polygon class="cls-5" points="138.93 27.74 108.05 97.19 92.63 97.19 92.63 89.48 77.18 89.48 77.18 74.04 61.75 74.04 61.75 58.61 46.31 58.61 46.31 43.16 30.88 43.16 30.88 27.75 30.86 27.74 138.93 27.74"/>
|
|
40
|
+
</g>
|
|
41
|
+
|
|
42
|
+
<!-- Branding text -->
|
|
43
|
+
<text class="brand-text" x="0" y="130">Powered by LabFREED </text>
|
|
44
|
+
</svg>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from typing import Any, Self
|
|
2
|
+
from urllib.parse import unquote
|
|
3
|
+
from werkzeug.datastructures import LanguageAccept
|
|
4
|
+
from werkzeug.http import parse_accept_header
|
|
5
|
+
from pydantic import ConfigDict, field_validator, model_validator
|
|
6
|
+
from labfreed.labfreed_infrastructure import LabFREED_BaseModel, LabFREED_ValidationError, ValidationMsgLevel
|
|
7
|
+
from labfreed.pac_id.pac_id import PAC_ID
|
|
8
|
+
|
|
9
|
+
ATTR_GROUPS = 'attr_grps'
|
|
10
|
+
ATTR_GROUPS_FWD_LKP= 'attr_fwd_lkp'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AttributeRequestData(LabFREED_BaseModel):
|
|
14
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
15
|
+
|
|
16
|
+
pac_id: str
|
|
17
|
+
language_preferences: LanguageAccept|None = None
|
|
18
|
+
restrict_to_attribute_groups: list[str]|None = None
|
|
19
|
+
do_forward_lookup: bool = True
|
|
20
|
+
|
|
21
|
+
def as_json(self):
|
|
22
|
+
return self.model_dump_json()
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_json(cls, json) -> Self:
|
|
26
|
+
return cls.model_validate_json(json)
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_http_request(cls, pac_id:str, params:dict, headers:dict):
|
|
30
|
+
restrict_to_attribute_groups = params.get(ATTR_GROUPS)
|
|
31
|
+
if restrict_to_attribute_groups == '':
|
|
32
|
+
restrict_to_attribute_groups = None
|
|
33
|
+
if restrict_to_attribute_groups:
|
|
34
|
+
restrict_to_attribute_groups = restrict_to_attribute_groups.split(',')
|
|
35
|
+
|
|
36
|
+
fwd_lkp = params.get(ATTR_GROUPS_FWD_LKP, True)
|
|
37
|
+
if fwd_lkp is True:
|
|
38
|
+
do_forward_lookup = True
|
|
39
|
+
else:
|
|
40
|
+
do_fwd_lookup = fwd_lkp.lower() not in ['false', 'no', '0', 'n', 'off']
|
|
41
|
+
|
|
42
|
+
lang_hdr = headers.get('Accept-Language')
|
|
43
|
+
language_preferences: LanguageAccept = parse_accept_header(lang_hdr, LanguageAccept)
|
|
44
|
+
out = cls(pac_id=pac_id,
|
|
45
|
+
restrict_to_attribute_groups = restrict_to_attribute_groups,
|
|
46
|
+
do_forward_lookup = do_forward_lookup,
|
|
47
|
+
language_preferences=language_preferences
|
|
48
|
+
)
|
|
49
|
+
return out
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@model_validator(mode="before")
|
|
54
|
+
@classmethod
|
|
55
|
+
def _scalars_to_list(cls, d):
|
|
56
|
+
if isinstance(lp:= d.get("language_preferences"), str):
|
|
57
|
+
d["language_preferences"] = [lp]
|
|
58
|
+
if isinstance(rag := d.get("restrict_to_attribute_groups"), str):
|
|
59
|
+
d["restrict_to_attribute_groups"] = [rag]
|
|
60
|
+
return d
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@field_validator('language_preferences', mode='before')
|
|
64
|
+
@classmethod
|
|
65
|
+
def convert_language_preferences(cls,lp):
|
|
66
|
+
if isinstance(lp, LanguageAccept):
|
|
67
|
+
return lp
|
|
68
|
+
lq = [(lng, 1-i/len(lp)) for i, lng in enumerate(lp)]
|
|
69
|
+
return LanguageAccept(lq)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@model_validator(mode="after")
|
|
73
|
+
def _revert_url_encoding(self):
|
|
74
|
+
self.pac_id = unquote(self.pac_id)
|
|
75
|
+
if self.restrict_to_attribute_groups:
|
|
76
|
+
self.restrict_to_attribute_groups = [unquote(g) for g in self.restrict_to_attribute_groups]
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
@model_validator(mode="after")
|
|
80
|
+
def _validate_pacs(self) -> Self:
|
|
81
|
+
try:
|
|
82
|
+
PAC_ID.from_url(self.pac_id)
|
|
83
|
+
except LabFREED_ValidationError:
|
|
84
|
+
self._add_validation_message(
|
|
85
|
+
source="pac_id",
|
|
86
|
+
level = ValidationMsgLevel.ERROR,
|
|
87
|
+
msg='{self.pac_id} is not a valid PAC-ID'
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if not self.is_valid:
|
|
91
|
+
raise LabFREED_ValidationError(message='Invalid request', validation_msgs=self.validation_messages())
|
|
92
|
+
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def language_preference_http_header(self) -> dict[str, str]:
|
|
96
|
+
if not self.language_preferences:
|
|
97
|
+
return {}
|
|
98
|
+
headers={'Accept-Language': LanguageAccept(self.language_preferences).to_header()}
|
|
99
|
+
return headers
|
|
100
|
+
|
|
101
|
+
def request_params(self) -> dict[str, Any]:
|
|
102
|
+
params = {ATTR_GROUPS_FWD_LKP: self.do_forward_lookup}
|
|
103
|
+
if self.restrict_to_attribute_groups:
|
|
104
|
+
params.update({ATTR_GROUPS: ','.join(self.restrict_to_attribute_groups)})
|
|
105
|
+
return params
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import re
|
|
5
|
+
from typing import Annotated, Any, Literal, Union, get_args
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
from labfreed.utilities.ensure_utc_time import ensure_utc
|
|
9
|
+
from labfreed.labfreed_infrastructure import LabFREED_BaseModel, ValidationMsgLevel, _quote_texts
|
|
10
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AttributeItemsElementBase(LabFREED_BaseModel, ABC):
|
|
15
|
+
value: Any
|
|
16
|
+
type:str
|
|
17
|
+
|
|
18
|
+
@model_validator(mode="after")
|
|
19
|
+
def _no_base_instances(self):
|
|
20
|
+
if type(self) is AttributeItemsElementBase:
|
|
21
|
+
raise TypeError("AttributeItemsElementBase must not be instantiated")
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
# def __init__(self, **data):
|
|
25
|
+
# # Automatically inject the Literal value for `type`
|
|
26
|
+
# discriminator_value = self._get_discriminator_value()
|
|
27
|
+
# data["type"] = discriminator_value
|
|
28
|
+
# super().__init__(**data)
|
|
29
|
+
|
|
30
|
+
# @classmethod
|
|
31
|
+
# def _get_discriminator_value(cls) -> str:
|
|
32
|
+
# """Extract the Literal value from the 'type' annotation."""
|
|
33
|
+
# try:
|
|
34
|
+
# type_annotation = cls.__annotations__["type"]
|
|
35
|
+
# literal_value = get_args(type_annotation)[0]
|
|
36
|
+
# return literal_value
|
|
37
|
+
# except Exception as e:
|
|
38
|
+
# raise TypeError(
|
|
39
|
+
# f"{cls.__name__} must define `type: Literal[<value>]` annotation"
|
|
40
|
+
# ) from e
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DateTimeAttributeItemsElement(AttributeItemsElementBase):
|
|
45
|
+
type: Literal["datetime"] = "datetime"
|
|
46
|
+
value: datetime
|
|
47
|
+
|
|
48
|
+
@field_validator('value', mode='after')
|
|
49
|
+
def set_utc__if_naive(cls, value):
|
|
50
|
+
if isinstance(value, datetime):
|
|
51
|
+
return ensure_utc(value)
|
|
52
|
+
else:
|
|
53
|
+
return value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class BoolAttributeItemsElement(AttributeItemsElementBase):
|
|
58
|
+
type: Literal["bool"] = "bool"
|
|
59
|
+
value: bool
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TextAttributeItemsElement(AttributeItemsElementBase):
|
|
65
|
+
type: Literal["text"] = "text"
|
|
66
|
+
value: str
|
|
67
|
+
|
|
68
|
+
@model_validator(mode='after')
|
|
69
|
+
def _validate_value(self):
|
|
70
|
+
_validate_text(self, self.value)
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _validate_text(mdl:LabFREED_BaseModel, v):
|
|
75
|
+
if len(v) > 5000:
|
|
76
|
+
mdl._add_validation_message(
|
|
77
|
+
source="Text Attribute",
|
|
78
|
+
level=ValidationMsgLevel.WARNING, # noqa: F821
|
|
79
|
+
msg=f"Text attribute {v} exceeds 5000 characters. It is recommended to stay below",
|
|
80
|
+
highlight_pattern = f'{v}'
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ReferenceAttributeItemsElement(AttributeItemsElementBase):
|
|
86
|
+
type: Literal["reference"] = "reference"
|
|
87
|
+
value: str
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ResourceAttributeItemsElement(AttributeItemsElementBase):
|
|
92
|
+
type: Literal["resource"] = "resource"
|
|
93
|
+
value: str
|
|
94
|
+
|
|
95
|
+
@model_validator(mode='after')
|
|
96
|
+
def _validate_value(self):
|
|
97
|
+
_validate_resource(self, self.value)
|
|
98
|
+
return self
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _validate_resource(mdl:LabFREED_BaseModel, v):
|
|
102
|
+
r = urlparse(v)
|
|
103
|
+
if not all([r.scheme, r.netloc]):
|
|
104
|
+
mdl._add_validation_message(
|
|
105
|
+
source="Resource Attribute",
|
|
106
|
+
level=ValidationMsgLevel.ERROR, # noqa: F821
|
|
107
|
+
msg="Must be a valid url",
|
|
108
|
+
highlight_pattern = f'{v}'
|
|
109
|
+
)
|
|
110
|
+
pattern = re.compile(r"\.\w{1,3}$", re.IGNORECASE)
|
|
111
|
+
if not bool(pattern.search(v)):
|
|
112
|
+
mdl._add_validation_message(
|
|
113
|
+
source="Resource Attribute",
|
|
114
|
+
level=ValidationMsgLevel.WARNING, # noqa: F821
|
|
115
|
+
msg="It is RECOMMENDED resource links end with a file extension",
|
|
116
|
+
highlight_pattern = f'{v}'
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class NumericAttributeItemsElement(AttributeItemsElementBase):
|
|
121
|
+
type: Literal["numeric"] = "numeric"
|
|
122
|
+
value: str
|
|
123
|
+
_numerical_value:str
|
|
124
|
+
_unit:str
|
|
125
|
+
|
|
126
|
+
@model_validator(mode='after')
|
|
127
|
+
def _validate_model(self):
|
|
128
|
+
self._numerical_value, self._unit = self.value.split(' ', 1)
|
|
129
|
+
self._validate_value()
|
|
130
|
+
self._validate_unit()
|
|
131
|
+
return self
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _validate_value(self):
|
|
135
|
+
value = self._numerical_value
|
|
136
|
+
if not_allowed_chars := set(re.sub(r'[0-9\.\-\+Ee]', '', value)):
|
|
137
|
+
self._add_validation_message(
|
|
138
|
+
source="Numeric Attribute",
|
|
139
|
+
level=ValidationMsgLevel.ERROR, # noqa: F821
|
|
140
|
+
msg=f"Characters {_quote_texts(not_allowed_chars)} are not allowed in quantity segment. Must be a number.",
|
|
141
|
+
highlight_pattern = f'{value}',
|
|
142
|
+
highlight_sub=not_allowed_chars
|
|
143
|
+
)
|
|
144
|
+
if not re.fullmatch(r'-?\d+(\.\d+)?([Ee][\+-]?\d+)?', value):
|
|
145
|
+
self._add_validation_message(
|
|
146
|
+
source="Numeric Attribute",
|
|
147
|
+
level=ValidationMsgLevel.ERROR,
|
|
148
|
+
msg=f"{value} cannot be converted to number",
|
|
149
|
+
highlight_pattern = f'{value}'
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def _validate_unit(self):
|
|
153
|
+
'''A sanity check on unit complying with UCUM. NOTE: It is not a complete validation
|
|
154
|
+
- I check for blankspaces and ^, which are often used for units, but are invalid.
|
|
155
|
+
- the general structure of a ucum unit is validated, but 1)parentheses are not matched 2) units are not validated 3)prefixes are not checked
|
|
156
|
+
'''
|
|
157
|
+
if ' ' in self._unit or '^' in self._unit:
|
|
158
|
+
self._add_validation_message(
|
|
159
|
+
source="Numeric Attribute",
|
|
160
|
+
level= ValidationMsgLevel.ERROR,
|
|
161
|
+
msg=f"Unit {self._unit} is invalid. Must not contain blankspace or '^'.",
|
|
162
|
+
highlight_pattern = self._unit
|
|
163
|
+
)
|
|
164
|
+
elif not re.fullmatch(r"^(((?P<unit>[\w\[\]]+?)(?P<exponent>\-?\d+)?|(?P<annotation>)\{\w+?\})(?P<operator>[\./]?)?)+", self._unit):
|
|
165
|
+
self._add_validation_message(
|
|
166
|
+
source="Numeric Attribute",
|
|
167
|
+
level= ValidationMsgLevel.WARNING,
|
|
168
|
+
msg=f"Unit {self._unit} is probably invalid. Ensure it complies with UCUM specifications.",
|
|
169
|
+
highlight_pattern = self._unit
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class ObjectAttributeItemsElement(AttributeItemsElementBase):
|
|
175
|
+
type: Literal["object"] = "object"
|
|
176
|
+
value: dict[str, Any]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
AttributeItemsElement = Annotated[
|
|
180
|
+
Union[
|
|
181
|
+
DateTimeAttributeItemsElement,
|
|
182
|
+
BoolAttributeItemsElement,
|
|
183
|
+
TextAttributeItemsElement,
|
|
184
|
+
NumericAttributeItemsElement,
|
|
185
|
+
ReferenceAttributeItemsElement,
|
|
186
|
+
ResourceAttributeItemsElement,
|
|
187
|
+
ObjectAttributeItemsElement
|
|
188
|
+
],
|
|
189
|
+
Field(discriminator="type"),
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Attribute(LabFREED_BaseModel):
|
|
195
|
+
key: str|None = Field(exclude=True)
|
|
196
|
+
label: str = ""
|
|
197
|
+
items: list[AttributeItemsElement]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class AttributeGroup(LabFREED_BaseModel):
|
|
202
|
+
group_key: str
|
|
203
|
+
group_label: str = ""
|
|
204
|
+
attributes: dict[str, Attribute]
|
|
205
|
+
|
|
206
|
+
@field_validator("attributes", mode="before")
|
|
207
|
+
@classmethod
|
|
208
|
+
def set_attribute_keys(cls, v):
|
|
209
|
+
if not isinstance(v, dict):
|
|
210
|
+
return v
|
|
211
|
+
|
|
212
|
+
out = {}
|
|
213
|
+
for k, a in v.items():
|
|
214
|
+
if isinstance(a, dict):
|
|
215
|
+
# raw input dict -> inject key if missing
|
|
216
|
+
out[k] = {**a, "key": a.get("key") or k}
|
|
217
|
+
else:
|
|
218
|
+
# already an Attribute (or something pydantic can parse)
|
|
219
|
+
out[k] = a
|
|
220
|
+
return out
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class AttributesOfPACID(LabFREED_BaseModel):
|
|
225
|
+
pac_id: str
|
|
226
|
+
attribute_groups: list[AttributeGroup]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
IMPORT_URL = "https://vocab.labfreed.org/attributes/v1.jsonld"
|
|
231
|
+
|
|
232
|
+
class AttributeResponsePayload(LabFREED_BaseModel):
|
|
233
|
+
schema_version: str = Field(default='1.0')
|
|
234
|
+
language:str
|
|
235
|
+
data: list[AttributesOfPACID]
|
|
236
|
+
|
|
237
|
+
context: str = Field(alias='@context', default=IMPORT_URL)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def to_json(self):
|
|
241
|
+
return self.model_dump_json(exclude_none=True, by_alias=True)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|