jaclang 0.7.9__py3-none-any.whl → 0.7.13__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 (35) hide show
  1. jaclang/cli/cli.py +13 -3
  2. jaclang/compiler/absyntree.py +10 -2
  3. jaclang/compiler/parser.py +2 -1
  4. jaclang/compiler/passes/ir_pass.py +9 -0
  5. jaclang/compiler/passes/main/import_pass.py +15 -2
  6. jaclang/compiler/passes/main/pyast_gen_pass.py +238 -32
  7. jaclang/compiler/passes/main/pyast_load_pass.py +3 -1
  8. jaclang/compiler/passes/main/tests/test_import_pass.py +13 -0
  9. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +2 -2
  10. jaclang/compiler/passes/main/tests/test_sub_node_pass.py +1 -3
  11. jaclang/compiler/passes/main/type_check_pass.py +0 -17
  12. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +1 -2
  13. jaclang/compiler/tests/test_importer.py +1 -1
  14. jaclang/core/importer.py +233 -62
  15. jaclang/langserve/engine.py +182 -136
  16. jaclang/langserve/server.py +29 -7
  17. jaclang/langserve/tests/fixtures/base_module_structure.jac +28 -2
  18. jaclang/langserve/tests/fixtures/import_include_statements.jac +1 -1
  19. jaclang/langserve/tests/test_server.py +73 -66
  20. jaclang/langserve/utils.py +266 -0
  21. jaclang/plugin/default.py +7 -4
  22. jaclang/plugin/feature.py +3 -3
  23. jaclang/plugin/spec.py +3 -3
  24. jaclang/settings.py +1 -0
  25. jaclang/tests/fixtures/deep/one_lev.jac +6 -4
  26. jaclang/tests/fixtures/needs_import.jac +1 -1
  27. jaclang/tests/test_cli.py +7 -9
  28. jaclang/tests/test_language.py +9 -13
  29. jaclang/tests/test_man_code.py +8 -10
  30. jaclang/utils/helpers.py +3 -3
  31. jaclang/utils/test.py +8 -0
  32. {jaclang-0.7.9.dist-info → jaclang-0.7.13.dist-info}/METADATA +2 -2
  33. {jaclang-0.7.9.dist-info → jaclang-0.7.13.dist-info}/RECORD +35 -35
  34. {jaclang-0.7.9.dist-info → jaclang-0.7.13.dist-info}/WHEEL +0 -0
  35. {jaclang-0.7.9.dist-info → jaclang-0.7.13.dist-info}/entry_points.txt +0 -0
jaclang/core/importer.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import importlib
4
4
  import marshal
5
+ import os
5
6
  import sys
6
7
  import types
7
8
  from os import getcwd, path
@@ -13,6 +14,137 @@ from jaclang.compiler.constant import Constants as Con
13
14
  from jaclang.core.utils import sys_path_context
14
15
 
15
16
 
17
+ def handle_directory(
18
+ module_name: str, full_mod_path: str, mod_bundle: Optional[Module]
19
+ ) -> types.ModuleType:
20
+ """Import from a directory that potentially contains multiple Jac modules."""
21
+ module = types.ModuleType(module_name)
22
+ module.__name__ = module_name
23
+ module.__path__ = [full_mod_path]
24
+ module.__file__ = None
25
+ module.__dict__["__jac_mod_bundle__"] = mod_bundle
26
+ if module_name not in sys.modules:
27
+ sys.modules[module_name] = module
28
+ return module
29
+
30
+
31
+ def process_items(
32
+ module: types.ModuleType,
33
+ items: dict[str, Union[str, Optional[str]]],
34
+ caller_dir: str,
35
+ lang: Optional[str],
36
+ mod_bundle: Optional[Module] = None,
37
+ cachable: bool = True,
38
+ ) -> list:
39
+ """Process items within a module by handling renaming and potentially loading missing attributes."""
40
+ unique_loaded_items = []
41
+
42
+ def handle_item_loading(item: str, alias: Union[str, Optional[str]]) -> None:
43
+ if item:
44
+ unique_loaded_items.append(item)
45
+ setattr(module, name, item)
46
+ if alias and alias != name and not isinstance(alias, bool):
47
+ setattr(module, alias, item)
48
+
49
+ for name, alias in items.items():
50
+ try:
51
+ item = getattr(module, name)
52
+ handle_item_loading(item, alias)
53
+ except AttributeError:
54
+ if lang == "jac":
55
+ jac_file_path = (
56
+ os.path.join(module.__path__[0], f"{name}.jac")
57
+ if hasattr(module, "__path__")
58
+ else module.__file__
59
+ )
60
+ if jac_file_path and os.path.isfile(jac_file_path):
61
+ item = load_jac_file(
62
+ module=module,
63
+ name=name,
64
+ jac_file_path=jac_file_path,
65
+ mod_bundle=mod_bundle,
66
+ cachable=cachable,
67
+ caller_dir=caller_dir,
68
+ )
69
+ handle_item_loading(item, alias)
70
+ elif jac_file_path and os.path.isdir(jac_file_path[:-4]):
71
+ item = handle_directory(name, jac_file_path[:-4], mod_bundle)
72
+ handle_item_loading(item, alias)
73
+ else:
74
+ if hasattr(module, "__path__"):
75
+ full_module_name = f"{module.__name__}.{name}"
76
+ item = importlib.import_module(full_module_name)
77
+ handle_item_loading(item, alias)
78
+ return unique_loaded_items
79
+
80
+
81
+ def load_jac_file(
82
+ module: types.ModuleType,
83
+ name: str,
84
+ jac_file_path: str,
85
+ mod_bundle: Optional[Module],
86
+ cachable: bool,
87
+ caller_dir: str,
88
+ ) -> Optional[types.ModuleType]:
89
+ """Load a single .jac file into the specified module component."""
90
+ try:
91
+ package_name = (
92
+ f"{module.__name__}.{name}"
93
+ if hasattr(module, "__path__")
94
+ else module.__name__
95
+ )
96
+ new_module = sys.modules.get(
97
+ package_name,
98
+ create_jac_py_module(mod_bundle, name, module.__name__, jac_file_path),
99
+ )
100
+
101
+ codeobj = get_codeobj(
102
+ full_target=jac_file_path,
103
+ module_name=name,
104
+ mod_bundle=mod_bundle,
105
+ cachable=cachable,
106
+ caller_dir=caller_dir,
107
+ )
108
+ if not codeobj:
109
+ raise ImportError(f"No bytecode found for {jac_file_path}")
110
+
111
+ exec(codeobj, new_module.__dict__)
112
+ return getattr(new_module, name, new_module)
113
+ except ImportError as e:
114
+ print(
115
+ f"Failed to load {name} from {jac_file_path} in {module.__name__}: {str(e)}"
116
+ )
117
+ return None
118
+
119
+
120
+ def get_codeobj(
121
+ full_target: str,
122
+ module_name: str,
123
+ mod_bundle: Optional[Module],
124
+ cachable: bool,
125
+ caller_dir: str,
126
+ ) -> Optional[types.CodeType]:
127
+ """Execcutes the code for a given module."""
128
+ if mod_bundle:
129
+ codeobj = mod_bundle.mod_deps[full_target].gen.py_bytecode
130
+ return marshal.loads(codeobj) if isinstance(codeobj, bytes) else None
131
+ gen_dir = os.path.join(caller_dir, Con.JAC_GEN_DIR)
132
+ pyc_file_path = os.path.join(gen_dir, module_name + ".jbc")
133
+ if cachable and os.path.exists(pyc_file_path):
134
+ with open(pyc_file_path, "rb") as f:
135
+ return marshal.load(f)
136
+
137
+ result = compile_jac(full_target, cache_result=cachable)
138
+ if result.errors_had or not result.ir.gen.py_bytecode:
139
+ for e in result.errors_had:
140
+ print(e)
141
+ return None
142
+ if result.ir.gen.py_bytecode is not None:
143
+ return marshal.loads(result.ir.gen.py_bytecode)
144
+ else:
145
+ return None
146
+
147
+
16
148
  def jac_importer(
17
149
  target: str,
18
150
  base_path: str,
@@ -22,24 +154,23 @@ def jac_importer(
22
154
  override_name: Optional[str] = None,
23
155
  mod_bundle: Optional[Module | str] = None,
24
156
  lng: Optional[str] = "jac",
25
- items: Optional[dict[str, Union[str, bool]]] = None,
26
- ) -> Optional[types.ModuleType]:
157
+ items: Optional[dict[str, Union[str, Optional[str]]]] = None,
158
+ ) -> tuple[types.ModuleType, ...]:
27
159
  """Core Import Process."""
