abstract-utilities 0.2.2.540__py3-none-any.whl → 0.2.2.667__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of abstract-utilities might be problematic. Click here for more details.

Files changed (66) hide show
  1. abstract_utilities/__init__.py +13 -4
  2. abstract_utilities/class_utils/abstract_classes.py +104 -34
  3. abstract_utilities/class_utils/caller_utils.py +57 -0
  4. abstract_utilities/class_utils/global_utils.py +35 -20
  5. abstract_utilities/class_utils/imports/imports.py +1 -1
  6. abstract_utilities/directory_utils/src/directory_utils.py +19 -1
  7. abstract_utilities/file_utils/imports/classes.py +59 -55
  8. abstract_utilities/file_utils/imports/imports.py +0 -4
  9. abstract_utilities/file_utils/imports/module_imports.py +1 -1
  10. abstract_utilities/file_utils/src/__init__.py +2 -3
  11. abstract_utilities/file_utils/src/file_filters/__init__.py +1 -0
  12. abstract_utilities/file_utils/src/file_filters/ensure_utils.py +490 -0
  13. abstract_utilities/file_utils/src/file_filters/filter_params.py +150 -0
  14. abstract_utilities/file_utils/src/file_filters/filter_utils.py +78 -0
  15. abstract_utilities/file_utils/src/file_filters/predicate_utils.py +44 -0
  16. abstract_utilities/file_utils/src/file_reader.py +0 -1
  17. abstract_utilities/file_utils/src/find_collect.py +10 -86
  18. abstract_utilities/file_utils/src/find_content.py +210 -0
  19. abstract_utilities/file_utils/src/initFunctionsGen.py +36 -23
  20. abstract_utilities/file_utils/src/initFunctionsGens.py +280 -0
  21. abstract_utilities/file_utils/src/reader_utils/__init__.py +4 -0
  22. abstract_utilities/file_utils/src/reader_utils/directory_reader.py +53 -0
  23. abstract_utilities/file_utils/src/reader_utils/file_reader.py +543 -0
  24. abstract_utilities/file_utils/src/reader_utils/file_readers.py +376 -0
  25. abstract_utilities/file_utils/src/reader_utils/imports.py +18 -0
  26. abstract_utilities/file_utils/src/reader_utils/pdf_utils.py +300 -0
  27. abstract_utilities/import_utils/circular_import_finder.py +222 -0
  28. abstract_utilities/import_utils/circular_import_finder2.py +118 -0
  29. abstract_utilities/import_utils/imports/__init__.py +1 -1
  30. abstract_utilities/import_utils/imports/init_imports.py +3 -0
  31. abstract_utilities/import_utils/imports/module_imports.py +4 -1
  32. abstract_utilities/import_utils/imports/utils.py +1 -1
  33. abstract_utilities/import_utils/src/__init__.py +1 -0
  34. abstract_utilities/import_utils/src/clean_imports.py +156 -25
  35. abstract_utilities/import_utils/src/dot_utils.py +11 -0
  36. abstract_utilities/import_utils/src/extract_utils.py +4 -0
  37. abstract_utilities/import_utils/src/import_functions.py +66 -2
  38. abstract_utilities/import_utils/src/import_utils.py +39 -0
  39. abstract_utilities/import_utils/src/layze_import_utils/__init__.py +2 -0
  40. abstract_utilities/import_utils/src/layze_import_utils/lazy_utils.py +41 -0
  41. abstract_utilities/import_utils/src/layze_import_utils/nullProxy.py +32 -0
  42. abstract_utilities/import_utils/src/nullProxy.py +30 -0
  43. abstract_utilities/import_utils/src/pkg_utils.py +58 -4
  44. abstract_utilities/import_utils/src/sysroot_utils.py +56 -1
  45. abstract_utilities/imports.py +3 -2
  46. abstract_utilities/json_utils/json_utils.py +11 -3
  47. abstract_utilities/log_utils/log_file.py +73 -24
  48. abstract_utilities/parse_utils/parse_utils.py +23 -0
  49. abstract_utilities/path_utils/imports/module_imports.py +1 -1
  50. abstract_utilities/path_utils/path_utils.py +32 -35
  51. abstract_utilities/read_write_utils/imports/imports.py +1 -1
  52. abstract_utilities/read_write_utils/read_write_utils.py +102 -32
  53. abstract_utilities/safe_utils/safe_utils.py +30 -0
  54. abstract_utilities/type_utils/__init__.py +5 -1
  55. abstract_utilities/type_utils/get_type.py +116 -0
  56. abstract_utilities/type_utils/imports/__init__.py +1 -0
  57. abstract_utilities/type_utils/imports/constants.py +134 -0
  58. abstract_utilities/type_utils/imports/module_imports.py +25 -1
  59. abstract_utilities/type_utils/is_type.py +455 -0
  60. abstract_utilities/type_utils/make_type.py +126 -0
  61. abstract_utilities/type_utils/mime_types.py +68 -0
  62. abstract_utilities/type_utils/type_utils.py +0 -877
  63. {abstract_utilities-0.2.2.540.dist-info → abstract_utilities-0.2.2.667.dist-info}/METADATA +1 -1
  64. {abstract_utilities-0.2.2.540.dist-info → abstract_utilities-0.2.2.667.dist-info}/RECORD +66 -41
  65. {abstract_utilities-0.2.2.540.dist-info → abstract_utilities-0.2.2.667.dist-info}/WHEEL +0 -0
  66. {abstract_utilities-0.2.2.540.dist-info → abstract_utilities-0.2.2.667.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,8 @@
1
1
  # --- auto-package bootstrap (run-safe) ---------------------------------
2
2
  from ..imports import *
3
3
  from .dot_utils import get_dot_range
4
- from .sysroot_utils import get_sysroot
4
+ from .sysroot_utils import get_sysroot,get_import_with_sysroot,get_py_files,get_all_py_sysroots
5
+ from .extract_utils import get_all_py_file_paths
5
6
  def clean_imports(imports,commaClean=True):
6
7
  chars=["*"]
7
8
  if not commaClean:
@@ -9,6 +10,13 @@ def clean_imports(imports,commaClean=True):
9
10
  if isinstance(imports,str):
10
11
  imports = imports.split(',')
11
12
  return [eatElse(imp,chars=chars) for imp in imports if imp]
13
+ def get_dot_range(import_pkg):
14
+ count = 0
15
+ for char in import_pkg:
16
+ if char != '.':
17
+ break
18
+ count+=1
19
+ return count
12
20
  def get_cleaned_import_list(line,commaClean=True):
13
21
  cleaned_import_list=[]
14
22
  if IMPORT_TAG in line:
@@ -17,7 +25,7 @@ def get_cleaned_import_list(line,commaClean=True):
17
25
  return cleaned_import_list
18
26
  def get_module_from_import(imp,path=None):
19
27
  path = path or os.getcwd()
20
- i = get_dot_range(None,[imp])
28
+ i = get_dot_range(imp)
21
29
  imp = eatAll(imp,'.')
22
30
  sysroot = get_sysroot(path,i)
23
31
  return os.path.join(sysroot, imp)
@@ -42,5 +50,61 @@ def safe_import(name: str, *, package: str | None = None, member: str | None = N
42
50
  mod = importlib.import_module(name, package=package)
43
51
  return getattr(mod, member) if member else mod
44
52
 
53
+ def dynamic_import(module_path: str, namespace: dict, all_imports = None):
54
+ """
55
+ Emulates:
56
+ from module_path import *
57
+ but includes private (_xxx) names too.
58
+ """
59
+ all_imports = if_none_change(all_imports,True)
60
+ if module_path:
61
+ module = importlib.import_module(module_path)
62
+ # Import literally everything except dunders, unless you want them too.
63
+ names = [n for n in dir(module) if n and ((not all_imports and not n.startswith("_")) or all_imports)]
64
+ for name in names:
65
+ namespace[name] = getattr(module, name)
66
+ return module
67
+ def get_monorepo_root(directory=None,files=None):
68
+ directory = directory or get_initial_caller_dir()
69
+
70
+ py_files = get_all_py_file_paths(directory,add=True)
71
+ sysroots = get_all_py_sysroots(directory=directory,files=py_files)
72
+ monorepo_root = get_common_root(sysroots)
73
+ return monorepo_root
74
+ def switch_to_monorepo_root(directory=None,files=None):
75
+ monorepo_root = get_monorepo_root(directory=directory,files=files)
76
+ if str(monorepo_root) not in sys.path:
77
+ sys.path.insert(0, str(monorepo_root))
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:
85
+ imp = get_import_with_sysroot(glo, sysroot)
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
45
107
 
108
+ setattr(self, name, getattr(module, name))
46
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
+
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):
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)
22
+
23
+ def get_lazy_attr(module_name: str, *attrs):
24
+ obj = lazy_import(module_name)
25
+
26
+ for attr in attrs:
27
+ try:
28
+ obj = getattr(obj, attr)
29
+ except Exception:
30
+ return nullProxy(module_name, attrs)
31
+
32
+ return obj
33
+ def lazy_import(name: str, *attrs):
34
+ """
35
+ Import module safely. If unavailable, return NullProxy.
36
+ """
37
+ if attrs:
38
+ obj = get_lazy_attr(name, *attrs)
39
+ else:
40
+ obj = lazy_import_single(name)
41
+ return obj
@@ -0,0 +1,32 @@
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=()):
11
+ self._name = name
12
+ self._path = path
13
+
14
+ def __getattr__(self, attr):
15
+ return nullProxy(self._name, self._path + (attr,))
16
+
17
+ def __call__(self, *args, **kwargs):
18
+ nullProxy_logger.warning(
19
+ "[lazy_import] Call to missing module/attr: %s.%s args=%s kwargs=%s",
20
+ self._name,
21
+ ".".join(self._path),
22
+ args,
23
+ kwargs,
24
+ )
25
+ return None
26
+
27
+ def __repr__(self):
28
+ full = ".".join((self._name, *self._path))
29
+ return f"<nullProxy {full}>"
30
+
31
+ def __bool__(self):
32
+ 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
@@ -1,6 +1,36 @@
1
1
  # safe_import_utils.py
