labfreed 1.0.0a14__tar.gz → 1.0.0a16__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.

Files changed (75) hide show
  1. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/PKG-INFO +1 -1
  2. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/__init__.py +1 -1
  3. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/labfreed_extended/app/app_infrastructure.py +3 -2
  4. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/api_data_models/response.py +1 -0
  5. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/pythonic/excel_attribute_data_source.py +9 -3
  6. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/pythonic/py_attributes.py +50 -21
  7. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id_resolver/resolver.py +8 -5
  8. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id_resolver/resolver_config.py +1 -1
  9. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id_resolver/resolver_config_common.py +2 -1
  10. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/.github/workflows/pypi-publish.yml +0 -0
  12. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/.github/workflows/run-tests.yml +0 -0
  13. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/CHANGELOG.md +0 -0
  14. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/LICENSE +0 -0
  15. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/README.md +0 -0
  16. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/labfreed_extended/app/formatted_print.py +0 -0
  17. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/labfreed_extended/app/pac_info/html_renderer/external-link.svg +0 -0
  18. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/labfreed_extended/app/pac_info/html_renderer/macros.jinja.html +0 -0
  19. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/labfreed_extended/app/pac_info/html_renderer/pac-info-style.css +0 -0
  20. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/labfreed_extended/app/pac_info/html_renderer/pac_info.jinja.html +0 -0
  21. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/labfreed_extended/app/pac_info/html_renderer/pac_info_card.jinja.html +0 -0
  22. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/labfreed_extended/app/pac_info/pac_info.py +0 -0
  23. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/labfreed_infrastructure.py +0 -0
  24. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/__init__.py +0 -0
  25. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/api_data_models/request.py +0 -0
  26. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/api_data_models/server_capabilities_response.py +0 -0
  27. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/client/__init__.py +0 -0
  28. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/client/attribute_cache.py +0 -0
  29. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/client/client.py +0 -0
  30. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/pythonic/attribute_server_factory.py +0 -0
  31. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/pythonic/py_dict_data_source.py +0 -0
  32. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/server/__init__.py +0 -0
  33. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/server/attribute_data_sources.py +0 -0
  34. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/server/server.py +0 -0
  35. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/server/translation_data_sources.py +0 -0
  36. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_attributes/well_knonw_attribute_keys.py +0 -0
  37. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_cat/__init__.py +0 -0
  38. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_cat/category_base.py +0 -0
  39. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_cat/pac_cat.py +0 -0
  40. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_cat/predefined_categories.py +0 -0
  41. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id/__init__.py +0 -0
  42. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id/extension.py +0 -0
  43. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id/id_segment.py +0 -0
  44. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id/pac_id.py +0 -0
  45. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id/url_parser.py +0 -0
  46. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id/url_serializer.py +0 -0
  47. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id_resolver/__init__.py +0 -0
  48. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id_resolver/cit_v1.py +0 -0
  49. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/pac_id_resolver/services.py +0 -0
  50. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/qr/__init__.py +0 -0
  51. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/qr/generate_qr.py +0 -0
  52. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/trex/__init__.py +0 -0
  53. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/trex/pythonic/__init__.py +0 -0
  54. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/trex/pythonic/data_table.py +0 -0
  55. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/trex/pythonic/pyTREX.py +0 -0
  56. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/trex/pythonic/quantity.py +0 -0
  57. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/trex/table_segment.py +0 -0
  58. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/trex/trex.py +0 -0
  59. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/trex/trex_base_models.py +0 -0
  60. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/trex/value_segments.py +0 -0
  61. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/utilities/base36.py +0 -0
  62. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/utilities/ensure_utc_time.py +0 -0
  63. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/utilities/translations.py +0 -0
  64. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_extensions/__init__.py +0 -0
  65. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_extensions/default_extension_interpreters.py +0 -0
  66. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_extensions/display_name_extension.py +0 -0
  67. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_extensions/text_base36_extension.py +0 -0
  68. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_extensions/trex_extension.py +0 -0
  69. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_keys/gs1/__init__.py +0 -0
  70. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_keys/gs1/gs1_ai_enum_sorted.py +0 -0
  71. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_keys/labfreed/well_known_keys.py +0 -0
  72. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_keys/unece/UneceUnits.json +0 -0
  73. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_keys/unece/__init__.py +0 -0
  74. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/labfreed/well_known_keys/unece/unece_units.py +0 -0
  75. {labfreed-1.0.0a14 → labfreed-1.0.0a16}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labfreed
