benchling-sdk 1.18.0__py3-none-any.whl → 1.18.1__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.
@@ -0,0 +1,186 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from functools import lru_cache
4
+ from typing import cast, List, Optional, Union
5
+
6
+ from benchling_api_client.v2.beta.extensions import Enums
7
+ from benchling_api_client.v2.beta.models.base_manifest_config import BaseManifestConfig
8
+ from benchling_api_client.v2.beta.models.dropdown_dependency import DropdownDependency
9
+ from benchling_api_client.v2.beta.models.dropdown_dependency_types import DropdownDependencyTypes
10
+ from benchling_api_client.v2.beta.models.entity_schema_dependency import EntitySchemaDependency
11
+ from benchling_api_client.v2.beta.models.entity_schema_dependency_type import EntitySchemaDependencyType
12
+ from benchling_api_client.v2.beta.models.field_definitions_manifest import FieldDefinitionsManifest
13
+ from benchling_api_client.v2.beta.models.manifest_array_config import ManifestArrayConfig
14
+ from benchling_api_client.v2.beta.models.manifest_float_scalar_config import ManifestFloatScalarConfig
15
+ from benchling_api_client.v2.beta.models.manifest_integer_scalar_config import ManifestIntegerScalarConfig
16
+ from benchling_api_client.v2.beta.models.manifest_scalar_config import ManifestScalarConfig
17
+ from benchling_api_client.v2.beta.models.manifest_text_scalar_config import ManifestTextScalarConfig
18
+ from benchling_api_client.v2.beta.models.resource_dependency import ResourceDependency
19
+ from benchling_api_client.v2.beta.models.resource_dependency_types import ResourceDependencyTypes
20
+ from benchling_api_client.v2.beta.models.schema_dependency import SchemaDependency
21
+ from benchling_api_client.v2.beta.models.schema_dependency_subtypes import SchemaDependencySubtypes
22
+ from benchling_api_client.v2.beta.models.schema_dependency_types import SchemaDependencyTypes
23
+ from benchling_api_client.v2.beta.models.workflow_task_schema_dependency import WorkflowTaskSchemaDependency
24
+ from benchling_api_client.v2.beta.models.workflow_task_schema_dependency_output import (
25
+ WorkflowTaskSchemaDependencyOutput,
26
+ )
27
+ from benchling_api_client.v2.beta.models.workflow_task_schema_dependency_type import (
28
+ WorkflowTaskSchemaDependencyType,
29
+ )
30
+ from benchling_api_client.v2.extensions import UnknownType
31
+ from benchling_api_client.v2.stable.extensions import NotPresentError
32
+
33
+ _ArrayElementDependency = Union[
34
+ SchemaDependency,
35
+ EntitySchemaDependency,
36
+ WorkflowTaskSchemaDependency,
37
+ DropdownDependency,
38
+ ResourceDependency,
39
+ ManifestScalarConfig,
40
+ ]
41
+
42
+
43
+ class _UnsupportedSubTypeError(Exception):
44
+ """Error when an unsupported subtype is encountered."""
45
+
46
+ pass
47
+
48
+
49
+ class _ScalarConfigTypes(Enums.KnownString):
50
+ """Enum type copied from an earlier version of benchling-api-client, for internal use only.
51
+
52
+ See BNCH-108704.
53
+ """
54
+
55
+ TEXT = "text"
56
+ FLOAT = "float"
57
+ INTEGER = "integer"
58
+ BOOLEAN = "boolean"
59
+ DATE = "date"
60
+ DATETIME = "datetime"
61
+ SECURE_TEXT = "secure_text"
62
+ JSON = "json"
63
+
64
+ def __str__(self) -> str:
65
+ return str(self.value)
66
+
67
+ @staticmethod
68
+ @lru_cache(maxsize=None)
69
+ def of_unknown(val: str) -> "_ScalarConfigTypes":
70
+ if not isinstance(val, str):
71
+ raise ValueError(f"Value of _ScalarConfigTypes must be a string (encountered: {val})")
72
+ newcls = Enum("_ScalarConfigTypes", {"_UNKNOWN": val}, type=Enums.UnknownString) # type: ignore
73
+ return cast(_ScalarConfigTypes, getattr(newcls, "_UNKNOWN"))
74
+
75
+
76
+ def _field_definitions_from_dependency(
77
+ dependency: Union[
78
+ EntitySchemaDependency,
79
+ SchemaDependency,
80
+ WorkflowTaskSchemaDependency,
81
+ WorkflowTaskSchemaDependencyOutput,
82
+ ]
83
+ ) -> List[FieldDefinitionsManifest]:
84
+ """Safely return a list of field definitions from a schema dependency or empty list."""
85
+ try:
86
+ if hasattr(dependency, "field_definitions"):
87
+ return dependency.field_definitions
88
+ # We can't seem to handle this programmatically by checking isinstance() or field truthiness
89
+ except NotPresentError:
90
+ pass
91
+ return []
92
+
93
+
94
+ def _element_definition_from_dependency(dependency: ManifestArrayConfig) -> List[_ArrayElementDependency]:
95
+ """Safely return an element definition as a list of dependencies from an array dependency or empty list."""
96
+ try:
97
+ if hasattr(dependency, "element_definition"):
98
+ return [
99
+ _fix_element_definition_deserialization(element) for element in dependency.element_definition
100
+ ]
101
+ # We can't seem to handle this programmatically by checking isinstance() or field truthiness
102
+ except NotPresentError:
103
+ pass
104
+ return []
105
+
106
+
107
+ def _enum_from_dependency(
108
+ dependency: Union[
109
+ ManifestFloatScalarConfig,
110
+ ManifestIntegerScalarConfig,
111
+ ManifestTextScalarConfig,
112
+ ]
113
+ ) -> List:
114
+ """Safely return an enum from a scalar config."""
115
+ try:
116
+ if hasattr(dependency, "enum"):
117
+ return dependency.enum
118
+ # We can't seem to handle this programmatically by checking isinstance() or field truthiness
119
+ except NotPresentError:
120
+ pass
121
+ return []
122
+
123
+
124
+ # TODO BNCH-57036 All element definitions currently deserialize to UnknownType. Hack around this temporarily
125
+ def _fix_element_definition_deserialization(
126
+ element: Union[UnknownType, _ArrayElementDependency]
127
+ ) -> _ArrayElementDependency:
128
+ if isinstance(element, UnknownType):
129
+ if "type" in element.value:
130
+ element_type = element.value["type"]
131
+ if element_type == WorkflowTaskSchemaDependencyType.WORKFLOW_TASK_SCHEMA:
132
+ return WorkflowTaskSchemaDependency.from_dict(element.value)
133
+ elif element_type == EntitySchemaDependencyType.ENTITY_SCHEMA:
134
+ return EntitySchemaDependency.from_dict(element.value)
135
+ elif element_type in [member.value for member in SchemaDependencyTypes]:
136
+ return SchemaDependency.from_dict(element.value)
137
+ elif element_type == DropdownDependencyTypes.DROPDOWN:
138
+ return DropdownDependency.from_dict(element.value)
139
+ elif element_type in [member.value for member in ResourceDependencyTypes]:
140
+ return ResourceDependency.from_dict(element.value)
141
+ elif element_type in [member.value for member in _ScalarConfigTypes]:
142
+ return type(element_type).from_dict(element.value)
143
+ raise NotImplementedError(f"No array deserialization fix for {element}")
144
+ return element
145
+
146
+
147
+ def _workflow_task_schema_output_from_dependency(
148
+ dependency: WorkflowTaskSchemaDependency,
149
+ ) -> Optional[WorkflowTaskSchemaDependencyOutput]:
150
+ """Safely return a workflow task schema output from a workflow task schema or None."""
151
+ try:
152
+ if hasattr(dependency, "output"):
153
+ return dependency.output
154
+ # We can't seem to handle this programmatically by checking isinstance() or output truthiness
155
+ except NotPresentError:
156
+ pass
157
+ return None
158
+
159
+
160
+ def _options_from_dependency(dependency: DropdownDependency) -> List[BaseManifestConfig]:
161
+ """Safely return a list of options from a dropdown dependency or empty list."""
162
+ try:
163
+ if hasattr(dependency, "options"):
164
+ return dependency.options
165
+ # We can't seem to handle this programmatically by checking isinstance() or field truthiness
166
+ except NotPresentError:
167
+ pass
168
+ return []
169
+
170
+
171
+ def _subtype_from_entity_schema_dependency(
172
+ dependency: EntitySchemaDependency,
173
+ ) -> Optional[SchemaDependencySubtypes]:
174
+ """Safely return an entity schema dependency's subtype, if present."""
175
+ try:
176
+ if hasattr(dependency, "subtype") and dependency.subtype:
177
+ return dependency.subtype
178
+ # We can't seem to handle this programmatically by checking isinstance() or field truthiness
179
+ except NotPresentError:
180
+ pass
181
+ return None
182
+
183
+
184
+ def datetime_config_value_to_str(value: datetime) -> str:
185
+ """Convert a datetime value to a valid string accepted by a datetime app config item."""
186
+ return value.strftime("%Y-%m-%d %I:%M:%S %p")
@@ -0,0 +1,635 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import date, datetime
4
+ import json
5
+ import random
6
+ import string
7
+ from typing import cast, Dict, get_args, List, Optional, Protocol, Union
8
+
9
+ from benchling_api_client.v2.beta.models.base_manifest_config import BaseManifestConfig
10
+ from benchling_api_client.v2.beta.models.benchling_app_manifest import BenchlingAppManifest
11
+ from benchling_api_client.v2.beta.models.dropdown_dependency import DropdownDependency
12
+ from benchling_api_client.v2.beta.models.entity_schema_dependency import EntitySchemaDependency
13
+ from benchling_api_client.v2.beta.models.field_definitions_manifest import FieldDefinitionsManifest
14
+ from benchling_api_client.v2.beta.models.manifest_array_config import ManifestArrayConfig
15
+ from benchling_api_client.v2.beta.models.manifest_boolean_scalar_config import ManifestBooleanScalarConfig
16
+ from benchling_api_client.v2.beta.models.manifest_date_scalar_config import ManifestDateScalarConfig
17
+ from benchling_api_client.v2.beta.models.manifest_datetime_scalar_config import ManifestDatetimeScalarConfig
18
+ from benchling_api_client.v2.beta.models.manifest_float_scalar_config import ManifestFloatScalarConfig
19
+ from benchling_api_client.v2.beta.models.manifest_integer_scalar_config import ManifestIntegerScalarConfig
20
+ from benchling_api_client.v2.beta.models.manifest_json_scalar_config import ManifestJsonScalarConfig
21
+ from benchling_api_client.v2.beta.models.manifest_scalar_config import ManifestScalarConfig
22
+ from benchling_api_client.v2.beta.models.manifest_secure_text_scalar_config import (
23
+ ManifestSecureTextScalarConfig,
24
+ )
25
+ from benchling_api_client.v2.beta.models.manifest_text_scalar_config import ManifestTextScalarConfig
26
+ from benchling_api_client.v2.beta.models.resource_dependency import ResourceDependency
27
+ from benchling_api_client.v2.beta.models.schema_dependency import SchemaDependency
28
+ from benchling_api_client.v2.beta.models.schema_dependency_subtypes import (
29
+ SchemaDependencySubtypes as SchemaDependencySubtypesBeta,
30
+ )
31
+ from benchling_api_client.v2.beta.models.workflow_task_schema_dependency import WorkflowTaskSchemaDependency
32
+ from benchling_api_client.v2.extensions import UnknownType
33
+ from benchling_api_client.v2.stable.types import UNSET, Unset
34
+
35
+ from benchling_sdk.apps.config.decryption_provider import BaseDecryptionProvider
36
+ from benchling_sdk.apps.config.errors import UnsupportedConfigItemError
37
+ from benchling_sdk.apps.config.framework import _supported_config_item, ConfigItemStore, StaticConfigProvider
38
+ from benchling_sdk.apps.config.helpers import (
39
+ _element_definition_from_dependency,
40
+ _enum_from_dependency,
41
+ _field_definitions_from_dependency,
42
+ _options_from_dependency,
43
+ _ScalarConfigTypes,
44
+ _subtype_from_entity_schema_dependency,
45
+ _workflow_task_schema_output_from_dependency,
46
+ datetime_config_value_to_str,
47
+ )
48
+ from benchling_sdk.apps.config.types import ConfigurationReference
49
+ from benchling_sdk.apps.types import JsonType
50
+ from benchling_sdk.helpers.logging_helpers import log_stability_warning, StabilityLevel
51
+ from benchling_sdk.models import (
52
+ AppConfigItem,
53
+ ArrayElementAppConfigItem,
54
+ ArrayElementAppConfigItemType,
55
+ BooleanAppConfigItem,
56
+ BooleanAppConfigItemType,
57
+ DateAppConfigItem,
58
+ DateAppConfigItemType,
59
+ DatetimeAppConfigItem,
60
+ DatetimeAppConfigItemType,
61
+ EntitySchemaAppConfigItem,
62
+ EntitySchemaAppConfigItemType,
63
+ FieldAppConfigItem,
64
+ FieldAppConfigItemType,
65
+ FloatAppConfigItem,
66
+ FloatAppConfigItemType,
67
+ GenericApiIdentifiedAppConfigItem,
68
+ GenericApiIdentifiedAppConfigItemType,
69
+ IntegerAppConfigItem,
70
+ IntegerAppConfigItemType,
71
+ JsonAppConfigItem,
72
+ JsonAppConfigItemType,
73
+ LinkedAppConfigResourceSummary,
74
+ SchemaDependencySubtypes,
75
+ SecureTextAppConfigItem,
76
+ SecureTextAppConfigItemType,
77
+ TextAppConfigItem,
78
+ TextAppConfigItemType,
79
+ )
80
+
81
+ ManifestDependencies = Union[
82
+ DropdownDependency,
83
+ EntitySchemaDependency,
84
+ ManifestArrayConfig,
85
+ ManifestScalarConfig,
86
+ ResourceDependency,
87
+ SchemaDependency,
88
+ WorkflowTaskSchemaDependency,
89
+ UnknownType,
90
+ ]
91
+
92
+ log_stability_warning(StabilityLevel.BETA)
93
+
94
+
95
+ class MockDecryptionFunction(Protocol):
96
+ """Mock out a decryption function for use with secure text."""
97
+
98
+ def __call__(self, ciphertext: str) -> str:
99
+ """Return a string representing plaintext given input ciphertext."""
100
+
101
+
102
+ class MockDecryptionProvider(BaseDecryptionProvider):
103
+ """
104
+ Mock Decryption Provider.
105
+
106
+ A generic class mocking a BaseDecryptionProvider. Can be passed a function to mock arbitrary decryption.
107
+
108
+ It's recommended to extend this class or use a specific implementation instead of initializing an instance.
109
+ """
110
+
111
+ _mock_decryption_function: MockDecryptionFunction
112
+
113
+ def __init__(self, mock_decryption_function: MockDecryptionFunction):
114
+ """
115
+ Init Mock Decryption Provider.
116
+
117
+ Pass a function that returns desired mocked plaintext given ciphertext.
118
+ """
119
+ self._mock_decryption_function = mock_decryption_function
120
+
121
+ def decrypt(self, ciphertext: str) -> str:
122
+ """
123
+ Decrypt.
124
+
125
+ Invokes the mocked decryption function provided when instantiating the class to return a "decrypted" value.
126
+ """
127
+ return self._mock_decryption_function(ciphertext)
128
+
129
+
130
+ class MockConfigItemStore(ConfigItemStore):
131
+ """
132
+ Mock App Config.
133
+
134
+ A helper class for easily mocking app config in various permutations.
135
+
136
+ Easily mock all config items from a manifest model (which can be loaded from
137
+ `benchling_sdk.apps.helpers.manifest_helpers.manifest_from_file()`.
138
+ """
139
+
140
+ _config_items: List[AppConfigItem]
141
+
142
+ def __init__(self, config_items: List[AppConfigItem]):
143
+ """
144
+ Init Mock Benchling App Config.
145
+
146
+ The class can be initialized by providing a list of AppConfigItem, but the recommended
147
+ usage is to mock directly from a manifest, like `MockBenchlingAppConfig.from_manifest()`
148
+ """
149
+ super().__init__(StaticConfigProvider([_supported_config_item(item) for item in config_items]))
150
+ self._config_items = config_items
151
+
152
+ @classmethod
153
+ def from_manifest(cls, manifest: BenchlingAppManifest) -> MockConfigItemStore:
154
+ """
155
+ From Manifest.
156
+
157
+ Reads a manifest amd mocks out all dependencies.
158
+ """
159
+ config_items = mock_app_config_items_from_manifest(manifest)
160
+ return cls(config_items)
161
+
162
+ def with_replacement(self, replacement: AppConfigItem) -> MockConfigItemStore:
163
+ """
164
+ With Replacement.
165
+
166
+ Returns a new MockBenchlingAppConfig with the app config item at the specified path replaced.
167
+ """
168
+ # list() solves "List is invariant"
169
+ replaced_app_config = replace_mocked_config_item_by_path(
170
+ list(self.config_items), _config_path(replacement), replacement
171
+ )
172
+ return MockConfigItemStore(replaced_app_config)
173
+
174
+ def with_replacements(self, replacements: List[AppConfigItem]) -> MockConfigItemStore:
175
+ """
176
+ With Replacement.
177
+
178
+ Returns a new MockBenchlingAppConfig with the app config item at the specified path replaced.
179
+ """
180
+ # list() solves "List is invariant"
181
+ replaced_app_config: List[AppConfigItem] = list(self.config_items)
182
+ for replacement in replacements:
183
+ replaced_app_config = replace_mocked_config_item_by_path(
184
+ list(replaced_app_config), _config_path(replacement), replacement
185
+ )
186
+ return MockConfigItemStore(list(replaced_app_config))
187
+
188
+ @property
189
+ def config_items(self) -> List[ConfigurationReference]:
190
+ """List the config items in the mock app config, excluding any unknown types."""
191
+ return [_supported_config_item(config_item) for config_item in self._config_items]
192
+
193
+ @property
194
+ def config_items_with_unknown(self) -> List[AppConfigItem]:
195
+ """List the config items in the mock app config, including any unknown types."""
196
+ return self._config_items
197
+
198
+
199
+ class MockDecryptionProviderStatic(MockDecryptionProvider):
200
+ """
201
+ Mock Decryption Provider Static.
202
+
203
+ Always return the same "decrypted" value regardless of what ciphertext is passed.
204
+ Useful if you only have a single secret value.
205
+ """
206
+
207
+ def __init__(self, decrypt_value: str):
208
+ """
209
+ Init Mock Decryption Provider Static.
210
+
211
+ Supply the string to always be returned.
212
+ """
213
+
214
+ def decrypt(ciphertext: str) -> str:
215
+ return decrypt_value
216
+
217
+ super().__init__(decrypt)
218
+
219
+
220
+ class MockDecryptionProviderMapped(MockDecryptionProvider):
221
+ """
222
+ Mock Decryption Provider Mapped.
223
+
224
+ Returns a "decrypted" value based on the input ciphertext.
225
+ Useful if you have multiple secrets to mock simultaneously.
226
+ """
227
+
228
+ def __init__(self, decrypt_mapping: Dict[str, str]):
229
+ """
230
+ Init Mock Decryption Provider Mapped.
231
+
232
+ Supply the dictionary mapping with ciphertext as keys and plaintext as values.
233
+ Any ciphertext decrypted without a corresponding value will result in a KeyError.
234
+ """
235
+
236
+ def decrypt(ciphertext: str) -> str:
237
+ return decrypt_mapping[ciphertext]
238
+
239
+ super().__init__(decrypt)
240
+
241
+
242
+ def mock_app_config_items_from_manifest(manifest: BenchlingAppManifest) -> List[AppConfigItem]:
243
+ """
244
+ Mock Benchling App Config Items.
245
+
246
+ This method accepts an app manifest model and creates mocked values for app the app config.
247
+
248
+ The concrete mocked out values, such as API Ids and schema names are nonsensical and random,
249
+ but are valid.
250
+
251
+ Code should avoid relying on specific values or conventions (such as API prefixes). If
252
+ specific dependency values need to be tested in isolation, the caller can selectively
253
+ override the randomized values with replace_mocked_config_item_by_path().
254
+ """
255
+ root_config_items = [_mock_dependency(dependency) for dependency in manifest.configuration]
256
+ return [config_item for sub_config_items in root_config_items for config_item in sub_config_items]
257
+
258
+
259
+ def replace_mocked_config_item_by_path(
260
+ original_config: List[AppConfigItem], replacement_path: List[str], replacement_item: AppConfigItem
261
+ ) -> List[AppConfigItem]:
262
+ """Return an updated list of app config items with a specific config item replaced with the provided mock."""
263
+ replaced_config = [config for config in original_config if _config_path(config) != replacement_path]
264
+ replaced_config.append(replacement_item)
265
+ return replaced_config
266
+
267
+
268
+ def mock_bool_app_config_item(path: List[str], value: Optional[bool]) -> BooleanAppConfigItem:
269
+ """Mock a bool app config item with a path and specified value."""
270
+ return BooleanAppConfigItem(
271
+ path=path,
272
+ value=value,
273
+ type=BooleanAppConfigItemType.BOOLEAN,
274
+ id=_random_string("aci_"),
275
+ )
276
+
277
+
278
+ def mock_date_app_config_item(path: List[str], value: Optional[date]) -> DateAppConfigItem:
279
+ """Mock a date app config item with a path and specified value."""
280
+ return DateAppConfigItem(
281
+ path=path,
282
+ value=str(value) if value is not None else None,
283
+ type=DateAppConfigItemType.DATE,
284
+ id=_random_string("aci_"),
285
+ )
286
+
287
+
288
+ def mock_datetime_app_config_item(path: List[str], value: Optional[datetime]) -> DatetimeAppConfigItem:
289
+ """Mock a datetime app config item with a path and specified value."""
290
+ return DatetimeAppConfigItem(
291
+ path=path,
292
+ value=datetime_config_value_to_str(value) if value else None,
293
+ type=DatetimeAppConfigItemType.DATETIME,
294
+ id=_random_string("aci_"),
295
+ )
296
+
297
+
298
+ def mock_float_app_config_item(path: List[str], value: Optional[float]) -> FloatAppConfigItem:
299
+ """Mock a float app config item with a path and specified value."""
300
+ return FloatAppConfigItem(
301
+ path=path,
302
+ value=value,
303
+ type=FloatAppConfigItemType.FLOAT,
304
+ id=_random_string("aci_"),
305
+ )
306
+
307
+
308
+ def mock_int_app_config_item(path: List[str], value: Optional[int]) -> IntegerAppConfigItem:
309
+ """Mock an int app config item with a path and specified value."""
310
+ return IntegerAppConfigItem(
311
+ path=path,
312
+ value=value,
313
+ type=IntegerAppConfigItemType.INTEGER,
314
+ id=_random_string("aci_"),
315
+ )
316
+
317
+
318
+ def mock_json_app_config_item(path: List[str], value: Optional[JsonType]) -> JsonAppConfigItem:
319
+ """Mock an int app config item with a path and specified value."""
320
+ return JsonAppConfigItem(
321
+ path=path,
322
+ value=json.dumps(value) if value is not None else None,
323
+ type=JsonAppConfigItemType.JSON,
324
+ id=_random_string("aci_"),
325
+ )
326
+
327
+
328
+ def mock_secure_text_app_config_item(path: List[str], value: Optional[str]) -> SecureTextAppConfigItem:
329
+ """Mock a secure text app config item with a path and specified value."""
330
+ return SecureTextAppConfigItem(
331
+ path=path,
332
+ value=value,
333
+ type=SecureTextAppConfigItemType.SECURE_TEXT,
334
+ id=_random_string("aci_"),
335
+ )
336
+
337
+
338
+ def mock_text_app_config_item(path: List[str], value: Optional[str]) -> TextAppConfigItem:
339
+ """Mock a text app config item with a path and specified value."""
340
+ return TextAppConfigItem(
341
+ path=path,
342
+ value=value,
343
+ type=TextAppConfigItemType.TEXT,
344
+ id=_random_string("aci_"),
345
+ )
346
+
347
+
348
+ def _mock_dependency(
349
+ dependency: ManifestDependencies,
350
+ parent_path: Optional[List[str]] = None,
351
+ ) -> List[AppConfigItem]:
352
+ """Mock a dependency from its manifest definition."""
353
+ parent_path = parent_path if parent_path else []
354
+ # MyPy has trouble inferring lists with [config_item] + sub_items so use the syntax like:
355
+ # [*[config_item], *sub_items]
356
+ # See https://github.com/python/mypy/issues/3933#issuecomment-808739063
357
+ if isinstance(dependency, DropdownDependency):
358
+ linked_resource_id = _random_string("val_")
359
+ config_item = GenericApiIdentifiedAppConfigItem(
360
+ id=_random_string("aci_"),
361
+ path=parent_path + [dependency.name],
362
+ type=GenericApiIdentifiedAppConfigItemType.DROPDOWN,
363
+ value=_random_string("val_"),
364
+ linked_resource=_mock_linked_resource(linked_resource_id),
365
+ )
366
+ sub_items = [
367
+ _mock_subdependency(subdependency, dependency, parent_path=parent_path)
368
+ for subdependency in _options_from_dependency(dependency)
369
+ ]
370
+ return [*[config_item], *sub_items]
371
+ elif isinstance(dependency, EntitySchemaDependency):
372
+ linked_resource_id = _random_string("val_")
373
+ subtype = _subtype_from_entity_schema_dependency(dependency)
374
+ optional_subtype: Union[SchemaDependencySubtypes, Unset] = (
375
+ _convert_entity_subtype(subtype) if subtype is not None else UNSET
376
+ )
377
+ entity_item = EntitySchemaAppConfigItem(
378
+ id=_random_string("aci_"),
379
+ path=parent_path + [dependency.name],
380
+ type=EntitySchemaAppConfigItemType.ENTITY_SCHEMA,
381
+ subtype=optional_subtype,
382
+ value=_random_string("val_"),
383
+ linked_resource=_mock_linked_resource(linked_resource_id),
384
+ )
385
+ sub_items = [
386
+ _mock_subdependency(subdependency, dependency, parent_path=parent_path)
387
+ for subdependency in _field_definitions_from_dependency(dependency)
388
+ ]
389
+ return [*[entity_item], *sub_items]
390
+ elif isinstance(dependency, SchemaDependency):
391
+ linked_resource_id = _random_string("val_")
392
+ config_item = GenericApiIdentifiedAppConfigItem(
393
+ id=_random_string("aci_"),
394
+ path=parent_path + [dependency.name],
395
+ type=GenericApiIdentifiedAppConfigItemType(dependency.type),
396
+ value=_random_string("val_"),
397
+ linked_resource=_mock_linked_resource(linked_resource_id),
398
+ )
399
+ sub_items = [
400
+ _mock_subdependency(subdependency, dependency, parent_path=parent_path)
401
+ for subdependency in _field_definitions_from_dependency(dependency)
402
+ ]
403
+ return [*[config_item], *sub_items]
404
+ elif isinstance(dependency, WorkflowTaskSchemaDependency):
405
+ linked_resource_id = _random_string("val_")
406
+ config_item = GenericApiIdentifiedAppConfigItem(
407
+ id=_random_string("aci_"),
408
+ path=parent_path + [dependency.name],
409
+ type=GenericApiIdentifiedAppConfigItemType.WORKFLOW_TASK_SCHEMA,
410
+ value=linked_resource_id,
411
+ linked_resource=_mock_linked_resource(linked_resource_id),
412
+ )
413
+ sub_items = [
414
+ _mock_subdependency(subdependency, dependency, parent_path=parent_path)
415
+ for subdependency in _field_definitions_from_dependency(dependency)
416
+ ]
417
+ workflow_task_output = _workflow_task_schema_output_from_dependency(dependency)
418
+ if workflow_task_output:
419
+ output_fields = _field_definitions_from_dependency(workflow_task_output)
420
+ output_items = [
421
+ _mock_workflow_output_subdependency(subdependency, dependency, parent_path=parent_path)
422
+ for subdependency in output_fields
423
+ ]
424
+ sub_items += output_items
425
+ return [*[config_item], *sub_items]
426
+ # Python can't compare isinstance for Union types until Python 3.10 so just do this for now
427
+ # from: https://github.com/python/typing/discussions/1132#discussioncomment-2560441
428
+ elif isinstance(dependency, get_args(ManifestScalarConfig)):
429
+ # Ignore type since MyPy definitely can't tell from above
430
+ return [_mock_scalar_dependency(dependency, parent_path=parent_path)] # type: ignore
431
+ elif isinstance(dependency, ManifestArrayConfig):
432
+ return _mock_array_dependency(dependency, parent_path=parent_path)
433
+ elif isinstance(dependency, UnknownType):
434
+ return [UnknownType(value="Unknown")]
435
+ else:
436
+ linked_resource_id = _random_string("val_")
437
+ return [
438
+ GenericApiIdentifiedAppConfigItem(
439
+ id=_random_string("aci_"),
440
+ path=parent_path + [dependency.name],
441
+ type=GenericApiIdentifiedAppConfigItemType(dependency.type),
442
+ value=linked_resource_id,
443
+ linked_resource=_mock_linked_resource(linked_resource_id),
444
+ )
445
+ ]
446
+
447
+
448
+ def _convert_entity_subtype(manifest_subtype: SchemaDependencySubtypesBeta) -> SchemaDependencySubtypes:
449
+ # Manifest types and config types should be equivalent but are technically different Enums
450
+ source_value: str = manifest_subtype.value
451
+ return SchemaDependencySubtypes(source_value)
452
+
453
+
454
+ def _mock_scalar_dependency(
455
+ dependency: ManifestScalarConfig, parent_path: Optional[List[str]] = None
456
+ ) -> AppConfigItem:
457
+ parent_path = parent_path if parent_path else []
458
+ if isinstance(dependency, ManifestBooleanScalarConfig):
459
+ bool_value = cast(bool, _mock_scalar_value(dependency))
460
+ bool_config = mock_bool_app_config_item([dependency.name], bool_value)
461
+ return _append_config_item_path(bool_config, parent_path)
462
+ elif isinstance(dependency, ManifestDateScalarConfig):
463
+ date_value = cast(date, _mock_scalar_value(dependency))
464
+ date_config = mock_date_app_config_item([dependency.name], date_value)
465
+ return _append_config_item_path(date_config, parent_path)
466
+ elif isinstance(dependency, ManifestDatetimeScalarConfig):
467
+ datetime_value = cast(datetime, _mock_scalar_value(dependency))
468
+ datetime_config = mock_datetime_app_config_item([dependency.name], datetime_value)
469
+ return _append_config_item_path(datetime_config, parent_path)
470
+ elif isinstance(dependency, ManifestFloatScalarConfig):
471
+ float_value = cast(float, _mock_scalar_value(dependency))
472
+ float_config = mock_float_app_config_item([dependency.name], float_value)
473
+ return _append_config_item_path(float_config, parent_path)
474
+ elif isinstance(dependency, ManifestIntegerScalarConfig):
475
+ int_value = cast(int, _mock_scalar_value(dependency))
476
+ int_config = mock_int_app_config_item([dependency.name], int_value)
477
+ return _append_config_item_path(int_config, parent_path)
478
+ elif isinstance(dependency, ManifestJsonScalarConfig):
479
+ json_value = cast(JsonType, _mock_scalar_value(dependency))
480
+ json_config = mock_json_app_config_item([dependency.name], json_value)
481
+ return _append_config_item_path(json_config, parent_path)
482
+ elif isinstance(dependency, ManifestSecureTextScalarConfig):
483
+ secure_text_value = cast(str, _mock_scalar_value(dependency))
484
+ secure_text_config = mock_secure_text_app_config_item([dependency.name], secure_text_value)
485
+ return _append_config_item_path(secure_text_config, parent_path)
486
+ else:
487
+ assert not isinstance(dependency, UnknownType), f"Unable to mock unknown type {dependency}"
488
+ text_value = cast(str, _mock_scalar_value(dependency))
489
+ text_config = mock_text_app_config_item([dependency.name], text_value)
490
+ return _append_config_item_path(text_config, parent_path)
491
+
492
+
493
+ def _append_config_item_path(config_item: AppConfigItem, parent_path: List[str]) -> AppConfigItem:
494
+ if isinstance(config_item, UnknownType):
495
+ return config_item
496
+ config_item.path = parent_path + config_item.path
497
+ return config_item
498
+
499
+
500
+ def _mock_array_dependency(
501
+ dependency: ManifestArrayConfig, parent_path: Optional[List[str]] = None, rows: int = 1
502
+ ) -> List[AppConfigItem]:
503
+ config_rows = []
504
+ parent_path = parent_path if parent_path else []
505
+ for i in range(rows):
506
+ row = _mock_array_row(dependency, parent_path=parent_path)
507
+ elements = _element_definition_from_dependency(dependency)
508
+ element_configs = [_mock_dependency(element, row.path) for element in elements]
509
+ flattened_configs = [element for sublist in element_configs for element in sublist]
510
+ config_rows.append(row)
511
+ config_rows.extend(flattened_configs)
512
+ return config_rows
513
+
514
+
515
+ def _mock_array_row(dependency: ManifestArrayConfig, parent_path: Optional[List[str]] = None):
516
+ row_name = _random_string("Row ")
517
+ parent_path = parent_path if parent_path else []
518
+ return ArrayElementAppConfigItem(
519
+ id=_random_string("aci_"),
520
+ path=parent_path + [dependency.name, row_name],
521
+ type=ArrayElementAppConfigItemType.ARRAY_ELEMENT,
522
+ value=row_name,
523
+ )
524
+
525
+
526
+ def _mock_scalar_with_enum(dependency: ManifestScalarConfig) -> Union[float, int, str]:
527
+ assert isinstance(
528
+ dependency, (ManifestFloatScalarConfig, ManifestIntegerScalarConfig, ManifestTextScalarConfig)
529
+ )
530
+ value = random.choice(dependency.enum)
531
+ if isinstance(dependency, ManifestFloatScalarConfig):
532
+ return cast(float, value)
533
+ elif isinstance(dependency, ManifestIntegerScalarConfig):
534
+ return cast(int, value)
535
+ return str(value)
536
+
537
+
538
+ def _is_scalar_with_enum(dependency: ManifestScalarConfig) -> bool:
539
+ if isinstance(
540
+ dependency, (ManifestFloatScalarConfig, ManifestIntegerScalarConfig, ManifestTextScalarConfig)
541
+ ):
542
+ # MyPy doesn't find this to be truthy without a specific len check
543
+ return len(_enum_from_dependency(dependency)) > 0
544
+ return False
545
+
546
+
547
+ def _mock_subdependency(
548
+ subdependency: Union[BaseManifestConfig, FieldDefinitionsManifest],
549
+ parent_config,
550
+ parent_path: Optional[List[str]] = None,
551
+ ) -> AppConfigItem:
552
+ parent_path = parent_path if parent_path else []
553
+ if isinstance(parent_config, DropdownDependency):
554
+ linked_resource_id = _random_string("opt_")
555
+ return GenericApiIdentifiedAppConfigItem(
556
+ id=_random_string("aci_"),
557
+ path=parent_path + [parent_config.name, subdependency.name],
558
+ type=GenericApiIdentifiedAppConfigItemType.DROPDOWN_OPTION,
559
+ value=linked_resource_id,
560
+ linked_resource=_mock_linked_resource(linked_resource_id),
561
+ )
562
+ elif isinstance(parent_config, (EntitySchemaDependency, SchemaDependency, WorkflowTaskSchemaDependency)):
563
+ path = [parent_config.name, subdependency.name]
564
+ linked_resource_id = _random_string("tsf_")
565
+ app_config = FieldAppConfigItem(
566
+ id=_random_string("aci_"),
567
+ path=parent_path + path,
568
+ type=FieldAppConfigItemType.FIELD,
569
+ value=linked_resource_id,
570
+ linked_resource=_mock_linked_resource(linked_resource_id),
571
+ )
572
+ return app_config
573
+ raise RuntimeError(f"Can't mock unsupported dependency ({subdependency})")
574
+
575
+
576
+ def _mock_workflow_output_subdependency(
577
+ subdependency: Union[BaseManifestConfig, FieldDefinitionsManifest],
578
+ parent_config,
579
+ parent_path: Optional[List[str]] = None,
580
+ ) -> AppConfigItem:
581
+ parent_path = parent_path if parent_path else []
582
+ linked_resource_id = _random_string("tsf_")
583
+ app_config = FieldAppConfigItem(
584
+ id=_random_string("aci_"),
585
+ path=parent_path + [parent_config.name, "output", subdependency.name],
586
+ type=FieldAppConfigItemType.FIELD,
587
+ value=linked_resource_id,
588
+ linked_resource=_mock_linked_resource(linked_resource_id),
589
+ )
590
+ return app_config
591
+
592
+
593
+ def _mock_linked_resource(id: str, name: Optional[str] = None) -> LinkedAppConfigResourceSummary:
594
+ return LinkedAppConfigResourceSummary(id=id, name=name if name else _random_string("Resource Name"))
595
+
596
+
597
+ def _mock_scalar_value(
598
+ dependency: ManifestScalarConfig,
599
+ ) -> Union[bool, date, datetime, int, float, str, Dict[str, Union[str, float]]]:
600
+ """Mock a scalar config value from its manifest definition."""
601
+ if isinstance(dependency, UnknownType):
602
+ raise UnsupportedConfigItemError(
603
+ f"Unable to mock scalar value for unsupported dependency type {dependency}"
604
+ )
605
+ # These types should be equivalent and this appeases MyPy
606
+ scalar_type = _ScalarConfigTypes(dependency.type)
607
+ if _is_scalar_with_enum(dependency):
608
+ return _mock_scalar_with_enum(dependency)
609
+ elif scalar_type == scalar_type.BOOLEAN:
610
+ return True
611
+ elif scalar_type == scalar_type.DATE:
612
+ return date.today()
613
+ elif scalar_type == scalar_type.DATETIME:
614
+ return datetime.now()
615
+ elif scalar_type == scalar_type.FLOAT:
616
+ return random.random()
617
+ elif scalar_type == scalar_type.INTEGER:
618
+ return random.randint(-1000, 1000)
619
+ elif scalar_type == scalar_type.JSON:
620
+ return json.dumps(
621
+ {_random_string(): [_random_string(), _random_string()], _random_string(): random.random()}
622
+ )
623
+ return _random_string()
624
+
625
+
626
+ def _random_string(prefix: str = "", random_length: int = 20) -> str:
627
+ """Generate a randomized string up to a specified length with an optional prefix."""
628
+ delimited_prefix = f"{prefix}-" if prefix else ""
629
+ return f"{delimited_prefix}{''.join(random.choice(string.ascii_letters) for i in range(random_length))}"
630
+
631
+
632
+ def _config_path(config_item: AppConfigItem) -> List[str]:
633
+ if isinstance(config_item, UnknownType):
634
+ return config_item.value["path"]
635
+ return config_item.path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: benchling-sdk
3
- Version: 1.18.0
3
+ Version: 1.18.1
4
4
  Summary: SDK for interacting with the Benchling Platform.