2
2
  from ..imports import *
3
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
+
4
34
 
5
35
  def ensure_import_pkg_js(import_pkg_js,file_path=None):
6
36
  import_pkg_js = import_pkg_js or {"context":{}}
@@ -79,18 +109,42 @@ def ensure_caller_package(caller_file: str, caller_globals: dict | None = None)
79
109
  if caller_globals and caller_globals.get("__name__") == "__main__" and not caller_globals.get("__package__"):
80
110
  caller_globals["__package__"] = pkg
81
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
82
134
  def get_import_pkg(line):
83
135
  if is_line_group_import(line):
84
136
  return clean_line(line.split(FROM_TAG)[1].split(IMPORT_TAG)[0])
85
137
  def get_imports_from_import_pkg(line):
86
138
  if is_line_group_import(line):
87
139
  return get_cleaned_import_list(line,commaClean=True)
88
- def add_imports_to_import_pkg_js(import_pkg,imports,import_pkg_js=None):
140
+ def add_imports_to_import_pkg_js(import_pkg,imports,import_pkg_js=None,file_path=None):
89
141
  import_pkg_js = ensure_import_pkg_js(import_pkg_js)
90
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)
91
145
  if import_pkg not in import_pkg_js:
92
146
  i = len(import_pkg_js["context"]["nulines"])
