benchling-sdk 1.9.0a4__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.
Files changed (33) hide show
  1. benchling_sdk/apps/canvas/__init__.py +0 -0
  2. benchling_sdk/apps/canvas/errors.py +14 -0
  3. benchling_sdk/apps/{helpers/canvas_helpers.py → canvas/framework.py} +129 -188
  4. benchling_sdk/apps/canvas/types.py +125 -0
  5. benchling_sdk/apps/config/__init__.py +0 -3
  6. benchling_sdk/apps/config/decryption_provider.py +1 -1
  7. benchling_sdk/apps/config/errors.py +38 -0
  8. benchling_sdk/apps/config/framework.py +343 -0
  9. benchling_sdk/apps/config/helpers.py +157 -0
  10. benchling_sdk/apps/config/{mock_dependencies.py → mock_config.py} +78 -99
  11. benchling_sdk/apps/config/types.py +36 -0
  12. benchling_sdk/apps/framework.py +49 -338
  13. benchling_sdk/apps/helpers/webhook_helpers.py +2 -2
  14. benchling_sdk/apps/status/__init__.py +0 -0
  15. benchling_sdk/apps/status/errors.py +85 -0
  16. benchling_sdk/apps/{helpers/session_helpers.py → status/framework.py} +58 -167
  17. benchling_sdk/apps/status/helpers.py +20 -0
  18. benchling_sdk/apps/status/types.py +45 -0
  19. benchling_sdk/apps/types.py +3 -0
  20. benchling_sdk/errors.py +4 -4
  21. benchling_sdk/helpers/retry_helpers.py +3 -1
  22. benchling_sdk/models/__init__.py +44 -0
  23. benchling_sdk/services/v2/beta/{v2_beta_dataset_service.py → v2_beta_data_frame_service.py} +126 -116
  24. benchling_sdk/services/v2/stable/assay_result_service.py +18 -0
  25. benchling_sdk/services/v2/v2_beta_service.py +11 -11
  26. {benchling_sdk-1.9.0a4.dist-info → benchling_sdk-1.10.0.dist-info}/METADATA +4 -4
  27. {benchling_sdk-1.9.0a4.dist-info → benchling_sdk-1.10.0.dist-info}/RECORD +30 -21
  28. benchling_sdk/apps/config/dependencies.py +0 -1085
  29. benchling_sdk/apps/config/scalars.py +0 -226
  30. benchling_sdk/apps/helpers/config_helpers.py +0 -409
  31. /benchling_sdk/apps/{helpers → config}/cryptography_helpers.py +0 -0
  32. {benchling_sdk-1.9.0a4.dist-info → benchling_sdk-1.10.0.dist-info}/LICENSE +0 -0
  33. {benchling_sdk-1.9.0a4.dist-info → benchling_sdk-1.10.0.dist-info}/WHEEL +0 -0
File without changes
@@ -0,0 +1,14 @@
1
+ class DuplicateBlockIdError(Exception):
2
+ """Error indicating that duplicate ids were present on blocks within a Canvas."""
3
+
4
+ pass
5
+
6
+
7
+ class NoMatchingBlocksError(Exception):
8
+ """
9
+ Error indicating that blocks were expected, but none matched.
10
+
11
+ Used to prevent requiring developers to handle Optional[_UiBlock] for type safety.
12
+ """
13
+
14
+ pass
@@ -1,142 +1,40 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  from typing import cast, Dict, Generic, List, Optional, Protocol, Set, Type, TypeVar, Union
4
5
 
5
6
  from benchling_api_client.v2.extensions import UnknownType
6
7
 
