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,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,6 @@ 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
20
+ from datetime import datetime, date
21
+ from decimal import Decimal
@@ -1,2 +1,2 @@
1
- from ...imports import json,re,os,logging
1
+ from ...imports import *
2
2
  from typing import *
@@ -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:
@@ -741,3 +749,29 @@ def flatten_json(data, parent_key='', sep='_'):
741
749
  items.append((parent_key, data))
742
750
 
743
751
  return dict(items)
752
+
753
+
754
+ def to_json_safe(obj):
755
+ if obj is None:
756
+ return None
757
+
758
+ if isinstance(obj, (str, int, float, bool)):
759
+ return obj
760
+
761
+ if isinstance(obj, (datetime, date)):
762
+ return obj.isoformat()
763
+
764
+ if isinstance(obj, Decimal):
765
+ return float(obj)
766
+
767
+ if isinstance(obj, Path):
768
+ return str(obj)
769
+
770
+ if isinstance(obj, dict):
771
+ return {k: to_json_safe(v) for k, v in obj.items()}
772
+
773
+ if isinstance(obj, (list, tuple, set)):
774
+ return [to_json_safe(v) for v in obj]
775
+
776
+ # Fallback (yt-dlp objects, enums, etc.)
777
+ return str(obj)
@@ -197,3 +197,6 @@ def make_list(obj:any) -> list:
197
197
  def make_list_it(obj=None):
198
198
  obj = make_list(obj or [])
199
199
  return obj
200
+
201
+ def get_single_from_list(list_obj,default=None):
202
+ return make_list(list_obj or default)[0]
@@ -1,42 +1,145 @@
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
+ import logging
5
+ from pathlib import Path
6
+ PACKAGE_NAME = "abstract_utilities" # ← update if needed
7
+
8
+
9
+ def _resolve_log_root():
3
10
  """
4
- If bpName is None, use the “caller module’s basename” as the logger name.
5
- Otherwise, use the explicitly provided bpName.
11
+ Returns a safe writable logging directory depending on environment:
12
+ - If running in a virtualenv → <venv>/.logs/<package>
13
+ - Else if user writable → ~/.cache/<package>/logs
14
+ - Else → /var/log/<package>
6
15
  """