93
- import_pkg_js[import_pkg]={"imports":imports,"line":i}
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)}
94
148
  import_line = f"from {import_pkg} import "
95
149
  if import_pkg == "import":
96
150
  import_line = IMPORT_TAG
@@ -98,12 +152,12 @@ def add_imports_to_import_pkg_js(import_pkg,imports,import_pkg_js=None):
98
152
  else:
99
153
  import_pkg_js[import_pkg]["imports"]+=imports
100
154
  return import_pkg_js
101
- def update_import_pkg_js(line,import_pkg_js=None):
155
+ def update_import_pkg_js(line,import_pkg_js=None,file_path=None):
102
156
  import_pkg_js = ensure_import_pkg_js(import_pkg_js)
103
157
  if is_line_group_import(line):
104
158
  import_pkg = get_import_pkg(line)
105
159
  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)
160
+ import_pkg_js = add_imports_to_import_pkg_js(import_pkg,imports,import_pkg_js=import_pkg_js,file_path=file_path)
107
161
  else:
108
162
  if len(import_pkg_js["context"]["nulines"]) >0 and line == '' and is_line_import(import_pkg_js["context"]["nulines"][-1]):
109
163
  pass
@@ -1,6 +1,9 @@
1
1
  from ..imports import *
2
2
  from .dot_utils import *
