nuf 0.0.2__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.
nuf/__init__.py 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)
nuf/core.py ADDED
@@ -0,0 +1,2 @@
1
+ def core_func():
2
+ return "Hello from core"
nuf/file_utils.py ADDED
@@ -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
+
nuf/format_utils.py ADDED
@@ -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
nuf/message_utils.py ADDED
@@ -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,10 @@
1
+ nuf/__init__.py,sha256=pdX0Vuk0aHB3vbVGIQ3Kkga7isd9UieG7VB3gTTZZRI,602
2
+ nuf/core.py,sha256=XermFsGus5ll1Fhx4d-hrH9s6l5UNbLwEwabD4DAz-I,46
3
+ nuf/file_utils.py,sha256=v9SaPlhnDwQK_ehLEr6z0DgKVyEljBJGl9efKKTov5Y,14991
4
+ nuf/format_utils.py,sha256=i_5Es-IT_J0dIJpgvLSe5z9w5byeeVxgIefLV9QDHFE,3121
5
+ nuf/message_utils.py,sha256=150hOxllP2iev-YuFGfvOYIKORhOJTgzwkJEwXPFSpI,1477
6
+ nuf-0.0.2.dist-info/licenses/LICENSE,sha256=8ig6042tksnVSfS8Zo2jFgRtE3-OPBFIi8qdPf3kJ20,1091
7
+ nuf-0.0.2.dist-info/METADATA,sha256=Nw_jcZSgCyBAQ2bYfOL4rNg9ywPqKBYdhk0E7-hNsSE,1850
8
+ nuf-0.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
9
+ nuf-0.0.2.dist-info/top_level.txt,sha256=mZxQgjMoy7GkS8ALAxzoI9GG1AdyF2iQNAjIs8qv9Ss,4
10
+ nuf-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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.
@@ -0,0 +1 @@
1
+ nuf