labfreed 1.0.0a25__py3-none-any.whl → 1.0.0b27__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.
Files changed (27) hide show
  1. labfreed/__init__.py +1 -1
  2. labfreed/labfreed_extended/app/app_infrastructure.py +2 -5
  3. labfreed/labfreed_extended/app/pac_info/pac_info.py +6 -8
  4. labfreed/labfreed_extended/pac_issuer_lib/lib/attribute.py +9 -6
  5. labfreed/labfreed_extended/pac_issuer_lib/static/external-link.svg +6 -6
  6. labfreed/labfreed_extended/pac_issuer_lib/static/logo.svg +44 -44
  7. labfreed/pac_attributes/api_data_models/request.py +82 -30
  8. labfreed/pac_attributes/api_data_models/response.py +107 -136
  9. labfreed/pac_attributes/client/client.py +30 -36
  10. labfreed/pac_attributes/client/client_attribute_group.py +18 -0
  11. labfreed/pac_attributes/pythonic/attribute_server_factory.py +39 -13
  12. labfreed/pac_attributes/pythonic/excel_attribute_data_source.py +1 -1
  13. labfreed/pac_attributes/pythonic/py_attributes.py +103 -125
  14. labfreed/pac_attributes/server/attribute_data_sources.py +9 -8
  15. labfreed/pac_attributes/server/server.py +33 -37
  16. labfreed/pac_id_resolver/resolver_config.py +2 -1
  17. {labfreed-1.0.0a25.dist-info → labfreed-1.0.0b27.dist-info}/METADATA +1 -1
  18. {labfreed-1.0.0a25.dist-info → labfreed-1.0.0b27.dist-info}/RECORD +20 -26
  19. labfreed/labfreed_extended/app/pac_info/html_renderer/external-link.svg +0 -7
  20. labfreed/labfreed_extended/app/pac_info/html_renderer/macros.jinja.html +0 -188
  21. labfreed/labfreed_extended/app/pac_info/html_renderer/pac-info-style.css +0 -176
  22. labfreed/labfreed_extended/app/pac_info/html_renderer/pac_info.jinja.html +0 -46
  23. labfreed/labfreed_extended/app/pac_info/html_renderer/pac_info_card.jinja.html +0 -7
  24. labfreed/pac_attributes/client/__init__.py +0 -0
  25. labfreed/pac_attributes/client/attribute_cache.py +0 -65
  26. {labfreed-1.0.0a25.dist-info → labfreed-1.0.0b27.dist-info}/WHEEL +0 -0
  27. {labfreed-1.0.0a25.dist-info → labfreed-1.0.0b27.dist-info}/licenses/LICENSE +0 -0
