benchling-sdk 1.9.0a5__py3-none-any.whl → 1.10.0__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.
- benchling_sdk/apps/canvas/__init__.py +0 -0
- benchling_sdk/apps/canvas/errors.py +14 -0
- benchling_sdk/apps/{helpers/canvas_helpers.py → canvas/framework.py} +129 -188
- benchling_sdk/apps/canvas/types.py +125 -0
- benchling_sdk/apps/config/__init__.py +0 -3
- benchling_sdk/apps/config/decryption_provider.py +1 -1
- benchling_sdk/apps/config/errors.py +38 -0
- benchling_sdk/apps/config/framework.py +343 -0
- benchling_sdk/apps/config/helpers.py +157 -0
- benchling_sdk/apps/config/{mock_dependencies.py → mock_config.py} +78 -99
- benchling_sdk/apps/config/types.py +36 -0
- benchling_sdk/apps/framework.py +49 -338
- benchling_sdk/apps/helpers/webhook_helpers.py +2 -2
- benchling_sdk/apps/status/__init__.py +0 -0
- benchling_sdk/apps/status/errors.py +85 -0
- benchling_sdk/apps/{helpers/session_helpers.py → status/framework.py} +58 -167
- benchling_sdk/apps/status/helpers.py +20 -0
- benchling_sdk/apps/status/types.py +45 -0
- benchling_sdk/apps/types.py +3 -0
- benchling_sdk/errors.py +4 -4
- benchling_sdk/models/__init__.py +44 -0
- benchling_sdk/services/v2/beta/{v2_beta_dataset_service.py → v2_beta_data_frame_service.py} +126 -116
- benchling_sdk/services/v2/stable/assay_result_service.py +18 -0
- benchling_sdk/services/v2/v2_beta_service.py +11 -11
- {benchling_sdk-1.9.0a5.dist-info → benchling_sdk-1.10.0.dist-info}/METADATA +4 -4
- {benchling_sdk-1.9.0a5.dist-info → benchling_sdk-1.10.0.dist-info}/RECORD +29 -20
- benchling_sdk/apps/config/dependencies.py +0 -1085
- benchling_sdk/apps/config/scalars.py +0 -226
- benchling_sdk/apps/helpers/config_helpers.py +0 -409
- /benchling_sdk/apps/{helpers → config}/cryptography_helpers.py +0 -0
- {benchling_sdk-1.9.0a5.dist-info → benchling_sdk-1.10.0.dist-info}/LICENSE +0 -0
- {benchling_sdk-1.9.0a5.dist-info → benchling_sdk-1.10.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
from typing import TypeVar, Union
|
2
|
+
|
3
|
+
from benchling_api_client.v2.extensions import UnknownType
|
4
|
+
|
5
|
+
from benchling_sdk.models import (
|
6
|
+
ButtonUiBlock,
|
7
|
+
ButtonUiBlockCreate,
|
8
|
+
ButtonUiBlockUpdate,
|
9
|
+
ChipUiBlock,
|
10
|
+
ChipUiBlockCreate,
|
11
|
+
ChipUiBlockUpdate,
|
12
|
+
DropdownMultiValueUiBlock,
|
13
|
+
DropdownMultiValueUiBlockCreate,
|
14
|
+
DropdownMultiValueUiBlockUpdate,
|
15
|
+
DropdownUiBlock,
|
16
|
+
DropdownUiBlockCreate,
|
17
|
+
DropdownUiBlockUpdate,
|
18
|
+
MarkdownUiBlock,
|
19
|
+
MarkdownUiBlockCreate,
|
20
|
+
MarkdownUiBlockUpdate,
|
21
|
+
SearchInputMultiValueUiBlock,
|
22
|
+
SearchInputMultiValueUiBlockCreate,
|
23
|
+
SearchInputMultiValueUiBlockUpdate,
|
24
|
+
SearchInputUiBlock,
|
25
|
+
SearchInputUiBlockCreate,
|
26
|
+
SearchInputUiBlockUpdate,
|
27
|
+
SectionUiBlock,
|
28
|
+
SectionUiBlockCreate,
|
29
|
+
SectionUiBlockUpdate,
|
30
|
+
SelectorInputMultiValueUiBlock,
|
31
|
+
SelectorInputMultiValueUiBlockCreate,
|
32
|
+
SelectorInputMultiValueUiBlockUpdate,
|
33
|
+
SelectorInputUiBlock,
|
34
|
+
SelectorInputUiBlockCreate,
|
35
|
+
SelectorInputUiBlockUpdate,
|
36
|
+
TableUiBlock,
|
37
|
+
TableUiBlockCreate,
|
38
|
+
TableUiBlockUpdate,
|
39
|
+
TextInputUiBlock,
|
40
|
+
TextInputUiBlockCreate,
|
41
|
+
TextInputUiBlockUpdate,
|
42
|
+
)
|
43
|
+
|
44
|
+
UiBlock = Union[
|
45
|
+
ButtonUiBlock,
|
46
|
+
ChipUiBlock,
|
47
|
+
DropdownMultiValueUiBlock,
|
48
|
+
DropdownUiBlock,
|
49
|
+
MarkdownUiBlock,
|
50
|
+
SearchInputMultiValueUiBlock,
|
51
|
+
SearchInputUiBlock,
|
52
|
+
SectionUiBlock,
|
53
|
+
SelectorInputMultiValueUiBlock,
|
54
|
+
SelectorInputUiBlock,
|
55
|
+
TableUiBlock,
|
56
|
+
TextInputUiBlock,
|
57
|
+
UnknownType,
|
58
|
+
]
|
59
|
+
|
60
|
+
UiBlockType = TypeVar(
|
61
|
+
"UiBlockType",
|
62
|
+
bound=UiBlock,
|
63
|
+
)
|
64
|
+
|
65
|
+
_UiBlockCreate = Union[
|
66
|
+
ButtonUiBlockCreate,
|
67
|
+
ChipUiBlockCreate,
|
68
|
+
DropdownMultiValueUiBlockCreate,
|
69
|
+
DropdownUiBlockCreate,
|
70
|
+
MarkdownUiBlockCreate,
|
71
|
+
SearchInputMultiValueUiBlockCreate,
|
72
|
+
SearchInputUiBlockCreate,
|
73
|
+
SectionUiBlockCreate,
|
74
|
+
SelectorInputMultiValueUiBlockCreate,
|
75
|
+
SelectorInputUiBlockCreate,
|
76
|
+
TableUiBlockCreate,
|
77
|
+
TextInputUiBlockCreate,
|
78
|
+
UnknownType,
|
79
|
+
]
|
80
|
+
|
81
|
+
_UiBlockUpdate = Union[
|
82
|
+
ButtonUiBlockUpdate,
|
83
|
+
ChipUiBlockUpdate,
|
84
|
+
DropdownMultiValueUiBlockUpdate,
|
85
|
+
DropdownUiBlockUpdate,
|
86
|
+
MarkdownUiBlockUpdate,
|
87
|
+
SearchInputMultiValueUiBlockUpdate,
|
88
|
+
SearchInputUiBlockUpdate,
|
89
|
+
SectionUiBlockUpdate,
|
90
|
+
SelectorInputMultiValueUiBlockUpdate,
|
91
|
+
SelectorInputUiBlockUpdate,
|
92
|
+
TableUiBlockUpdate,
|
93
|
+
TextInputUiBlockUpdate,
|
94
|
+
UnknownType,
|
95
|
+
]
|
96
|
+
|
97
|
+
_UI_BLOCK_MAPPINGS_CREATE = {
|
98
|
+
ButtonUiBlock: ButtonUiBlockCreate,
|
99
|
+
ChipUiBlock: ChipUiBlockCreate,
|
100
|
+
DropdownMultiValueUiBlock: DropdownMultiValueUiBlockCreate,
|
101
|
+
DropdownUiBlock: DropdownUiBlockCreate,
|
102
|
+
MarkdownUiBlock: MarkdownUiBlockCreate,
|
103
|
+
SearchInputMultiValueUiBlock: SearchInputMultiValueUiBlockCreate,
|
104
|
+
SearchInputUiBlock: SearchInputUiBlockCreate,
|
105
|
+
SectionUiBlock: SectionUiBlockCreate,
|
106
|
+
SelectorInputMultiValueUiBlock: SelectorInputMultiValueUiBlockCreate,
|
107
|
+
SelectorInputUiBlock: SelectorInputUiBlockCreate,
|
108
|
+
TableUiBlock: TableUiBlockCreate,
|
109
|
+
TextInputUiBlock: TextInputUiBlockCreate,
|
110
|
+
}
|
111
|
+
|
112
|
+
_UI_BLOCK_MAPPINGS_UPDATE = {
|
113
|
+
ButtonUiBlock: ButtonUiBlockUpdate,
|
114
|
+
ChipUiBlock: ChipUiBlockUpdate,
|
115
|
+
DropdownMultiValueUiBlock: DropdownMultiValueUiBlockUpdate,
|
116
|
+
DropdownUiBlock: DropdownUiBlockUpdate,
|
117
|
+
MarkdownUiBlock: MarkdownUiBlockUpdate,
|
118
|
+
SearchInputMultiValueUiBlock: SearchInputMultiValueUiBlockUpdate,
|
119
|
+
SearchInputUiBlock: SearchInputUiBlockUpdate,
|
120
|
+
SectionUiBlock: SectionUiBlockUpdate,
|
121
|
+
SelectorInputMultiValueUiBlock: SelectorInputMultiValueUiBlockUpdate,
|
122
|
+
SelectorInputUiBlock: SelectorInputUiBlockUpdate,
|
123
|
+
TableUiBlock: TableUiBlockUpdate,
|
124
|
+
TextInputUiBlock: TextInputUiBlockUpdate,
|
125
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
|
3
|
-
from benchling_sdk.apps.
|
3
|
+
from benchling_sdk.apps.config.cryptography_helpers import _create_key_type
|
4
4
|
from benchling_sdk.helpers.package_helpers import _required_packages_context, ExtrasPackage
|
5
5
|
|
6
6
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class UnsupportedConfigItemError(Exception):
|
2
|
+
"""
|
3
|
+
Unsupported config item error.
|
4
|
+
|
5
|
+
The manifest and configuration specified an item which the SDK is incapable of handling yet.
|
6
|
+
"""
|
7
|
+
|
8
|
+
pass
|
9
|
+
|
10
|
+
|
11
|
+
class MissingRequiredConfigItemError(Exception):
|
12
|
+
"""
|
13
|
+
Missing required config item error.
|
14
|
+
|
15
|
+
A config item was invoked as required, but missing in the configuration item store.
|
16
|
+
"""
|
17
|
+
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
class InaccessibleConfigItemError(Exception):
|
22
|
+
"""
|
23
|
+
Inaccessible config item error.
|
24
|
+
|
25
|
+
A config item linked resource was inaccessible (the caller does not have permissions).
|
26
|
+
"""
|
27
|
+
|
28
|
+
pass
|
29
|
+
|
30
|
+
|
31
|
+
class ConfigItemLinkedResourceError(Exception):
|
32
|
+
"""
|
33
|
+
Config item linked resource error.
|
34
|
+
|
35
|
+
A config item is not a type that has an associated linked resource.
|
36
|
+
"""
|
37
|
+
|
38
|
+
pass
|
@@ -0,0 +1,343 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Dict, List, Optional, OrderedDict, Protocol, Tuple, Union
|
4
|
+
|
5
|
+
from benchling_api_client.v2.extensions import UnknownType
|
6
|
+
from ordered_set import OrderedSet
|
7
|
+
|
8
|
+
from benchling_sdk.apps.config.errors import (
|
9
|
+
ConfigItemLinkedResourceError,
|
10
|
+
InaccessibleConfigItemError,
|
11
|
+
MissingRequiredConfigItemError,
|
12
|
+
UnsupportedConfigItemError,
|
13
|
+
)
|
14
|
+
from benchling_sdk.apps.config.types import ConfigItemPath, ConfigurationReference
|
15
|
+
from benchling_sdk.benchling import Benchling
|
16
|
+
from benchling_sdk.models import (
|
17
|
+
AppConfigItem,
|
18
|
+
EntitySchemaAppConfigItem,
|
19
|
+
FieldAppConfigItem,
|
20
|
+
GenericApiIdentifiedAppConfigItem,
|
21
|
+
InaccessibleResource,
|
22
|
+
LinkedAppConfigResourceSummary,
|
23
|
+
ListAppConfigurationItemsSort,
|
24
|
+
)
|
25
|
+
|
26
|
+
|
27
|
+
class _DefaultOrderedDict(OrderedDict):
|
28
|
+
_root_path: List[str]
|
29
|
+
|
30
|
+
def __init__(self, root_path: List[str]) -> None:
|
31
|
+
super().__init__()
|
32
|
+
self._root_path = root_path
|
33
|
+
|
34
|
+
def __missing__(self, key):
|
35
|
+
return ConfigItemWrapper(None, [*self._root_path, key])
|
36
|
+
|
37
|
+
|
38
|
+
class ConfigProvider(Protocol):
|
39
|
+
"""
|
40
|
+
Config provider.
|
41
|
+
|
42
|
+
Provides a list of ConfigurationReference.
|
43
|
+
"""
|
44
|
+
|
45
|
+
def config(self) -> List[ConfigurationReference]:
|
46
|
+
"""Implement to provide a list of configuration items, for instance from Benchling APIs."""
|
47
|
+
pass
|
48
|
+
|
49
|
+
|
50
|
+
class BenchlingConfigProvider(ConfigProvider):
|
51
|
+
"""
|
52
|
+
Benchling Config provider.
|
53
|
+
|
54
|
+
Provides a BenchlingAppConfiguration retrieved from Benchling's API.
|
55
|
+
"""
|
56
|
+
|
57
|
+
_app_id: str
|
58
|
+
_benchling: Benchling
|
59
|
+
|
60
|
+
def __init__(self, benchling: Benchling, app_id: str):
|
61
|
+
"""
|
62
|
+
Initialize Benchling Config Provider.
|
63
|
+
|
64
|
+
:param benchling: A Benchling instance.
|
65
|
+
:param app_id: The app_id from which to retrieve configuration.
|
66
|
+
"""
|
67
|
+
self._app_id = app_id
|
68
|
+
self._benchling = benchling
|
69
|
+
|
70
|
+
def config(self) -> List[ConfigurationReference]:
|
71
|
+
"""Provide a Benchling app configuration from Benchling's APIs."""
|
72
|
+
app_pages = self._benchling.apps.list_app_configuration_items(
|
73
|
+
app_id=self._app_id,
|
74
|
+
page_size=100,
|
75
|
+
sort=ListAppConfigurationItemsSort.CREATEDATASC,
|
76
|
+
)
|
77
|
+
|
78
|
+
# Eager load all config items for now since we don't yet have a way of lazily querying by path
|
79
|
+
all_config_pages = list(app_pages)
|
80
|
+
# Punt on UnknownType for now as apps using manifests with new types +
|
81
|
+
# older client could lead to unpredictable results
|
82
|
+
all_config_items = [
|
83
|
+
_supported_config_item(config_item) for page in all_config_pages for config_item in page
|
84
|
+
]
|
85
|
+
|
86
|
+
return all_config_items
|
87
|
+
|
88
|
+
|
89
|
+
class StaticConfigProvider(ConfigProvider):
|
90
|
+
"""
|
91
|
+
Static Config provider.
|
92
|
+
|
93
|
+
Provides a BenchlingAppConfiguration from a static declaration. Useful for mocking or testing.
|
94
|
+
"""
|
95
|
+
|
96
|
+
_configuration_items: List[ConfigurationReference]
|
97
|
+
|
98
|
+
def __init__(self, configuration_items: List[ConfigurationReference]):
|
99
|
+
"""
|
100
|
+
Initialize Static Config Provider.
|
101
|
+
|
102
|
+
:param configuration_items: The configuration items to return.
|
103
|
+
"""
|
104
|
+
self._configuration_items = configuration_items
|
105
|
+
|
106
|
+
def config(self) -> List[ConfigurationReference]:
|
107
|
+
"""Provide Benchling app configuration items from a static reference."""
|
108
|
+
return self._configuration_items
|
109
|
+
|
110
|
+
|
111
|
+
class ConfigItemWrapper:
|
112
|
+
"""
|
113
|
+
Config Item Wrapper.
|
114
|
+
|
115
|
+
A decorator class for AppConfigItem to assist with typesafe access to its values.
|
116
|
+
Access the `item` attribute for the original config item, if present.
|
117
|
+
"""
|
118
|
+
|
119
|
+
item: Optional[ConfigurationReference]
|
120
|
+
path: List[str]
|
121
|
+
|
122
|
+
def __init__(self, item: Optional[ConfigurationReference], path: List[str]) -> None:
|
123
|
+
"""Init Pathed Config Item."""
|
124
|
+
self.item = item
|
125
|
+
self.path = path
|
126
|
+
|
127
|
+
def required(self) -> RequiredConfigItemWrapper:
|
128
|
+
"""Return a `RequiredPathedConfigItem` to enforce that config item is not optional."""
|
129
|
+
if self.item is None:
|
130
|
+
raise MissingRequiredConfigItemError(
|
131
|
+
f"Required config item {self.path} is missing from the config store"
|
132
|
+
)
|
133
|
+
return RequiredConfigItemWrapper(self.item, self.path)
|
134
|
+
|
135
|
+
def linked_resource(self) -> Optional[LinkedAppConfigResourceSummary]:
|
136
|
+
"""
|
137
|
+
Return an optional LinkedAppConfigResourceSummary.
|
138
|
+
|
139
|
+
Raises exceptions if the config item is not of the type that supports linked resources,
|
140
|
+
or the linked resource is inaccessible.
|
141
|
+
"""
|
142
|
+
if self.item is not None:
|
143
|
+
# Python can't compare isinstance for Union types until Python 3.10 so just do this for now
|
144
|
+
# from: https://github.com/python/typing/discussions/1132#discussioncomment-2560441
|
145
|
+
# elif isinstance(self.item, get_args(LinkedResourceConfigurationReference)):
|
146
|
+
if isinstance(
|
147
|
+
self.item, (EntitySchemaAppConfigItem, FieldAppConfigItem, GenericApiIdentifiedAppConfigItem)
|
148
|
+
):
|
149
|
+
if self.item.linked_resource is None:
|
150
|
+
return None
|
151
|
+
elif isinstance(self.item.linked_resource, LinkedAppConfigResourceSummary):
|
152
|
+
return self.item.linked_resource
|
153
|
+
elif isinstance(self.item.linked_resource, InaccessibleResource):
|
154
|
+
raise InaccessibleConfigItemError(
|
155
|
+
f"The linked resource for config item {self.path} was inaccessible. "
|
156
|
+
f"The caller does not have permissions to the resource."
|
157
|
+
)
|
158
|
+
raise UnsupportedConfigItemError(
|
159
|
+
f"Unable to read app configuration with unsupported type: {self.item}"
|
160
|
+
)
|
161
|
+
raise ConfigItemLinkedResourceError(
|
162
|
+
f"Type mismatch: The config item {self.item} type never has a linked resource"
|
163
|
+
)
|
164
|
+
return None
|
165
|
+
|
166
|
+
def value(self) -> Union[str, float, int, bool, None]:
|
167
|
+
"""Return the value of the config item, if present."""
|
168
|
+
return self.item.value if self.item else None
|
169
|
+
|
170
|
+
def value_str(self) -> Optional[str]:
|
171
|
+
"""Return the value of the config item as a string, if present."""
|
172
|
+
return str(self.value()) if self.value() else None
|
173
|
+
|
174
|
+
def __bool__(self) -> bool:
|
175
|
+
return self.value() is not None
|
176
|
+
|
177
|
+
|
178
|
+
class RequiredConfigItemWrapper(ConfigItemWrapper):
|
179
|
+
"""
|
180
|
+
Required Config Item Wrapper.
|
181
|
+
|
182
|
+
A decorator class for AppConfigItem to assist with typesafe access to its values.
|
183
|
+
Enforces that a config item is present, and that it's value is not None.
|
184
|
+
Access the `item` attribute for the original config item.
|
185
|
+
"""
|
186
|
+
|
187
|
+
item: ConfigurationReference
|
188
|
+
|
189
|
+
def __init__(self, item: ConfigurationReference, path: List[str]) -> None:
|
190
|
+
"""Init Required Pathed Config Item."""
|
191
|
+
super().__init__(item, path)
|
192
|
+
|
193
|
+
def linked_resource(self) -> LinkedAppConfigResourceSummary:
|
194
|
+
"""
|
195
|
+
Return a LinkedAppConfigResourceSummary.
|
196
|
+
|
197
|
+
Raises exceptions if the config item is not of the type that supports linked resources,
|
198
|
+
or the linked resource is inaccessible.
|
199
|
+
"""
|
200
|
+
linked_resource = super().linked_resource()
|
201
|
+
if linked_resource is None:
|
202
|
+
raise MissingRequiredConfigItemError(
|
203
|
+
f"Required config item {self.path} is missing a linked resource"
|
204
|
+
)
|
205
|
+
return linked_resource
|
206
|
+
|
207
|
+
def value(self) -> Union[str, float, int, bool]:
|
208
|
+
"""Return the value of the config item."""
|
209
|
+
if self.item.value is None:
|
210
|
+
raise MissingRequiredConfigItemError(
|
211
|
+
f"Required config item {self.path} is missing from the config store"
|
212
|
+
)
|
213
|
+
return self.item.value
|
214
|
+
|
215
|
+
def value_str(self) -> str:
|
216
|
+
"""Return the value of the config item as a string."""
|
217
|
+
return str(self.value())
|
218
|
+
|
219
|
+
|
220
|
+
class ConfigItemStore:
|
221
|
+
"""
|
222
|
+
Dependency Link Store.
|
223
|
+
|
224
|
+
Marshals an app configuration from the configuration provider into an indexed structure.
|
225
|
+
Only retrieves app configuration once unless its cache is invalidated.
|
226
|
+
"""
|
227
|
+
|
228
|
+
_configuration_provider: ConfigProvider
|
229
|
+
_configuration: Optional[List[ConfigurationReference]] = None
|
230
|
+
_configuration_dict: Optional[Dict[ConfigItemPath, ConfigItemWrapper]] = None
|
231
|
+
_array_path_row_names: Dict[Tuple[str, ...], OrderedSet[str]] = dict()
|
232
|
+
|
233
|
+
def __init__(self, configuration_provider: ConfigProvider):
|
234
|
+
"""
|
235
|
+
Initialize Dependency Link Store.
|
236
|
+
|
237
|
+
:param configuration_provider: A ConfigProvider that will be invoked to provide the
|
238
|
+
underlying config from which to organize dependency links.
|
239
|
+
"""
|
240
|
+
self._configuration_provider = configuration_provider
|
241
|
+
self._array_path_row_names = dict()
|
242
|
+
|
243
|
+
@property
|
244
|
+
def configuration(self) -> List[ConfigurationReference]:
|
245
|
+
"""
|
246
|
+
Get the underlying configuration.
|
247
|
+
|
248
|
+
Return the raw, stored configuration. Can be used if the provided accessors are inadequate
|
249
|
+
to find particular configuration items.
|
250
|
+
"""
|
251
|
+
if not self._configuration:
|
252
|
+
self._configuration = self._configuration_provider.config()
|
253
|
+
return self._configuration
|
254
|
+
|
255
|
+
@property
|
256
|
+
def configuration_path_dict(self) -> Dict[ConfigItemPath, ConfigItemWrapper]:
|
257
|
+
"""
|
258
|
+
Config links.
|
259
|
+
|
260
|
+
Return a dict of configuration item paths to their corresponding configuration items.
|
261
|
+
"""
|
262
|
+
if not self._configuration_dict:
|
263
|
+
self._configuration_dict = {
|
264
|
+
tuple(item.path): ConfigItemWrapper(item, item.path) for item in self.configuration
|
265
|
+
}
|
266
|
+
return self._configuration_dict
|
267
|
+
|
268
|
+
def config_by_path(self, path: List[str]) -> ConfigItemWrapper:
|
269
|
+
"""
|
270
|
+
Config by path.
|
271
|
+
|
272
|
+
Find an app config item by its exact path match, if it exists. Does not search partial paths.
|
273
|
+
"""
|
274
|
+
# Since we eager load all config now, we know that missing path means it's not configured in Benchling
|
275
|
+
# Later if we support lazy loading, we'll need to differentiate what's in our cache versus missing
|
276
|
+
return self.configuration_path_dict.get(tuple(path), ConfigItemWrapper(None, path))
|
277
|
+
|
278
|
+
def config_keys_by_path(self, path: List[str]) -> OrderedSet[str]:
|
279
|
+
"""
|
280
|
+
Config keys by path.
|
281
|
+
|
282
|
+
Find a set of app config keys at the specified path, if any. Does not return keys that are nested
|
283
|
+
beyond the current level.
|
284
|
+
|
285
|
+
For instance, given paths:
|
286
|
+
["One", "Two"]
|
287
|
+
["One", "Two", "Three"]
|
288
|
+
["One", "Two", "Four"]
|
289
|
+
["One", "Two", "Three", "Five"]
|
290
|
+
["Zero", "One", "Two", "Three"]
|
291
|
+
|
292
|
+
The expected return from this method when path=["One", "Two"] is a set {"Three", "Four"}.
|
293
|
+
"""
|
294
|
+
# Convert path to tuple, as list is not hashable for dict keys
|
295
|
+
path_tuple = tuple(path)
|
296
|
+
if path_tuple not in self._array_path_row_names:
|
297
|
+
self._array_path_row_names[path_tuple] = OrderedSet(
|
298
|
+
[
|
299
|
+
config_item.path[len(path)]
|
300
|
+
# Use the list instead of configuration_dict to preserve order
|
301
|
+
for config_item in self.configuration
|
302
|
+
# The +1 is the name of the array row
|
303
|
+
if len(config_item.path) >= len(path) + 1
|
304
|
+
# Ignoring flake8 error E203 because black keeps putting in whitespace padding :
|
305
|
+
and config_item.path[0 : len(path_tuple)] == path # noqa: E203
|
306
|
+
and config_item.value is not None
|
307
|
+
]
|
308
|
+
)
|
309
|
+
return self._array_path_row_names[path_tuple]
|
310
|
+
|
311
|
+
def array_rows_to_dict(self, path: List[str]) -> OrderedDict[str, Dict[str, ConfigItemWrapper]]:
|
312
|
+
"""Given a path to the root of a config array, return each element as a named dict."""
|
313
|
+
array_keys = self.config_keys_by_path(path)
|
314
|
+
# Although we don't have a way of preserving order when pulling array elements from the API right now
|
315
|
+
# we should intentionally order these to accommodate a potential ordered future
|
316
|
+
array_elements_map = _DefaultOrderedDict(root_path=path)
|
317
|
+
for key in array_keys:
|
318
|
+
array_elements_map[key] = _DefaultOrderedDict(root_path=[*path, key])
|
319
|
+
# Don't care about order for the keys within a row, only the order of the rows themselves
|
320
|
+
for array_element_key in self.config_keys_by_path([*path, key]):
|
321
|
+
if self.config_by_path([*path, key, array_element_key]) is not None:
|
322
|
+
array_elements_map[key][array_element_key] = self.config_by_path(
|
323
|
+
[*path, key, array_element_key]
|
324
|
+
)
|
325
|
+
return array_elements_map
|
326
|
+
|
327
|
+
def invalidate_cache(self) -> None:
|
328
|
+
"""
|
329
|
+
Invalidate Cache.
|
330
|
+
|
331
|
+
Will force retrieval of configuration from the ConfigProvider the next time the link store is accessed.
|
332
|
+
"""
|
333
|
+
self._configuration = None
|
334
|
+
self._configuration_dict = None
|
335
|
+
self._array_path_row_names = dict()
|
336
|
+
|
337
|
+
|
338
|
+
def _supported_config_item(config_item: AppConfigItem) -> ConfigurationReference:
|
339
|
+
if isinstance(config_item, UnknownType):
|
340
|
+
raise UnsupportedConfigItemError(
|
341
|
+
f"Unable to read app configuration with unsupported type: {config_item}"
|
342
|
+
)
|
343
|
+
return config_item
|
@@ -0,0 +1,157 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import List, Optional, Union
|
3
|
+
|
4
|
+
from benchling_api_client.v2.beta.models.base_manifest_config import BaseManifestConfig
|
5
|
+
from benchling_api_client.v2.beta.models.dropdown_dependency import DropdownDependency
|
6
|
+
from benchling_api_client.v2.beta.models.dropdown_dependency_types import DropdownDependencyTypes
|
7
|
+
from benchling_api_client.v2.beta.models.entity_schema_dependency import EntitySchemaDependency
|
8
|
+
from benchling_api_client.v2.beta.models.entity_schema_dependency_type import EntitySchemaDependencyType
|
9
|
+
from benchling_api_client.v2.beta.models.field_definitions_manifest import FieldDefinitionsManifest
|
10
|
+
from benchling_api_client.v2.beta.models.manifest_array_config import ManifestArrayConfig
|
11
|
+
from benchling_api_client.v2.beta.models.manifest_float_scalar_config import ManifestFloatScalarConfig
|
12
|
+
from benchling_api_client.v2.beta.models.manifest_integer_scalar_config import ManifestIntegerScalarConfig
|
13
|
+
from benchling_api_client.v2.beta.models.manifest_scalar_config import ManifestScalarConfig
|
14
|
+
from benchling_api_client.v2.beta.models.manifest_text_scalar_config import ManifestTextScalarConfig
|
15
|
+
from benchling_api_client.v2.beta.models.resource_dependency import ResourceDependency
|
16
|
+
from benchling_api_client.v2.beta.models.resource_dependency_types import ResourceDependencyTypes
|
17
|
+
from benchling_api_client.v2.beta.models.scalar_config_types import ScalarConfigTypes
|
18
|
+
from benchling_api_client.v2.beta.models.schema_dependency import SchemaDependency
|
19
|
+
from benchling_api_client.v2.beta.models.schema_dependency_subtypes import SchemaDependencySubtypes
|
20
|
+
from benchling_api_client.v2.beta.models.schema_dependency_types import SchemaDependencyTypes
|
21
|
+
from benchling_api_client.v2.beta.models.workflow_task_schema_dependency import WorkflowTaskSchemaDependency
|
22
|
+
from benchling_api_client.v2.beta.models.workflow_task_schema_dependency_output import (
|
23
|
+
WorkflowTaskSchemaDependencyOutput,
|
24
|
+
)
|
25
|
+
from benchling_api_client.v2.beta.models.workflow_task_schema_dependency_type import (
|
26
|
+
WorkflowTaskSchemaDependencyType,
|
27
|
+
)
|
28
|
+
from benchling_api_client.v2.extensions import UnknownType
|
29
|
+
from benchling_api_client.v2.stable.extensions import NotPresentError
|
30
|
+
|
31
|
+
_ArrayElementDependency = Union[
|
32
|
+
SchemaDependency,
|
33
|
+
EntitySchemaDependency,
|
34
|
+
WorkflowTaskSchemaDependency,
|
35
|
+
DropdownDependency,
|
36
|
+
ResourceDependency,
|
37
|
+
ManifestScalarConfig,
|
38
|
+
]
|
39
|
+
|
40
|
+
|
41
|
+
class _UnsupportedSubTypeError(Exception):
|
42
|
+
"""Error when an unsupported subtype is encountered."""
|
43
|
+
|
44
|
+
pass
|
45
|
+
|
46
|
+
|
47
|
+
def _field_definitions_from_dependency(
|
48
|
+
dependency: Union[
|
49
|
+
EntitySchemaDependency,
|
50
|
+
SchemaDependency,
|
51
|
+
WorkflowTaskSchemaDependency,
|
52
|
+
WorkflowTaskSchemaDependencyOutput,
|
53
|
+
]
|
54
|
+
) -> List[FieldDefinitionsManifest]:
|
55
|
+
"""Safely return a list of field definitions from a schema dependency or empty list."""
|
56
|
+
try:
|
57
|
+
if hasattr(dependency, "field_definitions"):
|
58
|
+
return dependency.field_definitions
|
59
|
+
# We can't seem to handle this programmatically by checking isinstance() or field truthiness
|
60
|
+
except NotPresentError:
|
61
|
+
pass
|
62
|
+
return []
|
63
|
+
|
64
|
+
|
65
|
+
def _element_definition_from_dependency(dependency: ManifestArrayConfig) -> List[_ArrayElementDependency]:
|
66
|
+
"""Safely return an element definition as a list of dependencies from an array dependency or empty list."""
|
67
|
+
try:
|
68
|
+
if hasattr(dependency, "element_definition"):
|
69
|
+
return [
|
70
|
+
_fix_element_definition_deserialization(element) for element in dependency.element_definition
|
71
|
+
]
|
72
|
+
# We can't seem to handle this programmatically by checking isinstance() or field truthiness
|
73
|
+
except NotPresentError:
|
74
|
+
pass
|
75
|
+
return []
|
76
|
+
|
77
|
+
|
78
|
+
def _enum_from_dependency(
|
79
|
+
dependency: Union[
|
80
|
+
ManifestFloatScalarConfig,
|
81
|
+
ManifestIntegerScalarConfig,
|
82
|
+
ManifestTextScalarConfig,
|
83
|
+
]
|
84
|
+
) -> List:
|
85
|
+
"""Safely return an enum from a scalar config."""
|
86
|
+
try:
|
87
|
+
if hasattr(dependency, "enum"):
|
88
|
+
return dependency.enum
|
89
|
+
# We can't seem to handle this programmatically by checking isinstance() or field truthiness
|
90
|
+
except NotPresentError:
|
91
|
+
pass
|
92
|
+
return []
|
93
|
+
|
94
|
+
|
95
|
+
# TODO BNCH-57036 All element definitions currently deserialize to UnknownType. Hack around this temporarily
|
96
|
+
def _fix_element_definition_deserialization(
|
97
|
+
element: Union[UnknownType, _ArrayElementDependency]
|
98
|
+
) -> _ArrayElementDependency:
|
99
|
+
if isinstance(element, UnknownType):
|
100
|
+
if "type" in element.value:
|
101
|
+
element_type = element.value["type"]
|
102
|
+
if element_type == WorkflowTaskSchemaDependencyType.WORKFLOW_TASK_SCHEMA:
|
103
|
+
return WorkflowTaskSchemaDependency.from_dict(element.value)
|
104
|
+
elif element_type == EntitySchemaDependencyType.ENTITY_SCHEMA:
|
105
|
+
return EntitySchemaDependency.from_dict(element.value)
|
106
|
+
elif element_type in [member.value for member in SchemaDependencyTypes]:
|
107
|
+
return SchemaDependency.from_dict(element.value)
|
108
|
+
elif element_type == DropdownDependencyTypes.DROPDOWN:
|
109
|
+
return DropdownDependency.from_dict(element.value)
|
110
|
+
elif element_type in [member.value for member in ResourceDependencyTypes]:
|
111
|
+
return ResourceDependency.from_dict(element.value)
|
112
|
+
elif element_type in [member.value for member in ScalarConfigTypes]:
|
113
|
+
return type(element_type).from_dict(element.value)
|
114
|
+
raise NotImplementedError(f"No array deserialization fix for {element}")
|
115
|
+
return element
|
116
|
+
|
117
|
+
|
118
|
+
def _workflow_task_schema_output_from_dependency(
|
119
|
+
dependency: WorkflowTaskSchemaDependency,
|
120
|
+
) -> Optional[WorkflowTaskSchemaDependencyOutput]:
|
121
|
+
"""Safely return a workflow task schema output from a workflow task schema or None."""
|
122
|
+
try:
|
123
|
+
if hasattr(dependency, "output"):
|
124
|
+
return dependency.output
|
125
|
+
# We can't seem to handle this programmatically by checking isinstance() or output truthiness
|
126
|
+
except NotPresentError:
|
127
|
+
pass
|
128
|
+
return None
|
129
|
+
|
130
|
+
|
131
|
+
def _options_from_dependency(dependency: DropdownDependency) -> List[BaseManifestConfig]:
|
132
|
+
"""Safely return a list of options from a dropdown dependency or empty list."""
|
133
|
+
try:
|
134
|
+
if hasattr(dependency, "options"):
|
135
|
+
return dependency.options
|
136
|
+
# We can't seem to handle this programmatically by checking isinstance() or field truthiness
|
137
|
+
except NotPresentError:
|
138
|
+
pass
|
139
|
+
return []
|
140
|
+
|
141
|
+
|
142
|
+
def _subtype_from_entity_schema_dependency(
|
143
|
+
dependency: EntitySchemaDependency,
|
144
|
+
) -> Optional[SchemaDependencySubtypes]:
|
145
|
+
"""Safely return an entity schema dependency's subtype, if present."""
|
146
|
+
try:
|
147
|
+
if hasattr(dependency, "subtype") and dependency.subtype:
|
148
|
+
return dependency.subtype
|
149
|
+
# We can't seem to handle this programmatically by checking isinstance() or field truthiness
|
150
|
+
except NotPresentError:
|
151
|
+
pass
|
152
|
+
return None
|
153
|
+
|
154
|
+
|
155
|
+
def datetime_config_value_to_str(value: datetime) -> str:
|
156
|
+
"""Convert a datetime value to a valid string accepted by a datetime app config item."""
|
157
|
+
return value.strftime("%Y-%m-%d %I:%M:%S %p")
|