3
- Version: 1.0.0a14
3
+ Version: 1.0.0a16
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
@@ -2,7 +2,7 @@
2
2
  Python implementation of LabFREED building blocks
3
3
  '''
4
4
 
5
- __version__ = "1.0.0a14"
5
+ __version__ = "1.0.0a16"
6
6
 
7
7
  from labfreed.pac_id import * # noqa: F403
8
8
  from labfreed.pac_cat import * # noqa: F403
@@ -17,10 +17,11 @@ from labfreed.pac_id_resolver.services import ServiceGroup
17
17
 
18
18
 
19
19
  class Labfreed_App_Infrastructure():
20
- def __init__(self, markup = 'rich', language_preferences:list[str]|str='en', http_client:requests.Session|None=None):
20
+ def __init__(self, markup = 'rich', language_preferences:list[str]|str='en', http_client:requests.Session|None=None, use_issuer_resolver_config=True):
21
21
  if isinstance(language_preferences, str):
22
22
  language_preferences = [language_preferences]
23
23
  self._language_preferences = language_preferences
24
+ self._use_issuer_resolver_config = use_issuer_resolver_config
24
25
 
25
26
  self._resolver = PAC_ID_Resolver()
26
27
 
@@ -48,7 +49,7 @@ class Labfreed_App_Infrastructure():
48
49
  pac = PAC_ID.from_url(pac_url)
49
50
  else:
50
51
  pac = pac_url
51
- service_groups = self._resolver.resolve(pac, check_service_status=False)
52
+ service_groups = self._resolver.resolve(pac, check_service_status=False, use_issuer_resolver_config=self._use_issuer_resolver_config)
52
53
 
53
54
  pac_info = PacInfo(pac_id=pac)
54
55
 
@@ -121,6 +121,7 @@ class ResourceAttribute(AttributeBase):
121
121
  @model_validator(mode='after')
122
122
  def _validate_value(self):
123
123
  _validate_resource(self, self.value)
124
+ return self
124
125
 
125
126
  class ResourceListAttribute(AttributeBase):
126
127
  type: Literal["resource-list"]
@@ -75,9 +75,10 @@ class _BaseExcelAttributeDataSource(AttributeGroupDataSource):
75
75
  Subclasses implement `_read_rows_and_last_changed()`.
76
76
  """
77
77
 
78
- def __init__(self, *, base_url: str = "", cache_duration_seconds: int = 0, uses_pac_cat_short_form:bool=True, **kwargs):
78
+ def __init__(self, *, base_url: str = "", cache_duration_seconds: int = 0, uses_pac_cat_short_form:bool=True, pac_to_key=None, **kwargs):
79
79
  self._base_url = base_url
80
80
  self._uses_pac_cat_short_form = uses_pac_cat_short_form
81
+ self._pac_to_key = pac_to_key
81
82
  # allow instance-level TTL override
82
83
  try:
83
84
  _cache.ttl = int(cache_duration_seconds)
@@ -106,11 +107,16 @@ class _BaseExcelAttributeDataSource(AttributeGroupDataSource):
106
107
  except:
107
108
  ... # might as well try to match the original input
108
109
 
110
+ if f:= self._pac_to_key:
111
+ key = f(pac_url)
112
+ else:
113
+ key = pac_url
114
+
109
115
  rows, last_changed = self._read_rows_and_last_changed()
110
- d = _get_row_by_first_cell(rows, pac_url, self._base_url)
116
+ d = _get_row_by_first_cell(rows, key, self._base_url)
111
117
  if not d:
112
118
  return None
113
- attributes = [pyAttribute(key=k, value=v) for k, v in d.items()]
119
+ attributes = [pyAttribute(key=k, value=v) for k, v in d.items() if v is not None]
114
120
  return AttributeGroup(
115
121
  key=self._attribute_group_key,
116
122
  attributes=pyAttributes(attributes).to_payload_attributes()
@@ -1,12 +1,12 @@
1
1
 
2
- from datetime import date, datetime, time
2
+ from datetime import UTC, date, datetime, time
3
3
  import json
4
4
  from typing import Literal
5
5
  import warnings
6
6
  from pydantic import RootModel, field_validator
7
7
 
8
8
  from labfreed.labfreed_infrastructure import LabFREED_BaseModel
9
- from labfreed.pac_attributes.api_data_models.response import AttributeBase, AttributeGroup, BoolAttribute, DateTimeAttribute, NumericAttribute, NumericValue, ObjectAttribute, ReferenceAttribute, ResourceAttribute, TextAttribute
9
+ from labfreed.pac_attributes.api_data_models.response import AttributeBase, AttributeGroup, BoolAttribute, BoolListAttribute, DateTimeAttribute, DateTimeListAttribute, NumericAttribute, NumericListAttribute, NumericValue, ObjectAttribute, ReferenceAttribute, ReferenceListAttribute, ResourceAttribute, ResourceListAttribute, TextAttribute, TextListAttribute
10
10
  from labfreed.pac_attributes.client.attribute_cache import CacheableAttributeGroup
11
11
  from labfreed.pac_id.pac_id import PAC_ID
12
12
  from labfreed.trex.pythonic.quantity import Quantity
@@ -66,13 +66,21 @@ class pyAttributes(RootModel[list[pyAttribute]]):
66
66
  value_list = attribute.value_list
67
67
  first_value = value_list[0]
68
68
  if isinstance(first_value, bool):
69
- return BoolAttribute(value=value_list, **common_args)
69
+ if len(value_list) == 1:
70
+ return BoolAttribute(value=value_list[0], **common_args)
71
+ else:
72
+ return BoolListAttribute(value=value_list, **common_args)
73
+
70
74
 
71
75
  elif isinstance(first_value, datetime | date | time):
72
76
  for v in value_list:
73
77
  if not v.tzinfo:
74
78
  warnings.warn(f'No timezone given for {v}. Assuming it is in UTC.')
75
- return DateTimeAttribute(value=value_list, **common_args)
79
+ v.replace(tzinfo=UTC)
80
+ if len(value_list) == 1:
81
+ return DateTimeAttribute(value=value_list[0], **common_args)
82
+ else:
83
+ return DateTimeListAttribute(value=value_list, **common_args)
76
84
  # return DateTimeAttribute(value =_date_value_from_python_type(value).value, **common_args)
77
85
 
78
86
 
@@ -83,9 +91,13 @@ class pyAttributes(RootModel[list[pyAttribute]]):
83
91
  v = Quantity(value=v, unit='dimensionless')
84
92
  values.append(NumericValue(numerical_value=v.value_as_str(),
85
93
  unit = v.unit))
86
- num_attribute = NumericAttribute(value = values, **common_args)
87
- num_attribute.print_validation_messages()
88
- return num_attribute
94
+ if len(values) == 1:
95
+ num_attr = NumericAttribute(value=values[0], **common_args)
96
+ else:
97
+ num_attr = NumericListAttribute(value=values, **common_args)
98
+ num_attr.print_validation_messages()
99
+
100
+ return num_attr
89
101
 
90
102
  elif isinstance(first_value, str):
91
103
  # capture quantities in the form of "100.0e5 g/L"
@@ -94,20 +106,37 @@ class pyAttributes(RootModel[list[pyAttribute]]):
94
106
  for v in value_list:
95
107
  q = Quantity.from_str_with_unit(v)
96
108
  values.append( NumericValue(numerical_value=q.value_as_str(), unit = q.unit) )
97
- return NumericAttribute(value = values,
98
- **common_args)
99
-
109
+ if len(values) == 1:
110
+ return NumericAttribute(value=values[0], **common_args)
111
+ else:
112
+ return NumericListAttribute(value=values, **common_args)
113
+
100
114
  else:
101
- return TextAttribute(value = value_list, **common_args)
115
+ if len(value_list) == 1:
116
+ return TextAttribute(value=value_list[0], **common_args)
117
+ else:
118
+ return TextListAttribute(value=value_list, **common_args)
102
119
 
103
120
  elif isinstance(first_value, pyReference):
104
- return ReferenceAttribute(value = [v.root for v in value_list], **common_args)
121
+ values = [v.root for v in value_list]
122
+ if len(values) == 1:
123
+ return ReferenceAttribute(value=values[0], **common_args)
124
+ else:
125
+ return ReferenceListAttribute(value=values, **common_args)
105
126
 
106
127
  elif isinstance(first_value, pyResource):
107
- return ResourceAttribute(value = [v.root for v in value_list], **common_args)
128
+ values = [v.root for v in value_list]
129
+ if len(values) == 1:
130
+ return ResourceAttribute(value=values[0], **common_args)
131
+ else:
132
+ return ResourceListAttribute(value=values, **common_args)
108
133
 
109
134
  elif isinstance(first_value, PAC_ID):
110
- return ReferenceAttribute(value = [v.to_url(include_extensions=False) for v in value_list], **common_args)
135
+ values = [v.to_url(include_extensions=False) for v in value_list]
136
+ if len(values) == 1:
137
+ return ReferenceAttribute(value=values[0], **common_args)
138
+ else:
139
+ return ReferenceListAttribute(value=values, **common_args)
111
140
 
112
141
  else: #this covers the last resort case of arbitrary objects. Must be json serializable.
113
142
  try :
@@ -124,25 +153,25 @@ class pyAttributes(RootModel[list[pyAttribute]]):
124
153
  for a in attributes:
125
154
  value_list = a.value if isinstance(a.value, list) else [a.value]
126
155
  match a:
127
- case ReferenceAttribute():
156
+ case ReferenceAttribute() | ReferenceListAttribute():
128
157
  values = [pyReference(v) for v in value_list]
129
158
 
130
- case ResourceAttribute():
159
+ case ResourceAttribute() | ResourceListAttribute():
131
160
  values = [pyResource(v) for v in value_list]
132
161
 
133
- case NumericAttribute():
162
+ case NumericAttribute() | NumericListAttribute():
134
163
  values = [ Quantity.from_str_value(value=v.numerical_value, unit=v.unit) for v in value_list]
135
164
 
136
- case BoolAttribute():
165
+ case BoolAttribute() | BoolAttribute():
137
166
  values = value_list
138
167
 
139
- case TextAttribute():
168
+ case TextAttribute() | TextListAttribute():
140
169
  values = value_list
141
170
 
142
- case DateTimeAttribute():
171
+ case DateTimeAttribute() | DateTimeAttribute():
143
172
  values = value_list
144
173
 
145
- case ObjectAttribute():
174
+ case ObjectAttribute() | ObjectAttribute():
146
175
  values = value_list
147
176
 
148
177
 
@@ -101,11 +101,14 @@ class PAC_ID_Resolver():
101
101
 
102
102
  matches = []
103
103
  for cit in resolver_configs:
104
- if isinstance(cit, CIT_v1):
105
- # cit v1 has no concept of categories and implied keys. It would treat these segments as value segment
106
- matches.append(cit.evaluate_pac_id(pac_id_catless))
107
- else:
108
- matches.append(cit.evaluate_pac_id(pac_id))
104
+ try:
105
+ if isinstance(cit, CIT_v1):
106
+ # cit v1 has no concept of categories and implied keys. It would treat these segments as value segment
107
+ matches.append(cit.evaluate_pac_id(pac_id_catless))
108
+ else:
109
+ matches.append(cit.evaluate_pac_id(pac_id))
110
+ except Exception as e:
111
+ logging.error(f'Failed to resolve pac {pac_id.to_url()} with cit {cit.origin}')
109
112
 
110
113
  if check_service_status:
111
114
  for m in matches:
@@ -74,7 +74,7 @@ class ResolverConfigEntry(LabFREED_BaseModel):
74
74
 
75
75
  @model_validator(mode='after')
76
76
  def _validate_service_type(self):
77
- allowed_types = [ServiceType.ATTRIBUTE_SERVICE_GENERIC.value, ServiceType.USER_HANDOVER_GENERIC.value]
77
+ allowed_types = [ServiceType.ATTRIBUTE_SERVICE_GENERIC.value, ServiceType.USER_HANDOVER_GENERIC.value, ServiceType.ACTION_GENERIC.value]
78
78
  if self.service_type not in allowed_types:
79
79
  if isinstance(self.service_type, ServiceType):
80
80
  s= self.service_type.value
@@ -7,6 +7,7 @@ from labfreed.labfreed_infrastructure import ValidationMsgLevel, _quote_texts
7
7
  class ServiceType(Enum):
8
8
  USER_HANDOVER_GENERIC = 'userhandover-generic'
9
9
  ATTRIBUTE_SERVICE_GENERIC = 'attributes-generic'
10
+ ACTION_GENERIC = "action-generic"
10
11
 
11
12
 
12
13
  def _validate_service_name(service_name):
@@ -62,7 +63,7 @@ def _validate_service_type(service_type):
62
63
  service_type= service_type.value
63
64
  else:
64
65
  service_type= service_type
65
- allowed_types = [ServiceType.ATTRIBUTE_SERVICE_GENERIC.value, ServiceType.USER_HANDOVER_GENERIC.value]
66
+ allowed_types = [ServiceType.ATTRIBUTE_SERVICE_GENERIC.value, ServiceType.USER_HANDOVER_GENERIC.value, ServiceType.ACTION_GENERIC.value]
66
67
  if service_type not in allowed_types:
67
68
  msg_dict.append( {
68
69
  "level": ValidationMsgLevel.ERROR,
File without changes
File without changes
File without changes
File without changes