abstract-utilities 0.2.2.448__py3-none-any.whl → 0.2.2.449__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 (57) hide show
  1. abstract_utilities/__init__.py +43 -17
  2. abstract_utilities/abstract_classes.py +0 -49
  3. abstract_utilities/class_utils.py +3 -39
  4. abstract_utilities/cmd_utils/user_utils.py +1 -1
  5. abstract_utilities/{compare_utils/compare_utils.py → compare_utils.py} +1 -1
  6. abstract_utilities/dynimport.py +15 -7
  7. abstract_utilities/json_utils.py +0 -35
  8. abstract_utilities/log_utils.py +3 -14
  9. abstract_utilities/path_utils.py +6 -90
  10. abstract_utilities/read_write_utils.py +156 -99
  11. abstract_utilities/robust_reader/__init__.py +1 -1
  12. abstract_utilities/{file_utils/file_utils → robust_reader}/file_reader.py +19 -5
  13. abstract_utilities/{file_utils/file_utils → robust_reader}/pdf_utils.py +9 -1
  14. abstract_utilities/robust_readers/__init__.py +1 -0
  15. abstract_utilities/{file_utils/file_utils/file_utils.py → robust_readers/file_filters.py} +1 -2
  16. abstract_utilities/{file_utils/file_utils → robust_readers}/filter_params.py +38 -1
  17. abstract_utilities/robust_readers/initFuncGen.py +74 -82
  18. abstract_utilities/type_utils.py +1 -0
  19. {abstract_utilities-0.2.2.448.dist-info → abstract_utilities-0.2.2.449.dist-info}/METADATA +4 -15
  20. abstract_utilities-0.2.2.449.dist-info/RECORD +49 -0
  21. {abstract_utilities-0.2.2.448.dist-info → abstract_utilities-0.2.2.449.dist-info}/WHEEL +1 -1
  22. abstract_utilities/cmd_utils/imports/__init__.py +0 -1
  23. abstract_utilities/cmd_utils/imports/imports.py +0 -10
  24. abstract_utilities/cmd_utils/pexpect_utils.py +0 -310
  25. abstract_utilities/compare_utils/__init__.py +0 -3
  26. abstract_utilities/compare_utils/best_match.py +0 -150
  27. abstract_utilities/compare_utils/find_value.py +0 -105
  28. abstract_utilities/env_utils/__init__.py +0 -3
  29. abstract_utilities/env_utils/abstractEnv.py +0 -129
  30. abstract_utilities/env_utils/envy_it.py +0 -33
  31. abstract_utilities/env_utils/imports/__init__.py +0 -2
  32. abstract_utilities/env_utils/imports/imports.py +0 -8
  33. abstract_utilities/env_utils/imports/utils.py +0 -122
  34. abstract_utilities/file_utils/__init__.py +0 -3
  35. abstract_utilities/file_utils/file_utils/__init__.py +0 -6
  36. abstract_utilities/file_utils/file_utils/file_filters.py +0 -104
  37. abstract_utilities/file_utils/file_utils/imports.py +0 -1
  38. abstract_utilities/file_utils/file_utils/map_utils.py +0 -29
  39. abstract_utilities/file_utils/imports/__init__.py +0 -5
  40. abstract_utilities/file_utils/imports/classes.py +0 -381
  41. abstract_utilities/file_utils/imports/constants.py +0 -39
  42. abstract_utilities/file_utils/imports/file_functions.py +0 -10
  43. abstract_utilities/file_utils/imports/imports.py +0 -14
  44. abstract_utilities/file_utils/imports/module_imports.py +0 -9
  45. abstract_utilities/file_utils/req.py +0 -329
  46. abstract_utilities/robust_reader/imports/__init__.py +0 -1
  47. abstract_utilities/robust_reader/imports/imports.py +0 -12
  48. abstract_utilities/robust_readers/imports.py +0 -8
  49. abstract_utilities/safe_utils.py +0 -133
  50. abstract_utilities/ssh_utils/__init__.py +0 -3
  51. abstract_utilities/ssh_utils/classes.py +0 -127
  52. abstract_utilities/ssh_utils/imports.py +0 -10
  53. abstract_utilities/ssh_utils/pexpect_utils.py +0 -315
  54. abstract_utilities/ssh_utils/utils.py +0 -188
  55. abstract_utilities/string_utils.py +0 -12
  56. abstract_utilities-0.2.2.448.dist-info/RECORD +0 -83
  57. {abstract_utilities-0.2.2.448.dist-info → abstract_utilities-0.2.2.449.dist-info}/top_level.txt +0 -0