7
- from benchling_sdk.helpers.logging_helpers import log_stability_warning, StabilityLevel
8
+ from benchling_sdk.apps.canvas.errors import DuplicateBlockIdError, NoMatchingBlocksError
9
+ from benchling_sdk.apps.canvas.types import (
10
+ _UI_BLOCK_MAPPINGS_CREATE,
11
+ _UI_BLOCK_MAPPINGS_UPDATE,
12
+ _UiBlockCreate,
13
+ _UiBlockUpdate,
14
+ UiBlock,
15
+ UiBlockType,
16
+ )
17
+ from benchling_sdk.apps.types import JsonType
8
18
  from benchling_sdk.models import (
9
19
  AppCanvas,
10
20
  AppCanvasApp,
11
21
  AppCanvasCreate,
12
22
  AppCanvasUpdate,
13
- ButtonUiBlock,
14
- ButtonUiBlockCreate,
15
- ButtonUiBlockUpdate,
16
- ChipUiBlock,
17
- ChipUiBlockCreate,
18
- ChipUiBlockUpdate,
19
23
  DropdownMultiValueUiBlock,
20
- DropdownMultiValueUiBlockCreate,
21
- DropdownMultiValueUiBlockUpdate,
22
24
  DropdownUiBlock,
23
- DropdownUiBlockCreate,
24
- DropdownUiBlockUpdate,
25
- MarkdownUiBlock,
26
- MarkdownUiBlockCreate,
27
- MarkdownUiBlockUpdate,
28
25
  SearchInputMultiValueUiBlock,
29
- SearchInputMultiValueUiBlockCreate,
30
- SearchInputMultiValueUiBlockUpdate,
31
26
  SearchInputUiBlock,
32
- SearchInputUiBlockCreate,
33
- SearchInputUiBlockUpdate,
34
27
  SectionUiBlock,
35
- SectionUiBlockCreate,
36
- SectionUiBlockUpdate,
37
28
  SelectorInputMultiValueUiBlock,
38
- SelectorInputMultiValueUiBlockCreate,
39
- SelectorInputMultiValueUiBlockUpdate,
40
29
  SelectorInputUiBlock,
41
- SelectorInputUiBlockCreate,
42
- SelectorInputUiBlockUpdate,
43
30
  TableUiBlock,
44
- TableUiBlockCreate,
45
- TableUiBlockUpdate,
46
31
  TextInputUiBlock,
47
- TextInputUiBlockCreate,
48
- TextInputUiBlockUpdate,
49
32
  )
50
33
 
51
- log_stability_warning(StabilityLevel.BETA)
52
-
53
- S = TypeVar("S", bound="_FilteredCanvasBuilderBlockStream")
54
-
55
- _UiBlock = Union[
56
- ButtonUiBlock,
57
- ChipUiBlock,
58
- DropdownMultiValueUiBlock,
59
- DropdownUiBlock,
60
- MarkdownUiBlock,
61
- SearchInputMultiValueUiBlock,
62
- SearchInputUiBlock,
63
- SectionUiBlock,
64
- SelectorInputMultiValueUiBlock,
65
- SelectorInputUiBlock,
66
- TableUiBlock,
67
- TextInputUiBlock,
68
- UnknownType,
69
- ]
34
+ S = TypeVar("S", bound="FilteredCanvasBuilderBlockStream")
70
35
 
71
- _UiBlockType = TypeVar(
72
- "_UiBlockType",
73
- bound=_UiBlock,
74
- )
75
36
 
