jaclang 0.7.16__py3-none-any.whl → 0.7.18__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 (81) hide show
  1. jaclang/cli/cli.py +140 -77
  2. jaclang/compiler/absyntree.py +9 -4
  3. jaclang/compiler/constant.py +8 -8
  4. jaclang/compiler/parser.py +10 -2
  5. jaclang/compiler/passes/main/__init__.py +1 -1
  6. jaclang/compiler/passes/main/access_modifier_pass.py +96 -147
  7. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +152 -50
  8. jaclang/compiler/passes/main/import_pass.py +88 -59
  9. jaclang/compiler/passes/main/py_collect_dep_pass.py +70 -0
  10. jaclang/compiler/passes/main/pyast_gen_pass.py +46 -6
  11. jaclang/compiler/passes/main/pyast_load_pass.py +1 -0
  12. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +7 -0
  13. jaclang/compiler/passes/main/schedules.py +9 -2
  14. jaclang/compiler/passes/main/sym_tab_build_pass.py +9 -5
  15. jaclang/compiler/passes/main/tests/fixtures/autoimpl.empty.impl.jac +0 -0
  16. jaclang/compiler/passes/main/tests/fixtures/autoimpl.jac +1 -1
  17. jaclang/compiler/passes/main/tests/fixtures/py_imp_test.jac +29 -0
  18. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.py +3 -0
  19. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/color.py +3 -0
  20. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/constants.py +5 -0
  21. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/display.py +2 -0
  22. jaclang/compiler/passes/main/tests/test_import_pass.py +72 -13
  23. jaclang/compiler/passes/main/type_check_pass.py +15 -5
  24. jaclang/compiler/passes/tool/jac_formatter_pass.py +13 -3
  25. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +37 -41
  26. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +37 -41
  27. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/access_mod_check.jac +27 -0
  28. jaclang/compiler/passes/utils/mypy_ast_build.py +45 -0
  29. jaclang/compiler/symtable.py +16 -11
  30. jaclang/compiler/tests/test_importer.py +17 -9
  31. jaclang/langserve/engine.py +64 -16
  32. jaclang/langserve/server.py +16 -1
  33. jaclang/langserve/tests/fixtures/import_include_statements.jac +3 -3
  34. jaclang/langserve/tests/fixtures/rename.jac +30 -0
  35. jaclang/langserve/tests/test_server.py +224 -6
  36. jaclang/langserve/utils.py +28 -98
  37. jaclang/plugin/builtin.py +8 -4
  38. jaclang/plugin/default.py +86 -64
  39. jaclang/plugin/feature.py +13 -13
  40. jaclang/plugin/spec.py +10 -11
  41. jaclang/plugin/tests/fixtures/other_root_access.jac +82 -0
  42. jaclang/plugin/tests/test_jaseci.py +414 -42
  43. jaclang/runtimelib/architype.py +481 -333
  44. jaclang/runtimelib/constructs.py +5 -8
  45. jaclang/runtimelib/context.py +89 -69
  46. jaclang/runtimelib/importer.py +16 -15
  47. jaclang/runtimelib/machine.py +66 -2
  48. jaclang/runtimelib/memory.py +134 -75
  49. jaclang/runtimelib/utils.py +17 -10
  50. jaclang/settings.py +2 -4
  51. jaclang/tests/fixtures/access_checker.jac +12 -17
  52. jaclang/tests/fixtures/access_modifier.jac +88 -33
  53. jaclang/tests/fixtures/baddy.jac +3 -0
  54. jaclang/tests/fixtures/bar.jac +34 -0
  55. jaclang/tests/fixtures/builtin_dotgen.jac +1 -0
  56. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  57. jaclang/tests/fixtures/edge_ops.jac +1 -1
  58. jaclang/tests/fixtures/edges_walk.jac +1 -1
  59. jaclang/tests/fixtures/foo.jac +43 -0
  60. jaclang/tests/fixtures/game1.jac +1 -1
  61. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  62. jaclang/tests/fixtures/import.jac +9 -0
  63. jaclang/tests/fixtures/index_slice.jac +30 -0
  64. jaclang/tests/fixtures/objref.jac +12 -0
  65. jaclang/tests/fixtures/pyfunc_1.py +1 -1
  66. jaclang/tests/fixtures/pyfunc_2.py +2 -2
  67. jaclang/tests/fixtures/pygame_mock/__init__.py +3 -0
  68. jaclang/tests/fixtures/pygame_mock/color.py +3 -0
  69. jaclang/tests/fixtures/pygame_mock/constants.py +5 -0
  70. jaclang/tests/fixtures/pygame_mock/display.py +2 -0
  71. jaclang/tests/fixtures/pygame_mock/inner/__init__.py +0 -0
  72. jaclang/tests/fixtures/pygame_mock/inner/iner_mod.py +2 -0
  73. jaclang/tests/test_cli.py +49 -6
  74. jaclang/tests/test_language.py +126 -80
  75. jaclang/tests/test_reference.py +2 -9
  76. jaclang/utils/treeprinter.py +30 -3
  77. {jaclang-0.7.16.dist-info → jaclang-0.7.18.dist-info}/METADATA +3 -1
  78. {jaclang-0.7.16.dist-info → jaclang-0.7.18.dist-info}/RECORD +81 -59
  79. /jaclang/tests/fixtures/{err.test.jac → baddy.test.jac} +0 -0
  80. {jaclang-0.7.16.dist-info → jaclang-0.7.18.dist-info}/WHEEL +0 -0
  81. {jaclang-0.7.16.dist-info → jaclang-0.7.18.dist-info}/entry_points.txt +0 -0