5
5
  License: Apache-2.0
6
6
  Author: Benchling Support
@@ -10,6 +10,8 @@ benchling_sdk/apps/config/cryptography_helpers.py,sha256=VCzgQURLAitYjcjdwFzMPSK
10
10
  benchling_sdk/apps/config/decryption_provider.py,sha256=-HgjkBto8mYlooV0LXFenfaiP7PDhHRnKEHkK1P6gh4,2184
11
11
  benchling_sdk/apps/config/errors.py,sha256=-qkfLdcUTuuRD-V4L2l7nzQWnWTRuifDtkNDv8tfHoo,800
12
12
  benchling_sdk/apps/config/framework.py,sha256=a2cIkPugEVUfBNtHm2cbE3Qu326xb-SpMuM0Gi6U-3Q,13337
13
+ benchling_sdk/apps/config/helpers.py,sha256=xVOfiJPZn28et8Pg7-SsRZnvmA5KmzAgwXBWwBYuZEI,7938
14
+ benchling_sdk/apps/config/mock_config.py,sha256=laTcsVDjM4ae2gu53gfXbx1lVM-y8aSohIaa_Lf8xWs,26906
13
15
  benchling_sdk/apps/config/types.py,sha256=XKfSGv-75CU-j1XwfXBGq8zbtnkF-PQnuY6Z2U47-Tg,953