76
- _UiBlockCreate = Union[
77
- ButtonUiBlockCreate,
78
- ChipUiBlockCreate,
79
- DropdownMultiValueUiBlockCreate,
80
- DropdownUiBlockCreate,
81
- MarkdownUiBlockCreate,
82
- SearchInputMultiValueUiBlockCreate,
83
- SearchInputUiBlockCreate,
84
- SectionUiBlockCreate,
85
- SelectorInputMultiValueUiBlockCreate,
86
- SelectorInputUiBlockCreate,
87
- TableUiBlockCreate,
88
- TextInputUiBlockCreate,
89
- UnknownType,
90
- ]
91
-
92
- _UiBlockUpdate = Union[
93
- ButtonUiBlockUpdate,
94
- ChipUiBlockUpdate,
95
- DropdownMultiValueUiBlockUpdate,
96
- DropdownUiBlockUpdate,
97
- MarkdownUiBlockUpdate,
98
- SearchInputMultiValueUiBlockUpdate,
99
- SearchInputUiBlockUpdate,
100
- SectionUiBlockUpdate,
101
- SelectorInputMultiValueUiBlockUpdate,
102
- SelectorInputUiBlockUpdate,
103
- TableUiBlockUpdate,
104
- TextInputUiBlockUpdate,
105
- UnknownType,
106
- ]
107
-
108
- _UI_BLOCK_MAPPINGS_CREATE = {
109
- ButtonUiBlock: ButtonUiBlockCreate,
110
- ChipUiBlock: ChipUiBlockCreate,
111
- DropdownMultiValueUiBlock: DropdownMultiValueUiBlockCreate,
112
- DropdownUiBlock: DropdownUiBlockCreate,
113
- MarkdownUiBlock: MarkdownUiBlockCreate,
114
- SearchInputMultiValueUiBlock: SearchInputMultiValueUiBlockCreate,
115
- SearchInputUiBlock: SearchInputUiBlockCreate,
116
- SectionUiBlock: SectionUiBlockCreate,
117
- SelectorInputMultiValueUiBlock: SelectorInputMultiValueUiBlockCreate,
118
- SelectorInputUiBlock: SelectorInputUiBlockCreate,
119
- TableUiBlock: TableUiBlockCreate,
120
- TextInputUiBlock: TextInputUiBlockCreate,
121
- }
122
-
123
- _UI_BLOCK_MAPPINGS_UPDATE = {
124
- ButtonUiBlock: ButtonUiBlockUpdate,
125
- ChipUiBlock: ChipUiBlockUpdate,
126
- DropdownMultiValueUiBlock: DropdownMultiValueUiBlockUpdate,
127
- DropdownUiBlock: DropdownUiBlockUpdate,
128
- MarkdownUiBlock: MarkdownUiBlockUpdate,
129
- SearchInputMultiValueUiBlock: SearchInputMultiValueUiBlockUpdate,
130
- SearchInputUiBlock: SearchInputUiBlockUpdate,
131
- SectionUiBlock: SectionUiBlockUpdate,
132
- SelectorInputMultiValueUiBlock: SelectorInputMultiValueUiBlockUpdate,
133
- SelectorInputUiBlock: SelectorInputUiBlockUpdate,
134
- TableUiBlock: TableUiBlockUpdate,
135
- TextInputUiBlock: TextInputUiBlockUpdate,
136
- }
137
-
138
-
139
- def _ui_block_to_create(block: _UiBlock) -> _UiBlockCreate:
37
+ def _ui_block_to_create(block: UiBlock) -> _UiBlockCreate:
140
38
  # Rely on the fact that the read/write shapes are compatible, for now
141
39
  if isinstance(block, UnknownType):
142
40
  return block
@@ -150,7 +48,7 @@ def _ui_block_to_create(block: _UiBlock) -> _UiBlockCreate:
150
48
  return block # type: ignore
151
49
 
152
50
 
153
- def _ui_block_to_update(block: _UiBlock) -> _UiBlockUpdate:
51
+ def _ui_block_to_update(block: UiBlock) -> _UiBlockUpdate:
154
52
  # Rely on the fact that the read/write shapes are compatible, for now
155
53
  # Update is functionally the same as create at the moment but different for type safety
156
54
  # and reserved in case the shapes do diverge later
@@ -166,44 +64,27 @@ def _ui_block_to_update(block: _UiBlock) -> _UiBlockUpdate:
166
64
  return block # type: ignore
167
65
 
168
66
 
169
- class DuplicateBlockIdError(Exception):
170
- """Error indicating that duplicate ids were present on blocks within a Canvas."""
171
-
172
- pass
173
-
174
-
175
- class NoMatchingBlocksError(Exception):
176
- """
177
- Error indicating that blocks were expected, but none matched.
178
-
179
- Used to prevent requiring developers to handle Optional[_UiBlock] for type safety.
180
- """
181
-
182
- pass
183
-
184
-
185
- class _CanvasBuilderUiBlock(Generic[_UiBlockType]):
67
+ class CanvasBuilderUiBlock(Generic[UiBlockType]):
186
68
  """Internal UI block wrapper for CanvasBuilder."""
