potato-util 0.0.1__py3-none-any.whl → 0.0.3__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.
- potato_util/__init__.py +3 -5
- potato_util/__version__.py +1 -1
- potato_util/_base.py +0 -60
- potato_util/dt.py +128 -104
- potato_util/generator.py +65 -0
- potato_util/http/_base.py +3 -1
- potato_util/http/fastapi.py +1 -1
- potato_util/io/_async.py +36 -24
- potato_util/io/_sync.py +40 -24
- potato_util/sanitizer.py +12 -3
- potato_util/secure.py +8 -61
- potato_util/validator.py +12 -6
- {potato_util-0.0.1.dist-info → potato_util-0.0.3.dist-info}/METADATA +192 -19
- potato_util-0.0.3.dist-info/RECORD +25 -0
- potato_util-0.0.1.dist-info/RECORD +0 -24
- {potato_util-0.0.1.dist-info → potato_util-0.0.3.dist-info}/WHEEL +0 -0
- {potato_util-0.0.1.dist-info → potato_util-0.0.3.dist-info}/licenses/LICENSE.txt +0 -0
- {potato_util-0.0.1.dist-info → potato_util-0.0.3.dist-info}/top_level.txt +0 -0
potato_util/io/_sync.py
CHANGED
|
@@ -13,13 +13,13 @@ logger = logging.getLogger(__name__)
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@validate_call
|
|
16
|
-
def create_dir(create_dir: str, warn_mode: WarnEnum = WarnEnum.DEBUG) -> None:
|
|
16
|
+
def create_dir(create_dir: str, warn_mode: WarnEnum | str = WarnEnum.DEBUG) -> None:
|
|
17
17
|
"""Create directory if `create_dir` doesn't exist.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
20
|
-
create_dir (str, required): Create directory path.
|
|
21
|
-
warn_mode (str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'.
|
|
22
|
-
|
|
20
|
+
create_dir (str , required): Create directory path.
|
|
21
|
+
warn_mode (WarnEnum | str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'.
|
|
22
|
+
Defaults to 'DEBUG'.
|
|
23
23
|
|
|
24
24
|
Raises:
|
|
25
25
|
ValueError: If `create_dir` argument length is out of range.
|
|
@@ -34,6 +34,9 @@ def create_dir(create_dir: str, warn_mode: WarnEnum = WarnEnum.DEBUG) -> None:
|
|
|
34
34
|
f"must be between 1 and {MAX_PATH_LENGTH} characters!"
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
+
if isinstance(warn_mode, str):
|
|
38
|
+
warn_mode = WarnEnum(warn_mode.strip().upper())
|
|
39
|
+
|
|
37
40
|
if not os.path.isdir(create_dir):
|
|
38
41
|
try:
|
|
39
42
|
_message = f"Creating '{create_dir}' directory..."
|
|
@@ -63,13 +66,13 @@ def create_dir(create_dir: str, warn_mode: WarnEnum = WarnEnum.DEBUG) -> None:
|
|
|
63
66
|
|
|
64
67
|
|
|
65
68
|
@validate_call
|
|
66
|
-
def remove_dir(remove_dir: str, warn_mode: WarnEnum = WarnEnum.DEBUG) -> None:
|
|
69
|
+
def remove_dir(remove_dir: str, warn_mode: WarnEnum | str = WarnEnum.DEBUG) -> None:
|
|
67
70
|
"""Remove directory if `remove_dir` exists.
|
|
68
71
|
|
|
69
72
|
Args:
|
|
70
|
-
remove_dir (str, required): Remove directory path.
|
|
71
|
-
warn_mode (str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'.
|
|
72
|
-
|
|
73
|
+
remove_dir (str , required): Remove directory path.
|
|
74
|
+
warn_mode (WarnEnum | str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'.
|
|
75
|
+
Defaults to 'DEBUG'.
|
|
73
76
|
|
|
74
77
|
Raises:
|
|
75
78
|
ValueError: If `remove_dir` argument length is out of range.
|
|
@@ -84,6 +87,9 @@ def remove_dir(remove_dir: str, warn_mode: WarnEnum = WarnEnum.DEBUG) -> None:
|
|
|
84
87
|
f"must be between 1 and {MAX_PATH_LENGTH} characters!"
|
|
85
88
|
)
|
|
86
89
|
|
|
90
|
+
if isinstance(warn_mode, str):
|
|
91
|
+
warn_mode = WarnEnum(warn_mode.strip().upper())
|
|
92
|
+
|
|
87
93
|
if os.path.isdir(remove_dir):
|
|
88
94
|
try:
|
|
89
95
|
_message = f"Removing '{remove_dir}' directory..."
|
|
@@ -113,12 +119,14 @@ def remove_dir(remove_dir: str, warn_mode: WarnEnum = WarnEnum.DEBUG) -> None:
|
|
|
113
119
|
|
|
114
120
|
|
|
115
121
|
@validate_call
|
|
116
|
-
def remove_dirs(
|
|
122
|
+
def remove_dirs(
|
|
123
|
+
remove_dirs: list[str], warn_mode: WarnEnum | str = WarnEnum.DEBUG
|
|
124
|
+
) -> None:
|
|
117
125
|
"""Remove directories if `remove_dirs` exist.
|
|
118
126
|
|
|
119
127
|
Args:
|
|
120
|
-
remove_dirs (
|
|
121
|
-
warn_mode (str
|
|
128
|
+
remove_dirs (list[str] , required): Remove directory paths as list.
|
|
129
|
+
warn_mode (WarnEnum | str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'.
|
|
122
130
|
Defaults to 'DEBUG'.
|
|
123
131
|
"""
|
|
124
132
|
|
|
@@ -129,13 +137,13 @@ def remove_dirs(remove_dirs: list[str], warn_mode: WarnEnum = WarnEnum.DEBUG) ->
|
|
|
129
137
|
|
|
130
138
|
|
|
131
139
|
@validate_call
|
|
132
|
-
def remove_file(file_path: str, warn_mode: WarnEnum = WarnEnum.DEBUG) -> None:
|
|
140
|
+
def remove_file(file_path: str, warn_mode: WarnEnum | str = WarnEnum.DEBUG) -> None:
|
|
133
141
|
"""Remove file if `file_path` exists.
|
|
134
142
|
|
|
135
143
|
Args:
|
|
136
|
-
file_path (str, required): Remove file path.
|
|
137
|
-
warn_mode (str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'.
|
|
138
|
-
|
|
144
|
+
file_path (str , required): Remove file path.
|
|
145
|
+
warn_mode (WarnEnum | str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'.
|
|
146
|
+
Defaults to 'DEBUG'.
|
|
139
147
|
|
|
140
148
|
Raises:
|
|
141
149
|
ValueError: If `file_path` argument length is out of range.
|
|
@@ -150,6 +158,9 @@ def remove_file(file_path: str, warn_mode: WarnEnum = WarnEnum.DEBUG) -> None:
|
|
|
150
158
|
f"must be between 1 and {MAX_PATH_LENGTH} characters!"
|
|
151
159
|
)
|
|
152
160
|
|
|
161
|
+
if isinstance(warn_mode, str):
|
|
162
|
+
warn_mode = WarnEnum(warn_mode.strip().upper())
|
|
163
|
+
|
|
153
164
|
if os.path.isfile(file_path):
|
|
154
165
|
try:
|
|
155
166
|
_message = f"Removing '{file_path}' file..."
|
|
@@ -179,12 +190,14 @@ def remove_file(file_path: str, warn_mode: WarnEnum = WarnEnum.DEBUG) -> None:
|
|
|
179
190
|
|
|
180
191
|
|
|
181
192
|
@validate_call
|
|
182
|
-
def remove_files(
|
|
193
|
+
def remove_files(
|
|
194
|
+
file_paths: list[str], warn_mode: WarnEnum | str = WarnEnum.DEBUG
|
|
195
|
+
) -> None:
|
|
183
196
|
"""Remove files if `file_paths` exist.
|
|
184
197
|
|
|
185
198
|
Args:
|
|
186
|
-
file_paths (
|
|
187
|
-
warn_mode (str
|
|
199
|
+
file_paths (list[str] , required): Remove file paths as list.
|
|
200
|
+
warn_mode (WarnEnum | str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'.
|
|
188
201
|
Defaults to 'DEBUG'.
|
|
189
202
|
"""
|
|
190
203
|
|
|
@@ -199,16 +212,16 @@ def get_file_checksum(
|
|
|
199
212
|
file_path: str,
|
|
200
213
|
hash_method: HashAlgoEnum = HashAlgoEnum.md5,
|
|
201
214
|
chunk_size: int = 4096,
|
|
202
|
-
warn_mode: WarnEnum = WarnEnum.DEBUG,
|
|
215
|
+
warn_mode: WarnEnum | str = WarnEnum.DEBUG,
|
|
203
216
|
) -> str | None:
|
|
204
217
|
"""Get file checksum.
|
|
205
218
|
|
|
206
219
|
Args:
|
|
207
|
-
file_path (str
|
|
208
|
-
hash_method (HashAlgoEnum, optional): Hash method. Defaults to `HashAlgoEnum.md5`.
|
|
209
|
-
chunk_size (int
|
|
210
|
-
warn_mode (str
|
|
211
|
-
|
|
220
|
+
file_path (str , required): Target file path.
|
|
221
|
+
hash_method (HashAlgoEnum , optional): Hash method. Defaults to `HashAlgoEnum.md5`.
|
|
222
|
+
chunk_size (int , optional): Chunk size. Defaults to 4096.
|
|
223
|
+
warn_mode (WarnEnum | str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'.
|
|
224
|
+
Defaults to 'DEBUG'.
|
|
212
225
|
|
|
213
226
|
Raises:
|
|
214
227
|
ValueError: If `file_path` argument length is out of range.
|
|
@@ -231,6 +244,9 @@ def get_file_checksum(
|
|
|
231
244
|
f"`chunk_size` argument value {chunk_size} is invalid, must be greater than 10!"
|
|
232
245
|
)
|
|
233
246
|
|
|
247
|
+
if isinstance(warn_mode, str):
|
|
248
|
+
warn_mode = WarnEnum(warn_mode.strip().upper())
|
|
249
|
+
|
|
234
250
|
_file_checksum: str | None = None
|
|
235
251
|
if os.path.isfile(file_path):
|
|
236
252
|
_file_hash = hashlib.new(hash_method.value)
|
potato_util/sanitizer.py
CHANGED
|
@@ -30,7 +30,7 @@ def escape_html(val: str) -> str:
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@validate_call
|
|
33
|
-
def escape_url(val: AnyHttpUrl) -> str:
|
|
33
|
+
def escape_url(val: AnyHttpUrl | str) -> str:
|
|
34
34
|
"""Escape URL characters.
|
|
35
35
|
|
|
36
36
|
Args:
|
|
@@ -40,6 +40,9 @@ def escape_url(val: AnyHttpUrl) -> str:
|
|
|
40
40
|
str: Escaped string.
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
|
+
if isinstance(val, str):
|
|
44
|
+
val = AnyHttpUrl(val)
|
|
45
|
+
|
|
43
46
|
_escaped = quote(str(val))
|
|
44
47
|
return _escaped
|
|
45
48
|
|
|
@@ -47,13 +50,19 @@ def escape_url(val: AnyHttpUrl) -> str:
|
|
|
47
50
|
@validate_call
|
|
48
51
|
def sanitize_special_chars(val: str, mode: str = "LOW") -> str:
|
|
49
52
|
"""Sanitize special characters.
|
|
53
|
+
Available modes:
|
|
54
|
+
- "BASE" or "HTML": Basic HTML special characters.
|
|
55
|
+
- "LOW": Low-risk special characters.
|
|
56
|
+
- "MEDIUM": Medium-risk special characters.
|
|
57
|
+
- "HIGH", "SCRIPT", or "SQL": High-risk special characters.
|
|
58
|
+
- "STRICT": Strict mode, removes most special characters.
|
|
50
59
|
|
|
51
60
|
Args:
|
|
52
61
|
val (str, required): String to sanitize.
|
|
53
62
|
mode (str, optional): Sanitization mode. Defaults to "LOW".
|
|
54
63
|
|
|
55
64
|
Raises:
|
|
56
|
-
ValueError: If `mode` is
|
|
65
|
+
ValueError: If `mode` argument value is invalid.
|
|
57
66
|
|
|
58
67
|
Returns:
|
|
59
68
|
str: Sanitized string.
|
|
@@ -72,7 +81,7 @@ def sanitize_special_chars(val: str, mode: str = "LOW") -> str:
|
|
|
72
81
|
elif mode == "STRICT":
|
|
73
82
|
_pattern = SPECIAL_CHARS_STRICT_REGEX
|
|
74
83
|
else:
|
|
75
|
-
raise ValueError(f"
|
|
84
|
+
raise ValueError(f"`mode` argument value '{mode}' is invalid!")
|
|
76
85
|
|
|
77
86
|
_sanitized = re.sub(pattern=_pattern, repl="", string=val)
|
|
78
87
|
return _sanitized
|
potato_util/secure.py
CHANGED
|
@@ -1,73 +1,19 @@
|
|
|
1
|
-
import uuid
|
|
2
|
-
import string
|
|
3
|
-
import secrets
|
|
4
1
|
import hashlib
|
|
5
2
|
|
|
6
3
|
from pydantic import validate_call
|
|
7
4
|
|
|
8
5
|
from .constants import HashAlgoEnum
|
|
9
|
-
from .dt import now_ts
|
|
10
6
|
|
|
11
7
|
|
|
12
8
|
@validate_call
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
prefix (str, optional): Prefix of id. Defaults to ''.
|
|
18
|
-
|
|
19
|
-
Raises:
|
|
20
|
-
ValueError: If `prefix` length is greater than 32.
|
|
21
|
-
|
|
22
|
-
Returns:
|
|
23
|
-
str: Unique id.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
prefix = prefix.strip()
|
|
27
|
-
if 32 < len(prefix):
|
|
28
|
-
raise ValueError(
|
|
29
|
-
f"`prefix` argument length {len(prefix)} is too long, must be less than or equal to 32!",
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
_id = str(f"{prefix}{now_ts()}_{uuid.uuid4().hex}").lower()
|
|
33
|
-
return _id
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@validate_call
|
|
37
|
-
def gen_random_string(length: int = 16, is_alphanum: bool = True) -> str:
|
|
38
|
-
"""Generate secure random string.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
length (int , optional): Length of random string. Defaults to 16.
|
|
42
|
-
is_alphanum (bool, optional): If True, generate only alphanumeric string. Defaults to True.
|
|
43
|
-
|
|
44
|
-
Raises:
|
|
45
|
-
ValueError: If `length` is less than 1.
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
str: Generated random string.
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
if length < 1:
|
|
52
|
-
raise ValueError(
|
|
53
|
-
f"`length` argument value {length} is too small, must be greater than or equal to 1!",
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
_base_chars = string.ascii_letters + string.digits
|
|
57
|
-
if not is_alphanum:
|
|
58
|
-
_base_chars += string.punctuation
|
|
59
|
-
|
|
60
|
-
_random_str = "".join(secrets.choice(_base_chars) for _i in range(length))
|
|
61
|
-
return _random_str
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
@validate_call
|
|
65
|
-
def hash_str(val: str | bytes, algorithm: HashAlgoEnum = HashAlgoEnum.sha256) -> str:
|
|
9
|
+
def hash_str(
|
|
10
|
+
val: str | bytes, algorithm: HashAlgoEnum | str = HashAlgoEnum.sha256
|
|
11
|
+
) -> str:
|
|
66
12
|
"""Hash a string using a specified hash algorithm.
|
|
67
13
|
|
|
68
14
|
Args:
|
|
69
|
-
val (str | bytes
|
|
70
|
-
algorithm (HashAlgoEnum,
|
|
15
|
+
val (str | bytes , required): The value to be hashed.
|
|
16
|
+
algorithm (HashAlgoEnum | str, optional): The hash algorithm to use. Defaults to `HashAlgoEnum.sha256`.
|
|
71
17
|
|
|
72
18
|
Returns:
|
|
73
19
|
str: The hexadecimal representation of the digest.
|
|
@@ -76,6 +22,9 @@ def hash_str(val: str | bytes, algorithm: HashAlgoEnum = HashAlgoEnum.sha256) ->
|
|
|
76
22
|
if isinstance(val, str):
|
|
77
23
|
val = val.encode("utf-8")
|
|
78
24
|
|
|
25
|
+
if isinstance(algorithm, str):
|
|
26
|
+
algorithm = HashAlgoEnum(algorithm.strip().lower())
|
|
27
|
+
|
|
79
28
|
_hash = hashlib.new(algorithm.value)
|
|
80
29
|
_hash.update(val)
|
|
81
30
|
|
|
@@ -84,7 +33,5 @@ def hash_str(val: str | bytes, algorithm: HashAlgoEnum = HashAlgoEnum.sha256) ->
|
|
|
84
33
|
|
|
85
34
|
|
|
86
35
|
__all__ = [
|
|
87
|
-
"gen_unique_id",
|
|
88
|
-
"gen_random_string",
|
|
89
36
|
"hash_str",
|
|
90
37
|
]
|
potato_util/validator.py
CHANGED
|
@@ -18,7 +18,7 @@ def is_truthy(val: str | bool | int | float | None) -> bool:
|
|
|
18
18
|
"""Check if the value is truthy.
|
|
19
19
|
|
|
20
20
|
Args:
|
|
21
|
-
val (
|
|
21
|
+
val (str | bool | int | float | None, required): Value to check.
|
|
22
22
|
|
|
23
23
|
Raises:
|
|
24
24
|
ValueError: If `val` argument type is string and value is invalid.
|
|
@@ -45,7 +45,7 @@ def is_falsy(val: str | bool | int | float | None) -> bool:
|
|
|
45
45
|
"""Check if the value is falsy.
|
|
46
46
|
|
|
47
47
|
Args:
|
|
48
|
-
val (
|
|
48
|
+
val (str | bool | int | float | None, required): Value to check.
|
|
49
49
|
|
|
50
50
|
Returns:
|
|
51
51
|
bool: True if the value is falsy, False otherwise.
|
|
@@ -93,8 +93,8 @@ def is_valid(val: str, pattern: Pattern | str) -> bool:
|
|
|
93
93
|
"""Check if the string is valid with given pattern.
|
|
94
94
|
|
|
95
95
|
Args:
|
|
96
|
-
val (str
|
|
97
|
-
pattern (
|
|
96
|
+
val (str , required): String to check.
|
|
97
|
+
pattern (Pattern | str, required): Pattern regex to check.
|
|
98
98
|
|
|
99
99
|
Returns:
|
|
100
100
|
bool: True if the string is valid with given pattern, False otherwise.
|
|
@@ -107,13 +107,19 @@ def is_valid(val: str, pattern: Pattern | str) -> bool:
|
|
|
107
107
|
@validate_call
|
|
108
108
|
def has_special_chars(val: str, mode: str = "LOW") -> bool:
|
|
109
109
|
"""Check if the string has special characters.
|
|
110
|
+
Available modes:
|
|
111
|
+
- "BASE" or "HTML": Basic HTML special characters.
|
|
112
|
+
- "LOW": Low-risk special characters.
|
|
113
|
+
- "MEDIUM": Medium-risk special characters.
|
|
114
|
+
- "HIGH", "SCRIPT", or "SQL": High-risk special characters.
|
|
115
|
+
- "STRICT": Strict mode, checks for most special characters.
|
|
110
116
|
|
|
111
117
|
Args:
|
|
112
118
|
val (str, required): String to check.
|
|
113
119
|
mode (str, optional): Check mode. Defaults to "LOW".
|
|
114
120
|
|
|
115
121
|
Raises:
|
|
116
|
-
ValueError: If `mode` is
|
|
122
|
+
ValueError: If `mode` argument value is invalid.
|
|
117
123
|
|
|
118
124
|
Returns:
|
|
119
125
|
bool: True if the string has special characters, False otherwise.
|
|
@@ -134,7 +140,7 @@ def has_special_chars(val: str, mode: str = "LOW") -> bool:
|
|
|
134
140
|
elif mode == "STRICT":
|
|
135
141
|
_pattern = SPECIAL_CHARS_STRICT_REGEX
|
|
136
142
|
else:
|
|
137
|
-
raise ValueError(f"
|
|
143
|
+
raise ValueError(f"`mode` argument value '{mode}' is invalid!")
|
|
138
144
|
|
|
139
145
|
_has_special_chars = bool(re.search(pattern=_pattern, string=val))
|
|
140
146
|
return _has_special_chars
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: potato_util
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: 'potato_util' is collection of simple useful utils package for python.
|
|
5
5
|
Author-email: Batkhuu Byambajav <batkhuu10@gmail.com>
|
|
6
|
-
Project-URL: Homepage, https://github.com/bybatkhuu/module
|
|
6
|
+
Project-URL: Homepage, https://github.com/bybatkhuu/module-python-utils
|
|
7
7
|
Project-URL: Documentation, https://pyutils-docs.bybatkhuu.dev
|
|
8
|
-
Project-URL: Repository, https://github.com/bybatkhuu/module
|
|
9
|
-
Project-URL: Issues, https://github.com/bybatkhuu/module
|
|
10
|
-
Project-URL: Changelog, https://github.com/bybatkhuu/module
|
|
8
|
+
Project-URL: Repository, https://github.com/bybatkhuu/module-python-utils.git
|
|
9
|
+
Project-URL: Issues, https://github.com/bybatkhuu/module-python-utils/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/bybatkhuu/module-python-utils/blob/main/CHANGELOG.md
|
|
11
11
|
Keywords: potato_util,utils,utilities,tools,helpers
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -77,11 +77,11 @@ Requires-Dist: pyright<2.0.0,>=1.1.392; extra == "dev"
|
|
|
77
77
|
Requires-Dist: pre-commit<5.0.0,>=4.0.1; extra == "dev"
|
|
78
78
|
Dynamic: license-file
|
|
79
79
|
|
|
80
|
-
# Potato
|
|
80
|
+
# Potato Util (Python Utils)
|
|
81
81
|
|
|
82
82
|
[](https://choosealicense.com/licenses/mit)
|
|
83
|
-
[](https://img.shields.io/github/v/release/bybatkhuu/module
|
|
83
|
+
[](https://github.com/bybatkhuu/module-python-utils/actions/workflows/2.build-publish.yml)
|
|
84
|
+
[](https://github.com/bybatkhuu/module-python-utils/releases)
|
|
85
85
|
|
|
86
86
|
'potato_util' is collection of simple useful utils package for python.
|
|
87
87
|
|
|
@@ -89,11 +89,13 @@ Dynamic: license-file
|
|
|
89
89
|
|
|
90
90
|
- Python utilities
|
|
91
91
|
- Datetime utilities
|
|
92
|
-
-
|
|
93
|
-
- HTTP utilities
|
|
94
|
-
- Security utilities
|
|
92
|
+
- Generator utilities
|
|
95
93
|
- Sanitation utilities
|
|
94
|
+
- Security utilities
|
|
96
95
|
- Validation utilities
|
|
96
|
+
- HTTP utilities
|
|
97
|
+
- File I/O utilities
|
|
98
|
+
- And more...
|
|
97
99
|
|
|
98
100
|
---
|
|
99
101
|
|
|
@@ -130,20 +132,20 @@ cd ~/workspaces/projects
|
|
|
130
132
|
**OPTION A.** Clone the repository:
|
|
131
133
|
|
|
132
134
|
```sh
|
|
133
|
-
git clone https://github.com/bybatkhuu/module
|
|
134
|
-
cd module
|
|
135
|
+
git clone https://github.com/bybatkhuu/module-python-utils.git && \
|
|
136
|
+
cd module-python-utils
|
|
135
137
|
```
|
|
136
138
|
|
|
137
139
|
**OPTION B.** Clone the repository (for **DEVELOPMENT**: git + ssh key):
|
|
138
140
|
|
|
139
141
|
```sh
|
|
140
|
-
git clone git@github.com:bybatkhuu/module
|
|
141
|
-
cd module
|
|
142
|
+
git clone git@github.com:bybatkhuu/module-python-utils.git && \
|
|
143
|
+
cd module-python-utils
|
|
142
144
|
```
|
|
143
145
|
|
|
144
146
|
**OPTION C.** Download source code:
|
|
145
147
|
|
|
146
|
-
1. Download archived **zip** file from [**releases**](https://github.com/bybatkhuu/module
|
|
148
|
+
1. Download archived **zip** file from [**releases**](https://github.com/bybatkhuu/module-python-utils/releases).
|
|
147
149
|
2. Extract it into the projects directory.
|
|
148
150
|
|
|
149
151
|
### 3. 📦 Install the package
|
|
@@ -153,13 +155,13 @@ git clone git@github.com:bybatkhuu/module.python-utils.git && \
|
|
|
153
155
|
**OPTION A.** [**RECOMMENDED**] Install from **PyPi**:
|
|
154
156
|
|
|
155
157
|
```sh
|
|
156
|
-
pip install -U
|
|
158
|
+
pip install -U potato-util
|
|
157
159
|
```
|
|
158
160
|
|
|
159
161
|
**OPTION B.** Install latest version directly from **GitHub** repository:
|
|
160
162
|
|
|
161
163
|
```sh
|
|
162
|
-
pip install git+https://github.com/bybatkhuu/module
|
|
164
|
+
pip install git+https://github.com/bybatkhuu/module-python-utils.git
|
|
163
165
|
```
|
|
164
166
|
|
|
165
167
|
**OPTION C.** Install from the downloaded **source code**:
|
|
@@ -180,7 +182,7 @@ pip install -e .[dev]
|
|
|
180
182
|
|
|
181
183
|
**OPTION E.** Install from **pre-built release** files:
|
|
182
184
|
|
|
183
|
-
1. Download **`.whl`** or **`.tar.gz`** file from [**releases**](https://github.com/bybatkhuu/module
|
|
185
|
+
1. Download **`.whl`** or **`.tar.gz`** file from [**releases**](https://github.com/bybatkhuu/module-python-utils/releases)
|
|
184
186
|
2. Install with pip:
|
|
185
187
|
|
|
186
188
|
```sh
|
|
@@ -210,6 +212,177 @@ cp -r ./src/potato_util /some/path/project/
|
|
|
210
212
|
[**`examples/simple/main.py`**](./examples/simple/main.py):
|
|
211
213
|
|
|
212
214
|
```python
|
|
215
|
+
#!/usr/bin/env python
|
|
216
|
+
|
|
217
|
+
# Standard libraries
|
|
218
|
+
import os
|
|
219
|
+
import sys
|
|
220
|
+
import logging
|
|
221
|
+
|
|
222
|
+
# Third-party libraries
|
|
223
|
+
from pydantic import AnyHttpUrl
|
|
224
|
+
|
|
225
|
+
# Internal modules
|
|
226
|
+
import potato_util
|
|
227
|
+
import potato_util.dt as dt_utils
|
|
228
|
+
import potato_util.generator as gen_utils
|
|
229
|
+
import potato_util.sanitizer as sanitizer_utils
|
|
230
|
+
import potato_util.secure as secure_utils
|
|
231
|
+
import potato_util.validator as validator_utils
|
|
232
|
+
import potato_util.http as http_utils
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
logger = logging.getLogger(__name__)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def main() -> None:
|
|
239
|
+
_log_level = logging.INFO
|
|
240
|
+
if str(os.getenv("DEBUG", "0")).lower() in ("1", "true", "t", "yes", "y"):
|
|
241
|
+
_log_level = logging.DEBUG
|
|
242
|
+
|
|
243
|
+
logging.basicConfig(
|
|
244
|
+
stream=sys.stdout,
|
|
245
|
+
level=_log_level,
|
|
246
|
+
datefmt="%Y-%m-%d %H:%M:%S %z",
|
|
247
|
+
format="[%(asctime)s | %(levelname)s | %(filename)s:%(lineno)d]: %(message)s",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Base utils:
|
|
251
|
+
logger.info("[BASE UTILITIES]")
|
|
252
|
+
_dict1 = {"a": 1, "b": {"c": 2, "d": 3}, "g": [2, 3, 4]}
|
|
253
|
+
_dict2 = {"b": {"c": 20, "e": 30}, "f": 40, "g": [5, 6, 7]}
|
|
254
|
+
_merged_dict = potato_util.deep_merge(_dict1, _dict2)
|
|
255
|
+
logger.info(f"Merged dict: {_merged_dict}")
|
|
256
|
+
|
|
257
|
+
_camel_str = "CamelCaseString"
|
|
258
|
+
_snake_str = potato_util.camel_to_snake(_camel_str)
|
|
259
|
+
logger.info(f"Converted '{_camel_str}' to '{_snake_str}'")
|
|
260
|
+
logger.info("-" * 80)
|
|
261
|
+
|
|
262
|
+
# Datetime utils:
|
|
263
|
+
logger.info("[DATETIME UTILITIES]")
|
|
264
|
+
_now_local_dt = dt_utils.now_local_dt()
|
|
265
|
+
logger.info(f"Current local datetime: {_now_local_dt}")
|
|
266
|
+
|
|
267
|
+
_now_utc_dt = dt_utils.now_utc_dt()
|
|
268
|
+
logger.info(f"Current UTC datetime: {_now_utc_dt}")
|
|
269
|
+
|
|
270
|
+
_now_ny_dt = dt_utils.now_dt(tz="America/New_York")
|
|
271
|
+
logger.info(f"Current New York datetime: {_now_ny_dt}")
|
|
272
|
+
|
|
273
|
+
_now_ts = dt_utils.now_ts()
|
|
274
|
+
logger.info(f"Current UTC timestamp (seconds): {_now_ts}")
|
|
275
|
+
|
|
276
|
+
_now_ts_ms = dt_utils.now_ts(unit="MILLISECONDS")
|
|
277
|
+
logger.info(f"Current UTC timestamp (ms): {_now_ts_ms}")
|
|
278
|
+
|
|
279
|
+
_now_ts_micro = dt_utils.now_ts(unit="MICROSECONDS")
|
|
280
|
+
logger.info(f"Current UTC timestamp (microseconds): {_now_ts_micro}")
|
|
281
|
+
|
|
282
|
+
_now_ts_ns = dt_utils.now_ts(unit="NANOSECONDS")
|
|
283
|
+
logger.info(f"Current UTC timestamp (nanoseconds): {_now_ts_ns}")
|
|
284
|
+
|
|
285
|
+
_dt_ts = dt_utils.dt_to_ts(dt=_now_local_dt)
|
|
286
|
+
logger.info(f"Converted local datetime to UTC timestamp (seconds): {_dt_ts}")
|
|
287
|
+
|
|
288
|
+
_replaced_tz_dt = dt_utils.replace_tz(dt=_now_local_dt, tz="Asia/Ulaanbaatar")
|
|
289
|
+
logger.info(f"Add or replace timezone with Asia/Ulaanbaatar: {_replaced_tz_dt}")
|
|
290
|
+
|
|
291
|
+
_converted_dt = dt_utils.convert_tz(dt=_now_ny_dt, tz="Asia/Seoul")
|
|
292
|
+
logger.info(
|
|
293
|
+
f"Calculated and converted timezone from New York to Seoul: {_converted_dt}"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
_dt_iso = dt_utils.dt_to_iso(dt=_now_local_dt)
|
|
297
|
+
logger.info(f"Parsing datetime to ISO 8601 format string: {_dt_iso}")
|
|
298
|
+
|
|
299
|
+
_future_dt = dt_utils.calc_future_dt(delta=3600, dt=_now_local_dt, tz="Asia/Tokyo")
|
|
300
|
+
logger.info(f"Calculated future datetime after 3600 seconds in Tokyo: {_future_dt}")
|
|
301
|
+
logger.info("-" * 80)
|
|
302
|
+
|
|
303
|
+
# Generator utils:
|
|
304
|
+
logger.info("[GENERATOR UTILITIES]")
|
|
305
|
+
_unique_id = gen_utils.gen_unique_id(prefix="item_")
|
|
306
|
+
logger.info(f"Generated unique ID based on datetime and UUIDv4: {_unique_id}")
|
|
307
|
+
|
|
308
|
+
_random_str = gen_utils.gen_random_string(length=32, is_alphanum=False)
|
|
309
|
+
logger.info(f"Generated secure random string: {_random_str}")
|
|
310
|
+
logger.info("-" * 80)
|
|
311
|
+
|
|
312
|
+
# Sanitizer utils:
|
|
313
|
+
logger.info("[SANITIZER UTILITIES]")
|
|
314
|
+
_raw_html = ' <script>alert("XSS Attack!");</script> '
|
|
315
|
+
_escaped_html = sanitizer_utils.escape_html(val=_raw_html)
|
|
316
|
+
logger.info(f"Escaped HTML: {_escaped_html}")
|
|
317
|
+
|
|
318
|
+
_raw_url = "https://www.example.com/search?q=potato util&body=<script>alert('Attack!')</script>&lang=한국어"
|
|
319
|
+
_escaped_url = sanitizer_utils.escape_url(val=_raw_url)
|
|
320
|
+
logger.info(f"Escaped URL: {_escaped_url}")
|
|
321
|
+
|
|
322
|
+
_raw_str = "Hello@World! This is a test_string with special#chars$%&*()[]{};:'\",.<>?/\\|`~"
|
|
323
|
+
_sanitized_str = sanitizer_utils.sanitize_special_chars(val=_raw_str, mode="STRICT")
|
|
324
|
+
logger.info(f"Sanitized string: {_sanitized_str}")
|
|
325
|
+
logger.info("-" * 80)
|
|
326
|
+
|
|
327
|
+
# Secure utils:
|
|
328
|
+
logger.info("[SECURE UTILITIES]")
|
|
329
|
+
_input_str = "SensitiveInformation123!"
|
|
330
|
+
_hashed_str_sha256 = secure_utils.hash_str(val=_input_str, algorithm="sha256")
|
|
331
|
+
logger.info(f"SHA-256 hashed string: {_hashed_str_sha256}")
|
|
332
|
+
logger.info("-" * 80)
|
|
333
|
+
|
|
334
|
+
# Validator utils:
|
|
335
|
+
logger.info("[VALIDATOR UTILITIES]")
|
|
336
|
+
_is_yes_truthy = validator_utils.is_truthy(val="Yes")
|
|
337
|
+
logger.info(f"Is 'Yes' truthy: {_is_yes_truthy}")
|
|
338
|
+
_is_off_truthy = validator_utils.is_truthy(val="OFF")
|
|
339
|
+
logger.info(f"Is 'OFF' truthy: {_is_off_truthy}")
|
|
340
|
+
|
|
341
|
+
_is_no_falsy = validator_utils.is_falsy(val="f")
|
|
342
|
+
logger.info(f"Is 'f' falsy: {_is_no_falsy}")
|
|
343
|
+
_is_1_falsy = validator_utils.is_falsy(val="1")
|
|
344
|
+
logger.info(f"Is '1' falsy: {_is_1_falsy}")
|
|
345
|
+
|
|
346
|
+
_request_id = "f058ebd6-02f7-4d3f-942e-904344e8cde5"
|
|
347
|
+
_is_valid_request_id = validator_utils.is_request_id(val=_request_id)
|
|
348
|
+
logger.info(f"Is '{_request_id}' a valid request ID: {_is_valid_request_id}")
|
|
349
|
+
|
|
350
|
+
_blacklist = ["hacker", "guest"]
|
|
351
|
+
_input_username = "hacker"
|
|
352
|
+
_is_blacklisted = validator_utils.is_blacklisted(
|
|
353
|
+
val=_input_username, blacklist=_blacklist
|
|
354
|
+
)
|
|
355
|
+
logger.info(f"Is '{_input_username}' blacklisted: {_is_blacklisted}")
|
|
356
|
+
|
|
357
|
+
_pattern = r"^[a-zA-Z0-9_]{3,16}$" # Alphanumeric and underscores, 3-16 chars
|
|
358
|
+
_test_username = "valid_user123"
|
|
359
|
+
_is_valid_username = validator_utils.is_valid(val=_test_username, pattern=_pattern)
|
|
360
|
+
logger.info(f"Is '{_test_username}' a valid username: {_is_valid_username}")
|
|
361
|
+
|
|
362
|
+
_string_with_special_chars = "Hello@World!"
|
|
363
|
+
_has_special_chars = validator_utils.has_special_chars(
|
|
364
|
+
val=_string_with_special_chars, mode="STRICT"
|
|
365
|
+
)
|
|
366
|
+
logger.info(
|
|
367
|
+
f"Does '{_string_with_special_chars}' have special chars: {_has_special_chars}"
|
|
368
|
+
)
|
|
369
|
+
logger.info("-" * 80)
|
|
370
|
+
|
|
371
|
+
# HTTP utils:
|
|
372
|
+
logger.info("[HTTP UTILITIES]")
|
|
373
|
+
_http_status_tuple = http_utils.get_http_status(status_code=403)
|
|
374
|
+
logger.info(f"HTTP status and known: {_http_status_tuple}")
|
|
375
|
+
|
|
376
|
+
_url = AnyHttpUrl("https://www.google.com")
|
|
377
|
+
_is_connectable = http_utils.is_connectable(url=_url, timeout=3, check_status=True)
|
|
378
|
+
logger.info(f"Is '{_url}' connectable: {_is_connectable}")
|
|
379
|
+
logger.info("-" * 80)
|
|
380
|
+
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
if __name__ == "__main__":
|
|
385
|
+
main()
|
|
213
386
|
```
|
|
214
387
|
|
|
215
388
|
👍
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
potato_util/__init__.py,sha256=xl4th2Z_OmTk-3aO1w05Vh8ob0BnX4RcY1fT9tGX61c,74
|
|
2
|
+
potato_util/__version__.py,sha256=4GZKi13lDTD25YBkGakhZyEQZWTER_OWQMNPoH_UM2c,22
|
|
3
|
+
potato_util/_base.py,sha256=557Dj7eYmPIPE2s5yNtCKbJPOEj3lieHY4ZE1roxvFQ,1315
|
|
4
|
+
potato_util/dt.py,sha256=DatLzuXOxdanplBv-Kf3qOaomrTCUUBSYv_QgTtQB6U,7635
|
|
5
|
+
potato_util/generator.py,sha256=vdvHj1UrCOQI2WXQHQ2H63PMLQ6yOXAZPNl0m-6BgtY,1572
|
|
6
|
+
potato_util/sanitizer.py,sha256=pZuMDvZxKKIdwFKTcqfmHb9ji-UefSgRgJ8ynHG6UFs,2267
|
|
7
|
+
potato_util/secure.py,sha256=HxlUGyJl15qiL5WFrTZOJLgBZzuEr3Eu--qvBPmj2So,848
|
|
8
|
+
potato_util/validator.py,sha256=TANw7uWHi0PANPkrNDOFFvRcYL9Ge75b4xGSf3PTW1A,4083
|
|
9
|
+
potato_util/constants/__init__.py,sha256=lhxZ2k-QotMR_w9w-Gv-9pCEFrQSKxe1YEIvV83pf8E,80
|
|
10
|
+
potato_util/constants/_base.py,sha256=J1i611erzcrPRZ6USkVgnNZ_IvvN9YLbcltvyIngpcg,61
|
|
11
|
+
potato_util/constants/_enum.py,sha256=tThmf7lFsGfFUNNQE0hOUZ0JKc821ZGZnSyJIj2jVTQ,515
|
|
12
|
+
potato_util/constants/_regex.py,sha256=T7SSsqD0y8jXJJPURwAK2uA59AjLt1uqGfqqUd1a22o,710
|
|
13
|
+
potato_util/http/__init__.py,sha256=qWOzScMVJEHjzClEqOYmGtfkZyU8f8Hz8nT054Dbblo,229
|
|
14
|
+
potato_util/http/_async.py,sha256=dfs-ynchPKdnMNQXqyoqhcz-jhtQ7g_4JnNfVr3bXk0,1178
|
|
15
|
+
potato_util/http/_base.py,sha256=qoW5U8gxihVMx3v1gSLLmptMjScVsDS9PowvJ6tTc9c,1436
|
|
16
|
+
potato_util/http/_sync.py,sha256=i8oyRmh-WraOzotaUbssIjOPvBh1pvLPupdoCvyHTK0,1169
|
|
17
|
+
potato_util/http/fastapi.py,sha256=iqCxZcQDIVtcqCN9dZ4itcoUs6KzsfRGgK7sRd5EmOA,675
|
|
18
|
+
potato_util/io/__init__.py,sha256=FoGcN7t0uArQV4hbMR1X5aeC8Yq-h_ds4xooNpkmgG0,209
|
|
19
|
+
potato_util/io/_async.py,sha256=XVNFDCRII-ib32tksxj5G3nDLsV-ylTW2Hw3_seIY-k,10012
|
|
20
|
+
potato_util/io/_sync.py,sha256=ZVRukOYWWvXtVlU_Gf8GoKnQZqSI2LISHPMmxXi4-2U,9644
|
|
21
|
+
potato_util-0.0.3.dist-info/licenses/LICENSE.txt,sha256=CUTK-r0BWIg1r0bBiemAcMhakgV0N7HuRhw6rQ-A9A4,1074
|
|
22
|
+
potato_util-0.0.3.dist-info/METADATA,sha256=-7dkOo0302T7vu8b56CITU-K6RV_qnPtYudRt7dBeg8,15410
|
|
23
|
+
potato_util-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
potato_util-0.0.3.dist-info/top_level.txt,sha256=pLMnSfT6rhlYBpo2Gnd8kKMDxcuvxdVizqsv1dd1frw,12
|
|
25
|
+
potato_util-0.0.3.dist-info/RECORD,,
|