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,299 @@
1
+ from ..imports import *
2
+ from .dot_utils import *
3
+ from .sysroot_utils import *
4
+ from .extract_utils import *
5
+ def find_top_package_dir(p: Path) -> Path | None:
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
+ def safe_import(name: str, package: str | None = None, member: str | None = None):
17
+ """
18
+ Wraps importlib.import_module but also resolves relative imports like '..logPaneTab'.
19
+ If `member` is given, returns that attribute from the module.
20
+ """
21
+ mod = importlib.import_module(name, package=package)
22
+ return getattr(mod, member) if member else mod
23
+ def unique_module_name(base: str, path: Path) -> str:
24
+ # Make a stable, unique name per file
25
+ digest = hashlib.md5(str(path.resolve()).encode()).hexdigest()[:8]
26
+ stem = path.stem.replace('-', '_')
27
+ return f"_dyn.{base}.{stem}_{digest}"
28
+
29
+ def import_from_path(module_name: str, file_path: Path):
30
+ spec = importlib.util.spec_from_file_location(module_name, str(file_path))
31
+ if spec is None or spec.loader is None:
32
+ raise ImportError(f"Could not load spec for {file_path}")
33
+ mod = importlib.util.module_from_spec(spec)
34
+ # Register before exec to satisfy intra-module relative imports (rare but safe)
35
+ sys.modules[module_name] = mod
36
+ spec.loader.exec_module(mod)
37
+ return mod
38
+ def get_imports(files: List[str],self=None) -> Dict[str, Dict[str, Any]]:
39
+ """
40
+ Discover package context from each file's relative-import requirements,
41
+ add the proper sysroot to sys.path once, and import by dotted name.
42
+ Returns: {modname: {"filepath", "sysroot", "module", "class", "funcs", "selfs"}}
43
+ """
44
+ results: Dict[str, Dict[str, Any]] = {}
45
+ seen_sysroots: set[str] = set()
46
+ files = make_list(files)
47
+ for file in files:
48
+ file_p = Path(file).resolve()
49
+
50
+ # 1) Use your logic to find a viable sysroot (top where dots won't exceed)
51
+ sysroot_guess = Path(get_dot_range_sysroot(str(file_p))).resolve()
52
+ # 2) Ensure we import with *package* context: we need the directory ABOVE
53
+ # the topmost package on sys.path, not the package dir itself.
54
+ top_pkg_dir = find_top_package_dir(sysroot_guess) or find_top_package_dir(file_p)
55
+ if not top_pkg_dir:
56
+ raise RuntimeError(f"No package context found for {file_p}; add __init__.py up the tree.")
57
+ sysroot = top_pkg_dir.parent
58
+
59
+ if str(sysroot) not in seen_sysroots:
60
+ ensure_on_path(sysroot)
61
+ seen_sysroots.add(str(sysroot))
62
+
63
+ # 3) Compute dotted name from sysroot and import
64
+ dotted = dotted_from(file_p, sysroot)
65
+ try:
66
+ mod = importlib.import_module(dotted)
67
+ except Exception as e:
68
+ # Helpful hint if user ran file directly (no package context)
69
+ if "__package__" in dir() and not __package__:
70
+ raise RuntimeError(
71
+ f"Import failed for {dotted}. If you ran this file directly, "
72
+ f"bootstrap package context or run via `python -m ...`."
73
+ ) from e
74
+ raise
75
+
76
+ # 4) Collect symbols (you can keep your regex readers if you prefer)
77
+ classes = extract_class(str(file_p))
78
+ funcs = extract_funcs(str(file_p))
79
+ selfs = extract_selfs(str(file_p))
80
+
81
+ # stable result key (avoid collisions)
82
+ key = get_unique_name(get_file_parts(str(file_p))['filename'], results)
83
+
84
+ results[key] = {
85
+ "filepath": str(file_p),
86
+ "sysroot": str(sysroot),
87
+ "module": mod,
88
+ "class": classes,
89
+ "funcs": funcs,
90
+ "selfs": selfs,
91
+ }
92
+
93
+ return results
94
+ def inject_symbols_from_module(
95
+ mod,
96
+ into: dict,
97
+ *,
98
+ expose_functions: bool = True,
99
+ expose_classes: bool = True,
100
+ expose_methods: bool = False,
101
+ self: object | None = None, # <---- NEW
102
+ ctor_overrides: Mapping[str, Tuple[tuple, dict]] | None = None,
103
+ name_prefix: str = "",
104
+ name_filter: Iterable[str] | None = None,
105
+ overwrite: bool = False
106
+ ) -> Dict[str, Any]:
107
+ """
108
+ Inject functions/classes from `mod` into `into` (usually globals()).
109
+ If expose_methods=True and a `self` object is passed, bind instance methods
110
+ directly onto `self` instead of globals().
111
+ """
112
+ exported: Dict[str, Any] = {}
113
+ ctor_overrides = ctor_overrides or {}
114
+
115
+ def allowed(name: str) -> bool:
116
+ return name_filter is None or name in name_filter
117
+
118
+ # 1) functions
119
+ if expose_functions:
120
+ for name, obj in vars(mod).items():
121
+ input(name)
122
+ if not allowed(name):
123
+ continue
124
+ if inspect.isfunction(obj) and obj.__module__ == mod.__name__:
125
+ out_name = f"{name_prefix}{name}"
126
+ if overwrite or out_name not in into:
127
+ into[out_name] = obj
128
+ exported[out_name] = obj
129
+
130
+ # 2) classes (+ optional bound methods)
131
+ if expose_classes or expose_methods:
132
+ for cls_name, cls in vars(mod).items():
133
+ if not allowed(cls_name):
134
+ continue
135
+ if inspect.isclass(cls) and cls.__module__ == mod.__name__:
136
+ if expose_classes:
137
+ out_name = f"{name_prefix}{cls_name}"
138
+ if overwrite or out_name not in into:
139
+ into[out_name] = cls
140
+ exported[out_name] = cls
141
+
142
+ if expose_methods and self is not None:
143
+ # instantiate class
144
+ args, kwargs = ctor_overrides.get(cls_name, ((), {}))
145
+ try:
146
+ inst = cls(*args, **kwargs)
147
+ except Exception:
148
+ continue
149
+
150
+ for meth_name, meth_obj in vars(cls).items():
151
+ if meth_name.startswith("__"):
152
+ continue
153
+ if inspect.isfunction(meth_obj) or inspect.ismethoddescriptor(meth_obj):
154
+ try:
155
+ bound = getattr(inst, meth_name)
156
+ except Exception:
157
+ continue
158
+ if callable(bound):
159
+ # attach directly to the `self` you passed in
160
+ if not hasattr(self, meth_name) or overwrite:
161
+ setattr(self, meth_name, bound)
162
+ exported[f"{cls_name}.{meth_name}"] = bound
163
+
164
+ return exported
165
+ def inject_from_imports_map(
166
+ imports_map: Dict[str, Dict[str, Any]],
167
+ *,
168
+ into: Optional[dict] = None, # where to put free funcs/classes (defaults to this module's globals)
169
+ attach_self: Optional[object] = None, # bind names listed in "selfs" onto this object
170
+ prefix_modules: bool = False, # add "<module>__" prefix to avoid collisions
171
+ overwrite: bool = False, # allow overwriting existing names
172
+ self:any=None
173
+ ) -> Dict[str, Any]:
174
+ """
175
+ Emulates: from functions import * (plus: bind 'self' methods)
176
+ Returns dict of injected_name -> object (including methods bound to attach_self).
177
+ """
178
+ ns = into if into is not None else globals()
179
+ exported: Dict[str, Any] = {}
180
+
181
+ for mod_key, info in imports_map.items():
182
+ mod = info.get("module")
183
+ if mod is None:
184
+ continue
185
+
186
+ func_names: List[str] = info.get("funcs", [])
187
+ self_names: List[str] = info.get("selfs", [])
188
+ class_names: List[str] = info.get("class", [])
189
+
190
+ # 1) bind the "selfs" directly onto self
191
+ if self is not None:
192
+ for name in self_names:
193
+ func = getattr(mod, name, None)
194
+ if not callable(func):
195
+ continue
196
+ # sanity: ensure first param is literally named 'self'
197
+ try:
198
+ params = list(inspect.signature(func).parameters.values())
199
+ if not params or params[0].name != "self":
200
+ continue
201
+ except Exception:
202
+ continue
203
+
204
+ bound = MethodType(func, self)
205
+ if overwrite or not hasattr(self, name):
206
+ setattr(self, name, bound)
207
+ exported[f"{mod_key}.self.{name}"] = bound
208
+
209
+ # 2) Inject free functions (exclude the ones we just bound to self to avoid dupes)
210
+ for name in func_names:
211
+ if name in self_names:
212
+ continue
213
+ obj = getattr(mod, name, None)
214
+ if inspect.isfunction(obj) and obj.__module__ == mod.__name__:
215
+ out_name = f"{mod_key}__{name}" if prefix_modules else name
216
+ if overwrite or out_name not in ns:
217
+ ns[out_name] = obj
218
+ exported[out_name] = obj
219
+
220
+ # 3) Inject classes
221
+ for name in class_names:
222
+ cls = getattr(mod, name, None)
223
+ if inspect.isclass(cls) and cls.__module__ == mod.__name__:
224
+ out_name = f"{mod_key}__{name}" if prefix_modules else name
225
+ if overwrite or out_name not in ns:
226
+ ns[out_name] = cls
227
+ exported[out_name] = cls
228
+
229
+ # optional: control wildcard export from THIS module
230
+ ns["__all__"] = sorted(set(list(ns.get("__all__", [])) + list(exported.keys())))
231
+ return exported
232
+ def ifFunctionsInFiles(root: str | None = None, *, expose_methods: bool = True, self=None) -> Dict[str, Any]:
233
+ here = Path(__file__).resolve().parent
234
+ base = Path(root).resolve() if root else here
235
+ candidates = [base / "functions", base / "functions.py"]
236
+ files: List[str] = []
237
+ for item in candidates:
238
+ if item.exists():
239
+ if item.is_dir():
240
+ files = [str(p) for p in item.rglob("*.py") if p.name != "__init__.py"]
241
+ else:
242
+ files = [str(item)]
243
+ break
244
+
245
+ exported_all: Dict[str, Any] = {}
246
+ imports_map = get_imports(files)
247
+
248
+ exported = inject_from_imports_map(
249
+ imports_map,
250
+ self=self
251
+ )
252
+
253
+ return exported
254
+ def attach_self_functions(
255
+ imports_map: Dict[str, Dict[str, Any]],
256
+ self_obj: object,
257
+ *,
258
+ overwrite: bool = False,
259
+ only: Optional[List[str]] = None, # whitelist of method names to attach
260
+ prefix_modules: bool = False # attach as core_utils__on_run_code instead of on_run_code
261
+ ) -> Dict[str, Any]:
262
+ """
263
+ For each module in imports_map, bind names listed in 'selfs' (top-level defs whose
264
+ first param is 'self') directly onto `self_obj`.
265
+
266
+ Returns {attached_name: bound_method}.
267
+ """
268
+ attached: Dict[str, Any] = {}
269
+
270
+ for mod_key, info in imports_map.items():
271
+ mod = info.get("module")
272
+ if not mod:
273
+ continue
274
+
275
+ self_names: List[str] = info.get("selfs", [])
276
+ for name in self_names:
277
+ if only is not None and name not in only:
278
+ continue
279
+
280
+ func = getattr(mod, name, None)
281
+ if not callable(func):
282
+ continue
283
+
284
+ # sanity check: first param literally named 'self'
285
+ try:
286
+ params = list(inspect.signature(func).parameters.values())
287
+ if not params or params[0].name != "self":
288
+ continue
289
+ except Exception:
290
+ continue
291
+
292
+ bound = MethodType(func, self_obj)
293
+ out_name = f"{mod_key}__{name}" if prefix_modules else name
294
+
295
+ if overwrite or not hasattr(self_obj, out_name):
296
+ setattr(self_obj, out_name, bound)
297
+ attached[out_name] = bound
298
+
299
+ return attached
@@ -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