187
69
 
188
- _block: _UiBlockType
70
+ _block: UiBlockType
189
71
  _builder: CanvasBuilder
190
72
 
191
- def __init__(self, block: _UiBlockType, builder: CanvasBuilder):
73
+ def __init__(self, block: UiBlockType, builder: CanvasBuilder):
74
+ """Init CanvasBuilderUiBlock."""
192
75
  self._block = block
193
76
  self._builder = builder
194
77
 
195
78
  @classmethod
196
- def from_api_model(
197
- cls, block: _UiBlockType, builder: CanvasBuilder
198
- ) -> _CanvasBuilderUiBlock[_UiBlockType]:
79
+ def from_api_model(cls, block: UiBlockType, builder: CanvasBuilder) -> CanvasBuilderUiBlock[UiBlockType]:
199
80
  """Create a _CanvasBuilderUiBlock from an underlying API model."""
200
81
  return cls(block, builder)
201
82
 
202
- def to_api_model(self) -> _UiBlockType:
83
+ def to_api_model(self) -> UiBlockType:
203
84
  """Convert to the underlying API model."""
204
85
  return self._block
205
86
 
206
- def children(self) -> _CanvasBuilderBlockStream:
87
+ def children(self) -> CanvasBuilderBlockStream:
207
88
  """
208
89
  Return children for blocks when applicable, such as for section blocks.
209
90
 
@@ -214,16 +95,16 @@ class _CanvasBuilderUiBlock(Generic[_UiBlockType]):
214
95
  # MyPy can't recognize the type narrowing when we check .children below
215
96
  section_block = cast(SectionUiBlock, model)
216
97
  child_blocks = [
217
- _CanvasBuilderUiBlock.from_api_model(block, self._builder) for block in section_block.children
98
+ CanvasBuilderUiBlock.from_api_model(block, self._builder) for block in section_block.children
218
99
  ]
219
100
  # Pass reference to parent block (self)
220
- return _CanvasBuilderBlockStream(self._builder, child_blocks, child_blocks, self)
221
- return _CanvasBuilderBlockStream(self._builder, [], [])
101
+ return CanvasBuilderBlockStream(self._builder, child_blocks, child_blocks, self)
102
+ return CanvasBuilderBlockStream(self._builder, [], [])
222
103
 
223
- def replace(self, new_blocks: List[_UiBlock]) -> None:
104
+ def replace(self, new_blocks: List[UiBlock]) -> None:
224
105
  """Replace block with provided new_blocks."""
225
106
  parent = self._parent_block()
226
- model = cast(_UiBlock, self.to_api_model())
107
+ model = cast(UiBlock, self.to_api_model())
227
108
  if parent:
228
109
  self.insert_after(new_blocks)
229
110
  # Keeps MyPy happy; SectionUiBlock is not valid for .children of SectionUiBlock
@@ -237,7 +118,7 @@ class _CanvasBuilderUiBlock(Generic[_UiBlockType]):
237
118
  def remove(self) -> None:
238
119
  """Remove block."""
239
120
  parent = self._parent_block()
240
- model = cast(_UiBlock, self.to_api_model())
121
+ model = cast(UiBlock, self.to_api_model())
241
122
  if parent:
242
123
  # Keeps MyPy happy; SectionUiBlock is not valid for .children of SectionUiBlock
243
124
  assert not (isinstance(model, SectionUiBlock) or isinstance(model, TableUiBlock))
@@ -246,15 +127,15 @@ class _CanvasBuilderUiBlock(Generic[_UiBlockType]):
246
127
  # noinspection PyProtectedMember
247
128
  self._builder._source_canvas.blocks.remove(model)
248
129
 
249
- def insert_after(self, new_blocks: List[_UiBlock]) -> None:
130
+ def insert_after(self, new_blocks: List[UiBlock]) -> None:
250
131
  """Insert new_blocks after block."""
251
132
  self._nested_insert(new_blocks, 1)
252
133
 
253
- def insert_before(self, new_blocks: List[_UiBlock]) -> None:
134
+ def insert_before(self, new_blocks: List[UiBlock]) -> None:
254
135
  """Insert new_blocks before block."""
255
136
  self._nested_insert(new_blocks, 0)
256
137
 
257
- def _nested_insert(self, new_blocks: List[_UiBlock], offset: int) -> None:
138
+ def _nested_insert(self, new_blocks: List[UiBlock], offset: int) -> None:
258
139
  """
