jaclang 0.7.13__py3-none-any.whl → 0.7.16__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 (96) hide show
  1. jaclang/cli/cli.py +15 -10
  2. jaclang/cli/cmdreg.py +9 -12
  3. jaclang/compiler/__init__.py +19 -53
  4. jaclang/compiler/absyntree.py +95 -17
  5. jaclang/compiler/jac.lark +4 -3
  6. jaclang/compiler/parser.py +35 -23
  7. jaclang/compiler/passes/ir_pass.py +4 -13
  8. jaclang/compiler/passes/main/access_modifier_pass.py +1 -1
  9. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +4 -5
  10. jaclang/compiler/passes/main/import_pass.py +19 -23
  11. jaclang/compiler/passes/main/pyast_gen_pass.py +308 -567
  12. jaclang/compiler/passes/main/pyast_load_pass.py +33 -6
  13. jaclang/compiler/passes/main/registry_pass.py +37 -3
  14. jaclang/compiler/passes/main/sym_tab_build_pass.py +1 -1
  15. jaclang/compiler/passes/main/tests/__init__.py +1 -1
  16. jaclang/compiler/passes/main/tests/test_import_pass.py +5 -1
  17. jaclang/compiler/passes/main/type_check_pass.py +7 -0
  18. jaclang/compiler/passes/tool/fuse_comments_pass.py +14 -2
  19. jaclang/compiler/passes/tool/jac_formatter_pass.py +144 -94
  20. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +0 -1
  21. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/architype_test.jac +13 -0
  22. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comment_alignment.jac +11 -0
  23. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comments.jac +13 -0
  24. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/decorator_stack.jac +37 -0
  25. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/esc_keywords.jac +5 -0
  26. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/long_names.jac +19 -0
  27. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +6 -0
  28. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +11 -0
  29. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +33 -39
  30. jaclang/compiler/passes/transform.py +4 -0
  31. jaclang/compiler/semtable.py +31 -7
  32. jaclang/compiler/tests/test_importer.py +12 -5
  33. jaclang/langserve/engine.py +82 -143
  34. jaclang/langserve/sem_manager.py +379 -0
  35. jaclang/langserve/server.py +8 -10
  36. jaclang/langserve/tests/fixtures/base_module_structure.jac +27 -6
  37. jaclang/langserve/tests/fixtures/circle.jac +3 -3
  38. jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
  39. jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
  40. jaclang/langserve/tests/fixtures/import_include_statements.jac +1 -1
  41. jaclang/langserve/tests/test_sem_tokens.py +277 -0
  42. jaclang/langserve/tests/test_server.py +96 -16
  43. jaclang/langserve/utils.py +163 -96
  44. jaclang/plugin/builtin.py +1 -1
  45. jaclang/plugin/default.py +214 -24
  46. jaclang/plugin/feature.py +59 -11
  47. jaclang/plugin/spec.py +58 -6
  48. jaclang/{core → runtimelib}/architype.py +1 -1
  49. jaclang/{core → runtimelib}/context.py +8 -1
  50. jaclang/runtimelib/importer.py +361 -0
  51. jaclang/runtimelib/machine.py +94 -0
  52. jaclang/{core → runtimelib}/utils.py +13 -5
  53. jaclang/settings.py +4 -1
  54. jaclang/tests/fixtures/abc.jac +3 -3
  55. jaclang/tests/fixtures/blankwithentry.jac +3 -0
  56. jaclang/tests/fixtures/byllmissue.jac +1 -5
  57. jaclang/tests/fixtures/chandra_bugs2.jac +11 -10
  58. jaclang/tests/fixtures/cls_method.jac +41 -0
  59. jaclang/tests/fixtures/dblhello.jac +6 -0
  60. jaclang/tests/fixtures/deep/one_lev.jac +3 -3
  61. jaclang/tests/fixtures/deep/one_lev_dup.jac +2 -3
  62. jaclang/tests/fixtures/deep_import_mods.jac +13 -0
  63. jaclang/tests/fixtures/err.impl.jac +3 -0
  64. jaclang/tests/fixtures/err.jac +4 -2
  65. jaclang/tests/fixtures/err.test.jac +3 -0
  66. jaclang/tests/fixtures/err_runtime.jac +15 -0
  67. jaclang/tests/fixtures/game1.jac +1 -1
  68. jaclang/tests/fixtures/hello.jac +4 -0
  69. jaclang/tests/fixtures/impl_grab.impl.jac +2 -1
  70. jaclang/tests/fixtures/impl_grab.jac +4 -1
  71. jaclang/tests/fixtures/jp_importer_auto.jac +14 -0
  72. jaclang/tests/fixtures/maxfail_run_test.jac +4 -4
  73. jaclang/tests/fixtures/needs_import.jac +2 -2
  74. jaclang/tests/fixtures/pyfunc_2.py +3 -0
  75. jaclang/tests/fixtures/registry.jac +9 -0
  76. jaclang/tests/fixtures/run_test.jac +4 -4
  77. jaclang/tests/fixtures/semstr.jac +1 -4
  78. jaclang/tests/fixtures/simple_archs.jac +1 -1
  79. jaclang/tests/test_cli.py +65 -2
  80. jaclang/tests/test_language.py +83 -7
  81. jaclang/tests/test_man_code.py +17 -0
  82. jaclang/tests/test_reference.py +6 -0
  83. jaclang/utils/helpers.py +45 -21
  84. jaclang/utils/test.py +9 -0
  85. jaclang/utils/treeprinter.py +0 -4
  86. {jaclang-0.7.13.dist-info → jaclang-0.7.16.dist-info}/METADATA +3 -2
  87. {jaclang-0.7.13.dist-info → jaclang-0.7.16.dist-info}/RECORD +93 -77
  88. jaclang/core/importer.py +0 -344
  89. jaclang/tests/fixtures/aott_raise.jac +0 -25
  90. jaclang/tests/fixtures/package_import.jac +0 -6
  91. /jaclang/{core → runtimelib}/__init__.py +0 -0
  92. /jaclang/{core → runtimelib}/constructs.py +0 -0
  93. /jaclang/{core → runtimelib}/memory.py +0 -0
  94. /jaclang/{core → runtimelib}/test.py +0 -0
  95. {jaclang-0.7.13.dist-info → jaclang-0.7.16.dist-info}/WHEEL +0 -0
  96. {jaclang-0.7.13.dist-info → jaclang-0.7.16.dist-info}/entry_points.txt +0 -0