14
16
  benchling_sdk/apps/framework.py,sha256=G15mv20FH7FLHJrnXMPcuFdUsP3Va-grvb5A4eq0Qlk,3175
15
17
  benchling_sdk/apps/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -117,7 +119,7 @@ benchling_sdk/services/v2/v2_alpha_service.py,sha256=rl4niKxjU-Rvdx5W6cjyXd4rUMo
117
119
  benchling_sdk/services/v2/v2_beta_service.py,sha256=SEv2AsZG0phf_B4ORqjRO-3VBkYehu1hlUhRFHrpX3c,6093
118
120
  benchling_sdk/services/v2/v2_stable_service.py,sha256=21TzqEFHygMUFv0z0z8_MtXpSHLgwYC7YbIIsHrljaw,28232
119
121
  benchling_sdk/services/v2_service.py,sha256=cGX-Ps0hu7Oh1M7a0tu2zDN8-QG63dNDoK7w4eYo_OQ,3093
120
- benchling_sdk-1.18.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
121
- benchling_sdk-1.18.0.dist-info/METADATA,sha256=fA4iDSxO-S_Kty11oC5D6eV4lCljcLzIGIb600MtoCI,2108
122
- benchling_sdk-1.18.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
123
- benchling_sdk-1.18.0.dist-info/RECORD,,
122
+ benchling_sdk-1.18.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
123
+ benchling_sdk-1.18.1.dist-info/METADATA,sha256=71VdfOrD6cQbkklt5_8-4ZwFJUg5zV2WefRNOL4_KKY,2108
124
+ benchling_sdk-1.18.1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
125
+ benchling_sdk-1.18.1.dist-info/RECORD,,