abstract-utilities 0.2.2.583__py3-none-any.whl → 0.2.2.700__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 (51) hide show
  1. abstract_utilities/__init__.py +18 -6
  2. abstract_utilities/class_utils/abstract_classes.py +104 -34
  3. abstract_utilities/class_utils/caller_utils.py +39 -0
  4. abstract_utilities/class_utils/global_utils.py +35 -21
  5. abstract_utilities/class_utils/imports/imports.py +1 -1
  6. abstract_utilities/directory_utils/src/directory_utils.py +2 -0
  7. abstract_utilities/file_utils/imports/classes.py +59 -55
  8. abstract_utilities/file_utils/imports/module_imports.py +2 -2
  9. abstract_utilities/file_utils/src/file_filters/__init__.py +0 -3
  10. abstract_utilities/file_utils/src/file_filters/ensure_utils.py +382 -8
  11. abstract_utilities/file_utils/src/file_filters/filter_params.py +64 -0
  12. abstract_utilities/file_utils/src/file_filters/predicate_utils.py +21 -91
  13. abstract_utilities/file_utils/src/find_collect.py +10 -0
  14. abstract_utilities/file_utils/src/initFunctionsGen.py +36 -23
  15. abstract_utilities/file_utils/src/initFunctionsGens.py +280 -0
  16. abstract_utilities/import_utils/imports/__init__.py +1 -1
  17. abstract_utilities/import_utils/imports/init_imports.py +3 -0
  18. abstract_utilities/import_utils/imports/module_imports.py +2 -1
  19. abstract_utilities/import_utils/imports/utils.py +1 -1
  20. abstract_utilities/import_utils/src/__init__.py +1 -0
  21. abstract_utilities/import_utils/src/extract_utils.py +2 -2
  22. abstract_utilities/import_utils/src/import_functions.py +33 -14
  23. abstract_utilities/import_utils/src/import_utils.py +39 -0
  24. abstract_utilities/import_utils/src/layze_import_utils/__init__.py +2 -0
  25. abstract_utilities/import_utils/src/layze_import_utils/lazy_utils.py +41 -0
  26. abstract_utilities/import_utils/src/layze_import_utils/nullProxy.py +37 -0
  27. abstract_utilities/import_utils/src/nullProxy.py +30 -0
  28. abstract_utilities/import_utils/src/sysroot_utils.py +1 -1
  29. abstract_utilities/imports.py +5 -2
  30. abstract_utilities/json_utils/imports/imports.py +1 -1
  31. abstract_utilities/json_utils/json_utils.py +37 -3
  32. abstract_utilities/list_utils/list_utils.py +3 -0
  33. abstract_utilities/log_utils/log_file.py +137 -34
  34. abstract_utilities/parse_utils/parse_utils.py +23 -0
  35. abstract_utilities/path_utils/imports/module_imports.py +1 -1
  36. abstract_utilities/path_utils/path_utils.py +7 -12
  37. abstract_utilities/read_write_utils/imports/imports.py +1 -1
  38. abstract_utilities/read_write_utils/read_write_utils.py +137 -36
  39. abstract_utilities/type_utils/__init__.py +5 -1
  40. abstract_utilities/type_utils/get_type.py +120 -0
  41. abstract_utilities/type_utils/imports/__init__.py +1 -0
  42. abstract_utilities/type_utils/imports/constants.py +134 -0
  43. abstract_utilities/type_utils/imports/module_imports.py +25 -1
  44. abstract_utilities/type_utils/is_type.py +455 -0
  45. abstract_utilities/type_utils/make_type.py +126 -0
  46. abstract_utilities/type_utils/mime_types.py +68 -0
  47. abstract_utilities/type_utils/type_utils.py +0 -877
  48. {abstract_utilities-0.2.2.583.dist-info → abstract_utilities-0.2.2.700.dist-info}/METADATA +1 -1
  49. {abstract_utilities-0.2.2.583.dist-info → abstract_utilities-0.2.2.700.dist-info}/RECORD +51 -40
  50. {abstract_utilities-0.2.2.583.dist-info → abstract_utilities-0.2.2.700.dist-info}/WHEEL +0 -0
  51. {abstract_utilities-0.2.2.583.dist-info → abstract_utilities-0.2.2.700.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,10 @@
1
1
  # attach_functions.py — single helper you can import anywhere
2
2
  # attach_dynamic.py
3
3
  from __future__ import annotations
4
- from .find_collect import *
4
+ from .find_collect import get_files_and_dirs
5
5
  from ..imports import *
6
+ ABSPATH = os.path.abspath(__file__)
7
+ ABSROOT = os.path.dirname(ABSPATH)
6
8
 
7
9
  def caller_path():
8
10
  frame = inspect.stack()[1]
@@ -209,28 +211,37 @@ def get_for_all_tabs(root = None):
209
211
  root = root or caller_path()
210
212
  if os.path.isfile(root):
211
213
  root = os.path.dirname(root)
212
- all_tabs = get_dirs(root = root)
214
+ all_tabs = get_files_and_dirs(root,allowed_patterns='*Tab')[0]
215
+
213
216
  for ROOT in all_tabs:
214
- FUNCS_DIR = ifFunctionsInFile(ROOT)
215
- if FUNCS_DIR == None:
216
- for ROOT in get_dirs(root = ROOT):
217
- apply_inits(ROOT)
218
- else:
219
- apply_inits(ROOT)
217
+ if os.path.isdir(ROOT):
218
+ [apply_inits(os.path.join(ROOT,func)) for func in os.listdir(ROOT) if 'functions' == os.path.splitext(func)[0]]
219
+
220
+
220
221
 
221
222
 
222
223
  def apply_inits(ROOT):
223
- FUNCS_DIR = ifFunctionsInFile(ROOT)
224
-
225
-
226
- if_fun_dir = isDir(FUNCS_DIR)
227
- if if_fun_dir != None:
228
-
229
- if if_fun_dir:
230
- filepaths = collect_filepaths(FUNCS_DIR,allowed_exts='.py',add=True)
224
+ filepaths=[ROOT]
225
+ TAB_DIR = os.path.dirname(ROOT)
226
+ INIT_FUNCS_PAPTH = os.path.join(TAB_DIR, "initFuncs.py")
227
+ if_fun_dir=False
228
+ ## if os.path.isfile(ROOT):
229
+ ## dirname= os.path.dirname(ROOT)
230
+ ## basename= os.path.basename(ROOT)
231
+ ## functions_dir = os.path.join(dirname,'functions')
232
+ ## functions_path = os.path.join(functions_dir,basename)
233
+ ## input(get_clean_imports(ROOT))
234
+ ##
235
+ ## os.makedirs(functions_dir,exist_ok=True)
236
+ ## shutil.move(ROOT,functions_path)
237
+ ## ROOT=functions_dir
238
+ if os.path.isdir(ROOT):
239
+ INIT_PATH = os.path.join(ROOT, "__init__.py")
231
240
 
232
- else:
233
- filepaths = [FUNCS_DIR]
241
+ filepaths = get_files_and_dirs(directory=ROOT,allowed_exts='py',add=True)[-1]
242
+ if_fun_dir=True
243
+
244
+
234
245
 
235
246
  # Parse top-level def names
236
247
  def extract_funcs(path: str):
@@ -240,7 +251,7 @@ def apply_inits(ROOT):
240
251
  if m:
241
252
  funcs.append(m.group(1))
242
253
  return funcs
243
-
254
+
244
255
  # Build functions/__init__.py that re-exports all discovered functions
245
256
  import_lines = []
246
257
  all_funcs = []
@@ -252,7 +263,7 @@ def apply_inits(ROOT):
252
263
  all_funcs.extend(funcs)
253
264
  if if_fun_dir:
254
265
  functions_init = "\n".join(import_lines) + ("\n" if import_lines else "")
255
- write_to_file(contents=functions_init, file_path=os.path.join(FUNCS_DIR, "__init__.py"))
266
+ write_to_file(contents=functions_init, file_path=INIT_PATH)
256
267
 
257
268
  # Prepare the tuple literal of function names for import + loop
258
269
  uniq_funcs = sorted(set(all_funcs))
@@ -261,9 +272,10 @@ def apply_inits(ROOT):
261
272
  # Generate apiConsole/initFuncs.py using the safer setattr-loop
262
273
  init_funcs_src = textwrap.dedent(f"""\
263
274
 
264
-
275
+
276
+ from abstract_utilities import get_logFile
265
277
  from .functions import ({func_tuple})
266
-
278
+ logger=get_logFile(__name__)
267
279
  def initFuncs(self):
268
280
  try:
269
281
  for f in ({func_tuple}):
@@ -273,8 +285,9 @@ def apply_inits(ROOT):
273
285
  return self
274
286
  """)
275
287
 
276
- write_to_file(contents=init_funcs_src, file_path=os.path.join(ROOT, "initFuncs.py"))
288
+ write_to_file(contents=init_funcs_src, file_path=INIT_FUNCS_PAPTH)
277
289
 
278
290
  def call_for_all_tabs():
279
291
  root = get_caller_dir(2)
292
+
280
293
  get_for_all_tabs(root)
@@ -0,0 +1,280 @@
1
+ # attach_functions.py — single helper you can import anywhere
2
+ # attach_dynamic.py
3
+ from __future__ import annotations
4
+ from .find_collect import *
5
+ from ..imports import *
6
+
7
+ def caller_path():
8
+ frame = inspect.stack()[1]
9
+ return os.path.abspath(frame.filename)
10
+ def _is_defined_here(mod: types.ModuleType, obj: object) -> bool:
11
+ try:
12
+ return inspect.getmodule(obj) is mod
13
+ except Exception:
14
+ return False
15
+
16
+ def _collect_callables(mod: types.ModuleType) -> Dict[str, Callable]:
17
+ out: Dict[str, Callable] = {}
18
+ names = getattr(mod, "__all__", None)
19
+ if names:
20
+ # trust the author's export list
21
+ for n in names:
22
+ fn = getattr(mod, n, None)
23
+ if callable(fn):
24
+ out[n] = fn
25
+ return out
26
+ # otherwise, discover top-level callables defined in this module
27
+ for n in dir(mod):
28
+ if n.startswith("_"):
29
+ continue
30
+ obj = getattr(mod, n, None)
31
+ if callable(obj) and _is_defined_here(mod, obj):
32
+ out[n] = obj
33
+ return out
34
+
35
+ def _import_module_by_name(name: str) -> Optional[types.ModuleType]:
36
+ try:
37
+ return importlib.import_module(name)
38
+ except Exception:
39
+ return None
40
+
41
+ def _import_module_by_path(pkg_name: str, base_dir: str, filename: str) -> Optional[types.ModuleType]:
42
+ mod_name = f"{pkg_name}.functions"
43
+ path = os.path.join(base_dir, filename)
44
+ spec = importlib.util.spec_from_file_location(mod_name, path)
45
+ if not spec or not spec.loader:
46
+ return None
47
+ mod = importlib.util.module_from_spec(spec)
48
+ sys.modules[mod_name] = mod
49
+ spec.loader.exec_module(mod)
50
+ return mod
51
+
52
+ def _walk_functions_package(pkg_name: str, pkg_mod: types.ModuleType) -> List[types.ModuleType]:
53
+ """Import all immediate submodules in the functions/ package."""
54
+ mods: List[types.ModuleType] = [pkg_mod]
55
+ pkg_dir = os.path.dirname(pkg_mod.__file__ or "")
56
+ for info in pkgutil.iter_modules([pkg_dir]):
57
+ # only import direct children (no recursion here; easy to add if you need)
58
+ child_name = f"{pkg_mod.__name__}.{info.name}"
59
+ m = _import_module_by_name(child_name)
60
+ if m:
61
+ mods.append(m)
62
+ return mods
63
+
64
+ def _discover_functions(base_pkg: str, *, hot_reload: bool) -> List[Tuple[str, Callable, str]]:
65
+ """
66
+ Returns a list of (export_name, callable, module_basename).
67
+ Works if you have base_pkg.functions.py or base_pkg/functions/ package.
68
+ """
69
+ # Prefer normal import of '<base_pkg>.functions'
70
+ fqn = f"{base_pkg}.functions"
71
+ mod = _import_module_by_name(fqn)
72
+
73
+ if mod is None:
74
+ # fallback: sibling functions.py, even without being a package
75
+ base = _import_module_by_name(base_pkg)
76
+ if not base or not getattr(base, "__file__", None):
77
+ return []
78
+ base_dir = os.path.dirname(base.__file__)
79
+ if os.path.isfile(os.path.join(base_dir, "functions.py")):
80
+ mod = _import_module_by_path(base_pkg, base_dir, "functions.py")
81
+ else:
82
+ return []
83
+
84
+ if hot_reload:
85
+ try:
86
+ mod = importlib.reload(mod) # type: ignore[arg-type]
87
+ except Exception:
88
+ pass
89
+
90
+ results: List[Tuple[str, Callable, str]] = []
91
+ modules: List[types.ModuleType]
92
+
93
+ if hasattr(mod, "__path__"): # it's a package: import children
94
+ modules = _walk_functions_package(base_pkg, mod)
95
+ else:
96
+ modules = [mod]
97
+
98
+ for m in modules:
99
+ exported = _collect_callables(m)
100
+ module_basename = m.__name__.split(".")[-1]
101
+ for name, fn in exported.items():
102
+ results.append((name, fn, module_basename))
103
+ return results
104
+
105
+ def attach_functions(
106
+ obj_or_cls,
107
+ base_pkg: str | None = None,
108
+ hot_reload: bool = True,
109
+ prefix_with_module: bool = False,
110
+ include_private: bool = True,
111
+ only_defined_here: bool = True, # don't attach stuff imported from elsewhere
112
+ ) -> list[str]:
113
+ """
114
+ Attach all free functions found in <base_pkg>.functions (module or package)
115
+ to the *class* of obj_or_cls. Returns the list of attached attribute names.
116
+ """
117
+ cls = obj_or_cls if inspect.isclass(obj_or_cls) else obj_or_cls.__class__
118
+ # Derive "<package>.functions" from the class's module unless you pass base_pkg
119
+ caller_mod = cls.__module__
120
+ pkg_root = (base_pkg or caller_mod.rsplit(".", 1)[0]).rstrip(".")
121
+ funcs_pkg_name = f"{pkg_root}.functions"
122
+
123
+ def _import(name: str) -> ModuleType | None:
124
+ try:
125
+ if hot_reload and name in sys.modules:
126
+ return importlib.reload(sys.modules[name])
127
+ return importlib.import_module(name)
128
+ except Exception:
129
+ return None
130
+
131
+ def _is_pkg(m: ModuleType) -> bool:
132
+ return hasattr(m, "__path__")
133
+
134
+ mod = _import(funcs_pkg_name)
135
+ if mod is None:
136
+ # Nothing to attach (no functions.py or functions/ next to your class)
137
+ setattr(cls, "_attached_functions", tuple())
138
+ return []
139
+
140
+ modules: list[ModuleType] = [mod]
141
+ if _is_pkg(mod):
142
+ # attach from every submodule under functions/
143
+ for it in pkgutil.iter_modules(mod.__path__):
144
+ sub = _import(f"{funcs_pkg_name}.{it.name}")
145
+ if sub is not None:
146
+ modules.append(sub)
147
+
148
+ attached: list[str] = []
149
+ for m in modules:
150
+ for name, obj in vars(m).items():
151
+ # only callables (skip classes), and keep them sane
152
+ if not callable(obj) or isinstance(obj, type):
153
+ continue
154
+ if only_defined_here and getattr(obj, "__module__", None) != m.__name__:
155
+ continue
156
+ if not include_private and name.startswith("_"):
157
+ continue
158
+ if name.startswith("__") and name.endswith("__"):
159
+ continue
160
+ attr = f"{m.__name__.rsplit('.', 1)[-1]}__{name}" if prefix_with_module else name
161
+ try:
162
+ setattr(cls, attr, obj) # set on CLASS → becomes bound method on instances
163
+ attached.append(attr)
164
+ except Exception:
165
+ # don't explode if one name collides; keep going
166
+ continue
167
+
168
+ # handy for debugging
169
+ try:
170
+ setattr(cls, "_attached_functions", tuple(attached))
171
+ except Exception:
172
+ pass
173
+ return attached
174
+
175
+
176
+
177
+
178
+ def isTab(item):
179
+ item_lower = item.lower()
180
+ for key in ['console','tab']:
181
+ if item_lower.endswith(key):
182
+ return True
183
+ return False
184
+ def get_dir(root,item):
185
+ if None in [root]:
186
+ return None
187
+ path = root
188
+ if item != None:
189
+ path = os.path.join(path,item)
190
+ return path
191
+ def isDir(root,item=None):
192
+ path = get_dir(root,item)
193
+ if path:
194
+ return os.path.isdir(path)
195
+ def check_dir_item(root,item=None):
196
+ return (item and isTab(item) and isDir(root,item))
197
+ def get_dirs(root = None):
198
+ root = root or ABSROOT
199
+ dirpaths = [get_dir(root,item) for item in os.listdir(root) if check_dir_item(root,item)]
200
+ return dirpaths
201
+ def ifFunctionsInFile(root):
202
+ items = [os.path.join(root, "functions"),os.path.join(root, "functions.py")]
203
+ for item in items:
204
+ if os.path.exists(item):
205
+ return item
206
+
207
+
208
+ def get_for_all_tabs(root = None):
209
+ root = root or get_initial_caller_dir()
210
+ if os.path.isfile(root):
211
+ root = os.path.dirname(root)
212
+ all_tabs = get_dirs(root = root)
213
+ for ROOT in all_tabs:
214
+ FUNCS_DIR = ifFunctionsInFile(ROOT)
215
+ if FUNCS_DIR == None:
216
+ for ROOT in get_dirs(root = ROOT):
217
+ apply_inits(ROOT)
218
+ else:
219
+ apply_inits(ROOT)
220
+
221
+
222
+ def apply_inits(ROOT):
223
+ FUNCS_DIR = ifFunctionsInFile(ROOT)
224
+
225
+
226
+ if_fun_dir = isDir(FUNCS_DIR)
227
+ if if_fun_dir != None:
228
+
229
+ if if_fun_dir:
230
+ filepaths = collect_filepaths(FUNCS_DIR,allowed_exts='.py',add=True)
231
+
232
+ else:
233
+ filepaths = [FUNCS_DIR]
234
+
235
+ # Parse top-level def names
236
+ def extract_funcs(path: str):
237
+ funcs = []
238
+ for line in read_from_file(path).splitlines():
239
+ m = re.match(r"^def\s+([A-Za-z_]\w*)\s*\(self", line)
240
+ if m:
241
+ funcs.append(m.group(1))
242
+ return funcs
243
+
244
+ # Build functions/__init__.py that re-exports all discovered functions
245
+ import_lines = []
246
+ all_funcs = []
247
+ for fp in filepaths:
248
+ module = os.path.splitext(os.path.basename(fp))[0]
249
+ funcs = extract_funcs(fp)
250
+ if funcs:
251
+ import_lines.append(f"from .{module} import ({', '.join(funcs)})")
252
+ all_funcs.extend(funcs)
253
+ if if_fun_dir:
254
+ functions_init = "\n".join(import_lines) + ("\n" if import_lines else "")
255
+ write_to_file(contents=functions_init, file_path=os.path.join(FUNCS_DIR, "__init__.py"))
256
+
257
+ # Prepare the tuple literal of function names for import + loop
258
+ uniq_funcs = sorted(set(all_funcs))
259
+ func_tuple = ", ".join(uniq_funcs) + ("," if len(uniq_funcs) == 1 else "")
260
+
261
+ # Generate apiConsole/initFuncs.py using the safer setattr-loop
262
+ init_funcs_src = textwrap.dedent(f"""\
263
+
264
+ from abstract_utilities import get_logFile
265
+ from .functions import ({func_tuple})
266
+ logger=get_logFile(__name__)
267
+ def initFuncs(self):
268
+ try:
269
+ for f in ({func_tuple}):
270
+ setattr(self, f.__name__, f)
271
+ except Exception as e:
272
+ logger.info(f"{{e}}")
273
+ return self
274
+ """)
275
+
276
+ write_to_file(contents=init_funcs_src, file_path=os.path.join(ROOT, "initFuncs.py"))
277
+
278
+ def call_for_all_tabs():
279
+ root = get_initial_caller_dir()
280
+ get_for_all_tabs(root)
@@ -1,4 +1,4 @@
1
- from .imports import *
1
+ from .init_imports import *
2
2
  from .module_imports import *
3
3
  from .constants import *
4
4
  from .utils import *
@@ -0,0 +1,3 @@
1
+ from ...imports import *
2
+
3
+
@@ -1,8 +1,9 @@
1
1
  from ...read_write_utils import read_from_file,write_to_file,get_text_or_read
2
2
  from ...string_utils import eatAll,eatInner,eatElse,clean_line
3
- from ...class_utils import get_caller_path,get_caller_dir,if_none_default,get_initial_caller_dir
3
+ from ...class_utils import if_none_change,if_none_default,get_true_globals,get_initial_caller_dir,get_caller_path,get_caller_dir,if_none_default
4
4
  from ...list_utils import make_list
5
5
  from ...path_utils import get_file_parts
6
6
  from ...type_utils import is_number,make_list
7
7
  from ...file_utils import collect_filepaths,collect_globs
8
8
  from ...directory_utils import get_shortest_path,get_common_root
9
+ from ...log_utils import get_logFile
@@ -1,4 +1,4 @@
1
- import os
1
+ from .init_imports import *
2
2
  from .constants import *
3
3
 
4
4
  def is_line_import(line):
@@ -5,3 +5,4 @@ from .import_utils import *
5
5
  from .import_functions import *
6
6
  from .pkg_utils import *
7
7
  from .sysroot_utils import *
8
+ from .layze_import_utils import *
@@ -40,7 +40,7 @@ def extract_class(path: str):
40
40
  if m:
41
41
  funcs.append(m.group(1))
42
42
  return funcs
43
- def get_all_py_file_paths(*args,**kwargs):
44
- globs = collect_globs(*args,allowed_exts='.py',**kwargs)
43
+ def get_all_py_file_paths(directory,*args,**kwargs):
44
+ globs = collect_globs(directory,*args,allowed_exts='.py',**kwargs)
45
45
  globs = [glo for glo in globs.get('files') if glo]
46
46
  return globs
@@ -56,22 +56,17 @@ def dynamic_import(module_path: str, namespace: dict, all_imports = None):
56
56
  from module_path import *
57
57
  but includes private (_xxx) names too.
58
58
  """
59
- all_imports = if_none_default(all_imports,True,typ=bool)
59
+ all_imports = if_none_change(all_imports,True)
60
60
  if module_path:
61
61
  module = importlib.import_module(module_path)
62
-
63
62
  # Import literally everything except dunders, unless you want them too.
64
63
  names = [n for n in dir(module) if n and ((not all_imports and not n.startswith("_")) or all_imports)]
65
-
66
-
67
64
  for name in names:
68
65
  namespace[name] = getattr(module, name)
69
-
70
66
  return module
71
-
72
-
73
67
  def get_monorepo_root(directory=None,files=None):
74
68
  directory = directory or get_initial_caller_dir()
69
+
75
70
  py_files = get_all_py_file_paths(directory,add=True)
76
71
  sysroots = get_all_py_sysroots(directory=directory,files=py_files)
77
72
  monorepo_root = get_common_root(sysroots)
@@ -80,12 +75,36 @@ def switch_to_monorepo_root(directory=None,files=None):
80
75
  monorepo_root = get_monorepo_root(directory=directory,files=files)
81
76
  if str(monorepo_root) not in sys.path:
82
77
  sys.path.insert(0, str(monorepo_root))
83
- def get_all_imports(directory=None,sysroot=None):
84
- sysroot = sysroot or get_initial_caller_dir()
85
- directory = directory or sysroot
86
- files = get_py_files(directory=directory)
87
- switch_to_monorepo_root(directory=directory,files=files)
88
- for glo in [glo for glo in globs.get('files') if glo]:
78
+ return str(monorepo_root)
79
+ def get_all_imports(directory=None,sysroot=None,globs=None):
80
+ globs = globs or get_true_globals() or globals()
81
+ directory = directory or get_initial_caller_dir()
82
+ files = collect_globs(directory=directory,allowed_exts='.py').get('files')
83
+ sysroot = sysroot or switch_to_monorepo_root(directory=directory,files=files)
84
+ for glo in files:
89
85
  imp = get_import_with_sysroot(glo, sysroot)
90
- dynamic_import(imp, globals())
86
+ dynamic_import(imp, globs)
87
+ def get_all_imports_for_class(self, directory=None, sysroot=None, include_private=True):
88
+ """
89
+ Load all modules under `directory` and assign their exports as attributes
90
+ on the class instance (self).
91
+ """
92
+ directory = directory or get_initial_caller_dir()
93
+ files = collect_globs(directory=directory, allowed_exts='.py').get("files")
94
+
95
+ # Compute sysroot (monorepo root)
96
+ sysroot = sysroot or switch_to_monorepo_root(directory=directory, files=files)
97
+
98
+ for glo in files:
99
+ mod_path = get_import_with_sysroot(glo, sysroot)
100
+ module = importlib.import_module(mod_path)
101
+
102
+ for name in dir(module):
103
+ if name.startswith("__"):
104
+ continue
105
+ if not include_private and name.startswith("_"):
106
+ continue
107
+
108
+ setattr(self, name, getattr(module, name))
91
109
 
110
+ return self
@@ -297,3 +297,42 @@ def attach_self_functions(
297
297
  attached[out_name] = bound
298
298
 
299
299
  return attached
300
+ def initFuncs(self, mode=None):
301
+ """
302
+ Load functions in either dev (dynamic) or prod (static) mode.
303
+ """
304
+ mode = mode or "dev"
305
+ from abstract_utilities import get_logFile
306
+ logger = get_logFile(__name__)
307
+
308
+ try:
309
+ base_pkg = self.__class__.__module__.rsplit('.', 1)[0]
310
+
311
+ if mode == "prod":
312
+ # All imports come from the auto-generated import script
313
+ imports_mod = importlib.import_module(f"{base_pkg}.imports.funcs_imports")
314
+
315
+ # Attach all exports
316
+ for name in getattr(imports_mod, "__all__", []):
317
+ func = getattr(imports_mod, name)
318
+ setattr(self, name, func.__get__(self))
319
+
320
+ return self
321
+
322
+ # --- DEV MODE: your existing dynamic loader ---
323
+ import inspect, pkgutil, importlib
324
+ pkg = importlib.import_module(base_pkg + ".functions")
325
+
326
+ existing = set(self.__dict__) | set(dir(self))
327
+
328
+ for _, module_name, _ in pkgutil.iter_modules(pkg.__path__):
329
+ mod = importlib.import_module(f"{pkg.__name__}.{module_name}")
330
+
331
+ for name, obj in inspect.getmembers(mod, inspect.isfunction):
332
+ if name not in existing:
333
+ setattr(self, name, obj.__get__(self))
334
+
335
+ except Exception as e:
336
+ logger.error(f"initFuncs({mode}) error: {e}")
337
+
338
+ return self
@@ -0,0 +1,2 @@
1
+ from .nullProxy import *
2
+ from .lazy_utils import *
@@ -0,0 +1,41 @@
1
+ from ...imports import *
2
+ from .nullProxy import nullProxy,nullProxy_logger
3
+ @lru_cache(maxsize=None)
4
+ def lazy_import_single(name: str,fallback=None):
5
+ """
6
+ Import module safely. If unavailable, return NullProxy.
7
+ """
8
+
9
+ if name in sys.modules:
10
+ return sys.modules[name]
11
+
12
+ try:
13
+ module = importlib.import_module(name)
14
+ return module
15
+ except Exception as e:
16
+ nullProxy_logger.warning(
17
+ "[lazy_import] Failed to import '%s': %s",
18
+ name,
19
+ e,
20
+ )
21
+ return nullProxy(name,fallback=fallback)
22
+
23
+ def get_lazy_attr(module_name: str, *attrs,fallback=None):
24
+ obj = lazy_import(module_name,fallback=fallback)
25
+
26
+ for attr in attrs:
27
+ try:
28
+ obj = getattr(obj, attr)
29
+ except Exception:
30
+ return nullProxy(module_name, attrs,fallback=fallback)
31
+
32
+ return obj
33
+ def lazy_import(name: str, *attrs,fallback=None):
34
+ """
35
+ Import module safely. If unavailable, return NullProxy.
36
+ """
37
+ if attrs:
38
+ obj = get_lazy_attr(name, *attrs,fallback=fallback)
39
+ else:
40
+ obj = lazy_import_single(name,fallback=fallback)
41
+ return obj
@@ -0,0 +1,37 @@
1
+ from ...imports import *
2
+ nullProxy_logger = logging.getLogger("abstract.lazy_import")
3
+
4
+
5
+ class nullProxy:
6
+ """
7
+ Safe, chainable, callable placeholder for missing modules/attributes.
8
+ """
9
+
10
+ def __init__(self, name, path=(),fallback=None):
11
+ self._name = name
12
+ self._path = path
13
+ self.fallback=fallback
14
+ def __getattr__(self, attr):
15
+ return nullProxy(self._name, self._path + (attr,))
16
+
17
+ def __call__(self, *args, **kwargs):
18
+ if self.fallback is not None:
19
+ try:
20
+ return self.fallback(*args, **kwargs)
21
+ except Exception as e:
22
+ logger.info(f"{e}")
23
+ nullProxy_logger.warning(
24
+ "[lazy_import] Call to missing module/attr: %s.%s args=%s kwargs=%s",
25
+ self._name,
26
+ ".".join(self._path),
27
+ args,
28
+ kwargs,
29
+ )
30
+ return None
31
+
32
+ def __repr__(self):
33
+ full = ".".join((self._name, *self._path))
34
+ return f"<nullProxy {full}>"
35
+
36
+ def __bool__(self):
37
+ return False # safe in conditionals
@@ -0,0 +1,30 @@
1
+ from ...imports import *
2
+ lazy_import_logger = logging.getLogger("abstract.lazy_import")
3
+ class nullProxy:
4
+ """
5
+ Safe, chainable, callable placeholder for missing modules/attributes.
6
+ """
7
+
8
+ def __init__(self, name, path=()):
9
+ self._name = name
10
+ self._path = path
11
+
12
+ def __getattr__(self, attr):
13
+ return nullProxy(self._name, self._path + (attr,))
14
+
15
+ def __call__(self, *args, **kwargs):
16
+ lazy_import_logger.warning(
17
+ "[lazy_import] Call to missing module/attr: %s.%s args=%s kwargs=%s",
18
+ self._name,
19
+ ".".join(self._path),
20
+ args,
21
+ kwargs,
22
+ )
23
+ return None
24
+
25
+ def __repr__(self):
26
+ full = ".".join((self._name, *self._path))
27
+ return f"<nullProxy {full}>"
28
+
29
+ def __bool__(self):
30
+ return False # safe in conditionals
@@ -2,7 +2,7 @@ from ..imports import *
2
2
  from .dot_utils import *
3
3
  from .extract_utils import get_all_py_file_paths
4
4
  def get_py_files(directory=None):
5
- directory = directory or get_caller_dir(2)
5
+ directory = directory or get_initial_caller_dir()
6
6
  return get_all_py_file_paths(directory,add=True)
7
7
  def ensure_on_path(p: Path):
8
8
  s = str(p)