jaclang/plugin/feature.py CHANGED
@@ -2,11 +2,15 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import ast as ast3
5
6
  import types
6
- from typing import Any, Callable, Optional, Type, TypeAlias, Union
7
+ from typing import Any, Callable, Mapping, Optional, Sequence, Type, TypeAlias, Union
7
8
 
8
- from jaclang.compiler.absyntree import Module
9
- from jaclang.core.constructs import (
9
+ import jaclang.compiler.absyntree as ast
10
+ from jaclang.compiler.passes.main.pyast_gen_pass import PyastGenPass
11
+ from jaclang.plugin.default import ExecutionContext
12
+ from jaclang.plugin.spec import JacBuiltin, JacCmdSpec, JacFeatureSpec, P, T
13
+ from jaclang.runtimelib.constructs import (
10
14
  Architype,
11
15
  EdgeArchitype,
12
16
  Memory,
@@ -14,9 +18,6 @@ from jaclang.core.constructs import (
14
18
  Root,
15
19
  WalkerArchitype,
16
20
  )
17
- from jaclang.plugin.default import ExecutionContext
18
- from jaclang.plugin.spec import JacBuiltin, JacCmdSpec, JacFeatureSpec, T
19
-
20
21
 
21
22
  import pluggy
22
23
 
@@ -29,10 +30,11 @@ pm.add_hookspecs(JacBuiltin)
29
30
  class JacFeature:
30
31
  """Jac Feature."""
31
32
 
32
- import abc
33
- from jaclang.compiler.constant import EdgeDir
34
- from jaclang.core.constructs import DSFunc
33
+ from jaclang.compiler.constant import EdgeDir as EdgeDirType
34
+ from jaclang.runtimelib.constructs import DSFunc as DSFuncType
35
35
 
36
+ EdgeDir: TypeAlias = EdgeDirType
37
+ DSFunc: TypeAlias = DSFuncType
36
38
  RootType: TypeAlias = Root
37
39
  Obj: TypeAlias = Architype
38
40
  Node: TypeAlias = NodeArchitype
@@ -94,6 +96,13 @@ class JacFeature:
94
96
  """Create a walker architype."""
95
97
  return pm.hook.make_walker(on_entry=on_entry, on_exit=on_exit)
96
98
 
99
+ @staticmethod
100
+ def impl_patch_filename(
101
+ file_loc: str,
102
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
103
+ """Update impl file location."""
104
+ return pm.hook.impl_patch_filename(file_loc=file_loc)
105
+
97
106
  @staticmethod
98
107
  def jac_import(
99
108
  target: str,
@@ -102,9 +111,9 @@ class JacFeature:
102
111
  cachable: bool = True,
103
112
  mdl_alias: Optional[str] = None,
104
113
  override_name: Optional[str] = None,
105
- mod_bundle: Optional[Module | str] = None,
106
114
  lng: Optional[str] = "jac",
107
115
  items: Optional[dict[str, Union[str, Optional[str]]]] = None,
116
+ reload_module: Optional[bool] = False,
108
117
  ) -> tuple[types.ModuleType, ...]:
109
118
  """Core Import Process."""
110
119
  return pm.hook.jac_import(
@@ -114,9 +123,9 @@ class JacFeature:
114
123
  cachable=cachable,
115
124
  mdl_alias=mdl_alias,
116
125
  override_name=override_name,
117
- mod_bundle=mod_bundle,
118
126
  lng=lng,
119
127
  items=items,
128
+ reload_module=reload_module,
120
129
  )
121
130
 
122
131
  @staticmethod
@@ -301,6 +310,8 @@ class JacFeature:
301
310
  inputs: list[tuple[str, str, str, Any]],
302
311
  outputs: tuple,
303
312
  action: str,
313
+ _globals: dict,
314
+ _locals: Mapping,
304
315
  ) -> Any: # noqa: ANN401
305
316
  """Jac's with_llm feature."""
306
317
  return pm.hook.with_llm(
@@ -313,8 +324,45 @@ class JacFeature:
313
324
  inputs=inputs,
314
325
  outputs=outputs,
315
326
  action=action,
327
+ _globals=_globals,
328
+ _locals=_locals,
316
329
  )
317
330
 
331
+ @staticmethod
332
+ def gen_llm_body(_pass: PyastGenPass, node: ast.Ability) -> list[ast3.AST]:
333
+ """Generate the by LLM body."""
334
+ return pm.hook.gen_llm_body(_pass=_pass, node=node)
335
+
336
+ @staticmethod
337
+ def by_llm_call(
338
+ _pass: PyastGenPass,
339
+ model: ast3.AST,
340
+ model_params: dict[str, ast.Expr],
341
+ scope: ast3.AST,
342
+ inputs: Sequence[Optional[ast3.AST]],
343
+ outputs: Sequence[Optional[ast3.AST]] | ast3.Call,
344
+ action: Optional[ast3.AST],
345
+ include_info: list[tuple[str, ast3.AST]],
346
+ exclude_info: list[tuple[str, ast3.AST]],
347
+ ) -> ast3.Call:
348
+ """Return the LLM Call, e.g. _Jac.with_llm()."""
349
+ return pm.hook.by_llm_call(
350
+ _pass=_pass,
351
+ model=model,
352
+ model_params=model_params,
353
+ scope=scope,
354
+ inputs=inputs,
355
+ outputs=outputs,
356
+ action=action,
357
+ include_info=include_info,
358
+ exclude_info=exclude_info,
359
+ )
360
+
361
+ @staticmethod
362
+ def get_by_llm_call_args(_pass: PyastGenPass, node: ast.FuncCall) -> dict:
363
+ """Get the by LLM call args."""
364
+ return pm.hook.get_by_llm_call_args(_pass=_pass, node=node)
365
+
318
366
 
319
367
  class JacCmd:
320
368
  """Jac CLI command."""
jaclang/plugin/spec.py CHANGED
@@ -2,13 +2,26 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import ast as ast3
5
6
  import types
6
- from typing import Any, Callable, Optional, TYPE_CHECKING, Type, TypeVar, Union
7
-
8
- from jaclang.compiler.absyntree import Module
7
+ from typing import (
8
+ Any,
9
+ Callable,
10
+ Mapping,
11
+ Optional,
12
+ ParamSpec,
13
+ Sequence,
14
+ TYPE_CHECKING,
15
+ Type,
16
+ TypeVar,
17
+ Union,
18
+ )
19
+
20
+ import jaclang.compiler.absyntree as ast
21
+ from jaclang.compiler.passes.main.pyast_gen_pass import PyastGenPass
9
22
 
10
23
  if TYPE_CHECKING:
11
- from jaclang.core.constructs import EdgeArchitype, NodeArchitype
24
+ from jaclang.runtimelib.constructs import EdgeArchitype, NodeArchitype
12
25
  from jaclang.plugin.default import (
13
26
  Architype,
14
27
  EdgeDir,
@@ -17,13 +30,14 @@ if TYPE_CHECKING:
17
30
  Root,
18
31
  DSFunc,
19
32
  )
20
- from jaclang.core.memory import Memory
33
+ from jaclang.runtimelib.memory import Memory
21
34
 
22
35
  import pluggy
23
36
 
24
37
  hookspec = pluggy.HookspecMarker("jac")
25
38
 
26
39
  T = TypeVar("T")
40
+ P = ParamSpec("P")
27
41
 
28
42
 
29
43
  class JacFeatureSpec:
@@ -90,6 +104,14 @@ class JacFeatureSpec:
90
104
  """Create a walker architype."""
91
105
  raise NotImplementedError
92
106
 
107
+ @staticmethod
108
+ @hookspec(firstresult=True)
109
+ def impl_patch_filename(
110
+ file_loc: str,
111
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
112
+ """Update impl file location."""
113
+ raise NotImplementedError
114
+
93
115
  @staticmethod
94
116
  @hookspec(firstresult=True)
95
117
  def jac_import(
@@ -99,9 +121,9 @@ class JacFeatureSpec:
99
121
  cachable: bool,
100
122
  mdl_alias: Optional[str],
101
123
  override_name: Optional[str],
102
- mod_bundle: Optional[Module | str],
103
124
  lng: Optional[str],
104
125
  items: Optional[dict[str, Union[str, Optional[str]]]],
126
+ reload_module: Optional[bool],
105
127
  ) -> tuple[types.ModuleType, ...]:
106
128
  """Core Import Process."""
107
129
  raise NotImplementedError
@@ -283,10 +305,40 @@ class JacFeatureSpec:
283
305
  inputs: list[tuple[str, str, str, Any]],
284
306
  outputs: tuple,
285
307
  action: str,
308
+ _globals: dict,
309
+ _locals: Mapping,
286
310
  ) -> Any: # noqa: ANN401
287
311
  """Jac's with_llm stmt feature."""
288
312
  raise NotImplementedError
289
313
 
314
+ @staticmethod
315
+ @hookspec(firstresult=True)
316
+ def gen_llm_body(_pass: PyastGenPass, node: ast.Ability) -> list[ast3.AST]:
317
+ """Generate the by LLM body."""
318
+ raise NotImplementedError
319
+
320
+ @staticmethod
321
+ @hookspec(firstresult=True)
322
+ def by_llm_call(
323
+ _pass: PyastGenPass,
324
+ model: ast3.AST,
325
+ model_params: dict[str, ast.Expr],
326
+ scope: ast3.AST,
327
+ inputs: Sequence[Optional[ast3.AST]],
328
+ outputs: Sequence[Optional[ast3.AST]] | ast3.Call,
329
+ action: Optional[ast3.AST],
330
+ include_info: list[tuple[str, ast3.AST]],
331
+ exclude_info: list[tuple[str, ast3.AST]],
332
+ ) -> ast3.Call:
333
+ """Return the LLM Call, e.g. _Jac.with_llm()."""
334
+ raise NotImplementedError
335
+
336
+ @staticmethod
337
+ @hookspec(firstresult=True)
338
+ def get_by_llm_call_args(_pass: PyastGenPass, node: ast.FuncCall) -> dict:
339
+ """Get the by LLM call args."""
340
+ raise NotImplementedError
341
+
290
342
 
291
343
  class JacBuiltin:
292
344
  """Jac Builtins."""
@@ -8,7 +8,7 @@ from typing import Any, Callable, Optional
8
8
  from uuid import UUID, uuid4
9
9
 
10
10
  from jaclang.compiler.constant import EdgeDir
11
- from jaclang.core.utils import collect_node_connections
11
+ from jaclang.runtimelib.utils import collect_node_connections
12
12
 
13
13
 
14
14
  @dataclass(eq=False)
@@ -8,6 +8,7 @@ from typing import Callable, Optional
8
8
  from uuid import UUID
9
9
 
10
10
  from .architype import Architype, Root
11
+ from .machine import JacMachine, JacProgram
11
12
  from .memory import Memory, ShelveStorage
12
13
 
13
14
 
@@ -22,13 +23,19 @@ class ExecutionContext:
22
23
  super().__init__()
23
24
  self.mem = ShelveStorage()
24
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)
25
29
 
26
- def init_memory(self, session: str = "") -> None:
30
+ def init_memory(self, base_path: str = "", session: str = "") -> None:
27
31
  """Initialize memory."""
28
32
  if session:
29
33
  self.mem = ShelveStorage(session)
30
34
  else:
31
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)
32
39
 