3
-
3
+ from .extract_utils import get_all_py_file_paths
4
+ def get_py_files(directory=None):
5
+ directory = directory or get_initial_caller_dir()
6
+ return get_all_py_file_paths(directory,add=True)
4
7
  def ensure_on_path(p: Path):
5
8
  s = str(p)
6
9
  if s not in sys.path:
@@ -19,7 +22,59 @@ def get_dot_range_sysroot(filepath):
19
22
  sysroot = get_sysroot(sysroot,dot_range)
20
23
 
21
24
  return sysroot
25
+ def get_import_with_sysroot(file_path, sysroot):
26
+ """
27
+ Rewrite imports like:
28
+ from imports.constants import *
29
+ into:
30
+ from <relative_path>.imports.constants import *
31
+ Where <relative_path> is computed relative to sysroot.
32
+ """
33
+
34
+
35
+ # Absolute paths
36
+ file_dir = os.path.dirname(os.path.abspath(file_path))
37
+ sysroot = os.path.abspath(sysroot)
38
+
39
+ # Compute relative path
40
+ relpath = os.path.relpath(file_dir, sysroot)
41
+
42
+ bare_rel = eatAll(relpath,'.')
43
+
44
+ # Turn filesystem path into dotted python path
45
+ if relpath == ".":
46
+ dotted = ""
47
+ else:
48
+ dotted = ".".join(part for part in relpath.split(os.sep) if part)
49
+ if bare_rel.startswith('/') and dotted.startswith('.'):
50
+ dotted = dotted[1:]
51
+
52
+
53
+ # Build final rewritten import
54
+ return dotted
55
+ def get_all_sysroots(files):
56
+ sysroots=[]
57
+ for glo in files:
58
+ imp = compute_dotted_and_sysroot(glo)
59
+ sysroots.append(imp[-1])
60
+ return sysroots
61
+ def get_shortest_sysroot(files):
62
+ sysroots = get_all_sysroots(files)
63
+ return get_shortest_path(*sysroots)
64
+ def get_all_py_sysroots(directory=None,files=None):
65
+ py_files = files or get_py_files(directory=directory)
66
+ return [compute_dotted_and_sysroot(glo)[1] for glo in py_files]
22
67
 
68
+ def get__imports(directory=None, sysroot=None):
69
+ directory = directory or get_caller_dir(1)
70
+ globs = collect_globs(directory, allowed_exts='.py')
71
+ globs = [glo for glo in globs.get('files') if glo]
72
+ sysroots = [compute_dotted_and_sysroot(glo)[1] for glo in globs]
73
+ # ⭐ Get unified monorepo root
74
+ monorepo_root = get_common_root(sysroots)
75
+ if str(monorepo_root) not in sys.path:
76
+ sys.path.insert(0, str(monorepo_root))
77
+ get_all_imports(directory=directory, sysroot=monorepo_root)
23
78
  def is_import_or_init(sysroot,likely=None):
24
79
  file_data = get_file_parts(sysroot)
25
80
  nuroot = sysroot
@@ -1,14 +1,14 @@
1
1
  from __future__ import annotations
2
2
  import re,pexpect,shlex,ezodf,tiktoken,geopandas as gpd,os,PyPDF2,json,tempfile,requests
3
3
  import textwrap,pdfplumber,math,hashlib,pandas as pd,platform,textwrap as tw,glob,asyncio
4
- import fnmatch,importlib,shutil,sys,time,threading,posixpath,importlib.util,types
4
+ import fnmatch,importlib,shutil,sys,time,threading,posixpath,importlib.util,types, logging
5
5
  import subprocess,pytesseract,queue,logging,functools,pathlib,pkgutil,inspect
6
6
  from typing import *
7
7
  from datetime import timedelta,datetime
8
8
  from flask import jsonify
9
9
  from logging.handlers import RotatingFileHandler
10
10
  from pathlib import Path
11
- from functools import reduce
11
+ from functools import reduce,lru_cache
12
12
  from types import MethodType,ModuleType
13
13
  from werkzeug.utils import secure_filename
14
14
  from werkzeug.datastructures import FileStorage
@@ -16,3 +16,4 @@ from pdf2image import convert_from_path # only used for OCR fallback
16
16
  from dataclasses import dataclass,field,asdict
