benchling-sdk 1.10.0a2__py3-none-any.whl → 1.10.0a3__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} +9 -126
- benchling_sdk/apps/canvas/types.py +125 -0
- benchling_sdk/apps/config/decryption_provider.py +1 -1
- benchling_sdk/apps/config/framework.py +207 -0
- benchling_sdk/apps/config/mock_config.py +1 -1
- benchling_sdk/apps/framework.py +18 -233
- benchling_sdk/apps/status/__init__.py +0 -0
- benchling_sdk/apps/status/errors.py +82 -0
- benchling_sdk/apps/{helpers/session_helpers.py → status/framework.py} +35 -145
- benchling_sdk/apps/status/types.py +45 -0
- {benchling_sdk-1.10.0a2.dist-info → benchling_sdk-1.10.0a3.dist-info}/METADATA +1 -1
- {benchling_sdk-1.10.0a2.dist-info → benchling_sdk-1.10.0a3.dist-info}/RECORD +17 -11
- benchling_sdk/apps/errors.py +0 -16
- /benchling_sdk/apps/{helpers → config}/cryptography_helpers.py +0 -0
- {benchling_sdk-1.10.0a2.dist-info → benchling_sdk-1.10.0a3.dist-info}/LICENSE +0 -0
- {benchling_sdk-1.10.0a2.dist-info → benchling_sdk-1.10.0a3.dist-info}/WHEEL +0 -0
benchling_sdk/apps/framework.py
CHANGED
@@ -1,44 +1,21 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
4
|
-
|
5
|
-
|
6
|
-
from
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from benchling_sdk.apps.config.framework import BenchlingConfigProvider, ConfigItemStore
|
6
|
+
from benchling_sdk.apps.status.framework import (
|
7
|
+
continue_session_context,
|
8
|
+
new_session_context,
|
9
|
+
SessionContextEnterHandler,
|
10
|
+
SessionContextExitHandler,
|
11
|
+
SessionContextManager,
|
12
|
+
)
|
12
13
|
from benchling_sdk.benchling import Benchling
|
13
14
|
from benchling_sdk.helpers.logging_helpers import log_stability_warning, StabilityLevel
|
14
|
-
from benchling_sdk.models import AppConfigItem, ListAppConfigurationItemsSort
|
15
15
|
|
16
16
|
log_stability_warning(StabilityLevel.ALPHA)
|
17
17
|
|
18
18
|
|
19
|
-
AppType = TypeVar("AppType", bound="App")
|
20
|
-
|
21
|
-
|
22
|
-
class ConfigProvider(Protocol):
|
23
|
-
"""
|
24
|
-
Config provider.
|
25
|
-
|
26
|
-
Provides a list of ConfigurationReference.
|
27
|
-
"""
|
28
|
-
|
29
|
-
def config(self) -> List[ConfigurationReference]:
|
30
|
-
"""Implement to provide a list of configuration items, for instance from Benchling APIs."""
|
31
|
-
pass
|
32
|
-
|
33
|
-
|
34
|
-
class ConfigItemStoreProvider(Protocol):
|
35
|
-
"""Return a config item store."""
|
36
|
-
|
37
|
-
def __call__(self, app_id: str) -> ConfigItemStore:
|
38
|
-
"""Return a config item store."""
|
39
|
-
pass
|
40
|
-
|
41
|
-
|
42
19
|
class App:
|
43
20
|
"""
|
44
21
|
App.
|
@@ -88,220 +65,28 @@ class App:
|
|
88
65
|
return self._config_store
|
89
66
|
|
90
67
|
def create_session_context(
|
91
|
-
self
|
68
|
+
self,
|
92
69
|
name: str,
|
93
70
|
timeout_seconds: int,
|
94
|
-
context_enter_handler: Optional[
|
95
|
-
context_exit_handler: Optional[
|
96
|
-
) ->
|
71
|
+
context_enter_handler: Optional[SessionContextEnterHandler] = None,
|
72
|
+
context_exit_handler: Optional[SessionContextExitHandler] = None,
|
73
|
+
) -> SessionContextManager:
|
97
74
|
"""
|
98
75
|
Create Session Context.
|
99
76
|
|
100
77
|
Create a new app session in Benchling.
|
101
78
|
"""
|
102
|
-
# Avoid circular import + MyPy "is not defined" if using relative like above
|
103
|
-
from benchling_sdk.apps.helpers.session_helpers import new_session_context
|
104
|
-
|
105
79
|
return new_session_context(self, name, timeout_seconds, context_enter_handler, context_exit_handler)
|
106
80
|
|
107
81
|
def continue_session_context(
|
108
|
-
self
|
82
|
+
self,
|
109
83
|
session_id: str,
|
110
|
-
context_enter_handler: Optional[
|
111
|
-
context_exit_handler: Optional[
|
112
|
-
) ->
|
84
|
+
context_enter_handler: Optional[SessionContextEnterHandler] = None,
|
85
|
+
context_exit_handler: Optional[SessionContextExitHandler] = None,
|
86
|
+
) -> SessionContextManager:
|
113
87
|
"""
|
114
88
|
Continue Session Context.
|
115
89
|
|
116
90
|
Fetch an existing app session from Benchling and enter a context with it.
|
117
91
|
"""
|
118
|
-
# Avoid circular import + MyPy "is not defined" if using relative like above
|
119
|
-
from benchling_sdk.apps.helpers.session_helpers import continue_session_context
|
120
|
-
|
121
92
|
return continue_session_context(self, session_id, context_enter_handler, context_exit_handler)
|
122
|
-
|
123
|
-
|
124
|
-
class BenchlingConfigProvider(ConfigProvider):
|
125
|
-
"""
|
126
|
-
Benchling Config provider.
|
127
|
-
|
128
|
-
Provides a BenchlingAppConfiguration retrieved from Benchling's API.
|
129
|
-
"""
|
130
|
-
|
131
|
-
_app_id: str
|
132
|
-
_benchling: Benchling
|
133
|
-
|
134
|
-
def __init__(self, benchling: Benchling, app_id: str):
|
135
|
-
"""
|
136
|
-
Initialize Benchling Config Provider.
|
137
|
-
|
138
|
-
:param benchling_provider: A provider for a Benchling instance.
|
139
|
-
:param tenant_url_provider: A provider for a tenant url.
|
140
|
-
:param app_id: The app_id from which to retrieve configuration.
|
141
|
-
"""
|
142
|
-
self._app_id = app_id
|
143
|
-
self._benchling = benchling
|
144
|
-
|
145
|
-
def config(self) -> List[ConfigurationReference]:
|
146
|
-
"""Provide a Benchling app configuration from Benchling's APIs."""
|
147
|
-
app_pages = self._benchling.apps.list_app_configuration_items(
|
148
|
-
app_id=self._app_id,
|
149
|
-
page_size=100,
|
150
|
-
sort=ListAppConfigurationItemsSort.CREATEDATASC,
|
151
|
-
)
|
152
|
-
|
153
|
-
# Eager load all config items for now since we don't yet have a way of lazily querying by path
|
154
|
-
all_config_pages = list(app_pages)
|
155
|
-
# Punt on UnknownType for now as apps using manifests with new types + older client could lead to unpredictable results
|
156
|
-
all_config_items = [
|
157
|
-
_supported_config_item(config_item) for page in all_config_pages for config_item in page
|
158
|
-
]
|
159
|
-
|
160
|
-
return all_config_items
|
161
|
-
|
162
|
-
|
163
|
-
class StaticConfigProvider(ConfigProvider):
|
164
|
-
"""
|
165
|
-
Static Config provider.
|
166
|
-
|
167
|
-
Provides a BenchlingAppConfiguration from a static declaration. Useful for mocking or testing.
|
168
|
-
"""
|
169
|
-
|
170
|
-
_configuration_items: List[ConfigurationReference]
|
171
|
-
|
172
|
-
def __init__(self, configuration_items: List[ConfigurationReference]):
|
173
|
-
"""
|
174
|
-
Initialize Static Config Provider.
|
175
|
-
|
176
|
-
:param configuration_items: The configuration items to return.
|
177
|
-
"""
|
178
|
-
self._configuration_items = configuration_items
|
179
|
-
|
180
|
-
def config(self) -> List[ConfigurationReference]:
|
181
|
-
"""Provide Benchling app configuration items from a static reference."""
|
182
|
-
return self._configuration_items
|
183
|
-
|
184
|
-
|
185
|
-
class ConfigItemStore:
|
186
|
-
"""
|
187
|
-
Dependency Link Store.
|
188
|
-
|
189
|
-
Marshalls an app configuration from the configuration provider into an indexable structure.
|
190
|
-
Only retrieves app configuration once unless its cache is invalidated.
|
191
|
-
"""
|
192
|
-
|
193
|
-
_configuration_provider: ConfigProvider
|
194
|
-
_configuration: Optional[List[ConfigurationReference]] = None
|
195
|
-
_configuration_map: Optional[Dict[ConfigItemPath, ConfigurationReference]] = None
|
196
|
-
_array_path_row_names: Dict[Tuple[str, ...], OrderedSet[str]] = dict()
|
197
|
-
|
198
|
-
def __init__(self, configuration_provider: ConfigProvider):
|
199
|
-
"""
|
200
|
-
Initialize Dependency Link Store.
|
201
|
-
|
202
|
-
:param configuration_provider: A ConfigProvider that will be invoked to provide the
|
203
|
-
underlying config from which to organize dependency links.
|
204
|
-
"""
|
205
|
-
self._configuration_provider = configuration_provider
|
206
|
-
self._array_path_row_names = dict()
|
207
|
-
|
208
|
-
@property
|
209
|
-
def configuration(self) -> List[ConfigurationReference]:
|
210
|
-
"""
|
211
|
-
Get the underlying configuration.
|
212
|
-
|
213
|
-
Return the raw, stored configuration. Can be used if the provided accessors are inadequate
|
214
|
-
to find particular configuration items.
|
215
|
-
"""
|
216
|
-
if not self._configuration:
|
217
|
-
self._configuration = self._configuration_provider.config()
|
218
|
-
return self._configuration
|
219
|
-
|
220
|
-
@property
|
221
|
-
def configuration_path_map(self) -> Dict[ConfigItemPath, ConfigurationReference]:
|
222
|
-
"""
|
223
|
-
Config links.
|
224
|
-
|
225
|
-
Return a map of configuration item paths to their corresponding configuration items.
|
226
|
-
"""
|
227
|
-
if not self._configuration_map:
|
228
|
-
self._configuration_map = {tuple(item.path): item for item in self.configuration}
|
229
|
-
return self._configuration_map
|
230
|
-
|
231
|
-
def config_by_path(self, path: List[str]) -> Optional[ConfigurationReference]:
|
232
|
-
"""
|
233
|
-
Config by path.
|
234
|
-
|
235
|
-
Find an app config item by its exact path match, if it exists. Does not search partial paths.
|
236
|
-
"""
|
237
|
-
# Since we eager load all config now, we know that missing path means it's not configured in Benchling
|
238
|
-
# Later if we support lazy loading, we'll need to differentiate what's in our cache versus missing
|
239
|
-
return self.configuration_path_map.get(tuple(path))
|
240
|
-
|
241
|
-
def config_keys_by_path(self, path: List[str]) -> OrderedSet[str]:
|
242
|
-
"""
|
243
|
-
Config keys by path.
|
244
|
-
|
245
|
-
Find a set of app config keys at the specified path, if any. Does not return keys that are nested
|
246
|
-
beyond the current level.
|
247
|
-
|
248
|
-
For instance, given paths:
|
249
|
-
["One", "Two"]
|
250
|
-
["One", "Two", "Three"]
|
251
|
-
["One", "Two", "Four"]
|
252
|
-
["One", "Two", "Three", "Five"]
|
253
|
-
["Zero", "One", "Two", "Three"]
|
254
|
-
|
255
|
-
The expected return from this method when path=["One", "Two"] is a set {"Three", "Four"}.
|
256
|
-
"""
|
257
|
-
# Convert path to tuple, as list is not hashable for dict keys
|
258
|
-
path_tuple = tuple(path)
|
259
|
-
if path_tuple not in self._array_path_row_names:
|
260
|
-
self._array_path_row_names[path_tuple] = OrderedSet(
|
261
|
-
[
|
262
|
-
config_item.path[len(path)]
|
263
|
-
# Use the list instead of configuration_map to preserve order
|
264
|
-
for config_item in self.configuration
|
265
|
-
# The +1 is the name of the array row
|
266
|
-
if len(config_item.path) >= len(path) + 1
|
267
|
-
# Ignoring flake8 error E203 because black keeps putting in whitespace padding :
|
268
|
-
and config_item.path[0 : len(path_tuple)] == path # noqa: E203
|
269
|
-
and config_item.value is not None
|
270
|
-
]
|
271
|
-
)
|
272
|
-
return self._array_path_row_names[path_tuple]
|
273
|
-
|
274
|
-
def array_rows_to_dict(self, path: List[str]) -> OrderedDict[str, Dict[str, ConfigurationReference]]:
|
275
|
-
"""Given a path to the root of a config array, return each element as a named dict."""
|
276
|
-
# TODO BNCH-52772 Improve docstring if we keep this method
|
277
|
-
array_keys = self.config_keys_by_path(path)
|
278
|
-
# Although we don't have a way of preserving order when pulling array elements from the API right now
|
279
|
-
# we should intentionally order these to accommodate a potential ordered future
|
280
|
-
array_elements_map = collections.OrderedDict()
|
281
|
-
for key in array_keys:
|
282
|
-
# Don't care about order for the keys within a row, only the order of the rows themselves
|
283
|
-
array_elements_map[key] = {
|
284
|
-
array_element_key: self.config_by_path([*path, key, array_element_key])
|
285
|
-
for array_element_key in self.config_keys_by_path([*path, key])
|
286
|
-
if self.config_by_path([*path, key, array_element_key]) is not None
|
287
|
-
}
|
288
|
-
# TODO BNCH-52772 MyPy thinks the inner dict values can be None
|
289
|
-
return array_elements_map # type: ignore
|
290
|
-
|
291
|
-
def invalidate_cache(self) -> None:
|
292
|
-
"""
|
293
|
-
Invalidate Cache.
|
294
|
-
|
295
|
-
Will force retrieval of configuration from the ConfigProvider the next time the link store is accessed.
|
296
|
-
"""
|
297
|
-
self._configuration = None
|
298
|
-
self._configuration_map = None
|
299
|
-
self._array_path_row_names = dict()
|
300
|
-
|
301
|
-
|
302
|
-
def _supported_config_item(config_item: AppConfigItem) -> ConfigurationReference:
|
303
|
-
if isinstance(config_item, UnknownType):
|
304
|
-
raise UnsupportedConfigItemError(
|
305
|
-
f"Unable to read app configuration with unsupported type: {config_item}"
|
306
|
-
)
|
307
|
-
return config_item
|
File without changes
|
@@ -0,0 +1,82 @@
|
|
1
|
+
from typing import List, Union
|
2
|
+
|
3
|
+
from benchling_sdk.models import AppSessionMessageCreate, AppSessionMessageStyle
|
4
|
+
|
5
|
+
|
6
|
+
class SessionClosedError(Exception):
|
7
|
+
"""
|
8
|
+
Session Closed Error.
|
9
|
+
|
10
|
+
A session was inoperable because its status in Benchling was terminal.
|
11
|
+
"""
|
12
|
+
|
13
|
+
pass
|
14
|
+
|
15
|
+
|
16
|
+
class SessionContextClosedError(Exception):
|
17
|
+
"""
|
18
|
+
Session Context Closed Error.
|
19
|
+
|
20
|
+
An operation was attempted using the session context manager after it was closed.
|
21
|
+
"""
|
22
|
+
|
23
|
+
pass
|
24
|
+
|
25
|
+
|
26
|
+
class InvalidSessionTimeoutError(Exception):
|
27
|
+
"""
|
28
|
+
Invalid Session Timeout Error.
|
29
|
+
|
30
|
+
A session's timeout value was set to an invalid value.
|
31
|
+
"""
|
32
|
+
|
33
|
+
pass
|
34
|
+
|
35
|
+
|
36
|
+
class MissingAttachedCanvasError(Exception):
|
37
|
+
"""
|
38
|
+
Missing Attached Canvas Error.
|
39
|
+
|
40
|
+
A canvas operation was requested, but a session context has no attached canvas.
|
41
|
+
"""
|
42
|
+
|
43
|
+
pass
|
44
|
+
|
45
|
+
|
46
|
+
class AppUserFacingError(Exception):
|
47
|
+
"""
|
48
|
+
App User Facing Error.
|
49
|
+
|
50
|
+
Extend this class with custom exceptions you want to be written back to the user as a SessionMessage.
|
51
|
+
|
52
|
+
SessionClosingContextExitHandler will invoke messages() and write them to a user. Callers choosing to
|
53
|
+
write their own SessionContextExitHandler may need to replicate this behavior themselves.
|
54
|
+
|
55
|
+
This is useful for control flow where an app wants to terminate with an error state that is resolvable
|
56
|
+
by the user.
|
57
|
+
|
58
|
+
For example:
|
59
|
+
|
60
|
+
class InvalidUserInputError(AppUserFacingError):
|
61
|
+
pass
|
62
|
+
|
63
|
+
raise InvalidUserInputError("Please enter a number between 1 and 10")
|
64
|
+
|
65
|
+
This would create a message shown to the user like:
|
66
|
+
|
67
|
+
AppSessionMessageCreate("Please enter a number between 1 and 10", style=AppSessionMessageStyle.ERROR)
|
68
|
+
"""
|
69
|
+
|
70
|
+
_messages: List[str]
|
71
|
+
|
72
|
+
def __init__(self, messages: Union[str, List[str]], *args) -> None:
|
73
|
+
"""Initialize an AppUserFacingError with one message or a list."""
|
74
|
+
self._messages = [messages] if isinstance(messages, str) else messages
|
75
|
+
super().__init__(args)
|
76
|
+
|
77
|
+
def messages(self) -> List[AppSessionMessageCreate]:
|
78
|
+
"""Create a series of AppSessionMessageCreate to write to a Session and displayed to the user."""
|
79
|
+
return [
|
80
|
+
AppSessionMessageCreate(content=message, style=AppSessionMessageStyle.ERROR)
|
81
|
+
for message in self._messages
|
82
|
+
]
|
@@ -3,15 +3,21 @@ from __future__ import annotations
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
from contextlib import AbstractContextManager
|
5
5
|
from types import TracebackType
|
6
|
-
from typing import cast,
|
6
|
+
from typing import cast, Iterable, List, Optional, Protocol, Type, TYPE_CHECKING, Union
|
7
7
|
|
8
8
|
from benchling_api_client.v2.stable.types import Unset, UNSET
|
9
9
|
|
10
|
-
from benchling_sdk.apps.
|
10
|
+
from benchling_sdk.apps.status.errors import (
|
11
|
+
AppUserFacingError,
|
12
|
+
InvalidSessionTimeoutError,
|
13
|
+
MissingAttachedCanvasError,
|
14
|
+
SessionClosedError,
|
15
|
+
SessionContextClosedError,
|
16
|
+
)
|
17
|
+
from benchling_sdk.apps.status.types import _ReferencedSessionLinkType
|
11
18
|
from benchling_sdk.errors import BenchlingError
|
12
19
|
from benchling_sdk.helpers.logging_helpers import log_stability_warning, sdk_logger, StabilityLevel
|
13
20
|
from benchling_sdk.models import (
|
14
|
-
AaSequence,
|
15
21
|
AppCanvasUpdate,
|
16
22
|
AppSession,
|
17
23
|
AppSessionCreate,
|
@@ -22,132 +28,16 @@ from benchling_sdk.models import (
|
|
22
28
|
AppSessionUpdateStatus,
|
23
29
|
BadRequestError,
|
24
30
|
BadRequestErrorBulkError,
|
25
|
-
Blob,
|
26
|
-
Box,
|
27
|
-
Container,
|
28
|
-
CustomEntity,
|
29
|
-
DnaOligo,
|
30
|
-
DnaSequence,
|
31
|
-
Entry,
|
32
|
-
Folder,
|
33
|
-
Location,
|
34
|
-
Mixture,
|
35
|
-
Molecule,
|
36
|
-
Plate,
|
37
|
-
Request,
|
38
|
-
RnaOligo,
|
39
|
-
RnaSequence,
|
40
|
-
WorkflowOutput,
|
41
|
-
WorkflowTask,
|
42
31
|
)
|
43
32
|
|
44
|
-
|
45
|
-
|
46
|
-
_ReferencedSessionLinkType = Union[
|
47
|
-
AaSequence,
|
48
|
-
Blob,
|
49
|
-
Box,
|
50
|
-
Container,
|
51
|
-
CustomEntity,
|
52
|
-
DnaSequence,
|
53
|
-
DnaOligo,
|
54
|
-
Entry,
|
55
|
-
Folder,
|
56
|
-
Location,
|
57
|
-
Mixture,
|
58
|
-
Plate,
|
59
|
-
RnaOligo,
|
60
|
-
Molecule,
|
61
|
-
RnaSequence,
|
62
|
-
Request,
|
63
|
-
WorkflowOutput,
|
64
|
-
WorkflowTask,
|
65
|
-
]
|
33
|
+
if TYPE_CHECKING:
|
34
|
+
from benchling_sdk.apps.framework import App
|
66
35
|
|
67
36
|
_DEFAULT_APP_ERROR_MESSAGE = "An unexpected error occurred in the app"
|
68
37
|
|
69
38
|
log_stability_warning(StabilityLevel.BETA)
|
70
39
|
|
71
40
|
|
72
|
-
class SessionClosedError(Exception):
|
73
|
-
"""
|
74
|
-
Session Closed Error.
|
75
|
-
|
76
|
-
A session was inoperable because its status in Benchling was terminal.
|
77
|
-
"""
|
78
|
-
|
79
|
-
pass
|
80
|
-
|
81
|
-
|
82
|
-
class SessionContextClosedError(Exception):
|
83
|
-
"""
|
84
|
-
Session Context Closed Error.
|
85
|
-
|
86
|
-
An operation was attempted using the session context manager after it was closed.
|
87
|
-
"""
|
88
|
-
|
89
|
-
pass
|
90
|
-
|
91
|
-
|
92
|
-
class InvalidSessionTimeoutError(Exception):
|
93
|
-
"""
|
94
|
-
Invalid Session Timeout Error.
|
95
|
-
|
96
|
-
A session's timeout value was set to an invalid value.
|
97
|
-
"""
|
98
|
-
|
99
|
-
pass
|
100
|
-
|
101
|
-
|
102
|
-
class MissingAttachedCanvasError(Exception):
|
103
|
-
"""
|
104
|
-
Missing Attached Canvas Error.
|
105
|
-
|
106
|
-
A canvas operation was requested, but a session context has no attached canvas.
|
107
|
-
"""
|
108
|
-
|
109
|
-
pass
|
110
|
-
|
111
|
-
|
112
|
-
class AppUserFacingError(Exception):
|
113
|
-
"""
|
114
|
-
App User Facing Error.
|
115
|
-
|
116
|
-
Extend this class with custom exceptions you want to be written back to the user as a SessionMessage.
|
117
|
-
|
118
|
-
SessionClosingContextExitHandler will invoke messages() and write them to a user. Callers choosing to
|
119
|
-
write their own SessionContextExitHandler may need to replicate this behavior themselves.
|
120
|
-
|
121
|
-
This is useful for control flow where an app wants to terminate with an error state that is resolvable
|
122
|
-
by the user.
|
123
|
-
|
124
|
-
For example:
|
125
|
-
|
126
|
-
class InvalidUserInputError(AppUserFacingError):
|
127
|
-
pass
|
128
|
-
|
129
|
-
raise InvalidUserInputError("Please enter a number between 1 and 10")
|
130
|
-
|
131
|
-
This would create a message shown to the user like:
|
132
|
-
|
133
|
-
AppSessionMessageCreate("Please enter a number between 1 and 10", style=AppSessionMessageStyle.ERROR)
|
134
|
-
"""
|
135
|
-
|
136
|
-
_messages: List[str]
|
137
|
-
|
138
|
-
def __init__(self, messages: Union[str, List[str]], *args) -> None:
|
139
|
-
"""Initialize an AppUserFacingError with one message or a list."""
|
140
|
-
self._messages = [messages] if isinstance(messages, str) else messages
|
141
|
-
super().__init__(args)
|
142
|
-
|
143
|
-
def messages(self) -> List[AppSessionMessageCreate]:
|
144
|
-
"""Create a series of AppSessionMessageCreate to write to a Session and displayed to the user."""
|
145
|
-
return [
|
146
|
-
AppSessionMessageCreate(content=message, style=AppSessionMessageStyle.ERROR)
|
147
|
-
for message in self._messages
|
148
|
-
]
|
149
|
-
|
150
|
-
|
151
41
|
class SessionProvider(Protocol):
|
152
42
|
"""Provide a Benchling App Session to convey app status."""
|
153
43
|
|
@@ -181,7 +71,7 @@ class SessionContextErrorProcessor(ABC):
|
|
181
71
|
@abstractmethod
|
182
72
|
def process_error(
|
183
73
|
cls,
|
184
|
-
context: SessionContextManager
|
74
|
+
context: SessionContextManager,
|
185
75
|
exc_type: Type[BaseException],
|
186
76
|
exc_value: BaseException,
|
187
77
|
exc_traceback: TracebackType,
|
@@ -220,7 +110,7 @@ class AppUserFacingErrorProcessor(SessionContextErrorProcessor):
|
|
220
110
|
@classmethod
|
221
111
|
def process_error(
|
222
112
|
cls,
|
223
|
-
context: SessionContextManager
|
113
|
+
context: SessionContextManager,
|
224
114
|
exc_type: Type[BaseException],
|
225
115
|
exc_value: BaseException,
|
226
116
|
exc_traceback: TracebackType,
|
@@ -269,7 +159,7 @@ class BenchlingBadRequestErrorProcessor(AppUserFacingErrorProcessor):
|
|
269
159
|
@classmethod
|
270
160
|
def process_error(
|
271
161
|
cls,
|
272
|
-
context: SessionContextManager
|
162
|
+
context: SessionContextManager,
|
273
163
|
exc_type: Type[BaseException],
|
274
164
|
exc_value: BaseException,
|
275
165
|
exc_traceback: TracebackType,
|
@@ -318,7 +208,7 @@ class BenchlingBadRequestErrorProcessor(AppUserFacingErrorProcessor):
|
|
318
208
|
return messages
|
319
209
|
|
320
210
|
|
321
|
-
class SessionContextEnterHandler(ABC
|
211
|
+
class SessionContextEnterHandler(ABC):
|
322
212
|
"""
|
323
213
|
Session Context Enter Handler.
|
324
214
|
|
@@ -326,12 +216,12 @@ class SessionContextEnterHandler(ABC, Generic[AppType]):
|
|
326
216
|
"""
|
327
217
|
|
328
218
|
@abstractmethod
|
329
|
-
def on_enter(self, context: SessionContextManager
|
219
|
+
def on_enter(self, context: SessionContextManager) -> None:
|
330
220
|
"""Perform on session context enter after a Session has been started with Benchling."""
|
331
221
|
pass
|
332
222
|
|
333
223
|
|
334
|
-
class SessionContextExitHandler(ABC
|
224
|
+
class SessionContextExitHandler(ABC):
|
335
225
|
"""
|
336
226
|
Session Context Exit Handler.
|
337
227
|
|
@@ -339,7 +229,7 @@ class SessionContextExitHandler(ABC, Generic[AppType]):
|
|
339
229
|
"""
|
340
230
|
|
341
231
|
@abstractmethod
|
342
|
-
def on_success(self, context: SessionContextManager
|
232
|
+
def on_success(self, context: SessionContextManager) -> bool:
|
343
233
|
"""
|
344
234
|
Perform on session context exit when no errors are present.
|
345
235
|
|
@@ -354,7 +244,7 @@ class SessionContextExitHandler(ABC, Generic[AppType]):
|
|
354
244
|
@abstractmethod
|
355
245
|
def on_error(
|
356
246
|
self,
|
357
|
-
context: SessionContextManager
|
247
|
+
context: SessionContextManager,
|
358
248
|
exc_type: Type[BaseException],
|
359
249
|
exc_value: BaseException,
|
360
250
|
exc_traceback: TracebackType,
|
@@ -412,7 +302,7 @@ class SessionClosingContextExitHandler(SessionContextExitHandler):
|
|
412
302
|
self._error_messages = error_messages
|
413
303
|
self._error_processors = error_processors
|
414
304
|
|
415
|
-
def on_success(self, context: SessionContextManager
|
305
|
+
def on_success(self, context: SessionContextManager) -> bool:
|
416
306
|
"""
|
417
307
|
Close Active Session on Success.
|
418
308
|
|
@@ -443,7 +333,7 @@ class SessionClosingContextExitHandler(SessionContextExitHandler):
|
|
443
333
|
|
444
334
|
def on_error(
|
445
335
|
self,
|
446
|
-
context: SessionContextManager
|
336
|
+
context: SessionContextManager,
|
447
337
|
exc_type: Type[BaseException],
|
448
338
|
exc_value: BaseException,
|
449
339
|
exc_traceback: TracebackType,
|
@@ -497,7 +387,7 @@ class SessionClosingContextExitHandler(SessionContextExitHandler):
|
|
497
387
|
return None
|
498
388
|
|
499
389
|
|
500
|
-
def create_session_provider(app:
|
390
|
+
def create_session_provider(app: App, name: str, timeout_seconds: int) -> SessionProvider:
|
501
391
|
"""
|
502
392
|
Create Session Provider.
|
503
393
|
|
@@ -512,7 +402,7 @@ def create_session_provider(app: AppType, name: str, timeout_seconds: int) -> Se
|
|
512
402
|
return _new_session
|
513
403
|
|
514
404
|
|
515
|
-
def existing_session_provider(app:
|
405
|
+
def existing_session_provider(app: App, session_id: str) -> SessionProvider:
|
516
406
|
"""
|
517
407
|
Existing Session Provider.
|
518
408
|
|
@@ -537,7 +427,7 @@ def _ordered_messages(messages: Iterable[AppSessionMessageCreate]) -> List[AppSe
|
|
537
427
|
return list(messages)
|
538
428
|
|
539
429
|
|
540
|
-
class SessionContextManager(AbstractContextManager
|
430
|
+
class SessionContextManager(AbstractContextManager):
|
541
431
|
"""
|
542
432
|
Manage Benchling App Session.
|
543
433
|
|
@@ -546,19 +436,19 @@ class SessionContextManager(AbstractContextManager, Generic[AppType]):
|
|
546
436
|
success_exit_handler.
|
547
437
|
"""
|
548
438
|
|
549
|
-
_app:
|
439
|
+
_app: App
|
550
440
|
_session_provider: SessionProvider
|
551
|
-
_context_enter_handler: Optional[SessionContextEnterHandler
|
552
|
-
_context_exit_handler: SessionContextExitHandler
|
441
|
+
_context_enter_handler: Optional[SessionContextEnterHandler]
|
442
|
+
_context_exit_handler: SessionContextExitHandler
|
553
443
|
_session: Optional[AppSession]
|
554
444
|
_attached_canvas_id: Optional[str]
|
555
445
|
|
556
446
|
def __init__(
|
557
447
|
self,
|
558
|
-
app:
|
448
|
+
app: App,
|
559
449
|
session_provider: SessionProvider,
|
560
|
-
context_enter_handler: Optional[SessionContextEnterHandler
|
561
|
-
context_exit_handler: Optional[SessionContextExitHandler
|
450
|
+
context_enter_handler: Optional[SessionContextEnterHandler] = None,
|
451
|
+
context_exit_handler: Optional[SessionContextExitHandler] = None,
|
562
452
|
):
|
563
453
|
"""
|
564
454
|
Initialize SessionContextManager.
|
@@ -595,7 +485,7 @@ class SessionContextManager(AbstractContextManager, Generic[AppType]):
|
|
595
485
|
return self._context_exit_handler.on_success(self)
|
596
486
|
|
597
487
|
@property
|
598
|
-
def app(self) ->
|
488
|
+
def app(self) -> App:
|
599
489
|
"""Return the app for the session."""
|
600
490
|
return self._app
|
601
491
|
|
@@ -759,12 +649,12 @@ class SessionContextManager(AbstractContextManager, Generic[AppType]):
|
|
759
649
|
|
760
650
|
|
761
651
|
def new_session_context(
|
762
|
-
app:
|
652
|
+
app: App,
|
763
653
|
name: str,
|
764
654
|
timeout_seconds: int,
|
765
655
|
context_enter_handler: Optional[SessionContextEnterHandler] = None,
|
766
656
|
context_exit_handler: Optional[SessionContextExitHandler] = None,
|
767
|
-
) -> SessionContextManager
|
657
|
+
) -> SessionContextManager:
|
768
658
|
"""
|
769
659
|
Create New Session Context.
|
770
660
|
|
@@ -779,11 +669,11 @@ def new_session_context(
|
|
779
669
|
|
780
670
|
|
781
671
|
def continue_session_context(
|
782
|
-
app:
|
672
|
+
app: App,
|
783
673
|
session_id: str,
|
784
674
|
context_enter_handler: Optional[SessionContextEnterHandler] = None,
|
785
675
|
context_exit_handler: Optional[SessionContextExitHandler] = None,
|
786
|
-
) -> SessionContextManager
|
676
|
+
) -> SessionContextManager:
|
787
677
|
"""
|
788
678
|
Continue Session Context.
|
789
679
|
|