259
140
  Nested insert.
260
141
 
@@ -269,7 +150,7 @@ class _CanvasBuilderUiBlock(Generic[_UiBlockType]):
269
150
  # Using list() to solve "List" is invariant creates a copy which means this stops working
270
151
  self._insert(new_blocks, offset, child_blocks, self.to_api_model()) # type: ignore
271
152
  parent_block.children = child_blocks
272
- parent_builder_block = _CanvasBuilderUiBlock.from_api_model(parent_block, self._builder)
153
+ parent_builder_block = CanvasBuilderUiBlock.from_api_model(parent_block, self._builder)
273
154
  parent_builder_block.insert_after([parent_block])
274
155
  # noinspection PyProtectedMember
275
156
  self._builder._source_canvas.blocks.remove(parent_builder_block.to_api_model())
@@ -278,9 +159,7 @@ class _CanvasBuilderUiBlock(Generic[_UiBlockType]):
278
159
  self._insert(new_blocks, offset, self._builder._source_canvas.blocks, self.to_api_model())
279
160
 
280
161
  @staticmethod
281
- def _insert(
282
- new_blocks: List[_UiBlock], offset: int, blocks: List[_UiBlock], target_block: _UiBlock
283
- ) -> None:
162
+ def _insert(new_blocks: List[UiBlock], offset: int, blocks: List[UiBlock], target_block: UiBlock) -> None:
284
163
  """Insert new_blocks before block as a side effect."""
285
164
  index = blocks.index(target_block)
286
165
  for count, new_block in enumerate(new_blocks):
@@ -295,26 +174,29 @@ class _CanvasBuilderUiBlock(Generic[_UiBlockType]):
295
174
  return None
296
175
 
297
176
 
298
- class _CanvasBuilderFilter(Protocol):
299
- def __call__(self, block: _UiBlockType) -> bool:
177
+ class CanvasBuilderFilter(Protocol):
178
+ """Callable protocol for specifying a predicate for filtering UiBlocks."""
179
+
180
+ def __call__(self, block: UiBlockType) -> bool:
300
181
  """Return True if the UiBlock matches specified conditions."""
301
182
  pass
302
183
 
303
184
 
304
- class _FilteredCanvasBuilderBlockStream:
305
- """Internal Filtered UI block list wrapper for CanvasBuilder."""
185
+ class FilteredCanvasBuilderBlockStream:
186
+ """Filtered UI block list wrapper for CanvasBuilder."""
306
187
 
307
188
  _builder: CanvasBuilder
308
- _blocks: List[_CanvasBuilderUiBlock]
309
- _selected_blocks: List[_CanvasBuilderUiBlock]
189
+ _blocks: List[CanvasBuilderUiBlock]
190
+ _selected_blocks: List[CanvasBuilderUiBlock]
310
191
  _cursor: int
311
192
 
312
193
  def __init__(
313
194
  self,
314
195
  builder: CanvasBuilder,
315
- blocks: List[_CanvasBuilderUiBlock],
316
- selected_blocks: List[_CanvasBuilderUiBlock],
196
+ blocks: List[CanvasBuilderUiBlock],
197
+ selected_blocks: List[CanvasBuilderUiBlock],
317
198
  ):
199
+ """Init FilteredCanvasBuilderBlockStream."""
318
200
  self._builder = builder
319
201
  self._blocks = blocks
320
202
  self._selected_blocks = selected_blocks
