stouputils 1.14.2__py3-none-any.whl → 1.15.0__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.
- stouputils/continuous_delivery/pypi.py +1 -1
- stouputils/continuous_delivery/pypi.pyi +3 -2
- stouputils/data_science/config/get.py +51 -51
- stouputils/data_science/data_processing/image/__init__.py +66 -66
- stouputils/data_science/data_processing/image/auto_contrast.py +79 -79
- stouputils/data_science/data_processing/image/axis_flip.py +58 -58
- stouputils/data_science/data_processing/image/bias_field_correction.py +74 -74
- stouputils/data_science/data_processing/image/binary_threshold.py +73 -73
- stouputils/data_science/data_processing/image/blur.py +59 -59
- stouputils/data_science/data_processing/image/brightness.py +54 -54
- stouputils/data_science/data_processing/image/canny.py +110 -110
- stouputils/data_science/data_processing/image/clahe.py +92 -92
- stouputils/data_science/data_processing/image/common.py +30 -30
- stouputils/data_science/data_processing/image/contrast.py +53 -53
- stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -74
- stouputils/data_science/data_processing/image/denoise.py +378 -378
- stouputils/data_science/data_processing/image/histogram_equalization.py +123 -123
- stouputils/data_science/data_processing/image/invert.py +64 -64
- stouputils/data_science/data_processing/image/laplacian.py +60 -60
- stouputils/data_science/data_processing/image/median_blur.py +52 -52
- stouputils/data_science/data_processing/image/noise.py +59 -59
- stouputils/data_science/data_processing/image/normalize.py +65 -65
- stouputils/data_science/data_processing/image/random_erase.py +66 -66
- stouputils/data_science/data_processing/image/resize.py +69 -69
- stouputils/data_science/data_processing/image/rotation.py +80 -80
- stouputils/data_science/data_processing/image/salt_pepper.py +68 -68
- stouputils/data_science/data_processing/image/sharpening.py +55 -55
- stouputils/data_science/data_processing/image/shearing.py +64 -64
- stouputils/data_science/data_processing/image/threshold.py +64 -64
- stouputils/data_science/data_processing/image/translation.py +71 -71
- stouputils/data_science/data_processing/image/zoom.py +83 -83
- stouputils/data_science/data_processing/image_augmentation.py +118 -118
- stouputils/data_science/data_processing/image_preprocess.py +183 -183
- stouputils/data_science/data_processing/prosthesis_detection.py +359 -359
- stouputils/data_science/data_processing/technique.py +481 -481
- stouputils/data_science/dataset/__init__.py +45 -45
- stouputils/data_science/dataset/dataset.py +292 -292
- stouputils/data_science/dataset/dataset_loader.py +135 -135
- stouputils/data_science/dataset/grouping_strategy.py +296 -296
- stouputils/data_science/dataset/image_loader.py +100 -100
- stouputils/data_science/dataset/xy_tuple.py +696 -696
- stouputils/data_science/metric_dictionnary.py +106 -106
- stouputils/data_science/mlflow_utils.py +206 -206
- stouputils/data_science/models/abstract_model.py +149 -149
- stouputils/data_science/models/all.py +85 -85
- stouputils/data_science/models/keras/all.py +38 -38
- stouputils/data_science/models/keras/convnext.py +62 -62
- stouputils/data_science/models/keras/densenet.py +50 -50
- stouputils/data_science/models/keras/efficientnet.py +60 -60
- stouputils/data_science/models/keras/mobilenet.py +56 -56
- stouputils/data_science/models/keras/resnet.py +52 -52
- stouputils/data_science/models/keras/squeezenet.py +233 -233
- stouputils/data_science/models/keras/vgg.py +42 -42
- stouputils/data_science/models/keras/xception.py +38 -38
- stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -20
- stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -219
- stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -148
- stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -31
- stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -249
- stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -66
- stouputils/data_science/models/keras_utils/losses/__init__.py +12 -12
- stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -56
- stouputils/data_science/models/keras_utils/visualizations.py +416 -416
- stouputils/data_science/models/sandbox.py +116 -116
- stouputils/data_science/range_tuple.py +234 -234
- stouputils/data_science/utils.py +285 -285
- stouputils/decorators.py +53 -39
- stouputils/decorators.pyi +12 -2
- stouputils/installer/__init__.py +18 -18
- stouputils/installer/linux.py +144 -144
- stouputils/installer/main.py +223 -223
- stouputils/installer/windows.py +136 -136
- stouputils/io.py +16 -9
- stouputils/parallel.pyi +12 -7
- stouputils/print.py +229 -2
- stouputils/print.pyi +92 -3
- stouputils/py.typed +1 -1
- {stouputils-1.14.2.dist-info → stouputils-1.15.0.dist-info}/METADATA +1 -1
- stouputils-1.15.0.dist-info/RECORD +140 -0
- {stouputils-1.14.2.dist-info → stouputils-1.15.0.dist-info}/WHEEL +1 -1
- stouputils/stouputils/__init__.pyi +0 -15
- stouputils/stouputils/_deprecated.pyi +0 -12
- stouputils/stouputils/all_doctests.pyi +0 -46
- stouputils/stouputils/applications/__init__.pyi +0 -2
- stouputils/stouputils/applications/automatic_docs.pyi +0 -106
- stouputils/stouputils/applications/upscaler/__init__.pyi +0 -3
- stouputils/stouputils/applications/upscaler/config.pyi +0 -18
- stouputils/stouputils/applications/upscaler/image.pyi +0 -109
- stouputils/stouputils/applications/upscaler/video.pyi +0 -60
- stouputils/stouputils/archive.pyi +0 -67
- stouputils/stouputils/backup.pyi +0 -109
- stouputils/stouputils/collections.pyi +0 -86
- stouputils/stouputils/continuous_delivery/__init__.pyi +0 -5
- stouputils/stouputils/continuous_delivery/cd_utils.pyi +0 -129
- stouputils/stouputils/continuous_delivery/github.pyi +0 -162
- stouputils/stouputils/continuous_delivery/pypi.pyi +0 -53
- stouputils/stouputils/continuous_delivery/pyproject.pyi +0 -67
- stouputils/stouputils/continuous_delivery/stubs.pyi +0 -39
- stouputils/stouputils/ctx.pyi +0 -211
- stouputils/stouputils/decorators.pyi +0 -252
- stouputils/stouputils/image.pyi +0 -172
- stouputils/stouputils/installer/__init__.pyi +0 -5
- stouputils/stouputils/installer/common.pyi +0 -39
- stouputils/stouputils/installer/downloader.pyi +0 -24
- stouputils/stouputils/installer/linux.pyi +0 -39
- stouputils/stouputils/installer/main.pyi +0 -57
- stouputils/stouputils/installer/windows.pyi +0 -31
- stouputils/stouputils/io.pyi +0 -213
- stouputils/stouputils/parallel.pyi +0 -216
- stouputils/stouputils/print.pyi +0 -136
- stouputils/stouputils/version_pkg.pyi +0 -15
- stouputils-1.14.2.dist-info/RECORD +0 -171
- {stouputils-1.14.2.dist-info → stouputils-1.15.0.dist-info}/entry_points.txt +0 -0
stouputils/decorators.py
CHANGED
|
@@ -198,7 +198,7 @@ def timeout(
|
|
|
198
198
|
>>> slow_function() # Raises TimeoutError after 2 seconds
|
|
199
199
|
Traceback (most recent call last):
|
|
200
200
|
...
|
|
201
|
-
TimeoutError: Function 'slow_function' timed out after 2.0 seconds
|
|
201
|
+
TimeoutError: Function 'slow_function()' timed out after 2.0 seconds
|
|
202
202
|
|
|
203
203
|
>>> @timeout(seconds=1.0, message="Custom timeout message")
|
|
204
204
|
... def another_slow_function():
|
|
@@ -209,59 +209,73 @@ def timeout(
|
|
|
209
209
|
TimeoutError: Custom timeout message
|
|
210
210
|
"""
|
|
211
211
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
212
|
+
# Check if we can use signal-based timeout (Unix only)
|
|
213
|
+
import os
|
|
214
|
+
use_signal: bool = os.name != 'nt' # Not Windows
|
|
215
|
+
|
|
216
|
+
if use_signal:
|
|
217
|
+
try:
|
|
218
|
+
import signal
|
|
219
|
+
# Verify SIGALRM is available
|
|
220
|
+
use_signal = hasattr(signal, 'SIGALRM')
|
|
221
|
+
except ImportError:
|
|
222
|
+
use_signal = False
|
|
223
|
+
|
|
212
224
|
@wraps(func)
|
|
213
225
|
def wrapper(*args: tuple[Any, ...], **kwargs: dict[str, Any]) -> Any:
|
|
214
226
|
# Build timeout message
|
|
215
227
|
msg: str = message if message else f"Function '{_get_func_name(func)}()' timed out after {seconds} seconds"
|
|
216
228
|
|
|
217
|
-
#
|
|
218
|
-
|
|
229
|
+
# Use signal-based timeout on Unix (main thread only)
|
|
230
|
+
if use_signal:
|
|
219
231
|
import signal
|
|
220
|
-
|
|
221
|
-
raise TimeoutError(msg)
|
|
232
|
+
import threading
|
|
222
233
|
|
|
223
|
-
#
|
|
224
|
-
|
|
225
|
-
|
|
234
|
+
# Signal only works in main thread
|
|
235
|
+
if threading.current_thread() is threading.main_thread():
|
|
236
|
+
def timeout_handler(signum: int, frame: Any) -> None:
|
|
237
|
+
raise TimeoutError(msg)
|
|
226
238
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
# Cancel the alarm and restore the old handler
|
|
231
|
-
signal.setitimer(signal.ITIMER_REAL, 0) # type: ignore
|
|
232
|
-
signal.signal(signal.SIGALRM, old_handler) # type: ignore
|
|
239
|
+
# Set the signal handler and alarm
|
|
240
|
+
old_handler = signal.signal(signal.SIGALRM, timeout_handler) # type: ignore
|
|
241
|
+
signal.setitimer(signal.ITIMER_REAL, seconds) # type: ignore
|
|
233
242
|
|
|
234
|
-
|
|
243
|
+
try:
|
|
244
|
+
result = func(*args, **kwargs)
|
|
245
|
+
finally:
|
|
246
|
+
# Cancel the alarm and restore the old handler
|
|
247
|
+
signal.setitimer(signal.ITIMER_REAL, 0) # type: ignore
|
|
248
|
+
signal.signal(signal.SIGALRM, old_handler) # type: ignore
|
|
235
249
|
|
|
236
|
-
|
|
237
|
-
# SIGALRM not available (Windows) or not in main thread
|
|
238
|
-
# Fall back to polling-based timeout (less precise but portable)
|
|
239
|
-
import threading
|
|
250
|
+
return result
|
|
240
251
|
|
|
241
|
-
|
|
242
|
-
|
|
252
|
+
# Fall back to polling-based timeout (Windows or non-main thread)
|
|
253
|
+
import threading
|
|
243
254
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
255
|
+
result_container: list[Any] = []
|
|
256
|
+
exception_container: list[BaseException] = []
|
|
257
|
+
|
|
258
|
+
def target() -> None:
|
|
259
|
+
try:
|
|
260
|
+
result_container.append(func(*args, **kwargs))
|
|
261
|
+
except BaseException as e_2:
|
|
262
|
+
exception_container.append(e_2)
|
|
249
263
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
264
|
+
thread = threading.Thread(target=target, daemon=True)
|
|
265
|
+
thread.start()
|
|
266
|
+
thread.join(timeout=seconds)
|
|
253
267
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
268
|
+
if thread.is_alive():
|
|
269
|
+
# Thread is still running, timeout occurred
|
|
270
|
+
raise TimeoutError(msg)
|
|
257
271
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
272
|
+
# Check if an exception was raised in the thread
|
|
273
|
+
if exception_container:
|
|
274
|
+
raise exception_container[0]
|
|
261
275
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
276
|
+
# Return the result if available
|
|
277
|
+
if result_container:
|
|
278
|
+
return result_container[0]
|
|
265
279
|
|
|
266
280
|
wrapper.__name__ = _get_wrapper_name("stouputils.decorators.timeout", func)
|
|
267
281
|
return wrapper
|
|
@@ -454,7 +468,7 @@ def abstract(
|
|
|
454
468
|
>>> Base().method()
|
|
455
469
|
Traceback (most recent call last):
|
|
456
470
|
...
|
|
457
|
-
NotImplementedError: Function 'method' is abstract and must be implemented by a subclass
|
|
471
|
+
NotImplementedError: Function 'method()' is abstract and must be implemented by a subclass
|
|
458
472
|
"""
|
|
459
473
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
460
474
|
message: str = f"Function '{_get_func_name(func)}()' is abstract and must be implemented by a subclass"
|
stouputils/decorators.pyi
CHANGED
|
@@ -88,7 +88,7 @@ def timeout(func: Callable[..., Any] | None = None, *, seconds: float = 60.0, me
|
|
|
88
88
|
\t\t>>> slow_function() # Raises TimeoutError after 2 seconds
|
|
89
89
|
\t\tTraceback (most recent call last):
|
|
90
90
|
\t\t\t...
|
|
91
|
-
\t\tTimeoutError: Function \'slow_function\' timed out after 2.0 seconds
|
|
91
|
+
\t\tTimeoutError: Function \'slow_function()\' timed out after 2.0 seconds
|
|
92
92
|
|
|
93
93
|
\t\t>>> @timeout(seconds=1.0, message="Custom timeout message")
|
|
94
94
|
\t\t... def another_slow_function():
|
|
@@ -151,6 +151,16 @@ def simple_cache(func: Callable[..., Any] | None = None, *, method: Literal['str
|
|
|
151
151
|
\t\t3
|
|
152
152
|
\t\t>>> test2(3, 4)
|
|
153
153
|
\t\t7
|
|
154
|
+
|
|
155
|
+
\t\t>>> @simple_cache
|
|
156
|
+
\t\t... def factorial(n: int) -> int:
|
|
157
|
+
\t\t... return n * factorial(n - 1) if n else 1
|
|
158
|
+
\t\t>>> factorial(10) # no previously cached result, makes 11 recursive calls
|
|
159
|
+
\t\t3628800
|
|
160
|
+
\t\t>>> factorial(5) # no new calls, just returns the cached result
|
|
161
|
+
\t\t120
|
|
162
|
+
\t\t>>> factorial(12) # two new recursive calls, factorial(10) is cached
|
|
163
|
+
\t\t479001600
|
|
154
164
|
\t'''
|
|
155
165
|
def abstract(func: Callable[..., Any] | None = None, *, error_log: LogLevels = ...) -> Callable[..., Any]:
|
|
156
166
|
""" Decorator that marks a function as abstract.
|
|
@@ -180,7 +190,7 @@ def abstract(func: Callable[..., Any] | None = None, *, error_log: LogLevels = .
|
|
|
180
190
|
\t\t>>> Base().method()
|
|
181
191
|
\t\tTraceback (most recent call last):
|
|
182
192
|
\t\t\t...
|
|
183
|
-
\t\tNotImplementedError: Function 'method' is abstract and must be implemented by a subclass
|
|
193
|
+
\t\tNotImplementedError: Function 'method()' is abstract and must be implemented by a subclass
|
|
184
194
|
\t"""
|
|
185
195
|
def deprecated(func: Callable[..., Any] | None = None, *, message: str = '', version: str = '', error_log: LogLevels = ...) -> Callable[..., Any]:
|
|
186
196
|
''' Decorator that marks a function as deprecated.
|
stouputils/installer/__init__.py
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
""" Installer module for stouputils.
|
|
2
|
-
|
|
3
|
-
Provides functions for platform-agnostic installation tasks by dispatching
|
|
4
|
-
to platform-specific implementations (Windows, Linux/macOS).
|
|
5
|
-
|
|
6
|
-
It handles getting installation paths, adding programs to the PATH environment variable,
|
|
7
|
-
and installing programs from local zip files or URLs.
|
|
8
|
-
"""
|
|
9
|
-
# ruff: noqa: F403
|
|
10
|
-
# ruff: noqa: F405
|
|
11
|
-
|
|
12
|
-
# Imports
|
|
13
|
-
from .common import *
|
|
14
|
-
from .downloader import *
|
|
15
|
-
from .linux import *
|
|
16
|
-
from .main import *
|
|
17
|
-
from .windows import *
|
|
18
|
-
|
|
1
|
+
""" Installer module for stouputils.
|
|
2
|
+
|
|
3
|
+
Provides functions for platform-agnostic installation tasks by dispatching
|
|
4
|
+
to platform-specific implementations (Windows, Linux/macOS).
|
|
5
|
+
|
|
6
|
+
It handles getting installation paths, adding programs to the PATH environment variable,
|
|
7
|
+
and installing programs from local zip files or URLs.
|
|
8
|
+
"""
|
|
9
|
+
# ruff: noqa: F403
|
|
10
|
+
# ruff: noqa: F405
|
|
11
|
+
|
|
12
|
+
# Imports
|
|
13
|
+
from .common import *
|
|
14
|
+
from .downloader import *
|
|
15
|
+
from .linux import *
|
|
16
|
+
from .main import *
|
|
17
|
+
from .windows import *
|
|
18
|
+
|
stouputils/installer/linux.py
CHANGED
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
""" Installer module for Linux/macOS specific functions.
|
|
2
|
-
|
|
3
|
-
Provides Linux/macOS specific implementations for checking admin privileges,
|
|
4
|
-
determining appropriate installation paths (global/local), and suggesting
|
|
5
|
-
how to add directories to the system's PATH environment variable.
|
|
6
|
-
"""
|
|
7
|
-
# Imports
|
|
8
|
-
import os
|
|
9
|
-
|
|
10
|
-
from ..decorators import LogLevels, handle_error
|
|
11
|
-
from ..io import clean_path
|
|
12
|
-
from ..print import debug, info, warning
|
|
13
|
-
from .common import ask_install_type, prompt_for_path
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# Functions
|
|
17
|
-
@handle_error(message="Failed to suggest how to add to PATH (Linux)", error_log=LogLevels.WARNING_TRACEBACK)
|
|
18
|
-
def add_to_path_linux(install_path: str) -> bool:
|
|
19
|
-
""" Suggest how to add install_path to PATH on Linux.
|
|
20
|
-
|
|
21
|
-
Checks the current shell and provides instructions for adding the path
|
|
22
|
-
to the appropriate configuration file (e.g., .bashrc, .zshrc, config.fish).
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
install_path (str): The path to add to the PATH environment variable.
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
bool: True if instructions were provided, False otherwise (e.g., unknown shell).
|
|
29
|
-
"""
|
|
30
|
-
shell_config_files: dict[str, str] = {
|
|
31
|
-
"bash": "~/.bashrc",
|
|
32
|
-
"zsh": "~/.zshrc",
|
|
33
|
-
"fish": "~/.config/fish/config.fish"
|
|
34
|
-
}
|
|
35
|
-
current_shell: str = os.environ.get("SHELL", "").split('/')[-1]
|
|
36
|
-
config_file: str | None = shell_config_files.get(current_shell)
|
|
37
|
-
|
|
38
|
-
if config_file:
|
|
39
|
-
export_cmd: str = ""
|
|
40
|
-
if current_shell == "fish":
|
|
41
|
-
export_cmd = f"set -gx PATH $PATH {install_path}"
|
|
42
|
-
else:
|
|
43
|
-
export_cmd = f"export PATH=\"$PATH:{install_path}\"" # Escape quotes for print
|
|
44
|
-
|
|
45
|
-
debug(
|
|
46
|
-
f"To add the installation directory to your PATH, add the following line to your '{config_file}':\n"
|
|
47
|
-
f" {export_cmd}\n"
|
|
48
|
-
f"Then restart your shell or run 'source {config_file}'."
|
|
49
|
-
)
|
|
50
|
-
return True
|
|
51
|
-
else:
|
|
52
|
-
warning(f"Could not determine your shell configuration file. Please add '{install_path}' to your PATH manually.")
|
|
53
|
-
return False
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def check_admin_linux() -> bool:
|
|
57
|
-
""" Check if the script is running with root privileges on Linux/macOS.
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
bool: True if the effective user ID is 0 (root), False otherwise.
|
|
61
|
-
"""
|
|
62
|
-
try:
|
|
63
|
-
return os.geteuid() == 0 # type: ignore
|
|
64
|
-
except AttributeError as e:
|
|
65
|
-
# os.geteuid() is not available on all platforms (e.g., Windows)
|
|
66
|
-
# This function should ideally only be called on Linux/macOS.
|
|
67
|
-
warning(f"Could not determine user privileges on this platform: {e}")
|
|
68
|
-
return False
|
|
69
|
-
except Exception as e:
|
|
70
|
-
warning(f"Error checking admin privileges: {e}")
|
|
71
|
-
return False
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@handle_error(message="Failed to get installation path (Linux)", error_log=LogLevels.ERROR_TRACEBACK)
|
|
75
|
-
def get_install_path_linux(
|
|
76
|
-
program_name: str,
|
|
77
|
-
ask_global: int = 0,
|
|
78
|
-
add_path: bool = True,
|
|
79
|
-
append_to_path: str = "",
|
|
80
|
-
default_global: str = "/usr/local/bin",
|
|
81
|
-
) -> str:
|
|
82
|
-
""" Get the installation path for the program on Linux/macOS.
|
|
83
|
-
|
|
84
|
-
Args:
|
|
85
|
-
program_name (str): The name of the program to install.
|
|
86
|
-
ask_global (int): 0 = ask for anything, 1 = install globally, 2 = install locally
|
|
87
|
-
add_path (bool): Whether to add the program to the PATH environment variable. (Only if installed globally)
|
|
88
|
-
append_to_path (str): String to append to the installation path when adding to PATH.
|
|
89
|
-
(ex: "bin" if executables are in the bin folder)
|
|
90
|
-
default_global (str): The default global installation path.
|
|
91
|
-
(Default is "/usr/local/bin" which is the most common location for executables on Linux/macOS,
|
|
92
|
-
could be "/opt" or any other directory)
|
|
93
|
-
|
|
94
|
-
Returns:
|
|
95
|
-
str: The chosen installation path, or an empty string if installation is cancelled.
|
|
96
|
-
"""
|
|
97
|
-
# Default paths
|
|
98
|
-
default_local_path: str = clean_path(os.path.join(os.getcwd(), program_name))
|
|
99
|
-
|
|
100
|
-
# Common global locations: /usr/local/bin for executables, /opt/ for self-contained apps
|
|
101
|
-
# We assume 'program_name' might be an executable or a directory, /usr/local/ is safer
|
|
102
|
-
default_global_path: str = clean_path(f"{default_global}/{program_name}") # Or potentially /opt/{program_name}
|
|
103
|
-
|
|
104
|
-
# Ask user for installation type (global/local)
|
|
105
|
-
install_type: str = ask_install_type(ask_global, default_local_path, default_global_path)
|
|
106
|
-
|
|
107
|
-
# Handle global installation choice
|
|
108
|
-
if install_type == 'g':
|
|
109
|
-
if not check_admin_linux():
|
|
110
|
-
warning(
|
|
111
|
-
f"Global installation typically requires sudo privileges to write to "
|
|
112
|
-
f"'{os.path.dirname(default_global_path)}'.\n"
|
|
113
|
-
f"You may need to re-run the script with 'sudo'.\n"
|
|
114
|
-
f"Install locally instead to '{default_local_path}'? (Y/n): "
|
|
115
|
-
)
|
|
116
|
-
if input().lower() == 'n':
|
|
117
|
-
info("Installation cancelled.")
|
|
118
|
-
return ""
|
|
119
|
-
else:
|
|
120
|
-
# Fallback to local path if user agrees
|
|
121
|
-
return prompt_for_path(
|
|
122
|
-
f"Falling back to local installation path: {default_local_path}.",
|
|
123
|
-
default_local_path
|
|
124
|
-
)
|
|
125
|
-
else:
|
|
126
|
-
# User is admin or proceeding with global install anyway
|
|
127
|
-
install_path: str = prompt_for_path(
|
|
128
|
-
f"Default global installation path is {default_global_path}.",
|
|
129
|
-
default_global_path
|
|
130
|
-
)
|
|
131
|
-
if add_path:
|
|
132
|
-
# Suggest adding the *directory* containing the program to PATH,
|
|
133
|
-
# or the path itself if it seems like a directory install
|
|
134
|
-
path_to_add: str = os.path.dirname(install_path) if os.path.isfile(install_path) else install_path
|
|
135
|
-
add_to_path_linux(os.path.join(path_to_add, append_to_path))
|
|
136
|
-
return install_path
|
|
137
|
-
|
|
138
|
-
# Handle local installation choice
|
|
139
|
-
else: # install_type == 'l'
|
|
140
|
-
return prompt_for_path(
|
|
141
|
-
f"Default local installation path is {default_local_path}.",
|
|
142
|
-
default_local_path
|
|
143
|
-
)
|
|
144
|
-
|
|
1
|
+
""" Installer module for Linux/macOS specific functions.
|
|
2
|
+
|
|
3
|
+
Provides Linux/macOS specific implementations for checking admin privileges,
|
|
4
|
+
determining appropriate installation paths (global/local), and suggesting
|
|
5
|
+
how to add directories to the system's PATH environment variable.
|
|
6
|
+
"""
|
|
7
|
+
# Imports
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from ..decorators import LogLevels, handle_error
|
|
11
|
+
from ..io import clean_path
|
|
12
|
+
from ..print import debug, info, warning
|
|
13
|
+
from .common import ask_install_type, prompt_for_path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Functions
|
|
17
|
+
@handle_error(message="Failed to suggest how to add to PATH (Linux)", error_log=LogLevels.WARNING_TRACEBACK)
|
|
18
|
+
def add_to_path_linux(install_path: str) -> bool:
|
|
19
|
+
""" Suggest how to add install_path to PATH on Linux.
|
|
20
|
+
|
|
21
|
+
Checks the current shell and provides instructions for adding the path
|
|
22
|
+
to the appropriate configuration file (e.g., .bashrc, .zshrc, config.fish).
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
install_path (str): The path to add to the PATH environment variable.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
bool: True if instructions were provided, False otherwise (e.g., unknown shell).
|
|
29
|
+
"""
|
|
30
|
+
shell_config_files: dict[str, str] = {
|
|
31
|
+
"bash": "~/.bashrc",
|
|
32
|
+
"zsh": "~/.zshrc",
|
|
33
|
+
"fish": "~/.config/fish/config.fish"
|
|
34
|
+
}
|
|
35
|
+
current_shell: str = os.environ.get("SHELL", "").split('/')[-1]
|
|
36
|
+
config_file: str | None = shell_config_files.get(current_shell)
|
|
37
|
+
|
|
38
|
+
if config_file:
|
|
39
|
+
export_cmd: str = ""
|
|
40
|
+
if current_shell == "fish":
|
|
41
|
+
export_cmd = f"set -gx PATH $PATH {install_path}"
|
|
42
|
+
else:
|
|
43
|
+
export_cmd = f"export PATH=\"$PATH:{install_path}\"" # Escape quotes for print
|
|
44
|
+
|
|
45
|
+
debug(
|
|
46
|
+
f"To add the installation directory to your PATH, add the following line to your '{config_file}':\n"
|
|
47
|
+
f" {export_cmd}\n"
|
|
48
|
+
f"Then restart your shell or run 'source {config_file}'."
|
|
49
|
+
)
|
|
50
|
+
return True
|
|
51
|
+
else:
|
|
52
|
+
warning(f"Could not determine your shell configuration file. Please add '{install_path}' to your PATH manually.")
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def check_admin_linux() -> bool:
|
|
57
|
+
""" Check if the script is running with root privileges on Linux/macOS.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
bool: True if the effective user ID is 0 (root), False otherwise.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
return os.geteuid() == 0 # type: ignore
|
|
64
|
+
except AttributeError as e:
|
|
65
|
+
# os.geteuid() is not available on all platforms (e.g., Windows)
|
|
66
|
+
# This function should ideally only be called on Linux/macOS.
|
|
67
|
+
warning(f"Could not determine user privileges on this platform: {e}")
|
|
68
|
+
return False
|
|
69
|
+
except Exception as e:
|
|
70
|
+
warning(f"Error checking admin privileges: {e}")
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@handle_error(message="Failed to get installation path (Linux)", error_log=LogLevels.ERROR_TRACEBACK)
|
|
75
|
+
def get_install_path_linux(
|
|
76
|
+
program_name: str,
|
|
77
|
+
ask_global: int = 0,
|
|
78
|
+
add_path: bool = True,
|
|
79
|
+
append_to_path: str = "",
|
|
80
|
+
default_global: str = "/usr/local/bin",
|
|
81
|
+
) -> str:
|
|
82
|
+
""" Get the installation path for the program on Linux/macOS.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
program_name (str): The name of the program to install.
|
|
86
|
+
ask_global (int): 0 = ask for anything, 1 = install globally, 2 = install locally
|
|
87
|
+
add_path (bool): Whether to add the program to the PATH environment variable. (Only if installed globally)
|
|
88
|
+
append_to_path (str): String to append to the installation path when adding to PATH.
|
|
89
|
+
(ex: "bin" if executables are in the bin folder)
|
|
90
|
+
default_global (str): The default global installation path.
|
|
91
|
+
(Default is "/usr/local/bin" which is the most common location for executables on Linux/macOS,
|
|
92
|
+
could be "/opt" or any other directory)
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
str: The chosen installation path, or an empty string if installation is cancelled.
|
|
96
|
+
"""
|
|
97
|
+
# Default paths
|
|
98
|
+
default_local_path: str = clean_path(os.path.join(os.getcwd(), program_name))
|
|
99
|
+
|
|
100
|
+
# Common global locations: /usr/local/bin for executables, /opt/ for self-contained apps
|
|
101
|
+
# We assume 'program_name' might be an executable or a directory, /usr/local/ is safer
|
|
102
|
+
default_global_path: str = clean_path(f"{default_global}/{program_name}") # Or potentially /opt/{program_name}
|
|
103
|
+
|
|
104
|
+
# Ask user for installation type (global/local)
|
|
105
|
+
install_type: str = ask_install_type(ask_global, default_local_path, default_global_path)
|
|
106
|
+
|
|
107
|
+
# Handle global installation choice
|
|
108
|
+
if install_type == 'g':
|
|
109
|
+
if not check_admin_linux():
|
|
110
|
+
warning(
|
|
111
|
+
f"Global installation typically requires sudo privileges to write to "
|
|
112
|
+
f"'{os.path.dirname(default_global_path)}'.\n"
|
|
113
|
+
f"You may need to re-run the script with 'sudo'.\n"
|
|
114
|
+
f"Install locally instead to '{default_local_path}'? (Y/n): "
|
|
115
|
+
)
|
|
116
|
+
if input().lower() == 'n':
|
|
117
|
+
info("Installation cancelled.")
|
|
118
|
+
return ""
|
|
119
|
+
else:
|
|
120
|
+
# Fallback to local path if user agrees
|
|
121
|
+
return prompt_for_path(
|
|
122
|
+
f"Falling back to local installation path: {default_local_path}.",
|
|
123
|
+
default_local_path
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
# User is admin or proceeding with global install anyway
|
|
127
|
+
install_path: str = prompt_for_path(
|
|
128
|
+
f"Default global installation path is {default_global_path}.",
|
|
129
|
+
default_global_path
|
|
130
|
+
)
|
|
131
|
+
if add_path:
|
|
132
|
+
# Suggest adding the *directory* containing the program to PATH,
|
|
133
|
+
# or the path itself if it seems like a directory install
|
|
134
|
+
path_to_add: str = os.path.dirname(install_path) if os.path.isfile(install_path) else install_path
|
|
135
|
+
add_to_path_linux(os.path.join(path_to_add, append_to_path))
|
|
136
|
+
return install_path
|
|
137
|
+
|
|
138
|
+
# Handle local installation choice
|
|
139
|
+
else: # install_type == 'l'
|
|
140
|
+
return prompt_for_path(
|
|
141
|
+
f"Default local installation path is {default_local_path}.",
|
|
142
|
+
default_local_path
|
|
143
|
+
)
|
|
144
|
+
|