@@ -1,329 +0,0 @@
1
- # attach_functions.py — single helper you can import anywhere
2
- # attach_dynamic.py
3
- from __future__ import annotations
4
- from .file_utils import define_defaults,get_files_and_dirs
5
- from .imports import *
6
- ABSPATH = os.path.abspath(__file__)
7
- ABSROOT = os.path.dirname(ABSPATH)
8
- def caller_path():
9
- frame = inspect.stack()[1]
10
- return os.path.abspath(frame.filename)
11
- def _is_defined_here(mod: types.ModuleType, obj: object) -> bool:
12
- try:
13
- return inspect.getmodule(obj) is mod
14
- except Exception:
15
- return False
16
-
17
- def _collect_callables(mod: types.ModuleType) -> Dict[str, Callable]:
18
- out: Dict[str, Callable] = {}
19
- names = getattr(mod, "__all__", None)
20
- if names:
21
- # trust the author's export list
22
- for n in names:
23
- fn = getattr(mod, n, None)
24
- if callable(fn):
25
- out[n] = fn
26
- return out
27
- # otherwise, discover top-level callables defined in this module
28
- for n in dir(mod):
29
- if n.startswith("_"):
30
- continue
31
- obj = getattr(mod, n, None)
32
- if callable(obj) and _is_defined_here(mod, obj):
33
- out[n] = obj
34
- return out
35
-
36
- def _import_module_by_name(name: str) -> Optional[types.ModuleType]:
37
- try:
38
- return importlib.import_module(name)
39
- except Exception:
40
- return None
41
-
42
- def _import_module_by_path(pkg_name: str, base_dir: str, filename: str) -> Optional[types.ModuleType]:
43
- mod_name = f"{pkg_name}.functions"
44
- path = os.path.join(base_dir, filename)
45
- spec = importlib.util.spec_from_file_location(mod_name, path)
46
- if not spec or not spec.loader:
47
- return None
48
- mod = importlib.util.module_from_spec(spec)
49
- sys.modules[mod_name] = mod
50
- spec.loader.exec_module(mod)
51
- return mod
52
-
53
- def _walk_functions_package(pkg_name: str, pkg_mod: types.ModuleType) -> List[types.ModuleType]:
54
- """Import all immediate submodules in the functions/ package."""
55
- mods: List[types.ModuleType] = [pkg_mod]
56
- pkg_dir = os.path.dirname(pkg_mod.__file__ or "")
57
- for info in pkgutil.iter_modules([pkg_dir]):
58
- # only import direct children (no recursion here; easy to add if you need)
59
- child_name = f"{pkg_mod.__name__}.{info.name}"
60
- m = _import_module_by_name(child_name)
61
- if m:
62
- mods.append(m)
63
- return mods
64
-
65
- def _discover_functions(base_pkg: str, *, hot_reload: bool) -> List[Tuple[str, Callable, str]]:
66
- """
67
- Returns a list of (export_name, callable, module_basename).
68
- Works if you have base_pkg.functions.py or base_pkg/functions/ package.
69
- """
70
- # Prefer normal import of '<base_pkg>.functions'
71
- fqn = f"{base_pkg}.functions"
72
- mod = _import_module_by_name(fqn)
73
-
74
- if mod is None:
75
- # fallback: sibling functions.py, even without being a package
76
- base = _import_module_by_name(base_pkg)
77
- if not base or not getattr(base, "__file__", None):
78
- return []
79
- base_dir = os.path.dirname(base.__file__)
80
- if os.path.isfile(os.path.join(base_dir, "functions.py")):
81
- mod = _import_module_by_path(base_pkg, base_dir, "functions.py")
82
- else:
83
- return []
84
-
85
- if hot_reload:
86
- try:
87
- mod = importlib.reload(mod) # type: ignore[arg-type]
88
- except Exception:
89
- pass
90
-
91
- results: List[Tuple[str, Callable, str]] = []
92
- modules: List[types.ModuleType]
93
-
94
- if hasattr(mod, "__path__"): # it's a package: import children
95
- modules = _walk_functions_package(base_pkg, mod)
96
- else:
97
- modules = [mod]
98
-
99
- for m in modules:
100
- exported = _collect_callables(m)
101
- module_basename = m.__name__.split(".")[-1]
102
- for name, fn in exported.items():
103
- results.append((name, fn, module_basename))
104
- return results
105
-
106
- def attach_functionss(
107
- self,
108
- *,
109
- base_pkg: Optional[str] = None,
110
- hot_reload: bool = False,
111
- prefix_with_module: bool = False,
112
- on_collision: str = "last_wins", # or "error" or "skip"
113
- ):
114
- """
115
- Attach discovered functions from '<base_pkg>.functions' onto `self`.
116
-
117
- - base_pkg: package containing either functions.py or functions/ (defaults to the class' package)
118
- - hot_reload: reload modules each call (handy during development)
119
- - prefix_with_module: if True, attach as '<module>_<funcname>' to avoid name clashes
120
- - on_collision: how to handle duplicate names: 'last_wins' | 'error' | 'skip'
121
- """
122
- if base_pkg is None:
123
- # Infer from the class' module: e.g., "myapp.tabs.users.widgets" -> "myapp.tabs.users"
124
- cls_mod = self.__class__.__module__
125
- base_pkg = cls_mod.rsplit(".", 1)[0] if "." in cls_mod else cls_mod
126
-
127
- discovered = _discover_functions(base_pkg, hot_reload=hot_reload)
128
- seen: Dict[str, str] = {}
129
-
130
- for name, fn, mod_base in discovered:
131
- attr_name = f"{mod_base}_{name}" if prefix_with_module else name
132
-
133
- if hasattr(self, attr_name):
134
- if on_collision == "skip":
135
- continue
136
- if on_collision == "error":
137
- raise RuntimeError(f"attach_functions collision on '{attr_name}' from module '{mod_base}'")
138
- # last_wins: fall through
139
-
140
- setattr(self, attr_name, fn)
141
- seen[attr_name] = mod_base
142
-
143
- # breadcrumb for debugging
144
- setattr(self, "_attached_functions", sorted(seen.keys()))
145
- return self
146
-
147
- def attach_functions(
148
- obj_or_cls,
149
- base_pkg: str | None = None,
150
- hot_reload: bool = True,
151
- prefix_with_module: bool = False,
152
- include_private: bool = True,
153
- only_defined_here: bool = True, # don't attach stuff imported from elsewhere
154
- ) -> list[str]:
155
- """
156
- Attach all free functions found in <base_pkg>.functions (module or package)
157
- to the *class* of obj_or_cls. Returns the list of attached attribute names.
158
- """
159
- cls = obj_or_cls if inspect.isclass(obj_or_cls) else obj_or_cls.__class__
160
- # Derive "<package>.functions" from the class's module unless you pass base_pkg
161
- caller_mod = cls.__module__
162
- pkg_root = (base_pkg or caller_mod.rsplit(".", 1)[0]).rstrip(".")
163
- funcs_pkg_name = f"{pkg_root}.functions"
164
-
165
- def _import(name: str) -> ModuleType | None:
166
- try:
167
- if hot_reload and name in sys.modules:
168
- return importlib.reload(sys.modules[name])
169
- return importlib.import_module(name)
170
- except Exception:
171
- return None
172
-
173
- def _is_pkg(m: ModuleType) -> bool:
174
- return hasattr(m, "__path__")
175
-
176
- mod = _import(funcs_pkg_name)
177
- if mod is None:
178
- # Nothing to attach (no functions.py or functions/ next to your class)
179
- setattr(cls, "_attached_functions", tuple())
180
- return []
181
-
182
- modules: list[ModuleType] = [mod]
183
- if _is_pkg(mod):
184
- # attach from every submodule under functions/
185
- for it in pkgutil.iter_modules(mod.__path__):
186
- sub = _import(f"{funcs_pkg_name}.{it.name}")
187
- if sub is not None:
188
- modules.append(sub)
189
-
190
- attached: list[str] = []
191
- for m in modules:
192
- for name, obj in vars(m).items():
193
- # only callables (skip classes), and keep them sane
194
- if not callable(obj) or isinstance(obj, type):
195
- continue
196
- if only_defined_here and getattr(obj, "__module__", None) != m.__name__:
197
- continue
198
- if not include_private and name.startswith("_"):
199
- continue
200
- if name.startswith("__") and name.endswith("__"):
201
- continue
202
- attr = f"{m.__name__.rsplit('.', 1)[-1]}__{name}" if prefix_with_module else name
203
- try:
204
- setattr(cls, attr, obj) # set on CLASS → becomes bound method on instances
205
- attached.append(attr)
206
- except Exception:
207
- # don't explode if one name collides; keep going
208
- continue
209
-
210
- # handy for debugging
211
- try:
212
- setattr(cls, "_attached_functions", tuple(attached))
213
- except Exception:
214
- pass
215
- return attached
216
-
217
-
218
-
219
- def clean_imports():
220
- alls = str(list(set("""os,re,subprocess,sys,re,traceback,pydot, enum, inspect, sys, traceback, threading,json,traceback,logging,requests""".replace('\n','').replace(' ','').replace('\t','').split(','))))[1:-1].replace('"','').replace("'",'')
221
- input(alls)
222
- def isTab(item):
223
- item_lower = item.lower()
224
- for key in ['console','tab']:
225
- if item_lower.endswith(key):
226
- return True
227
- return False
228
- def get_dir(root,item):
229
- if None in [root]:
230
- return None
231
- path = root
232
- if item != None:
233
- path = os.path.join(path,item)
234
- return path
235
- def isDir(root,item=None):
236
- path = get_dir(root,item)
237
- if path:
238
- return os.path.isdir(path)
239
- def check_dir_item(root,item=None):
240
- return (item and isTab(item) and isDir(root,item))
241
- def get_dirs(root = None):
242
- root = root or ABSROOT
243
- dirpaths = [get_dir(root,item) for item in os.listdir(root) if check_dir_item(root,item)]
244
- return dirpaths
245
- def ifFunctionsInFile(root):
246
- items = [os.path.join(root, "functions"),os.path.join(root, "functions.py")]
247
- for item in items:
248
- if os.path.exists(item):
249
- return item
250
-
251
-
252
- def get_for_all_tabs(root = None):
253
- root = root or caller_path()
254
- if os.path.isfile(root):
255
- root = os.path.dirname(root)
256
- all_tabs = get_dirs(root = root)
257
- for ROOT in all_tabs:
258
- FUNCS_DIR = ifFunctionsInFile(ROOT)
259
- if FUNCS_DIR == None:
260
- for ROOT in get_dirs(root = ROOT):
261
- apply_inits(ROOT)
262
- else:
263
- apply_inits(ROOT)
264
-
265
-
266
- def apply_inits(ROOT):
267
- FUNCS_DIR = ifFunctionsInFile(ROOT)
268
-
269
-
270
- if_fun_dir = isDir(FUNCS_DIR)
271
- if if_fun_dir != None:
272
-
273
- if if_fun_dir:
274
- CFG = define_defaults(allowed_exts='.py',
275
- unallowed_exts = True,
276
- exclude_types = True,
277
- exclude_dirs = True,
278
- exclude_patterns = True)
279
- _,filepaths = get_files_and_dirs(FUNCS_DIR,cfg=CFG)
280
-
281
- else:
282
- filepaths = [FUNCS_DIR]
283
-
284
- # Parse top-level def names
285
- def extract_funcs(path: str):
286
- funcs = []
287
- for line in read_from_file(path).splitlines():
288
- m = re.match(r"^def\s+([A-Za-z_]\w*)\s*\(self", line)
289
- if m:
290
- funcs.append(m.group(1))
291
- return funcs
292
-
293
- # Build functions/__init__.py that re-exports all discovered functions
294
- import_lines = []
295
- all_funcs = []
296
- for fp in filepaths:
297
- module = os.path.splitext(os.path.basename(fp))[0]
298
- funcs = extract_funcs(fp)
299
- if funcs:
300
- import_lines.append(f"from .{module} import ({', '.join(funcs)})")
301
- all_funcs.extend(funcs)
302
- if if_fun_dir:
303
- functions_init = "\n".join(import_lines) + ("\n" if import_lines else "")
304
- write_to_file(contents=functions_init, file_path=os.path.join(FUNCS_DIR, "__init__.py"))
305
-
306
- # Prepare the tuple literal of function names for import + loop
307
- uniq_funcs = sorted(set(all_funcs))
308
- func_tuple = ", ".join(uniq_funcs) + ("," if len(uniq_funcs) == 1 else "")
309
-
310
- # Generate apiConsole/initFuncs.py using the safer setattr-loop
311
- init_funcs_src = textwrap.dedent(f"""\
312
-
313
-
314
- from .functions import ({func_tuple})
315
-
316
- def initFuncs(self):
317
- try:
318
- for f in ({func_tuple}):
319
- setattr(self, f.__name__, f)
320
- except Exception as e:
321
- logger.info(f"{{e}}")
322
- return self
323
- """)
324
-
325
- write_to_file(contents=init_funcs_src, file_path=os.path.join(ROOT, "initFuncs.py"))
326
-
327
- def call_for_all_tabs():
328
- root = get_caller_dir(2)
329
- get_for_all_tabs(root)
@@ -1 +0,0 @@
1
- from .imports import *
@@ -1,12 +0,0 @@
1
- import os,tempfile,shutil,logging,ezodf,fnmatch,pytesseract,pdfplumber
2
- import pandas as pd
3
- import geopandas as gpd
4
- from datetime import datetime
5
- from pathlib import Path
6
- from typing import *
7
- from werkzeug.utils import secure_filename
8
- from werkzeug.datastructures import FileStorage
9
- from pdf2image import convert_from_path # only used for OCR fallback
10
- from ...abstract_classes import SingletonMeta
11
- from ..pdf_utils import *
12
- from ...read_write_utils import *
@@ -1,8 +0,0 @@
1
- from types import ModuleType
2
- from typing import Iterable
3
- from ..file_utils import get_caller_dir,get_caller_path,define_defaults,get_files_and_dirs
4
- from ..read_write_utils import *
5
- import textwrap, pkgutil, os, re, textwrap, sys, types, importlib, importlib.util
6
- from typing import *
7
- ABSPATH = os.path.abspath(__file__)
8
- ABSROOT = os.path.dirname(ABSPATH)
@@ -1,133 +0,0 @@
1
- """
2
- abstract_safeops.py
3
- -------------------
4
- Utility functions for safely splitting, slicing, and retrieving elements from iterable or string objects
5
- without raising exceptions on invalid input or out-of-range indices.
6
-
7
- Designed for compatibility with the abstract_ ecosystem (e.g. abstract_utilities, abstract_math, etc.).
8
- """
9
-
10
- from typing import *
11
- from .type_utils import is_number
12
- from .class_utils import get_caller_dir
13
-
14
- _BASE_DIR = get_caller_dir()
15
-
16
-
17
- def safe_split(
18
- string: Any,
19
- char: Any,
20
- i: Optional[int] = None,
21
- default: Union[bool, Any] = False
22
- ) -> Union[str, List[str], Any, None]:
23
- """
24
- Safely split a string by a character and optionally return index i.
25
-
26
- Args:
27
- string: Input string (or any object convertible to string).
28
- char: Delimiter to split on.
29
- i: Optional index to retrieve from the split result.
30
- default: If True, return the original string on error.
31
- If any other value, return that instead of raising.
32
-
33
- Returns:
34
- The split list, or the element at index i, or default behavior on error.
35
- """
36
- if string is None or char is None:
37
- return string
38
-
39
- s, c = str(string), str(char)
40
- if c not in s:
41
- return string
42
-
43
- parts = s.split(c)
44
-
45
- if i is None:
46
- return parts
47
-
48
- if is_number(i):
49
- idx = int(i)
50
- if 0 <= idx < len(parts):
51
- return parts[idx]
52
-
53
- if default:
54
- return string if default is True else default
55
-
56
- return None
57
-
58
-
59
- def safe_slice(
60
- obj: Any,
61
- i: Optional[int] = None,
62
- k: Optional[int] = None,
63
- default: Union[bool, Any] = False
64
- ) -> Any:
65
- """
66
- Safely slice an iterable object or string, with fallback behavior on invalid indices.
67
-
68
- Args:
69
- obj: Iterable or string-like object.
70
- i: Start index (can be negative).
71
- k: End index (can be negative).
72
- default: If True, returns the original object on error.
73
- If any other value, return that value on error.
74
-
75
- Returns:
76
- The sliced object, or default behavior on error.
77
- """
78
- # Null or invalid base case
79
- if obj is None or isinstance(obj, bool):
80
- return obj if default is True else default if default else None
81
-
82
- # Non-iterable guard
83
- if not hasattr(obj, "__getitem__"):
84
- return obj if default is True else default if default else None
85
-
86
- obj_len = len(obj)
87
-
88
- # Normalize negative indices
89
- if isinstance(i, int) and i < 0:
90
- i = obj_len + i
91
- if isinstance(k, int) and k < 0:
92
- k = obj_len + k
93
-
94
- # Bound indices
95
- if i is not None:
96
- i = max(0, min(i, obj_len))
97
- if k is not None:
98
- k = max(0, min(k, obj_len))
99
-
100
- try:
101
- return obj[i:k]
102
- except Exception:
103
- return obj if default is True else default if default else None
104
-
105
- def safe_join(*paths):
106
- paths = list(paths)
107
- paths = [path for path in paths if path]
108
- return os.path.join(*paths)
109
- def safe_get(
110
- obj: Any,
111
- key: Union[int, str, None] = None,
112
- default: Union[bool, Any] = False
113
- ) -> Any:
114
- """
115
- Generalized safe getter for both indexable and mapping types.
116
-
117
- Args:
118
- obj: The object to access (list, dict, string, etc.).
119
- key: Index or key to retrieve.
120
- default: Fallback value or True for "return obj".
121
-
122
- Returns:
123
- Retrieved element, or default value on failure.
124
- """
125
- if obj is None or key is None:
126
- return obj if default is True else default if default else None
127
-
128
- try:
129
- if isinstance(obj, dict):
130
- return obj.get(key, obj if default is True else default if default else None)
131
- return obj[key]
132
- except Exception:
133
- return obj if default is True else default if default else None
@@ -1,3 +0,0 @@
1
- from .classes import *
2
- from .utils import *
3
- from .pexpect_utils import *
@@ -1,127 +0,0 @@
1
- from .imports import *
2
- from .utils import run_local_cmd, run_ssh_cmd,run_any_cmd,run_cmd
3
-
4
- class PathBackend(Protocol):
5
- def join(self, *parts: str) -> str: ...
6
- def isfile(self, path: str) -> bool: ...
7
- def isdir(self, path: str) -> bool: ...
8
- def glob_recursive(self, base: str, **opts) -> List[str]: ...
9
- def listdir(self, base: str) -> List[str]: ...
10
-
11
- class LocalFS:
12
- def join(self, *parts: str) -> str:
13
- return os.path.join(*parts)
14
- def isfile(self, path: str) -> bool:
15
- return os.path.isfile(path)
16
- def isdir(self, path: str) -> bool:
17
- return os.path.isdir(path)
18
- def glob_recursive(self, base: str, **opts) -> List[str]:
19
- """
20
- opts:
21
- - maxdepth: int | None
22
- - mindepth: int (default 1)
23
- - follow_symlinks: bool
24
- - include_dirs: bool
25
- - include_files: bool
26
- - exclude_hidden: bool
27
- """
28
- maxdepth = opts.get("maxdepth")
29
- mindepth = opts.get("mindepth", 1)
30
- follow = opts.get("follow_symlinks", False)
31
- want_d = opts.get("include_dirs", True)
32
- want_f = opts.get("include_files", True)
33
- hide = opts.get("exclude_hidden", False)
34
-
35
- results: List[str] = []
36
- base_depth = os.path.normpath(base).count(os.sep)
37
-
38
- for root, dirs, files in os.walk(base, followlinks=follow):
39
- depth = os.path.normpath(root).count(os.sep) - base_depth
40
- if maxdepth is not None and depth > maxdepth:
41
- dirs[:] = []
42
- continue
43
- if want_d and depth >= mindepth:
44
- for d in dirs:
45
- if hide and d.startswith("."): continue
46
- results.append(os.path.join(root, d))
47
- if want_f and depth >= mindepth:
48
- for f in files:
49
- if hide and f.startswith("."): continue
50
- results.append(os.path.join(root, f))
51
- return results
52
-
53
- def listdir(self, base: str) -> List[str]:
54
- try:
55
- return [os.path.join(base, name) for name in os.listdir(base)]
56
- except Exception:
57
- return []
58
-
59
- class SSHFS:
60
- """Remote POSIX backend via your run_remote_cmd."""
61
- def __init__(self, user_at_host: str):
62
- self.user_at_host = user_at_host
63
-
64
- def join(self, *parts: str) -> str:
65
- return posixpath.join(*parts)
66
-
67
- def isfile(self, path: str) -> bool:
68
- cmd = f"test -f {shlex.quote(path)} && echo __OK__ || true"
69
- out = run_remote_cmd(self.user_at_host, cmd)
70
- return "__OK__" in (out or "")
71
-
72
- def isdir(self, path: str) -> bool:
73
- cmd = f"test -d {shlex.quote(path)} && echo __OK__ || true"
74
- out = run_remote_cmd(self.user_at_host, cmd)
75
- return "__OK__" in (out or "")
76
-
77
- def glob_recursive(self, base: str, **opts) -> List[str]:
78
- maxdepth = opts.get("maxdepth")
79
- mindepth = opts.get("mindepth", 1)
80
- follow = opts.get("follow_symlinks", False)
81
- want_d = opts.get("include_dirs", True)
82
- want_f = opts.get("include_files", True)
83
- hide = opts.get("exclude_hidden", False)
84
-
85
- parts = []
86
- if follow:
87
- parts.append("-L")
88
- parts += ["find", shlex.quote(base)]
89
- if mindepth is not None:
90
- parts += ["-mindepth", str(mindepth)]
91
- if maxdepth is not None:
92
- parts += ["-maxdepth", str(maxdepth)]
93
-
94
- type_filters = []
95
- if want_d and not want_f:
96
- type_filters = ["-type", "d"]
97
- elif want_f and not want_d:
98
- type_filters = ["-type", "f"]
99
-
100
- hidden_filter = []
101
- if hide:
102
- hidden_filter = ["!", "-regex", r".*/\..*"]
103
-
104
- cmd = " ".join(parts + type_filters + hidden_filter + ["-printf", r"'%p\n'"]) + " 2>/dev/null"
105
- out = run_remote_cmd(self.user_at_host, cmd)
106
- return [line.strip().strip("'") for line in (out or "").splitlines() if line.strip()]
107
-
108
- def listdir(self, base: str) -> List[str]:
109
- cmd = f"find {shlex.quote(base)} -maxdepth 1 -mindepth 1 -printf '%p\\n' 2>/dev/null"
110
- out = run_remote_cmd(self.user_at_host, cmd)
111
- return [line.strip() for line in (out or "").splitlines() if line.strip()]
112
-
113
- # ---- auto-detect "user@host:/abs/path" ----
114
- REMOTE_RE = re.compile(r"^(?P<host>[^:\s]+@[^:\s]+):(?P<path>/.*)$")
115
-
116
- def normalize_items(paths: Iterable[str],user_at_host=None,**kwargs) -> List[tuple[PathBackend, str]]:
117
- pairs: List[tuple[PathBackend, str]] = []
118
- host = user_at_host or kwargs.get('host')
119
-
120
- for item in paths:
121
- if not item: continue
122
- m = REMOTE_RE.match(item)
123
- if m:
124
- pairs.append((SSHFS(m.group("host") or user_at_host), m.group("path")))
125
- else:
126
- pairs.append((LocalFS(), item))
127
- return pairs
@@ -1,10 +0,0 @@
1
- # remote_fs.py
2
- from __future__ import annotations
3
- from typing import *
4
- import subprocess, shlex, os, fnmatch, glob, posixpath, re
5
- # exec_api.py
6
- # ---- import your existing pieces ----
7
- from ..type_utils import make_list
8
- from ..time_utils import get_sleep
9
- from ..env_utils import *
10
- from ..string_clean import eatOuter