@@ -339,26 +221,26 @@ class _FilteredCanvasBuilderBlockStream:
339
221
  """
340
222
  # noinspection PyProtectedMember
341
223
  blocks = [
342
- _CanvasBuilderUiBlock.from_api_model(block, builder)
224
+ CanvasBuilderUiBlock.from_api_model(block, builder)
343
225
  for block in builder._source_canvas.blocks
344
226
  if not isinstance(block, UnknownType)
345
227
  ]
346
228
  return cls(builder, blocks, blocks)
347
229
 
348
- def filter(self, filter_function: _CanvasBuilderFilter) -> _FilteredCanvasBuilderBlockStream:
230
+ def filter(self, filter_function: CanvasBuilderFilter) -> FilteredCanvasBuilderBlockStream:
349
231
  """
350
232
  Filter.
351
233
 
352
234
  Accept a predicate that evaluates if a UiBlock should be included in the result or not.
353
235
  Returns a new stream of blocks filtered to the predicate, which is further operable.
354
236
  """
355
- return _CanvasBuilderBlockStream(
237
+ return CanvasBuilderBlockStream(
356
238
  self._builder,
357
239
  self._blocks,
358
240
  [block for block in self._selected_blocks if filter_function(block.to_api_model())],
359
241
  )
360
242
 
361
- def get_by_id(self, block_id: str) -> _CanvasBuilderUiBlock:
243
+ def get_by_id(self, block_id: str) -> CanvasBuilderUiBlock:
362
244
  """
363
245
  Get a block by its id.
364
246
 
@@ -370,8 +252,8 @@ class _FilteredCanvasBuilderBlockStream:
370
252
  return matched_block
371
253
 
372
254
  def _block_by_id(
373
- self, block_id: str, blocks: List[_CanvasBuilderUiBlock]
374
- ) -> Optional[_CanvasBuilderUiBlock]:
255
+ self, block_id: str, blocks: List[CanvasBuilderUiBlock]
256
+ ) -> Optional[CanvasBuilderUiBlock]:
375
257
  for block in blocks:
376
258
  api_block = block.to_api_model()
377
259
  if api_block.id == block_id:
@@ -387,13 +269,13 @@ class _FilteredCanvasBuilderBlockStream:
387
269
  """Return a count of the elements in the list of blocks."""
388
270
  return len(self._selected_blocks)
389
271
 
390
- def first(self) -> _CanvasBuilderUiBlock:
272
+ def first(self) -> CanvasBuilderUiBlock:
391
273
  """Return the first block in the list."""
392
274
  if len(self._selected_blocks) < 1:
393
275
  raise NoMatchingBlocksError
394
276
  return self._selected_blocks[0]
395
277
 
396
- def last(self) -> _CanvasBuilderUiBlock:
278
+ def last(self) -> CanvasBuilderUiBlock:
397
279
  """Return the last block in the list."""
398
280
  if len(self._selected_blocks) < 1:
399
281
  raise NoMatchingBlocksError
@@ -408,26 +290,27 @@ class _FilteredCanvasBuilderBlockStream:
408
290
  self._builder._source_canvas.blocks = updated_blocks
409
291
 
410
292
 
411
- class _CanvasBuilderBlockStream(_FilteredCanvasBuilderBlockStream):
293
+ class CanvasBuilderBlockStream(FilteredCanvasBuilderBlockStream):
412
294
  """
413
295
  Internal UI block list wrapper for CanvasBuilder.
414
296
 
415
297
  Possesses some additional operations unavailable to filtered block streams.
416
298
  """
417
299
 
418
- _parent: Optional[_CanvasBuilderUiBlock]
300
+ _parent: Optional[CanvasBuilderUiBlock]
419
301
 
420
302
  def __init__(
421
303
  self,
422
304
  builder: CanvasBuilder,
423
- blocks: List[_CanvasBuilderUiBlock],
424
- selected_blocks: List[_CanvasBuilderUiBlock],
425
- parent: Optional[_CanvasBuilderUiBlock] = None,
305
+ blocks: List[CanvasBuilderUiBlock],
306
+ selected_blocks: List[CanvasBuilderUiBlock],
307
+ parent: Optional[CanvasBuilderUiBlock] = None,
426
308
  ):
