labfreed 0.3.1a5__py3-none-any.whl → 1.0.0a2__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/__init__.py +1 -1
- labfreed/labfreed_extended/app/app_infrastructure.py +7 -16
- labfreed/labfreed_extended/app/pac_info.py +45 -7
- labfreed/pac_attributes/api_data_models/request.py +3 -3
- labfreed/pac_attributes/api_data_models/response.py +3 -3
- labfreed/pac_attributes/client/client.py +27 -5
- labfreed/pac_attributes/pythonic/attribute_server_factory.py +41 -7
- labfreed/pac_attributes/pythonic/py_attributes.py +15 -6
- labfreed/pac_attributes/server/server.py +4 -45
- labfreed/pac_attributes/well_knonw_attribute_keys.py +1 -1
- labfreed/pac_cat/category_base.py +2 -2
- labfreed/pac_id/url_parser.py +1 -1
- labfreed/pac_id_resolver/cit_v2.py +1 -0
- labfreed/pac_id_resolver/resolver.py +2 -2
- labfreed/trex/pythonic/pyTREX.py +1 -1
- labfreed/trex/pythonic/quantity.py +5 -3
- {labfreed-0.3.1a5.dist-info → labfreed-1.0.0a2.dist-info}/METADATA +1 -1
- {labfreed-0.3.1a5.dist-info → labfreed-1.0.0a2.dist-info}/RECORD +20 -20
- {labfreed-0.3.1a5.dist-info → labfreed-1.0.0a2.dist-info}/WHEEL +0 -0
- {labfreed-0.3.1a5.dist-info → labfreed-1.0.0a2.dist-info}/licenses/LICENSE +0 -0
labfreed/__init__.py
CHANGED
|
@@ -5,7 +5,8 @@ import requests
|
|
|
5
5
|
|
|
6
6
|
from labfreed.labfreed_extended.app.pac_info import PacInfo
|
|
7
7
|
from labfreed.pac_attributes.client.attribute_cache import MemoryAttributeCache
|
|
8
|
-
from labfreed.pac_attributes.client.client import AttributeClient,
|
|
8
|
+
from labfreed.pac_attributes.client.client import AttributeClient, http_attribute_request_default_callback_factory
|
|
9
|
+
from labfreed.pac_attributes.pythonic.py_attributes import pyAttributeGroup
|
|
9
10
|
from labfreed.pac_attributes.well_knonw_attribute_keys import MetaAttributeKeys
|
|
10
11
|
from labfreed.well_known_extensions.display_name_extension import DisplayNameExtension
|
|
11
12
|
|
|
@@ -29,7 +30,7 @@ class Labfreed_App_Infrastructure():
|
|
|
29
30
|
if not http_client:
|
|
30
31
|
http_client = requests.Session()
|
|
31
32
|
self._http_client= http_client
|
|
32
|
-
callback =
|
|
33
|
+
callback = http_attribute_request_default_callback_factory(http_client)
|
|
33
34
|
|
|
34
35
|
self._attribute_client = AttributeClient(http_post_callback=callback, cache_store=MemoryAttributeCache())
|
|
35
36
|
|
|
@@ -63,25 +64,15 @@ class Labfreed_App_Infrastructure():
|
|
|
63
64
|
pac_info.user_handovers = sg_user_handovers
|
|
64
65
|
|
|
65
66
|
# Attributes
|
|
66
|
-
attribute_groups =
|
|
67
|
+
attribute_groups = {}
|
|
67
68
|
for sg in service_groups:
|
|
68
69
|
attributes_urls = [s.url for s in sg.services if s.service_type == 'attributes-generic']
|
|
69
70
|
for url in attributes_urls:
|
|
70
|
-
ags = self._attribute_client.get_attributes(url, pac_id=pac.to_url(include_extensions=False), language_preferences=self._language_preferences)
|
|
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)}
|
|
71
72
|
if ags:
|
|
72
|
-
attribute_groups.
|
|
73
|
+
attribute_groups.update(ags)
|
|
73
74
|
pac_info.attributes = attribute_groups
|
|
74
|
-
|
|
75
|
-
if dn := pac.get_extension('N'):
|
|
76
|
-
dn = DisplayNameExtension.from_extension(dn)
|
|
77
|
-
pac_info.display_name = dn.display_name or ""
|
|
78
|
-
# there can be a display name in attributes, too
|
|
79
|
-
if meta := [ag for ag in pac_info.attributes if ag.key == MetaAttributeKeys.GROUPKEY.value]:
|
|
80
|
-
dn_attr = [a for a in meta[0].attributes if a.key == MetaAttributeKeys.DISPLAYNAME.value]
|
|
81
|
-
if dn_attr:
|
|
82
|
-
dn = dn_attr[0].value
|
|
83
|
-
pac_info.display_name += f' ( aka {dn} )'
|
|
84
|
-
|
|
75
|
+
|
|
85
76
|
return pac_info
|
|
86
77
|
|
|
87
78
|
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
+
from functools import cached_property
|
|
3
4
|
from pydantic import BaseModel, Field
|
|
4
|
-
from labfreed.pac_attributes.pythonic.py_attributes import pyAttribute, pyAttributes
|
|
5
|
+
from labfreed.pac_attributes.pythonic.py_attributes import pyAttribute, pyAttributeGroup, pyAttributes
|
|
6
|
+
from labfreed.pac_attributes.well_knonw_attribute_keys import MetaAttributeKeys
|
|
5
7
|
from labfreed.pac_cat.pac_cat import PAC_CAT
|
|
6
8
|
from labfreed.pac_id.pac_id import PAC_ID
|
|
7
9
|
from labfreed.pac_id_resolver.services import ServiceGroup
|
|
8
10
|
from labfreed.labfreed_extended.app.formatted_print import StringIOLineBreak
|
|
11
|
+
from labfreed.trex.pythonic.pyTREX import pyTREX
|
|
12
|
+
from labfreed.well_known_extensions.display_name_extension import DisplayNameExtension
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class PacInfo(BaseModel):
|
|
12
16
|
"""A convenient collection of information about a PAC-ID"""
|
|
13
17
|
pac_id:PAC_ID
|
|
14
|
-
display_name:str|None = None
|
|
15
18
|
user_handovers: list[ServiceGroup] = Field(default_factory=list)
|
|
16
|
-
attributes:
|
|
19
|
+
attributes:dict[str, pyAttributeGroup] = Field(default_factory=dict)
|
|
17
20
|
|
|
18
21
|
@property
|
|
19
22
|
def pac_url(self):
|
|
@@ -28,12 +31,48 @@ class PacInfo(BaseModel):
|
|
|
28
31
|
|
|
29
32
|
@property
|
|
30
33
|
def attached_data(self):
|
|
31
|
-
return self.pac_id.get_extension_of_type('TREX')
|
|
34
|
+
return { trex_ext.name: pyTREX.from_trex(trex=trex_ext.trex) for trex_ext in self.pac_id.get_extension_of_type('TREX')}
|
|
35
|
+
|
|
32
36
|
|
|
33
37
|
@property
|
|
34
38
|
def summary(self):
|
|
35
39
|
return self.pac_id.get_extension('SUM')
|
|
36
40
|
|
|
41
|
+
@property
|
|
42
|
+
def image_url(self) -> str:
|
|
43
|
+
if meta := self.attributes.get(MetaAttributeKeys.GROUPKEY.value):
|
|
44
|
+
image_attr = meta.attributes.get(MetaAttributeKeys.IMAGE.value)
|
|
45
|
+
return image_attr.value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def display_name(self) -> str|None:
|
|
50
|
+
display_name = None
|
|
51
|
+
pac = self.pac_id
|
|
52
|
+
if dn := pac.get_extension('N'):
|
|
53
|
+
dn = DisplayNameExtension.from_extension(dn)
|
|
54
|
+
display_name = dn.display_name or ""
|
|
55
|
+
# there can be a display name in attributes, too
|
|
56
|
+
|
|
57
|
+
if dn_attr := self._all_attributes.get(MetaAttributeKeys.DISPLAYNAME.value):
|
|
58
|
+
dn = dn_attr.value
|
|
59
|
+
display_name = dn + f' ( aka {display_name} )' if display_name else dn
|
|
60
|
+
return display_name
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def safety_pictograms(self) -> dict[str, pyAttribute]:
|
|
65
|
+
pictogram_attributes = {k: a for k, a in self._all_attributes.items() if "https://labfreed.org/ghs/pictogram/" in a.key}
|
|
66
|
+
return pictogram_attributes
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@cached_property
|
|
70
|
+
def _all_attributes(self) -> dict[str, pyAttribute]:
|
|
71
|
+
out = {}
|
|
72
|
+
for ag in self.attributes.values():
|
|
73
|
+
out.update(ag.attributes)
|
|
74
|
+
return out
|
|
75
|
+
|
|
37
76
|
|
|
38
77
|
|
|
39
78
|
def format_for_print(self, markup:str='rich') -> str:
|
|
@@ -62,10 +101,9 @@ class PacInfo(BaseModel):
|
|
|
62
101
|
|
|
63
102
|
|
|
64
103
|
printout.title1("Attributes")
|
|
65
|
-
for ag in self.attributes:
|
|
104
|
+
for ag in self.attributes.values():
|
|
66
105
|
printout.title2(f'{ag.label} (from {ag.origin})')
|
|
67
|
-
|
|
68
|
-
for k, v in attributes.items():
|
|
106
|
+
for v in ag.attributes.values():
|
|
69
107
|
v:pyAttribute
|
|
70
108
|
#print(f'{k}: ({v.label}) :: {v.value} ')
|
|
71
109
|
printout.key_value(v.label, v.value)
|
|
@@ -7,7 +7,7 @@ from labfreed.pac_id.pac_id import PAC_ID
|
|
|
7
7
|
class AttributeRequestPayload(LabFREED_BaseModel):
|
|
8
8
|
model_config = ConfigDict(frozen=True)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
pac_ids: list[str]
|
|
11
11
|
language_preferences: list[str]
|
|
12
12
|
restrict_to_attribute_groups: list[str]|None = None
|
|
13
13
|
suppress_forward_lookup: bool = False
|
|
@@ -29,14 +29,14 @@ class AttributeRequestPayload(LabFREED_BaseModel):
|
|
|
29
29
|
|
|
30
30
|
@model_validator(mode="after")
|
|
31
31
|
def _validate_pacs(self) -> Self:
|
|
32
|
-
if len(self.
|
|
32
|
+
if len(self.pac_ids) > 100:
|
|
33
33
|
self._add_validation_message(
|
|
34
34
|
source="pacs",
|
|
35
35
|
level = ValidationMsgLevel.ERROR,
|
|
36
36
|
msg='The number of pac-ids must be limited to 100'
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
-
for pac_url in self.
|
|
39
|
+
for pac_url in self.pac_ids:
|
|
40
40
|
try:
|
|
41
41
|
PAC_ID.from_url(pac_url)
|
|
42
42
|
except LabFREED_ValidationError:
|
|
@@ -70,12 +70,12 @@ class TextAttribute(AttributeBase):
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
class NumericValue(LabFREED_BaseModel):
|
|
73
|
-
|
|
73
|
+
numerical_value: str
|
|
74
74
|
unit: str
|
|
75
75
|
|
|
76
76
|
@model_validator(mode='after')
|
|
77
77
|
def _validate_value(self):
|
|
78
|
-
value = self.
|
|
78
|
+
value = self.numerical_value
|
|
79
79
|
if not_allowed_chars := set(re.sub(r'[0-9\.\-\+Ee]', '', value)):
|
|
80
80
|
self._add_validation_message(
|
|
81
81
|
source="Numeric Attribute",
|
|
@@ -160,7 +160,7 @@ class AttributeGroup(LabFREED_BaseModel):
|
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
class AttributesOfPACID(LabFREED_BaseModel):
|
|
163
|
-
|
|
163
|
+
pac_id: str
|
|
164
164
|
attribute_groups: list[AttributeGroup]
|
|
165
165
|
|
|
166
166
|
|
|
@@ -10,6 +10,7 @@ from pydantic import ValidationError
|
|
|
10
10
|
from labfreed.pac_attributes.api_data_models.request import AttributeRequestPayload
|
|
11
11
|
from labfreed.pac_attributes.api_data_models.response import AttributeResponsePayload
|
|
12
12
|
from labfreed.pac_attributes.client.attribute_cache import AttributeCache, CacheableAttributeGroup
|
|
13
|
+
from labfreed.pac_attributes.server.server import AttributeServerRequestHandler
|
|
13
14
|
from labfreed.pac_id.pac_id import PAC_ID
|
|
14
15
|
|
|
15
16
|
|
|
@@ -35,7 +36,7 @@ class AttributeRequestCallback(Protocol):
|
|
|
35
36
|
...
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
def
|
|
39
|
+
def http_attribute_request_default_callback_factory(session: requests.Session = None) -> AttributeRequestCallback:
|
|
39
40
|
""" Returns a default implementation of AttributeRequestCallback using `requests` package.
|
|
40
41
|
|
|
41
42
|
Args:
|
|
@@ -49,12 +50,31 @@ def attribute_request_default_callback_factory(session: requests.Session = None)
|
|
|
49
50
|
|
|
50
51
|
def callback(url: str, attribute_request_body: str) -> tuple[int, str]:
|
|
51
52
|
try:
|
|
52
|
-
resp = session.post(url, data=attribute_request_body, headers={'Content-Type': 'application/json'})
|
|
53
|
+
resp = session.post(url, data=attribute_request_body, headers={'Content-Type': 'application/json'}, timeout=10)
|
|
53
54
|
return resp.status_code, resp.text
|
|
54
55
|
except requests.exceptions.RequestException as e:
|
|
55
56
|
return 500, str(e)
|
|
56
57
|
return callback
|
|
57
58
|
|
|
59
|
+
|
|
60
|
+
def local_attribute_request_callback_factory(request_handler:AttributeServerRequestHandler) -> AttributeRequestCallback:
|
|
61
|
+
""" Returns a default implementation of AttributeRequestCallback using `requests` package.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
request_handler: The request handler
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
AttributeRequestCallback: a callback following the AttributeRequestCallback protocol.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def callback(url: str, attribute_request_body: str) -> tuple[int, str]:
|
|
71
|
+
try:
|
|
72
|
+
resp = request_handler.handle_attribute_request(attribute_request_body)
|
|
73
|
+
return 200, resp
|
|
74
|
+
except requests.exceptions.RequestException as e:
|
|
75
|
+
return 500, str(e)
|
|
76
|
+
return callback
|
|
77
|
+
|
|
58
78
|
|
|
59
79
|
|
|
60
80
|
|
|
@@ -104,7 +124,7 @@ class AttributeClient():
|
|
|
104
124
|
return attribute_groups
|
|
105
125
|
|
|
106
126
|
# no valid data found in cache > request to server
|
|
107
|
-
attribute_request_body = AttributeRequestPayload(
|
|
127
|
+
attribute_request_body = AttributeRequestPayload(pac_ids=[pac_id.to_url()],
|
|
108
128
|
restrict_to_attribute_groups=restrict_to_attribute_groups,
|
|
109
129
|
language_preferences=language_preferences
|
|
110
130
|
)
|
|
@@ -125,7 +145,7 @@ class AttributeClient():
|
|
|
125
145
|
|
|
126
146
|
# update cache
|
|
127
147
|
for ag_for_pac in r.pac_attributes:
|
|
128
|
-
pac = PAC_ID.from_url(ag_for_pac.
|
|
148
|
+
pac = PAC_ID.from_url(ag_for_pac.pac_id)
|
|
129
149
|
ags = [
|
|
130
150
|
CacheableAttributeGroup(
|
|
131
151
|
key= ag.key,
|
|
@@ -140,7 +160,9 @@ class AttributeClient():
|
|
|
140
160
|
|
|
141
161
|
if pac_id == pac:
|
|
142
162
|
attribute_groups_out = ags
|
|
143
|
-
|
|
163
|
+
return attribute_groups_out
|
|
164
|
+
else:
|
|
165
|
+
return []
|
|
144
166
|
|
|
145
167
|
|
|
146
168
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
from typing import Any, Protocol
|
|
3
3
|
|
|
4
|
-
from flask import Blueprint
|
|
4
|
+
from flask import Blueprint, current_app
|
|
5
|
+
from labfreed.pac_attributes.api_data_models.request import AttributeRequestPayload
|
|
5
6
|
from labfreed.pac_attributes.server.server import AttributeGroupDataSource, AttributeServerRequestHandler, InvalidRequestError, TranslationDataSource
|
|
6
7
|
|
|
7
8
|
try:
|
|
@@ -30,7 +31,8 @@ class AttributeServerFactory():
|
|
|
30
31
|
default_language:str,
|
|
31
32
|
translation_data_sources:list[TranslationDataSource],
|
|
32
33
|
authenticator: Authenticator|None,
|
|
33
|
-
framework:Webframework=Webframework.FLASK
|
|
34
|
+
framework:Webframework=Webframework.FLASK,
|
|
35
|
+
doc_text:str=""
|
|
34
36
|
):
|
|
35
37
|
|
|
36
38
|
if not authenticator:
|
|
@@ -43,7 +45,7 @@ class AttributeServerFactory():
|
|
|
43
45
|
|
|
44
46
|
match(framework):
|
|
45
47
|
case Webframework.FLASK:
|
|
46
|
-
app = AttributeFlaskApp(request_handler,authenticator=authenticator)
|
|
48
|
+
app = AttributeFlaskApp(request_handler,authenticator=authenticator, doc_text=doc_text)
|
|
47
49
|
return app
|
|
48
50
|
case Webframework.FASTAPI:
|
|
49
51
|
raise NotImplementedError('FastAPI webapp not implemented')
|
|
@@ -53,10 +55,11 @@ class AttributeServerFactory():
|
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
class AttributeFlaskApp(Flask):
|
|
56
|
-
def __init__(self, request_handler: AttributeServerRequestHandler, authenticator: Authenticator | None = None, **kwargs: Any):
|
|
58
|
+
def __init__(self, request_handler: AttributeServerRequestHandler, authenticator: Authenticator | None = None, doc_text:str="", **kwargs: Any):
|
|
57
59
|
super().__init__(__name__, **kwargs)
|
|
58
60
|
self.config['ATTRIBUTE_REQUEST_HANDLER'] = request_handler
|
|
59
61
|
self.config['AUTHENTICATOR'] = authenticator
|
|
62
|
+
self.config['DOC_TEXT'] = doc_text
|
|
60
63
|
|
|
61
64
|
bp = self.create_attribute_blueprint(request_handler, authenticator)
|
|
62
65
|
self.register_blueprint(bp)
|
|
@@ -68,7 +71,7 @@ class AttributeFlaskApp(Flask):
|
|
|
68
71
|
) -> Blueprint:
|
|
69
72
|
bp = Blueprint("attribute", __name__)
|
|
70
73
|
|
|
71
|
-
@bp.route("/", methods=["POST"])
|
|
74
|
+
@bp.route("/", methods=["POST"], strict_slashes=False)
|
|
72
75
|
def handle_attribute_request():
|
|
73
76
|
if authenticator and not authenticator(request):
|
|
74
77
|
return Response(
|
|
@@ -86,9 +89,40 @@ class AttributeFlaskApp(Flask):
|
|
|
86
89
|
return "The request was valid, but the server encountered an error", 500
|
|
87
90
|
return response_body
|
|
88
91
|
|
|
89
|
-
@bp.route("/
|
|
92
|
+
@bp.route("/", methods=["GET"], strict_slashes=False)
|
|
90
93
|
def capabilities():
|
|
91
|
-
|
|
94
|
+
doc_text = current_app.config.get('DOC_TEXT', "")
|
|
95
|
+
capabilities = request_handler.capabilities()
|
|
96
|
+
authentication_required = bool(current_app.config['AUTHENTICATOR'])
|
|
97
|
+
example_request = AttributeRequestPayload(pac_ids=['HTTPS://PAC.METTORIUS.COM/EXAMPLE'], language_preferences=['fr', 'de']).model_dump_json(indent=2, exclude_none=True, exclude_unset=True)
|
|
98
|
+
server_address = request.url.rstrip('/')
|
|
99
|
+
response = f'''
|
|
100
|
+
<body>
|
|
101
|
+
This is a <h1>LabFREED attribute server </h1>
|
|
102
|
+
<h2>Capabilities</h2>
|
|
103
|
+
Available Attribute Groups: {', '.join([f'<a href="{ag}"> {ag} </a>' for ag in capabilities.available_attribute_groups])} <br>
|
|
104
|
+
|
|
105
|
+
Supported Languages: {', '.join([f'<b> {l} </b>' for l in capabilities.supported_languages])} <br>
|
|
106
|
+
Default Language: <b>{capabilities.default_language}</b> <br>
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
<h2>How to use</h2>
|
|
110
|
+
Make a <b>POST</b> request to <a href="{server_address}">{server_address}</a> with the following body:
|
|
111
|
+
<pre>{example_request}</pre>
|
|
112
|
+
Consult {'<a href="https://github.com/ApiniLabs/PAC-Attributes"> the specification </a>' if doc_text else ""} for details. <br>
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
{'This server <b> requires authentication </b> ' if authentication_required else ''}
|
|
116
|
+
<br>
|
|
117
|
+
|
|
118
|
+
{"<h2>Further Information</h2>"if doc_text else ""}
|
|
119
|
+
{doc_text or ""}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
</body>
|
|
123
|
+
'''
|
|
124
|
+
|
|
125
|
+
return response
|
|
92
126
|
|
|
93
127
|
return bp
|
|
94
128
|
|
|
@@ -6,7 +6,8 @@ import warnings
|
|
|
6
6
|
from pydantic import RootModel
|
|
7
7
|
|
|
8
8
|
from labfreed.labfreed_infrastructure import LabFREED_BaseModel
|
|
9
|
-
from labfreed.pac_attributes.api_data_models.response import AttributeBase, 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, TextAttribute
|
|
10
|
+
from labfreed.pac_attributes.client.attribute_cache import CacheableAttributeGroup
|
|
10
11
|
from labfreed.pac_id.pac_id import PAC_ID
|
|
11
12
|
from labfreed.trex.pythonic.quantity import Quantity
|
|
12
13
|
|
|
@@ -54,7 +55,7 @@ class pyAttributes(RootModel[list[pyAttribute]]):
|
|
|
54
55
|
elif isinstance(attribute.value, Quantity|int|float):
|
|
55
56
|
if not isinstance(attribute.value, Quantity):
|
|
56
57
|
value = Quantity(value=attribute.value, unit='dimensionless')
|
|
57
|
-
num_attribute = NumericAttribute(value = NumericValue(
|
|
58
|
+
num_attribute = NumericAttribute(value = NumericValue(numerical_value=value.value_as_str(),
|
|
58
59
|
unit = value.unit),
|
|
59
60
|
**common_args)
|
|
60
61
|
num_attribute.print_validation_messages()
|
|
@@ -63,7 +64,7 @@ class pyAttributes(RootModel[list[pyAttribute]]):
|
|
|
63
64
|
elif isinstance(value, str):
|
|
64
65
|
# capture quantities in the form of "100.0e5 g/L"
|
|
65
66
|
if q := Quantity.from_str_with_unit(value):
|
|
66
|
-
return NumericAttribute(value = NumericValue(
|
|
67
|
+
return NumericAttribute(value = NumericValue(numerical_value=q.value_as_str(),
|
|
67
68
|
unit = q.unit),
|
|
68
69
|
**common_args)
|
|
69
70
|
else:
|
|
@@ -87,7 +88,7 @@ class pyAttributes(RootModel[list[pyAttribute]]):
|
|
|
87
88
|
|
|
88
89
|
@staticmethod
|
|
89
90
|
def from_payload_attributes(attributes:list[AttributeBase]) -> 'pyAttributes':
|
|
90
|
-
out =
|
|
91
|
+
out = list()
|
|
91
92
|
for a in attributes:
|
|
92
93
|
match a:
|
|
93
94
|
|
|
@@ -95,7 +96,7 @@ class pyAttributes(RootModel[list[pyAttribute]]):
|
|
|
95
96
|
value = pyReference(a.value)
|
|
96
97
|
|
|
97
98
|
case NumericAttribute():
|
|
98
|
-
value = Quantity.from_str_value(value=a.value.
|
|
99
|
+
value = Quantity.from_str_value(value=a.value.numerical_value, unit=a.value.unit)
|
|
99
100
|
|
|
100
101
|
case BoolAttribute():
|
|
101
102
|
value = a.value
|
|
@@ -117,8 +118,16 @@ class pyAttributes(RootModel[list[pyAttribute]]):
|
|
|
117
118
|
# valid_until=datetime(**_parse_date_time_str(a.valid_until)),
|
|
118
119
|
# observed_at=datetime(**_parse_date_time_str(a.value))
|
|
119
120
|
)
|
|
120
|
-
out.
|
|
121
|
+
out.append(attr )
|
|
121
122
|
return out
|
|
122
123
|
|
|
123
124
|
|
|
124
125
|
|
|
126
|
+
class pyAttributeGroup(CacheableAttributeGroup):
|
|
127
|
+
attributes:dict[str,pyAttribute]
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def from_attribute_group(attribute_group:AttributeGroup):
|
|
131
|
+
data = vars(attribute_group).copy()
|
|
132
|
+
data["attributes"] = {a.key: a for a in pyAttributes.from_payload_attributes(attribute_group.attributes)}
|
|
133
|
+
return pyAttributeGroup(**data)
|
|
@@ -60,7 +60,7 @@ class AttributeServerRequestHandler():
|
|
|
60
60
|
raise InvalidRequestError
|
|
61
61
|
attributes_for_pac_id = []
|
|
62
62
|
referenced_pac_ids = set()
|
|
63
|
-
for pac_url in r.
|
|
63
|
+
for pac_url in r.pac_ids:
|
|
64
64
|
attributes_for_pac = self._get_attributes_for_pac_id(pac_url=pac_url,
|
|
65
65
|
restrict_to_attribute_groups = r.restrict_to_attribute_groups)
|
|
66
66
|
attributes_for_pac_id.append(attributes_for_pac)
|
|
@@ -103,7 +103,7 @@ class AttributeServerRequestHandler():
|
|
|
103
103
|
traceback.print_exc()
|
|
104
104
|
raise e
|
|
105
105
|
|
|
106
|
-
return AttributesOfPACID(
|
|
106
|
+
return AttributesOfPACID(pac_id=pac_url, # return the pac_url as given, i.e. with the extension if there was one
|
|
107
107
|
attribute_groups=attribute_groups)
|
|
108
108
|
|
|
109
109
|
|
|
@@ -120,44 +120,6 @@ class AttributeServerRequestHandler():
|
|
|
120
120
|
pass
|
|
121
121
|
return referenced_pacs
|
|
122
122
|
|
|
123
|
-
# def get_translations(self, attributes_for_pac_id):
|
|
124
|
-
# ontology_map = {} # ontology name → list of TermTranslations
|
|
125
|
-
|
|
126
|
-
# for ag_for_pac in attributes_for_pac_id:
|
|
127
|
-
# for ag in ag_for_pac.attribute_groups:
|
|
128
|
-
# ag: AttributeGroup
|
|
129
|
-
# ontology_name = ag.ontology
|
|
130
|
-
# translation_data_source: TranslationDataSource = self._translation_data_sources.get(ontology_name, {})
|
|
131
|
-
|
|
132
|
-
# attribute_keys = [a.key for a in ag.attributes]
|
|
133
|
-
# all_keys = set([ag.key] + attribute_keys)
|
|
134
|
-
|
|
135
|
-
# for k in all_keys:
|
|
136
|
-
# t = translation_data_source.get_translations_for(k)
|
|
137
|
-
# if t:
|
|
138
|
-
# ontology_map.setdefault(ontology_name, []).append(t)
|
|
139
|
-
|
|
140
|
-
# translations_by_ontology = [ TranslationsForOntology(ontology=name, terms=terms) for name, terms in ontology_map.items()
|
|
141
|
-
# ]
|
|
142
|
-
# return translations_by_ontology
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
# def _get_display_name_for_key(self, key, requested_languages:str):
|
|
146
|
-
# for tds in self._translation_data_sources:
|
|
147
|
-
# if term := tds.get_translations_for(key):
|
|
148
|
-
# # try the languages requested by the user
|
|
149
|
-
# for l in requested_languages:
|
|
150
|
-
# if dn := term.in_language(l):
|
|
151
|
-
# return dn
|
|
152
|
-
# # remove the country codes and try the again
|
|
153
|
-
# for l_fallback in [l.split('-')[0] for l in requested_languages]:
|
|
154
|
-
# if dn := term.in_language(l_fallback):
|
|
155
|
-
# return dn
|
|
156
|
-
# # use the server fallback language
|
|
157
|
-
# if dn := term.in_language(self._default_language):
|
|
158
|
-
# return
|
|
159
|
-
# warnings.warn(f'No translation for {key}')
|
|
160
|
-
# return None
|
|
161
123
|
|
|
162
124
|
def _add_display_names(self, attributes_of_pac:AttributesOfPACID, language:str) -> str:
|
|
163
125
|
'''
|
|
@@ -181,9 +143,6 @@ class AttributeServerRequestHandler():
|
|
|
181
143
|
|
|
182
144
|
|
|
183
145
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
146
|
def _get_display_name_for_key(self, key, language:str):
|
|
188
147
|
'''call this only with a language you know there is a translation for'''
|
|
189
148
|
for tds in self._translation_data_sources:
|
|
@@ -211,10 +170,10 @@ class AttributeServerRequestHandler():
|
|
|
211
170
|
|
|
212
171
|
|
|
213
172
|
|
|
214
|
-
def capabilities(self):
|
|
173
|
+
def capabilities(self) -> ServerCapabilities:
|
|
215
174
|
return ServerCapabilities(supported_languages=self._supported_languages,
|
|
216
175
|
default_language=self._default_language,
|
|
217
|
-
available_attribute_groups= [ds.attribute_group_key for ds in self._attribute_group_data_sources])
|
|
176
|
+
available_attribute_groups= [ds.attribute_group_key for ds in self._attribute_group_data_sources])
|
|
218
177
|
|
|
219
178
|
|
|
220
179
|
|
|
@@ -6,6 +6,6 @@ class MetaAttributeKeys(Enum):
|
|
|
6
6
|
IMAGE = "https://schema.org/image"
|
|
7
7
|
ALIAS = "https://schema.org/alternateName"
|
|
8
8
|
DESCRIPTION = "https://schema.org/description"
|
|
9
|
-
GROUPKEY = "https://labfreed.org/
|
|
9
|
+
GROUPKEY = "https://labfreed.org/terms/attribute_group_metadata"
|
|
10
10
|
|
|
11
11
|
|
|
@@ -44,14 +44,14 @@ class Category(LabFREED_BaseModel):
|
|
|
44
44
|
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)])
|
|
45
45
|
return s
|
|
46
46
|
|
|
47
|
-
def segments_as_dict(self):
|
|
47
|
+
def segments_as_dict(self, include_alias=False):
|
|
48
48
|
''' returns the segments in a dict, with nice keys and values'''
|
|
49
49
|
out = dict()
|
|
50
50
|
for field_name, field_info in self.model_fields.items():
|
|
51
51
|
if field_name =='additional_segments':
|
|
52
52
|
continue
|
|
53
53
|
if v := getattr(self, field_name):
|
|
54
|
-
if field_info.alias:
|
|
54
|
+
if field_info.alias and include_alias:
|
|
55
55
|
k = f"{field_name} ({ field_info.alias})"
|
|
56
56
|
else:
|
|
57
57
|
k = f"{field_name}"
|
labfreed/pac_id/url_parser.py
CHANGED
|
@@ -82,7 +82,7 @@ class PAC_Parser():
|
|
|
82
82
|
@classmethod
|
|
83
83
|
def _parse_pac_id(cls,id_str:str) -> "PAC_ID":
|
|
84
84
|
# m = re.match('(HTTPS://)?(PAC.)?(?P<issuer>.+?\..+?)/(?P<identifier>.*)', id_str)
|
|
85
|
-
m = re.match('(HTTPS://)?(PAC.)?(?P<issuer>.+?)/(?P<identifier>.*)', id_str)
|
|
85
|
+
m = re.match('(HTTPS://)?(PAC.)?(?P<issuer>.+?)/(?P<identifier>.*)', id_str, re.IGNORECASE)
|
|
86
86
|
if not m:
|
|
87
87
|
raise LabFREED_ValidationError(f'{id_str} does not match the pattern expected for PAC-ID')
|
|
88
88
|
d = m.groupdict()
|
|
@@ -67,10 +67,10 @@ class PAC_ID_Resolver():
|
|
|
67
67
|
def resolve(self, pac_id:PAC_ID|str, check_service_status=True, use_issuer_cit=True) -> list[ServiceGroup]:
|
|
68
68
|
'''Resolve a PAC-ID'''
|
|
69
69
|
if isinstance(pac_id, str):
|
|
70
|
-
pac_id = PAC_CAT.from_url(pac_id)
|
|
71
70
|
pac_id_catless = PAC_ID.from_url(pac_id, try_pac_cat=False)
|
|
71
|
+
pac_id = PAC_CAT.from_url(pac_id)
|
|
72
72
|
|
|
73
|
-
# it's likely to
|
|
73
|
+
# it's likely to
|
|
74
74
|
if isinstance(pac_id, PAC_ID):
|
|
75
75
|
pac_id_catless = PAC_ID.from_url(pac_id.to_url(), try_pac_cat=False)
|
|
76
76
|
else:
|
labfreed/trex/pythonic/pyTREX.py
CHANGED
|
@@ -215,7 +215,7 @@ def _trex_value_to_python_type(v):
|
|
|
215
215
|
|
|
216
216
|
elif isinstance(v,DateValue):
|
|
217
217
|
d = v._date_time_dict
|
|
218
|
-
if d.get('year') and d.get('hour'): # input is only a time
|
|
218
|
+
if d.get('year') and d.get('hour') is not None: # input is only a time
|
|
219
219
|
return datetime(**d)
|
|
220
220
|
elif d.get('year'):
|
|
221
221
|
return date(**d)
|
|
@@ -24,11 +24,12 @@ class Quantity(BaseModel):
|
|
|
24
24
|
#dimensionless_unit
|
|
25
25
|
unit:str= d.get('unit')
|
|
26
26
|
if unit and unit in ['1', '', 'dimensionless']:
|
|
27
|
-
|
|
27
|
+
unit = None
|
|
28
|
+
d['unit'] = unit
|
|
28
29
|
|
|
29
30
|
#try to coerce to ucum. catch the two most likely mistakes to use blanks for multiplication and ^ for exponents.
|
|
30
31
|
if unit:
|
|
31
|
-
unit = unit.replace('/ ', '/').replace(' /', '/').replace(' ', '.').replace('^', '')
|
|
32
|
+
unit = unit.replace('/ ', '/').replace(' /', '/').replace(' ', '.').replace('^', '').replace('·','.')
|
|
32
33
|
d['unit'] = unit
|
|
33
34
|
|
|
34
35
|
return d
|
|
@@ -122,8 +123,9 @@ class Quantity(BaseModel):
|
|
|
122
123
|
|
|
123
124
|
def __str__(self):
|
|
124
125
|
unit_symbol = self.unit
|
|
125
|
-
if self.unit
|
|
126
|
+
if self.unit in [ "1", "dimensionless"] or not self.unit:
|
|
126
127
|
unit_symbol = ""
|
|
128
|
+
unit_symbol = unit_symbol.replace('.', '·')
|
|
127
129
|
val = self.value_as_str()
|
|
128
130
|
return f"{val} {unit_symbol}"
|
|
129
131
|
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
labfreed/__init__.py,sha256=
|
|
1
|
+
labfreed/__init__.py,sha256=o2qxE39XObi_KwNFEMO1fKgVTLltzbQn3tl5YQnhFXg,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=1VMc_Vtjwof9hwzSG95KuIUjT1h7uNsWf_lTORYFFuQ,3981
|
|
4
4
|
labfreed/labfreed_extended/app/formatted_print.py,sha256=DcwWP0ix1e_wYNIdceIp6cETkJdG2DqpU8Gs3aZAL40,1930
|
|
5
|
-
labfreed/labfreed_extended/app/pac_info.py,sha256=
|
|
5
|
+
labfreed/labfreed_extended/app/pac_info.py,sha256=oiU940sUJiV3yfNLOmkCsn7Go4qui_jijULeHTOFgrg,4163
|
|
6
6
|
labfreed/pac_attributes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
labfreed/pac_attributes/well_knonw_attribute_keys.py,sha256=
|
|
8
|
-
labfreed/pac_attributes/api_data_models/request.py,sha256
|
|
9
|
-
labfreed/pac_attributes/api_data_models/response.py,sha256=
|
|
7
|
+
labfreed/pac_attributes/well_knonw_attribute_keys.py,sha256=axE81MeJ3G_Wy1PbmNAXH6SfPtl96NXvQJMyrvK10t4,324
|
|
8
|
+
labfreed/pac_attributes/api_data_models/request.py,sha256=-CI3rU_Bzw2DZGSS06Jl4zajrxMkfPGhKHWmIfnmWlk,1868
|
|
9
|
+
labfreed/pac_attributes/api_data_models/response.py,sha256=4VliJuKM_r-J-xaLEqfcdW3JTe2BGG_hQSCaHPG3-xM,5726
|
|
10
10
|
labfreed/pac_attributes/api_data_models/server_capabilities_response.py,sha256=ypDm4f8xZZl036fp8PuIe6lJHNW5Zg1fItgUlnV75V0,178
|
|
11
11
|
labfreed/pac_attributes/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
labfreed/pac_attributes/client/attribute_cache.py,sha256=eWFy7h-T6gd25ENj9pLvSadNFRrzzIineqBoov2cGyw,2688
|
|
13
|
-
labfreed/pac_attributes/client/client.py,sha256=
|
|
14
|
-
labfreed/pac_attributes/pythonic/attribute_server_factory.py,sha256=
|
|
13
|
+
labfreed/pac_attributes/client/client.py,sha256=Sfvbr8WMW8NOL6_tkWFvU0IybKnzyC0XydmcEFUyb5s,7102
|
|
14
|
+
labfreed/pac_attributes/pythonic/attribute_server_factory.py,sha256=_wasafjBlwvzOaM6-uPgqPethsDQHEpaXoiRW7w9aV0,5759
|
|
15
15
|
labfreed/pac_attributes/pythonic/excel_attribute_data_source.py,sha256=mS310M3UOKuv00mJsRAK7acfTdyOw9c-_9UHXKuAqvs,6923
|
|
16
|
-
labfreed/pac_attributes/pythonic/py_attributes.py,sha256=
|
|
16
|
+
labfreed/pac_attributes/pythonic/py_attributes.py,sha256=LoPSH5DWdPTKq-3d2CT-7tTfkqYN9s53sNEeSq_6fHg,5615
|
|
17
17
|
labfreed/pac_attributes/pythonic/py_dict_data_source.py,sha256=nAz6GA7Xx_0IORPPpt_Wl3sFJa1Q5Fnq5vdf1uQiJF8,531
|
|
18
18
|
labfreed/pac_attributes/server/__init__.py,sha256=JvQ2kpQx62OUwP18bGhOWYU9an_nQW59Y8Lh7HyfVxY,301
|
|
19
19
|
labfreed/pac_attributes/server/attribute_data_sources.py,sha256=bCFqozKBEVdR8etRJjG9RCE5Uea9SMudPN4Mwh-iQr4,2083
|
|
20
|
-
labfreed/pac_attributes/server/server.py,sha256=
|
|
20
|
+
labfreed/pac_attributes/server/server.py,sha256=_Rzi_vzX02o0g03lbm-fdg5AJHJnESDWD7cJEKRFs8w,8841
|
|
21
21
|
labfreed/pac_attributes/server/translation_data_sources.py,sha256=axALOqfP840sOSdVCRYtrens97mm-hpfONMUyuVlCrY,2145
|
|
22
22
|
labfreed/pac_cat/__init__.py,sha256=KNPtQzBD1XVohvG_ucOs7RJj-oi6biUTGB1k-T2o6pk,568
|
|
23
|
-
labfreed/pac_cat/category_base.py,sha256=
|
|
23
|
+
labfreed/pac_cat/category_base.py,sha256=wVejYgDnUIUQ71q05ctophtZY2w9fD-PZ0xk3wUAEkQ,2522
|
|
24
24
|
labfreed/pac_cat/pac_cat.py,sha256=wcb_fhvgjS2xmqTsxS8_Oibvr1nsQt5zr8aUajLfK1E,5578
|
|
25
25
|
labfreed/pac_cat/predefined_categories.py,sha256=5wnMCj-CrACV2W4lH13w7qynWIwi506G3uLNcxuJQGg,8832
|
|
26
26
|
labfreed/pac_id/__init__.py,sha256=NGMbzkwQ4txKeT5pxdIZordwHO8J3_q84jzPanjKoHg,675
|
|
27
27
|
labfreed/pac_id/extension.py,sha256=NgLexs1LbRMMm4ETrn5m4EY2iWoMDgOTb0UV556jatQ,2227
|
|
28
28
|
labfreed/pac_id/id_segment.py,sha256=r5JU1SJuRXhZJJxy5T3xjrb598wIDTLpivSJhIUAzjQ,4526
|
|
29
29
|
labfreed/pac_id/pac_id.py,sha256=DDcSYJ8DBWqIoW_usOT7eDjHZ9700cTYxeUgenHluOA,5378
|
|
30
|
-
labfreed/pac_id/url_parser.py,sha256=
|
|
30
|
+
labfreed/pac_id/url_parser.py,sha256=F3SPiscfbPwZ0uMzgirJ1vwgaXclN546lBW46Ywo3nk,5979
|
|
31
31
|
labfreed/pac_id/url_serializer.py,sha256=01LB30pNMBtv2rYHsiE_4Ga2iVA515Boj4ikOIYhiBQ,3511
|
|
32
32
|
labfreed/pac_id_resolver/__init__.py,sha256=RNBlrDOSR42gmSNH9wJVhK_xwEX45cvTKVgWW2bjh7Q,113
|
|
33
33
|
labfreed/pac_id_resolver/cit_common.py,sha256=jzoDOxog8YW68q7vyvDGCZcVcgIzJHXlMt8KwgVnx6o,2885
|
|
34
34
|
labfreed/pac_id_resolver/cit_v1.py,sha256=TVvOWJA6-wmBkwzoHBqNuwV-tndRTSKAK07k56eoJBU,9326
|
|
35
|
-
labfreed/pac_id_resolver/cit_v2.py,sha256=
|
|
36
|
-
labfreed/pac_id_resolver/resolver.py,sha256=
|
|
35
|
+
labfreed/pac_id_resolver/cit_v2.py,sha256=iDEvUb9A3ocX_ijewTIK3p951CIESnKyI7upjrd3Vjw,11842
|
|
36
|
+
labfreed/pac_id_resolver/resolver.py,sha256=IxI57XRZfGHZigym6_f5tnt3ycwU5LnfXs5n1D9pPDs,3077
|
|
37
37
|
labfreed/pac_id_resolver/services.py,sha256=vtnxLm38t4PNOf73cXh6UZOtWZZOGxfBCfXUDRxGHog,2592
|
|
38
38
|
labfreed/qr/__init__.py,sha256=fdKwP6W2Js--yMbBUdn-g_2uq2VqPpfQJeDLHsMDO-Y,61
|
|
39
39
|
labfreed/qr/generate_qr.py,sha256=mSt-U872O3ReHB_UdS-MzYu0wRgdlKcAOEfTxg5CLRk,16616
|
|
@@ -44,8 +44,8 @@ labfreed/trex/trex_base_models.py,sha256=591559BISc82fyIoEP_dq_GHDbPCVzApx7FM3TR
|
|
|
44
44
|
labfreed/trex/value_segments.py,sha256=_fbDHDRfo7pxIWpzcbyjppv1VNd8Tnd-VXhbd6igI8g,3698
|
|
45
45
|
labfreed/trex/pythonic/__init__.py,sha256=dyAQG7t-uYN6VfGXbWIq2bHxGcGI63l7FS2-VPYs2RQ,137
|
|
46
46
|
labfreed/trex/pythonic/data_table.py,sha256=KGyA5hRdDOOkJB0EOyjmzbbMusgYkBB_2SIFzrJ25yI,3148
|
|
47
|
-
labfreed/trex/pythonic/pyTREX.py,sha256=
|
|
48
|
-
labfreed/trex/pythonic/quantity.py,sha256=
|
|
47
|
+
labfreed/trex/pythonic/pyTREX.py,sha256=wWYYb9pyZB01ObGOHf8gXzvMyAGzuCTduHzP03yvKj8,10157
|
|
48
|
+
labfreed/trex/pythonic/quantity.py,sha256=jmhHfmEclYBQ1cLmOtmV7Ull4S0qHKIYVE0uhtNFFz0,5441
|
|
49
49
|
labfreed/utilities/base36.py,sha256=_yX8aQ1OwrK5tnJU1NUEzQSFGr9xAVnNvPObpNzCPYs,2895
|
|
50
50
|
labfreed/utilities/ensure_utc_time.py,sha256=1ZTTzyIt7IimQ4ArTzdgw5hxiabkkplltbQe3Wdt2ZQ,307
|
|
51
51
|
labfreed/utilities/translations.py,sha256=XY4Wud_BfXswUOpebdh0U_D2bMzb2vqluuGWzFK-3uU,1851
|
|
@@ -59,7 +59,7 @@ labfreed/well_known_keys/labfreed/well_known_keys.py,sha256=p-hXwEEIs7p2SKn9DQeL
|
|
|
59
59
|
labfreed/well_known_keys/unece/UneceUnits.json,sha256=kwfQSp_nTuWbADfBBgqTWrvPl6XtM5SedEVLbMJrM7M,898953
|
|
60
60
|
labfreed/well_known_keys/unece/__init__.py,sha256=MSP9lmjg9_D9iqG9Yq2_ajYfQSNS9wIT7FXA1c--59M,122
|
|
61
61
|
labfreed/well_known_keys/unece/unece_units.py,sha256=J20d64H69qKDE3XlGdJoXIIh0G-d0jKoiIDsg9an5pk,1655
|
|
62
|
-
labfreed-0.
|
|
63
|
-
labfreed-0.
|
|
64
|
-
labfreed-0.
|
|
65
|
-
labfreed-0.
|
|
62
|
+
labfreed-1.0.0a2.dist-info/licenses/LICENSE,sha256=gHFOv9FRKHxO8cInP3YXyPoJnuNeqrvcHjaE_wPSsQ8,1100
|
|
63
|
+
labfreed-1.0.0a2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
64
|
+
labfreed-1.0.0a2.dist-info/METADATA,sha256=5puhyvihc2xCJlVdQjfPAXbwsllH_x4LmYBz-xzgM-E,19740
|
|
65
|
+
labfreed-1.0.0a2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|