@@ -4,26 +4,24 @@ from __future__ import annotations
4
4
 
5
5
 
6
6
  from .architype import (
7
+ Anchor,
7
8
  Architype,
8
9
  DSFunc,
9
10
  EdgeAnchor,
10
11
  EdgeArchitype,
11
- ElementAnchor,
12
12
  GenericEdge,
13
13
  NodeAnchor,
14
14
  NodeArchitype,
15
- ObjectAnchor,
16
15
  Root,
17
16
  WalkerAnchor,
18
17
  WalkerArchitype,
19
18
  )
20
- from .context import ExecutionContext, exec_context
21
- from .memory import Memory, ShelveStorage
19
+ from .context import ExecutionContext
20
+ from .memory import Memory, ShelfStorage
22
21
  from .test import JacTestCheck, JacTestResult, JacTextTestRunner
23
22
 
24
23
  __all__ = [
25
- "ElementAnchor",
26
- "ObjectAnchor",
24
+ "Anchor",
27
25
  "NodeAnchor",
28
26
  "EdgeAnchor",
29
27
  "WalkerAnchor",
@@ -35,9 +33,8 @@ __all__ = [
35
33
  "Root",
36
34
  "DSFunc",
37
35
  "Memory",
38
- "ShelveStorage",
36
+ "ShelfStorage",
39
37
  "ExecutionContext",
40
- "exec_context",
41
38
  "JacTestResult",
42
39
  "JacTextTestRunner",
43
40
  "JacTestCheck",
@@ -4,80 +4,100 @@ from __future__ import annotations
4
4
 
5
5
  import unittest
6
6
  from contextvars import ContextVar
7
- from typing import Callable, Optional
7
+ from typing import Any, Callable, Optional, cast
8
8
  from uuid import UUID
9
9
 
10
- from .architype import Architype, Root
11
- from .machine import JacMachine, JacProgram
12
- from .memory import Memory, ShelveStorage
10
+ from .architype import NodeAnchor, Root
11
+ from .memory import Memory, ShelfStorage
13
12
 
14
13
 
15
- class ExecutionContext:
16
- """Default Execution Context implementation."""
17
-
18
- mem: Optional[Memory]
19
- root: Optional[Root]
20
-
21
- def __init__(self) -> None:
22
- """Create execution context."""
23
- super().__init__()
24
- self.mem = ShelveStorage()
25
- self.root = None
26
- self.jac_machine = JacMachine()
27
- jac_program = JacProgram(mod_bundle=None, bytecode=None)
28
- self.jac_machine.attach_program(jac_program)
29
-
30
- def init_memory(self, base_path: str = "", session: str = "") -> None:
31
- """Initialize memory."""
32
- if session:
33
- self.mem = ShelveStorage(session)
34
- else:
35
- self.mem = Memory()
36
- self.jac_machine = JacMachine(base_path)
37
- jac_program = JacProgram(mod_bundle=None, bytecode=None)
38
- self.jac_machine.attach_program(jac_program)
39
-
40
- def get_root(self) -> Root:
41
- """Get the root object."""
42
- if self.mem is None:
43
- raise ValueError("Memory not initialized")
44
-
45
- if not self.root:
46
- root = self.mem.get_obj(UUID(int=0))
47
- if root is None:
48
- self.root = Root()
49
- self.mem.save_obj(self.root, persistent=self.root._jac_.persistent)
50
- elif not isinstance(root, Root):
51
- raise ValueError(f"Invalid root object: {root}")
52
- else:
53
- self.root = root
54
- return self.root
55
-
56
- def get_obj(self, obj_id: UUID) -> Architype | None:
57
- """Get object from memory."""
58
- if self.mem is None:
59
- raise ValueError("Memory not initialized")
60
-
61
- return self.mem.get_obj(obj_id)
62
-
63
- def save_obj(self, item: Architype, persistent: bool) -> None:
64
- """Save object to memory."""
65
- if self.mem is None:
66
- raise ValueError("Memory not initialized")
67
-
68
- self.mem.save_obj(item, persistent)
69
-
70
- def reset(self) -> None:
71
- """Reset the execution context."""
72
- if self.mem:
73
- self.mem.close()
74
- self.mem = None
75
- self.root = None
76
-
77
-
78
- exec_context: ContextVar[ExecutionContext | None] = ContextVar(
79
- "ExecutionContext", default=None
14
+ EXECUTION_CONTEXT = ContextVar[Optional["ExecutionContext"]]("ExecutionContext")
15
+
16
+ SUPER_ROOT_UUID = UUID("00000000-0000-0000-0000-000000000000")
17
+ SUPER_ROOT_ARCHITYPE = object.__new__(Root)
18
+ SUPER_ROOT_ANCHOR = NodeAnchor(
19
+ id=SUPER_ROOT_UUID, architype=SUPER_ROOT_ARCHITYPE, persistent=False, edges=[]
80
20
  )
21
+ SUPER_ROOT_ARCHITYPE.__jac__ = SUPER_ROOT_ANCHOR
22
+
23
+
24
+ class ExecutionContext:
25
+ """Execution Context."""
26
+
27
+ mem: Memory
28
+ reports: list[Any]
29
+ system_root: NodeAnchor
30
+ root: NodeAnchor
31
+ entry_node: NodeAnchor
32
+
33
+ def init_anchor(
34
+ self,
35
+ anchor_id: str | None,
36
+ default: NodeAnchor,
37
+ ) -> NodeAnchor:
38
+ """Load initial anchors."""
39
+ if anchor_id:
40
+ if isinstance(anchor := self.mem.find_by_id(UUID(anchor_id)), NodeAnchor):
41
+ return anchor
42
+ raise ValueError(f"Invalid anchor id {anchor_id} !")
43
+ return default
44
+
45
+ def validate_access(self) -> bool:
46
+ """Validate access."""
47
+ return self.root.has_read_access(self.entry_node)
48
+
49
+ def set_entry_node(self, entry_node: str | None) -> None:
50
+ """Override entry."""
51
+ self.entry_node = self.init_anchor(entry_node, self.root)
52
+
53
+ def close(self) -> None:
54
+ """Close current ExecutionContext."""
55
+ self.mem.close()
56
+ EXECUTION_CONTEXT.set(None)
57
+
58
+ @staticmethod
59
+ def create(
60
+ session: Optional[str] = None,
61
+ root: Optional[str] = None,
62
+ auto_close: bool = True,
63
+ ) -> ExecutionContext:
64
+ """Create ExecutionContext."""
65
+ ctx = ExecutionContext()
66
+ ctx.mem = ShelfStorage(session)
67
+ ctx.reports = []
68
+
69
+ if not isinstance(
70
+ system_root := ctx.mem.find_by_id(SUPER_ROOT_UUID), NodeAnchor
71
+ ):
72
+ system_root = Root().__jac__
73
+ system_root.id = SUPER_ROOT_UUID
74
+ ctx.mem.set(system_root.id, system_root)
75
+
76
+ ctx.system_root = system_root
77
+
78
+ ctx.entry_node = ctx.root = ctx.init_anchor(root, ctx.system_root)
79
+
80
+ if auto_close and (old_ctx := EXECUTION_CONTEXT.get(None)):
81
+ old_ctx.close()
82
+
83
+ EXECUTION_CONTEXT.set(ctx)
84
+
85
+ return ctx
86
+
87
+ @staticmethod
88
+ def get() -> ExecutionContext:
89
+ """Get current ExecutionContext."""
90
+ if ctx := EXECUTION_CONTEXT.get(None):
91
+ return ctx
92
+ raise Exception("ExecutionContext is not yet available!")
93
+
94
+ @staticmethod
95
+ def get_root() -> Root:
96
+ """Get current root."""
97
+ if ctx := EXECUTION_CONTEXT.get(None):
98
+ return cast(Root, ctx.root.architype)
99
+
100
+ return SUPER_ROOT_ARCHITYPE
81
101
 
82
102
 
83
103
  class JacTestResult(unittest.TextTestResult):
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import importlib
6
+ import importlib.util
6
7
  import os
7
8
  import sys
8
9
  import types
@@ -139,7 +140,7 @@ class ImportReturn:
139
140
  else module.__name__
140
141
  )
141
142
  if isinstance(self.importer, JacImporter):
142
- new_module = sys.modules.get(
143
+ new_module = self.importer.jac_machine.loaded_modules.get(
143
144
  package_name,
144
145
  self.importer.create_jac_py_module(
145
146
  self.importer.get_sys_mod_name(jac_file_path),
@@ -177,8 +178,8 @@ class Importer:
177
178
 
178
179
  def update_sys(self, module: types.ModuleType, spec: ImportPathSpec) -> None:
179
180
  """Update sys.modules with the newly imported module."""
180
- if spec.module_name not in sys.modules:
181
- sys.modules[spec.module_name] = module
181
+ if spec.module_name not in self.jac_machine.loaded_modules:
182
+ self.jac_machine.load_module(spec.module_name, module)
182
183
 
183
184
 
184
185
  class PythonImporter(Importer):
@@ -280,8 +281,8 @@ class JacImporter(Importer):
280
281
  module.__path__ = [full_mod_path]
281
282
  module.__file__ = None
282
283
 
283
- if module_name not in sys.modules:
284
- sys.modules[module_name] = module
284
+ if module_name not in self.jac_machine.loaded_modules:
285
+ self.jac_machine.load_module(module_name, module)
285
286
  return module
286
287
 
287
288
  def create_jac_py_module(
@@ -299,7 +300,7 @@ class JacImporter(Importer):
299
300
  parts = package_path.split(".")
300
301
  for i in range(len(parts)):
301
302
  package_name = ".".join(parts[: i + 1])
302
- if package_name not in sys.modules:
303
+ if package_name not in self.jac_machine.loaded_modules:
303
304
  full_mod_path = path.join(
304
305
  base_path, package_name.replace(".", path.sep)
305
306
  )
@@ -307,7 +308,7 @@ class JacImporter(Importer):
307
308
  module_name=package_name,
308
309
  full_mod_path=full_mod_path,
309
310
  )
310
- sys.modules[module_name] = module
311
+ self.jac_machine.load_module(module_name, module)
311
312
  return module
312
313
 
313
314
  def run_import(
@@ -322,7 +323,7 @@ class JacImporter(Importer):
322
323
  else:
323
324
  module_name = self.get_sys_mod_name(spec.full_target)
324
325
 
325
- module = sys.modules.get(module_name)
326
+ module = self.jac_machine.loaded_modules.get(module_name)
326
327
 
327
328
  if not module or module.__name__ == "__main__" or reload:
328
329
  if os.path.isdir(spec.full_target):
@@ -340,14 +341,14 @@ class JacImporter(Importer):
340
341
  caller_dir=spec.caller_dir,
341
342
  cachable=spec.cachable,
342
343
  )
343
- if not codeobj:
344
- raise ImportError(f"No bytecode found for {spec.full_target}")
345
- with sys_path_context(spec.caller_dir):
346
- try:
344
+ try:
345
+ if not codeobj:
346
+ raise ImportError(f"No bytecode found for {spec.full_target}")
347
+ with sys_path_context(spec.caller_dir):
347
348
  exec(codeobj, module.__dict__)
348
- except Exception as e:
349
- logger.error(dump_traceback(e))
350
- raise e
349
+ except Exception as e:
350
+ logger.error(dump_traceback(e))
351
+ raise e
351
352
  import_return = ImportReturn(module, unique_loaded_items, self)
352
353
  if spec.items:
353
354
  import_return.process_items(
@@ -1,25 +1,33 @@
1
1
  """Jac Machine module."""
2
2
 
3
+ import inspect
3
4
  import marshal
4
5
  import os
6
+ import sys
5
7
  import types
8
+ from contextvars import ContextVar
6
9
  from typing import Optional
7
10
 
8
11
  from jaclang.compiler.absyntree import Module
9
12
  from jaclang.compiler.compile import compile_jac
10
13
  from jaclang.compiler.constant import Constants as Con
14
+ from jaclang.runtimelib.architype import EdgeArchitype, NodeArchitype, WalkerArchitype
11
15
  from jaclang.utils.log import logging
12
16
 
17
+
13
18
  logger = logging.getLogger(__name__)
14
19
 
15
20
 
21
+ JACMACHINE_CONTEXT = ContextVar["JacMachine | None"]("JacMachine")
22
+
23
+
16
24
  class JacMachine:
17
25
  """JacMachine to handle the VM-related functionalities and loaded programs."""
18
26
 
19
27
  def __init__(self, base_path: str = "") -> None:
20
28
  """Initialize the JacMachine object."""
21
- # self.loaded_modules: Dict[str, types.ModuleType] = {}
22
- if not len(base_path):
29
+ self.loaded_modules: dict[str, types.ModuleType] = {}
30
+ if not base_path:
23
31
  base_path = os.getcwd()
24
32
  self.base_path = base_path
25
33
  self.base_path_dir = (
@@ -29,6 +37,8 @@ class JacMachine:
29
37
  )
30
38
  self.jac_program: Optional[JacProgram] = None
31
39
 
40
+ JACMACHINE_CONTEXT.set(self)
41
+
32
42
  def attach_program(self, jac_program: "JacProgram") -> None:
33
43
  """Attach a JacProgram to the machine."""
34
44
  self.jac_program = jac_program
@@ -53,6 +63,60 @@ class JacMachine:
53
63
  )
54
64
  return None
55
65
 
66
+ def load_module(self, module_name: str, module: types.ModuleType) -> None:
67
+ """Load a module into the machine."""
68
+ self.loaded_modules[module_name] = module
69
+ sys.modules[module_name] = module
70
+
71
+ def list_modules(self) -> list[str]:
72
+ """List all loaded modules."""
73
+ return list(self.loaded_modules.keys())
74
+
75
+ def list_walkers(self, module_name: str) -> list[str]:
76
+ """List all walkers in a specific module."""
77
+ module = self.loaded_modules.get(module_name)
78
+ if module:
79
+ walkers = []
80
+ for name, obj in inspect.getmembers(module):
81
+ if isinstance(obj, type) and issubclass(obj, WalkerArchitype):
82
+ walkers.append(name)
83
+ return walkers
84
+ return []
85
+
86
+ def list_nodes(self, module_name: str) -> list[str]:
87
+ """List all nodes in a specific module."""
88
+ module = self.loaded_modules.get(module_name)
89
+ if module:
90
+ nodes = []
91
+ for name, obj in inspect.getmembers(module):
92
+ if isinstance(obj, type) and issubclass(obj, NodeArchitype):
93
+ nodes.append(name)
94
+ return nodes
95
+ return []
96
+
97
+ def list_edges(self, module_name: str) -> list[str]:
98
+ """List all edges in a specific module."""
99
+ module = self.loaded_modules.get(module_name)
100
+ if module:
101
+ nodes = []
102
+ for name, obj in inspect.getmembers(module):
103
+ if isinstance(obj, type) and issubclass(obj, EdgeArchitype):
104
+ nodes.append(name)
105
+ return nodes
106
+ return []
107
+
108
+ @staticmethod
109
+ def get(base_path: str = "") -> "JacMachine":
110
+ """Get current jac machine."""
111
+ if (jac_machine := JACMACHINE_CONTEXT.get(None)) is None:
112
+ jac_machine = JacMachine(base_path)
113
+ return jac_machine
114
+
115
+ @staticmethod
116
+ def detach() -> None:
117
+ """Detach current jac machine."""
118
+ JACMACHINE_CONTEXT.set(None)
119
+
56
120
 
57
121
  class JacProgram:
58
122
  """Class to hold the mod_bundle and bytecode for Jac modules."""
@@ -2,98 +2,157 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import shelve
5
+ from dataclasses import dataclass, field
6
+ from pickle import dumps
7
+ from shelve import Shelf, open
8
+ from typing import Callable, Generator, Generic, Iterable, TypeVar
6
9
  from uuid import UUID
7
10
 
8
- from .architype import Architype
11
+ from .architype import Anchor, NodeAnchor, Root, TANCH
9
12
 
13
+ ID = TypeVar("ID")
10
14
 
11
- class Memory:
12
- """Memory module interface."""
13
15
 
14
- mem: dict[UUID, Architype] = {}
15
- save_obj_list: dict[UUID, Architype] = {}
16
+ @dataclass
17
+ class Memory(Generic[ID, TANCH]):
18
+ """Generic Memory Handler."""
16
19
 
17
- def __init__(self) -> None:
18
- """init."""
19
- pass
20
+ __mem__: dict[ID, TANCH] = field(default_factory=dict)
21
+ __gc__: set[TANCH] = field(default_factory=set)
20
22
 
21
- def get_obj(self, obj_id: UUID) -> Architype | None:
22
- """Get object from memory."""
23
- return self.get_obj_from_store(obj_id)
24
-
25
- def get_obj_from_store(self, obj_id: UUID) -> Architype | None:
26
- """Get object from the underlying store."""
27
- ret = self.mem.get(obj_id)
28
- return ret
23
+ def close(self) -> None:
24
+ """Close memory handler."""
25
+ self.__mem__.clear()
26
+ self.__gc__.clear()
27
+
28
+ def find(
29
+ self,
30
+ ids: ID | Iterable[ID],
31
+ filter: Callable[[TANCH], TANCH] | None = None,
32
+ ) -> Generator[TANCH, None, None]:
33
+ """Find anchors from memory by ids with filter."""
34
+ if not isinstance(ids, Iterable):
35
+ ids = [ids]
36
+
37
+ return (
38
+ anchor
39
+ for id in ids
40
+ if (anchor := self.__mem__.get(id)) and (not filter or filter(anchor))
41
+ )
29
42
 
30
- def has_obj(self, obj_id: UUID) -> bool:
31
- """Check if the object exists."""
32
- return self.has_obj_in_store(obj_id)
43
+ def find_one(
44
+ self,
45
+ ids: ID | Iterable[ID],
46
+ filter: Callable[[TANCH], TANCH] | None = None,
47
+ ) -> TANCH | None:
48
+ """Find one anchor from memory by ids with filter."""
49
+ return next(self.find(ids, filter), None)
33
50
 
34
- def has_obj_in_store(self, obj_id: UUID) -> bool:
35
- """Check if the object exists in the underlying store."""
36
- return obj_id in self.mem
51
+ def find_by_id(self, id: ID) -> TANCH | None:
52
+ """Find one by id."""
53
+ return self.__mem__.get(id)
37
54
 
38
- def save_obj(self, item: Architype, persistent: bool) -> None:
39
- """Save object."""
40
- self.mem[item._jac_.id] = item
41
- if persistent:
42
- # TODO: check if it needs to be saved, i.e. dirty or not
43
- self.save_obj_list[item._jac_.id] = item
55
+ def set(self, id: ID, data: TANCH) -> None:
56
+ """Save anchor to memory."""
57
+ self.__mem__[id] = data
44
58
 
45
- def commit(self) -> None:
46
- """Commit changes to persistent storage, if applicable."""
47
- pass
59
+ def remove(self, ids: ID | Iterable[ID]) -> None:
60
+ """Remove anchor/s from memory."""
61
+ if not isinstance(ids, Iterable):
62
+ ids = [ids]
48
63
 
49
- def close(self) -> None:
50
- """Close any connection, if applicable."""
51
- self.mem.clear()
64
+ for id in ids:
65
+ if anchor := self.__mem__.pop(id, None):
66
+ self.__gc__.add(anchor)
52
67
 
53
68
 
54
- class ShelveStorage(Memory):
55
- """Shelve storage for jaclang runtime object."""
69
+ @dataclass
70
+ class ShelfStorage(Memory[UUID, Anchor]):
71
+ """Shelf Handler."""
56
72
 
57
- storage: shelve.Shelf | None = None
73
+ __shelf__: Shelf[Anchor] | None = None
58
74
 
59
- def __init__(self, session: str = "") -> None:
60
- """Init shelve storage."""
75
+ def __init__(self, session: str | None = None) -> None:
76
+ """Initialize memory handler."""
61
77
  super().__init__()
62
- if session:
63
- self.connect(session)
64
-
65
- def get_obj_from_store(self, obj_id: UUID) -> Architype | None:
66
- """Get object from the underlying store."""
67
- obj = super().get_obj_from_store(obj_id)
68
- if obj is None and self.storage:
69
- obj = self.storage.get(str(obj_id))
70
- if obj is not None:
71
- self.mem[obj_id] = obj
72
-
73
- return obj
74
-
75
- def has_obj_in_store(self, obj_id: UUID | str) -> bool:
76
- """Check if the object exists in the underlying store."""
77
- return obj_id in self.mem or (
78
- str(obj_id) in self.storage if self.storage else False
79
- )
80
-
81
- def commit(self) -> None:
82
- """Commit changes to persistent storage."""
83
- if self.storage is not None:
84
- for obj_id, obj in self.save_obj_list.items():
85
- self.storage[str(obj_id)] = obj
86
- self.save_obj_list.clear()
87
-
88
- def connect(self, session: str) -> None:
89
- """Connect to storage."""
90
- self.session = session
91
- self.storage = shelve.open(session)
78
+ self.__shelf__ = open(session) if session else None # noqa: SIM115
92
79
 
93
80
  def close(self) -> None:
94
- """Close the storage."""
81
+ """Close memory handler."""
82
+ if isinstance(self.__shelf__, Shelf):
83
+ from jaclang.plugin.feature import JacFeature as Jac
84
+
85
+ root = Jac.get_root().__jac__
86
+
87
+ for anchor in self.__gc__:
88
+ self.__shelf__.pop(str(anchor.id), None)
89
+ self.__mem__.pop(anchor.id, None)
90
+
91
+ for d in self.__mem__.values():
92
+ if d.persistent and d.hash != hash(dumps(d)):
93
+ _id = str(d.id)
94
+ if p_d := self.__shelf__.get(_id):
95
+ if (
96
+ isinstance(p_d, NodeAnchor)
97
+ and isinstance(d, NodeAnchor)
98
+ and p_d.edges != d.edges
99
+ and root.has_connect_access(d)
100
+ ):
101
+ if not d.edges:
102
+ self.__shelf__.pop(_id, None)
103
+ continue
104
+ p_d.edges = d.edges
105
+
106
+ if root.has_write_access(d):
107
+ if hash(dumps(p_d.access)) != hash(dumps(d.access)):
108
+ p_d.access = d.access
109
+ if hash(dumps(d.architype)) != hash(dumps(d.architype)):
110
+ p_d.architype = d.architype
111
+
112
+ self.__shelf__[_id] = p_d
113
+ elif not (
114
+ isinstance(d, NodeAnchor)
115
+ and not isinstance(d.architype, Root)
116
+ and not d.edges
117
+ ):
118
+ self.__shelf__[_id] = d
119
+
120
+ self.__shelf__.close()
95
121
  super().close()
96
- self.commit()
97
- if self.storage:
98
- self.storage.close()
99
- self.storage = None
122
+
123
+ def find(
124
+ self,
125
+ ids: UUID | Iterable[UUID],
126
+ filter: Callable[[Anchor], Anchor] | None = None,
127
+ ) -> Generator[Anchor, None, None]:
128
+ """Find anchors from datasource by ids with filter."""
129
+ if not isinstance(ids, Iterable):
130
+ ids = [ids]
131
+
132
+ if isinstance(self.__shelf__, Shelf):
133
+ for id in ids:
134
+ anchor = self.__mem__.get(id)
135
+
136
+ if (
137
+ not anchor
138
+ and id not in self.__gc__
139
+ and (_anchor := self.__shelf__.get(str(id)))
140
+ ):
141
+ self.__mem__[id] = anchor = _anchor
142
+ if anchor and (not filter or filter(anchor)):
143
+ yield anchor
144
+ else:
145
+ yield from super().find(ids, filter)
146
+
147
+ def find_by_id(self, id: UUID) -> Anchor | None:
148
+ """Find one by id."""
149
+ data = super().find_by_id(id)
150
+
151
+ if (
152
+ not data
153
+ and isinstance(self.__shelf__, Shelf)
154
+ and (data := self.__shelf__.get(str(id)))
155
+ ):
156
+ self.__mem__[id] = data
157
+
158
+ return data
@@ -37,12 +37,16 @@ def collect_node_connections(
37
37
  visited_nodes.add(current_node)
38
38
  edges = current_node.edges
39
39
  for edge_ in edges:
40
- target = edge_._jac_.target
40
+ target = edge_.target
41
41
  if target:
42
42
  connections.add(
43
- (current_node.obj, target._jac_.obj, edge_.__class__.__name__)
43
+ (
44
+ current_node.architype,
45
+ target.architype,
46
+ edge_.__class__.__name__,
47
+ )
44
48
  )
45
- collect_node_connections(target._jac_, visited_nodes, connections)
49
+ collect_node_connections(target, visited_nodes, connections)
46
50
 
47
51
 
48
52
  def traverse_graph(
@@ -61,17 +65,20 @@ def traverse_graph(
61
65
  edge_limit: int,
62
66
  ) -> None:
63
67
  """Traverse the graph using Breadth-First Search (BFS) or Depth-First Search (DFS)."""
64
- for edge in node._jac_.edges:
65
- is_self_loop = id(edge._jac_.source) == id(edge._jac_.target)
66
- is_in_edge = edge._jac_.target == node
67
- if (traverse and is_in_edge) or edge._jac_.obj.__class__.__name__ in edge_type:
68
+ for edge in node.__jac__.edges:
69
+ is_self_loop = id(edge.source) == id(edge.target)
70
+ is_in_edge = edge.target == node.__jac__
71
+ if (traverse and is_in_edge) or edge.architype.__class__.__name__ in edge_type:
68
72
  continue
69
73
  if is_self_loop:
70
74
  continue # lets skip self loop for a while, need to handle it later
71
- else:
72
- other_nd = edge._jac_.target if not is_in_edge else edge._jac_.source
75
+ elif (other_nda := edge.target if not is_in_edge else edge.source) and (
76
+ other_nd := other_nda.architype
77
+ ):
73
78
  new_con = (
74
- (node, other_nd, edge) if not is_in_edge else (other_nd, node, edge)
79
+ (node, other_nd, edge.architype)
80
+ if not is_in_edge
81
+ else (other_nd, node, edge.architype)
75
82
  )
76
83
  if node in node_depths and node_depths[node] is not None:
77
84
  if other_nd in node_depths: