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.
Files changed (201) hide show
  1. abstract_utilities/__init__.py +6 -10
  2. abstract_utilities/circular_import_finder.py +222 -0
  3. abstract_utilities/circular_import_finder2.py +118 -0
  4. abstract_utilities/class_utils/__init__.py +7 -0
  5. abstract_utilities/class_utils/abstract_classes.py +74 -0
  6. abstract_utilities/class_utils/caller_utils.py +53 -0
  7. abstract_utilities/class_utils/class_utils.py +109 -0
  8. abstract_utilities/class_utils/function_utils.py +153 -0
  9. abstract_utilities/class_utils/global_utils.py +57 -0
  10. abstract_utilities/class_utils/imports/__init__.py +2 -0
  11. abstract_utilities/class_utils/imports/imports.py +2 -0
  12. abstract_utilities/class_utils/imports/utils.py +40 -0
  13. abstract_utilities/class_utils/module_utils.py +63 -0
  14. abstract_utilities/directory_utils/__init__.py +2 -0
  15. abstract_utilities/directory_utils/directory_utils.py +94 -0
  16. abstract_utilities/directory_utils/imports/__init__.py +2 -0
  17. abstract_utilities/directory_utils/imports/imports.py +1 -0
  18. abstract_utilities/directory_utils/imports/module_imports.py +2 -0
  19. abstract_utilities/directory_utils/name_utils.py +43 -0
  20. abstract_utilities/directory_utils/size_utils.py +57 -0
  21. abstract_utilities/directory_utils/src/__init__.py +4 -0
  22. abstract_utilities/directory_utils/src/directory_utils.py +108 -0
  23. abstract_utilities/directory_utils/src/name_utils.py +43 -0
  24. abstract_utilities/directory_utils/src/size_utils.py +57 -0
  25. abstract_utilities/directory_utils/src/utils.py +116 -0
  26. abstract_utilities/directory_utils/utils.py +116 -0
  27. abstract_utilities/env_utils/imports/imports.py +5 -3
  28. abstract_utilities/error_utils/__init__.py +2 -0
  29. abstract_utilities/error_utils/error_utils.py +25 -0
  30. abstract_utilities/error_utils/imports/__init__.py +2 -0
  31. abstract_utilities/error_utils/imports/imports.py +1 -0
  32. abstract_utilities/error_utils/imports/module_imports.py +1 -0
  33. abstract_utilities/file_utils/__init__.py +1 -2
  34. abstract_utilities/file_utils/imports/constants.py +84 -4
  35. abstract_utilities/file_utils/imports/imports.py +2 -21
  36. abstract_utilities/file_utils/imports/module_imports.py +2 -7
  37. abstract_utilities/file_utils/module_imports.py +12 -0
  38. abstract_utilities/file_utils/src/__init__.py +7 -0
  39. abstract_utilities/file_utils/src/file_filters/__init__.py +4 -0
  40. abstract_utilities/file_utils/src/file_filters/ensure_utils.py +116 -0
  41. abstract_utilities/file_utils/src/file_filters/filter_params.py +86 -0
  42. abstract_utilities/file_utils/src/file_filters/filter_utils.py +78 -0
  43. abstract_utilities/file_utils/src/file_filters/predicate_utils.py +114 -0
  44. abstract_utilities/file_utils/src/file_filters.py +177 -0
  45. abstract_utilities/file_utils/src/file_reader.py +543 -0
  46. abstract_utilities/file_utils/src/file_utils.py +156 -0
  47. abstract_utilities/file_utils/src/filter_params.py +197 -0
  48. abstract_utilities/file_utils/src/find_collect.py +190 -0
  49. abstract_utilities/file_utils/src/find_content.py +210 -0
  50. abstract_utilities/file_utils/src/initFunctionsGen.py +280 -0
  51. abstract_utilities/file_utils/src/map_utils.py +29 -0
  52. abstract_utilities/file_utils/src/pdf_utils.py +300 -0
  53. abstract_utilities/file_utils/src/reader_utils/__init__.py +4 -0
  54. abstract_utilities/file_utils/src/reader_utils/directory_reader.py +53 -0
  55. abstract_utilities/file_utils/src/reader_utils/file_reader.py +543 -0
  56. abstract_utilities/file_utils/src/reader_utils/file_readers.py +376 -0
  57. abstract_utilities/file_utils/src/reader_utils/imports.py +18 -0
  58. abstract_utilities/file_utils/src/reader_utils/pdf_utils.py +300 -0
  59. abstract_utilities/file_utils/src/type_checks.py +91 -0
  60. abstract_utilities/file_utils (2)/__init__.py +2 -0
  61. abstract_utilities/file_utils (2)/imports/__init__.py +2 -0
  62. abstract_utilities/file_utils (2)/imports/constants.py +118 -0
  63. abstract_utilities/file_utils (2)/imports/imports/__init__.py +3 -0
  64. abstract_utilities/file_utils (2)/imports/imports/constants.py +119 -0
  65. abstract_utilities/file_utils (2)/imports/imports/imports.py +46 -0
  66. abstract_utilities/file_utils (2)/imports/imports/module_imports.py +8 -0
  67. abstract_utilities/file_utils (2)/imports/utils/__init__.py +3 -0
  68. abstract_utilities/file_utils (2)/imports/utils/classes.py +379 -0
  69. abstract_utilities/file_utils (2)/imports/utils/clean_imps.py +155 -0
  70. abstract_utilities/file_utils (2)/imports/utils/filter_utils.py +341 -0
  71. abstract_utilities/file_utils (2)/src/__init__.py +8 -0
  72. abstract_utilities/file_utils (2)/src/file_filters.py +155 -0
  73. abstract_utilities/file_utils (2)/src/file_reader.py +604 -0
  74. abstract_utilities/file_utils (2)/src/find_collect.py +258 -0
  75. abstract_utilities/file_utils (2)/src/initFunctionsGen.py +286 -0
  76. abstract_utilities/file_utils (2)/src/map_utils.py +28 -0
  77. abstract_utilities/file_utils (2)/src/pdf_utils.py +300 -0
  78. abstract_utilities/hash_utils/__init__.py +2 -0
  79. abstract_utilities/hash_utils/hash_utils.py +5 -0
  80. abstract_utilities/hash_utils/imports/__init__.py +2 -0
  81. abstract_utilities/hash_utils/imports/imports.py +1 -0
  82. abstract_utilities/hash_utils/imports/module_imports.py +0 -0
  83. abstract_utilities/history_utils/__init__.py +2 -0
  84. abstract_utilities/history_utils/history_utils.py +37 -0
  85. abstract_utilities/history_utils/imports/__init__.py +2 -0
  86. abstract_utilities/history_utils/imports/imports.py +1 -0
  87. abstract_utilities/history_utils/imports/module_imports.py +0 -0
  88. abstract_utilities/import_utils/__init__.py +2 -0
  89. abstract_utilities/import_utils/circular_import_finder.py +222 -0
  90. abstract_utilities/import_utils/circular_import_finder2.py +118 -0
  91. abstract_utilities/import_utils/imports/__init__.py +4 -0
  92. abstract_utilities/import_utils/imports/constants.py +2 -0
  93. abstract_utilities/import_utils/imports/imports.py +4 -0
  94. abstract_utilities/import_utils/imports/module_imports.py +8 -0
  95. abstract_utilities/import_utils/imports/utils.py +30 -0
  96. abstract_utilities/import_utils/src/__init__.py +7 -0
  97. abstract_utilities/import_utils/src/clean_imports.py +278 -0
  98. abstract_utilities/import_utils/src/dot_utils.py +80 -0
  99. abstract_utilities/import_utils/src/extract_utils.py +46 -0
  100. abstract_utilities/import_utils/src/import_functions.py +91 -0
  101. abstract_utilities/import_utils/src/import_utils.py +299 -0
  102. abstract_utilities/import_utils/src/package_utils/__init__.py +139 -0
  103. abstract_utilities/import_utils/src/package_utils/context_utils.py +27 -0
  104. abstract_utilities/import_utils/src/package_utils/import_collectors.py +53 -0
  105. abstract_utilities/import_utils/src/package_utils/path_utils.py +28 -0
  106. abstract_utilities/import_utils/src/package_utils/safe_import.py +27 -0
  107. abstract_utilities/import_utils/src/package_utils.py +140 -0
  108. abstract_utilities/import_utils/src/package_utilss/__init__.py +139 -0
  109. abstract_utilities/import_utils/src/package_utilss/context_utils.py +27 -0
  110. abstract_utilities/import_utils/src/package_utilss/import_collectors.py +53 -0
  111. abstract_utilities/import_utils/src/package_utilss/path_utils.py +28 -0
  112. abstract_utilities/import_utils/src/package_utilss/safe_import.py +27 -0
  113. abstract_utilities/import_utils/src/pkg_utils.py +194 -0
  114. abstract_utilities/import_utils/src/sysroot_utils.py +112 -0
  115. abstract_utilities/imports.py +18 -0
  116. abstract_utilities/json_utils/__init__.py +2 -0
  117. abstract_utilities/json_utils/imports/__init__.py +2 -0
  118. abstract_utilities/json_utils/imports/imports.py +2 -0
  119. abstract_utilities/json_utils/imports/module_imports.py +5 -0
  120. abstract_utilities/json_utils/json_utils.py +743 -0
  121. abstract_utilities/list_utils/__init__.py +2 -0
  122. abstract_utilities/list_utils/imports/__init__.py +2 -0
  123. abstract_utilities/list_utils/imports/imports.py +1 -0
  124. abstract_utilities/list_utils/imports/module_imports.py +0 -0
  125. abstract_utilities/list_utils/list_utils.py +199 -0
  126. abstract_utilities/log_utils/__init__.py +5 -0
  127. abstract_utilities/log_utils/abstractLogManager.py +64 -0
  128. abstract_utilities/log_utils/call_response.py +68 -0
  129. abstract_utilities/log_utils/imports/__init__.py +2 -0
  130. abstract_utilities/log_utils/imports/imports.py +7 -0
  131. abstract_utilities/log_utils/imports/module_imports.py +2 -0
  132. abstract_utilities/log_utils/log_file.py +59 -0
  133. abstract_utilities/log_utils/logger_callable.py +49 -0
  134. abstract_utilities/math_utils/__init__.py +2 -0
  135. abstract_utilities/math_utils/imports/__init__.py +2 -0
  136. abstract_utilities/math_utils/imports/imports.py +2 -0
  137. abstract_utilities/math_utils/imports/module_imports.py +1 -0
  138. abstract_utilities/math_utils/math_utils.py +208 -0
  139. abstract_utilities/parse_utils/__init__.py +2 -0
  140. abstract_utilities/parse_utils/imports/__init__.py +3 -0
  141. abstract_utilities/parse_utils/imports/constants.py +10 -0
  142. abstract_utilities/parse_utils/imports/imports.py +2 -0
  143. abstract_utilities/parse_utils/imports/module_imports.py +4 -0
  144. abstract_utilities/parse_utils/parse_utils.py +516 -0
  145. abstract_utilities/path_utils/__init__.py +2 -0
  146. abstract_utilities/path_utils/imports/__init__.py +3 -0
  147. abstract_utilities/path_utils/imports/imports.py +1 -0
  148. abstract_utilities/path_utils/imports/module_imports.py +8 -0
  149. abstract_utilities/path_utils/path_utils.py +253 -0
  150. abstract_utilities/path_utils.py +95 -14
  151. abstract_utilities/read_write_utils/__init__.py +1 -0
  152. abstract_utilities/read_write_utils/imports/__init__.py +2 -0
  153. abstract_utilities/read_write_utils/imports/imports.py +2 -0
  154. abstract_utilities/read_write_utils/imports/module_imports.py +5 -0
  155. abstract_utilities/read_write_utils/read_write_utils.py +338 -0
  156. abstract_utilities/read_write_utils.py +66 -34
  157. abstract_utilities/safe_utils/__init__.py +2 -0
  158. abstract_utilities/safe_utils/imports/__init__.py +3 -0
  159. abstract_utilities/safe_utils/imports/imports.py +2 -0
  160. abstract_utilities/safe_utils/imports/module_imports.py +2 -0
  161. abstract_utilities/safe_utils/safe_utils.py +166 -0
  162. abstract_utilities/ssh_utils/__init__.py +3 -1
  163. abstract_utilities/ssh_utils/classes.py +0 -1
  164. abstract_utilities/ssh_utils/cmd_utils.py +207 -0
  165. abstract_utilities/ssh_utils/imports/__init__.py +3 -0
  166. abstract_utilities/ssh_utils/imports/imports.py +5 -0
  167. abstract_utilities/ssh_utils/imports/module_imports.py +6 -0
  168. abstract_utilities/ssh_utils/imports/utils.py +189 -0
  169. abstract_utilities/ssh_utils/pexpect_utils.py +11 -18
  170. abstract_utilities/ssh_utils/type_checks.py +92 -0
  171. abstract_utilities/string_utils/__init__.py +4 -0
  172. abstract_utilities/string_utils/clean_utils.py +28 -0
  173. abstract_utilities/string_utils/eat_utils.py +103 -0
  174. abstract_utilities/string_utils/imports/__init__.py +3 -0
  175. abstract_utilities/string_utils/imports/imports.py +2 -0
  176. abstract_utilities/string_utils/imports/module_imports.py +2 -0
  177. abstract_utilities/string_utils/imports/utils.py +81 -0
  178. abstract_utilities/string_utils/replace_utils.py +27 -0
  179. abstract_utilities/thread_utils/__init__.py +2 -0
  180. abstract_utilities/thread_utils/imports/__init__.py +2 -0
  181. abstract_utilities/thread_utils/imports/imports.py +2 -0
  182. abstract_utilities/thread_utils/imports/module_imports.py +2 -0
  183. abstract_utilities/thread_utils/thread_utils.py +140 -0
  184. abstract_utilities/time_utils/__init__.py +2 -0
  185. abstract_utilities/time_utils/imports/__init__.py +2 -0
  186. abstract_utilities/time_utils/imports/imports.py +3 -0
  187. abstract_utilities/time_utils/imports/module_imports.py +1 -0
  188. abstract_utilities/time_utils/time_utils.py +392 -0
  189. abstract_utilities/type_utils/__init__.py +3 -0
  190. abstract_utilities/type_utils/alpha_utils.py +59 -0
  191. abstract_utilities/type_utils/imports/__init__.py +2 -0
  192. abstract_utilities/type_utils/imports/imports.py +4 -0
  193. abstract_utilities/type_utils/imports/module_imports.py +1 -0
  194. abstract_utilities/type_utils/num_utils.py +19 -0
  195. abstract_utilities/type_utils/type_utils.py +981 -0
  196. {abstract_utilities-0.2.2.492.dist-info → abstract_utilities-0.2.2.583.dist-info}/METADATA +1 -1
  197. abstract_utilities-0.2.2.583.dist-info/RECORD +277 -0
  198. imports/__init__.py +36 -0
  199. abstract_utilities-0.2.2.492.dist-info/RECORD +0 -92
  200. {abstract_utilities-0.2.2.492.dist-info → abstract_utilities-0.2.2.583.dist-info}/WHEEL +0 -0
  201. {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