stouputils 1.14.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/__init__.py +40 -0
- stouputils/__main__.py +86 -0
- stouputils/_deprecated.py +37 -0
- stouputils/all_doctests.py +160 -0
- stouputils/applications/__init__.py +22 -0
- stouputils/applications/automatic_docs.py +634 -0
- stouputils/applications/upscaler/__init__.py +39 -0
- stouputils/applications/upscaler/config.py +128 -0
- stouputils/applications/upscaler/image.py +247 -0
- stouputils/applications/upscaler/video.py +287 -0
- stouputils/archive.py +344 -0
- stouputils/backup.py +488 -0
- stouputils/collections.py +244 -0
- stouputils/continuous_delivery/__init__.py +27 -0
- stouputils/continuous_delivery/cd_utils.py +243 -0
- stouputils/continuous_delivery/github.py +522 -0
- stouputils/continuous_delivery/pypi.py +130 -0
- stouputils/continuous_delivery/pyproject.py +147 -0
- stouputils/continuous_delivery/stubs.py +86 -0
- stouputils/ctx.py +408 -0
- stouputils/data_science/config/get.py +51 -0
- stouputils/data_science/config/set.py +125 -0
- stouputils/data_science/data_processing/image/__init__.py +66 -0
- stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
- stouputils/data_science/data_processing/image/axis_flip.py +58 -0
- stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
- stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
- stouputils/data_science/data_processing/image/blur.py +59 -0
- stouputils/data_science/data_processing/image/brightness.py +54 -0
- stouputils/data_science/data_processing/image/canny.py +110 -0
- stouputils/data_science/data_processing/image/clahe.py +92 -0
- stouputils/data_science/data_processing/image/common.py +30 -0
- stouputils/data_science/data_processing/image/contrast.py +53 -0
- stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
- stouputils/data_science/data_processing/image/denoise.py +378 -0
- stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
- stouputils/data_science/data_processing/image/invert.py +64 -0
- stouputils/data_science/data_processing/image/laplacian.py +60 -0
- stouputils/data_science/data_processing/image/median_blur.py +52 -0
- stouputils/data_science/data_processing/image/noise.py +59 -0
- stouputils/data_science/data_processing/image/normalize.py +65 -0
- stouputils/data_science/data_processing/image/random_erase.py +66 -0
- stouputils/data_science/data_processing/image/resize.py +69 -0
- stouputils/data_science/data_processing/image/rotation.py +80 -0
- stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
- stouputils/data_science/data_processing/image/sharpening.py +55 -0
- stouputils/data_science/data_processing/image/shearing.py +64 -0
- stouputils/data_science/data_processing/image/threshold.py +64 -0
- stouputils/data_science/data_processing/image/translation.py +71 -0
- stouputils/data_science/data_processing/image/zoom.py +83 -0
- stouputils/data_science/data_processing/image_augmentation.py +118 -0
- stouputils/data_science/data_processing/image_preprocess.py +183 -0
- stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
- stouputils/data_science/data_processing/technique.py +481 -0
- stouputils/data_science/dataset/__init__.py +45 -0
- stouputils/data_science/dataset/dataset.py +292 -0
- stouputils/data_science/dataset/dataset_loader.py +135 -0
- stouputils/data_science/dataset/grouping_strategy.py +296 -0
- stouputils/data_science/dataset/image_loader.py +100 -0
- stouputils/data_science/dataset/xy_tuple.py +696 -0
- stouputils/data_science/metric_dictionnary.py +106 -0
- stouputils/data_science/metric_utils.py +847 -0
- stouputils/data_science/mlflow_utils.py +206 -0
- stouputils/data_science/models/abstract_model.py +149 -0
- stouputils/data_science/models/all.py +85 -0
- stouputils/data_science/models/base_keras.py +765 -0
- stouputils/data_science/models/keras/all.py +38 -0
- stouputils/data_science/models/keras/convnext.py +62 -0
- stouputils/data_science/models/keras/densenet.py +50 -0
- stouputils/data_science/models/keras/efficientnet.py +60 -0
- stouputils/data_science/models/keras/mobilenet.py +56 -0
- stouputils/data_science/models/keras/resnet.py +52 -0
- stouputils/data_science/models/keras/squeezenet.py +233 -0
- stouputils/data_science/models/keras/vgg.py +42 -0
- stouputils/data_science/models/keras/xception.py +38 -0
- stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
- stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
- stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
- stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
- stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
- stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
- stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
- stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
- stouputils/data_science/models/keras_utils/visualizations.py +416 -0
- stouputils/data_science/models/model_interface.py +939 -0
- stouputils/data_science/models/sandbox.py +116 -0
- stouputils/data_science/range_tuple.py +234 -0
- stouputils/data_science/scripts/augment_dataset.py +77 -0
- stouputils/data_science/scripts/exhaustive_process.py +133 -0
- stouputils/data_science/scripts/preprocess_dataset.py +70 -0
- stouputils/data_science/scripts/routine.py +168 -0
- stouputils/data_science/utils.py +285 -0
- stouputils/decorators.py +605 -0
- stouputils/image.py +441 -0
- stouputils/installer/__init__.py +18 -0
- stouputils/installer/common.py +67 -0
- stouputils/installer/downloader.py +101 -0
- stouputils/installer/linux.py +144 -0
- stouputils/installer/main.py +223 -0
- stouputils/installer/windows.py +136 -0
- stouputils/io.py +486 -0
- stouputils/parallel.py +483 -0
- stouputils/print.py +482 -0
- stouputils/py.typed +1 -0
- stouputils/stouputils/__init__.pyi +15 -0
- stouputils/stouputils/_deprecated.pyi +12 -0
- stouputils/stouputils/all_doctests.pyi +46 -0
- stouputils/stouputils/applications/__init__.pyi +2 -0
- stouputils/stouputils/applications/automatic_docs.pyi +106 -0
- stouputils/stouputils/applications/upscaler/__init__.pyi +3 -0
- stouputils/stouputils/applications/upscaler/config.pyi +18 -0
- stouputils/stouputils/applications/upscaler/image.pyi +109 -0
- stouputils/stouputils/applications/upscaler/video.pyi +60 -0
- stouputils/stouputils/archive.pyi +67 -0
- stouputils/stouputils/backup.pyi +109 -0
- stouputils/stouputils/collections.pyi +86 -0
- stouputils/stouputils/continuous_delivery/__init__.pyi +5 -0
- stouputils/stouputils/continuous_delivery/cd_utils.pyi +129 -0
- stouputils/stouputils/continuous_delivery/github.pyi +162 -0
- stouputils/stouputils/continuous_delivery/pypi.pyi +53 -0
- stouputils/stouputils/continuous_delivery/pyproject.pyi +67 -0
- stouputils/stouputils/continuous_delivery/stubs.pyi +39 -0
- stouputils/stouputils/ctx.pyi +211 -0
- stouputils/stouputils/decorators.pyi +252 -0
- stouputils/stouputils/image.pyi +172 -0
- stouputils/stouputils/installer/__init__.pyi +5 -0
- stouputils/stouputils/installer/common.pyi +39 -0
- stouputils/stouputils/installer/downloader.pyi +24 -0
- stouputils/stouputils/installer/linux.pyi +39 -0
- stouputils/stouputils/installer/main.pyi +57 -0
- stouputils/stouputils/installer/windows.pyi +31 -0
- stouputils/stouputils/io.pyi +213 -0
- stouputils/stouputils/parallel.pyi +216 -0
- stouputils/stouputils/print.pyi +136 -0
- stouputils/stouputils/version_pkg.pyi +15 -0
- stouputils/version_pkg.py +189 -0
- stouputils-1.14.0.dist-info/METADATA +178 -0
- stouputils-1.14.0.dist-info/RECORD +140 -0
- stouputils-1.14.0.dist-info/WHEEL +4 -0
- stouputils-1.14.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +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
|
+
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
""" Main module of the installer subpackage for stouputils.
|
|
2
|
+
|
|
3
|
+
Provides functions for installing programs from local zip files or URLs.
|
|
4
|
+
It handles downloading, extracting, and setting up programs in a platform-agnostic way.
|
|
5
|
+
|
|
6
|
+
This module contains the core installation functions that are used by both the Windows
|
|
7
|
+
and Linux/macOS specific modules.
|
|
8
|
+
"""
|
|
9
|
+
# ruff: noqa: F403
|
|
10
|
+
# ruff: noqa: F405
|
|
11
|
+
|
|
12
|
+
# Imports
|
|
13
|
+
import os
|
|
14
|
+
import platform
|
|
15
|
+
import shutil
|
|
16
|
+
import tarfile
|
|
17
|
+
import zipfile
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from tempfile import TemporaryDirectory
|
|
20
|
+
|
|
21
|
+
import requests
|
|
22
|
+
|
|
23
|
+
from ..decorators import LogLevels, handle_error
|
|
24
|
+
from ..print import info, warning
|
|
25
|
+
from .common import *
|
|
26
|
+
from .linux import *
|
|
27
|
+
from .windows import *
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Helper functions
|
|
31
|
+
def extract_archive(
|
|
32
|
+
extraction_path: str,
|
|
33
|
+
temp_dir: str,
|
|
34
|
+
extract_func: Callable[[str], None],
|
|
35
|
+
get_file_list_func: Callable[[], list[str]]
|
|
36
|
+
) -> None:
|
|
37
|
+
""" Helper function to extract archive files with consistent handling.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
extraction_path (str): Path where files should be extracted
|
|
41
|
+
temp_dir (str): Temporary directory for intermediate extraction
|
|
42
|
+
extract_func (Callable[[str], None]): Function to extract the archive
|
|
43
|
+
get_file_list_func (Callable[[], list[str]]): Function to get the list of files in the archive
|
|
44
|
+
"""
|
|
45
|
+
os.makedirs(extraction_path, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
# Check if the archive contains a single folder
|
|
48
|
+
file_list: list[str] = get_file_list_func()
|
|
49
|
+
root_dirs: set[str] = {item.split('/')[0] + '/' for item in file_list if '/' in item}
|
|
50
|
+
|
|
51
|
+
# If all files are in a single root directory
|
|
52
|
+
if len(root_dirs) == 1 and all(item.startswith(next(iter(root_dirs))) for item in file_list):
|
|
53
|
+
# Extract to a temporary location first
|
|
54
|
+
temp_extract_path: str = os.path.join(temp_dir, "extracted")
|
|
55
|
+
os.makedirs(temp_extract_path, exist_ok=True)
|
|
56
|
+
extract_func(temp_extract_path)
|
|
57
|
+
|
|
58
|
+
# Move contents from the single folder to the final path
|
|
59
|
+
single_folder_path: str = os.path.join(temp_extract_path, next(iter(root_dirs)).rstrip('/'))
|
|
60
|
+
for item in os.listdir(single_folder_path):
|
|
61
|
+
src_path: str = os.path.join(single_folder_path, item)
|
|
62
|
+
dst_path: str = os.path.join(extraction_path, item)
|
|
63
|
+
if os.path.isdir(src_path):
|
|
64
|
+
shutil.copytree(src_path, dst_path, dirs_exist_ok=True)
|
|
65
|
+
else:
|
|
66
|
+
shutil.copy2(src_path, dst_path)
|
|
67
|
+
debug(f"Extracted program contents from single folder to '{extraction_path}'")
|
|
68
|
+
else:
|
|
69
|
+
# Normal extraction if not a single folder
|
|
70
|
+
extract_func(extraction_path)
|
|
71
|
+
debug(f"Extracted program to '{extraction_path}'")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Functions
|
|
75
|
+
def get_install_path(
|
|
76
|
+
program_name: str,
|
|
77
|
+
platform_str: str = platform.system(),
|
|
78
|
+
ask_global: int = 0,
|
|
79
|
+
add_path: bool = True,
|
|
80
|
+
append_to_path: str = "",
|
|
81
|
+
) -> str:
|
|
82
|
+
""" Get the installation path for the program on the current platform.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
program_name (str): The name of the program to install.
|
|
86
|
+
platform_str (str): The platform to get the installation path for.
|
|
87
|
+
ask_global (int): Whether to ask the user for a path, 0 = ask, 1 = install globally, 2 = install locally.
|
|
88
|
+
add_path (bool): Whether to add the program to the PATH environment variable.
|
|
89
|
+
append_to_path (str): String to append to the installation path when adding to PATH.
|
|
90
|
+
(ex: "bin" if executables are in the bin folder)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
str: The installation path for the program.
|
|
94
|
+
"""
|
|
95
|
+
platform_str = str(platform_str).lower()
|
|
96
|
+
if platform_str == "windows":
|
|
97
|
+
return get_install_path_windows(program_name, ask_global, add_path=add_path, append_to_path=append_to_path)
|
|
98
|
+
elif platform_str == "linux":
|
|
99
|
+
return get_install_path_linux(program_name, ask_global, add_path=add_path, append_to_path=append_to_path)
|
|
100
|
+
else:
|
|
101
|
+
warning(f"Unsupported platform for automatic install path: {platform_str}")
|
|
102
|
+
return ""
|
|
103
|
+
|
|
104
|
+
def add_to_path(install_path: str, platform_str: str = platform.system()) -> bool:
|
|
105
|
+
""" Add the program to the PATH environment variable.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
install_path (str): The path to the program to add to the PATH environment variable.
|
|
109
|
+
platform_str (str): The platform you are running on (ex: "Windows", "Linux", "Darwin", ...),
|
|
110
|
+
we use this to determine the installation path if not provided.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
bool: True if add to PATH was successful, False otherwise.
|
|
114
|
+
"""
|
|
115
|
+
platform_str = str(platform_str).lower()
|
|
116
|
+
if platform_str == "windows":
|
|
117
|
+
return add_to_path_windows(install_path) is True
|
|
118
|
+
elif platform_str == "linux":
|
|
119
|
+
return add_to_path_linux(install_path) is True
|
|
120
|
+
else:
|
|
121
|
+
warning(f"Unsupported platform for automatic add to PATH: {platform_str}")
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
@handle_error(message="Failed during program installation", error_log=LogLevels.WARNING_TRACEBACK)
|
|
125
|
+
def install_program(
|
|
126
|
+
input_path: str,
|
|
127
|
+
install_path: str = "",
|
|
128
|
+
platform_str: str = platform.system(),
|
|
129
|
+
program_name: str = "",
|
|
130
|
+
add_path: bool = True,
|
|
131
|
+
append_to_path: str = "",
|
|
132
|
+
) -> bool:
|
|
133
|
+
""" Install a program to a specific path from a local zip file or URL.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
input_path (str): Path to a zip file or a download URL.
|
|
137
|
+
install_path (str): The directory to extract the program into, we ask user for a path if not provided.
|
|
138
|
+
platform_str (str): The platform you are running on (ex: "Windows", "Linux", "Darwin", ...),
|
|
139
|
+
we use this to determine the installation path if not provided.
|
|
140
|
+
add_path (bool): Whether to add the program to the PATH environment variable.
|
|
141
|
+
program_name (str): Override the program name, we get it from the input path if not provided.
|
|
142
|
+
append_to_path (str): String to append to the installation path when adding to PATH.
|
|
143
|
+
(ex: "bin" if executables are in the bin folder)
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
bool: True if installation was successful, False otherwise.
|
|
147
|
+
"""
|
|
148
|
+
# Get program name from input path if not provided
|
|
149
|
+
# (ex: "https://example.com/program.zip" -> "program", "/var/www/program.exe" -> "program")
|
|
150
|
+
if not program_name:
|
|
151
|
+
program_name = os.path.splitext(os.path.basename(input_path))[0]
|
|
152
|
+
|
|
153
|
+
# If no install path is provided, ask the user for one
|
|
154
|
+
final_install_path: str = install_path
|
|
155
|
+
if not install_path:
|
|
156
|
+
final_install_path = get_install_path(program_name, platform_str, add_path=add_path, append_to_path=append_to_path)
|
|
157
|
+
if not final_install_path:
|
|
158
|
+
warning("Failed to get installation path, please provide a path to install the program to.")
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
# Create a temporary directory
|
|
162
|
+
with TemporaryDirectory() as temp_dir:
|
|
163
|
+
temp_dir: str
|
|
164
|
+
program_path: str = ""
|
|
165
|
+
|
|
166
|
+
# Download the program if it's a URL
|
|
167
|
+
if input_path.startswith("http"):
|
|
168
|
+
info(f"Downloading program from '{input_path}'")
|
|
169
|
+
response: requests.Response = requests.get(input_path)
|
|
170
|
+
if response.status_code != 200:
|
|
171
|
+
warning(f"Failed to download program from '{input_path}', reason: {response.reason}")
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
# Save the program to a temporary directory
|
|
175
|
+
temp_file: str = os.path.join(temp_dir, "program.zip")
|
|
176
|
+
with open(temp_file, "wb") as f:
|
|
177
|
+
f.write(response.content)
|
|
178
|
+
program_path = temp_file
|
|
179
|
+
debug(f"Downloaded program to '{program_path}'")
|
|
180
|
+
else:
|
|
181
|
+
program_path = input_path
|
|
182
|
+
debug(f"Using local program path '{program_path}'")
|
|
183
|
+
|
|
184
|
+
# Extract the program if it's a zip file
|
|
185
|
+
if program_path.endswith(".zip"):
|
|
186
|
+
with zipfile.ZipFile(program_path, "r") as zip_ref:
|
|
187
|
+
extract_archive(
|
|
188
|
+
final_install_path,
|
|
189
|
+
temp_dir,
|
|
190
|
+
lambda path: zip_ref.extractall(path),
|
|
191
|
+
lambda: zip_ref.namelist()
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Extract the program if it's a tar.xz file
|
|
195
|
+
elif program_path.endswith(".tar.xz"):
|
|
196
|
+
with tarfile.open(program_path, "r:xz") as tar_ref:
|
|
197
|
+
extract_archive(
|
|
198
|
+
final_install_path,
|
|
199
|
+
temp_dir,
|
|
200
|
+
lambda path: tar_ref.extractall(path),
|
|
201
|
+
lambda: tar_ref.getnames()
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Else, it's a directory so we just need to copy it
|
|
205
|
+
elif os.path.isdir(program_path):
|
|
206
|
+
shutil.copytree(program_path, final_install_path)
|
|
207
|
+
|
|
208
|
+
else:
|
|
209
|
+
warning(f"Failed to install program, input path is not a zip, tar.xz file or directory: '{program_path}'")
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
# If add_path is True, and the installation path was provided, we add it to the PATH environment variable
|
|
213
|
+
if add_path and install_path:
|
|
214
|
+
if not add_to_path(os.path.join(final_install_path, append_to_path), platform_str):
|
|
215
|
+
warning(
|
|
216
|
+
f"Failed to add program to PATH, please add it manually to your PATH environment variable:\n"
|
|
217
|
+
f"{final_install_path}"
|
|
218
|
+
)
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
# If we get here, the program was installed successfully
|
|
222
|
+
return True
|
|
223
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
""" Installer module for Windows specific functions.
|
|
2
|
+
|
|
3
|
+
Provides Windows specific implementations for checking administrator privileges,
|
|
4
|
+
determining appropriate installation paths (global/local), and modifying
|
|
5
|
+
the user'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 add to PATH (Windows)", error_log=LogLevels.WARNING_TRACEBACK)
|
|
18
|
+
def add_to_path_windows(install_path: str) -> bool | None:
|
|
19
|
+
""" Add install_path to the User PATH environment variable on Windows.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
install_path (str): The path to add to the User PATH environment variable.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
bool | None: True if the path was added to the User PATH environment variable, None otherwise.
|
|
26
|
+
"""
|
|
27
|
+
# Convert install_path to a Windows path if it's not already
|
|
28
|
+
install_path = install_path.replace("/", "\\")
|
|
29
|
+
os.makedirs(install_path, exist_ok=True)
|
|
30
|
+
|
|
31
|
+
# Get current user PATH
|
|
32
|
+
import winreg
|
|
33
|
+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment", 0, winreg.KEY_READ | winreg.KEY_WRITE) as key:
|
|
34
|
+
|
|
35
|
+
# Get the number of values in the registry key
|
|
36
|
+
num_values = winreg.QueryInfoKey(key)[1]
|
|
37
|
+
|
|
38
|
+
# Find the index of the 'Path' value
|
|
39
|
+
path_index = -1
|
|
40
|
+
for i in range(num_values):
|
|
41
|
+
if winreg.EnumValue(key, i)[0] == 'Path':
|
|
42
|
+
path_index = i
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
# Get the current path value
|
|
46
|
+
current_path: str = winreg.EnumValue(key, path_index)[1]
|
|
47
|
+
|
|
48
|
+
# Check if path is already present
|
|
49
|
+
if install_path not in current_path.split(';'):
|
|
50
|
+
new_path: str = f"{current_path};{install_path}"
|
|
51
|
+
winreg.SetValueEx(key, "Path", 0, winreg.REG_EXPAND_SZ, new_path)
|
|
52
|
+
debug(f"Added '{install_path}' to user PATH. Please restart your terminal for changes to take effect.")
|
|
53
|
+
else:
|
|
54
|
+
debug(f"'{install_path}' is already in user PATH.")
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check_admin_windows() -> bool:
|
|
59
|
+
""" Check if the script is running with administrator privileges on Windows. """
|
|
60
|
+
try:
|
|
61
|
+
import ctypes
|
|
62
|
+
return ctypes.windll.shell32.IsUserAnAdmin() != 0
|
|
63
|
+
except Exception:
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@handle_error(message="Failed to get installation path (Windows)", error_log=LogLevels.ERROR_TRACEBACK)
|
|
68
|
+
def get_install_path_windows(
|
|
69
|
+
program_name: str,
|
|
70
|
+
ask_global: int = 0,
|
|
71
|
+
add_path: bool = True,
|
|
72
|
+
append_to_path: str = "",
|
|
73
|
+
default_global: str = os.environ.get("ProgramFiles", "C:\\Program Files")
|
|
74
|
+
) -> str:
|
|
75
|
+
""" Get the installation path for the program
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
program_name (str): The name of the program to install.
|
|
79
|
+
ask_global (int): 0 = ask for anything, 1 = install globally, 2 = install locally
|
|
80
|
+
add_path (bool): Whether to add the program to the PATH environment variable. (Only if installed globally)
|
|
81
|
+
append_to_path (str): String to append to the installation path when adding to PATH.
|
|
82
|
+
(ex: "bin" if executables are in the bin folder)
|
|
83
|
+
default_global (str): The default global installation path.
|
|
84
|
+
(Default is "C:\\Program Files" which is the most common location for executables on Windows)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
str: The installation path.
|
|
88
|
+
"""
|
|
89
|
+
# Default path is located in the current working directory
|
|
90
|
+
default_local_path: str = clean_path(os.path.join(os.getcwd(), program_name))
|
|
91
|
+
|
|
92
|
+
# Define default global path (used in prompt even if not chosen initially)
|
|
93
|
+
default_global_path: str = clean_path(os.path.join(default_global, program_name))
|
|
94
|
+
|
|
95
|
+
# Ask user for installation type (global/local)
|
|
96
|
+
install_type: str = ask_install_type(ask_global, default_local_path, default_global_path)
|
|
97
|
+
|
|
98
|
+
# If the user wants to install globally,
|
|
99
|
+
if install_type == 'g':
|
|
100
|
+
|
|
101
|
+
# Check if the user has admin privileges,
|
|
102
|
+
if not check_admin_windows():
|
|
103
|
+
|
|
104
|
+
# If the user doesn't have admin privileges, fallback to local
|
|
105
|
+
warning(
|
|
106
|
+
f"Global installation requires administrator privileges. Please re-run as administrator.\n"
|
|
107
|
+
f"Install locally instead to '{default_local_path}'? (Y/n): "
|
|
108
|
+
)
|
|
109
|
+
if input().lower() == 'n':
|
|
110
|
+
info("Installation cancelled.")
|
|
111
|
+
return ""
|
|
112
|
+
else:
|
|
113
|
+
# Fallback to local path if user agrees
|
|
114
|
+
return prompt_for_path(
|
|
115
|
+
f"Falling back to local installation path: {default_local_path}.",
|
|
116
|
+
default_local_path
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# If the user has admin privileges,
|
|
120
|
+
else:
|
|
121
|
+
# Ask it user wants to override the default global install path
|
|
122
|
+
install_path: str = prompt_for_path(
|
|
123
|
+
f"Default global installation path is {default_global_path}.",
|
|
124
|
+
default_global_path
|
|
125
|
+
)
|
|
126
|
+
if add_path:
|
|
127
|
+
add_to_path_windows(os.path.join(install_path, append_to_path))
|
|
128
|
+
return install_path
|
|
129
|
+
|
|
130
|
+
# Local install
|
|
131
|
+
else: # install_type == 'l'
|
|
132
|
+
return prompt_for_path(
|
|
133
|
+
f"Default local installation path is {default_local_path}.",
|
|
134
|
+
default_local_path
|
|
135
|
+
)
|
|
136
|
+
|