309
+ """Init CanvasBuilderBlockStream."""
427
310
  super().__init__(builder, blocks, selected_blocks)
428
311
  self._parent = parent
429
312
 
430
- def append(self, new_blocks: List[_UiBlock]) -> None:
313
+ def append(self, new_blocks: List[UiBlock]) -> None:
431
314
  """
432
315
  Append new_blocks to the end of list of blocks.
433
316
 
@@ -484,7 +367,8 @@ class CanvasBuilder:
484
367
  resource_id: str,
485
368
  enabled: bool = True,
486
369
  session_id: Optional[str] = None,
487
- blocks: Optional[List[_UiBlock]] = None,
370
+ blocks: Optional[List[UiBlock]] = None,
371
+ data: Optional[JsonType] = None,
488
372
  ):
489
373
  """
490
374
  Init AppCanvas.
@@ -498,6 +382,7 @@ class CanvasBuilder:
498
382
  enabled=enabled,
499
383
  session_id=session_id,
500
384
  blocks=blocks if blocks else [],
385
+ data=json.dumps(data) if data is not None else None,
501
386
  )
502
387
 
503
388
  @classmethod
@@ -514,6 +399,7 @@ class CanvasBuilder:
514
399
  enabled=canvas.enabled,
515
400
  session_id=canvas.session_id,
516
401
  blocks=canvas.blocks,
402
+ data=json.loads(canvas.data) if isinstance(canvas.data, str) else None,
517
403
  )
518
404
 
519
405
  def _with_enabled(self, value: bool) -> CanvasBuilder:
@@ -525,23 +411,65 @@ class CanvasBuilder:
525
411
  enabled=value,
526
412
  session_id=self._source_canvas.session_id,
527
413
  blocks=self._source_canvas.blocks,
414
+ data=self.data_to_json(),
528
415
  )
529
416
 
530
- def with_disabled(self) -> CanvasBuilder:
417
+ def with_enabled(self, enabled: bool = True) -> CanvasBuilder:
531
418
  """
532
- Return a new CanvasBuilder with the underlying canvas disabled.
419
+ Return a new CanvasBuilder with the underlying canvas enabled set to the specified value.
533
420
 
421
+ Specify `False` to disable the canvas.
534
422
  This does not call the API, it only assigns state in the CanvasBuilder.
535
423
  """
536
- return self._with_enabled(False)
424
+ return self._with_enabled(enabled)
537
425
 
538
- def with_enabled(self) -> CanvasBuilder:
426
+ def with_blocks(self, new_blocks: List[UiBlock]) -> CanvasBuilder:
539
427
  """
540
- Return a new CanvasBuilder with the underlying canvas enabled.
428
+ Return a new CanvasBuilder with the underlying blocks replaced.
541
429
 
542
430
  This does not call the API, it only assigns state in the CanvasBuilder.
543
431
  """
544
- return self._with_enabled(True)
432
+ return CanvasBuilder(
433
+ app_id=self._source_canvas.app.id,
434
+ feature_id=self._source_canvas.feature_id,
435
+ resource_id=self._source_canvas.resource_id,
436
+ enabled=self._source_canvas.enabled,
437
+ session_id=self._source_canvas.session_id,
438
+ blocks=new_blocks,
439
+ data=self.data_to_json(),
440
+ )
441
+
442
+ def with_data(self, new_data: Optional[JsonType]) -> CanvasBuilder:
443
+ """
444
+ Return a new CanvasBuilder with the underlying data replaced.
445
+
446
+ This does not call the API, it only assigns state in the CanvasBuilder.
447
+ """
448
+ return CanvasBuilder(
449
+ app_id=self._source_canvas.app.id,
450
+ feature_id=self._source_canvas.feature_id,
451
+ resource_id=self._source_canvas.resource_id,
452
+ enabled=self._source_canvas.enabled,
453
+ session_id=self._source_canvas.session_id,
454
+ blocks=self._source_canvas.blocks,
455
+ data=new_data,
456
+ )
457
+
458
+ def with_session_id(self, session_id: Optional[str]) -> CanvasBuilder:
459
+ """
460
+ Return a new CanvasBuilder with an optional session_id set.
461
+
462
+ This does not call the API, it only assigns state in the CanvasBuilder.
463
+ """
464
+ return CanvasBuilder(
465
+ app_id=self._source_canvas.app.id,
466
+ feature_id=self._source_canvas.feature_id,
467
+ resource_id=self._source_canvas.resource_id,
468
+ enabled=self._source_canvas.enabled,
469
+ session_id=session_id,
470
+ blocks=self._source_canvas.blocks,
471
+ data=self.data_to_json(),
472
+ )
545
473
 
