abstract-utilities 0.2.2.492__py3-none-any.whl → 0.2.2.583__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.
- abstract_utilities/__init__.py +6 -10
- abstract_utilities/circular_import_finder.py +222 -0
- abstract_utilities/circular_import_finder2.py +118 -0
- abstract_utilities/class_utils/__init__.py +7 -0
- abstract_utilities/class_utils/abstract_classes.py +74 -0
- abstract_utilities/class_utils/caller_utils.py +53 -0
- abstract_utilities/class_utils/class_utils.py +109 -0
- abstract_utilities/class_utils/function_utils.py +153 -0
- abstract_utilities/class_utils/global_utils.py +57 -0
- abstract_utilities/class_utils/imports/__init__.py +2 -0
- abstract_utilities/class_utils/imports/imports.py +2 -0
- abstract_utilities/class_utils/imports/utils.py +40 -0
- abstract_utilities/class_utils/module_utils.py +63 -0
- abstract_utilities/directory_utils/__init__.py +2 -0
- abstract_utilities/directory_utils/directory_utils.py +94 -0
- abstract_utilities/directory_utils/imports/__init__.py +2 -0
- abstract_utilities/directory_utils/imports/imports.py +1 -0
- abstract_utilities/directory_utils/imports/module_imports.py +2 -0
- abstract_utilities/directory_utils/name_utils.py +43 -0
- abstract_utilities/directory_utils/size_utils.py +57 -0
- abstract_utilities/directory_utils/src/__init__.py +4 -0
- abstract_utilities/directory_utils/src/directory_utils.py +108 -0
- abstract_utilities/directory_utils/src/name_utils.py +43 -0
- abstract_utilities/directory_utils/src/size_utils.py +57 -0
- abstract_utilities/directory_utils/src/utils.py +116 -0
- abstract_utilities/directory_utils/utils.py +116 -0
- abstract_utilities/env_utils/imports/imports.py +5 -3
- abstract_utilities/error_utils/__init__.py +2 -0
- abstract_utilities/error_utils/error_utils.py +25 -0
- abstract_utilities/error_utils/imports/__init__.py +2 -0
- abstract_utilities/error_utils/imports/imports.py +1 -0
- abstract_utilities/error_utils/imports/module_imports.py +1 -0
- abstract_utilities/file_utils/__init__.py +1 -2
- abstract_utilities/file_utils/imports/constants.py +84 -4
- abstract_utilities/file_utils/imports/imports.py +2 -21
- abstract_utilities/file_utils/imports/module_imports.py +2 -7
- abstract_utilities/file_utils/module_imports.py +12 -0
- abstract_utilities/file_utils/src/__init__.py +7 -0
- abstract_utilities/file_utils/src/file_filters/__init__.py +4 -0
- abstract_utilities/file_utils/src/file_filters/ensure_utils.py +116 -0
- abstract_utilities/file_utils/src/file_filters/filter_params.py +86 -0
- abstract_utilities/file_utils/src/file_filters/filter_utils.py +78 -0
- abstract_utilities/file_utils/src/file_filters/predicate_utils.py +114 -0
- abstract_utilities/file_utils/src/file_filters.py +177 -0
- abstract_utilities/file_utils/src/file_reader.py +543 -0
- abstract_utilities/file_utils/src/file_utils.py +156 -0
- abstract_utilities/file_utils/src/filter_params.py +197 -0
- abstract_utilities/file_utils/src/find_collect.py +190 -0
- abstract_utilities/file_utils/src/find_content.py +210 -0
- abstract_utilities/file_utils/src/initFunctionsGen.py +280 -0
- abstract_utilities/file_utils/src/map_utils.py +29 -0
- abstract_utilities/file_utils/src/pdf_utils.py +300 -0
- abstract_utilities/file_utils/src/reader_utils/__init__.py +4 -0
- abstract_utilities/file_utils/src/reader_utils/directory_reader.py +53 -0
- abstract_utilities/file_utils/src/reader_utils/file_reader.py +543 -0
- abstract_utilities/file_utils/src/reader_utils/file_readers.py +376 -0
- abstract_utilities/file_utils/src/reader_utils/imports.py +18 -0
- abstract_utilities/file_utils/src/reader_utils/pdf_utils.py +300 -0
- abstract_utilities/file_utils/src/type_checks.py +91 -0
- abstract_utilities/file_utils (2)/__init__.py +2 -0
- abstract_utilities/file_utils (2)/imports/__init__.py +2 -0
- abstract_utilities/file_utils (2)/imports/constants.py +118 -0
- abstract_utilities/file_utils (2)/imports/imports/__init__.py +3 -0
- abstract_utilities/file_utils (2)/imports/imports/constants.py +119 -0
- abstract_utilities/file_utils (2)/imports/imports/imports.py +46 -0
- abstract_utilities/file_utils (2)/imports/imports/module_imports.py +8 -0
- abstract_utilities/file_utils (2)/imports/utils/__init__.py +3 -0
- abstract_utilities/file_utils (2)/imports/utils/classes.py +379 -0
- abstract_utilities/file_utils (2)/imports/utils/clean_imps.py +155 -0
- abstract_utilities/file_utils (2)/imports/utils/filter_utils.py +341 -0
- abstract_utilities/file_utils (2)/src/__init__.py +8 -0
- abstract_utilities/file_utils (2)/src/file_filters.py +155 -0
- abstract_utilities/file_utils (2)/src/file_reader.py +604 -0
- abstract_utilities/file_utils (2)/src/find_collect.py +258 -0
- abstract_utilities/file_utils (2)/src/initFunctionsGen.py +286 -0
- abstract_utilities/file_utils (2)/src/map_utils.py +28 -0
- abstract_utilities/file_utils (2)/src/pdf_utils.py +300 -0
- abstract_utilities/hash_utils/__init__.py +2 -0
- abstract_utilities/hash_utils/hash_utils.py +5 -0
- abstract_utilities/hash_utils/imports/__init__.py +2 -0
- abstract_utilities/hash_utils/imports/imports.py +1 -0
- abstract_utilities/hash_utils/imports/module_imports.py +0 -0
- abstract_utilities/history_utils/__init__.py +2 -0
- abstract_utilities/history_utils/history_utils.py +37 -0
- abstract_utilities/history_utils/imports/__init__.py +2 -0
- abstract_utilities/history_utils/imports/imports.py +1 -0
- abstract_utilities/history_utils/imports/module_imports.py +0 -0
- abstract_utilities/import_utils/__init__.py +2 -0
- abstract_utilities/import_utils/circular_import_finder.py +222 -0
- abstract_utilities/import_utils/circular_import_finder2.py +118 -0
- abstract_utilities/import_utils/imports/__init__.py +4 -0
- abstract_utilities/import_utils/imports/constants.py +2 -0
- abstract_utilities/import_utils/imports/imports.py +4 -0
- abstract_utilities/import_utils/imports/module_imports.py +8 -0
- abstract_utilities/import_utils/imports/utils.py +30 -0
- abstract_utilities/import_utils/src/__init__.py +7 -0
- abstract_utilities/import_utils/src/clean_imports.py +278 -0
- abstract_utilities/import_utils/src/dot_utils.py +80 -0
- abstract_utilities/import_utils/src/extract_utils.py +46 -0
- abstract_utilities/import_utils/src/import_functions.py +91 -0
- abstract_utilities/import_utils/src/import_utils.py +299 -0
- abstract_utilities/import_utils/src/package_utils/__init__.py +139 -0
- abstract_utilities/import_utils/src/package_utils/context_utils.py +27 -0
- abstract_utilities/import_utils/src/package_utils/import_collectors.py +53 -0
- abstract_utilities/import_utils/src/package_utils/path_utils.py +28 -0
- abstract_utilities/import_utils/src/package_utils/safe_import.py +27 -0
- abstract_utilities/import_utils/src/package_utils.py +140 -0
- abstract_utilities/import_utils/src/package_utilss/__init__.py +139 -0
- abstract_utilities/import_utils/src/package_utilss/context_utils.py +27 -0
- abstract_utilities/import_utils/src/package_utilss/import_collectors.py +53 -0
- abstract_utilities/import_utils/src/package_utilss/path_utils.py +28 -0
- abstract_utilities/import_utils/src/package_utilss/safe_import.py +27 -0
- abstract_utilities/import_utils/src/pkg_utils.py +194 -0
- abstract_utilities/import_utils/src/sysroot_utils.py +112 -0
- abstract_utilities/imports.py +18 -0
- abstract_utilities/json_utils/__init__.py +2 -0
- abstract_utilities/json_utils/imports/__init__.py +2 -0
- abstract_utilities/json_utils/imports/imports.py +2 -0
- abstract_utilities/json_utils/imports/module_imports.py +5 -0
- abstract_utilities/json_utils/json_utils.py +743 -0
- abstract_utilities/list_utils/__init__.py +2 -0
- abstract_utilities/list_utils/imports/__init__.py +2 -0
- abstract_utilities/list_utils/imports/imports.py +1 -0
- abstract_utilities/list_utils/imports/module_imports.py +0 -0
- abstract_utilities/list_utils/list_utils.py +199 -0
- abstract_utilities/log_utils/__init__.py +5 -0
- abstract_utilities/log_utils/abstractLogManager.py +64 -0
- abstract_utilities/log_utils/call_response.py +68 -0
- abstract_utilities/log_utils/imports/__init__.py +2 -0
- abstract_utilities/log_utils/imports/imports.py +7 -0
- abstract_utilities/log_utils/imports/module_imports.py +2 -0
- abstract_utilities/log_utils/log_file.py +59 -0
- abstract_utilities/log_utils/logger_callable.py +49 -0
- abstract_utilities/math_utils/__init__.py +2 -0
- abstract_utilities/math_utils/imports/__init__.py +2 -0
- abstract_utilities/math_utils/imports/imports.py +2 -0
- abstract_utilities/math_utils/imports/module_imports.py +1 -0
- abstract_utilities/math_utils/math_utils.py +208 -0
- abstract_utilities/parse_utils/__init__.py +2 -0
- abstract_utilities/parse_utils/imports/__init__.py +3 -0
- abstract_utilities/parse_utils/imports/constants.py +10 -0
- abstract_utilities/parse_utils/imports/imports.py +2 -0
- abstract_utilities/parse_utils/imports/module_imports.py +4 -0
- abstract_utilities/parse_utils/parse_utils.py +516 -0
- abstract_utilities/path_utils/__init__.py +2 -0
- abstract_utilities/path_utils/imports/__init__.py +3 -0
- abstract_utilities/path_utils/imports/imports.py +1 -0
- abstract_utilities/path_utils/imports/module_imports.py +8 -0
- abstract_utilities/path_utils/path_utils.py +253 -0
- abstract_utilities/path_utils.py +95 -14
- abstract_utilities/read_write_utils/__init__.py +1 -0
- abstract_utilities/read_write_utils/imports/__init__.py +2 -0
- abstract_utilities/read_write_utils/imports/imports.py +2 -0
- abstract_utilities/read_write_utils/imports/module_imports.py +5 -0
- abstract_utilities/read_write_utils/read_write_utils.py +338 -0
- abstract_utilities/read_write_utils.py +66 -34
- abstract_utilities/safe_utils/__init__.py +2 -0
- abstract_utilities/safe_utils/imports/__init__.py +3 -0
- abstract_utilities/safe_utils/imports/imports.py +2 -0
- abstract_utilities/safe_utils/imports/module_imports.py +2 -0
- abstract_utilities/safe_utils/safe_utils.py +166 -0
- abstract_utilities/ssh_utils/__init__.py +3 -1
- abstract_utilities/ssh_utils/classes.py +0 -1
- abstract_utilities/ssh_utils/cmd_utils.py +207 -0
- abstract_utilities/ssh_utils/imports/__init__.py +3 -0
- abstract_utilities/ssh_utils/imports/imports.py +5 -0
- abstract_utilities/ssh_utils/imports/module_imports.py +6 -0
- abstract_utilities/ssh_utils/imports/utils.py +189 -0
- abstract_utilities/ssh_utils/pexpect_utils.py +11 -18
- abstract_utilities/ssh_utils/type_checks.py +92 -0
- abstract_utilities/string_utils/__init__.py +4 -0
- abstract_utilities/string_utils/clean_utils.py +28 -0
- abstract_utilities/string_utils/eat_utils.py +103 -0
- abstract_utilities/string_utils/imports/__init__.py +3 -0
- abstract_utilities/string_utils/imports/imports.py +2 -0
- abstract_utilities/string_utils/imports/module_imports.py +2 -0
- abstract_utilities/string_utils/imports/utils.py +81 -0
- abstract_utilities/string_utils/replace_utils.py +27 -0
- abstract_utilities/thread_utils/__init__.py +2 -0
- abstract_utilities/thread_utils/imports/__init__.py +2 -0
- abstract_utilities/thread_utils/imports/imports.py +2 -0
- abstract_utilities/thread_utils/imports/module_imports.py +2 -0
- abstract_utilities/thread_utils/thread_utils.py +140 -0
- abstract_utilities/time_utils/__init__.py +2 -0
- abstract_utilities/time_utils/imports/__init__.py +2 -0
- abstract_utilities/time_utils/imports/imports.py +3 -0
- abstract_utilities/time_utils/imports/module_imports.py +1 -0
- abstract_utilities/time_utils/time_utils.py +392 -0
- abstract_utilities/type_utils/__init__.py +3 -0
- abstract_utilities/type_utils/alpha_utils.py +59 -0
- abstract_utilities/type_utils/imports/__init__.py +2 -0
- abstract_utilities/type_utils/imports/imports.py +4 -0
- abstract_utilities/type_utils/imports/module_imports.py +1 -0
- abstract_utilities/type_utils/num_utils.py +19 -0
- abstract_utilities/type_utils/type_utils.py +981 -0
- {abstract_utilities-0.2.2.492.dist-info → abstract_utilities-0.2.2.583.dist-info}/METADATA +1 -1
- abstract_utilities-0.2.2.583.dist-info/RECORD +277 -0
- imports/__init__.py +36 -0
- abstract_utilities-0.2.2.492.dist-info/RECORD +0 -92
- {abstract_utilities-0.2.2.492.dist-info → abstract_utilities-0.2.2.583.dist-info}/WHEEL +0 -0
- {abstract_utilities-0.2.2.492.dist-info → abstract_utilities-0.2.2.583.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# safe_import_utils.py
|
|
2
|
+
from ..imports import *
|
|
3
|
+
from .import_functions import *
|
|
4
|
+
|
|
5
|
+
def ensure_import_pkg_js(import_pkg_js,file_path=None):
|
|
6
|
+
import_pkg_js = import_pkg_js or {"context":{}}
|
|
7
|
+
if "context" not in import_pkg_js:
|
|
8
|
+
import_pkg_js["context"]={}
|
|
9
|
+
for key in ["nulines","file_path","all_data"]:
|
|
10
|
+
if key not in import_pkg_js["context"]:
|
|
11
|
+
import_pkg_js["context"][key]=[]
|
|
12
|
+
if file_path and file_path != import_pkg_js["context"]["file_path"]:
|
|
13
|
+
found=False
|
|
14
|
+
nu_data = {"file_path":import_pkg_js["context"]["file_path"],"nulines":import_pkg_js["context"]["nulines"]}
|
|
15
|
+
for i,data in enumerate(import_pkg_js["context"]["all_data"]):
|
|
16
|
+
if data.get('file_path') == import_pkg_js["context"]["file_path"]:
|
|
17
|
+
import_pkg_js["context"]["all_data"][i] = nu_data
|
|
18
|
+
found = True
|
|
19
|
+
break
|
|
20
|
+
if found == False:
|
|
21
|
+
import_pkg_js["context"]["all_data"].append(nu_data)
|
|
22
|
+
import_pkg_js["context"]["nulines"]=[]
|
|
23
|
+
import_pkg_js["context"]["file_path"]=file_path
|
|
24
|
+
return import_pkg_js
|
|
25
|
+
def ensure_package_context(file: str):
|
|
26
|
+
"""
|
|
27
|
+
Ensure that running this file directly still gives it the correct package.
|
|
28
|
+
Sets sys.path and __package__ based on the __init__.py chain.
|
|
29
|
+
"""
|
|
30
|
+
file = file or os.getcwd()
|
|
31
|
+
here = Path(file).resolve()
|
|
32
|
+
top_pkg_dir = find_top_pkg_dir(here)
|
|
33
|
+
if not top_pkg_dir:
|
|
34
|
+
raise RuntimeError(f"No package context above {here}. Add __init__.py files up the tree.")
|
|
35
|
+
|
|
36
|
+
sysroot = top_pkg_dir.parent # dir ABOVE the top package (e.g., .../src)
|
|
37
|
+
if str(sysroot) not in sys.path:
|
|
38
|
+
sys.path.insert(0, str(sysroot))
|
|
39
|
+
|
|
40
|
+
# Compute this module's package (exclude the filename)
|
|
41
|
+
parts = here.with_suffix("").relative_to(sysroot).parts
|
|
42
|
+
pkg_name = ".".join(parts[:-1]) # e.g. abstract_ide.consoles.launcherWindowTab
|
|
43
|
+
|
|
44
|
+
# When run as a script, __package__ is empty -> set it
|
|
45
|
+
if (__name__ == "__main__") and (not globals().get("__package__")):
|
|
46
|
+
globals()["__package__"] = pkg_name
|
|
47
|
+
# --- tiny utils ---
|
|
48
|
+
def find_top_package_dir(p: Path) -> Path | None:
|
|
49
|
+
p = p.resolve()
|
|
50
|
+
if p.is_file():
|
|
51
|
+
p = p.parent
|
|
52
|
+
top = None
|
|
53
|
+
while (p / "__init__.py").exists():
|
|
54
|
+
top = p
|
|
55
|
+
if p.parent == p:
|
|
56
|
+
break
|
|
57
|
+
p = p.parent
|
|
58
|
+
return top
|
|
59
|
+
|
|
60
|
+
def derive_package_for_file(file: str) -> tuple[str, Path]:
|
|
61
|
+
"""Return (pkg_name, sysroot) for the module file."""
|
|
62
|
+
here = Path(file).resolve()
|
|
63
|
+
top_pkg_dir = find_top_pkg_dir(here)
|
|
64
|
+
if not top_pkg_dir:
|
|
65
|
+
raise RuntimeError(f"No package context above {here}. Add __init__.py up the tree.")
|
|
66
|
+
sysroot = top_pkg_dir.parent
|
|
67
|
+
if str(sysroot) not in sys.path:
|
|
68
|
+
sys.path.insert(0, str(sysroot))
|
|
69
|
+
parts = here.with_suffix("").relative_to(sysroot).parts
|
|
70
|
+
pkg_name = ".".join(parts[:-1]) # package of the module (drop the filename)
|
|
71
|
+
return pkg_name, sysroot
|
|
72
|
+
|
|
73
|
+
def ensure_caller_package(caller_file: str, caller_globals: dict | None = None) -> str:
|
|
74
|
+
"""
|
|
75
|
+
Ensure sysroot is on sys.path and return the caller's dotted package.
|
|
76
|
+
Optionally set caller_globals['__package__'] when running as a script.
|
|
77
|
+
"""
|
|
78
|
+
pkg, _ = derive_package_for_file(caller_file)
|
|
79
|
+
if caller_globals and caller_globals.get("__name__") == "__main__" and not caller_globals.get("__package__"):
|
|
80
|
+
caller_globals["__package__"] = pkg
|
|
81
|
+
return pkg
|
|
82
|
+
def get_import_pkg(line):
|
|
83
|
+
if is_line_group_import(line):
|
|
84
|
+
return clean_line(line.split(FROM_TAG)[1].split(IMPORT_TAG)[0])
|
|
85
|
+
def get_imports_from_import_pkg(line):
|
|
86
|
+
if is_line_group_import(line):
|
|
87
|
+
return get_cleaned_import_list(line,commaClean=True)
|
|
88
|
+
def add_imports_to_import_pkg_js(import_pkg,imports,import_pkg_js=None):
|
|
89
|
+
import_pkg_js = ensure_import_pkg_js(import_pkg_js)
|
|
90
|
+
imports = clean_imports(imports)
|
|
91
|
+
if import_pkg not in import_pkg_js:
|
|
92
|
+
i = len(import_pkg_js["context"]["nulines"])
|
|
93
|
+
import_pkg_js[import_pkg]={"imports":imports,"line":i}
|
|
94
|
+
import_line = f"from {import_pkg} import "
|
|
95
|
+
if import_pkg == "import":
|
|
96
|
+
import_line = IMPORT_TAG
|
|
97
|
+
import_pkg_js["context"]["nulines"].append(import_line)
|
|
98
|
+
else:
|
|
99
|
+
import_pkg_js[import_pkg]["imports"]+=imports
|
|
100
|
+
return import_pkg_js
|
|
101
|
+
def update_import_pkg_js(line,import_pkg_js=None):
|
|
102
|
+
import_pkg_js = ensure_import_pkg_js(import_pkg_js)
|
|
103
|
+
if is_line_group_import(line):
|
|
104
|
+
import_pkg = get_import_pkg(line)
|
|
105
|
+
imports = get_imports_from_import_pkg(line)
|
|
106
|
+
import_pkg_js = add_imports_to_import_pkg_js(import_pkg,imports,import_pkg_js=import_pkg_js)
|
|
107
|
+
else:
|
|
108
|
+
if len(import_pkg_js["context"]["nulines"]) >0 and line == '' and is_line_import(import_pkg_js["context"]["nulines"][-1]):
|
|
109
|
+
pass
|
|
110
|
+
else:
|
|
111
|
+
import_pkg_js["context"]["nulines"].append(line)
|
|
112
|
+
return import_pkg_js
|
|
113
|
+
# --- public API ---
|
|
114
|
+
def safe_import(
|
|
115
|
+
name: str,
|
|
116
|
+
*,
|
|
117
|
+
member: str | None = None,
|
|
118
|
+
package: str | None = None,
|
|
119
|
+
file: str | None = None,
|
|
120
|
+
caller_globals: dict | None = None,
|
|
121
|
+
):
|
|
122
|
+
"""
|
|
123
|
+
Import `name` (relative or absolute).
|
|
124
|
+
- If `name` is relative and `package` is missing, derive it from `file` (or caller).
|
|
125
|
+
- If running the caller as a script, we can set its __package__ when caller_globals is provided.
|
|
126
|
+
"""
|
|
127
|
+
if file is None:
|
|
128
|
+
# best-effort: use the immediate caller's file
|
|
129
|
+
frame = inspect.currentframe()
|
|
130
|
+
assert frame is not None
|
|
131
|
+
outer = frame.f_back
|
|
132
|
+
caller_file = (outer.f_globals.get("__file__") if outer else None) or __file__
|
|
133
|
+
else:
|
|
134
|
+
caller_file = file
|
|
135
|
+
|
|
136
|
+
if name.startswith(".") and not package:
|
|
137
|
+
package = ensure_caller_package(caller_file, caller_globals=caller_globals)
|
|
138
|
+
|
|
139
|
+
mod = importlib.import_module(name, package=package)
|
|
140
|
+
return getattr(mod, member) if member else mod
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from importlib.util import find_spec
|
|
4
|
+
from typing import Dict, Set
|
|
5
|
+
import ast, sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from importlib.util import find_spec
|
|
8
|
+
from typing import Dict, Set
|
|
9
|
+
from src.abstract_utilities.import_utils import *
|
|
10
|
+
STDLIB_NAMES = set(sys.builtin_module_names)
|
|
11
|
+
def parse_imports(file_path: Path):
|
|
12
|
+
"""Return list of (module, level) for every import/from-import."""
|
|
13
|
+
try:
|
|
14
|
+
src = file_path.read_text(errors="ignore")
|
|
15
|
+
tree = ast.parse(src, filename=str(file_path))
|
|
16
|
+
except Exception:
|
|
17
|
+
return []
|
|
18
|
+
imports = []
|
|
19
|
+
for node in ast.walk(tree):
|
|
20
|
+
if isinstance(node, ast.Import):
|
|
21
|
+
for alias in node.names:
|
|
22
|
+
imports.append((alias.name, 0))
|
|
23
|
+
elif isinstance(node, ast.ImportFrom):
|
|
24
|
+
imports.append((node.module, node.level))
|
|
25
|
+
return imports
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def resolve_relative_import(base_file: Path, module: str | None, level: int) -> Path | None:
|
|
29
|
+
"""Follow a relative import path to its real file if it exists."""
|
|
30
|
+
base = base_file.parent
|
|
31
|
+
for _ in range(level - 1):
|
|
32
|
+
base = base.parent
|
|
33
|
+
if not module:
|
|
34
|
+
target = base
|
|
35
|
+
else:
|
|
36
|
+
target = base / module.replace(".", "/")
|
|
37
|
+
if (target / "__init__.py").exists():
|
|
38
|
+
return target / "__init__.py"
|
|
39
|
+
if target.with_suffix(".py").exists():
|
|
40
|
+
return target.with_suffix(".py")
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def classify_import(mod_name: str, root_pkg: str) -> str:
|
|
47
|
+
"""Return 'local', 'internal', or 'external'."""
|
|
48
|
+
if not mod_name:
|
|
49
|
+
return "unknown"
|
|
50
|
+
if mod_name.startswith("."):
|
|
51
|
+
return "local"
|
|
52
|
+
if mod_name.split(".")[0] == root_pkg:
|
|
53
|
+
return "internal"
|
|
54
|
+
if mod_name.split(".")[0] in STDLIB_NAMES:
|
|
55
|
+
return "stdlib"
|
|
56
|
+
return "external"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def follow_imports(entry: Path, root_pkg: str,
|
|
60
|
+
visited: Dict[Path, Dict[str, Set[str]]] | None = None):
|
|
61
|
+
"""
|
|
62
|
+
Recursively follow only internal/local imports.
|
|
63
|
+
Returns {file_path: {'internal': set(), 'external': set()}}
|
|
64
|
+
"""
|
|
65
|
+
visited = visited or {}
|
|
66
|
+
if entry in visited:
|
|
67
|
+
return visited
|
|
68
|
+
|
|
69
|
+
visited[entry] = {"internal": set(), "external": set()}
|
|
70
|
+
|
|
71
|
+
for mod, level in parse_imports(entry):
|
|
72
|
+
if level > 0:
|
|
73
|
+
target = resolve_relative_import(entry, mod, level)
|
|
74
|
+
if target:
|
|
75
|
+
visited[entry]["internal"].add(str(target))
|
|
76
|
+
follow_imports(target, root_pkg, visited)
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
category = classify_import(mod, root_pkg)
|
|
80
|
+
if category == "internal":
|
|
81
|
+
spec = find_spec(mod)
|
|
82
|
+
if spec and spec.origin and spec.origin.endswith(".py"):
|
|
83
|
+
visited[entry]["internal"].add(spec.origin)
|
|
84
|
+
follow_imports(Path(spec.origin), root_pkg, visited)
|
|
85
|
+
elif category == "external":
|
|
86
|
+
visited[entry]["external"].add(mod)
|
|
87
|
+
elif category == "stdlib":
|
|
88
|
+
# stdlib gets treated like external but labeled
|
|
89
|
+
visited[entry]["external"].add(mod + " # stdlib")
|
|
90
|
+
|
|
91
|
+
return visited
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def build_master_imports(entry: Path, root_pkg: str, output: Path):
|
|
96
|
+
trace = follow_imports(entry, root_pkg)
|
|
97
|
+
lines = ["# Auto-generated master imports for abstract_utilities\n"]
|
|
98
|
+
all_modules = set()
|
|
99
|
+
external_modules = set()
|
|
100
|
+
imports = get_all_imports(path)
|
|
101
|
+
for _, data in trace.items():
|
|
102
|
+
for dep in data["internal"]:
|
|
103
|
+
path = Path(dep)
|
|
104
|
+
if path.suffix != ".py":
|
|
105
|
+
continue
|
|
106
|
+
try:
|
|
107
|
+
rel_parts = path.with_suffix("").parts
|
|
108
|
+
idx = rel_parts.index(root_pkg)
|
|
109
|
+
dotted = ".".join(rel_parts[idx:])
|
|
110
|
+
all_modules.add(dotted)
|
|
111
|
+
except ValueError:
|
|
112
|
+
continue
|
|
113
|
+
external_modules.update(data["external"])
|
|
114
|
+
|
|
115
|
+
for mod in sorted(all_modules):
|
|
116
|
+
short = mod.split(".", 1)[-1]
|
|
117
|
+
lines.append(f"from .{short} import *")
|
|
118
|
+
|
|
119
|
+
if external_modules:
|
|
120
|
+
lines.append("\n# External / stdlib imports (not traced, for reference)")
|
|
121
|
+
for ext in sorted(external_modules):
|
|
122
|
+
lines.append(f"# {ext}")
|
|
123
|
+
|
|
124
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
output.write_text("\n".join(lines+str(imports)))
|
|
126
|
+
print(f"✅ wrote master imports hub → {output}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
entry = Path(
|
|
132
|
+
"/home/flerb/Documents/pythonTools/modules/src/modules/abstract_utilities/src/"
|
|
133
|
+
"abstract_utilities/import_utils/src/import_functions.py"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
pkg = "abstract_utilities"
|
|
138
|
+
out = entry.parents[4] / "imports" / "__init__.py"
|
|
139
|
+
build_master_imports(entry, pkg, out)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import sys, os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from .path_utils import find_top_package_dir, derive_package_for_file
|
|
4
|
+
|
|
5
|
+
def ensure_package_context(file: str):
|
|
6
|
+
"""Ensure that running this file directly gives correct package context."""
|
|
7
|
+
file = file or os.getcwd()
|
|
8
|
+
here = Path(file).resolve()
|
|
9
|
+
top_pkg_dir = find_top_package_dir(here)
|
|
10
|
+
if not top_pkg_dir:
|
|
11
|
+
raise RuntimeError(f"No package context above {here}. Add __init__.py files up the tree.")
|
|
12
|
+
|
|
13
|
+
sysroot = top_pkg_dir.parent
|
|
14
|
+
if str(sysroot) not in sys.path:
|
|
15
|
+
sys.path.insert(0, str(sysroot))
|
|
16
|
+
|
|
17
|
+
parts = here.with_suffix("").relative_to(sysroot).parts
|
|
18
|
+
pkg_name = ".".join(parts[:-1])
|
|
19
|
+
if (__name__ == "__main__") and not globals().get("__package__"):
|
|
20
|
+
globals()["__package__"] = pkg_name
|
|
21
|
+
|
|
22
|
+
def ensure_caller_package(caller_file: str, caller_globals: dict | None = None) -> str:
|
|
23
|
+
"""Ensure sysroot is on sys.path and return caller's dotted package name."""
|
|
24
|
+
pkg, _ = derive_package_for_file(caller_file)
|
|
25
|
+
if caller_globals and caller_globals.get("__name__") == "__main__" and not caller_globals.get("__package__"):
|
|
26
|
+
caller_globals["__package__"] = pkg
|
|
27
|
+
return pkg
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from ..imports import *
|
|
2
|
+
from .import_functions import *
|
|
3
|
+
|
|
4
|
+
def ensure_import_pkg_js(import_pkg_js=None, file_path=None):
|
|
5
|
+
import_pkg_js = import_pkg_js or {"context": {}}
|
|
6
|
+
if "context" not in import_pkg_js:
|
|
7
|
+
import_pkg_js["context"] = {}
|
|
8
|
+
for key in ["nulines", "file_path", "all_data"]:
|
|
9
|
+
import_pkg_js["context"].setdefault(key, [] if key != "file_path" else None)
|
|
10
|
+
|
|
11
|
+
if file_path and file_path != import_pkg_js["context"].get("file_path"):
|
|
12
|
+
found = False
|
|
13
|
+
nu_data = {
|
|
14
|
+
"file_path": import_pkg_js["context"]["file_path"],
|
|
15
|
+
"nulines": import_pkg_js["context"]["nulines"]
|
|
16
|
+
}
|
|
17
|
+
for i, data in enumerate(import_pkg_js["context"]["all_data"]):
|
|
18
|
+
if data.get("file_path") == import_pkg_js["context"]["file_path"]:
|
|
19
|
+
import_pkg_js["context"]["all_data"][i] = nu_data
|
|
20
|
+
found = True
|
|
21
|
+
break
|
|
22
|
+
if not found:
|
|
23
|
+
import_pkg_js["context"]["all_data"].append(nu_data)
|
|
24
|
+
import_pkg_js["context"]["nulines"] = []
|
|
25
|
+
import_pkg_js["context"]["file_path"] = file_path
|
|
26
|
+
return import_pkg_js
|
|
27
|
+
|
|
28
|
+
def add_imports_to_import_pkg_js(import_pkg, imports, import_pkg_js=None):
|
|
29
|
+
import_pkg_js = ensure_import_pkg_js(import_pkg_js)
|
|
30
|
+
imports = clean_imports(imports)
|
|
31
|
+
if import_pkg not in import_pkg_js:
|
|
32
|
+
i = len(import_pkg_js["context"]["nulines"])
|
|
33
|
+
file_path = import_pkg_js["context"]["file_path"]
|
|
34
|
+
file_parts = get_file_parts(file_path)
|
|
35
|
+
dirname = file_parts["dirname"]
|
|
36
|
+
import_pkg_js[import_pkg] = {"imports": imports, "line": i}
|
|
37
|
+
import_pkg_js["context"]["nulines"].append(f"from {import_pkg} import ")
|
|
38
|
+
else:
|
|
39
|
+
import_pkg_js[import_pkg]["imports"] += imports
|
|
40
|
+
return import_pkg_js
|
|
41
|
+
|
|
42
|
+
def update_import_pkg_js(line, import_pkg_js=None):
|
|
43
|
+
import_pkg_js = ensure_import_pkg_js(import_pkg_js)
|
|
44
|
+
if is_line_group_import(line):
|
|
45
|
+
import_pkg = get_import_pkg(line)
|
|
46
|
+
imports = get_imports_from_import_pkg(line)
|
|
47
|
+
import_pkg_js = add_imports_to_import_pkg_js(import_pkg, imports, import_pkg_js=import_pkg_js)
|
|
48
|
+
else:
|
|
49
|
+
if import_pkg_js["context"]["nulines"] and line == "" and is_line_import(import_pkg_js["context"]["nulines"][-1]):
|
|
50
|
+
pass
|
|
51
|
+
else:
|
|
52
|
+
import_pkg_js["context"]["nulines"].append(line)
|
|
53
|
+
return import_pkg_js
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os, sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
def find_top_package_dir(p: Path) -> Path | None:
|
|
5
|
+
"""Walk upward until the topmost __init__.py-containing directory."""
|
|
6
|
+
p = p.resolve()
|
|
7
|
+
if p.is_file():
|
|
8
|
+
p = p.parent
|
|
9
|
+
top = None
|
|
10
|
+
while (p / "__init__.py").exists():
|
|
11
|
+
top = p
|
|
12
|
+
if p.parent == p:
|
|
13
|
+
break
|
|
14
|
+
p = p.parent
|
|
15
|
+
return top
|
|
16
|
+
|
|
17
|
+
def derive_package_for_file(file: str) -> tuple[str, Path]:
|
|
18
|
+
"""Return (pkg_name, sysroot) for the module file."""
|
|
19
|
+
here = Path(file).resolve()
|
|
20
|
+
top_pkg_dir = find_top_package_dir(here)
|
|
21
|
+
if not top_pkg_dir:
|
|
22
|
+
raise RuntimeError(f"No package context above {here}. Add __init__.py up the tree.")
|
|
23
|
+
sysroot = top_pkg_dir.parent
|
|
24
|
+
if str(sysroot) not in sys.path:
|
|
25
|
+
sys.path.insert(0, str(sysroot))
|
|
26
|
+
parts = here.with_suffix("").relative_to(sysroot).parts
|
|
27
|
+
pkg_name = ".".join(parts[:-1])
|
|
28
|
+
return pkg_name, sysroot
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import inspect, importlib
|
|
2
|
+
from .context_utils import ensure_caller_package
|
|
3
|
+
|
|
4
|
+
def safe_import(
|
|
5
|
+
name: str,
|
|
6
|
+
*,
|
|
7
|
+
member: str | None = None,
|
|
8
|
+
package: str | None = None,
|
|
9
|
+
file: str | None = None,
|
|
10
|
+
caller_globals: dict | None = None,
|
|
11
|
+
):
|
|
12
|
+
"""
|
|
13
|
+
Safe dynamic import that resolves relative imports when run as a script.
|
|
14
|
+
"""
|
|
15
|
+
if file is None:
|
|
16
|
+
frame = inspect.currentframe()
|
|
17
|
+
assert frame is not None
|
|
18
|
+
outer = frame.f_back
|
|
19
|
+
caller_file = (outer.f_globals.get("__file__") if outer else None) or __file__
|
|
20
|
+
else:
|
|
21
|
+
caller_file = file
|
|
22
|
+
|
|
23
|
+
if name.startswith(".") and not package:
|
|
24
|
+
package = ensure_caller_package(caller_file, caller_globals=caller_globals)
|
|
25
|
+
|
|
26
|
+
mod = importlib.import_module(name, package=package)
|
|
27
|
+
return getattr(mod, member) if member else mod
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# safe_import_utils.py
|
|
2
|
+
from ..imports import *
|
|
3
|
+
from .import_functions import *
|
|
4
|
+
from ...safe_utils import *
|
|
5
|
+
from .sysroot_utils import get_import_with_sysroot
|
|
6
|
+
def try_is_file(file_path):
|
|
7
|
+
try:
|
|
8
|
+
return os.path.isfile(file_path)
|
|
9
|
+
except:
|
|
10
|
+
return False
|
|
11
|
+
def try_is_dir(file_path):
|
|
12
|
+
try:
|
|
13
|
+
return os.path.isdir(file_path)
|
|
14
|
+
except:
|
|
15
|
+
return False
|
|
16
|
+
def try_join(*args):
|
|
17
|
+
try:
|
|
18
|
+
return safe_join(*args)
|
|
19
|
+
except:
|
|
20
|
+
return False
|
|
21
|
+
def get_pkg_or_init(pkg_path):
|
|
22
|
+
if pkg_path:
|
|
23
|
+
if try_is_file(pkg_path):
|
|
24
|
+
return pkg_path
|
|
25
|
+
pkg_py_path = f"{pkg_path}.py"
|
|
26
|
+
if try_is_file(pkg_py_path):
|
|
27
|
+
return pkg_py_path
|
|
28
|
+
pkg_init_path = try_join(pkg_path,'__init__.py')
|
|
29
|
+
if try_is_dir(pkg_path):
|
|
30
|
+
if os.path.isfile(pkg_init_path):
|
|
31
|
+
return pkg_init_path
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def ensure_import_pkg_js(import_pkg_js,file_path=None):
|
|
36
|
+
import_pkg_js = import_pkg_js or {"context":{}}
|
|
37
|
+
if "context" not in import_pkg_js:
|
|
38
|
+
import_pkg_js["context"]={}
|
|
39
|
+
for key in ["nulines","file_path","all_data"]:
|
|
40
|
+
if key not in import_pkg_js["context"]:
|
|
41
|
+
import_pkg_js["context"][key]=[]
|
|
42
|
+
if file_path and file_path != import_pkg_js["context"]["file_path"]:
|
|
43
|
+
found=False
|
|
44
|
+
nu_data = {"file_path":import_pkg_js["context"]["file_path"],"nulines":import_pkg_js["context"]["nulines"]}
|
|
45
|
+
for i,data in enumerate(import_pkg_js["context"]["all_data"]):
|
|
46
|
+
if data.get('file_path') == import_pkg_js["context"]["file_path"]:
|
|
47
|
+
import_pkg_js["context"]["all_data"][i] = nu_data
|
|
48
|
+
found = True
|
|
49
|
+
break
|
|
50
|
+
if found == False:
|
|
51
|
+
import_pkg_js["context"]["all_data"].append(nu_data)
|
|
52
|
+
import_pkg_js["context"]["nulines"]=[]
|
|
53
|
+
import_pkg_js["context"]["file_path"]=file_path
|
|
54
|
+
return import_pkg_js
|
|
55
|
+
def ensure_package_context(file: str):
|
|
56
|
+
"""
|
|
57
|
+
Ensure that running this file directly still gives it the correct package.
|
|
58
|
+
Sets sys.path and __package__ based on the __init__.py chain.
|
|
59
|
+
"""
|
|
60
|
+
file = file or os.getcwd()
|
|
61
|
+
here = Path(file).resolve()
|
|
62
|
+
top_pkg_dir = find_top_pkg_dir(here)
|
|
63
|
+
if not top_pkg_dir:
|
|
64
|
+
raise RuntimeError(f"No package context above {here}. Add __init__.py files up the tree.")
|
|
65
|
+
|
|
66
|
+
sysroot = top_pkg_dir.parent # dir ABOVE the top package (e.g., .../src)
|
|
67
|
+
if str(sysroot) not in sys.path:
|
|
68
|
+
sys.path.insert(0, str(sysroot))
|
|
69
|
+
|
|
70
|
+
# Compute this module's package (exclude the filename)
|
|
71
|
+
parts = here.with_suffix("").relative_to(sysroot).parts
|
|
72
|
+
pkg_name = ".".join(parts[:-1]) # e.g. abstract_ide.consoles.launcherWindowTab
|
|
73
|
+
|
|
74
|
+
# When run as a script, __package__ is empty -> set it
|
|
75
|
+
if (__name__ == "__main__") and (not globals().get("__package__")):
|
|
76
|
+
globals()["__package__"] = pkg_name
|
|
77
|
+
# --- tiny utils ---
|
|
78
|
+
def find_top_package_dir(p: Path) -> Path | None:
|
|
79
|
+
p = p.resolve()
|
|
80
|
+
if p.is_file():
|
|
81
|
+
p = p.parent
|
|
82
|
+
top = None
|
|
83
|
+
while (p / "__init__.py").exists():
|
|
84
|
+
top = p
|
|
85
|
+
if p.parent == p:
|
|
86
|
+
break
|
|
87
|
+
p = p.parent
|
|
88
|
+
return top
|
|
89
|
+
|
|
90
|
+
def derive_package_for_file(file: str) -> tuple[str, Path]:
|
|
91
|
+
"""Return (pkg_name, sysroot) for the module file."""
|
|
92
|
+
here = Path(file).resolve()
|
|
93
|
+
top_pkg_dir = find_top_pkg_dir(here)
|
|
94
|
+
if not top_pkg_dir:
|
|
95
|
+
raise RuntimeError(f"No package context above {here}. Add __init__.py up the tree.")
|
|
96
|
+
sysroot = top_pkg_dir.parent
|
|
97
|
+
if str(sysroot) not in sys.path:
|
|
98
|
+
sys.path.insert(0, str(sysroot))
|
|
99
|
+
parts = here.with_suffix("").relative_to(sysroot).parts
|
|
100
|
+
pkg_name = ".".join(parts[:-1]) # package of the module (drop the filename)
|
|
101
|
+
return pkg_name, sysroot
|
|
102
|
+
|
|
103
|
+
def ensure_caller_package(caller_file: str, caller_globals: dict | None = None) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Ensure sysroot is on sys.path and return the caller's dotted package.
|
|
106
|
+
Optionally set caller_globals['__package__'] when running as a script.
|
|
107
|
+
"""
|
|
108
|
+
pkg, _ = derive_package_for_file(caller_file)
|
|
109
|
+
if caller_globals and caller_globals.get("__name__") == "__main__" and not caller_globals.get("__package__"):
|
|
110
|
+
caller_globals["__package__"] = pkg
|
|
111
|
+
return pkg
|
|
112
|
+
def get_init_dots(import_pkg):
|
|
113
|
+
count = 0
|
|
114
|
+
for char in import_pkg:
|
|
115
|
+
if char != '.':
|
|
116
|
+
break
|
|
117
|
+
count+=1
|
|
118
|
+
return count
|
|
119
|
+
def make_relative_pkg(import_pkg,file_path):
|
|
120
|
+
full_pkg = eatAll(import_pkg,'.')
|
|
121
|
+
if file_path:
|
|
122
|
+
dirname = file_path
|
|
123
|
+
dot_count = get_init_dots(import_pkg)
|
|
124
|
+
for i in range(dot_count):
|
|
125
|
+
dirname = os.path.dirname(dirname)
|
|
126
|
+
dirbase = os.path.basename(dirname)
|
|
127
|
+
full_pkg=f"{dirbase}.{full_pkg}"
|
|
128
|
+
return full_pkg
|
|
129
|
+
def is_local_import(import_pkg,file_path=None):
|
|
130
|
+
relative_pkg = get_module_from_import(import_pkg,file_path)
|
|
131
|
+
if get_pkg_or_init(relative_pkg):
|
|
132
|
+
return True
|
|
133
|
+
return False
|
|
134
|
+
def get_import_pkg(line):
|
|
135
|
+
if is_line_group_import(line):
|
|
136
|
+
return clean_line(line.split(FROM_TAG)[1].split(IMPORT_TAG)[0])
|
|
137
|
+
def get_imports_from_import_pkg(line):
|
|
138
|
+
if is_line_group_import(line):
|
|
139
|
+
return get_cleaned_import_list(line,commaClean=True)
|
|
140
|
+
def add_imports_to_import_pkg_js(import_pkg,imports,import_pkg_js=None,file_path=None):
|
|
141
|
+
import_pkg_js = ensure_import_pkg_js(import_pkg_js)
|
|
142
|
+
imports = clean_imports(imports)
|
|
143
|
+
pkg_path = get_module_from_import(import_pkg,file_path)
|
|
144
|
+
local_import = get_pkg_or_init(pkg_path)
|
|
145
|
+
if import_pkg not in import_pkg_js:
|
|
146
|
+
i = len(import_pkg_js["context"]["nulines"])
|
|
147
|
+
import_pkg_js[import_pkg]={"imports":imports,"line":i,"file_path":file_path,"local_import":local_import,"is_local":is_local_import(import_pkg,file_path=file_path)}
|
|
148
|
+
import_line = f"from {import_pkg} import "
|
|
149
|
+
if import_pkg == "import":
|
|
150
|
+
import_line = IMPORT_TAG
|
|
151
|
+
import_pkg_js["context"]["nulines"].append(import_line)
|
|
152
|
+
else:
|
|
153
|
+
import_pkg_js[import_pkg]["imports"]+=imports
|
|
154
|
+
return import_pkg_js
|
|
155
|
+
def update_import_pkg_js(line,import_pkg_js=None,file_path=None):
|
|
156
|
+
import_pkg_js = ensure_import_pkg_js(import_pkg_js)
|
|
157
|
+
if is_line_group_import(line):
|
|
158
|
+
import_pkg = get_import_pkg(line)
|
|
159
|
+
imports = get_imports_from_import_pkg(line)
|
|
160
|
+
import_pkg_js = add_imports_to_import_pkg_js(import_pkg,imports,import_pkg_js=import_pkg_js,file_path=file_path)
|
|
161
|
+
else:
|
|
162
|
+
if len(import_pkg_js["context"]["nulines"]) >0 and line == '' and is_line_import(import_pkg_js["context"]["nulines"][-1]):
|
|
163
|
+
pass
|
|
164
|
+
else:
|
|
165
|
+
import_pkg_js["context"]["nulines"].append(line)
|
|
166
|
+
return import_pkg_js
|
|
167
|
+
# --- public API ---
|
|
168
|
+
def safe_import(
|
|
169
|
+
name: str,
|
|
170
|
+
*,
|
|
171
|
+
member: str | None = None,
|
|
172
|
+
package: str | None = None,
|
|
173
|
+
file: str | None = None,
|
|
174
|
+
caller_globals: dict | None = None,
|
|
175
|
+
):
|
|
176
|
+
"""
|
|
177
|
+
Import `name` (relative or absolute).
|
|
178
|
+
- If `name` is relative and `package` is missing, derive it from `file` (or caller).
|
|
179
|
+
- If running the caller as a script, we can set its __package__ when caller_globals is provided.
|
|
180
|
+
"""
|
|
181
|
+
if file is None:
|
|
182
|
+
# best-effort: use the immediate caller's file
|
|
183
|
+
frame = inspect.currentframe()
|
|
184
|
+
assert frame is not None
|
|
185
|
+
outer = frame.f_back
|
|
186
|
+
caller_file = (outer.f_globals.get("__file__") if outer else None) or __file__
|
|
187
|
+
else:
|
|
188
|
+
caller_file = file
|
|
189
|
+
|
|
190
|
+
if name.startswith(".") and not package:
|
|
191
|
+
package = ensure_caller_package(caller_file, caller_globals=caller_globals)
|
|
192
|
+
|
|
193
|
+
mod = importlib.import_module(name, package=package)
|
|
194
|
+
return getattr(mod, member) if member else mod
|