labfreed 0.3.1a6__py3-none-any.whl → 1.0.0a3__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 CHANGED
@@ -2,7 +2,7 @@
2
2
  Python implementation of LabFREED building blocks
3
3
  '''
4
4
 
5
- __version__ = "0.3.1a6"
5
+ __version__ = "1.0.0a3"
6
6
 
7
7
  from labfreed.pac_id import * # noqa: F403
8
8
  from labfreed.pac_cat import * # noqa: F403
@@ -5,7 +5,7 @@ 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, attribute_request_default_callback_factory
8
+ from labfreed.pac_attributes.client.client import AttributeClient, http_attribute_request_default_callback_factory
9
9
  from labfreed.pac_attributes.pythonic.py_attributes import pyAttributeGroup
10
10
  from labfreed.pac_attributes.well_knonw_attribute_keys import MetaAttributeKeys
11
11
  from labfreed.well_known_extensions.display_name_extension import DisplayNameExtension
@@ -30,7 +30,7 @@ class Labfreed_App_Infrastructure():
30
30
  if not http_client:
31
31
  http_client = requests.Session()
32
32
  self._http_client= http_client
33
- callback = attribute_request_default_callback_factory(http_client)
33
+ callback = http_attribute_request_default_callback_factory(http_client)
34
34
 
35
35
  self._attribute_client = AttributeClient(http_post_callback=callback, cache_store=MemoryAttributeCache())
36
36
 
@@ -1,5 +1,6 @@
1
1
 
2
2
 
3
+ from functools import cached_property
3
4
  from pydantic import BaseModel, Field
4
5
  from labfreed.pac_attributes.pythonic.py_attributes import pyAttribute, pyAttributeGroup, pyAttributes
5
6
  from labfreed.pac_attributes.well_knonw_attribute_keys import MetaAttributeKeys
@@ -7,6 +8,7 @@ from labfreed.pac_cat.pac_cat import PAC_CAT
7
8
  from labfreed.pac_id.pac_id import PAC_ID
8
9
  from labfreed.pac_id_resolver.services import ServiceGroup
9
10
  from labfreed.labfreed_extended.app.formatted_print import StringIOLineBreak
11
+ from labfreed.trex.pythonic.pyTREX import pyTREX
10
12
  from labfreed.well_known_extensions.display_name_extension import DisplayNameExtension
11
13
 
12
14
 
@@ -29,7 +31,8 @@ class PacInfo(BaseModel):
29
31
 
30
32
  @property
31
33
  def attached_data(self):
32
- 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
+
33
36
 
34
37
  @property
35
38
  def summary(self):
@@ -50,13 +53,25 @@ class PacInfo(BaseModel):
50
53
  dn = DisplayNameExtension.from_extension(dn)
51
54
  display_name = dn.display_name or ""
52
55
  # there can be a display name in attributes, too
53
- if meta := self.attributes.get(MetaAttributeKeys.GROUPKEY.value):
54
- dn_attr = meta.attributes.get(MetaAttributeKeys.DISPLAYNAME.value)
55
- if dn_attr:
56
- dn = dn_attr.value
57
- display_name += f' ( aka {dn} )'
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
58
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
59
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
60
75
 
61
76
 
62
77
 
@@ -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
- pac_urls: list[str]
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.pac_urls) > 100:
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.pac_urls:
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
- magnitude: str
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.magnitude
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
- pac_url: str
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 attribute_request_default_callback_factory(session: requests.Session = None) -> AttributeRequestCallback:
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:
@@ -55,6 +56,25 @@ def attribute_request_default_callback_factory(session: requests.Session = None)
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(pac_urls=[pac_id.to_url()],
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
  )
@@ -124,8 +144,9 @@ class AttributeClient():
124
144
 
125
145
 
126
146
  # update cache
147
+ attribute_groups_out = []
127
148
  for ag_for_pac in r.pac_attributes:
128
- pac = PAC_ID.from_url(ag_for_pac.pac_url)
149
+ pac_from_response = PAC_ID.from_url(ag_for_pac.pac_id)
129
150
  ags = [
130
151
  CacheableAttributeGroup(
131
152
  key= ag.key,
@@ -136,13 +157,15 @@ class AttributeClient():
136
157
  state_of=ag.state_of)
137
158
  for ag in ag_for_pac.attribute_groups
138
159
  ]
139
- self.cache_store.update(server_url, pac, ags)
160
+ self.cache_store.update(server_url, pac_from_response, ags)
140
161
 
141
- if pac_id == pac:
162
+ # compare pac_id from response with pac_id we need attributes for.
163
+ # if identical this is the part of the response we care about. other PAC-ID are just for the cache
164
+ if pac_id.to_url() == pac_from_response.to_url():
142
165
  attribute_groups_out = ags
143
- return attribute_groups_out
144
- else:
145
- return []
166
+
167
+ return attribute_groups_out
168
+
146
169
 
147
170
 
148
171
 
@@ -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)
@@ -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("/capabilities", methods=["GET"], strict_slashes=False)
92
+ @bp.route("/", methods=["GET"], strict_slashes=False)
90
93
  def capabilities():
91
- return request_handler.capabilities()
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
 
@@ -10,6 +10,7 @@ from cachetools import TTLCache, cached
10
10
  from labfreed.pac_attributes.api_data_models.response import AttributeGroup
11
11
  from labfreed.pac_attributes.pythonic.py_attributes import pyAttribute, pyAttributes
12
12
  from labfreed.pac_attributes.server.server import AttributeGroupDataSource
13
+ from labfreed.pac_cat.pac_cat import PAC_CAT
13
14
 
14
15
  try:
15
16
  from openpyxl import load_workbook
@@ -74,8 +75,9 @@ class _BaseExcelAttributeDataSource(AttributeGroupDataSource):
74
75
  Subclasses implement `_read_rows_and_last_changed()`.
75
76
  """
76
77
 
77
- def __init__(self, *, base_url: str = "", cache_duration_seconds: int = 0, **kwargs):
78
+ def __init__(self, *, base_url: str = "", cache_duration_seconds: int = 0, uses_pac_cat_short_form:bool=True, **kwargs):
78
79
  self._base_url = base_url
80
+ self._uses_pac_cat_short_form = uses_pac_cat_short_form
79
81
  # allow instance-level TTL override
80
82
  try:
81
83
  _cache.ttl = int(cache_duration_seconds)
@@ -96,9 +98,14 @@ class _BaseExcelAttributeDataSource(AttributeGroupDataSource):
96
98
  return []
97
99
  return [self._base_url + r for r in rows[0][1:]]
98
100
 
99
- def attributes(self, pac_url: str) -> Optional[AttributeGroup]:
100
- if not self._include_extensions:
101
- pac_url = pac_url.split('*')[0]
101
+ def attributes(self, pac_url:str) -> Optional[AttributeGroup]:
102
+ try:
103
+ p = PAC_CAT.from_url(pac_url)
104
+ pac_url = p.to_url(use_short_notation=self._uses_pac_cat_short_form, include_extensions=self._include_extensions)
105
+ print(f'Lookup in Excel of {pac_url}')
106
+ except:
107
+ ... # might as well try to match the original input
108
+
102
109
  rows, last_changed = self._read_rows_and_last_changed()
103
110
  d = _get_row_by_first_cell(rows, pac_url, self._base_url)
104
111
  if not d:
@@ -55,7 +55,7 @@ class pyAttributes(RootModel[list[pyAttribute]]):
55
55
  elif isinstance(attribute.value, Quantity|int|float):
56
56
  if not isinstance(attribute.value, Quantity):
57
57
  value = Quantity(value=attribute.value, unit='dimensionless')
58
- num_attribute = NumericAttribute(value = NumericValue(magnitude=value.value_as_str(),
58
+ num_attribute = NumericAttribute(value = NumericValue(numerical_value=value.value_as_str(),
59
59
  unit = value.unit),
60
60
  **common_args)
61
61
  num_attribute.print_validation_messages()
@@ -64,7 +64,7 @@ class pyAttributes(RootModel[list[pyAttribute]]):
64
64
  elif isinstance(value, str):
65
65
  # capture quantities in the form of "100.0e5 g/L"
66
66
  if q := Quantity.from_str_with_unit(value):
67
- return NumericAttribute(value = NumericValue(magnitude=q.value_as_str(),
67
+ return NumericAttribute(value = NumericValue(numerical_value=q.value_as_str(),
68
68
  unit = q.unit),
69
69
  **common_args)
70
70
  else:
@@ -96,7 +96,7 @@ class pyAttributes(RootModel[list[pyAttribute]]):
96
96
  value = pyReference(a.value)
97
97
 
98
98
  case NumericAttribute():
99
- value = Quantity.from_str_value(value=a.value.magnitude, unit=a.value.unit)
99
+ value = Quantity.from_str_value(value=a.value.numerical_value, unit=a.value.unit)
100
100
 
101
101
  case BoolAttribute():
102
102
  value = a.value
@@ -1,6 +1,8 @@
1
1
  from abc import ABC, abstractmethod, abstractproperty
2
2
  from datetime import datetime, timezone
3
3
  from labfreed.pac_attributes.api_data_models.response import VALID_FOREVER, AttributeBase, AttributeGroup
4
+ from labfreed.pac_cat.pac_cat import PAC_CAT
5
+ from labfreed.pac_id.pac_id import PAC_ID
4
6
 
5
7
 
6
8
  class AttributeGroupDataSource(ABC):
@@ -29,12 +31,13 @@ class AttributeGroupDataSource(ABC):
29
31
 
30
32
 
31
33
  class Dict_DataSource(AttributeGroupDataSource):
32
- def __init__(self, data:dict[str, list[AttributeBase]], *args, **kwargs):
34
+ def __init__(self, data:dict[str, list[AttributeBase]], uses_pac_cat_short_form=True, *args, **kwargs):
33
35
  if not all([isinstance(e, list) for e in data.values()]):
34
36
  raise ValueError('Invalid data')
35
37
 
36
38
  self._data = data
37
39
  self._state_of = datetime.now(tz=timezone.utc)
40
+ self.uses_pac_cat_short_form = uses_pac_cat_short_form
38
41
 
39
42
  super().__init__(*args, **kwargs)
40
43
 
@@ -45,9 +48,12 @@ class Dict_DataSource(AttributeGroupDataSource):
45
48
 
46
49
 
47
50
  def attributes(self, pac_url: str) -> AttributeGroup:
48
- if not self._include_extensions:
49
- pac_url = pac_url.split('*')[0]
50
-
51
+ try:
52
+ p = PAC_CAT.from_url(pac_url)
53
+ pac_url = p.to_url(use_short_notation=self.uses_pac_cat_short_form, include_extensions=self._include_extensions)
54
+ except:
55
+ ... # might as well try to match the original input
56
+
51
57
  attributes = self._data.get(pac_url)
52
58
  if not attributes:
53
59
  return None
@@ -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.pac_urls:
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(pac_url=pac_url, # return the pac_url as given, i.e. with the extension if there was one
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]).model_dump_json()
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/attribute_metadata_group"
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}"
@@ -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()
@@ -105,6 +105,7 @@ class CITBlock_v2(LabFREED_BaseModel):
105
105
 
106
106
 
107
107
  class CIT_v2(LabFREED_BaseModel):
108
+ schema_version: str = Field(default='2.0')
108
109
  '''Coupling Information Table (CIT)'''
109
110
  origin: str = ''
110
111
  model_config = {
@@ -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
- d['unit'] = None
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 == "dimensionless" or not 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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labfreed
3
- Version: 0.3.1a6
3
+ Version: 1.0.0a3
4
4
  Summary: Python implementation of LabFREED building blocks
5
5
  Author-email: Reto Thürer <thuerer.r@buchi.com>
6
6
  Requires-Python: >=3.11
@@ -1,38 +1,38 @@
1
- labfreed/__init__.py,sha256=v2zpLGZIaAHFZ8Sr8kdTFNRkliSQeGzUbZcflCQIXHI,338
1
+ labfreed/__init__.py,sha256=feEsSamwMYNQXaiqTt0oe0dEoLnkS-SJ5-YIdMtkerE,338
2
2
  labfreed/labfreed_infrastructure.py,sha256=YZmU-kgopyB1tvpTR_k_uIt1Q2ezexMrWvu-HaP65IE,10104
3
- labfreed/labfreed_extended/app/app_infrastructure.py,sha256=oqinlNwJzyo-yzENyW_rwKd5btrf-26nDuigxftOHi0,3971
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=mQ9B39-P-v-RLumyQeZ0RkYje16R7frVx-f_jz6tBrU,3591
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=ruHawnr1AffuhdKz1j1Nx67QphvcpmWsObgZhkuwkH0,318
8
- labfreed/pac_attributes/api_data_models/request.py,sha256=x-GuFYhCIpqAKa1AHs968OfD8O2cJKg14YUw2hTKVg4,1871
9
- labfreed/pac_attributes/api_data_models/response.py,sha256=czqQ59myLsN4WNgCdI_dnxRG8hSw4YI062XOPydfePU,5715
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=uw06sM8TlWoG8J1Ve_ybrtbJiBPvNDQ2mKD2aLNJfWk,6297
14
- labfreed/pac_attributes/pythonic/attribute_server_factory.py,sha256=PkPVT40iA2n_ysw7893Y0clil11n7rqojTgIZv8vxnM,3888
15
- labfreed/pac_attributes/pythonic/excel_attribute_data_source.py,sha256=mS310M3UOKuv00mJsRAK7acfTdyOw9c-_9UHXKuAqvs,6923
16
- labfreed/pac_attributes/pythonic/py_attributes.py,sha256=vo9UpI5wYemVz1HK2v4L94N4Hx444ubl15SDNxmL0uY,5597
13
+ labfreed/pac_attributes/client/client.py,sha256=J606oYqcJ98NI0w1bSoahys5t7Wxy4CXjJVGUXyj2Zs,7347
14
+ labfreed/pac_attributes/pythonic/attribute_server_factory.py,sha256=_wasafjBlwvzOaM6-uPgqPethsDQHEpaXoiRW7w9aV0,5759
15
+ labfreed/pac_attributes/pythonic/excel_attribute_data_source.py,sha256=Mvn-uIMWZjQJfSOMVY244yCKpnYeR4btc9Pe4BL8_4M,7313
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
- labfreed/pac_attributes/server/attribute_data_sources.py,sha256=bCFqozKBEVdR8etRJjG9RCE5Uea9SMudPN4Mwh-iQr4,2083
20
- labfreed/pac_attributes/server/server.py,sha256=0dM9BmL0u_6g8O-v7_tPRUj6og2LwlhrcPiDPFdp5Bo,10772
19
+ labfreed/pac_attributes/server/attribute_data_sources.py,sha256=gfaERhFrn3SIoSNRiVzchuxpt2ttoe3gw0-fMmv12hU,2448
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=nFa-y1IiTnPV1xz3RibqkEINISvP045329z8QgbsJ-4,2483
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=9_VHqklm7veknmZitKXw0-Yerl99bmeKYqnPUQPMDAw,5964
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=1Nywuv9cpROiOt7X326ePpgajPAe4F9nGqylqaZcsxY,11794
35
+ labfreed/pac_id_resolver/cit_v2.py,sha256=iDEvUb9A3ocX_ijewTIK3p951CIESnKyI7upjrd3Vjw,11842
36
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
@@ -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=WZPp8pZIrlibiLBz2vnXZoJOnxjrJ4Vll9eA-w5K_Ks,10145
48
- labfreed/trex/pythonic/quantity.py,sha256=CzK3kCXopTl11qOCirkaiThDvMN6m1FbpLMrsHy2NkI,5336
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.3.1a6.dist-info/licenses/LICENSE,sha256=gHFOv9FRKHxO8cInP3YXyPoJnuNeqrvcHjaE_wPSsQ8,1100
63
- labfreed-0.3.1a6.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
64
- labfreed-0.3.1a6.dist-info/METADATA,sha256=1IkquClbXfrujhLIpMdZ_HFFSE77MD9ZK5b_HZlOiyE,19740
65
- labfreed-0.3.1a6.dist-info/RECORD,,
62
+ labfreed-1.0.0a3.dist-info/licenses/LICENSE,sha256=gHFOv9FRKHxO8cInP3YXyPoJnuNeqrvcHjaE_wPSsQ8,1100
63
+ labfreed-1.0.0a3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
64
+ labfreed-1.0.0a3.dist-info/METADATA,sha256=y5M4I537vHyGZcYkVCEuFrOi4uMVLp74Yz21tne3WQM,19740
65
+ labfreed-1.0.0a3.dist-info/RECORD,,