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
labfreed/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  Python implementation of LabFREED building blocks
3
3
  '''
4
4
 
5
- __version__ = "1.0.0a25"
5
+ __version__ = "1.0.0b27"
6
6
 
7
7
  from labfreed.pac_id import * # noqa: F403
8
8
  from labfreed.pac_cat import * # noqa: F403
@@ -4,7 +4,6 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
4
4
  import requests
5
5
 
6
6
  from labfreed.labfreed_extended.app.pac_info.pac_info import PacInfo
7
- from labfreed.pac_attributes.client.attribute_cache import MemoryAttributeCache
8
7
  from labfreed.pac_attributes.client.client import AttributeClient, http_attribute_request_default_callback_factory
9
8
  from labfreed.pac_attributes.pythonic.py_attributes import pyAttributeGroup
10
9
 
@@ -14,8 +13,6 @@ from labfreed.pac_id_resolver.services import ServiceGroup
14
13
 
15
14
 
16
15
 
17
-
18
-
19
16
  class Labfreed_App_Infrastructure():
20
17
  def __init__(self, markup = 'rich', language_preferences:list[str]|str='en', http_client:requests.Session|None=None, use_issuer_resolver_config=True):
21
18
  if isinstance(language_preferences, str):
@@ -30,7 +27,7 @@ class Labfreed_App_Infrastructure():
30
27
  self._http_client= http_client
31
28
  callback = http_attribute_request_default_callback_factory(http_client)
32
29
 
33
- self._attribute_client = AttributeClient(http_post_callback=callback, cache_store=MemoryAttributeCache(), always_use_cached_value_for_minutes=1)
30
+ self._attribute_client = AttributeClient(http_post_callback=callback)
34
31
 
35
32
 
36
33
  def add_resolver_config(self, cit:str):
@@ -79,7 +76,7 @@ class Labfreed_App_Infrastructure():
79
76
  for sg in service_groups:
80
77
  attributes_urls = [s.url for s in sg.services if s.service_type == 'attributes-generic']
81
78
  for url in attributes_urls:
82
- ags = {ag.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)}
79
+ ags = {ag.group_key: pyAttributeGroup.from_attribute_group(ag) for ag in self._attribute_client.get_attributes(url, pac_id=pac.to_url(include_extensions=False), language_preferences=self._language_preferences)}
83
80
  if ags:
84
81
  attribute_groups.update(ags)
85
82
  pac_info.attribute_groups = attribute_groups
@@ -1,5 +1,3 @@
1
-
2
-
3
1
  from functools import cached_property
4
2
  from pathlib import Path
5
3
  from urllib.parse import urlparse
@@ -164,10 +162,10 @@ class PacInfo(BaseModel):
164
162
  @cached_property
165
163
  def image_url(self) -> str:
166
164
  image_attr = self._all_attributes.get(MetaAttributeKeys.IMAGE.value)
167
- if isinstance(image_attr.value, pyResource):
168
- return image_attr.value.root
169
- if isinstance(image_attr.value, str):
170
- return image_attr.value
165
+ if isinstance(image_attr.values, pyResource):
166
+ return image_attr.values.root
167
+ if isinstance(image_attr.values, str):
168
+ return image_attr.values
171
169
 
172
170
 
173
171
  @cached_property
@@ -180,7 +178,7 @@ class PacInfo(BaseModel):
180
178
  # there can be a display name in attributes, too
181
179
 
182
180
  if dn_attr := self._all_attributes.get(MetaAttributeKeys.DISPLAYNAME.value):
183
- dn = dn_attr.value
181
+ dn = dn_attr.values
184
182
  display_name = dn + f' ( aka {display_name} )' if display_name else dn
185
183
 
186
184
  if not display_name and self.main_category:
@@ -244,7 +242,7 @@ class PacInfo(BaseModel):
244
242
 
245
243
  printout.title1("Attributes")
246
244
  for ag in self.attribute_groups.values():
247
- printout.title2(f'{ag.label} (from {ag.origin})')
245
+ printout.title2(f'{ag.group_label} (from {ag.origin})')
248
246
  for v in ag.attributes.values():
249
247
  v:pyAttribute
250
248
  #print(f'{k}: ({v.label}) :: {v.value} ')
@@ -1,6 +1,7 @@
1
1
 
2
2
  import logging
3
3
  from flask import render_template, request
4
+ from labfreed.pac_attributes.api_data_models.request import AttributeRequestData
4
5
  from labfreed.pac_attributes.api_data_models.response import AttributeGroup
5
6
  from labfreed.pac_cat.pac_cat import PAC_CAT
6
7
  from labfreed.pac_cat.predefined_categories import Material_Device, Material_Consumable, Material_Substance
@@ -75,7 +76,7 @@ class DynamicDemoAttributeGroup(AttributeGroupDataSource):
75
76
  return None
76
77
 
77
78
  attributes = pyAttributes( [pyAttribute(key=d[0], value=d[1]) for d in self._data] ).to_payload_attributes()
78
- return AttributeGroup(key=self._attribute_group_key,
79
+ return AttributeGroup(group_key=self._attribute_group_key,
79
80
  attributes=attributes)
80
81
 
81
82
 
@@ -106,7 +107,7 @@ class SessionLocalDirectCall(requests.Session):
106
107
  self._request_handlers = request_handlers
107
108
 
108
109
 
109
- def post(self, url, *args, **kwargs):
110
+ def get(self, url, *args, **kwargs):
110
111
 
111
112
  if is_self_request(url):
112
113
  # Case: server calls itself
@@ -122,11 +123,15 @@ class SessionLocalDirectCall(requests.Session):
122
123
  # Example: directly call the Flask view function instead of HTTP
123
124
  # You could map URLs to functions if you know your routing
124
125
  # For now, just return a mock response
125
- rh = self._request_handlers.get(path.strip("/"))
126
+ path, pac = path.strip("/").rsplit("/", 1)
127
+ rh = self._request_handlers.get(path)
126
128
 
127
129
  r = Response()
128
130
  if rh:
129
- body = rh.handle_attribute_request(json_request_body=kwargs.get("data"))
131
+ request_data = AttributeRequestData.from_http_request(pac_id = pac,
132
+ params = kwargs.get('params'),
133
+ headers = kwargs.get('headers'))
134
+ body = rh.handle_attribute_request(request_data=request_data)
130
135
  r.status_code = 200
131
136
  r._content = body.encode("utf-8")
132
137
  r.encoding = "utf-8"
@@ -161,8 +166,6 @@ def is_self_request(url: str) -> bool:
161
166
  target_ip = resolve_ip(parsed.hostname.lower())
162
167
  current_ip = resolve_ip(request.host.split(":")[0].lower())
163
168
 
164
-
165
-
166
169
  return target_ip == current_ip
167
170
 
168
171
 
@@ -1,7 +1,7 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
- <title>icons/external-link</title>
4
- <g id="icons/external-link" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
5
- <path d="M5.10570888,18.8942911 C4.83722343,18.6258057 4.81484965,18.204406 5.03858752,17.9104349 L5.10570888,17.8336309 L15.9992174,6.94117952 L10.8105793,6.93942074 L10.7088087,6.93257412 C10.3427331,6.8829117 10.0605793,6.5691165 10.0605793,6.18942074 C10.0605793,5.81286295 10.3380887,5.50112097 10.6997498,5.44755266 L10.8105793,5.43942074 L18.5605793,5.43942074 L18.5605793,13.1894207 L18.5537326,13.2911913 C18.5090365,13.6206593 18.2503927,13.8821507 17.9223127,13.9311544 L17.8105793,13.9394207 L17.7088087,13.9325741 C17.3793407,13.8878779 17.1178493,13.6292342 17.0688456,13.3011542 L17.0605793,13.1894207 L17.0598776,8.00183969 L6.16636906,18.8942911 C5.87347584,19.1871843 5.3986021,19.1871843 5.10570888,18.8942911 Z" id="Icons/Navigation/External-Link/Dark" fill="#1C1847" fill-rule="nonzero"></path>
6
- </g>
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <title>icons/external-link</title>
4
+ <g id="icons/external-link" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
5
+ <path d="M5.10570888,18.8942911 C4.83722343,18.6258057 4.81484965,18.204406 5.03858752,17.9104349 L5.10570888,17.8336309 L15.9992174,6.94117952 L10.8105793,6.93942074 L10.7088087,6.93257412 C10.3427331,6.8829117 10.0605793,6.5691165 10.0605793,6.18942074 C10.0605793,5.81286295 10.3380887,5.50112097 10.6997498,5.44755266 L10.8105793,5.43942074 L18.5605793,5.43942074 L18.5605793,13.1894207 L18.5537326,13.2911913 C18.5090365,13.6206593 18.2503927,13.8821507 17.9223127,13.9311544 L17.8105793,13.9394207 L17.7088087,13.9325741 C17.3793407,13.8878779 17.1178493,13.6292342 17.0688456,13.3011542 L17.0605793,13.1894207 L17.0598776,8.00183969 L6.16636906,18.8942911 C5.87347584,19.1871843 5.3986021,19.1871843 5.10570888,18.8942911 Z" id="Icons/Navigation/External-Link/Dark" fill="#1C1847" fill-rule="nonzero"></path>
6
+ </g>
7
7
  </svg>
@@ -1,44 +1,44 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <svg id="LabfreedLogo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 233" width="200" height="116.5">
3
- <defs>
4
- <style>
5
- .cls-1 { fill: #51a1d7; }
6
- .cls-2 { fill: #438ad7; }
7
- .cls-3 { fill: #94d5e4; }
8
- .cls-4 { fill: #4db9d2; }
9
- .cls-5 { fill: #2b63bc; }
10
- .brand-text {
11
- font-family: Arial, sans-serif;
12
- font-size: 24px;
13
- font-weight: bold;
14
- fill: #51a1d7;
15
- }
16
- </style>
17
- </defs>
18
-
19
- <!-- Half-size dove -->
20
- <g transform="scale(0.5)">
21
- <g>
22
- <g>
23
- <polygon class="cls-3" points="115.8 143.51 115.8 143.55 115.77 143.51 115.8 143.51"/>
24
- <polygon class="cls-1" points="208.4 97.2 141.5 164.1 138.93 158.95 115.78 112.63 108.05 97.2 208.4 97.2"/>
25
- <polygon class="cls-2" points="208.4 97.19 108.05 97.19 138.93 27.74 169.8 58.61 208.4 97.19"/>
26
- <polygon class="cls-5" points="208.4 43.17 208.4 97.2 169.81 58.62 208.4 43.17"/>
27
- <polygon class="cls-2" points="247 74.05 208.4 74.05 208.4 43.17 247 74.05"/>
28
- </g>
29
- <path class="cls-4" d="M108.05,189.82h.02v-7.72h-.02v7.72ZM108.06,143.51v7.72h.02v-7.72h-.02ZM108.06,158.93v15.45h.02v-15.45h-.02Z"/>
30
- <path class="cls-2" d="M141.5,164.1l-2.57-5.15-23.15-46.32-7.72-15.43v46.3h.02v7.72h-.02v7.71h.02v15.45h-.02v7.72h.02v7.72h-.02v30.87l38.6-46.32-5.15-10.27ZM115.8,143.55l-.03-.05h.03v.05Z"/>
31
- <rect class="cls-2" x="30.88" y="43.16" width="15.43" height="15.45"/>
32
- <rect class="cls-2" x="15.43" y="27.73" width="15.44" height="15.44"/>
33
- <rect class="cls-1" y="12.3" width="15.44" height="15.44"/>
34
- <rect class="cls-2" x="61.75" y="74.04" width="15.43" height="15.45"/>
35
- <rect class="cls-1" x="46.32" y="74.05" width="15.44" height="15.44"/>
36
- <rect class="cls-1" x="30.87" y="89.49" width="15.44" height="15.44"/>
37
- <rect class="cls-1" y="58.61" width="15.44" height="15.44"/>
38
- </g>
39
- <polygon class="cls-5" points="138.93 27.74 108.05 97.19 92.63 97.19 92.63 89.48 77.18 89.48 77.18 74.04 61.75 74.04 61.75 58.61 46.31 58.61 46.31 43.16 30.88 43.16 30.88 27.75 30.86 27.74 138.93 27.74"/>
40
- </g>
41
-
42
- <!-- Branding text -->
43
- <text class="brand-text" x="0" y="130">Powered by LabFREED </text>
44
- </svg>
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="LabfreedLogo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 233" width="200" height="116.5">
3
+ <defs>
4
+ <style>
5
+ .cls-1 { fill: #51a1d7; }
6
+ .cls-2 { fill: #438ad7; }
7
+ .cls-3 { fill: #94d5e4; }
8
+ .cls-4 { fill: #4db9d2; }
9
+ .cls-5 { fill: #2b63bc; }
10
+ .brand-text {
11
+ font-family: Arial, sans-serif;
12
+ font-size: 24px;
13
+ font-weight: bold;
14
+ fill: #51a1d7;
15
+ }
16
+ </style>
17
+ </defs>
18
+
19
+ <!-- Half-size dove -->
20
+ <g transform="scale(0.5)">
21
+ <g>
22
+ <g>
23
+ <polygon class="cls-3" points="115.8 143.51 115.8 143.55 115.77 143.51 115.8 143.51"/>
24
+ <polygon class="cls-1" points="208.4 97.2 141.5 164.1 138.93 158.95 115.78 112.63 108.05 97.2 208.4 97.2"/>
25
+ <polygon class="cls-2" points="208.4 97.19 108.05 97.19 138.93 27.74 169.8 58.61 208.4 97.19"/>
26
+ <polygon class="cls-5" points="208.4 43.17 208.4 97.2 169.81 58.62 208.4 43.17"/>
27
+ <polygon class="cls-2" points="247 74.05 208.4 74.05 208.4 43.17 247 74.05"/>
28
+ </g>
29
+ <path class="cls-4" d="M108.05,189.82h.02v-7.72h-.02v7.72ZM108.06,143.51v7.72h.02v-7.72h-.02ZM108.06,158.93v15.45h.02v-15.45h-.02Z"/>
30
+ <path class="cls-2" d="M141.5,164.1l-2.57-5.15-23.15-46.32-7.72-15.43v46.3h.02v7.72h-.02v7.71h.02v15.45h-.02v7.72h.02v7.72h-.02v30.87l38.6-46.32-5.15-10.27ZM115.8,143.55l-.03-.05h.03v.05Z"/>
31
+ <rect class="cls-2" x="30.88" y="43.16" width="15.43" height="15.45"/>
32
+ <rect class="cls-2" x="15.43" y="27.73" width="15.44" height="15.44"/>
33
+ <rect class="cls-1" y="12.3" width="15.44" height="15.44"/>
34
+ <rect class="cls-2" x="61.75" y="74.04" width="15.43" height="15.45"/>
35
+ <rect class="cls-1" x="46.32" y="74.05" width="15.44" height="15.44"/>
36
+ <rect class="cls-1" x="30.87" y="89.49" width="15.44" height="15.44"/>
37
+ <rect class="cls-1" y="58.61" width="15.44" height="15.44"/>
38
+ </g>
39
+ <polygon class="cls-5" points="138.93 27.74 108.05 97.19 92.63 97.19 92.63 89.48 77.18 89.48 77.18 74.04 61.75 74.04 61.75 58.61 46.31 58.61 46.31 43.16 30.88 43.16 30.88 27.75 30.86 27.74 138.93 27.74"/>
40
+ </g>
41
+
42
+ <!-- Branding text -->
43
+ <text class="brand-text" x="0" y="130">Powered by LabFREED </text>
44
+ </svg>
@@ -1,56 +1,108 @@
1
- from typing import Self
2
- from pydantic import ConfigDict, model_validator
1
+ from typing import Any, Self
2
+ from urllib.parse import unquote
3
+ from werkzeug.datastructures import LanguageAccept
4
+ from werkzeug.http import parse_accept_header
5
+ from pydantic import ConfigDict, field_validator, model_validator
3
6
  from labfreed.labfreed_infrastructure import LabFREED_BaseModel, LabFREED_ValidationError, ValidationMsgLevel
4
7
  from labfreed.pac_id.pac_id import PAC_ID
5
8
 
9
+ ATTR_GROUPS = 'attr_grps'
10
+ ATTR_GROUPS_FWD_LKP= 'attr_fwd_lkp'
6
11
 
7
- class AttributeRequestPayload(LabFREED_BaseModel):
8
- model_config = ConfigDict(frozen=True)
12
+
13
+ class AttributeRequestData(LabFREED_BaseModel):
14
+ model_config = ConfigDict(arbitrary_types_allowed=True)
9
15
 
10
- pac_ids: list[str]
11
- language_preferences: list[str]|None = None
16
+ pac_id: str
17
+ language_preferences: LanguageAccept|None = None
12
18
  restrict_to_attribute_groups: list[str]|None = None
13
- suppress_forward_lookup: bool = False
19
+ do_forward_lookup: bool = True
14
20
 
15
21
  def as_json(self):
16
22
  return self.model_dump_json()
17
23
 
18
24
  @classmethod
19
- def from_json(cls, json):
25
+ def from_json(cls, json) -> Self:
20
26
  return cls.model_validate_json(json)
21
27
 
28
+ @classmethod
29
+ def from_http_request(cls, pac_id:str, params:dict, headers:dict):
30
+ restrict_to_attribute_groups = params.get(ATTR_GROUPS)
31
+ if restrict_to_attribute_groups == '':
32
+ restrict_to_attribute_groups = None
33
+ if restrict_to_attribute_groups:
34
+ restrict_to_attribute_groups = restrict_to_attribute_groups.split(',')
35
+
36
+ fwd_lkp = params.get(ATTR_GROUPS_FWD_LKP, True)
37
+ if fwd_lkp is True:
38
+ do_forward_lookup = True
39
+ else:
40
+ do_fwd_lookup = fwd_lkp.lower() not in ['false', 'no', '0', 'n', 'off']
41
+
42
+ lang_hdr = headers.get('Accept-Language')
43
+ language_preferences: LanguageAccept = parse_accept_header(lang_hdr, LanguageAccept)
44
+ out = cls(pac_id=pac_id,
45
+ restrict_to_attribute_groups = restrict_to_attribute_groups,
46
+ do_forward_lookup = do_forward_lookup,
47
+ language_preferences=language_preferences
48
+ )
49
+ return out
50
+
51
+
52
+
22
53
  @model_validator(mode="before")
23
54
  @classmethod
24
- def _handle_single_pac_url(cls, data):
25
- p = data.get('pac_urls')
26
- if isinstance(p, str):
27
- data['pac_urls'] = [p]
28
- return data
55
+ def _scalars_to_list(cls, d):
56
+ if isinstance(lp:= d.get("language_preferences"), str):
57
+ d["language_preferences"] = [lp]
58
+ if isinstance(rag := d.get("restrict_to_attribute_groups"), str):
59
+ d["restrict_to_attribute_groups"] = [rag]
60
+ return d
61
+
62
+
63
+ @field_validator('language_preferences', mode='before')
64
+ @classmethod
65
+ def convert_language_preferences(cls,lp):
66
+ if isinstance(lp, LanguageAccept):
67
+ return lp
68
+ lq = [(lng, 1-i/len(lp)) for i, lng in enumerate(lp)]
69
+ return LanguageAccept(lq)
70
+
29
71
 
30
72
  @model_validator(mode="after")
31
- def _validate_pacs(self) -> Self:
32
- if len(self.pac_ids) > 100:
73
+ def _revert_url_encoding(self):
74
+ self.pac_id = unquote(self.pac_id)
75
+ if self.restrict_to_attribute_groups:
76
+ self.restrict_to_attribute_groups = [unquote(g) for g in self.restrict_to_attribute_groups]
77
+ return self
78
+
79
+ @model_validator(mode="after")
80
+ def _validate_pacs(self) -> Self:
81
+ try:
82
+ PAC_ID.from_url(self.pac_id)
83
+ except LabFREED_ValidationError:
33
84
  self._add_validation_message(
34
- source="pacs",
35
- level = ValidationMsgLevel.ERROR,
36
- msg='The number of pac-ids must be limited to 100'
37
- )
38
-
39
- for pac_url in self.pac_ids:
40
- try:
41
- PAC_ID.from_url(pac_url)
42
- except LabFREED_ValidationError:
43
- self._add_validation_message(
44
- source="pacs",
45
- level = ValidationMsgLevel.ERROR,
46
- msg='{pac_url} is not a valid PAC-ID'
47
- )
85
+ source="pac_id",
86
+ level = ValidationMsgLevel.ERROR,
87
+ msg='{self.pac_id} is not a valid PAC-ID'
88
+ )
48
89
 
49
90
  if not self.is_valid:
50
91
  raise LabFREED_ValidationError(message='Invalid request', validation_msgs=self.validation_messages())
51
92
 
52
93
  return self
53
94
 
95
+ def language_preference_http_header(self) -> dict[str, str]:
96
+ if not self.language_preferences:
97
+ return {}
98
+ headers={'Accept-Language': LanguageAccept(self.language_preferences).to_header()}
99
+ return headers
100
+
101
+ def request_params(self) -> dict[str, Any]:
102
+ params = {ATTR_GROUPS_FWD_LKP: self.do_forward_lookup}
103
+ if self.restrict_to_attribute_groups:
104
+ params.update({ATTR_GROUPS: ','.join(self.restrict_to_attribute_groups)})
105
+ return params
106
+
54
107
 
55
108
 
56
-