7
- if bpName is None:
8
- # Find the first frame outside logging_utils.py
9
- frame_idx = _find_caller_frame_index()
10
- frame_info = inspect.stack()[frame_idx]
11
- caller_path = frame_info.filename # e.g. "/home/joe/project/app/routes.py"
12
- bpName = os.path.splitext(os.path.basename(caller_path))[0]
13
- del frame_info
14
-
15
-
16
-
17
- logger = logging.getLogger(bpName)
18
- logger.setLevel(logging.INFO)
19
-
20
- if not logger.handlers:
21
- try:
22
- log_dir = mkdirs("logs")
23
- log_path = os.path.join(log_dir, f"{bpName}.log")
24
- handler = RotatingFileHandler(log_path, maxBytes=maxBytes, backupCount=backupCount)
25
- handler.setLevel(logging.INFO)
26
-
27
- fmt = "%(asctime)s - %(levelname)s - %(pathname)s - %(message)s"
28
- formatter = logging.Formatter(fmt)
29
- handler.setFormatter(formatter)
30
- logger.addHandler(handler)
31
-
32
- console = logging.StreamHandler()
33
- console.setLevel(logging.INFO)
34
- console.setFormatter(formatter)
35
- logger.addHandler(console)
36
- except Exception as e:
37
- print(f"{e}")
16
+ # 1) Virtualenv or Conda environment
17
+ venv = os.getenv("VIRTUAL_ENV") or os.getenv("CONDA_PREFIX")
18
+ if venv:
19
+ root = os.path.join(venv, ".logs", PACKAGE_NAME)
20
+ os.makedirs(root, exist_ok=True)
21
+ return root
22
+
23
+ # 2) User home cache folder
24
+ home = os.path.expanduser("~")
25
+ user_cache_root = os.path.join(home, ".cache", PACKAGE_NAME, "logs")
26
+ try:
27
+ os.makedirs(user_cache_root, exist_ok=True)
28
+ return user_cache_root
29
+ except PermissionError:
30
+ pass
31
+
32
+ # 3) Last resort: system log dir (requires correct service user permissions)
33
+ system_root = f"/var/log/{PACKAGE_NAME}"
34
+ try:
35
+ os.makedirs(system_root, exist_ok=True)
36
+ return system_root
37
+ except PermissionError:
38
+ # Fail-safe fallback to /tmp
39
+ fallback = f"/tmp/{PACKAGE_NAME}/logs"
40
+ os.makedirs(fallback, exist_ok=True)
41
+ return fallback
42
+
43
+
44
+ LOG_ROOT = _resolve_log_root()
45
+
46
+
47
+ ##def get_logFile(bpName=None, maxBytes=100_000, backupCount=3):
48
+ ## """
49
+ ## A logger that always writes to a safe OS-appropriate path.
50
+ ## Works even when installed through pip.
51
+ ## """
52
+ ## if bpName is None:
53
+ ## frame_idx = _find_caller_frame_index()
54
+ ## frame_info = inspect.stack()[frame_idx]
55
+ ## caller_path = frame_info.filename
56
+ ## bpName = os.path.splitext(os.path.basename(caller_path))[0]
57
+ ## del frame_info
58
+ ##
59
+ ## logger = logging.getLogger(f"{PACKAGE_NAME}.{bpName}")
60
+ ## logger.setLevel(logging.INFO)
61
+ ##
62
+ ## if not logger.handlers:
63
+ ## log_file = os.path.join(LOG_ROOT, f"{bpName}.log")
64
+ ## handler = RotatingFileHandler(log_file, maxBytes=maxBytes, backupCount=backupCount)
65
+ ##
66
+ ## fmt = "%(asctime)s - %(levelname)s - %(pathname)s:%(lineno)d - %(message)s"
67
+ ## formatter = logging.Formatter(fmt)
68
+ ## handler.setFormatter(formatter)
69
+ ##
70
+ ## logger.addHandler(handler)
71
+ ##
72
+ ## # Console handler (optional; can disable for gunicorn)
73
+ ## console = logging.StreamHandler(sys.stdout)
74
+ ## console.setFormatter(formatter)
75
+ ## logger.addHandler(console)
76
+ ##
77
+ ## return logger
78
+ LOG_FORMAT = (
79
+ "[%(asctime)s] "
80
+ "%(levelname)-8s "
81
+ "%(name)s:%(lineno)d | "
82
+ "%(message)s"
83
+ )
84
+
85
+ DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
86
+
87
+
88
+
89
+
90
+ def get_logFile(
91
+ name: str,
92
+ log_dir: str | Path = "logs",
93
+ level: int = logging.INFO,
94
+ console: bool = True,
95
+ max_bytes: int = 5 * 1024 * 1024,
96
+ backup_count: int = 5,
97
+ ):
98
+ logger = logging.getLogger(name)
99
+
100
+ if logger.handlers:
101
+ return logger
102
+
103
+ logger.setLevel(level)
104
+
105
+ formatter = logging.Formatter(LOG_FORMAT, DATE_FORMAT)
106
+
107
+ try:
108
+ log_dir = Path(log_dir)
109
+ log_dir.mkdir(parents=True, exist_ok=True)
110
+
111
+ file_handler = RotatingFileHandler(
112
+ log_dir / f"{name}.log",
113
+ maxBytes=max_bytes,
114
+ backupCount=backup_count,
115
+ encoding="utf-8",
116
+ )
117
+ file_handler.setFormatter(formatter)
118
+ logger.addHandler(file_handler)
119
+
120
+ except PermissionError:
121
+ # 🔒 Import-safe fallback
122
+ logger.addHandler(logging.NullHandler())
123
+
124
+ if console:
125
+ console_handler = logging.StreamHandler()
126
+ console_handler.setFormatter(formatter)
127
+ logger.addHandler(console_handler)
128
+
129
+ logger.propagate = False
38
130
  return logger