546
474
  def inputs_to_dict(self) -> Dict[str, Union[str, List[str]]]:
547
475
  """
@@ -605,7 +533,7 @@ class CanvasBuilder:
605
533
 
606
534
  def inputs_to_dict_multi_value(self) -> Dict[str, List[str]]:
607
535
  """
608
- Read Inputs to dict, but only for multi-valued blocks.
536
+ Read Inputs to dict, but only for multivalued blocks.
609
537
 
610
538
  Return a dictionary of {block_id: block_value} for all blocks on the canvas with multivalued input values.
611
539
  Blocks that only have read attributes are omitted. Excludes TableUiBlock.
@@ -631,9 +559,9 @@ class CanvasBuilder:
631
559
 
632
560
  def _values_from_blocks(
633
561
  self,
634
- blocks: List[_UiBlock],
562
+ blocks: List[UiBlock],
635
563
  existing_keys: Optional[List[str]] = None,
636
- included_classes: Optional[Set[Type[_UiBlock]]] = None,
564
+ included_classes: Optional[Set[Type[UiBlock]]] = None,
637
565
  ) -> Dict[str, Union[str, List[str]]]:
638
566
  existing_keys = existing_keys if existing_keys else []
639
567
  values: Dict[str, Union[str, List[str]]] = dict()
@@ -670,6 +598,7 @@ class CanvasBuilder:
670
598
  enabled=self._source_canvas.enabled,
671
599
  session_id=self._source_canvas.session_id,
672
600
  blocks=[_ui_block_to_update(block) for block in self._source_canvas.blocks],
601
+ data=self._source_canvas.data,
673
602
  )
674
603
 
675
604
  def to_create(self) -> AppCanvasCreate:
@@ -681,18 +610,30 @@ class CanvasBuilder:
681
610
  enabled=self._source_canvas.enabled,
682
611
  session_id=self._source_canvas.session_id,
683
612
  blocks=[_ui_block_to_create(block) for block in self._source_canvas.blocks],
613
+ data=self._source_canvas.data,
684
614
  )
685
615
 
686
616
  @property
687
- def blocks(self) -> _CanvasBuilderBlockStream:
617
+ def blocks(self) -> CanvasBuilderBlockStream:
688
618
  """
689
619
  Blocks.
690
620
 
691
621
  Return a stream of blocks which can be iterated and operated on to mutate the canvas
692
622
  stored by the builder.
693
623
  """
694
- return _CanvasBuilderBlockStream.from_builder(self)
624
+ return CanvasBuilderBlockStream.from_builder(self)
625
+
626
+ def data_to_json(self) -> Optional[JsonType]:
627
+ """
628
+ Convert Canvas data to JSON.
629
+
630
+ Return a JSON object parsed from the string of the canvas's `data`, if present. Otherwise, return None.
631
+ """
632
+ return json.loads(self._source_canvas.data) if self._source_canvas.data is not None else None
633
+
634
+ def __eq__(self, other) -> bool:
635
+ return isinstance(other, CanvasBuilder) and self._source_canvas == other._source_canvas
695
636
 
696
637
 
697
- def _is_included_class(included_classes: Set[Type[_UiBlock]], target_class: _UiBlock) -> bool:
638
+ def _is_included_class(included_classes: Set[Type[UiBlock]], target_class: UiBlock) -> bool:
698
639
  return isinstance(target_class, tuple(c for c in included_classes))