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.
- abstract_utilities/__init__.py +18 -6
- abstract_utilities/class_utils/abstract_classes.py +104 -34
- abstract_utilities/class_utils/caller_utils.py +39 -0
- abstract_utilities/class_utils/global_utils.py +35 -21
- abstract_utilities/class_utils/imports/imports.py +1 -1
- abstract_utilities/directory_utils/src/directory_utils.py +2 -0
- abstract_utilities/file_utils/imports/classes.py +59 -55
- abstract_utilities/file_utils/imports/module_imports.py +2 -2
- abstract_utilities/file_utils/src/file_filters/__init__.py +0 -3
- abstract_utilities/file_utils/src/file_filters/ensure_utils.py +382 -8
- abstract_utilities/file_utils/src/file_filters/filter_params.py +64 -0
- abstract_utilities/file_utils/src/file_filters/predicate_utils.py +21 -91
- abstract_utilities/file_utils/src/find_collect.py +10 -0
- abstract_utilities/file_utils/src/initFunctionsGen.py +36 -23
- abstract_utilities/file_utils/src/initFunctionsGens.py +280 -0
- abstract_utilities/import_utils/imports/__init__.py +1 -1
- abstract_utilities/import_utils/imports/init_imports.py +3 -0
- abstract_utilities/import_utils/imports/module_imports.py +2 -1
- abstract_utilities/import_utils/imports/utils.py +1 -1
- abstract_utilities/import_utils/src/__init__.py +1 -0
- abstract_utilities/import_utils/src/extract_utils.py +2 -2
- abstract_utilities/import_utils/src/import_functions.py +33 -14
- abstract_utilities/import_utils/src/import_utils.py +39 -0
- abstract_utilities/import_utils/src/layze_import_utils/__init__.py +2 -0
- abstract_utilities/import_utils/src/layze_import_utils/lazy_utils.py +41 -0
- abstract_utilities/import_utils/src/layze_import_utils/nullProxy.py +37 -0
- abstract_utilities/import_utils/src/nullProxy.py +30 -0
- abstract_utilities/import_utils/src/sysroot_utils.py +1 -1
- abstract_utilities/imports.py +5 -2
- abstract_utilities/json_utils/imports/imports.py +1 -1
- abstract_utilities/json_utils/json_utils.py +37 -3
- abstract_utilities/list_utils/list_utils.py +3 -0
- abstract_utilities/log_utils/log_file.py +137 -34
- abstract_utilities/parse_utils/parse_utils.py +23 -0
- abstract_utilities/path_utils/imports/module_imports.py +1 -1
- abstract_utilities/path_utils/path_utils.py +7 -12
- abstract_utilities/read_write_utils/imports/imports.py +1 -1
- abstract_utilities/read_write_utils/read_write_utils.py +137 -36
- abstract_utilities/type_utils/__init__.py +5 -1
- abstract_utilities/type_utils/get_type.py +120 -0
- abstract_utilities/type_utils/imports/__init__.py +1 -0
- abstract_utilities/type_utils/imports/constants.py +134 -0
- abstract_utilities/type_utils/imports/module_imports.py +25 -1
- abstract_utilities/type_utils/is_type.py +455 -0
- abstract_utilities/type_utils/make_type.py +126 -0
- abstract_utilities/type_utils/mime_types.py +68 -0
- abstract_utilities/type_utils/type_utils.py +0 -877
- {abstract_utilities-0.2.2.583.dist-info → abstract_utilities-0.2.2.700.dist-info}/METADATA +1 -1
- {abstract_utilities-0.2.2.583.dist-info → abstract_utilities-0.2.2.700.dist-info}/RECORD +51 -40
- {abstract_utilities-0.2.2.583.dist-info → abstract_utilities-0.2.2.700.dist-info}/WHEEL +0 -0
- {abstract_utilities-0.2.2.583.dist-info → abstract_utilities-0.2.2.700.dist-info}/top_level.txt +0 -0
abstract_utilities/imports.py
CHANGED
|
@@ -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
|
|
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(
|
|
126
|
+
def safe_read_from_json(file_path,*args,**kwargs):
|
|
120
127
|
is_read=True
|
|
121
|
-
|
|
122
|
-
|
|
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)
|
|
@@ -1,42 +1,145 @@
|
|
|
1
1
|
from .imports import *
|
|
2
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
283
|
+
|
|
284
|
+
def _write_to_file(contents: str, file_path: str, **kwargs) -> str:
|
|
202
285
|
"""
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
kwargs["
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
#
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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)
|