python-base-toolkit 0.0.1__py3-none-any.whl → 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.
- python_base_toolkit/consts/operating_system.py +4 -0
- python_base_toolkit/consts/units/__init__.py +2 -2
- python_base_toolkit/consts/units/binary_units.py +4 -4
- python_base_toolkit/decorators/telemetry.py +7 -9
- python_base_toolkit/decorators/timer.py +10 -3
- python_base_toolkit/instances/instance_manager.py +17 -16
- python_base_toolkit/utils/data_serialization.py +3 -3
- python_base_toolkit/utils/date_time.py +2 -2
- python_base_toolkit/utils/execute.py +1 -1
- python_base_toolkit/utils/file_utils.py +167 -160
- python_base_toolkit/utils/generate_id.py +35 -0
- python_base_toolkit/utils/logo.py +1 -1
- python_base_toolkit/utils/network.py +67 -23
- python_base_toolkit/utils/path_utils.py +4 -5
- python_base_toolkit/utils/pretty_print.py +3 -6
- python_base_toolkit/utils/pycache.py +3 -3
- python_base_toolkit/utils/random_utils.py +7 -7
- python_base_toolkit/utils/venv_details.py +4 -4
- {python_base_toolkit-0.0.1.dist-info → python_base_toolkit-0.0.2.dist-info}/METADATA +6 -4
- python_base_toolkit-0.0.2.dist-info/RECORD +30 -0
- python_base_toolkit-0.0.1.dist-info/RECORD +0 -28
- {python_base_toolkit-0.0.1.dist-info → python_base_toolkit-0.0.2.dist-info}/WHEEL +0 -0
- {python_base_toolkit-0.0.1.dist-info → python_base_toolkit-0.0.2.dist-info}/licenses/LICENSE +0 -0
- {python_base_toolkit-0.0.1.dist-info → python_base_toolkit-0.0.2.dist-info}/top_level.txt +0 -0
|
@@ -7,7 +7,7 @@ PiB = TiB * 1024
|
|
|
7
7
|
EiB = PiB * 1024
|
|
8
8
|
|
|
9
9
|
KB = 1000 # 1 kilobyte (KB) = 1000 bytes in decimal (SI)
|
|
10
|
-
GB = 1000
|
|
11
|
-
TB = 1000
|
|
12
|
-
PB = 1000
|
|
13
|
-
EB = 1000
|
|
10
|
+
GB = 1000**3 # 1 gigabyte (GB) = 1000^3 bytes in decimal (SI)
|
|
11
|
+
TB = 1000**4 # 1 terabyte (TB) = 1000^4 bytes in decimal (SI)
|
|
12
|
+
PB = 1000**5 # 1 petabyte (PB) = 1000^5 bytes in decimal (SI)
|
|
13
|
+
EB = 1000**6 # 1 exabyte (EB) = 1000^6 bytes in decimal (SI)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import datetime
|
|
1
2
|
import json
|
|
2
3
|
import time
|
|
4
|
+
from collections.abc import Callable
|
|
3
5
|
from functools import wraps
|
|
4
|
-
import
|
|
5
|
-
from typing import Callable, Any
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
8
|
from custom_python_logger import get_logger
|
|
8
9
|
|
|
@@ -10,11 +11,7 @@ logger = get_logger(__name__)
|
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def report_telemetry(
|
|
13
|
-
func: Callable[..., Any],
|
|
14
|
-
start_time: datetime.datetime,
|
|
15
|
-
end_time: datetime.datetime,
|
|
16
|
-
*args: Any,
|
|
17
|
-
**kwargs: Any
|
|
14
|
+
func: Callable[..., Any], start_time: datetime.datetime, end_time: datetime.datetime, *args: Any, **kwargs: Any
|
|
18
15
|
) -> None:
|
|
19
16
|
data = {
|
|
20
17
|
"function_name": func.__name__,
|
|
@@ -25,7 +22,8 @@ def report_telemetry(
|
|
|
25
22
|
"timestamp": time.time(),
|
|
26
23
|
}
|
|
27
24
|
logger.info(
|
|
28
|
-
f"Sending telemetry data with the following data: {json.dumps(data, indent=4, sort_keys=False, default=str)}"
|
|
25
|
+
f"Sending telemetry data with the following data: {json.dumps(data, indent=4, sort_keys=False, default=str)}"
|
|
26
|
+
)
|
|
29
27
|
|
|
30
28
|
|
|
31
29
|
def report_func_telemetry(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
@@ -37,7 +35,7 @@ def report_func_telemetry(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
37
35
|
start_time = datetime.datetime.now(datetime.UTC)
|
|
38
36
|
result = func(*args, **kwargs)
|
|
39
37
|
end_time = datetime.datetime.now(datetime.UTC)
|
|
40
|
-
report_telemetry(func=func, start_time=start_time, end_time=end_time,
|
|
38
|
+
report_telemetry(*args, func=func, start_time=start_time, end_time=end_time, **kwargs)
|
|
41
39
|
return result
|
|
42
40
|
|
|
43
41
|
return wrapper
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import time
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from functools import wraps
|
|
3
|
-
from typing import
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
from custom_python_logger import get_logger
|
|
6
7
|
|
|
@@ -8,12 +9,17 @@ logger = get_logger(__name__)
|
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Timer:
|
|
11
|
-
def
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self.start_time = None
|
|
14
|
+
self.end_time = None
|
|
15
|
+
self.elapsed_time = None
|
|
16
|
+
|
|
17
|
+
def __enter__(self) -> "Timer":
|
|
12
18
|
self.start_time = time.perf_counter()
|
|
13
19
|
logger.info("Timer started.")
|
|
14
20
|
return self
|
|
15
21
|
|
|
16
|
-
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
22
|
+
def __exit__(self, exc_type: type, exc_value: Exception, exc_traceback: object) -> None:
|
|
17
23
|
# logger.info(exc_type, exc_value, exc_traceback)
|
|
18
24
|
self.end_time = time.perf_counter()
|
|
19
25
|
self.elapsed_time = self.end_time - self.start_time
|
|
@@ -25,4 +31,5 @@ def timer(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
25
31
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
26
32
|
with Timer():
|
|
27
33
|
return func(*args, **kwargs)
|
|
34
|
+
|
|
28
35
|
return wrapper
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
from custom_python_logger.logger import get_logger
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
class InstanceManager:
|
|
4
|
-
def __init__(self):
|
|
5
|
+
def __init__(self) -> None:
|
|
5
6
|
self.logger = get_logger(__class__.__name__)
|
|
6
7
|
self._instances = []
|
|
7
8
|
|
|
8
|
-
def add(self, instance):
|
|
9
|
+
def add(self, instance: object) -> None:
|
|
9
10
|
self._instances.append(instance)
|
|
10
11
|
|
|
11
|
-
def close_all(self):
|
|
12
|
+
def close_all(self) -> None:
|
|
12
13
|
for instance in reversed(self._instances):
|
|
13
14
|
_instance_name = instance.__class__.__name__
|
|
14
15
|
try:
|
|
15
|
-
if hasattr(instance,
|
|
16
|
+
if hasattr(instance, "close"):
|
|
16
17
|
instance.close()
|
|
17
18
|
else: # hasattr(instance, '__exit__'):
|
|
18
19
|
instance.__exit__(None, None, None)
|
|
@@ -20,31 +21,31 @@ class InstanceManager:
|
|
|
20
21
|
except Exception as e:
|
|
21
22
|
self.logger.info(f"Failed to close instance {instance}: {e}")
|
|
22
23
|
|
|
23
|
-
def __enter__(self):
|
|
24
|
+
def __enter__(self) -> "InstanceManager":
|
|
24
25
|
return self
|
|
25
26
|
|
|
26
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
|
27
|
+
def __exit__(self, exc_type: type, exc_value: Exception, traceback: object) -> None:
|
|
27
28
|
self.close_all()
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
def main():
|
|
31
|
+
def main() -> None:
|
|
31
32
|
class SomeInstance:
|
|
32
|
-
def __init__(self, add_to_instance_manager: bool = False):
|
|
33
|
+
def __init__(self, add_to_instance_manager: bool = False) -> None:
|
|
33
34
|
self.logger = get_logger(__class__.__name__)
|
|
34
35
|
|
|
35
36
|
if add_to_instance_manager:
|
|
36
|
-
instance_manager.add(self)
|
|
37
|
+
instance_manager.add(self) # pylint: disable=E0601
|
|
37
38
|
|
|
38
39
|
@property
|
|
39
|
-
def
|
|
40
|
+
def class_name(self) -> str:
|
|
40
41
|
return self.__class__.__name__
|
|
41
42
|
|
|
42
|
-
def __enter__(self):
|
|
43
|
-
self.logger.info(f"Entering {self.
|
|
43
|
+
def __enter__(self) -> "SomeInstance":
|
|
44
|
+
self.logger.info(f"Entering {self.class_name}")
|
|
44
45
|
return self
|
|
45
46
|
|
|
46
|
-
def __exit__(self, exc_type, exc_value,
|
|
47
|
-
self.logger.info(f"Exiting {self.
|
|
47
|
+
def __exit__(self, exc_type: type, exc_value: Exception, traceback: object) -> bool:
|
|
48
|
+
self.logger.info(f"Exiting {self.class_name}")
|
|
48
49
|
# Handle any cleanup here
|
|
49
50
|
if exc_type:
|
|
50
51
|
self.logger.info(f"Exception: {exc_value}")
|
|
@@ -52,8 +53,8 @@ def main():
|
|
|
52
53
|
|
|
53
54
|
SomeInstance(add_to_instance_manager=True)
|
|
54
55
|
|
|
55
|
-
resource1 = open(
|
|
56
|
-
resource2 = open(
|
|
56
|
+
resource1 = open("file1.txt", "w") # pylint: disable=R1732
|
|
57
|
+
resource2 = open("file2.txt", "w") # pylint: disable=R1732
|
|
57
58
|
|
|
58
59
|
instance_manager.add(resource1)
|
|
59
60
|
instance_manager.add(resource2)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from dataclasses import is_dataclass
|
|
3
|
-
from datetime import
|
|
3
|
+
from datetime import date, datetime, time
|
|
4
4
|
from decimal import Decimal
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from pathlib import Path
|
|
@@ -23,7 +23,7 @@ def default_serialize(obj: object) -> object:
|
|
|
23
23
|
return list(obj)
|
|
24
24
|
if isinstance(obj, tuple):
|
|
25
25
|
return list(obj)
|
|
26
|
-
if isinstance(obj,
|
|
26
|
+
if isinstance(obj, datetime | date | time):
|
|
27
27
|
return obj.isoformat()
|
|
28
28
|
if isinstance(obj, Decimal):
|
|
29
29
|
return float(obj)
|
|
@@ -31,7 +31,7 @@ def default_serialize(obj: object) -> object:
|
|
|
31
31
|
return str(obj)
|
|
32
32
|
if obj is pd.NA:
|
|
33
33
|
return None
|
|
34
|
-
logger.error(f
|
|
34
|
+
logger.error(f"Object is not serializable: {obj}")
|
|
35
35
|
raise TypeError(f"Type {type(obj)} not serializable")
|
|
36
36
|
|
|
37
37
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from zoneinfo import ZoneInfo
|
|
3
3
|
|
|
4
|
-
utc_timezone = ZoneInfo(
|
|
4
|
+
utc_timezone = ZoneInfo("UTC")
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def datetime_now_with_timezone(timezone: ZoneInfo = utc_timezone) -> datetime:
|
|
@@ -9,6 +9,6 @@ def datetime_now_with_timezone(timezone: ZoneInfo = utc_timezone) -> datetime:
|
|
|
9
9
|
raise TypeError(f"Expected pytz timezone, got {type(timezone).__name__}")
|
|
10
10
|
|
|
11
11
|
now_utc = datetime.now(timezone)
|
|
12
|
-
if timezone.key ==
|
|
12
|
+
if timezone.key == "UTC":
|
|
13
13
|
return now_utc
|
|
14
14
|
return now_utc.astimezone(timezone)
|
|
@@ -16,7 +16,7 @@ def _timed_execution(
|
|
|
16
16
|
pbar = tqdm(total=timeout, desc=pb_description, unit="s", ncols=100)
|
|
17
17
|
start_time = time()
|
|
18
18
|
|
|
19
|
-
while time() - start_time < timeout:
|
|
19
|
+
while time() - start_time < timeout: # pylint: disable=W0149
|
|
20
20
|
res = func()
|
|
21
21
|
if (expect_true and res) or (not expect_true and not res):
|
|
22
22
|
pbar.close()
|
|
@@ -1,256 +1,263 @@
|
|
|
1
|
+
import csv
|
|
1
2
|
import gzip
|
|
2
|
-
import os
|
|
3
3
|
import hashlib
|
|
4
|
-
import shutil
|
|
5
4
|
import json
|
|
6
|
-
import
|
|
7
|
-
import yaml
|
|
5
|
+
import os
|
|
8
6
|
import re
|
|
9
|
-
|
|
7
|
+
import shutil
|
|
10
8
|
import tarfile
|
|
11
9
|
import zipfile
|
|
10
|
+
from typing import Any, BinaryIO, TextIO, cast
|
|
12
11
|
|
|
12
|
+
import yaml
|
|
13
13
|
from custom_python_logger import get_logger
|
|
14
14
|
|
|
15
15
|
logger = get_logger(__name__)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class
|
|
18
|
+
class _FileCompression:
|
|
19
19
|
@staticmethod
|
|
20
|
-
def
|
|
21
|
-
if not os.path.
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
def gzip_file(input_path: str, output_path: str | None = None) -> str:
|
|
21
|
+
if not os.path.isfile(input_path):
|
|
22
|
+
raise FileNotFoundError(f"Input file does not exist: {input_path}")
|
|
23
|
+
|
|
24
|
+
if output_path is None:
|
|
25
|
+
output_path = input_path + ".gz"
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
with open(input_path, "rb") as f_in:
|
|
29
|
+
with gzip.open(output_path, "wb") as f_out:
|
|
30
|
+
f_out.write(f_in.read())
|
|
31
|
+
return output_path
|
|
32
|
+
except Exception as e:
|
|
33
|
+
raise OSError(f"Error compressing file: {e}") from e
|
|
24
34
|
|
|
25
35
|
@staticmethod
|
|
26
|
-
def
|
|
27
|
-
|
|
36
|
+
def ungzip_file(gz_path: str, output_path: str | None = None) -> str:
|
|
37
|
+
if not os.path.isfile(gz_path):
|
|
38
|
+
raise FileNotFoundError(f"Gzipped file does not exist: {gz_path}")
|
|
39
|
+
|
|
40
|
+
if output_path is None:
|
|
41
|
+
if gz_path.endswith(".gz"):
|
|
42
|
+
output_path = gz_path[:-3] # Remove .gz extension
|
|
43
|
+
else:
|
|
44
|
+
output_path = gz_path + "_decompressed"
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
with gzip.open(gz_path, "rb") as f_in:
|
|
48
|
+
with open(output_path, "wb") as f_out:
|
|
49
|
+
f_out.write(f_in.read())
|
|
50
|
+
return output_path
|
|
51
|
+
except Exception as e:
|
|
52
|
+
raise OSError(f"Error decompressing file: {e}") from e
|
|
28
53
|
|
|
29
54
|
@staticmethod
|
|
30
|
-
def
|
|
31
|
-
|
|
55
|
+
def compress_directory(directory: str, output_path: str | None = None) -> str:
|
|
56
|
+
"""Compress directory to tarball."""
|
|
57
|
+
if not os.path.isdir(directory):
|
|
58
|
+
raise FileNotFoundError(f"Directory does not exist: {directory}")
|
|
59
|
+
|
|
60
|
+
if output_path is None:
|
|
61
|
+
output_path = directory.rstrip(os.sep) + ".tar.gz"
|
|
62
|
+
|
|
63
|
+
with tarfile.open(output_path, "w:gz") as tar:
|
|
64
|
+
tar.add(directory, arcname=os.path.basename(directory))
|
|
65
|
+
|
|
66
|
+
return output_path
|
|
32
67
|
|
|
33
68
|
@staticmethod
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
69
|
+
def extract_archive(archive_path: str, output_dir: str | None = None) -> str:
|
|
70
|
+
"""Extract archive file."""
|
|
71
|
+
if not os.path.isfile(archive_path):
|
|
72
|
+
raise FileNotFoundError(f"Archive does not exist: {archive_path}")
|
|
38
73
|
|
|
74
|
+
if output_dir is None:
|
|
75
|
+
output_dir = os.path.splitext(archive_path)[0]
|
|
76
|
+
# Handle double extensions like .tar.gz
|
|
77
|
+
if output_dir.endswith(".tar"):
|
|
78
|
+
output_dir = output_dir[:-4]
|
|
79
|
+
|
|
80
|
+
_FilePath.ensure_dir(output_dir)
|
|
81
|
+
|
|
82
|
+
if archive_path.endswith((".tar.gz", ".tgz")):
|
|
83
|
+
with tarfile.open(archive_path, "r:gz") as tar:
|
|
84
|
+
tar.extractall(path=output_dir)
|
|
85
|
+
elif archive_path.endswith(".tar.bz2"):
|
|
86
|
+
with tarfile.open(archive_path, "r:bz2") as tar:
|
|
87
|
+
tar.extractall(path=output_dir)
|
|
88
|
+
elif archive_path.endswith(".tar"):
|
|
89
|
+
with tarfile.open(archive_path, "r") as tar:
|
|
90
|
+
tar.extractall(path=output_dir)
|
|
91
|
+
elif archive_path.endswith(".zip"):
|
|
92
|
+
with zipfile.ZipFile(archive_path, "r") as zip_ref:
|
|
93
|
+
zip_ref.extractall(output_dir)
|
|
94
|
+
else:
|
|
95
|
+
raise ValueError(f"Unsupported archive format: {archive_path}")
|
|
96
|
+
|
|
97
|
+
return output_dir
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class _FileHash:
|
|
39
101
|
@staticmethod
|
|
40
|
-
def
|
|
41
|
-
|
|
102
|
+
def file_md5(filename: str, chunk_size: int = 8192) -> str:
|
|
103
|
+
"""Calculate MD5 hash of a file."""
|
|
104
|
+
hash_md5 = hashlib.md5()
|
|
105
|
+
with open(filename, "rb") as f:
|
|
106
|
+
for chunk in iter(lambda: f.read(chunk_size), b""):
|
|
107
|
+
hash_md5.update(chunk)
|
|
108
|
+
return hash_md5.hexdigest()
|
|
42
109
|
|
|
43
110
|
@staticmethod
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return
|
|
111
|
+
def file_sha1(filename: str, chunk_size: int = 8192) -> str:
|
|
112
|
+
"""Calculate SHA1 hash of a file."""
|
|
113
|
+
hash_sha1 = hashlib.sha1()
|
|
114
|
+
with open(filename, "rb") as f:
|
|
115
|
+
for chunk in iter(lambda: f.read(chunk_size), b""):
|
|
116
|
+
hash_sha1.update(chunk)
|
|
117
|
+
return hash_sha1.hexdigest()
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def file_sha256(filename: str, chunk_size: int = 8192) -> str:
|
|
121
|
+
"""Calculate SHA256 hash of a file."""
|
|
122
|
+
hash_sha256 = hashlib.sha256()
|
|
123
|
+
with open(filename, "rb") as f:
|
|
124
|
+
for chunk in iter(lambda: f.read(chunk_size), b""):
|
|
125
|
+
hash_sha256.update(chunk)
|
|
126
|
+
return hash_sha256.hexdigest()
|
|
51
127
|
|
|
52
128
|
|
|
53
|
-
class
|
|
129
|
+
class _FileIO:
|
|
54
130
|
@staticmethod
|
|
55
|
-
def safe_open(
|
|
56
|
-
|
|
57
|
-
mode: str = 'r',
|
|
58
|
-
encoding: Optional[str] = None,
|
|
59
|
-
**kwargs
|
|
60
|
-
) -> Union[TextIO, BinaryIO]:
|
|
61
|
-
if 'b' in mode:
|
|
131
|
+
def safe_open(filename: str, mode: str = "r", encoding: str | None = None, **kwargs: Any) -> TextIO | BinaryIO:
|
|
132
|
+
if "b" in mode:
|
|
62
133
|
return cast(BinaryIO, open(filename, mode, **kwargs))
|
|
63
|
-
return cast(TextIO, open(filename, mode, encoding=encoding or
|
|
134
|
+
return cast(TextIO, open(filename, mode, encoding=encoding or "utf-8", **kwargs))
|
|
64
135
|
|
|
65
136
|
@staticmethod
|
|
66
|
-
def read_text(filename: str, encoding: str =
|
|
67
|
-
with open(filename,
|
|
137
|
+
def read_text(filename: str, encoding: str = "utf-8") -> str:
|
|
138
|
+
with open(filename, encoding=encoding) as f:
|
|
68
139
|
return f.read()
|
|
69
140
|
|
|
70
141
|
@staticmethod
|
|
71
|
-
def write_text(text: str, filename: str, encoding: str =
|
|
72
|
-
with open(filename,
|
|
142
|
+
def write_text(text: str, filename: str, encoding: str = "utf-8") -> None:
|
|
143
|
+
with open(filename, "w", encoding=encoding) as f:
|
|
73
144
|
f.write(text)
|
|
74
145
|
|
|
75
|
-
|
|
76
|
-
|
|
146
|
+
@staticmethod
|
|
147
|
+
def read_json(filename: str) -> dict[str, Any]:
|
|
148
|
+
with _FileIO.safe_open(filename, "r") as f:
|
|
77
149
|
return json.load(f)
|
|
78
150
|
|
|
79
151
|
@staticmethod
|
|
80
152
|
def write_json(data: dict[str, Any], filename: str, indent: int = 2) -> None:
|
|
81
|
-
with open(filename,
|
|
153
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
82
154
|
json.dump(data, f, indent=indent, ensure_ascii=False)
|
|
83
155
|
|
|
84
156
|
@staticmethod
|
|
85
157
|
def read_yaml(filename: str) -> Any:
|
|
86
|
-
with open(filename,
|
|
158
|
+
with open(filename, encoding="utf-8") as f:
|
|
87
159
|
return yaml.safe_load(f)
|
|
88
160
|
|
|
89
161
|
@staticmethod
|
|
90
|
-
def write_yaml(data: Any, filename: str, **kwargs) -> None:
|
|
91
|
-
with open(filename,
|
|
162
|
+
def write_yaml(data: Any, filename: str, **kwargs: Any) -> None:
|
|
163
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
92
164
|
yaml.safe_dump(data, f, allow_unicode=True, **kwargs)
|
|
93
165
|
|
|
94
|
-
def read_csv(self, filename: str, **kwargs) -> list[dict[str, Any]]:
|
|
95
|
-
with self.safe_open(filename,
|
|
166
|
+
def read_csv(self, filename: str, **kwargs: Any) -> list[dict[str, Any]]:
|
|
167
|
+
with self.safe_open(filename, "r", newline="") as f:
|
|
96
168
|
reader = csv.DictReader(f, **kwargs)
|
|
97
169
|
return list(reader)
|
|
98
170
|
|
|
99
171
|
@staticmethod
|
|
100
|
-
def write_csv(
|
|
172
|
+
def write_csv(
|
|
173
|
+
data: list[dict[str, Any]], filename: str, fieldnames: list[str] | None = None, **kwargs: Any
|
|
174
|
+
) -> None:
|
|
101
175
|
if not data:
|
|
102
176
|
return
|
|
103
177
|
|
|
104
178
|
if fieldnames is None:
|
|
105
179
|
fieldnames = list(data[0].keys())
|
|
106
180
|
|
|
107
|
-
with open(filename,
|
|
181
|
+
with open(filename, "w", newline="", encoding="utf-8") as f:
|
|
108
182
|
writer = csv.DictWriter(f, fieldnames=fieldnames, **kwargs)
|
|
109
183
|
writer.writeheader()
|
|
110
184
|
writer.writerows(data)
|
|
111
185
|
|
|
112
186
|
@staticmethod
|
|
113
187
|
def is_binary_file(path: str, chunk_size: int = 1024) -> bool:
|
|
114
|
-
with open(path,
|
|
188
|
+
with open(path, "rb") as f:
|
|
115
189
|
chunk = f.read(chunk_size)
|
|
116
|
-
return b
|
|
190
|
+
return b"\0" in chunk
|
|
117
191
|
|
|
118
192
|
@staticmethod
|
|
119
193
|
def is_file_empty(path: str) -> bool:
|
|
120
194
|
return os.path.getsize(path) == 0
|
|
121
195
|
|
|
122
196
|
|
|
123
|
-
class
|
|
197
|
+
class _FileManipulation:
|
|
124
198
|
@staticmethod
|
|
125
199
|
def remove_emojis(text: str) -> str:
|
|
126
|
-
return re.sub(r
|
|
127
|
-
|
|
200
|
+
return re.sub(r"[\U00010000-\U0010ffff]", "", text).strip()
|
|
128
201
|
|
|
129
|
-
class FileSystem:
|
|
130
|
-
@staticmethod
|
|
131
|
-
def copy_file(src: str, dst: str) -> None:
|
|
132
|
-
shutil.copy2(src, dst)
|
|
133
202
|
|
|
203
|
+
class _FilePath:
|
|
134
204
|
@staticmethod
|
|
135
|
-
def
|
|
136
|
-
|
|
205
|
+
def ensure_dir(directory: str) -> str:
|
|
206
|
+
if not os.path.exists(directory):
|
|
207
|
+
os.makedirs(directory)
|
|
208
|
+
return directory
|
|
137
209
|
|
|
138
210
|
@staticmethod
|
|
139
|
-
def
|
|
140
|
-
|
|
141
|
-
os.remove(path)
|
|
211
|
+
def file_exists(path: str) -> bool:
|
|
212
|
+
return os.path.isfile(path)
|
|
142
213
|
|
|
143
214
|
@staticmethod
|
|
144
|
-
def
|
|
145
|
-
return os.path.
|
|
146
|
-
|
|
215
|
+
def get_file_extension(path: str) -> str:
|
|
216
|
+
return os.path.splitext(path)[1][1:]
|
|
147
217
|
|
|
148
|
-
class FileHash:
|
|
149
218
|
@staticmethod
|
|
150
|
-
def
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
for chunk in iter(lambda: f.read(chunk_size), b""):
|
|
155
|
-
hash_md5.update(chunk)
|
|
156
|
-
return hash_md5.hexdigest()
|
|
219
|
+
def get_filename(path: str, with_extension: bool = True) -> str:
|
|
220
|
+
if with_extension:
|
|
221
|
+
return os.path.basename(path)
|
|
222
|
+
return os.path.splitext(os.path.basename(path))[0]
|
|
157
223
|
|
|
158
224
|
@staticmethod
|
|
159
|
-
def
|
|
160
|
-
|
|
161
|
-
hash_sha1 = hashlib.sha1()
|
|
162
|
-
with open(filename, "rb") as f:
|
|
163
|
-
for chunk in iter(lambda: f.read(chunk_size), b""):
|
|
164
|
-
hash_sha1.update(chunk)
|
|
165
|
-
return hash_sha1.hexdigest()
|
|
225
|
+
def get_relative_path(path: str, base_path: str) -> str:
|
|
226
|
+
return os.path.relpath(path, base_path)
|
|
166
227
|
|
|
167
228
|
@staticmethod
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return
|
|
229
|
+
def list_files(directory: str, extension: str | None = None, recursive: bool = False) -> list[str]:
|
|
230
|
+
result = []
|
|
231
|
+
for root, _, files in os.walk(directory) if recursive else [(directory, [], os.listdir(directory))]:
|
|
232
|
+
for file in files:
|
|
233
|
+
if not extension or file.endswith(extension):
|
|
234
|
+
result.append(os.path.join(root, file))
|
|
235
|
+
return result
|
|
175
236
|
|
|
176
237
|
|
|
177
|
-
class
|
|
238
|
+
class _FileSystem:
|
|
178
239
|
@staticmethod
|
|
179
|
-
def
|
|
180
|
-
|
|
181
|
-
raise FileNotFoundError(f"Input file does not exist: {input_path}")
|
|
182
|
-
|
|
183
|
-
if output_path is None:
|
|
184
|
-
output_path = input_path + '.gz'
|
|
185
|
-
|
|
186
|
-
try:
|
|
187
|
-
with open(input_path, 'rb') as f_in:
|
|
188
|
-
with gzip.open(output_path, 'wb') as f_out:
|
|
189
|
-
f_out.write(f_in.read())
|
|
190
|
-
return output_path
|
|
191
|
-
except Exception as e:
|
|
192
|
-
raise IOError(f"Error compressing file: {e}")
|
|
240
|
+
def copy_file(src: str, dst: str) -> None:
|
|
241
|
+
shutil.copy2(src, dst)
|
|
193
242
|
|
|
194
243
|
@staticmethod
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
raise FileNotFoundError(f"Gzipped file does not exist: {gz_path}")
|
|
198
|
-
|
|
199
|
-
if output_path is None:
|
|
200
|
-
if gz_path.endswith('.gz'):
|
|
201
|
-
output_path = gz_path[:-3] # Remove .gz extension
|
|
202
|
-
else:
|
|
203
|
-
output_path = gz_path + '_decompressed'
|
|
204
|
-
|
|
205
|
-
try:
|
|
206
|
-
with gzip.open(gz_path, 'rb') as f_in:
|
|
207
|
-
with open(output_path, 'wb') as f_out:
|
|
208
|
-
f_out.write(f_in.read())
|
|
209
|
-
return output_path
|
|
210
|
-
except Exception as e:
|
|
211
|
-
raise IOError(f"Error decompressing file: {e}")
|
|
244
|
+
def move_file(src: str, dst: str) -> None:
|
|
245
|
+
shutil.move(src, dst)
|
|
212
246
|
|
|
213
247
|
@staticmethod
|
|
214
|
-
def
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
raise FileNotFoundError(f"Directory does not exist: {directory}")
|
|
218
|
-
|
|
219
|
-
if output_path is None:
|
|
220
|
-
output_path = directory.rstrip(os.sep) + '.tar.gz'
|
|
221
|
-
|
|
222
|
-
with tarfile.open(output_path, 'w:gz') as tar:
|
|
223
|
-
tar.add(directory, arcname=os.path.basename(directory))
|
|
224
|
-
|
|
225
|
-
return output_path
|
|
248
|
+
def delete_file(path: str) -> None:
|
|
249
|
+
if os.path.isfile(path):
|
|
250
|
+
os.remove(path)
|
|
226
251
|
|
|
227
252
|
@staticmethod
|
|
228
|
-
def
|
|
229
|
-
|
|
230
|
-
if not os.path.isfile(archive_path):
|
|
231
|
-
raise FileNotFoundError(f"Archive does not exist: {archive_path}")
|
|
232
|
-
|
|
233
|
-
if output_dir is None:
|
|
234
|
-
output_dir = os.path.splitext(archive_path)[0]
|
|
235
|
-
# Handle double extensions like .tar.gz
|
|
236
|
-
if output_dir.endswith('.tar'):
|
|
237
|
-
output_dir = output_dir[:-4]
|
|
238
|
-
|
|
239
|
-
FilePath.ensure_dir(output_dir)
|
|
253
|
+
def file_size(path: str) -> int:
|
|
254
|
+
return os.path.getsize(path)
|
|
240
255
|
|
|
241
|
-
if archive_path.endswith(('.tar.gz', '.tgz')):
|
|
242
|
-
with tarfile.open(archive_path, 'r:gz') as tar:
|
|
243
|
-
tar.extractall(path=output_dir)
|
|
244
|
-
elif archive_path.endswith('.tar.bz2'):
|
|
245
|
-
with tarfile.open(archive_path, 'r:bz2') as tar:
|
|
246
|
-
tar.extractall(path=output_dir)
|
|
247
|
-
elif archive_path.endswith('.tar'):
|
|
248
|
-
with tarfile.open(archive_path, 'r') as tar:
|
|
249
|
-
tar.extractall(path=output_dir)
|
|
250
|
-
elif archive_path.endswith('.zip'):
|
|
251
|
-
with zipfile.ZipFile(archive_path, 'r') as zip_ref:
|
|
252
|
-
zip_ref.extractall(output_dir)
|
|
253
|
-
else:
|
|
254
|
-
raise ValueError(f"Unsupported archive format: {archive_path}")
|
|
255
256
|
|
|
256
|
-
|
|
257
|
+
class FileUtils:
|
|
258
|
+
file_compression = _FileCompression
|
|
259
|
+
file_hash = _FileHash
|
|
260
|
+
file_io = _FileIO
|
|
261
|
+
file_manipulation = _FileManipulation
|
|
262
|
+
file_path = _FilePath
|
|
263
|
+
file_system = _FileSystem
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import string
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from custom_python_logger import get_logger
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_id(length: int = 8) -> str:
|
|
11
|
+
# helper function for generating an id
|
|
12
|
+
id_ = "".join(random.choices(string.ascii_uppercase, k=length))
|
|
13
|
+
logger.info(f"id is: {id_}")
|
|
14
|
+
return id_
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def generate_id_by_date() -> str:
|
|
18
|
+
timestamp = datetime.now()
|
|
19
|
+
id_ = timestamp.strftime("%Y%m%d")
|
|
20
|
+
logger.info(f"id is: {id_}")
|
|
21
|
+
return id_
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def generate_id_by_time() -> str:
|
|
25
|
+
timestamp = datetime.now()
|
|
26
|
+
id_ = timestamp.strftime("%H%M%S%m")
|
|
27
|
+
logger.info(f"id is: {id_}")
|
|
28
|
+
return id_
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def generate_id_by_date_and_time() -> str:
|
|
32
|
+
timestamp = datetime.now()
|
|
33
|
+
id_ = timestamp.strftime("%Y%m%d%H%M%S%m")
|
|
34
|
+
logger.info(f"id is: {id_}")
|
|
35
|
+
return id_
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import ipaddress
|
|
2
|
+
import os
|
|
2
3
|
import re
|
|
3
|
-
from typing import Optional
|
|
4
4
|
|
|
5
|
+
from custom_python_logger import get_logger
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
from python_base_toolkit.consts.operating_system import Platform
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_ip_address(ip: str) -> bool:
|
|
13
|
+
try:
|
|
14
|
+
ipaddress.ip_address(ip)
|
|
15
|
+
return True
|
|
16
|
+
except ValueError:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_ip_version(ip: str) -> int | None:
|
|
7
21
|
"""
|
|
8
22
|
Returns the type of the IP address as integer (IPv4, IPv6 or None in case of an invalid IP address)
|
|
9
23
|
:param ip: IP address (String)
|
|
@@ -15,7 +29,7 @@ def get_ip_version(ip: str) -> Optional[int]:
|
|
|
15
29
|
return None
|
|
16
30
|
|
|
17
31
|
|
|
18
|
-
def normalize_connection_string(ip: str, port:
|
|
32
|
+
def normalize_connection_string(ip: str, port: int | None = None) -> str:
|
|
19
33
|
"""
|
|
20
34
|
Returns a normalized connection string (IPv4:port or [IPv6]:port)
|
|
21
35
|
:param ip: IP address (String)
|
|
@@ -25,25 +39,25 @@ def normalize_connection_string(ip: str, port: Optional[int] = None) -> str:
|
|
|
25
39
|
w/ Port - IPv4:port or [IPv6]:port
|
|
26
40
|
w/o Port - IPv4: or [IPv6]:
|
|
27
41
|
"""
|
|
28
|
-
_operation =
|
|
42
|
+
_operation = "Normalize connection string"
|
|
29
43
|
|
|
30
44
|
_ip_version = get_ip_version(ip)
|
|
31
45
|
if _ip_version == 4:
|
|
32
46
|
return f'{ip}:{port or ""}'
|
|
33
47
|
if _ip_version == 6:
|
|
34
48
|
return f'[{ip}]:{port or ""}'
|
|
35
|
-
raise ValueError(f
|
|
49
|
+
raise ValueError(f"Invalid IP address: {ip} - {_operation}")
|
|
36
50
|
|
|
37
51
|
|
|
38
|
-
def normalize_http_url(ip: str, port:
|
|
52
|
+
def normalize_http_url(ip: str, port: int | None = None, https: bool = True) -> str:
|
|
39
53
|
"""
|
|
40
54
|
Returns a normalized HTTP URL (http://IPv4:port or http://[IPv6]:port)
|
|
41
55
|
:param ip: IP address (String)
|
|
42
56
|
:param port: Port (Integer)
|
|
43
57
|
:param https: Use HTTPS (Boolean)
|
|
44
58
|
"""
|
|
45
|
-
url_schema =
|
|
46
|
-
return f
|
|
59
|
+
url_schema = "https" if https else "http"
|
|
60
|
+
return f"{url_schema}://{normalize_connection_string(ip, port)}"
|
|
47
61
|
|
|
48
62
|
|
|
49
63
|
def parse_ifconfig_to_json(ifconfig_output: str) -> dict:
|
|
@@ -63,40 +77,70 @@ def parse_ifconfig_to_json(ifconfig_output: str) -> dict:
|
|
|
63
77
|
current_interface = None
|
|
64
78
|
|
|
65
79
|
for line in ifconfig_output.splitlines():
|
|
66
|
-
match_interface
|
|
67
|
-
if match_interface:
|
|
80
|
+
if match_interface := re.match("^(\\S+):\\s", line):
|
|
68
81
|
current_interface = match_interface.group(1)
|
|
69
82
|
interfaces[current_interface] = {}
|
|
70
83
|
|
|
71
|
-
match_mac = re.search(r
|
|
84
|
+
match_mac = re.search(r"ether\s([0-9a-f:]+)", line)
|
|
72
85
|
if match_mac and current_interface:
|
|
73
|
-
interfaces[current_interface][
|
|
86
|
+
interfaces[current_interface]["mac_address"] = match_mac.group(1)
|
|
74
87
|
|
|
75
|
-
match_ipv4 = re.search(r
|
|
88
|
+
match_ipv4 = re.search(r"inet\s(\d+\.\d+\.\d+\.\d+)", line)
|
|
76
89
|
if match_ipv4 and current_interface:
|
|
77
|
-
interfaces[current_interface][
|
|
90
|
+
interfaces[current_interface]["ipv4_address"] = match_ipv4.group(1)
|
|
78
91
|
|
|
79
92
|
# Try different netmask patterns
|
|
80
|
-
match_netmask = re.search(r
|
|
93
|
+
match_netmask = re.search(r"netmask\s+(0x[0-9a-f]+|(?:\d+\.){3}\d+)", line, re.IGNORECASE)
|
|
81
94
|
if match_netmask and current_interface:
|
|
82
95
|
netmask = match_netmask.group(1)
|
|
83
|
-
if netmask.startswith(
|
|
96
|
+
if netmask.startswith("0x"):
|
|
84
97
|
# Handle hex format
|
|
85
98
|
netmask_int = int(netmask, 16)
|
|
86
99
|
else:
|
|
87
100
|
# Handle decimal format (255.255.255.0)
|
|
88
|
-
octets = [int(x) for x in netmask.split(
|
|
101
|
+
octets = [int(x) for x in netmask.split(".")]
|
|
89
102
|
netmask_int = sum(octet << (24 - 8 * i) for i, octet in enumerate(octets))
|
|
90
103
|
|
|
91
|
-
binary_netmask = f
|
|
92
|
-
interfaces[current_interface][
|
|
104
|
+
binary_netmask = f"{netmask_int:032b}"
|
|
105
|
+
interfaces[current_interface]["ipv4_netmask"] = binary_netmask.count("1")
|
|
93
106
|
|
|
94
|
-
match_ipv6 = re.search(r
|
|
107
|
+
match_ipv6 = re.search(r"inet6\s([0-9a-fA-F:]+)", line)
|
|
95
108
|
if match_ipv6 and current_interface:
|
|
96
|
-
interfaces[current_interface][
|
|
109
|
+
interfaces[current_interface]["ipv6_address"] = match_ipv6.group(1)
|
|
97
110
|
|
|
98
|
-
match_subnet_mask = re.search(r
|
|
111
|
+
match_subnet_mask = re.search(r"prefixlen\s([0-9a-f:]+)", line)
|
|
99
112
|
if match_subnet_mask and current_interface:
|
|
100
|
-
interfaces[current_interface][
|
|
113
|
+
interfaces[current_interface]["ipv6_prefixlen"] = match_subnet_mask.group(1)
|
|
101
114
|
|
|
102
115
|
return interfaces
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def check_ping_from_linux(ip_address: str, number_of_ping: int = 4) -> bool:
|
|
119
|
+
try:
|
|
120
|
+
if os.system(f"ping -c {number_of_ping} {ip_address}") == 0:
|
|
121
|
+
return True
|
|
122
|
+
return False
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.exception(f"ping to {ip_address} failed: {e}")
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def check_ping_from_windows(ip_address: str, number_of_ping: int = 4) -> bool:
|
|
129
|
+
try:
|
|
130
|
+
if os.system(f"ping -c {number_of_ping} {ip_address}") == 0:
|
|
131
|
+
return True
|
|
132
|
+
return False
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.exception(f"ping to {ip_address} failed: {e}")
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def check_ping_status(platform: Platform, ip_address: str, number_of_ping: int = 4) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Check the ping status of an IP address based on the operating system platform.
|
|
141
|
+
"""
|
|
142
|
+
if platform in [Platform.LINUX, Platform.MACOS]:
|
|
143
|
+
return check_ping_from_linux(ip_address, number_of_ping)
|
|
144
|
+
if platform == Platform.WINDOWS:
|
|
145
|
+
return check_ping_from_windows(ip_address, number_of_ping)
|
|
146
|
+
raise ValueError(f"Unsupported platform: {platform}")
|
|
@@ -3,18 +3,17 @@ from pathlib import Path
|
|
|
3
3
|
|
|
4
4
|
def get_project_path_by_name(project_name: str) -> str:
|
|
5
5
|
current_path = Path(__file__).parent
|
|
6
|
-
while current_path != current_path.parent:
|
|
7
|
-
if str(current_path).endswith(f
|
|
6
|
+
while current_path != current_path.parent: # pylint: disable=W0149
|
|
7
|
+
if str(current_path).endswith(f"/{project_name}"):
|
|
8
8
|
return str(current_path)
|
|
9
9
|
current_path = current_path.parent
|
|
10
10
|
raise FileNotFoundError(
|
|
11
|
-
f'Project "{project_name}" not found in any parent directories.\n'
|
|
12
|
-
f'Current path: {Path(__file__)}',
|
|
11
|
+
f'Project "{project_name}" not found in any parent directories.\n' f"Current path: {Path(__file__)}",
|
|
13
12
|
)
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
def get_project_path_by_file(markers: set = None) -> Path:
|
|
17
|
-
markers = markers or {
|
|
16
|
+
markers = markers or {".git", ".gitignore", "setup.py", "pyproject.toml", "LICENSE", "README.md"}
|
|
18
17
|
for marker in markers:
|
|
19
18
|
path = Path(__file__).resolve()
|
|
20
19
|
for parent in path.parents:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
from custom_python_logger import get_logger
|
|
6
7
|
|
|
@@ -8,11 +9,7 @@ logger = get_logger(__name__)
|
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def log_in_format(
|
|
11
|
-
data: Any,
|
|
12
|
-
log_level: int = logging.INFO,
|
|
13
|
-
indent: int = 4,
|
|
14
|
-
sort_keys: bool = True,
|
|
15
|
-
default: Callable = None
|
|
12
|
+
data: Any, log_level: int = logging.INFO, indent: int = 4, sort_keys: bool = True, default: Callable = None
|
|
16
13
|
) -> None:
|
|
17
14
|
formatted_data = json.dumps(data, indent=indent, sort_keys=sort_keys, default=default)
|
|
18
15
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import shutil
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
|
|
5
4
|
from custom_python_logger.logger import get_logger
|
|
5
|
+
|
|
6
6
|
from python_base_toolkit.utils.path_utils import get_project_path_by_file
|
|
7
7
|
|
|
8
8
|
logger = get_logger(__name__)
|
|
@@ -10,9 +10,9 @@ logger = get_logger(__name__)
|
|
|
10
10
|
|
|
11
11
|
def delete_pycache_folder(root_dir: Path = None, ignored_dirs: set = None) -> None:
|
|
12
12
|
root_dir = Path(root_dir) if root_dir else get_project_path_by_file()
|
|
13
|
-
ignored_dirs = ignored_dirs or {
|
|
13
|
+
ignored_dirs = ignored_dirs or {".venv"}
|
|
14
14
|
|
|
15
|
-
for path in root_dir.rglob(
|
|
15
|
+
for path in root_dir.rglob("__pycache__"):
|
|
16
16
|
if any(ignored in path.parts for ignored in ignored_dirs):
|
|
17
17
|
continue
|
|
18
18
|
|
|
@@ -10,7 +10,7 @@ def generate_random_string(length: int = 8, charset: str = ascii_letters + digit
|
|
|
10
10
|
:param charset: The set of characters to use for generating the string
|
|
11
11
|
Return - A random string based on the specified character set
|
|
12
12
|
"""
|
|
13
|
-
return
|
|
13
|
+
return "".join(choice(charset) for _ in range(length))
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def generate_random_password(length: int = 8) -> str:
|
|
@@ -25,14 +25,14 @@ def generate_random_password(length: int = 8) -> str:
|
|
|
25
25
|
password += choice(ascii_lowercase)
|
|
26
26
|
|
|
27
27
|
# Fill the remaining characters with a mix of all types
|
|
28
|
-
remaining_chars =
|
|
28
|
+
remaining_chars = "".join(
|
|
29
29
|
choice(ascii_letters + digits + special_characters) for _ in range(length - len(password))
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
password += remaining_chars
|
|
33
33
|
|
|
34
34
|
# Shuffle the password to make it more random but keep the first character as a letter
|
|
35
|
-
return password[0] +
|
|
35
|
+
return password[0] + "".join(random.sample(password[1:], len(password) - 1))
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def generate_random_ipv4() -> str:
|
|
@@ -43,9 +43,9 @@ def generate_random_ipv4() -> str:
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
def generate_random_ipv6() -> str:
|
|
46
|
-
first_group = f
|
|
47
|
-
other_groups = [f
|
|
48
|
-
return
|
|
46
|
+
first_group = f"{random.randint(0x2000, 0x3fff):04x}"
|
|
47
|
+
other_groups = [f"{random.randint(0, 0xffff):04x}" for _ in range(7)]
|
|
48
|
+
return ":".join([first_group] + other_groups)
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def generate_random_ip(ip_version: int) -> str:
|
|
@@ -64,4 +64,4 @@ def generate_random_ip(ip_version: int) -> str:
|
|
|
64
64
|
def create_random_text(min_length: int = 64, max_length: int = 4096) -> str:
|
|
65
65
|
"""Generate a random string with a length between min_length and max_length characters."""
|
|
66
66
|
length = random.randint(min_length, max_length)
|
|
67
|
-
return
|
|
67
|
+
return "".join(random.choice(ascii_letters) for _ in range(length))
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
import os
|
|
2
|
+
import sys
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def get_venv_details() -> dict:
|
|
6
6
|
return {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
"python_executable_path": sys.executable,
|
|
8
|
+
"python_version": sys.version,
|
|
9
|
+
"venv_path": os.getenv("VIRTUAL_ENV"),
|
|
10
10
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-base-toolkit
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: A Python package for managing pytest plugins.
|
|
5
5
|
Home-page: https://github.com/aviz92/python-base-toolkit
|
|
6
6
|
Author: Avi Zaguri
|
|
@@ -13,6 +13,8 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: setuptools
|
|
15
15
|
Requires-Dist: wheel
|
|
16
|
+
Requires-Dist: python-dotenv
|
|
17
|
+
Requires-Dist: pre-commit
|
|
16
18
|
Requires-Dist: colorlog
|
|
17
19
|
Requires-Dist: pathlib
|
|
18
20
|
Requires-Dist: requests
|
|
@@ -47,15 +49,15 @@ pip install python-toolkit
|
|
|
47
49
|
---
|
|
48
50
|
|
|
49
51
|
## 🚀 Features
|
|
50
|
-
- Constants Pack
|
|
51
|
-
- binary
|
|
52
|
+
- Constants Pack
|
|
53
|
+
- binary
|
|
52
54
|
- time
|
|
53
55
|
|
|
54
56
|
- Decorators Pack
|
|
55
57
|
- telemetry
|
|
56
58
|
- timer
|
|
57
59
|
|
|
58
|
-
- Instances Pack
|
|
60
|
+
- Instances Pack
|
|
59
61
|
- instance_manager
|
|
60
62
|
|
|
61
63
|
- Utilities Pack
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
python_base_toolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
python_base_toolkit/consts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
python_base_toolkit/consts/operating_system.py,sha256=8cnaHVz80AecTDhQjhU-i-s88bf1UZ-u0ouxHXxDsmA,80
|
|
4
|
+
python_base_toolkit/consts/units/__init__.py,sha256=1sTK8_l5mO1Osk5dD9aWkyPVXrvYjRJU1brnCmpLdRk,157
|
|
5
|
+
python_base_toolkit/consts/units/binary_units.py,sha256=fgRAP6HXIis6OX4uK8woD-OVD7HtBOBDuhDxJ6AGZVE,412
|
|
6
|
+
python_base_toolkit/consts/units/time_units.py,sha256=aDwRnRR_mbOn2B7NUB9b2-qO98CLFzNE8-ZKfwUZ09Q,216
|
|
7
|
+
python_base_toolkit/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
python_base_toolkit/decorators/telemetry.py,sha256=XKQTYbTqt_3mLVyin5HY7ucgAyAs_hRX_qRd-d7zRJ4,1416
|
|
9
|
+
python_base_toolkit/decorators/timer.py,sha256=MjfkM_aZ1gYG1mmG3Cos3sCM1QdWVyT2wEOEdFA-Uqs,1011
|
|
10
|
+
python_base_toolkit/instances/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
python_base_toolkit/instances/instance_manager.py,sha256=iq72kcPlbc7-AQTuKcBOeqKZbC8vYcsU5GiFXvt5UMs,2278
|
|
12
|
+
python_base_toolkit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
python_base_toolkit/utils/data_serialization.py,sha256=Xf_JTMQPAQfCDM4b0CJxHK7CjmbmVassYViDa65B97A,1197
|
|
14
|
+
python_base_toolkit/utils/date_time.py,sha256=U1P_8qPnEQzI4DguHVEofYLzlyuWfK7L6VsOKPbMa7c,429
|
|
15
|
+
python_base_toolkit/utils/execute.py,sha256=6QYQWd1Z15Ofb4gcAtXCx90l1D-REwTWDZNybU6OibM,1560
|
|
16
|
+
python_base_toolkit/utils/file_utils.py,sha256=XGiFocSNpb77GeX_hEuR_7juNsSwA-RikpH1CvEG4TM,8793
|
|
17
|
+
python_base_toolkit/utils/generate_id.py,sha256=cXyt7AbHRvKvPsKzVE-zFZ3fPIcJ-VVhCaUh63yYUOA,817
|
|
18
|
+
python_base_toolkit/utils/logo.py,sha256=GXCSyPR7RXNshTuMUq1pqZGVZUSJfaktCOv8dWLSz5Q,140
|
|
19
|
+
python_base_toolkit/utils/network.py,sha256=9Y3appWnaIvliA8Jit6vM0uZOmwf6sDVRwzp6BIouHw,4922
|
|
20
|
+
python_base_toolkit/utils/path_utils.py,sha256=zmyIs-bm5GSP6AH8D-ExCQ91aIc-GKwwjfSNzWgpICE,895
|
|
21
|
+
python_base_toolkit/utils/pretty_print.py,sha256=tj1b6zYSsTLBRFfRMXVx3sYv09T5PvslXV8dpBAeq7g,555
|
|
22
|
+
python_base_toolkit/utils/pycache.py,sha256=X-pWRjAd2xcBtMkcyCuKz34SbHWc2leBHBIjqOVoXmw,911
|
|
23
|
+
python_base_toolkit/utils/random_utils.py,sha256=gTH6k1MKKGNZvBTLc5H-eSmtEGvXEdLqtzXN9aHuu-A,2730
|
|
24
|
+
python_base_toolkit/utils/shorten_url.py,sha256=zsyW3wloFJdVb9s3fhR-olL6ekT03FDlFm2xbFY4viw,359
|
|
25
|
+
python_base_toolkit/utils/venv_details.py,sha256=d1WlZr1LuWz2SXdFP0Hqh6VHXXXSIuXB0oTmWXJrGqM,210
|
|
26
|
+
python_base_toolkit-0.0.2.dist-info/licenses/LICENSE,sha256=cSikHY6SZFsPZSBizCDAJ0-Bjjzxt-JtX6TVbKxwimo,1067
|
|
27
|
+
python_base_toolkit-0.0.2.dist-info/METADATA,sha256=Wo5JsmwNIpe9y3IVV2o-L7RqmfkTC-GY82uD1fXGi0g,2208
|
|
28
|
+
python_base_toolkit-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
python_base_toolkit-0.0.2.dist-info/top_level.txt,sha256=I9kK6FGKq_6z3aGySoWamY68GRgg0xsL6sS-uyaxJ2k,20
|
|
30
|
+
python_base_toolkit-0.0.2.dist-info/RECORD,,
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
python_base_toolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
python_base_toolkit/consts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
python_base_toolkit/consts/units/__init__.py,sha256=-1lY3rI3SU7gZKlAKl9E6FjputEdKMGxO0K1GJRXSN0,157
|
|
4
|
-
python_base_toolkit/consts/units/binary_units.py,sha256=8NssiyGO_cJ-FNAh4EYzVlCKRM5n4Q0GC8sL96kvNl4,420
|
|
5
|
-
python_base_toolkit/consts/units/time_units.py,sha256=aDwRnRR_mbOn2B7NUB9b2-qO98CLFzNE8-ZKfwUZ09Q,216
|
|
6
|
-
python_base_toolkit/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
python_base_toolkit/decorators/telemetry.py,sha256=bxittYChUb0zRnDoX_miCZKFQukZ1rPjjPmYfXfcQzA,1400
|
|
8
|
-
python_base_toolkit/decorators/timer.py,sha256=DU0JoqY5cN5hmLVDrNxaMgkPbYNvN3DGU3-FpZXtJK0,813
|
|
9
|
-
python_base_toolkit/instances/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
python_base_toolkit/instances/instance_manager.py,sha256=NDNKVBaYSXvbASehM0YHBWCaYOSLWFJfuKZ7cf0iTho,2058
|
|
11
|
-
python_base_toolkit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
python_base_toolkit/utils/data_serialization.py,sha256=MCF9pALQU2s5tufWXTzW1Dwj3NS1cjI1Le_BL4717iI,1197
|
|
13
|
-
python_base_toolkit/utils/date_time.py,sha256=ycWWTPnkLukENza4NzCX8vQ113UGu4ugPJHGyx4Dgx0,429
|
|
14
|
-
python_base_toolkit/utils/execute.py,sha256=eppuC5YywOHDK-AwFSmcT5YpuO119fFgpJjcSQY80ks,1535
|
|
15
|
-
python_base_toolkit/utils/file_utils.py,sha256=Na6Y1O8PAb21w0y-VnhBFWonOpXl40IrXnefBY8oKNk,8611
|
|
16
|
-
python_base_toolkit/utils/logo.py,sha256=X4u0CmBgTJWqn3b_V0MG2mKV1Gw4rLGOLSQmY7hatIs,140
|
|
17
|
-
python_base_toolkit/utils/network.py,sha256=i3RyWTJGHt77b45gYn0eO9zqXUkScYZvnq-hOFY_eAk,3585
|
|
18
|
-
python_base_toolkit/utils/path_utils.py,sha256=xY0pMHIO-HoWLgc4In-WK8HjsfIwqwozDYzV_nxC_Ps,878
|
|
19
|
-
python_base_toolkit/utils/pretty_print.py,sha256=66XQQKUDjTJcClqWIU_eNmKLKz0h7_1hxMItEAsxbyo,544
|
|
20
|
-
python_base_toolkit/utils/pycache.py,sha256=YwYGYiERSYQJWg1gYDQH1JmjpZpRYWSdacWd8n6zHD8,925
|
|
21
|
-
python_base_toolkit/utils/random_utils.py,sha256=g3yXqei1bGEGxPnAXsdnVtG2DXIF0t68clqul14cAN8,2730
|
|
22
|
-
python_base_toolkit/utils/shorten_url.py,sha256=zsyW3wloFJdVb9s3fhR-olL6ekT03FDlFm2xbFY4viw,359
|
|
23
|
-
python_base_toolkit/utils/venv_details.py,sha256=B67fyXSXdkLnU-fc7QJgSCSpJX5Sv6D13K7to_CDmf4,210
|
|
24
|
-
python_base_toolkit-0.0.1.dist-info/licenses/LICENSE,sha256=cSikHY6SZFsPZSBizCDAJ0-Bjjzxt-JtX6TVbKxwimo,1067
|
|
25
|
-
python_base_toolkit-0.0.1.dist-info/METADATA,sha256=8-kOqa9CkOUB3kopAtR8ZX0wqL0MN35Pi-Emuvx_tnk,2156
|
|
26
|
-
python_base_toolkit-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
27
|
-
python_base_toolkit-0.0.1.dist-info/top_level.txt,sha256=I9kK6FGKq_6z3aGySoWamY68GRgg0xsL6sS-uyaxJ2k,20
|
|
28
|
-
python_base_toolkit-0.0.1.dist-info/RECORD,,
|
|
File without changes
|
{python_base_toolkit-0.0.1.dist-info → python_base_toolkit-0.0.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|