17
17
  from pprint import pprint
18
18
  from dotenv import load_dotenv
19
+ from types import MethodType
@@ -26,6 +26,13 @@ from .imports import *
26
26
 
27
27
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
28
28
  logger = logging.getLogger(__name__)
29
+ def get_keys(mapping,typ=None):
30
+ typ = typ or set
31
+ if isinstance(mapping,dict):
32
+ mapping = mapping.keys()
33
+ return typ(mapping)
34
+ def make_key_map(dict_obj):
35
+ return {k:get_keys(v) for k,v in dict_obj.items()}
29
36
  def convert_and_normalize_values(values):
30
37
  for value in values:
31
38
  if isinstance(value, str):
@@ -116,10 +123,11 @@ def safe_dump_to_file(data, file_path=None, ensure_ascii=False, indent=4, *args,
116
123
  else:
117
124
  logger.error("file_path and data must be provided to safe_dump_to_file")
118
125
 
119
- def safe_read_from_json(*args,**kwargs):
126
+ def safe_read_from_json(file_path,*args,**kwargs):
120
127
  is_read=True
121
- file_path = args[0]
122
- valid_file_path = get_file_path(*args,is_read=is_read,**kwargs)
128
+
129
+
130
+ valid_file_path = get_file_path(file_path,*args,is_read=is_read,**kwargs)
123
131
  if valid_file_path:
124
132
  file_path = valid_file_path
125
133
  try:
@@ -1,41 +1,90 @@
1
1
  from .imports import *
2
- def get_logFile(bpName: str = None, maxBytes: int = 100_000, backupCount: int = 3) -> logging.Logger:
2
+ import os, sys, inspect, logging
3
+ from logging.handlers import RotatingFileHandler
4
+
5
+ PACKAGE_NAME = "abstract_utilities" # ← update if needed
6
+
7
+
8
+ def _resolve_log_root():
9
+ """
10
+ Returns a safe writable logging directory depending on environment:
11
+ - If running in a virtualenv → <venv>/.logs/<package>
12
+ - Else if user writable → ~/.cache/<package>/logs
13
+ - Else → /var/log/<package>
14
+ """
15
+ # 1) Virtualenv or Conda environment
16
+ venv = os.getenv("VIRTUAL_ENV") or os.getenv("CONDA_PREFIX")
17
+ if venv:
18
+ root = os.path.join(venv, ".logs", PACKAGE_NAME)
19
+ os.makedirs(root, exist_ok=True)
20
+ return root
21
+
22
+ # 2) User home cache folder
23
+ home = os.path.expanduser("~")
24
+ user_cache_root = os.path.join(home, ".cache", PACKAGE_NAME, "logs")
25
+ try:
26
+ os.makedirs(user_cache_root, exist_ok=True)
27
+ return user_cache_root
28
+ except PermissionError:
29
+ pass
30
+
31
+ # 3) Last resort: system log dir (requires correct service user permissions)
32
+ system_root = f"/var/log/{PACKAGE_NAME}"
33
+ try:
34
+ os.makedirs(system_root, exist_ok=True)
35
+ return system_root
36
+ except PermissionError:
37
+ # Fail-safe fallback to /tmp
38
+ fallback = f"/tmp/{PACKAGE_NAME}/logs"
39
+ os.makedirs(fallback, exist_ok=True)
40
+ return fallback
41
+
42
+
43
+ LOG_ROOT = _resolve_log_root()
44
+
45
+
46
+ def get_logFile(bpName=None, maxBytes=100_000, backupCount=3):
3
47
  """
4
- If bpName is None, use the “caller module’s basename” as the logger name.
5
- Otherwise, use the explicitly provided bpName.
48
+ A logger that always writes to a safe OS-appropriate path.
49
+ Works even when installed through pip.
6
50
  """
7
51
  if bpName is None:
8
- # Find the first frame outside logging_utils.py
9
52
  frame_idx = _find_caller_frame_index()
10
53
  frame_info = inspect.stack()[frame_idx]
11
- caller_path = frame_info.filename # e.g. "/home/joe/project/app/routes.py"
54
+ caller_path = frame_info.filename
12
55
  bpName = os.path.splitext(os.path.basename(caller_path))[0]
13
56
  del frame_info
14
57
 
15
- log_dir = mkdirs("logs")
16
- log_path = os.path.join(log_dir, f"{bpName}.log")
17
-
18
- logger = logging.getLogger(bpName)
58
+ logger = logging.getLogger(f"{PACKAGE_NAME}.{bpName}")
19
59
  logger.setLevel(logging.INFO)
20
60
 
21
61
  if not logger.handlers:
22
- try:
23
- handler = RotatingFileHandler(log_path, maxBytes=maxBytes, backupCount=backupCount)
24
- handler.setLevel(logging.INFO)
25
-
26
- fmt = "%(asctime)s - %(levelname)s - %(pathname)s - %(message)s"
27
- formatter = logging.Formatter(fmt)
28
- handler.setFormatter(formatter)
29
- logger.addHandler(handler)
30
-
31
- console = logging.StreamHandler()
32
- console.setLevel(logging.INFO)
33
- console.setFormatter(formatter)
34
- logger.addHandler(console)
35
- except Exception as e:
36
- print(f"{e}")
62
+ log_file = os.path.join(LOG_ROOT, f"{bpName}.log")
63
+ handler = RotatingFileHandler(log_file, maxBytes=maxBytes, backupCount=backupCount)
64
+
65
+ fmt = "%(asctime)s - %(levelname)s - %(pathname)s:%(lineno)d - %(message)s"
66
+ formatter = logging.Formatter(fmt)
67
+ handler.setFormatter(formatter)
68
+
69
+ logger.addHandler(handler)
70
+
71
+ # Console handler (optional; can disable for gunicorn)
72
+ console = logging.StreamHandler(sys.stdout)
73
+ console.setFormatter(formatter)
74
+ logger.addHandler(console)
75
+
37
76
  return logger
38
77
 
78
+
79
+ def _find_caller_frame_index():
80
+ """Find the correct caller module outside this logger."""
81
+ for idx, frame_info in enumerate(inspect.stack()):
82
+ if idx == 0:
83
+ continue
84
+ module = inspect.getmodule(frame_info.frame)
85
+ if module and module.__name__ not in (__name__, "logging"):
86
+ return idx
87
+ return 1
39
88
  def _find_caller_frame_index():
40
89
  """
41
90
  Scan up the call stack until we find a frame whose module is NOT logging_utils.
@@ -64,6 +64,29 @@ def detect_language_from_text(text: str):
64
64
 
65
65
  likely = [lang for lang, score in scores.items() if score == max_score]
66
66
  return likely[0] if len(likely) == 1 else 'uncertain'
67
+ def get_tripple_string(string):
68
+ nustring = ''
69
+ for i in range(3):
70
+ nustring +=string
71
+ return nustring
72
+ def get_within_quotes(text,quotes=None):
73
+ quotes_strings = quotes or ["'",'"']
74
+ in_quotes = []
75
+ for quotes_string in quotes_strings:
76
+ if not isinstance(quotes_string,list):
77
+ tripple= get_tripple_string(quotes_string)
78
+ texts = [text]
79
+ if tripple in text:
80
+ texts= text.split(tripple)
81
+ for text_part in texts:
82
+ quote_count = len(text_part) - len(text_part.replace(quotes_string,''))
83
+ quote_spl = text_part.split(quotes_string)
84
+ in_quotes+=[quote_spl[i] for i in range(quote_count) if ((i == 1 or i%2 != float(0)) and len(quote_spl) > i)]
85
+ else:
86
+ texts= text.split(quotes_string[0])
87
+ for text in texts:
88
+ in_quotes.append(text.split(quotes_string[1])[0])
89
+ return in_quotes
67
90
 
68
91
  def search_code(code_languages, parts):
69
92
  return [data for datas in parts for data in make_list(datas)
@@ -2,7 +2,7 @@ from ...string_utils import eatAll
2
2
  from ...list_utils import make_list
3
3
  from ...type_utils import get_media_exts, is_media_type,MIME_TYPES
4
4
  from ...safe_utils import safe_join,get_slash
5
- from ...class_utils import get_caller_path,get_caller_dir
5
+ from ...class_utils import get_caller_path,get_caller_dir,get_initial_caller,get_initial_caller_dir
6
6
  from ...file_utils import is_file,is_dir,is_exists
7
7
  from ...ssh_utils import is_file,is_dir,is_exists
8
8
  from ...directory_utils import *