@@ -1,188 +0,0 @@
1
- {# ---------- PAC-INFO MACROS ---------- #}
2
- {# These macros render semantic HTML with light class hooks. #}
3
-
4
- {%- macro info_card(pac_info) -%}
5
- <section class="lf-info-card">
6
- <div class="lf-info-title">{{ pac_info.display_name }}</div>
7
- <div class="lf-info-grid">
8
- {% if pac_info.image_url %}
9
- <img src="{{ pac_info.image_url }}" class="prod_img lf-info-item" alt="">
10
- {% endif %}
11
-
12
- {% if pac_info.main_category %}
13
- {% for c in pac_info.pac_id.categories %}
14
- {% for k, v in c.segments_as_dict().items() %}
15
- {% if k != "key" %}
16
- <div class="lf-info-item">
17
- {% if k %}
18
- <div class="lf-info-key">{{ k }}</div>
19
- {% else %}
20
- <div class="lf-info-key"> <i>{{"No Key" }}</i></div>
21
- {% endif %}
22
- <div class="lf-info-val">{{ v }}</div>
23
- </div>
24
- {% endif %}
25
- {% endfor %}
26
- {% endfor %}
27
- {% endif %}
28
-
29
- </div>
30
- {% if pac_info.safety_pictograms %}
31
- {% for p in pac_info.safety_pictograms.values() %}
32
- <figure class="lf-figure lf-figure--image">
33
- <img src="{{ p.value|e }}" alt="{{ p.label|e }}" class="lf-attr-image">
34
- <figcaption class="lf-figure__caption sr-only">{{ p.label }}</figcaption>
35
- </figure>
36
- {% endfor %}
37
- {% endif %}
38
-
39
- </section>
40
- {%- endmacro -%}
41
-
42
-
43
- {%- macro key_value_table(pairs) -%}
44
- {# pairs: dict-like object #}
45
- <table class="lf-table lf-table--kv">
46
- <tbody>
47
- {%- for k, v in pairs.items() -%}
48
- {%- if k != "key" -%}
49
- <tr>
50
- <th class="lf-th lf-th--key">{{ k }}</th>
51
- <td class="lf-td lf-td--val">{{ v }}</td>
52
- </tr>
53
- {%- endif -%}
54
- {%- endfor -%}
55
- </tbody>
56
- </table>
57
- {%- endmacro -%}
58
-
59
-
60
- {%- macro category_block(category) -%}
61
- <div class="lf-section lf-section--category">
62
- <h5 class="lf-section__title">{{ category.__class__.__name__ }}</h5>
63
- {{ key_value_table(category.segments_as_dict()) }}
64
- </div>
65
- {%- endmacro -%}
66
-
67
-
68
- {%- macro services_table(user_handover_group) -%}
69
- <div class="lf-section lf-section--services">
70
- <div class="lf-origin-hint">from {{ user_handover_group.origin }}</div>
71
- <div class="lf-services">
72
- {%- for s in user_handover_group.services -%}
73
- <a href="{{ s.url }}" target="_blank" rel="noopener" class="lf-service">
74
- <span class="lf-service__icon">
75
- {% include "external-link.svg" %}
76
- </span>
77
- <span class="lf-service__name">{{ s.service_name }}</span>
78
- </a>
79
- {%- endfor -%}
80
- </div>
81
- </div>
82
- {%- endmacro -%}
83
-
84
-
85
- {%- macro reference_value(value) -%}
86
- <span class="lf-reference">{{ value }}</span>
87
- {%- endmacro -%}
88
-
89
-
90
- {%- macro attribute_row(a) -%}
91
- <tr>
92
- <th class="lf-th lf-th--key">{{ a.label }}</th>
93
- <td class="lf-td lf-td--val">
94
- {% set value = a.value %}
95
- {%- if is_image(value) -%}
96
- <figure class="lf-figure lf-figure--image">
97
- <img src="{{ value|e }}" alt="{{ label|e }}" class="lf-attr-image">
98
- <figcaption class="lf-figure__caption sr-only">{{ label }}</figcaption>
99
- </figure>
100
- {%- elif is_url(value) -%}
101
- <a href="{{ value|e }}" target="_blank" rel="noopener" class="lf-link">{{ value }}</a>
102
- {%- elif is_reference(value) -%}
103
- {{ reference_value(value)}}
104
- {%- else -%}
105
- <span class="lf-text">{{ value }}</span>
106
- {%- endif -%}
107
- </td>
108
- </tr>
109
- {%- endmacro -%}
110
-
111
-
112
- {%- macro attribute_group_block(ag) -%}
113
- <div class="lf-section lf-section--attributes">
114
- <h5 class="lf-section__title">
115
- {{ ag.label }} <span class="lf-origin-hint">(from {{ ag.origin }})</span>
116
- </h5>
117
- <table class="lf-table lf-table--kv">
118
- <tbody>
119
- {%- for a in ag.attributes.values() -%}
120
- {{ attribute_row(a) }}
121
- {%- endfor -%}
122
- </tbody>
123
- </table>
124
- </div>
125
- {%- endmacro -%}
126
-
127
-
128
- {%- macro data_table_inline(dt) -%}
129
- {# Renders your DataTable structure; expects filters: is_data_table(dt) #}
130
- {%- if dt.data | length == 1 -%}
131
- <table class="lf-table lf-table--kv">
132
- <tbody>
133
- {%- for i in range(dt.col_names | length) -%}
134
- <tr>
135
- <th class="lf-th lf-th--key">{{ dt.col_names[i] }}</th>
136
- <td class="lf-td lf-td--val">{{ dt.data[0][i] }}</td>
137
- </tr>
138
- {%- endfor -%}
139
- </tbody>
140
- </table>
141
- {%- else -%}
142
- <table class="lf-table lf-table--grid">
143
- <thead>
144
- <tr>
145
- {%- for rn in dt.col_names -%}
146
- <th class="lf-th">{{ rn }}</th>
147
- {%- endfor -%}
148
- </tr>
149
- </thead>
150
- <tbody>
151
- {%- for row in dt.data -%}
152
- <tr>
153
- {%- for e in row -%}
154
- <td class="lf-td">{{ e }}</td>
155
- {%- endfor -%}
156
- </tr>
157
- {%- endfor -%}
158
- </tbody>
159
- </table>
160
- {%- endif -%}
161
- {%- endmacro -%}
162
-
163
-
164
- {%- macro attached_data_block(attached_data) -%}
165
- <div class="lf-section lf-section--attached">
166
- {%- for name, trex in attached_data.items() -%}
167
- <div class="lf-subsection">
168
- <h5 class="lf-subsection__title">{{ name }}</h5>
169
- <table class="lf-table lf-table--kv">
170
- <tbody>
171
- {%- for k, v in trex.items() -%}
172
- <tr>
173
- <th class="lf-th lf-th--key">{{ k }}</th>
174
- <td class="lf-td lf-td--val">
175
- {%- if is_data_table(v) -%}
176
- {{ data_table_inline(v) }}
177
- {%- else -%}
178
- {{ v }}
179
- {%- endif -%}
180
- </td>
181
- </tr>
182
- {%- endfor -%}
183
- </tbody>
184
- </table>
185
- </div>
186
- {%- endfor -%}
187
- </div>
188
- {%- endmacro -%}
@@ -1,176 +0,0 @@
1
-
2
- .lf-section {
3
- margin-top: 0.5rem;
4
- }
5
-
6
- .lf-origin-hint {
7
- font-style: italic; /* cursive */
8
- font-size: 1rem; /* reset to regular text size */
9
- font-weight: normal; /* not bold, even if inside a heading */
10
- }
11
-
12
-
13
- .lf-results {
14
- display: grid;
15
- gap: 1.5rem;
16
- padding: 1rem; /* space around the whole grid */
17
- }
18
-
19
- .lf-info-card {
20
- width:100%;
21
- border: solid 1px #ccc;
22
- border-radius: 8px;
23
- }
24
-
25
- .lf-info-card {
26
- padding: 1rem;
27
- border: 1px solid #e5e7eb;
28
- border-radius: 8px;
29
- background: #fff;
30
- }
31
-
32
- /* title as "slightly bigger text" */
33
- .lf-info-title {
34
- font-size: 1.25rem; /* a bit larger than normal text */
35
- font-weight: 600;
36
- margin-bottom: 1rem;
37
- }
38
-
39
- /* responsive grid for key/value */
40
- .lf-info-grid {
41
- display: grid;
42
- gap: 1rem;
43
- grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
44
- }
45
-
46
- .lf-info-item {
47
- display: flex;
48
- flex-direction: column;
49
- gap: 0.25rem;
50
- padding: 0.5rem;
51
- }
52
-
53
- .lf-info-key {
54
- font-size: 0.875rem;
55
- font-weight: 600;
56
- color: #374151; /* slate-700 */
57
- }
58
-
59
- .lf-info-val {
60
- font-size: 1rem;
61
- color: #111827; /* slate-900 */
62
- }
63
-
64
-
65
-
66
- .prod_img {
67
- height: 10vw;
68
- width: auto;
69
- }
70
-
71
-
72
- /* Apply to all PAC macro tables */
73
- .lf-table {
74
- width: 100%;
75
- border-collapse: collapse;
76
- table-layout: fixed; /* ensures consistent column widths */
77
- }
78
-
79
- .lf-table th,
80
- .lf-table td {
81
- border: 1px solid #ccc;
82
- padding: 0.4rem 0.6rem;
83
- vertical-align: top;
84
- text-align: left;
85
- }
86
-
87
- /* Key/value style: 1/3 vs 2/3 columns */
88
- .lf-table--kv th.lf-th--key {
89
- width: 33%;
90
- font-weight: 600;
91
- }
92
- .lf-table--kv td.lf-td--val {
93
- width: 67%;
94
- }
95
-
96
- .lf-attr-image {
97
- width: 30px;
98
- }
99
-
100
- /* Services laid out as equal-width grid columns */
101
- .lf-services {
102
- display: grid;
103
- gap: 0.75rem;
104
-
105
- /* 2 columns on small, 3 on md, 4 on lg — tweak as you like */
106
- grid-template-columns: repeat(2, 1fr);
107
- }
108
-
109
- @media (min-width: 640px) {
110
- .lf-services { grid-template-columns: repeat(3, 1fr); }
111
- }
112
- @media (min-width: 1024px) {
113
- .lf-services { grid-template-columns: repeat(4, 1fr); }
114
- }
115
-
116
- /* Each service card fills its grid cell */
117
- .lf-service {
118
- display: flex; /* still use flex inside for icon + text */
119
- align-items: center;
120
- gap: 0.4rem;
121
- padding: 0.4rem 0.6rem;
122
- height: 100%; /* same height across a row (since your content height is uniform) */
123
- width: 100%; /* ensure full cell width */
124
- text-decoration: none;
125
- color: var(--lf-link, #2563eb);
126
- }
127
-
128
- .lf-service:hover {
129
- font-size: 1.05em; /* ~5% larger */
130
- text-decoration: none;
131
- }
132
-
133
- .lf-service__icon { width: 1rem; height: 1rem; flex-shrink: 0; }
134
- .lf-service__name {
135
- flex: 1;
136
- overflow: hidden;
137
- text-overflow: ellipsis;
138
- white-space: nowrap;
139
- }
140
-
141
-
142
-
143
- /* Position context */
144
- .lf-ref-card-wrap {
145
- position: relative;
146
- display: inline-block;
147
- }
148
-
149
- /* The floating card is hidden by default */
150
- .lf-ref-card {
151
- display: none;
152
- position: absolute;
153
- top: 100%; /* appear below the reference text */
154
- left: 0;
155
- z-index: 1000;
156
- width: min(420px, 90vw);
157
- margin-top: .5rem;
158
- padding: 1rem;
159
- background: #fff;
160
- border: 1px solid #e5e7eb;
161
- border-radius: 8px;
162
- box-shadow: 0 10px 20px rgba(0,0,0,.08);
163
- }
164
-
165
- /* Show on hover */
166
- .lf-ref-card-wrap:hover .lf-ref-card {
167
- display: block;
168
- }
169
-
170
- /* Style the inline reference */
171
- .lf-reference {
172
- color: var(--lf-link, #2563eb);
173
- }
174
-
175
-
176
-
@@ -1,46 +0,0 @@
1
- {% import "macros.jinja.html" as pac with context %}
2
-
3
- <style>
4
- {% include "pac-info-style.css" %}
5
- </style>
6
-
7
-
8
- {% if pac_info %}
9
- <section class="lf-results">
10
- {# ----- Info Card ---- #}
11
- {{ pac.info_card(pac_info) }}
12
-
13
- {# ---- Services ---- #}
14
- {% if pac_info.user_handovers %}
15
- <section>
16
- <!--<h4>Services</h4>-->
17
- {% for sg in pac_info.user_handovers %}
18
- {{ pac.services_table(sg) }}
19
- {% endfor %}
20
- </section>
21
- {% endif %}
22
-
23
- {# ---- Attributes ---- #}
24
- {% if pac_info.attributes %}
25
- <section>
26
- <!--<h4>Attributes</h4>-->
27
- {% for ag in pac_info.attributes.values() %}
28
- {% if ag.key not in hide_attribute_groups %}
29
- {{ pac.attribute_group_block(ag) }}
30
- {% endif %}
31
- {% endfor %}
32
- </section>
33
-
34
- {% endif %}
35
-
36
- {# ---- Attached Data ---- #}
37
- {% if pac_info.attached_data %}
38
- <section>
39
- <!--<h4 class="lf-section__title">Attached Data</h4>-->
40
- {{ pac.attached_data_block(pac_info.attached_data) }}
41
- </section>
42
- {% endif %}
43
-
44
- </section>
45
-
46
- {% endif %}
@@ -1,7 +0,0 @@
1
- {% import "macros.jinja.html" as macros with context %}
2
-
3
-
4
- <style>
5
- {% include "pac-info-style.css" %}
6
- </style>
7
- {{ macros.info_card(pac_info) }}
File without changes
@@ -1,65 +0,0 @@
1
-
2
-
3
- from datetime import UTC, datetime, timedelta
4
- from typing import Literal, Protocol
5
-
6
-
7
- from labfreed.pac_attributes.api_data_models.response import AttributeGroup
8
- from labfreed.pac_id.pac_id import PAC_ID
9
-
10
-
11
-
12
- class CacheableAttributeGroup(AttributeGroup):
13
- origin:str
14
- language:str
15
- value_from: datetime | None = None
16
-
17
- def still_valid(self, accept_cache_for_minutes):
18
- if self.value_from is None:
19
- return False
20
- else:
21
- return ( datetime.now(tz=UTC) - timedelta(minutes=accept_cache_for_minutes)) > self.value_from
22
-
23
-
24
-
25
-
26
-
27
-
28
- class AttributeCache(Protocol):
29
- def get_all(self, service_url:str, pac:PAC_ID) -> list[CacheableAttributeGroup]:
30
- pass
31
-
32
- def get_attribute_groups(self, service_url:str, pac:PAC_ID, attribute_groups:list[str]):
33
- pass
34
-
35
- def update(self, service_url:str, pac:PAC_ID, attribute_groups:list[CacheableAttributeGroup]):
36
- pass
37
-
38
-
39
-
40
- class MemoryAttributeCache(AttributeCache):
41
- '''simple in-memory implementation of AttributeCache'''
42
- def __init__(self):
43
- self._store = dict()
44
-
45
- def get_all(self, service_url:str, pac:PAC_ID|str) -> list[CacheableAttributeGroup]:
46
- if isinstance(pac, str):
47
- pac = PAC_ID.from_url(pac)
48
- k = self._generate_dict_key(service_url=service_url, pac=pac)
49
-
50
- ags = [CacheableAttributeGroup.model_validate(e) for e in self._store.get(k, [])]
51
- return ags
52
-
53
- def get_attribute_groups(self, service_url:str, pac:PAC_ID, attribute_groups:list[str]):
54
- all_ags = self.get_all(service_url=service_url, pac=pac)
55
- selected_ags = [ag for ag in all_ags if ag.key in attribute_groups]
56
- return selected_ags
57
-
58
- def update(self, service_url:str, pac:PAC_ID, attribute_groups: list[CacheableAttributeGroup] ):
59
- k = self._generate_dict_key(service_url=service_url, pac=pac)
60
- self._store.update({k: [e.model_dump() for e in attribute_groups]})
61
-
62
- @staticmethod
63
- def _generate_dict_key(service_url:str, pac:PAC_ID):
64
- key = service_url +";"+ pac.to_url(include_extensions=False)
65
- return key