39
131
 
132
+
133
+
134
+ def _find_caller_frame_index():
135
+ """Find the correct caller module outside this logger."""
136
+ for idx, frame_info in enumerate(inspect.stack()):
137
+ if idx == 0:
138
+ continue
139
+ module = inspect.getmodule(frame_info.frame)
140
+ if module and module.__name__ not in (__name__, "logging"):
141
+ return idx
142
+ return 1
40
143
  def _find_caller_frame_index():
41
144
  """
42
145
  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 *
@@ -107,17 +107,6 @@ def path_join(*paths, isfile=False):
107
107
  os.makedirs(final_path, exist_ok=True)
108
108
  return final_path
109
109
 
110
- def is_file(*paths):
111
- item_path = os.path.join(*paths)
112
- return os.path.isfile(item_path)
113
-
114
- def is_dir(*paths):
115
- item_path = os.path.join(*paths)
116
- return os.path.isdir(item_path)
117
-
118
- def is_path(*paths):
119
- item_path = os.path.join(*paths)
120
- return item_path if os.path.exists(item_path) else None
121
110
 
122
111
  def get_all_directories(directory):
123
112
  dir_list = os.listdir(directory)
@@ -132,7 +121,7 @@ def get_all_files(directory=None):
132
121
 
133
122
  def get_all_items(directory):
134
123
  dir_list = os.listdir(directory)
135
- file_list = [item for item in dir_list if is_path(directory,item)]
124
+ file_list = [item for item in dir_list if is_exists(directory,item)]
136
125
  return file_list
137
126
 
138
127
 
@@ -176,6 +165,7 @@ def get_safe_splitext(path=None,basename=None):
176
165
  basename_str = str(basename)
177
166
  filename,ext = os.path.splitext(basename_str)
178
167
  return filename,ext
168
+ return None,None
179
169
  def get_safe_filename(path=None,basename=None):
180
170
  filename,_ = get_safe_splitext(path=path,basename=basename)
181
171
  return filename
@@ -223,6 +213,11 @@ def create_base_dir(directory=None, child=None):
223
213
 
224
214
 
225
215
 
216
+ def get_abs_path(path,i=None):
217
+ abs_dir = get_initial_caller_dir()
218
+ return os.path.join(abs_dir,path)
219
+
220
+
226
221
 
227
222
  def get_file_parts(path):
228
223
  if path:
@@ -1,2 +1,2 @@
1
1
  from ...imports import os,shlex
2
-
2
+ import base64
@@ -15,6 +15,38 @@ Usage:
15
15
  from .imports import *
16
16
  _FILE_PATH_KEYS = ['file', 'filepath', 'file_path', 'path', 'directory', 'f', 'dst', 'dest']
17
17
  _CONTENTS_KEYS = ['cont', 'content', 'contents', 'data', 'datas', 'dat', 'src', 'source']
18
+ from pathlib import Path
19
+ import uuid
20
+ import shlex
21
+
22
+ _STAGE_ROOT = Path("/var/tmp/abstract_stage")
23
+
24
+
25
+ def _stage_file(contents: str, suffix=".tmp") -> Path:
26
+ """
27
+ Write contents to a local staging file.
28
+ """
29
+ _STAGE_ROOT.mkdir(parents=True, exist_ok=True)
30
+ path = _STAGE_ROOT / f"{uuid.uuid4().hex}{suffix}"
31
+ path.write_text(str(contents), encoding="utf-8")
32
+ return path
33
+
34
+
35
+ def _install_file(staged: Path, dest: str, **kwargs) -> str:
36
+ """
37
+ Atomically install a staged file to destination using sudo install.
38
+ """
39
+ cmd = (
40
+ f"sudo install -D -m 0644 "
41
+ f"{shlex.quote(str(staged))} "
42
+ f"{shlex.quote(dest)}"
43
+ )
44
+ return run_local_cmd(
45
+ cmd=cmd,
46
+ password=kwargs.get("password"),
47
+ key=kwargs.get("key"),
48
+ env_path=kwargs.get("env_path"),
49
+ )
18
50
 
19
51
 
20
52
  # --- Helper utilities --------------------------------------------------------
@@ -194,45 +226,114 @@ def write_to_path(
194
226
  ## f.write(str(contents))
195
227
  ## return file_path
196
228
  # --- Core functionality -------------------------------------------------------
197
- def write_to_file(*args, **kwargs):
229
+ ##def write_to_file(*args, **kwargs):
230
+ ## """
231
+ ## Write contents to a file (create if missing).
232
+ ##
233
+ ## Returns the file_path written.
234
+ ## """
235
+ ## file_path, contents = check_read_write_params(*args, **kwargs)
236
+ ## values,kwargs = get_from_kwargs(['file_path','contents'],del_kwarg=True,**kwargs)
237
+ ## dirname = os.path.dirname(file_path)
238
+ ##
239
+ ## if contents is None:
240
+ ## raise ValueError("Missing contents to write.")
241
+ ## user_at_host = kwargs.get("user_at_host")
242
+ ## if get_user_pass_host_key(**kwargs):
243
+ ## make_dirs(dirname, exist_ok=True,**kwargs)
244
+ ## kwargs["cwd"] = kwargs.get('cwd') or os.path.dirname(file_path)
245
+ ## # sanitize for shell safety
246
+ ## quoted_path = shlex.quote(file_path)
247
+ ## quoted_data = shlex.quote(str(contents))
248
+ ## # shell command that fully overwrites
249
+ ## # (no append, replaces contents entirely)
250
+ ## kwargs["cmd"] = f'sh -c "echo {quoted_data} > {quoted_path}"'
251
+ ## if not kwargs.get('password') and not kwargs.get('key'):
252
+ ## kwargs["cmd"]=f'sudo {kwargs["cmd"]}'
253
+ ## result = run_pruned_func(run_cmd,**kwargs)
254
+ ## if 'file_path' in kwargs:
255
+ ## del kwargs['file_path']
256
+ ## if not is_file(file_path,**kwargs) or str(contents) != read_from_file(file_path,**kwargs):
257
+ ## kwargs["cmd"]=f'sudo {kwargs["cmd"]}'
258
+ ## result = run_pruned_func(run_cmd,**kwargs)
259
+ ## return result
260
+ ##
261
+ ## make_dirs(dirname or ".", exist_ok=True)
262
+ ## with open(file_path, "w", encoding="utf-8") as f:
263
+ ## f.write(str(contents))
264
+ ## return file_path
265
+
266
+ def _should_use_remote(**kwargs) -> bool:
198
267
  """
199
- Write contents to a file (create if missing).
268
+ Only use remote mode IF:
269
+ - user_at_host is provided
270
+ - AND password/key is provided
271
+ Otherwise: local write.
272
+ """
273
+ user = kwargs.get("user_at_host")
274
+ if not user:
275
+ return False # not remote
276
+
277
+ # If user_at_host is provided, then password or key MUST be present
278
+ if kwargs.get("password") or kwargs.get("key"):
279
+ return True
280
+
281
+ return False # user provided but no auth → treat as local
200
282
 