28
- dir_path, file_name = path.split(
29
- path.join(*(target.split("."))) + (".jac" if lng == "jac" else ".py")
30
- )
160
+ unique_loaded_items = []
161
+ dir_path, file_name = path.split(path.join(*(target.split("."))))
31
162
  module_name = path.splitext(file_name)[0]
32
163
  package_path = dir_path.replace(path.sep, ".")
33
-
34
164
  if (
35
165
  not override_name
36
166
  and package_path
37
167
  and f"{package_path}.{module_name}" in sys.modules
38
168
  ):
39
- return sys.modules[f"{package_path}.{module_name}"]
169
+ module = sys.modules[f"{package_path}.{module_name}"]
40
170
  elif not override_name and not package_path and module_name in sys.modules:
41
- return sys.modules[module_name]
42
-
171
+ module = sys.modules[module_name]
172
+ else:
173
+ module = None
43
174
  valid_mod_bundle = (
44
175
  sys.modules[mod_bundle].__jac_mod_bundle__
45
176
  if isinstance(mod_bundle, str)
@@ -50,47 +181,61 @@ def jac_importer(
50
181
 
51
182
  caller_dir = get_caller_dir(target, base_path, dir_path)
52
183
  full_target = path.normpath(path.join(caller_dir, file_name))
53
-
54
- if lng == "py":
55
- module = py_import(
56
- target=target, items=items, absorb=absorb, mdl_alias=mdl_alias
57
- )
58
- else:
59
- module_name = override_name if override_name else module_name
60
- module = create_jac_py_module(
61
- valid_mod_bundle, module_name, package_path, full_target
62
- )
63
- if valid_mod_bundle:
64
- codeobj = valid_mod_bundle.mod_deps[full_target].gen.py_bytecode
65
- codeobj = marshal.loads(codeobj) if isinstance(codeobj, bytes) else None
184
+ if not module:
185
+ if os.path.isdir(full_target):
186
+ module = handle_directory(module_name, full_target, valid_mod_bundle)
66
187
  else:
67
- gen_dir = path.join(caller_dir, Con.JAC_GEN_DIR)
68
- pyc_file_path = path.join(gen_dir, module_name + ".jbc")
69
- if (
70
- cachable
71
- and path.exists(pyc_file_path)
72
- and path.getmtime(pyc_file_path) > path.getmtime(full_target)
73
- ):
74
- with open(pyc_file_path, "rb") as f:
75
- codeobj = marshal.load(f)
76
- else:
77
- result = compile_jac(full_target, cache_result=cachable)
78
- if result.errors_had or not result.ir.gen.py_bytecode:
79
- # for e in result.errors_had:
80
- # print(e)
81
- return None
82
- else:
83
- codeobj = marshal.loads(result.ir.gen.py_bytecode)
84
- if not codeobj:
85
- raise ImportError(f"No bytecode found for {full_target}")
86
- with sys_path_context(caller_dir):
87
- exec(codeobj, module.__dict__)
188
+ full_target += ".jac" if lng == "jac" else ".py"
189
+ module_name = path.splitext(file_name)[0]
190
+ package_path = dir_path.replace(path.sep, ".")
88
191
 
89
- return module
192
+ if lng == "py":
193
+ module, *unique_loaded_items = py_import(
194
+ target=target,
195
+ items=items,
196
+ absorb=absorb,
197
+ mdl_alias=mdl_alias,
198
+ caller_dir=caller_dir,
199
+ )
200
+ else:
201
+ module_name = override_name if override_name else module_name
202
+ module = create_jac_py_module(
203
+ valid_mod_bundle,
204
+ module_name,
205
+ package_path,
206
+ full_target,
207
+ )
208
+ codeobj = get_codeobj(
209
+ full_target,
210
+ module_name,
211
+ valid_mod_bundle,
212
+ cachable,
213
+ caller_dir=caller_dir,
214
+ )
215
+ try:
216
+ if not codeobj:
217
+ raise ImportError(f"No bytecode found for {full_target}")
218
+ with sys_path_context(caller_dir):
219
+ exec(codeobj, module.__dict__)
220
+ except Exception as e:
221
+ raise ImportError(f"Error importing {full_target}: {str(e)}")
222
+ unique_loaded_items = (
223
+ process_items(
224
+ module=module,
225
+ items=items,
226
+ caller_dir=caller_dir,
227
+ mod_bundle=valid_mod_bundle,
228
+ cachable=cachable,
229
+ lang=lng,
230
+ )
231
+ if items
232
+ else []
233
+ )
234
+ return (module,) if absorb or not items else tuple(unique_loaded_items)
90
235
 
91
236
 
92
237
  def create_jac_py_module(
93
- mod_bundle: Optional[Module | str],
238
+ mod_bundle: Optional[Module],
94
239
  module_name: str,
95
240
  package_path: str,
96
241
  full_target: str,
@@ -102,10 +247,18 @@ def create_jac_py_module(
102
247
  module.__dict__["__jac_mod_bundle__"] = mod_bundle
103
248
  if package_path:
104
249
  parts = package_path.split(".")
250
+ base_path = full_target.split(package_path.replace(".", path.sep))[0]
105
251
  for i in range(len(parts)):
106
252
  package_name = ".".join(parts[: i + 1])
107
253
  if package_name not in sys.modules:
108
- sys.modules[package_name] = types.ModuleType(package_name)
254
+ full_mod_path = path.join(
255
+ base_path, package_name.replace(".", path.sep)
256
+ )
257
+ handle_directory(
258
+ module_name=package_name,
259
+ full_mod_path=full_mod_path,
260
+ mod_bundle=mod_bundle,
261
+ )
109
262
 
110
263
  setattr(sys.modules[package_path], module_name, module)
111
264
  sys.modules[f"{package_path}.{module_name}"] = module
@@ -129,14 +282,26 @@ def get_caller_dir(target: str, base_path: str, dir_path: str) -> str:
129
282
 
130
283
  def py_import(
131
284
  target: str,
132
- items: Optional[dict[str, Union[str, bool]]] = None,
285
+ caller_dir: str,
286
+ items: Optional[dict[str, Union[str, Optional[str]]]] = None,
133
287
  absorb: bool = False,
134
288
  mdl_alias: Optional[str] = None,
135
- ) -> types.ModuleType:
289
+ ) -> tuple[types.ModuleType, ...]:
136
290
  """Import a Python module."""
137
291
  try:
138
- target = target.lstrip(".") if target.startswith("..") else target
139
- imported_module = importlib.import_module(name=target)
292
+ loaded_items: list = []
293
+ if target.startswith("."):
294
+ target = target.lstrip(".")
295
+ full_target = path.normpath(path.join(caller_dir, target))
296
+ spec = importlib.util.spec_from_file_location(target, full_target + ".py")
297
+ if spec and spec.loader:
298
+ imported_module = importlib.util.module_from_spec(spec)
299
+ sys.modules[spec.name] = imported_module
300
+ spec.loader.exec_module(imported_module)
301
+ else:
302
+ raise ImportError(f"Cannot find module {target} at {full_target}")
303
+ else:
304
+ imported_module = importlib.import_module(name=target)
140
305
  main_module = __import__("__main__")
141
306
  if absorb:
142
307
  for name in dir(imported_module):
@@ -145,19 +310,25 @@ def py_import(
145
310
 
146
311
  elif items:
147
312
  for name, alias in items.items():
313
+ if isinstance(alias, bool):
314
+ alias = name
148
315
  try:
149
- setattr(
150
- main_module,
151
- alias if isinstance(alias, str) else name,
152
- getattr(imported_module, name),
153
- )
154
- except AttributeError as e:
155
- if hasattr(imported_module, "__path__"):
316
+ item = getattr(imported_module, name)
317
+ if item not in loaded_items:
156
318
  setattr(
157
- main_module,
158
- alias if isinstance(alias, str) else name,
159
- importlib.import_module(f"{target}.{name}"),
319
+ main_module, alias if isinstance(alias, str) else name, item
160
320
  )
321
+ loaded_items.append(item)
322
+ except AttributeError as e:
323
+ if hasattr(imported_module, "__path__"):
324
+ item = importlib.import_module(f"{target}.{name}")
325
+ if item not in loaded_items:
326
+ setattr(
327
+ main_module,
328
+ alias if isinstance(alias, str) else name,
329
+ item,
330
+ )
331
+ loaded_items.append(item)
161
332
  else:
162
333
  raise e
163
334
 
@@ -167,7 +338,7 @@ def py_import(
167
338
  mdl_alias if isinstance(mdl_alias, str) else target,
168
339
  imported_module,
169
340
  )
170
- return imported_module
341
+ return (imported_module, *loaded_items)
342
+
171
343
  except ImportError as e:
172
- print(f"Failed to import module {target}")
173
344
  raise e