33
40
  def get_root(self) -> Root:
34
41
  """Get the root object."""
@@ -0,0 +1,361 @@
1
+ """Special Imports for Jac Code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import os
7
+ import sys
8
+ import types
9
+ from os import getcwd, path
10
+ from typing import Optional, Union
11
+
12
+ from jaclang.runtimelib.machine import JacMachine
13
+ from jaclang.runtimelib.utils import sys_path_context
14
+ from jaclang.utils.helpers import dump_traceback
15
+ from jaclang.utils.log import logging
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class ImportPathSpec:
21
+ """Import Specification."""
22
+
23
+ def __init__(
24
+ self,
25
+ target: str,
26
+ base_path: str,
27
+ absorb: bool,
28
+ cachable: bool,
29
+ mdl_alias: Optional[str],
30
+ override_name: Optional[str],
31
+ lng: Optional[str],
32
+ items: Optional[dict[str, Union[str, Optional[str]]]],
33
+ ) -> None:
34
+ """Initialize the ImportPathSpec object."""
35
+ self.target = target
36
+ self.base_path = base_path
37
+ self.absorb = absorb
38
+ self.cachable = cachable
39
+ self.mdl_alias = mdl_alias
40
+ self.override_name = override_name
41
+ self.language = lng
42
+ self.items = items
43
+ self.dir_path, self.file_name = path.split(path.join(*(target.split("."))))
44
+ self.module_name = path.splitext(self.file_name)[0]
45
+ self.package_path = self.dir_path.replace(path.sep, ".")
46
+ self.caller_dir = self.get_caller_dir()
47
+ self.full_target = path.abspath(path.join(self.caller_dir, self.file_name))
48
+
49
+ def get_caller_dir(self) -> str:
50
+ """Get the directory of the caller."""
51
+ caller_dir = (
52
+ self.base_path
53
+ if path.isdir(self.base_path)
54
+ else path.dirname(self.base_path)
55
+ )
56
+ caller_dir = caller_dir if caller_dir else getcwd()
57
+ chomp_target = self.target
58
+ if chomp_target.startswith("."):
59
+ chomp_target = chomp_target[1:]
60
+ while chomp_target.startswith("."):
61
+ caller_dir = path.dirname(caller_dir)
62
+ chomp_target = chomp_target[1:]
63
+ return path.join(caller_dir, self.dir_path)
64
+
65
+
66
+ class ImportReturn:
67
+ """Import Return Object."""
68
+
69
+ def __init__(
70
+ self,
71
+ ret_mod: types.ModuleType,
72
+ ret_items: list[types.ModuleType],
73
+ importer: Importer,
74
+ ) -> None:
75
+ """Initialize the ImportReturn object."""
76
+ self.ret_mod = ret_mod
77
+ self.ret_items = ret_items
78
+ self.importer = importer
79
+
80
+ def process_items(
81
+ self,
82
+ module: types.ModuleType,
83
+ items: dict[str, Union[str, Optional[str]]],
84
+ caller_dir: str,
85
+ lang: Optional[str],
86
+ cachable: bool = True,
87
+ ) -> None:
88
+ """Process items within a module by handling renaming and potentially loading missing attributes."""
89
+
90
+ def handle_item_loading(
91
+ item: types.ModuleType, alias: Union[str, Optional[str]]
92
+ ) -> None:
93
+ if item:
94
+ self.ret_items.append(item)
95
+ setattr(module, name, item)
96
+ if alias and alias != name and not isinstance(alias, bool):
97
+ setattr(module, alias, item)
98
+
99
+ for name, alias in items.items():
100
+ try:
101
+ item = getattr(module, name)
102
+ handle_item_loading(item, alias)
103
+ except AttributeError:
104
+ if lang == "jac":
105
+ jac_file_path = (
106
+ os.path.join(module.__path__[0], f"{name}.jac")
107
+ if hasattr(module, "__path__")
108
+ else module.__file__
109
+ )
110
+
111
+ if jac_file_path and os.path.isfile(jac_file_path):
112
+ item = self.load_jac_mod_as_item(
113
+ module=module,
114
+ name=name,
115
+ jac_file_path=jac_file_path,
116
+ cachable=cachable,
117
+ caller_dir=caller_dir,
118
+ )
119
+ handle_item_loading(item, alias)
120
+ else:
121
+ if hasattr(module, "__path__"):
122
+ full_module_name = f"{module.__name__}.{name}"
123
+ item = importlib.import_module(full_module_name)
124
+ handle_item_loading(item, alias)
125
+
126
+ def load_jac_mod_as_item(
127
+ self,
128
+ module: types.ModuleType,
129
+ name: str,
130
+ jac_file_path: str,
131
+ cachable: bool,
132
+ caller_dir: str,
133
+ ) -> Optional[types.ModuleType]:
134
+ """Load a single .jac file into the specified module component."""
135
+ try:
136
+ package_name = (
137
+ f"{module.__name__}.{name}"
138
+ if hasattr(module, "__path__")
139
+ else module.__name__
140
+ )
141
+ if isinstance(self.importer, JacImporter):
142
+ new_module = sys.modules.get(
143
+ package_name,
144
+ self.importer.create_jac_py_module(
145
+ self.importer.get_sys_mod_name(jac_file_path),
146
+ module.__name__,
147
+ jac_file_path,
148
+ ),
149
+ )
150
+ codeobj = self.importer.jac_machine.get_bytecode(
151
+ name, jac_file_path, caller_dir=caller_dir, cachable=cachable
152
+ )
153
+ if not codeobj:
154
+ raise ImportError(f"No bytecode found for {jac_file_path}")
155
+
156
+ exec(codeobj, new_module.__dict__)
157
+ return getattr(new_module, name, new_module)
158
+ except ImportError as e:
159
+ logger.error(dump_traceback(e))
160
+ # logger.error(
161
+ # f"Failed to load {name} from {jac_file_path} in {module.__name__}: {str(e)}"
162
+ # )
163
+ return None
164
+
165
+
166
+ class Importer:
167
+ """Abstract base class for all importers."""
168
+
169
+ def __init__(self, jac_machine: JacMachine) -> None:
170
+ """Initialize the Importer object."""
171
+ self.jac_machine = jac_machine
172
+ self.result: Optional[ImportReturn] = None
173
+
174
+ def run_import(self, spec: ImportPathSpec) -> ImportReturn:
175
+ """Run the import process."""
176
+ raise NotImplementedError
177
+
178
+ def update_sys(self, module: types.ModuleType, spec: ImportPathSpec) -> None:
179
+ """Update sys.modules with the newly imported module."""
180
+ if spec.module_name not in sys.modules:
181
+ sys.modules[spec.module_name] = module
182
+
183
+
184
+ class PythonImporter(Importer):
185
+ """Importer for Python modules."""
186
+
187
+ def __init__(self, jac_machine: JacMachine) -> None:
188
+ """Initialize the PythonImporter object."""
189
+ self.jac_machine = jac_machine
190
+
191
+ def run_import(self, spec: ImportPathSpec) -> ImportReturn:
192
+ """Run the import process for Python modules."""
193
+ try:
194
+ loaded_items: list = []
195
+ if spec.target.startswith("."):
196
+ spec.target = spec.target.lstrip(".")
197
+ full_target = path.normpath(path.join(spec.caller_dir, spec.target))
198
+ imp_spec = importlib.util.spec_from_file_location(
199
+ spec.target, full_target + ".py"
200
+ )
201
+ if imp_spec and imp_spec.loader:
202
+ imported_module = importlib.util.module_from_spec(imp_spec)
203
+ sys.modules[imp_spec.name] = imported_module
204
+ imp_spec.loader.exec_module(imported_module)
205
+ else:
206
+ raise ImportError(
207
+ f"Cannot find module {spec.target} at {full_target}"
208
+ )
209
+ else:
210
+ imported_module = importlib.import_module(name=spec.target)
211
+
212
+ main_module = __import__("__main__")
213
+ if spec.absorb:
214
+ for name in dir(imported_module):
215
+ if not name.startswith("_"):
216
+ setattr(main_module, name, getattr(imported_module, name))
217
+
218
+ elif spec.items:
219
+ for name, alias in spec.items.items():
220
+ if isinstance(alias, bool):
221
+ alias = name
222
+ try:
223
+ item = getattr(imported_module, name)
224
+ if item not in loaded_items:
225
+ setattr(
226
+ main_module,
227
+ alias if isinstance(alias, str) else name,
228
+ item,
229
+ )
230
+ loaded_items.append(item)
231
+ except AttributeError as e:
232
+ if hasattr(imported_module, "__path__"):
233
+ item = importlib.import_module(f"{spec.target}.{name}")
234
+ if item not in loaded_items:
235
+ setattr(
236
+ main_module,
237
+ alias if isinstance(alias, str) else name,
238
+ item,
239
+ )
240
+ loaded_items.append(item)
241
+ else:
242
+ raise e
243
+
244
+ else:
245
+ setattr(
246
+ __import__("__main__"),
247
+ spec.mdl_alias if isinstance(spec.mdl_alias, str) else spec.target,
248
+ imported_module,
249
+ )
250
+ self.result = ImportReturn(imported_module, loaded_items, self)
251
+ return self.result
252
+
253
+ except ImportError as e:
254
+ raise e
255
+
256
+
257
+ class JacImporter(Importer):
258
+ """Importer for Jac modules."""
259
+
260
+ def __init__(self, jac_machine: JacMachine) -> None:
261
+ """Initialize the JacImporter object."""
262
+ self.jac_machine = jac_machine
263
+
264
+ def get_sys_mod_name(self, full_target: str) -> str:
265
+ """Generate the system module name based on full target path and package path."""
266
+ if full_target == self.jac_machine.base_path_dir:
267
+ return path.basename(self.jac_machine.base_path_dir)
268
+ relative_path = path.relpath(full_target, start=self.jac_machine.base_path_dir)
269
+ base_name = path.splitext(relative_path)[0]
270
+ sys_mod_name = base_name.replace(os.sep, ".").strip(".")
271
+ return sys_mod_name
272
+
273
+ def handle_directory(
274
+ self, module_name: str, full_mod_path: str
275
+ ) -> types.ModuleType:
276
+ """Import from a directory that potentially contains multiple Jac modules."""
277
+ module_name = self.get_sys_mod_name(full_mod_path)
278
+ module = types.ModuleType(module_name)
279
+ module.__name__ = module_name
280
+ module.__path__ = [full_mod_path]
281
+ module.__file__ = None
282
+
283
+ if module_name not in sys.modules:
284
+ sys.modules[module_name] = module
285
+ return module
286
+
287
+ def create_jac_py_module(
288
+ self,
289
+ module_name: str,
290
+ package_path: str,
291
+ full_target: str,
292
+ ) -> types.ModuleType:
293
+ """Create a module."""
294
+ module = types.ModuleType(module_name)
295
+ module.__file__ = full_target
296
+ module.__name__ = module_name
297
+ if package_path:
298
+ base_path = full_target.split(package_path.replace(".", path.sep))[0]
299
+ parts = package_path.split(".")
300
+ for i in range(len(parts)):
301
+ package_name = ".".join(parts[: i + 1])
302
+ if package_name not in sys.modules:
303
+ full_mod_path = path.join(
304
+ base_path, package_name.replace(".", path.sep)
305
+ )
306
+ self.handle_directory(
307
+ module_name=package_name,
308
+ full_mod_path=full_mod_path,
309
+ )
310
+ sys.modules[module_name] = module
311
+ return module
312
+
313
+ def run_import(
314
+ self, spec: ImportPathSpec, reload: Optional[bool] = False
315
+ ) -> ImportReturn:
316
+ """Run the import process for Jac modules."""
317
+ unique_loaded_items: list[types.ModuleType] = []
318
+ module = None
319
+ if os.path.isfile(spec.full_target + ".jac"):
320
+ module_name = self.get_sys_mod_name(spec.full_target + ".jac")
321
+ module_name = spec.override_name if spec.override_name else module_name
322
+ else:
323
+ module_name = self.get_sys_mod_name(spec.full_target)
324
+
325
+ module = sys.modules.get(module_name)
326
+
327
+ if not module or module.__name__ == "__main__" or reload:
328
+ if os.path.isdir(spec.full_target):
329
+ module = self.handle_directory(spec.module_name, spec.full_target)
330
+ else:
331
+ spec.full_target += ".jac" if spec.language == "jac" else ".py"
332
+ module = self.create_jac_py_module(
333
+ module_name,
334
+ spec.package_path,
335
+ spec.full_target,
336
+ )
337
+ codeobj = self.jac_machine.get_bytecode(
338
+ module_name,
339
+ spec.full_target,
340
+ caller_dir=spec.caller_dir,
341
+ cachable=spec.cachable,
342
+ )
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:
347
+ exec(codeobj, module.__dict__)
348
+ except Exception as e:
349
+ logger.error(dump_traceback(e))
350
+ raise e
351
+ import_return = ImportReturn(module, unique_loaded_items, self)
352
+ if spec.items:
353
+ import_return.process_items(
354
+ module=module,
355
+ items=spec.items,
356
+ caller_dir=spec.caller_dir,
357
+ cachable=spec.cachable,
358
+ lang=spec.language,
359
+ )
360
+ self.result = import_return
361
+ return self.result