201
- Returns the file_path written.
283
+
284
+ def _write_to_file(contents: str, file_path: str, **kwargs) -> str:
202
285
  """
203
- file_path, contents = check_read_write_params(*args, **kwargs)
204
- values,kwargs = get_from_kwargs(['file_path','contents'],del_kwarg=True,**kwargs)
205
- dirname = os.path.dirname(file_path)
206
-
207
- if contents is None:
208
- raise ValueError("Missing contents to write.")
209
- user_at_host = kwargs.get("user_at_host")
210
- if get_user_pass_host_key(**kwargs):
211
- make_dirs(dirname, exist_ok=True,**kwargs)
212
- kwargs["cwd"] = kwargs.get('cwd') or os.path.dirname(file_path)
213
- # sanitize for shell safety
214
- quoted_path = shlex.quote(file_path)
215
- quoted_data = shlex.quote(str(contents))
216
- # shell command that fully overwrites
217
- # (no append, replaces contents entirely)
218
- kwargs["cmd"] = f'sh -c "echo {quoted_data} > {quoted_path}"'
219
- if not kwargs.get('password') and not kwargs.get('key'):
220
- kwargs["cmd"]=f'sudo {kwargs["cmd"]}'
221
- result = run_pruned_func(run_cmd,**kwargs)
222
- if 'file_path' in kwargs:
223
- del kwargs['file_path']
224
- if not is_file(file_path,**kwargs) or str(contents) != read_from_file(file_path,**kwargs):
225
- kwargs["cmd"]=f'sudo {kwargs["cmd"]}'
226
- result = run_pruned_func(run_cmd,**kwargs)
227
- return result
228
-
229
- make_dirs(dirname or ".", exist_ok=True)
230
- with open(file_path, "w", encoding="utf-8") as f:
231
- f.write(str(contents))
232
- return file_path
233
-
234
-
235
- def read_from_file(file_path,**kwargs):
286
+ Unified writer using stage → install model.
287
+ """
288
+
289
+ remote = _should_use_remote(**kwargs)
290
+
291
+ # --- Remote path (unchanged conceptually) ---
292
+ if remote:
293
+ tmp_path = _stage_file(contents)
294
+
295
+ user_at_host = kwargs["user_at_host"]
296
+ password = kwargs.get("password")
297
+ key = kwargs.get("key")
298
+
299
+ # copy staged file
300
+ scp_cmd = (
301
+ f"scp {shlex.quote(str(tmp_path))} "
302
+ f"{shlex.quote(user_at_host)}:{shlex.quote(file_path)}"
303
+ )
304
+ return run_pruned_func(
305
+ run_local_cmd,
306
+ cmd=scp_cmd,
307
+ password=password,
308
+ key=key,
309
+ **kwargs
310
+ )
311
+
312
+ # --- Local path ---
313
+ try:
314
+ # Attempt direct write for non-privileged paths
315
+ os.makedirs(os.path.dirname(file_path) or ".", exist_ok=True)
316
+ with open(file_path, "w", encoding="utf-8") as f:
317
+ f.write(str(contents))
318
+ return file_path
319
+
320
+ except (PermissionError, FileNotFoundError):
321
+ # Privileged path → stage + install
322
+ staged = _stage_file(contents)
323
+ return _install_file(staged, file_path, **kwargs)
324
+
325
+
326
+
327
+ def write_to_file(*, contents: str, file_path: str, **kwargs):
328
+ """
329
+ Error-handled public writer.
330
+ """
331
+ try:
332
+ return _write_to_file(contents=contents, file_path=file_path, **kwargs)
333
+ except Exception as e:
334
+ print("WRITE ERROR:", e)
335
+ raise RuntimeError(f"Failed writing: {file_path}")
336
+ def read_from_file(file_path=None,**kwargs):
236
337
  if get_user_pass_host_key(**kwargs):
237
338
  kwargs["cwd"] = kwargs.get('cwd') or os.path.dirname(file_path)
238
339
  basename = os.path.basename(file_path)
@@ -1,3 +1,7 @@
1
+ from .imports import *
1
2
  from .alpha_utils import *
2
3
  from .num_utils import *
3
- from .type_utils import *
4
+ from .is_type import *
5
+ from .make_type import *
6
+ from .get_type import *
7
+ from .mime_types import *