nuf 0.0.2__tar.gz
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.
- nuf-0.0.2/LICENSE +21 -0
- nuf-0.0.2/PKG-INFO +57 -0
- nuf-0.0.2/README.md +41 -0
- nuf-0.0.2/pyproject.toml +26 -0
- nuf-0.0.2/setup.cfg +4 -0
- nuf-0.0.2/src/nuf/__init__.py +20 -0
- nuf-0.0.2/src/nuf/core.py +2 -0
- nuf-0.0.2/src/nuf/file_utils.py +456 -0
- nuf-0.0.2/src/nuf/format_utils.py +95 -0
- nuf-0.0.2/src/nuf/message_utils.py +42 -0
- nuf-0.0.2/src/nuf.egg-info/PKG-INFO +57 -0
- nuf-0.0.2/src/nuf.egg-info/SOURCES.txt +13 -0
- nuf-0.0.2/src/nuf.egg-info/dependency_links.txt +1 -0
- nuf-0.0.2/src/nuf.egg-info/requires.txt +3 -0
- nuf-0.0.2/src/nuf.egg-info/top_level.txt +1 -0
nuf-0.0.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nathan Falvey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
nuf-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nuf
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A versatile multi-file utility module, a series of useful code that is referenced from various projects that i repeatedly use.
|
|
5
|
+
Author: Nathan Falvey
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: colorama
|
|
13
|
+
Requires-Dist: send2trash
|
|
14
|
+
Requires-Dist: pathlib
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# nates-useful-functions-python-module (nuf)
|
|
18
|
+
|
|
19
|
+
A versatile, multi-file utility module that aggregates a series of useful Python code referenced repeatedly across various projects.
|
|
20
|
+
|
|
21
|
+
## Package Structure
|
|
22
|
+
|
|
23
|
+
The package is structured as follows under the `src` directory:
|
|
24
|
+
- `src/nuf/__init__.py`: Initializes the package and dynamically imports all Python files/modules in its directory.
|
|
25
|
+
- `src/nuf/core.py`: Core functionality.
|
|
26
|
+
- `src/nuf/file_utils.py`: Utilities for working with files.
|
|
27
|
+
- `src/nuf/message_utils.py`: Utilities for messaging and logs.
|
|
28
|
+
- `src/nuf/format_utils.py`: Utilities for formatting and time conversions.
|
|
29
|
+
|
|
30
|
+
## Dynamic Importing
|
|
31
|
+
|
|
32
|
+
This module automatically discovers and imports all Python files present inside the `src/nuf/` folder. When you add a new Python file (e.g. `src/nuf/new_helper.py`), it is automatically available as `nuf.new_helper` without needing to modify `__init__.py`.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Here is a quick example of how to import and use the package:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import nuf
|
|
40
|
+
|
|
41
|
+
# Access functions from core.py
|
|
42
|
+
print(nuf.core.core_func())
|
|
43
|
+
|
|
44
|
+
# Access functions from file_utils.py
|
|
45
|
+
print(nuf.file_utils.file_func())
|
|
46
|
+
|
|
47
|
+
# Access functions from message_utils.py
|
|
48
|
+
print(nuf.message_utils.message_func())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Running Tests
|
|
52
|
+
|
|
53
|
+
To run the local verification test suite, run:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python test_nuf.py
|
|
57
|
+
```
|
nuf-0.0.2/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# nates-useful-functions-python-module (nuf)
|
|
2
|
+
|
|
3
|
+
A versatile, multi-file utility module that aggregates a series of useful Python code referenced repeatedly across various projects.
|
|
4
|
+
|
|
5
|
+
## Package Structure
|
|
6
|
+
|
|
7
|
+
The package is structured as follows under the `src` directory:
|
|
8
|
+
- `src/nuf/__init__.py`: Initializes the package and dynamically imports all Python files/modules in its directory.
|
|
9
|
+
- `src/nuf/core.py`: Core functionality.
|
|
10
|
+
- `src/nuf/file_utils.py`: Utilities for working with files.
|
|
11
|
+
- `src/nuf/message_utils.py`: Utilities for messaging and logs.
|
|
12
|
+
- `src/nuf/format_utils.py`: Utilities for formatting and time conversions.
|
|
13
|
+
|
|
14
|
+
## Dynamic Importing
|
|
15
|
+
|
|
16
|
+
This module automatically discovers and imports all Python files present inside the `src/nuf/` folder. When you add a new Python file (e.g. `src/nuf/new_helper.py`), it is automatically available as `nuf.new_helper` without needing to modify `__init__.py`.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Here is a quick example of how to import and use the package:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import nuf
|
|
24
|
+
|
|
25
|
+
# Access functions from core.py
|
|
26
|
+
print(nuf.core.core_func())
|
|
27
|
+
|
|
28
|
+
# Access functions from file_utils.py
|
|
29
|
+
print(nuf.file_utils.file_func())
|
|
30
|
+
|
|
31
|
+
# Access functions from message_utils.py
|
|
32
|
+
print(nuf.message_utils.message_func())
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Running Tests
|
|
36
|
+
|
|
37
|
+
To run the local verification test suite, run:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python test_nuf.py
|
|
41
|
+
```
|
nuf-0.0.2/pyproject.toml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "colorama", "send2trash"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nuf"
|
|
7
|
+
version = "0.0.2"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Nathan Falvey" },
|
|
10
|
+
]
|
|
11
|
+
description = "A versatile multi-file utility module, a series of useful code that is referenced from various projects that i repeatedly use."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"colorama",
|
|
21
|
+
"send2trash",
|
|
22
|
+
"pathlib"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["src"]
|
nuf-0.0.2/setup.cfg
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import pkgutil
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__name__ = "nuf"
|
|
6
|
+
__version__ = "dev_testing"
|
|
7
|
+
|
|
8
|
+
# Dynamically import all python files (modules) in the current package directory
|
|
9
|
+
for _, module_name, _ in pkgutil.iter_modules(__path__):
|
|
10
|
+
# Import the module dynamically
|
|
11
|
+
module = importlib.import_module(f"{__name__}.{module_name}")
|
|
12
|
+
# Expose the module in the package namespace
|
|
13
|
+
globals()[module_name] = module
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
import sys
|
|
19
|
+
print("You are running the nuf package directly, which is not recommended. Please import this package in your own scripts instead.")
|
|
20
|
+
sys.exit(1)
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
from nuf.message_utils import print_success
|
|
2
|
+
import logging
|
|
3
|
+
import shutil
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Union, List
|
|
7
|
+
|
|
8
|
+
# Set up logging
|
|
9
|
+
logger = logging.getLogger("nuf.file_utils")
|
|
10
|
+
|
|
11
|
+
# Custom Exception Hierarchy
|
|
12
|
+
class FileUtilsError(Exception):
|
|
13
|
+
"""Base exception for file_utils operations."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
class PathNotFoundError(FileUtilsError):
|
|
17
|
+
"""Raised when a file or directory is not found."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
class PermissionDeniedError(FileUtilsError):
|
|
21
|
+
"""Raised when permission is denied for a file or directory operation."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
class DirectoryNotEmptyError(FileUtilsError):
|
|
25
|
+
"""Raised when trying to delete a non-empty directory using a strict rmdir."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
class OperationFailedError(FileUtilsError):
|
|
29
|
+
"""Raised when an OS or file system operation fails."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
# Dummy function for linting
|
|
33
|
+
def file_func() -> str:
|
|
34
|
+
return "Hello from file_utils"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def check_file_exists(path: Union[str, Path]) -> bool:
|
|
38
|
+
"""Checks if a file exists at the given path."""
|
|
39
|
+
try:
|
|
40
|
+
return Path(path).is_file()
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.debug("Failed to check file existence for %s: %s", path, e)
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def check_dir_exists(path: Union[str, Path]) -> bool:
|
|
47
|
+
"""Checks if a directory exists at the given path."""
|
|
48
|
+
try:
|
|
49
|
+
return Path(path).is_dir()
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.debug("Failed to check directory existence for %s: %s", path, e)
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_file_size(path: Union[str, Path]) -> int:
|
|
56
|
+
"""Returns the size of the file in bytes.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
PathNotFoundError: If the file does not exist.
|
|
60
|
+
PermissionDeniedError: If access is denied.
|
|
61
|
+
OperationFailedError: For other OS/filesystem errors.
|
|
62
|
+
"""
|
|
63
|
+
p = Path(path)
|
|
64
|
+
try:
|
|
65
|
+
if not p.is_file():
|
|
66
|
+
raise PathNotFoundError(f"File not found: {p}")
|
|
67
|
+
return p.stat().st_size
|
|
68
|
+
except PathNotFoundError:
|
|
69
|
+
raise
|
|
70
|
+
except PermissionError as e:
|
|
71
|
+
raise PermissionDeniedError(f"Permission denied accessing size of: {p}. Original error: {e}")
|
|
72
|
+
except OSError as e:
|
|
73
|
+
raise OperationFailedError(f"Failed to get file size for: {p}. Original error: {e}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def create_dir(path: Union[str, Path], parents: bool = True, exist_ok: bool = True) -> bool:
|
|
77
|
+
"""Creates a directory at the given path.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
PermissionDeniedError: If permission is denied.
|
|
81
|
+
OperationFailedError: If creation fails due to other OS/file system errors.
|
|
82
|
+
"""
|
|
83
|
+
p = Path(path)
|
|
84
|
+
try:
|
|
85
|
+
p.mkdir(parents=parents, exist_ok=exist_ok)
|
|
86
|
+
return True
|
|
87
|
+
except PermissionError as e:
|
|
88
|
+
raise PermissionDeniedError(f"Permission denied creating directory: {p}. Original error: {e}")
|
|
89
|
+
except OSError as e:
|
|
90
|
+
raise OperationFailedError(f"Failed to create directory: {p}. Original error: {e}")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def delete_dir(path: Union[str, Path], recursive: bool = False) -> bool:
|
|
94
|
+
"""Deletes a directory. If recursive is True, deletes all its contents.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
PathNotFoundError: If the directory does not exist.
|
|
98
|
+
PermissionDeniedError: If permission is denied.
|
|
99
|
+
DirectoryNotEmptyError: If directory is not empty and recursive is False.
|
|
100
|
+
OperationFailedError: If deletion fails.
|
|
101
|
+
"""
|
|
102
|
+
p = Path(path)
|
|
103
|
+
try:
|
|
104
|
+
if not p.is_dir():
|
|
105
|
+
raise PathNotFoundError(f"Directory not found: {p}")
|
|
106
|
+
|
|
107
|
+
if recursive:
|
|
108
|
+
shutil.rmtree(p)
|
|
109
|
+
else:
|
|
110
|
+
p.rmdir()
|
|
111
|
+
return True
|
|
112
|
+
except PathNotFoundError:
|
|
113
|
+
raise
|
|
114
|
+
except PermissionError as e:
|
|
115
|
+
raise PermissionDeniedError(f"Permission denied deleting directory: {p}. Original error: {e}")
|
|
116
|
+
except OSError as e:
|
|
117
|
+
import errno
|
|
118
|
+
# Error code for Directory Not Empty is ENOTEMPTY or EEXIST
|
|
119
|
+
if e.errno in (errno.ENOTEMPTY, errno.EEXIST):
|
|
120
|
+
raise DirectoryNotEmptyError(f"Directory not empty: {p}. Set recursive=True to delete contents.")
|
|
121
|
+
raise OperationFailedError(f"Failed to delete directory: {p}. Original error: {e}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def create_file(path: Union[str, Path], exist_ok: bool = True) -> bool:
|
|
125
|
+
"""Creates an empty file at the given path.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
PermissionDeniedError: If permission is denied.
|
|
129
|
+
OperationFailedError: If creation fails.
|
|
130
|
+
"""
|
|
131
|
+
p = Path(path)
|
|
132
|
+
try:
|
|
133
|
+
if p.is_file() and not exist_ok:
|
|
134
|
+
raise OperationFailedError(f"File already exists: {p}")
|
|
135
|
+
p.touch(exist_ok=exist_ok)
|
|
136
|
+
return True
|
|
137
|
+
except PermissionError as e:
|
|
138
|
+
raise PermissionDeniedError(f"Permission denied creating file: {p}. Original error: {e}")
|
|
139
|
+
except OSError as e:
|
|
140
|
+
raise OperationFailedError(f"Failed to create file: {p}. Original error: {e}")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def append_line_to_file(path: Union[str, Path], line: str, encoding: str = 'utf-8') -> bool:
|
|
144
|
+
"""Writes a line to a file (creates parent directories if needed).
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
PermissionDeniedError: If permission is denied.
|
|
148
|
+
OperationFailedError: If write operation fails.
|
|
149
|
+
"""
|
|
150
|
+
p = Path(path)
|
|
151
|
+
try:
|
|
152
|
+
if not p.parent.exists():
|
|
153
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
|
|
155
|
+
with open(p, 'a', encoding=encoding) as file:
|
|
156
|
+
file.write(line + '\n')
|
|
157
|
+
return True
|
|
158
|
+
except PermissionError as e:
|
|
159
|
+
raise PermissionDeniedError(f"Permission denied writing to file: {p}. Original error: {e}")
|
|
160
|
+
except OSError as e:
|
|
161
|
+
raise OperationFailedError(f"Failed to write line to file: {p}. Original error: {e}")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def return_lines_from_file(path: Union[str, Path], encoding: str = 'utf-8') -> List[str]:
|
|
165
|
+
"""Returns a list of lines from a file.
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
PathNotFoundError: If file does not exist.
|
|
169
|
+
PermissionDeniedError: If permission is denied.
|
|
170
|
+
OperationFailedError: If read operation fails.
|
|
171
|
+
"""
|
|
172
|
+
p = Path(path)
|
|
173
|
+
try:
|
|
174
|
+
if not p.is_file():
|
|
175
|
+
raise PathNotFoundError(f"File not found: {p}")
|
|
176
|
+
with open(p, 'r', encoding=encoding) as file:
|
|
177
|
+
return file.readlines()
|
|
178
|
+
except PathNotFoundError:
|
|
179
|
+
raise
|
|
180
|
+
except PermissionError as e:
|
|
181
|
+
raise PermissionDeniedError(f"Permission denied reading file: {p}. Original error: {e}")
|
|
182
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
183
|
+
raise OperationFailedError(f"Failed to read lines from file: {p}. Original error: {e}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def stream_files(path: Union[str, Path], recursive: bool = False) -> object:
|
|
187
|
+
""" Streams a list of filenames from a directory, paths are chosen by extension
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
path (Union[str, Path]): The path to the directory to stream files from.
|
|
191
|
+
recursive (bool): Whether to stream files recursively.
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
PathNotFoundError: If file does not exist.
|
|
195
|
+
PermissionDeniedError: If permission is denied.
|
|
196
|
+
OperationFailedError: If read operation fails.
|
|
197
|
+
|
|
198
|
+
Yields:
|
|
199
|
+
Iterator[Path]: An iterator of paths to files.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
p = Path(path)
|
|
203
|
+
if not p.is_dir():
|
|
204
|
+
raise PathNotFoundError(f"Directory not found: {p}")
|
|
205
|
+
|
|
206
|
+
if recursive:
|
|
207
|
+
files = p.glob("**/*")
|
|
208
|
+
else:
|
|
209
|
+
files = p.glob("*")
|
|
210
|
+
|
|
211
|
+
for file in files:
|
|
212
|
+
if file.is_file():
|
|
213
|
+
yield file
|
|
214
|
+
|
|
215
|
+
def stream_files_by_extension(path: Union[str, Path], extensions: list, recursive: bool = False) -> object:
|
|
216
|
+
""" Streams a list of filenames from a directory, paths are chosen by extension
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
path (Union[str, Path]): The path to the directory to stream files from.
|
|
220
|
+
extensions (list): A list of extensions to stream files by.
|
|
221
|
+
recursive (bool): Whether to stream files recursively.
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
PathNotFoundError: If file does not exist.
|
|
225
|
+
PermissionDeniedError: If permission is denied.
|
|
226
|
+
OperationFailedError: If read operation fails.
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
p = Path(path)
|
|
231
|
+
if not p.is_dir():
|
|
232
|
+
raise PathNotFoundError(f"Directory not found: {p}")
|
|
233
|
+
|
|
234
|
+
if recursive:
|
|
235
|
+
files = p.glob("**/*")
|
|
236
|
+
else:
|
|
237
|
+
files = p.glob("*")
|
|
238
|
+
|
|
239
|
+
for file in files:
|
|
240
|
+
if file.is_file() and file.suffix in extensions:
|
|
241
|
+
yield file
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def list_files_by_extension(path: Union[str, Path], extensions: list, recursive: bool = False) -> List[Path]:
|
|
245
|
+
""" Returns a list of filenames from a directory, paths are chosen by extension
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
path (Union[str, Path]): The path to the directory to list files from.
|
|
249
|
+
extensions (list): A list of extensions to list files by.
|
|
250
|
+
recursive (bool): Whether to list files recursively.
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
PathNotFoundError: If file does not exist.
|
|
254
|
+
PermissionDeniedError: If permission is denied.
|
|
255
|
+
OperationFailedError: If read operation fails.
|
|
256
|
+
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
p = Path(path)
|
|
260
|
+
if not p.is_dir():
|
|
261
|
+
raise PathNotFoundError(f"Directory not found: {p}")
|
|
262
|
+
|
|
263
|
+
if recursive:
|
|
264
|
+
files = p.glob("**/*")
|
|
265
|
+
else:
|
|
266
|
+
files = p.glob("*")
|
|
267
|
+
|
|
268
|
+
return [file for file in files if file.is_file() and file.suffix in extensions]
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def get_files_by_extension(path: Union[str, Path], extensions: list, recursive: bool = False) -> List[Path]:
|
|
272
|
+
""" Returns a list of filenames from a directory, paths are chosen by extension
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
path (Union[str, Path]): The path to the directory to get files from.
|
|
276
|
+
extensions (list): A list of extensions to get files by.
|
|
277
|
+
recursive (bool): Whether to get files recursively.
|
|
278
|
+
|
|
279
|
+
Raises:
|
|
280
|
+
PathNotFoundError: If file does not exist.
|
|
281
|
+
PermissionDeniedError: If permission is denied.
|
|
282
|
+
OperationFailedError: If read operation fails.
|
|
283
|
+
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
p = Path(path)
|
|
287
|
+
if not p.is_dir():
|
|
288
|
+
raise PathNotFoundError(f"Directory not found: {p}")
|
|
289
|
+
|
|
290
|
+
if recursive:
|
|
291
|
+
files = p.glob("**/*")
|
|
292
|
+
else:
|
|
293
|
+
files = p.glob("*")
|
|
294
|
+
|
|
295
|
+
return [file for file in files if file.is_file() and file.suffix in extensions]
|
|
296
|
+
|
|
297
|
+
def sort_files_by_time(files: list, reverse: bool = False) -> List[Path]:
|
|
298
|
+
""" Sorts a list of files by time
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
files (list): List of files to sort
|
|
302
|
+
reverse (bool, optional): Reverse the sort order. Defaults to False.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
List[Path]: Sorted list of files
|
|
306
|
+
|
|
307
|
+
Raises:
|
|
308
|
+
PermissionDeniedError: If permission is denied.
|
|
309
|
+
OperationFailedError: If read operation fails.
|
|
310
|
+
|
|
311
|
+
"""
|
|
312
|
+
return sorted(files, key=lambda x: x.stat().st_mtime, reverse=reverse)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def sort_files_by_size(files: list, reverse: bool = False) -> List[Path]:
|
|
316
|
+
""" Sorts a list of files by size
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
files (list): List of files to sort
|
|
320
|
+
reverse (bool, optional): Reverse the sort order. Defaults to False.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
List[Path]: Sorted list of files
|
|
324
|
+
|
|
325
|
+
Raises:
|
|
326
|
+
PermissionDeniedError: If permission is denied.
|
|
327
|
+
OperationFailedError: If read operation fails.
|
|
328
|
+
|
|
329
|
+
"""
|
|
330
|
+
return sorted(files, key=lambda x: x.stat().st_size, reverse=reverse)
|
|
331
|
+
|
|
332
|
+
def sort_files_by_name(files: list, reverse: bool = False) -> List[Path]:
|
|
333
|
+
""" Sorts a list of files by name
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
files (list): List of files to sort
|
|
337
|
+
reverse (bool, optional): Reverse the sort order. Defaults to False.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
List[Path]: Sorted list of files
|
|
341
|
+
|
|
342
|
+
Raises:
|
|
343
|
+
PermissionDeniedError: If permission is denied.
|
|
344
|
+
OperationFailedError: If read operation fails.
|
|
345
|
+
|
|
346
|
+
"""
|
|
347
|
+
return sorted(files, key=lambda x: x.name, reverse=reverse)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def calculate_checksum_of_file(file_path: Union[str, Path], chunk_size: int = 8192) -> str:
|
|
351
|
+
""" Calculates the checksum of a file
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
file_path (Union[str, Path]): The path to the file to calculate the checksum of.
|
|
355
|
+
chunk_size (int, optional): The chunk size to read the file in. Defaults to 8192.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
str: The checksum of the file.
|
|
359
|
+
|
|
360
|
+
Raises:
|
|
361
|
+
PathNotFoundError: If file does not exist.
|
|
362
|
+
PermissionDeniedError: If permission is denied.
|
|
363
|
+
OperationFailedError: If read operation fails.
|
|
364
|
+
|
|
365
|
+
"""
|
|
366
|
+
import hashlib
|
|
367
|
+
p = Path(file_path)
|
|
368
|
+
if not p.is_file():
|
|
369
|
+
raise PathNotFoundError(f"File not found: {p}")
|
|
370
|
+
checksum = hashlib.md5()
|
|
371
|
+
with open(p, 'rb') as file:
|
|
372
|
+
while True:
|
|
373
|
+
chunk = file.read(chunk_size)
|
|
374
|
+
if not chunk:
|
|
375
|
+
break
|
|
376
|
+
checksum.update(chunk)
|
|
377
|
+
return checksum.hexdigest()
|
|
378
|
+
|
|
379
|
+
# File deletion functions
|
|
380
|
+
|
|
381
|
+
def delete_file(file_path: Union[str, Path]) -> None:
|
|
382
|
+
""" Deletes a file
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
file_path (Union[str, Path]): The path to the file to delete.
|
|
386
|
+
|
|
387
|
+
Raises:
|
|
388
|
+
PathNotFoundError: If file does not exist.
|
|
389
|
+
PermissionDeniedError: If permission is denied.
|
|
390
|
+
OperationFailedError: If delete operation fails.
|
|
391
|
+
|
|
392
|
+
"""
|
|
393
|
+
p = Path(file_path)
|
|
394
|
+
if not p.is_file():
|
|
395
|
+
raise PathNotFoundError(f"File not found: {p}")
|
|
396
|
+
try:
|
|
397
|
+
p.unlink()
|
|
398
|
+
except PermissionError as e:
|
|
399
|
+
raise PermissionDeniedError(f"Permission denied deleting file: {p}. Original error: {e}")
|
|
400
|
+
except OSError as e:
|
|
401
|
+
raise OperationFailedError(f"Failed to delete file: {p}. Original error: {e}")
|
|
402
|
+
|
|
403
|
+
def obfuscate_file_and_delete(file_path: Union[str, Path], debug: bool = False) -> None:
|
|
404
|
+
""" Obfuscates a file by overwriting it with random data and then deleting it
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
file_path (Union[str, Path]): The path to the file to obfuscate and delete.
|
|
408
|
+
|
|
409
|
+
Raises:
|
|
410
|
+
PathNotFoundError: If file does not exist.
|
|
411
|
+
PermissionDeniedError: If permission is denied.
|
|
412
|
+
OperationFailedError: If obfuscate or delete operation fails.
|
|
413
|
+
|
|
414
|
+
"""
|
|
415
|
+
p = Path(file_path)
|
|
416
|
+
if not p.is_file():
|
|
417
|
+
raise PathNotFoundError(f"File not found: {p}")
|
|
418
|
+
|
|
419
|
+
size = p.stat().st_size
|
|
420
|
+
|
|
421
|
+
try:
|
|
422
|
+
with open(p, 'wb') as file:
|
|
423
|
+
file.write(os.urandom(size))
|
|
424
|
+
if debug:
|
|
425
|
+
print_success(f"Obfuscated file: {p}")
|
|
426
|
+
p.unlink(missing_ok=False)
|
|
427
|
+
if debug:
|
|
428
|
+
print_success(f"Deleted file: {p}")
|
|
429
|
+
except PermissionError as e:
|
|
430
|
+
raise PermissionDeniedError(f"Permission denied obfuscating or deleting file: {p}. Original error: {e}")
|
|
431
|
+
except OSError as e:
|
|
432
|
+
raise OperationFailedError(f"Failed to obfuscate or delete file: {p}. Original error: {e}")
|
|
433
|
+
|
|
434
|
+
def send_file_to_trash(file_path: Union[str, Path], debug: bool = False) -> None:
|
|
435
|
+
""" Sends a file to the trash
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
file_path (Union[str, Path]): The path to the file to send to the trash.
|
|
439
|
+
|
|
440
|
+
Raises:
|
|
441
|
+
PathNotFoundError: If file does not exist.
|
|
442
|
+
PermissionDeniedError: If permission is denied.
|
|
443
|
+
OperationFailedError: If send to trash operation fails.
|
|
444
|
+
|
|
445
|
+
"""
|
|
446
|
+
import send2trash
|
|
447
|
+
p = Path(file_path)
|
|
448
|
+
if not p.is_file():
|
|
449
|
+
raise PathNotFoundError(f"File not found: {p}")
|
|
450
|
+
try:
|
|
451
|
+
send2trash.send2trash(p)
|
|
452
|
+
if debug:
|
|
453
|
+
print_success(f"Sent file to trash: {p}")
|
|
454
|
+
except Exception as e:
|
|
455
|
+
raise OperationFailedError(f"Failed to send file to trash: {p}. Original error: {e}")
|
|
456
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def format_func() -> str:
|
|
7
|
+
return "Hello from format_utils"
|
|
8
|
+
|
|
9
|
+
def format_bytes(size_bytes: int, decimal_places: int = 2) -> str:
|
|
10
|
+
"""Converts a number of bytes to a human-readable string representation (e.g. KB, MB, GB)."""
|
|
11
|
+
if size_bytes < 0:
|
|
12
|
+
raise ValueError("Size in bytes cannot be negative")
|
|
13
|
+
|
|
14
|
+
if size_bytes == 0:
|
|
15
|
+
return "0 B"
|
|
16
|
+
|
|
17
|
+
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]
|
|
18
|
+
size = float(size_bytes)
|
|
19
|
+
unit_idx = 0
|
|
20
|
+
while size >= 1024.0 and unit_idx < len(units) - 1:
|
|
21
|
+
size /= 1024.0
|
|
22
|
+
unit_idx += 1
|
|
23
|
+
|
|
24
|
+
return f"{size:.{decimal_places}f} {units[unit_idx]}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def clamp(value: float, min_value: float, max_value: float) -> float:
|
|
28
|
+
"""Clamps a value to a specified range."""
|
|
29
|
+
return max(min_value, min(value, max_value))
|
|
30
|
+
|
|
31
|
+
def random_string(length: int = 10, include_special_characters: bool = False) -> str:
|
|
32
|
+
"""Generates a random string of the specified length."""
|
|
33
|
+
import string
|
|
34
|
+
import random
|
|
35
|
+
|
|
36
|
+
if include_special_characters:
|
|
37
|
+
return ''.join(random.choices(string.ascii_letters + string.digits + string.punctuation, k=length))
|
|
38
|
+
else:
|
|
39
|
+
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Time Format Functions
|
|
44
|
+
def format_time_diff(seconds: float) -> str:
|
|
45
|
+
"""Converts a time difference in seconds to a human-readable string (e.g. 1h30m 12s)."""
|
|
46
|
+
if seconds < 0:
|
|
47
|
+
raise ValueError("Time difference cannot be negative")
|
|
48
|
+
|
|
49
|
+
parts = []
|
|
50
|
+
|
|
51
|
+
# Calculate hours
|
|
52
|
+
hours, remainder = divmod(seconds, 3600)
|
|
53
|
+
if hours > 0:
|
|
54
|
+
parts.append(f"{int(hours)}h")
|
|
55
|
+
|
|
56
|
+
# Calculate minutes
|
|
57
|
+
minutes, seconds = divmod(remainder, 60)
|
|
58
|
+
if minutes > 0:
|
|
59
|
+
parts.append(f"{int(minutes)}m")
|
|
60
|
+
|
|
61
|
+
# Calculate seconds (with milliseconds if needed)
|
|
62
|
+
if seconds > 0 or not parts:
|
|
63
|
+
if seconds >= 1:
|
|
64
|
+
parts.append(f"{int(seconds)}s")
|
|
65
|
+
else:
|
|
66
|
+
parts.append(f"{seconds:.3f}s")
|
|
67
|
+
|
|
68
|
+
return " ".join(parts)
|
|
69
|
+
|
|
70
|
+
def format_timestamp(timestamp: float, output_format: str = "%d-%m-%Y %H:%M:%S") -> str:
|
|
71
|
+
"""Formats a timestamp (seconds since epoch) into a human-readable date/time string."""
|
|
72
|
+
import datetime
|
|
73
|
+
return datetime.datetime.fromtimestamp(timestamp).strftime(output_format)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# File Age Functions
|
|
78
|
+
def get_file_age_in_seconds(file_path: Union[str, Path]) -> float:
|
|
79
|
+
"""Returns the age of the file in seconds."""
|
|
80
|
+
p = Path(file_path)
|
|
81
|
+
if not p.is_file():
|
|
82
|
+
raise FileNotFoundError(f"File not found: {p}")
|
|
83
|
+
return p.stat().st_mtime
|
|
84
|
+
|
|
85
|
+
def get_file_age_in_minutes(file_path: Union[str, Path]) -> float:
|
|
86
|
+
"""Returns the age of the file in minutes."""
|
|
87
|
+
return get_file_age_in_seconds(file_path) / 60
|
|
88
|
+
|
|
89
|
+
def get_file_age_in_hours(file_path: Union[str, Path]) -> float:
|
|
90
|
+
"""Returns the age of the file in hours."""
|
|
91
|
+
return get_file_age_in_seconds(file_path) / 3600
|
|
92
|
+
|
|
93
|
+
def get_file_age_in_days(file_path: Union[str, Path]) -> float:
|
|
94
|
+
"""Returns the age of the file in days."""
|
|
95
|
+
return get_file_age_in_seconds(file_path) / 86400
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import colorama
|
|
2
|
+
from colorama import Fore, Style
|
|
3
|
+
|
|
4
|
+
# Initialize colorama (safe to call multiple times, handles Windows ANSI translation)
|
|
5
|
+
colorama.init(autoreset=True)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
COLOUR_TABLE = {
|
|
9
|
+
"default": Style.RESET_ALL,
|
|
10
|
+
"info": Fore.BLUE,
|
|
11
|
+
"success": Fore.GREEN,
|
|
12
|
+
"warning": Fore.YELLOW,
|
|
13
|
+
"error": Fore.RED,
|
|
14
|
+
"critical": Fore.RED + Style.BRIGHT,
|
|
15
|
+
"debug": Style.DIM,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def message_func() -> str:
|
|
19
|
+
return "Hello from message_utils"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def bracketed_message_string(label: str, style: str = "default") -> str:
|
|
23
|
+
return f"[{COLOUR_TABLE[style]}{label}{COLOUR_TABLE['default']}]"
|
|
24
|
+
|
|
25
|
+
def print_warning(message: str, prefix_label: str = "WARNING") -> None:
|
|
26
|
+
print(f"{bracketed_message_string(prefix_label, 'warning')} {message}")
|
|
27
|
+
|
|
28
|
+
def print_error(message: str, prefix_label: str = "ERROR") -> None:
|
|
29
|
+
print(f"{bracketed_message_string(prefix_label, 'error')} {message}")
|
|
30
|
+
|
|
31
|
+
def print_success(message: str, prefix_label: str = "SUCCESS") -> None:
|
|
32
|
+
print(f"{bracketed_message_string(prefix_label, 'success')} {message}")
|
|
33
|
+
|
|
34
|
+
def print_info(message: str, prefix_label: str = "INFO") -> None:
|
|
35
|
+
print(f"{bracketed_message_string(prefix_label, 'info')} {message}")
|
|
36
|
+
|
|
37
|
+
def print_debug(message: str, prefix_label: str = "DEBUG") -> None:
|
|
38
|
+
print(f"{bracketed_message_string(prefix_label, 'debug')} {message}")
|
|
39
|
+
|
|
40
|
+
def print_critical(message: str, prefix_label: str = "CRITICAL") -> None:
|
|
41
|
+
print(f"{bracketed_message_string(prefix_label, 'critical')} {message}")
|
|
42
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nuf
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A versatile multi-file utility module, a series of useful code that is referenced from various projects that i repeatedly use.
|
|
5
|
+
Author: Nathan Falvey
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: colorama
|
|
13
|
+
Requires-Dist: send2trash
|
|
14
|
+
Requires-Dist: pathlib
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# nates-useful-functions-python-module (nuf)
|
|
18
|
+
|
|
19
|
+
A versatile, multi-file utility module that aggregates a series of useful Python code referenced repeatedly across various projects.
|
|
20
|
+
|
|
21
|
+
## Package Structure
|
|
22
|
+
|
|
23
|
+
The package is structured as follows under the `src` directory:
|
|
24
|
+
- `src/nuf/__init__.py`: Initializes the package and dynamically imports all Python files/modules in its directory.
|
|
25
|
+
- `src/nuf/core.py`: Core functionality.
|
|
26
|
+
- `src/nuf/file_utils.py`: Utilities for working with files.
|
|
27
|
+
- `src/nuf/message_utils.py`: Utilities for messaging and logs.
|
|
28
|
+
- `src/nuf/format_utils.py`: Utilities for formatting and time conversions.
|
|
29
|
+
|
|
30
|
+
## Dynamic Importing
|
|
31
|
+
|
|
32
|
+
This module automatically discovers and imports all Python files present inside the `src/nuf/` folder. When you add a new Python file (e.g. `src/nuf/new_helper.py`), it is automatically available as `nuf.new_helper` without needing to modify `__init__.py`.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Here is a quick example of how to import and use the package:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import nuf
|
|
40
|
+
|
|
41
|
+
# Access functions from core.py
|
|
42
|
+
print(nuf.core.core_func())
|
|
43
|
+
|
|
44
|
+
# Access functions from file_utils.py
|
|
45
|
+
print(nuf.file_utils.file_func())
|
|
46
|
+
|
|
47
|
+
# Access functions from message_utils.py
|
|
48
|
+
print(nuf.message_utils.message_func())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Running Tests
|
|
52
|
+
|
|
53
|
+
To run the local verification test suite, run:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python test_nuf.py
|
|
57
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/nuf/__init__.py
|
|
5
|
+
src/nuf/core.py
|
|
6
|
+
src/nuf/file_utils.py
|
|
7
|
+
src/nuf/format_utils.py
|
|
8
|
+
src/nuf/message_utils.py
|
|
9
|
+
src/nuf.egg-info/PKG-INFO
|
|
10
|
+
src/nuf.egg-info/SOURCES.txt
|
|
11
|
+
src/nuf.egg-info/dependency_links.txt
|
|
12
|
+
src/nuf.egg-info/requires.txt
|
|
13
|
+
src/nuf.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nuf
|