jaclang 0.7.25__py3-none-any.whl → 0.7.27__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.

Potentially problematic release.


This version of jaclang might be problematic. Click here for more details.

Files changed (34) hide show
  1. jaclang/compiler/absyntree.py +27 -18
  2. jaclang/compiler/jac.lark +4 -1
  3. jaclang/compiler/parser.py +37 -20
  4. jaclang/compiler/passes/main/def_impl_match_pass.py +50 -13
  5. jaclang/compiler/passes/main/def_use_pass.py +1 -2
  6. jaclang/compiler/passes/main/pyast_gen_pass.py +46 -20
  7. jaclang/compiler/passes/main/pyast_load_pass.py +20 -6
  8. jaclang/compiler/passes/main/sym_tab_build_pass.py +1 -2
  9. jaclang/compiler/passes/main/tests/fixtures/uninitialized_hasvars.jac +26 -0
  10. jaclang/compiler/passes/main/tests/test_decl_def_match_pass.py +36 -29
  11. jaclang/compiler/passes/main/tests/test_def_use_pass.py +3 -3
  12. jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -2
  13. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +3 -3
  14. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +3 -3
  15. jaclang/compiler/tests/test_parser.py +7 -1
  16. jaclang/plugin/default.py +64 -18
  17. jaclang/plugin/feature.py +16 -1
  18. jaclang/plugin/spec.py +14 -2
  19. jaclang/plugin/tests/fixtures/graph_purger.jac +101 -0
  20. jaclang/plugin/tests/fixtures/other_root_access.jac +19 -0
  21. jaclang/plugin/tests/fixtures/savable_object.jac +84 -0
  22. jaclang/plugin/tests/test_jaseci.py +214 -6
  23. jaclang/runtimelib/architype.py +29 -1
  24. jaclang/runtimelib/memory.py +27 -8
  25. jaclang/runtimelib/utils.py +16 -0
  26. jaclang/tests/fixtures/architype_def_bug.jac +17 -0
  27. jaclang/tests/fixtures/decl_defn_param_name.jac +19 -0
  28. jaclang/tests/fixtures/multi_dim_array_split.jac +19 -0
  29. jaclang/tests/test_cli.py +18 -0
  30. jaclang/tests/test_language.py +43 -0
  31. {jaclang-0.7.25.dist-info → jaclang-0.7.27.dist-info}/METADATA +1 -1
  32. {jaclang-0.7.25.dist-info → jaclang-0.7.27.dist-info}/RECORD +34 -28
  33. {jaclang-0.7.25.dist-info → jaclang-0.7.27.dist-info}/WHEEL +0 -0
  34. {jaclang-0.7.25.dist-info → jaclang-0.7.27.dist-info}/entry_points.txt +0 -0
jaclang/plugin/default.py CHANGED
@@ -11,7 +11,7 @@ from collections import OrderedDict
11
11
  from dataclasses import field
12
12
  from functools import wraps
13
13
  from logging import getLogger
14
- from typing import Any, Callable, Mapping, Optional, Sequence, Type, Union
14
+ from typing import Any, Callable, Mapping, Optional, Sequence, Type, Union, cast
15
15
  from uuid import UUID
16
16
 
17
17
  from jaclang.compiler.constant import colors
@@ -41,6 +41,7 @@ from jaclang.runtimelib.constructs import (
41
41
  )
42
42
  from jaclang.runtimelib.importer import ImportPathSpec, JacImporter, PythonImporter
43
43
  from jaclang.runtimelib.machine import JacMachine, JacProgram
44
+ from jaclang.runtimelib.memory import Shelf, ShelfStorage
44
45
  from jaclang.runtimelib.utils import collect_node_connections, traverse_graph
45
46
 
46
47
 
@@ -50,9 +51,30 @@ hookimpl = pluggy.HookimplMarker("jac")
50
51
  logger = getLogger(__name__)
51
52
 
52
53
 
54
+ class JacCallableImplementation:
55
+ """Callable Implementations."""
56
+
57
+ @staticmethod
58
+ def get_object(id: str) -> Architype | None:
59
+ """Get object by id."""
60
+ if id == "root":
61
+ return Jac.get_context().root.architype
62
+ elif obj := Jac.get_context().mem.find_by_id(UUID(id)):
63
+ return obj.architype
64
+
65
+ return None
66
+
67
+
53
68
  class JacAccessValidationImpl:
54
69
  """Jac Access Validation Implementations."""
55
70
 
71
+ @staticmethod
72
+ @hookimpl
73
+ def elevate_root() -> None:
74
+ """Elevate context root to system_root."""
75
+ jctx = Jac.get_context()
76
+ jctx.root = jctx.system_root
77
+
56
78
  @staticmethod
57
79
  @hookimpl
58
80
  def allow_root(
@@ -379,7 +401,8 @@ class JacWalkerImpl:
379
401
  if walker.next:
380
402
  current_node = walker.next[-1].architype
381
403
  for i in warch._jac_entry_funcs_:
382
- if not i.trigger:
404
+ trigger = i.get_funcparam_annotations(i.func)
405
+ if not trigger:
383
406
  if i.func:
384
407
  i.func(warch, current_node)
385
408
  else:
@@ -387,7 +410,8 @@ class JacWalkerImpl:
387
410
  while len(walker.next):
388
411
  if current_node := walker.next.pop(0).architype:
389
412
  for i in current_node._jac_entry_funcs_:
390
- if not i.trigger or isinstance(warch, i.trigger):
413
+ trigger = i.get_funcparam_annotations(i.func)
414
+ if not trigger or isinstance(warch, trigger):
391
415
  if i.func:
392
416
  i.func(current_node, warch)
393
417
  else:
@@ -395,27 +419,30 @@ class JacWalkerImpl:
395
419
  if walker.disengaged:
396
420
  return warch
397
421
  for i in warch._jac_entry_funcs_:
398
- if not i.trigger or isinstance(current_node, i.trigger):
399
- if i.func and i.trigger:
422
+ trigger = i.get_funcparam_annotations(i.func)
423
+ if not trigger or isinstance(current_node, trigger):
424
+ if i.func and trigger:
400
425
  i.func(warch, current_node)
401
- elif not i.trigger:
426
+ elif not trigger:
402
427
  continue
403
428
  else:
404
429
  raise ValueError(f"No function {i.name} to call.")
405
430
  if walker.disengaged:
406
431
  return warch
407
432
  for i in warch._jac_exit_funcs_:
408
- if not i.trigger or isinstance(current_node, i.trigger):
409
- if i.func and i.trigger:
433
+ trigger = i.get_funcparam_annotations(i.func)
434
+ if not trigger or isinstance(current_node, trigger):
435
+ if i.func and trigger:
410
436
  i.func(warch, current_node)
411
- elif not i.trigger:
437
+ elif not trigger:
412
438
  continue
413
439
  else:
414
440
  raise ValueError(f"No function {i.name} to call.")
415
441
  if walker.disengaged:
416
442
  return warch
417
443
  for i in current_node._jac_exit_funcs_:
418
- if not i.trigger or isinstance(warch, i.trigger):
444
+ trigger = i.get_funcparam_annotations(i.func)
445
+ if not trigger or isinstance(warch, trigger):
419
446
  if i.func:
420
447
  i.func(current_node, warch)
421
448
  else:
@@ -423,7 +450,8 @@ class JacWalkerImpl:
423
450
  if walker.disengaged:
424
451
  return warch
425
452
  for i in warch._jac_exit_funcs_:
426
- if not i.trigger:
453
+ trigger = i.get_funcparam_annotations(i.func)
454
+ if not trigger:
427
455
  if i.func:
428
456
  i.func(warch, current_node)
429
457
  else:
@@ -562,14 +590,32 @@ class JacFeatureImpl(
562
590
 
563
591
  @staticmethod
564
592
  @hookimpl
565
- def get_object(id: str) -> Architype | None:
566
- """Get object by id."""
567
- if id == "root":
568
- return Jac.get_context().root.architype
569
- elif obj := Jac.get_context().mem.find_by_id(UUID(id)):
570
- return obj.architype
593
+ def reset_graph(root: Optional[Root] = None) -> int:
594
+ """Purge current or target graph."""
595
+ ctx = Jac.get_context()
596
+ mem = cast(ShelfStorage, ctx.mem)
597
+ ranchor = root.__jac__ if root else ctx.root
598
+
599
+ deleted_count = 0
600
+ for anchor in (
601
+ anchors.values()
602
+ if isinstance(anchors := mem.__shelf__, Shelf)
603
+ else mem.__mem__.values()
604
+ ):
605
+ if anchor == ranchor or anchor.root != ranchor.id:
606
+ continue
571
607
 
572
- return None
608
+ if loaded_anchor := mem.find_by_id(anchor.id):
609
+ deleted_count += 1
610
+ Jac.destroy(loaded_anchor)
611
+
612
+ return deleted_count
613
+
614
+ @staticmethod
615
+ @hookimpl
616
+ def get_object_func() -> Callable[[str], Architype | None]:
617
+ """Get object by id func."""
618
+ return JacCallableImplementation.get_object
573
619
 
574
620
  @staticmethod
575
621
  @hookimpl
jaclang/plugin/feature.py CHANGED
@@ -41,6 +41,11 @@ from jaclang.plugin.spec import (
41
41
  class JacAccessValidation:
42
42
  """Jac Access Validation Specs."""
43
43
 
44
+ @staticmethod
45
+ def elevate_root() -> None:
46
+ """Elevate context root to system_root."""
47
+ plugin_manager.hook.elevate_root()
48
+
44
49
  @staticmethod
45
50
  def allow_root(
46
51
  architype: Architype,
@@ -254,10 +259,20 @@ class JacFeature(
254
259
  """Get current execution context."""
255
260
  return plugin_manager.hook.get_context()
256
261
 
262
+ @staticmethod
263
+ def reset_graph(root: Optional[Root] = None) -> int:
264
+ """Purge current or target graph."""
265
+ return plugin_manager.hook.reset_graph(root=root)
266
+
257
267
  @staticmethod
258
268
  def get_object(id: str) -> Architype | None:
259
269
  """Get object given id."""
260
- return plugin_manager.hook.get_object(id=id)
270
+ return plugin_manager.hook.get_object_func()(id=id)
271
+
272
+ @staticmethod
273
+ def get_object_func() -> Callable[[str], Architype | None]:
274
+ """Get object given id."""
275
+ return plugin_manager.hook.get_object_func()
261
276
 
262
277
  @staticmethod
263
278
  def object_ref(obj: Architype) -> str:
jaclang/plugin/spec.py CHANGED
@@ -46,6 +46,12 @@ P = ParamSpec("P")
46
46
  class JacAccessValidationSpec:
47
47
  """Jac Access Validation Specs."""
48
48
 
49
+ @staticmethod
50
+ @hookspec(firstresult=True)
51
+ def elevate_root() -> None:
52
+ """Elevate context root to system_root."""
53
+ raise NotImplementedError
54
+
49
55
  @staticmethod
50
56
  @hookspec(firstresult=True)
51
57
  def allow_root(
@@ -246,8 +252,14 @@ class JacFeatureSpec(
246
252
 
247
253
  @staticmethod
248
254
  @hookspec(firstresult=True)
249
- def get_object(id: str) -> Architype | None:
250
- """Get object by id."""
255
+ def reset_graph(root: Optional[Root]) -> int:
256
+ """Purge current or target graph."""
257
+ raise NotImplementedError
258
+
259
+ @staticmethod
260
+ @hookspec(firstresult=True)
261
+ def get_object_func() -> Callable[[str], Architype | None]:
262
+ """Get object by id func."""
251
263
  raise NotImplementedError
252
264
 
253
265
  @staticmethod
@@ -0,0 +1,101 @@
1
+ node A {
2
+ has id: int;
3
+ }
4
+
5
+ node B {
6
+ has id: int;
7
+ }
8
+
9
+ node C {
10
+ has id: int;
11
+ }
12
+
13
+ node D {
14
+ has id: int;
15
+ }
16
+
17
+ node E {
18
+ has id: int;
19
+ }
20
+
21
+
22
+ walker populate {
23
+ can setup1 with `root entry {
24
+ for i in range(2) {
25
+ here ++> A(id=i);
26
+ }
27
+ visit [-->];
28
+ }
29
+
30
+ can setup2 with A entry {
31
+ for i in range(2) {
32
+ here ++> B(id=i);
33
+ }
34
+ visit [-->];
35
+ }
36
+
37
+ can setup3 with B entry {
38
+ for i in range(2) {
39
+ here ++> C(id=i);
40
+ }
41
+ visit [-->];
42
+ }
43
+
44
+ can setup4 with C entry {
45
+ for i in range(2) {
46
+ here ++> D(id=i);
47
+ }
48
+ visit [-->];
49
+ }
50
+
51
+ can setup5 with D entry {
52
+ for i in range(2) {
53
+ here ++> E(id=i);
54
+ }
55
+ visit [-->];
56
+ }
57
+ }
58
+
59
+ walker traverse {
60
+ can enter1 with `root entry {
61
+ print(here);
62
+ visit [-->];
63
+ }
64
+
65
+ can enter2 with A entry {
66
+ print(here);
67
+ visit [-->];
68
+ }
69
+
70
+ can enter3 with B entry {
71
+ print(here);
72
+ visit [-->];
73
+ }
74
+
75
+ can enter4 with C entry {
76
+ print(here);
77
+ visit [-->];
78
+ }
79
+
80
+ can enter5 with D entry {
81
+ print(here);
82
+ visit [-->];
83
+ }
84
+
85
+ can enter6 with E entry {
86
+ print(here);
87
+ visit [-->];
88
+ }
89
+ }
90
+
91
+ walker purge {
92
+ can purge with `root entry {
93
+ print(Jac.reset_graph());
94
+ }
95
+ }
96
+
97
+ walker check {
98
+ can enter with `root entry {
99
+ print(len(Jac.get_context().mem.__shelf__.values()));
100
+ }
101
+ }
@@ -23,6 +23,25 @@ walker update_node {
23
23
  }
24
24
  }
25
25
 
26
+ walker update_target_node {
27
+ has val: int;
28
+ has node_id: str;
29
+
30
+ can enter with `root entry {
31
+ target_node = &(self.node_id);
32
+ target_node.val = self.val;
33
+ }
34
+ }
35
+
36
+ walker update_node_forced {
37
+ has val: int;
38
+
39
+ can enter2 with A entry {
40
+ Jac.elevate_root();
41
+ here.val = self.val;
42
+ }
43
+ }
44
+
26
45
  walker create_node {
27
46
  has val: int;
28
47
 
@@ -0,0 +1,84 @@
1
+ enum Enum {
2
+ A = "a",
3
+ B = "b",
4
+ C = "c"
5
+ }
6
+
7
+ obj Child {
8
+ has val: int, arr: list[int], json: dict[str, int], enum_field: Enum;
9
+ }
10
+
11
+ obj Parent:Child: {
12
+ has child: Child;
13
+ }
14
+
15
+ obj SavableObject {
16
+ has val: int, arr: list[int], json: dict[str, int], parent: Parent, enum_field: Enum;
17
+ }
18
+
19
+ walker create_custom_object {
20
+ can enter1 with `root entry {
21
+ o = SavableObject(
22
+ val=0,
23
+ arr=[],
24
+ json={},
25
+ parent=Parent(
26
+ val=1,
27
+ arr=[1],
28
+ json={"a": 1},
29
+ child=Child(
30
+ val=2,
31
+ arr=[1, 2],
32
+ json={"a": 1, "b": 2},
33
+ enum_field = Enum.C
34
+ ),
35
+ enum_field = Enum.B
36
+ ),
37
+ enum_field = Enum.A
38
+ );
39
+ Jac.save(o);
40
+ print(jid(o));
41
+ print(o);
42
+ }
43
+ }
44
+
45
+ walker get_custom_object {
46
+ has object_id: str;
47
+
48
+ can enter1 with `root entry {
49
+ try {
50
+ print(&(self.object_id));
51
+ } except Exception as e {
52
+ print(None);
53
+ }
54
+ }
55
+ }
56
+
57
+ walker update_custom_object {
58
+ has object_id: str;
59
+
60
+ can enter1 with `root entry {
61
+ savable_object = &(self.object_id);
62
+ savable_object.parent.child.json["c"] = 3;
63
+ savable_object.parent.child.arr.append(3);
64
+ savable_object.parent.child.val = 3;
65
+ savable_object.parent.child.enum_field = Enum.A;
66
+ savable_object.parent.json["b"] = 2;
67
+ savable_object.parent.arr.append(2);
68
+ savable_object.parent.val = 2;
69
+ savable_object.parent.enum_field = Enum.C;
70
+ savable_object.json["a"] = 1;
71
+ savable_object.arr.append(1);
72
+ savable_object.val = 1;
73
+ savable_object.enum_field = Enum.B;
74
+ print(savable_object);
75
+ }
76
+ }
77
+
78
+ walker delete_custom_object {
79
+ has object_id: str;
80
+
81
+ can enter1 with `root entry {
82
+ Jac.destroy(&(self.object_id));
83
+ }
84
+ }
@@ -269,6 +269,75 @@ class TestJaseciPlugin(TestCase):
269
269
  )
270
270
  self._del_session(session)
271
271
 
272
+ def test_walker_purger(self) -> None:
273
+ """Test simple persistent object."""
274
+ session = self.fixture_abs_path("test_walker_purger.session")
275
+ self._output2buffer()
276
+ cli.enter(
277
+ filename=self.fixture_abs_path("graph_purger.jac"),
278
+ session=session,
279
+ entrypoint="populate",
280
+ args=[],
281
+ )
282
+ cli.enter(
283
+ filename=self.fixture_abs_path("graph_purger.jac"),
284
+ session=session,
285
+ entrypoint="traverse",
286
+ args=[],
287
+ )
288
+ cli.enter(
289
+ filename=self.fixture_abs_path("graph_purger.jac"),
290
+ session=session,
291
+ entrypoint="check",
292
+ args=[],
293
+ )
294
+ cli.enter(
295
+ filename=self.fixture_abs_path("graph_purger.jac"),
296
+ session=session,
297
+ entrypoint="purge",
298
+ args=[],
299
+ )
300
+ output = self.capturedOutput.getvalue().strip()
301
+ self.assertEqual(
302
+ output,
303
+ (
304
+ "Root()\n"
305
+ "A(id=0)\nA(id=1)\n"
306
+ "B(id=0)\nB(id=1)\nB(id=0)\nB(id=1)\n"
307
+ "C(id=0)\nC(id=1)\nC(id=0)\nC(id=1)\nC(id=0)\nC(id=1)\nC(id=0)\nC(id=1)\n"
308
+ "D(id=0)\nD(id=1)\nD(id=0)\nD(id=1)\nD(id=0)\nD(id=1)\nD(id=0)\nD(id=1)\n"
309
+ "D(id=0)\nD(id=1)\nD(id=0)\nD(id=1)\nD(id=0)\nD(id=1)\nD(id=0)\nD(id=1)\n"
310
+ "E(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\n"
311
+ "E(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\n"
312
+ "E(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\n"
313
+ "E(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\nE(id=0)\nE(id=1)\n"
314
+ "125\n124"
315
+ ),
316
+ )
317
+ self._output2buffer()
318
+ cli.enter(
319
+ filename=self.fixture_abs_path("graph_purger.jac"),
320
+ session=session,
321
+ entrypoint="traverse",
322
+ args=[],
323
+ )
324
+ cli.enter(
325
+ filename=self.fixture_abs_path("graph_purger.jac"),
326
+ session=session,
327
+ entrypoint="check",
328
+ args=[],
329
+ )
330
+ cli.enter(
331
+ filename=self.fixture_abs_path("graph_purger.jac"),
332
+ session=session,
333
+ entrypoint="purge",
334
+ args=[],
335
+ )
336
+ output = self.capturedOutput.getvalue().strip()
337
+ self.assertEqual(output, "Root()\n1\n0")
338
+
339
+ self._del_session(session)
340
+
272
341
  def trigger_access_validation_test(
273
342
  self, give_access_to_full_graph: bool, via_all: bool = False
274
343
  ) -> None:
@@ -316,6 +385,22 @@ class TestJaseciPlugin(TestCase):
316
385
  node=self.nodes[0],
317
386
  )
318
387
 
388
+ cli.enter(
389
+ filename=self.fixture_abs_path("other_root_access.jac"),
390
+ entrypoint="update_target_node",
391
+ args=[20, self.nodes[1]],
392
+ session=session,
393
+ root=self.roots[0],
394
+ )
395
+
396
+ cli.enter(
397
+ filename=self.fixture_abs_path("other_root_access.jac"),
398
+ entrypoint="update_target_node",
399
+ args=[10, self.nodes[0]],
400
+ session=session,
401
+ root=self.roots[1],
402
+ )
403
+
319
404
  cli.enter(
320
405
  filename=self.fixture_abs_path("other_root_access.jac"),
321
406
  entrypoint="check_node",
@@ -340,6 +425,55 @@ class TestJaseciPlugin(TestCase):
340
425
  self.assertEqual(archs[0], "A(val=2)")
341
426
  self.assertEqual(archs[1], "A(val=1)")
342
427
 
428
+ ##############################################
429
+ # WITH READ ACCESS BUT ELEVATED #
430
+ ##############################################
431
+
432
+ self._output2buffer()
433
+
434
+ cli.enter(
435
+ filename=self.fixture_abs_path("other_root_access.jac"),
436
+ entrypoint="update_node_forced",
437
+ args=[20],
438
+ session=session,
439
+ root=self.roots[0],
440
+ node=self.nodes[1],
441
+ )
442
+ cli.enter(
443
+ filename=self.fixture_abs_path("other_root_access.jac"),
444
+ entrypoint="update_node_forced",
445
+ args=[10],
446
+ session=session,
447
+ root=self.roots[1],
448
+ node=self.nodes[0],
449
+ )
450
+
451
+ cli.enter(
452
+ filename=self.fixture_abs_path("other_root_access.jac"),
453
+ entrypoint="check_node",
454
+ args=[],
455
+ session=session,
456
+ root=self.roots[0],
457
+ node=self.nodes[1],
458
+ )
459
+ cli.enter(
460
+ filename=self.fixture_abs_path("other_root_access.jac"),
461
+ entrypoint="check_node",
462
+ args=[],
463
+ session=session,
464
+ root=self.roots[1],
465
+ node=self.nodes[0],
466
+ )
467
+ archs = self.capturedOutput.getvalue().strip().split("\n")
468
+ self.assertTrue(len(archs) == 2)
469
+
470
+ # ---------- UPDATE SHOULD HAPPEN ---------- #
471
+
472
+ self.assertEqual(archs[0], "A(val=20)")
473
+ self.assertEqual(archs[1], "A(val=10)")
474
+
475
+ # ---------- DISALLOW READ ACCESS ---------- #
476
+
343
477
  self._output2buffer()
344
478
  cli.enter(
345
479
  filename=self.fixture_abs_path("other_root_access.jac"),
@@ -400,7 +534,7 @@ class TestJaseciPlugin(TestCase):
400
534
  cli.enter(
401
535
  filename=self.fixture_abs_path("other_root_access.jac"),
402
536
  entrypoint="update_node",
403
- args=[20],
537
+ args=[200],
404
538
  root=self.roots[0],
405
539
  node=self.nodes[1],
406
540
  session=session,
@@ -408,7 +542,7 @@ class TestJaseciPlugin(TestCase):
408
542
  cli.enter(
409
543
  filename=self.fixture_abs_path("other_root_access.jac"),
410
544
  entrypoint="update_node",
411
- args=[10],
545
+ args=[100],
412
546
  session=session,
413
547
  root=self.roots[1],
414
548
  node=self.nodes[0],
@@ -433,10 +567,12 @@ class TestJaseciPlugin(TestCase):
433
567
  archs = self.capturedOutput.getvalue().strip().split("\n")
434
568
  self.assertTrue(len(archs) == 2)
435
569
 
436
- # --------- UPDATE SHOULD HAPPEN -------- #
570
+ # ---------- UPDATE SHOULD HAPPEN ---------- #
437
571
 
438
- self.assertEqual(archs[0], "A(val=20)")
439
- self.assertEqual(archs[1], "A(val=10)")
572
+ self.assertEqual(archs[0], "A(val=200)")
573
+ self.assertEqual(archs[1], "A(val=100)")
574
+
575
+ # ---------- DISALLOW WRITE ACCESS --------- #
440
576
 
441
577
  self._output2buffer()
442
578
  cli.enter(
@@ -474,7 +610,7 @@ class TestJaseciPlugin(TestCase):
474
610
  )
475
611
  self.assertFalse(self.capturedOutput.getvalue().strip())
476
612
 
477
- # --------- ROOTS RESET OWN NODE -------- #
613
+ # ---------- ROOTS RESET OWN NODE ---------- #
478
614
 
479
615
  cli.enter(
480
616
  filename=self.fixture_abs_path("other_root_access.jac"),
@@ -608,3 +744,75 @@ class TestJaseciPlugin(TestCase):
608
744
  )
609
745
 
610
746
  self._del_session(session)
747
+
748
+ def test_savable_object(self) -> None:
749
+ """Test ObjectAnchor save."""
750
+ global session
751
+ session = self.fixture_abs_path("other_root_access.session")
752
+
753
+ self._output2buffer()
754
+
755
+ cli.enter(
756
+ filename=self.fixture_abs_path("savable_object.jac"),
757
+ entrypoint="create_custom_object",
758
+ args=[],
759
+ session=session,
760
+ )
761
+
762
+ prints = self.capturedOutput.getvalue().strip().split("\n")
763
+ id = prints[0]
764
+
765
+ self.assertEqual(
766
+ "SavableObject(val=0, arr=[], json={}, parent=Parent(val=1, arr=[1], json"
767
+ "={'a': 1}, enum_field=<Enum.B: 'b'>, child=Child(val=2, arr=[1, 2], json"
768
+ "={'a': 1, 'b': 2}, enum_field=<Enum.C: 'c'>)), enum_field=<Enum.A: 'a'>)",
769
+ prints[1],
770
+ )
771
+
772
+ self._output2buffer()
773
+
774
+ cli.enter(
775
+ filename=self.fixture_abs_path("savable_object.jac"),
776
+ entrypoint="get_custom_object",
777
+ args=[id],
778
+ session=session,
779
+ )
780
+ self.assertEqual(
781
+ "SavableObject(val=0, arr=[], json={}, parent=Parent(val=1, arr=[1], json"
782
+ "={'a': 1}, enum_field=<Enum.B: 'b'>, child=Child(val=2, arr=[1, 2], json"
783
+ "={'a': 1, 'b': 2}, enum_field=<Enum.C: 'c'>)), enum_field=<Enum.A: 'a'>)",
784
+ self.capturedOutput.getvalue().strip(),
785
+ )
786
+
787
+ self._output2buffer()
788
+
789
+ cli.enter(
790
+ filename=self.fixture_abs_path("savable_object.jac"),
791
+ entrypoint="update_custom_object",
792
+ args=[id],
793
+ session=session,
794
+ )
795
+
796
+ self.assertEqual(
797
+ "SavableObject(val=1, arr=[1], json={'a': 1}, parent=Parent(val=2, arr=[1, 2], json"
798
+ "={'a': 1, 'b': 2}, enum_field=<Enum.C: 'c'>, child=Child(val=3, arr=[1, 2, 3], json"
799
+ "={'a': 1, 'b': 2, 'c': 3}, enum_field=<Enum.A: 'a'>)), enum_field=<Enum.B: 'b'>)",
800
+ self.capturedOutput.getvalue().strip(),
801
+ )
802
+
803
+ self._output2buffer()
804
+
805
+ cli.enter(
806
+ filename=self.fixture_abs_path("savable_object.jac"),
807
+ entrypoint="delete_custom_object",
808
+ args=[id],
809
+ session=session,
810
+ )
811
+
812
+ cli.enter(
813
+ filename=self.fixture_abs_path("savable_object.jac"),
814
+ entrypoint="get_custom_object",
815
+ args=[id],
816
+ session=session,
817
+ )
818
+ self.assertEqual("None", self.capturedOutput.getvalue().strip())