potato-util 0.0.2__tar.gz → 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {potato_util-0.0.2/src/potato_util.egg-info → potato_util-0.0.4}/PKG-INFO +197 -19
- potato_util-0.0.4/README.md +386 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/pyproject.toml +15 -17
- potato_util-0.0.4/requirements/requirements.dev.txt +2 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.test.txt +0 -1
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/__init__.py +3 -5
- potato_util-0.0.4/src/potato_util/__version__.py +1 -0
- potato_util-0.0.4/src/potato_util/_base.py +57 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/dt.py +128 -104
- potato_util-0.0.2/src/potato_util/secure.py → potato_util-0.0.4/src/potato_util/generator.py +3 -28
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/_base.py +3 -1
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/fastapi.py +1 -1
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/io/_async.py +36 -24
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/io/_sync.py +40 -24
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/sanitizer.py +12 -3
- potato_util-0.0.4/src/potato_util/secure.py +37 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/validator.py +12 -6
- {potato_util-0.0.2 → potato_util-0.0.4/src/potato_util.egg-info}/PKG-INFO +197 -19
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util.egg-info/SOURCES.txt +1 -0
- potato_util-0.0.2/README.md +0 -208
- potato_util-0.0.2/requirements/requirements.dev.txt +0 -6
- potato_util-0.0.2/src/potato_util/__version__.py +0 -1
- potato_util-0.0.2/src/potato_util/_base.py +0 -117
- {potato_util-0.0.2 → potato_util-0.0.4}/.python-version +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/LICENSE.txt +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.async.txt +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.build.txt +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.docs.txt +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.fastapi.txt +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/requirements.txt +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/setup.cfg +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/setup.py +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/constants/__init__.py +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/constants/_base.py +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/constants/_enum.py +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/constants/_regex.py +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/__init__.py +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/_async.py +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/_sync.py +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/io/__init__.py +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util.egg-info/dependency_links.txt +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util.egg-info/requires.txt +0 -0
- {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util.egg-info/top_level.txt +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: potato_util
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
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,13 @@ 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
|
-
#
|
|
80
|
+
# Python Utils (potato-util)
|
|
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
|
+
[](https://pypi.org/project/potato-util)
|
|
86
|
+
[](https://docs.conda.io/en/latest/miniconda.html)
|
|
85
87
|
|
|
86
88
|
'potato_util' is collection of simple useful utils package for python.
|
|
87
89
|
|
|
@@ -89,11 +91,13 @@ Dynamic: license-file
|
|
|
89
91
|
|
|
90
92
|
- Python utilities
|
|
91
93
|
- Datetime utilities
|
|
92
|
-
-
|
|
93
|
-
- HTTP utilities
|
|
94
|
-
- Security utilities
|
|
94
|
+
- Generator utilities
|
|
95
95
|
- Sanitation utilities
|
|
96
|
+
- Security utilities
|
|
96
97
|
- Validation utilities
|
|
98
|
+
- HTTP utilities
|
|
99
|
+
- File I/O utilities
|
|
100
|
+
- And more...
|
|
97
101
|
|
|
98
102
|
---
|
|
99
103
|
|
|
@@ -109,7 +113,7 @@ Dynamic: license-file
|
|
|
109
113
|
[OPTIONAL] For **DEVELOPMENT** environment:
|
|
110
114
|
|
|
111
115
|
- Install [**git**](https://git-scm.com/downloads)
|
|
112
|
-
- Setup an [**SSH key**](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh)
|
|
116
|
+
- Setup an [**SSH key**](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh)
|
|
113
117
|
|
|
114
118
|
### 2. 📥 Download or clone the repository
|
|
115
119
|
|
|
@@ -130,20 +134,20 @@ cd ~/workspaces/projects
|
|
|
130
134
|
**OPTION A.** Clone the repository:
|
|
131
135
|
|
|
132
136
|
```sh
|
|
133
|
-
git clone https://github.com/bybatkhuu/module
|
|
134
|
-
cd module
|
|
137
|
+
git clone https://github.com/bybatkhuu/module-python-utils.git && \
|
|
138
|
+
cd module-python-utils
|
|
135
139
|
```
|
|
136
140
|
|
|
137
141
|
**OPTION B.** Clone the repository (for **DEVELOPMENT**: git + ssh key):
|
|
138
142
|
|
|
139
143
|
```sh
|
|
140
|
-
git clone git@github.com:bybatkhuu/module
|
|
141
|
-
cd module
|
|
144
|
+
git clone git@github.com:bybatkhuu/module-python-utils.git && \
|
|
145
|
+
cd module-python-utils
|
|
142
146
|
```
|
|
143
147
|
|
|
144
148
|
**OPTION C.** Download source code:
|
|
145
149
|
|
|
146
|
-
1. Download archived **zip** file from [**releases**](https://github.com/bybatkhuu/module
|
|
150
|
+
1. Download archived **zip** file from [**releases**](https://github.com/bybatkhuu/module-python-utils/releases).
|
|
147
151
|
2. Extract it into the projects directory.
|
|
148
152
|
|
|
149
153
|
### 3. 📦 Install the package
|
|
@@ -159,7 +163,7 @@ pip install -U potato-util
|
|
|
159
163
|
**OPTION B.** Install latest version directly from **GitHub** repository:
|
|
160
164
|
|
|
161
165
|
```sh
|
|
162
|
-
pip install git+https://github.com/bybatkhuu/module
|
|
166
|
+
pip install git+https://github.com/bybatkhuu/module-python-utils.git
|
|
163
167
|
```
|
|
164
168
|
|
|
165
169
|
**OPTION C.** Install from the downloaded **source code**:
|
|
@@ -176,11 +180,14 @@ pip install -e .
|
|
|
176
180
|
|
|
177
181
|
```sh
|
|
178
182
|
pip install -e .[dev]
|
|
183
|
+
|
|
184
|
+
# Install pre-commit hooks:
|
|
185
|
+
pre-commit install
|
|
179
186
|
```
|
|
180
187
|
|
|
181
188
|
**OPTION E.** Install from **pre-built release** files:
|
|
182
189
|
|
|
183
|
-
1. Download **`.whl`** or **`.tar.gz`** file from [**releases**](https://github.com/bybatkhuu/module
|
|
190
|
+
1. Download **`.whl`** or **`.tar.gz`** file from [**releases**](https://github.com/bybatkhuu/module-python-utils/releases)
|
|
184
191
|
2. Install with pip:
|
|
185
192
|
|
|
186
193
|
```sh
|
|
@@ -210,6 +217,177 @@ cp -r ./src/potato_util /some/path/project/
|
|
|
210
217
|
[**`examples/simple/main.py`**](./examples/simple/main.py):
|
|
211
218
|
|
|
212
219
|
```python
|
|
220
|
+
#!/usr/bin/env python
|
|
221
|
+
|
|
222
|
+
# Standard libraries
|
|
223
|
+
import os
|
|
224
|
+
import sys
|
|
225
|
+
import logging
|
|
226
|
+
|
|
227
|
+
# Third-party libraries
|
|
228
|
+
from pydantic import AnyHttpUrl
|
|
229
|
+
|
|
230
|
+
# Internal modules
|
|
231
|
+
import potato_util
|
|
232
|
+
import potato_util.dt as dt_utils
|
|
233
|
+
import potato_util.generator as gen_utils
|
|
234
|
+
import potato_util.sanitizer as sanitizer_utils
|
|
235
|
+
import potato_util.secure as secure_utils
|
|
236
|
+
import potato_util.validator as validator_utils
|
|
237
|
+
import potato_util.http as http_utils
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
logger = logging.getLogger(__name__)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def main() -> None:
|
|
244
|
+
_log_level = logging.INFO
|
|
245
|
+
if str(os.getenv("DEBUG", "0")).lower() in ("1", "true", "t", "yes", "y"):
|
|
246
|
+
_log_level = logging.DEBUG
|
|
247
|
+
|
|
248
|
+
logging.basicConfig(
|
|
249
|
+
stream=sys.stdout,
|
|
250
|
+
level=_log_level,
|
|
251
|
+
datefmt="%Y-%m-%d %H:%M:%S %z",
|
|
252
|
+
format="[%(asctime)s | %(levelname)s | %(filename)s:%(lineno)d]: %(message)s",
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Base utils:
|
|
256
|
+
logger.info("[BASE UTILITIES]")
|
|
257
|
+
_dict1 = {"a": 1, "b": {"c": 2, "d": 3}, "g": [2, 3, 4]}
|
|
258
|
+
_dict2 = {"b": {"c": 20, "e": 30}, "f": 40, "g": [5, 6, 7]}
|
|
259
|
+
_merged_dict = potato_util.deep_merge(_dict1, _dict2)
|
|
260
|
+
logger.info(f"Merged dict: {_merged_dict}")
|
|
261
|
+
|
|
262
|
+
_camel_str = "CamelCaseString"
|
|
263
|
+
_snake_str = potato_util.camel_to_snake(_camel_str)
|
|
264
|
+
logger.info(f"Converted '{_camel_str}' to '{_snake_str}'")
|
|
265
|
+
logger.info("-" * 80)
|
|
266
|
+
|
|
267
|
+
# Datetime utils:
|
|
268
|
+
logger.info("[DATETIME UTILITIES]")
|
|
269
|
+
_now_local_dt = dt_utils.now_local_dt()
|
|
270
|
+
logger.info(f"Current local datetime: {_now_local_dt}")
|
|
271
|
+
|
|
272
|
+
_now_utc_dt = dt_utils.now_utc_dt()
|
|
273
|
+
logger.info(f"Current UTC datetime: {_now_utc_dt}")
|
|
274
|
+
|
|
275
|
+
_now_ny_dt = dt_utils.now_dt(tz="America/New_York")
|
|
276
|
+
logger.info(f"Current New York datetime: {_now_ny_dt}")
|
|
277
|
+
|
|
278
|
+
_now_ts = dt_utils.now_ts()
|
|
279
|
+
logger.info(f"Current UTC timestamp (seconds): {_now_ts}")
|
|
280
|
+
|
|
281
|
+
_now_ts_ms = dt_utils.now_ts(unit="MILLISECONDS")
|
|
282
|
+
logger.info(f"Current UTC timestamp (ms): {_now_ts_ms}")
|
|
283
|
+
|
|
284
|
+
_now_ts_micro = dt_utils.now_ts(unit="MICROSECONDS")
|
|
285
|
+
logger.info(f"Current UTC timestamp (microseconds): {_now_ts_micro}")
|
|
286
|
+
|
|
287
|
+
_now_ts_ns = dt_utils.now_ts(unit="NANOSECONDS")
|
|
288
|
+
logger.info(f"Current UTC timestamp (nanoseconds): {_now_ts_ns}")
|
|
289
|
+
|
|
290
|
+
_dt_ts = dt_utils.dt_to_ts(dt=_now_local_dt)
|
|
291
|
+
logger.info(f"Converted local datetime to UTC timestamp (seconds): {_dt_ts}")
|
|
292
|
+
|
|
293
|
+
_replaced_tz_dt = dt_utils.replace_tz(dt=_now_local_dt, tz="Asia/Ulaanbaatar")
|
|
294
|
+
logger.info(f"Add or replace timezone with Asia/Ulaanbaatar: {_replaced_tz_dt}")
|
|
295
|
+
|
|
296
|
+
_converted_dt = dt_utils.convert_tz(dt=_now_ny_dt, tz="Asia/Seoul")
|
|
297
|
+
logger.info(
|
|
298
|
+
f"Calculated and converted timezone from New York to Seoul: {_converted_dt}"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
_dt_iso = dt_utils.dt_to_iso(dt=_now_local_dt)
|
|
302
|
+
logger.info(f"Parsing datetime to ISO 8601 format string: {_dt_iso}")
|
|
303
|
+
|
|
304
|
+
_future_dt = dt_utils.calc_future_dt(delta=3600, dt=_now_local_dt, tz="Asia/Tokyo")
|
|
305
|
+
logger.info(f"Calculated future datetime after 3600 seconds in Tokyo: {_future_dt}")
|
|
306
|
+
logger.info("-" * 80)
|
|
307
|
+
|
|
308
|
+
# Generator utils:
|
|
309
|
+
logger.info("[GENERATOR UTILITIES]")
|
|
310
|
+
_unique_id = gen_utils.gen_unique_id(prefix="item_")
|
|
311
|
+
logger.info(f"Generated unique ID based on datetime and UUIDv4: {_unique_id}")
|
|
312
|
+
|
|
313
|
+
_random_str = gen_utils.gen_random_string(length=32, is_alphanum=False)
|
|
314
|
+
logger.info(f"Generated secure random string: {_random_str}")
|
|
315
|
+
logger.info("-" * 80)
|
|
316
|
+
|
|
317
|
+
# Sanitizer utils:
|
|
318
|
+
logger.info("[SANITIZER UTILITIES]")
|
|
319
|
+
_raw_html = ' <script>alert("XSS Attack!");</script> '
|
|
320
|
+
_escaped_html = sanitizer_utils.escape_html(val=_raw_html)
|
|
321
|
+
logger.info(f"Escaped HTML: {_escaped_html}")
|
|
322
|
+
|
|
323
|
+
_raw_url = "https://www.example.com/search?q=potato util&body=<script>alert('Attack!')</script>&lang=한국어"
|
|
324
|
+
_escaped_url = sanitizer_utils.escape_url(val=_raw_url)
|
|
325
|
+
logger.info(f"Escaped URL: {_escaped_url}")
|
|
326
|
+
|
|
327
|
+
_raw_str = "Hello@World! This is a test_string with special#chars$%&*()[]{};:'\",.<>?/\\|`~"
|
|
328
|
+
_sanitized_str = sanitizer_utils.sanitize_special_chars(val=_raw_str, mode="STRICT")
|
|
329
|
+
logger.info(f"Sanitized string: {_sanitized_str}")
|
|
330
|
+
logger.info("-" * 80)
|
|
331
|
+
|
|
332
|
+
# Secure utils:
|
|
333
|
+
logger.info("[SECURE UTILITIES]")
|
|
334
|
+
_input_str = "SensitiveInformation123!"
|
|
335
|
+
_hashed_str_sha256 = secure_utils.hash_str(val=_input_str, algorithm="sha256")
|
|
336
|
+
logger.info(f"SHA-256 hashed string: {_hashed_str_sha256}")
|
|
337
|
+
logger.info("-" * 80)
|
|
338
|
+
|
|
339
|
+
# Validator utils:
|
|
340
|
+
logger.info("[VALIDATOR UTILITIES]")
|
|
341
|
+
_is_yes_truthy = validator_utils.is_truthy(val="Yes")
|
|
342
|
+
logger.info(f"Is 'Yes' truthy: {_is_yes_truthy}")
|
|
343
|
+
_is_off_truthy = validator_utils.is_truthy(val="OFF")
|
|
344
|
+
logger.info(f"Is 'OFF' truthy: {_is_off_truthy}")
|
|
345
|
+
|
|
346
|
+
_is_no_falsy = validator_utils.is_falsy(val="f")
|
|
347
|
+
logger.info(f"Is 'f' falsy: {_is_no_falsy}")
|
|
348
|
+
_is_1_falsy = validator_utils.is_falsy(val="1")
|
|
349
|
+
logger.info(f"Is '1' falsy: {_is_1_falsy}")
|
|
350
|
+
|
|
351
|
+
_request_id = "f058ebd6-02f7-4d3f-942e-904344e8cde5"
|
|
352
|
+
_is_valid_request_id = validator_utils.is_request_id(val=_request_id)
|
|
353
|
+
logger.info(f"Is '{_request_id}' a valid request ID: {_is_valid_request_id}")
|
|
354
|
+
|
|
355
|
+
_blacklist = ["hacker", "guest"]
|
|
356
|
+
_input_username = "hacker"
|
|
357
|
+
_is_blacklisted = validator_utils.is_blacklisted(
|
|
358
|
+
val=_input_username, blacklist=_blacklist
|
|
359
|
+
)
|
|
360
|
+
logger.info(f"Is '{_input_username}' blacklisted: {_is_blacklisted}")
|
|
361
|
+
|
|
362
|
+
_pattern = r"^[a-zA-Z0-9_]{3,16}$" # Alphanumeric and underscores, 3-16 chars
|
|
363
|
+
_test_username = "valid_user123"
|
|
364
|
+
_is_valid_username = validator_utils.is_valid(val=_test_username, pattern=_pattern)
|
|
365
|
+
logger.info(f"Is '{_test_username}' a valid username: {_is_valid_username}")
|
|
366
|
+
|
|
367
|
+
_string_with_special_chars = "Hello@World!"
|
|
368
|
+
_has_special_chars = validator_utils.has_special_chars(
|
|
369
|
+
val=_string_with_special_chars, mode="STRICT"
|
|
370
|
+
)
|
|
371
|
+
logger.info(
|
|
372
|
+
f"Does '{_string_with_special_chars}' have special chars: {_has_special_chars}"
|
|
373
|
+
)
|
|
374
|
+
logger.info("-" * 80)
|
|
375
|
+
|
|
376
|
+
# HTTP utils:
|
|
377
|
+
logger.info("[HTTP UTILITIES]")
|
|
378
|
+
_http_status_tuple = http_utils.get_http_status(status_code=403)
|
|
379
|
+
logger.info(f"HTTP status and known: {_http_status_tuple}")
|
|
380
|
+
|
|
381
|
+
_url = AnyHttpUrl("https://www.google.com")
|
|
382
|
+
_is_connectable = http_utils.is_connectable(url=_url, timeout=3, check_status=True)
|
|
383
|
+
logger.info(f"Is '{_url}' connectable: {_is_connectable}")
|
|
384
|
+
logger.info("-" * 80)
|
|
385
|
+
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
if __name__ == "__main__":
|
|
390
|
+
main()
|
|
213
391
|
```
|
|
214
392
|
|
|
215
393
|
👍
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# Python Utils (potato-util)
|
|
2
|
+
|
|
3
|
+
[](https://choosealicense.com/licenses/mit)
|
|
4
|
+
[](https://github.com/bybatkhuu/module-python-utils/actions/workflows/2.build-publish.yml)
|
|
5
|
+
[](https://github.com/bybatkhuu/module-python-utils/releases)
|
|
6
|
+
[](https://pypi.org/project/potato-util)
|
|
7
|
+
[](https://docs.conda.io/en/latest/miniconda.html)
|
|
8
|
+
|
|
9
|
+
'potato_util' is collection of simple useful utils package for python.
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- Python utilities
|
|
14
|
+
- Datetime utilities
|
|
15
|
+
- Generator utilities
|
|
16
|
+
- Sanitation utilities
|
|
17
|
+
- Security utilities
|
|
18
|
+
- Validation utilities
|
|
19
|
+
- HTTP utilities
|
|
20
|
+
- File I/O utilities
|
|
21
|
+
- And more...
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🛠 Installation
|
|
26
|
+
|
|
27
|
+
### 1. 🚧 Prerequisites
|
|
28
|
+
|
|
29
|
+
- Install **Python (>= v3.10)** and **pip (>= 23)**:
|
|
30
|
+
- **[RECOMMENDED] [Miniconda (v3)](https://www.anaconda.com/docs/getting-started/miniconda/install)**
|
|
31
|
+
- *[arm64/aarch64] [Miniforge (v3)](https://github.com/conda-forge/miniforge)*
|
|
32
|
+
- *[Python virutal environment] [venv](https://docs.python.org/3/library/venv.html)*
|
|
33
|
+
|
|
34
|
+
[OPTIONAL] For **DEVELOPMENT** environment:
|
|
35
|
+
|
|
36
|
+
- Install [**git**](https://git-scm.com/downloads)
|
|
37
|
+
- Setup an [**SSH key**](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh)
|
|
38
|
+
|
|
39
|
+
### 2. 📥 Download or clone the repository
|
|
40
|
+
|
|
41
|
+
[TIP] Skip this step, if you're going to install the package directly from **PyPi** or **GitHub** repository.
|
|
42
|
+
|
|
43
|
+
**2.1.** Prepare projects directory (if not exists):
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
# Create projects directory:
|
|
47
|
+
mkdir -pv ~/workspaces/projects
|
|
48
|
+
|
|
49
|
+
# Enter into projects directory:
|
|
50
|
+
cd ~/workspaces/projects
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**2.2.** Follow one of the below options **[A]**, **[B]** or **[C]**:
|
|
54
|
+
|
|
55
|
+
**OPTION A.** Clone the repository:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
git clone https://github.com/bybatkhuu/module-python-utils.git && \
|
|
59
|
+
cd module-python-utils
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**OPTION B.** Clone the repository (for **DEVELOPMENT**: git + ssh key):
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
git clone git@github.com:bybatkhuu/module-python-utils.git && \
|
|
66
|
+
cd module-python-utils
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**OPTION C.** Download source code:
|
|
70
|
+
|
|
71
|
+
1. Download archived **zip** file from [**releases**](https://github.com/bybatkhuu/module-python-utils/releases).
|
|
72
|
+
2. Extract it into the projects directory.
|
|
73
|
+
|
|
74
|
+
### 3. 📦 Install the package
|
|
75
|
+
|
|
76
|
+
[NOTE] Choose one of the following methods to install the package **[A ~ F]**:
|
|
77
|
+
|
|
78
|
+
**OPTION A.** [**RECOMMENDED**] Install from **PyPi**:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
pip install -U potato-util
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**OPTION B.** Install latest version directly from **GitHub** repository:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
pip install git+https://github.com/bybatkhuu/module-python-utils.git
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**OPTION C.** Install from the downloaded **source code**:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
# Install directly from the source code:
|
|
94
|
+
pip install .
|
|
95
|
+
|
|
96
|
+
# Or install with editable mode:
|
|
97
|
+
pip install -e .
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**OPTION D.** Install for **DEVELOPMENT** environment:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
pip install -e .[dev]
|
|
104
|
+
|
|
105
|
+
# Install pre-commit hooks:
|
|
106
|
+
pre-commit install
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**OPTION E.** Install from **pre-built release** files:
|
|
110
|
+
|
|
111
|
+
1. Download **`.whl`** or **`.tar.gz`** file from [**releases**](https://github.com/bybatkhuu/module-python-utils/releases)
|
|
112
|
+
2. Install with pip:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
# Install from .whl file:
|
|
116
|
+
pip install ./potato_util-[VERSION]-py3-none-any.whl
|
|
117
|
+
|
|
118
|
+
# Or install from .tar.gz file:
|
|
119
|
+
pip install ./potato_util-[VERSION].tar.gz
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**OPTION F.** Copy the **module** into the project directory (for **testing**):
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
# Install python dependencies:
|
|
126
|
+
pip install -r ./requirements.txt
|
|
127
|
+
|
|
128
|
+
# Copy the module source code into the project:
|
|
129
|
+
cp -r ./src/potato_util [PROJECT_DIR]
|
|
130
|
+
# For example:
|
|
131
|
+
cp -r ./src/potato_util /some/path/project/
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 🚸 Usage/Examples
|
|
135
|
+
|
|
136
|
+
### Simple
|
|
137
|
+
|
|
138
|
+
[**`examples/simple/main.py`**](./examples/simple/main.py):
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
#!/usr/bin/env python
|
|
142
|
+
|
|
143
|
+
# Standard libraries
|
|
144
|
+
import os
|
|
145
|
+
import sys
|
|
146
|
+
import logging
|
|
147
|
+
|
|
148
|
+
# Third-party libraries
|
|
149
|
+
from pydantic import AnyHttpUrl
|
|
150
|
+
|
|
151
|
+
# Internal modules
|
|
152
|
+
import potato_util
|
|
153
|
+
import potato_util.dt as dt_utils
|
|
154
|
+
import potato_util.generator as gen_utils
|
|
155
|
+
import potato_util.sanitizer as sanitizer_utils
|
|
156
|
+
import potato_util.secure as secure_utils
|
|
157
|
+
import potato_util.validator as validator_utils
|
|
158
|
+
import potato_util.http as http_utils
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
logger = logging.getLogger(__name__)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main() -> None:
|
|
165
|
+
_log_level = logging.INFO
|
|
166
|
+
if str(os.getenv("DEBUG", "0")).lower() in ("1", "true", "t", "yes", "y"):
|
|
167
|
+
_log_level = logging.DEBUG
|
|
168
|
+
|
|
169
|
+
logging.basicConfig(
|
|
170
|
+
stream=sys.stdout,
|
|
171
|
+
level=_log_level,
|
|
172
|
+
datefmt="%Y-%m-%d %H:%M:%S %z",
|
|
173
|
+
format="[%(asctime)s | %(levelname)s | %(filename)s:%(lineno)d]: %(message)s",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Base utils:
|
|
177
|
+
logger.info("[BASE UTILITIES]")
|
|
178
|
+
_dict1 = {"a": 1, "b": {"c": 2, "d": 3}, "g": [2, 3, 4]}
|
|
179
|
+
_dict2 = {"b": {"c": 20, "e": 30}, "f": 40, "g": [5, 6, 7]}
|
|
180
|
+
_merged_dict = potato_util.deep_merge(_dict1, _dict2)
|
|
181
|
+
logger.info(f"Merged dict: {_merged_dict}")
|
|
182
|
+
|
|
183
|
+
_camel_str = "CamelCaseString"
|
|
184
|
+
_snake_str = potato_util.camel_to_snake(_camel_str)
|
|
185
|
+
logger.info(f"Converted '{_camel_str}' to '{_snake_str}'")
|
|
186
|
+
logger.info("-" * 80)
|
|
187
|
+
|
|
188
|
+
# Datetime utils:
|
|
189
|
+
logger.info("[DATETIME UTILITIES]")
|
|
190
|
+
_now_local_dt = dt_utils.now_local_dt()
|
|
191
|
+
logger.info(f"Current local datetime: {_now_local_dt}")
|
|
192
|
+
|
|
193
|
+
_now_utc_dt = dt_utils.now_utc_dt()
|
|
194
|
+
logger.info(f"Current UTC datetime: {_now_utc_dt}")
|
|
195
|
+
|
|
196
|
+
_now_ny_dt = dt_utils.now_dt(tz="America/New_York")
|
|
197
|
+
logger.info(f"Current New York datetime: {_now_ny_dt}")
|
|
198
|
+
|
|
199
|
+
_now_ts = dt_utils.now_ts()
|
|
200
|
+
logger.info(f"Current UTC timestamp (seconds): {_now_ts}")
|
|
201
|
+
|
|
202
|
+
_now_ts_ms = dt_utils.now_ts(unit="MILLISECONDS")
|
|
203
|
+
logger.info(f"Current UTC timestamp (ms): {_now_ts_ms}")
|
|
204
|
+
|
|
205
|
+
_now_ts_micro = dt_utils.now_ts(unit="MICROSECONDS")
|
|
206
|
+
logger.info(f"Current UTC timestamp (microseconds): {_now_ts_micro}")
|
|
207
|
+
|
|
208
|
+
_now_ts_ns = dt_utils.now_ts(unit="NANOSECONDS")
|
|
209
|
+
logger.info(f"Current UTC timestamp (nanoseconds): {_now_ts_ns}")
|
|
210
|
+
|
|
211
|
+
_dt_ts = dt_utils.dt_to_ts(dt=_now_local_dt)
|
|
212
|
+
logger.info(f"Converted local datetime to UTC timestamp (seconds): {_dt_ts}")
|
|
213
|
+
|
|
214
|
+
_replaced_tz_dt = dt_utils.replace_tz(dt=_now_local_dt, tz="Asia/Ulaanbaatar")
|
|
215
|
+
logger.info(f"Add or replace timezone with Asia/Ulaanbaatar: {_replaced_tz_dt}")
|
|
216
|
+
|
|
217
|
+
_converted_dt = dt_utils.convert_tz(dt=_now_ny_dt, tz="Asia/Seoul")
|
|
218
|
+
logger.info(
|
|
219
|
+
f"Calculated and converted timezone from New York to Seoul: {_converted_dt}"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
_dt_iso = dt_utils.dt_to_iso(dt=_now_local_dt)
|
|
223
|
+
logger.info(f"Parsing datetime to ISO 8601 format string: {_dt_iso}")
|
|
224
|
+
|
|
225
|
+
_future_dt = dt_utils.calc_future_dt(delta=3600, dt=_now_local_dt, tz="Asia/Tokyo")
|
|
226
|
+
logger.info(f"Calculated future datetime after 3600 seconds in Tokyo: {_future_dt}")
|
|
227
|
+
logger.info("-" * 80)
|
|
228
|
+
|
|
229
|
+
# Generator utils:
|
|
230
|
+
logger.info("[GENERATOR UTILITIES]")
|
|
231
|
+
_unique_id = gen_utils.gen_unique_id(prefix="item_")
|
|
232
|
+
logger.info(f"Generated unique ID based on datetime and UUIDv4: {_unique_id}")
|
|
233
|
+
|
|
234
|
+
_random_str = gen_utils.gen_random_string(length=32, is_alphanum=False)
|
|
235
|
+
logger.info(f"Generated secure random string: {_random_str}")
|
|
236
|
+
logger.info("-" * 80)
|
|
237
|
+
|
|
238
|
+
# Sanitizer utils:
|
|
239
|
+
logger.info("[SANITIZER UTILITIES]")
|
|
240
|
+
_raw_html = ' <script>alert("XSS Attack!");</script> '
|
|
241
|
+
_escaped_html = sanitizer_utils.escape_html(val=_raw_html)
|
|
242
|
+
logger.info(f"Escaped HTML: {_escaped_html}")
|
|
243
|
+
|
|
244
|
+
_raw_url = "https://www.example.com/search?q=potato util&body=<script>alert('Attack!')</script>&lang=한국어"
|
|
245
|
+
_escaped_url = sanitizer_utils.escape_url(val=_raw_url)
|
|
246
|
+
logger.info(f"Escaped URL: {_escaped_url}")
|
|
247
|
+
|
|
248
|
+
_raw_str = "Hello@World! This is a test_string with special#chars$%&*()[]{};:'\",.<>?/\\|`~"
|
|
249
|
+
_sanitized_str = sanitizer_utils.sanitize_special_chars(val=_raw_str, mode="STRICT")
|
|
250
|
+
logger.info(f"Sanitized string: {_sanitized_str}")
|
|
251
|
+
logger.info("-" * 80)
|
|
252
|
+
|
|
253
|
+
# Secure utils:
|
|
254
|
+
logger.info("[SECURE UTILITIES]")
|
|
255
|
+
_input_str = "SensitiveInformation123!"
|
|
256
|
+
_hashed_str_sha256 = secure_utils.hash_str(val=_input_str, algorithm="sha256")
|
|
257
|
+
logger.info(f"SHA-256 hashed string: {_hashed_str_sha256}")
|
|
258
|
+
logger.info("-" * 80)
|
|
259
|
+
|
|
260
|
+
# Validator utils:
|
|
261
|
+
logger.info("[VALIDATOR UTILITIES]")
|
|
262
|
+
_is_yes_truthy = validator_utils.is_truthy(val="Yes")
|
|
263
|
+
logger.info(f"Is 'Yes' truthy: {_is_yes_truthy}")
|
|
264
|
+
_is_off_truthy = validator_utils.is_truthy(val="OFF")
|
|
265
|
+
logger.info(f"Is 'OFF' truthy: {_is_off_truthy}")
|
|
266
|
+
|
|
267
|
+
_is_no_falsy = validator_utils.is_falsy(val="f")
|
|
268
|
+
logger.info(f"Is 'f' falsy: {_is_no_falsy}")
|
|
269
|
+
_is_1_falsy = validator_utils.is_falsy(val="1")
|
|
270
|
+
logger.info(f"Is '1' falsy: {_is_1_falsy}")
|
|
271
|
+
|
|
272
|
+
_request_id = "f058ebd6-02f7-4d3f-942e-904344e8cde5"
|
|
273
|
+
_is_valid_request_id = validator_utils.is_request_id(val=_request_id)
|
|
274
|
+
logger.info(f"Is '{_request_id}' a valid request ID: {_is_valid_request_id}")
|
|
275
|
+
|
|
276
|
+
_blacklist = ["hacker", "guest"]
|
|
277
|
+
_input_username = "hacker"
|
|
278
|
+
_is_blacklisted = validator_utils.is_blacklisted(
|
|
279
|
+
val=_input_username, blacklist=_blacklist
|
|
280
|
+
)
|
|
281
|
+
logger.info(f"Is '{_input_username}' blacklisted: {_is_blacklisted}")
|
|
282
|
+
|
|
283
|
+
_pattern = r"^[a-zA-Z0-9_]{3,16}$" # Alphanumeric and underscores, 3-16 chars
|
|
284
|
+
_test_username = "valid_user123"
|
|
285
|
+
_is_valid_username = validator_utils.is_valid(val=_test_username, pattern=_pattern)
|
|
286
|
+
logger.info(f"Is '{_test_username}' a valid username: {_is_valid_username}")
|
|
287
|
+
|
|
288
|
+
_string_with_special_chars = "Hello@World!"
|
|
289
|
+
_has_special_chars = validator_utils.has_special_chars(
|
|
290
|
+
val=_string_with_special_chars, mode="STRICT"
|
|
291
|
+
)
|
|
292
|
+
logger.info(
|
|
293
|
+
f"Does '{_string_with_special_chars}' have special chars: {_has_special_chars}"
|
|
294
|
+
)
|
|
295
|
+
logger.info("-" * 80)
|
|
296
|
+
|
|
297
|
+
# HTTP utils:
|
|
298
|
+
logger.info("[HTTP UTILITIES]")
|
|
299
|
+
_http_status_tuple = http_utils.get_http_status(status_code=403)
|
|
300
|
+
logger.info(f"HTTP status and known: {_http_status_tuple}")
|
|
301
|
+
|
|
302
|
+
_url = AnyHttpUrl("https://www.google.com")
|
|
303
|
+
_is_connectable = http_utils.is_connectable(url=_url, timeout=3, check_status=True)
|
|
304
|
+
logger.info(f"Is '{_url}' connectable: {_is_connectable}")
|
|
305
|
+
logger.info("-" * 80)
|
|
306
|
+
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
if __name__ == "__main__":
|
|
311
|
+
main()
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
👍
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
### 🌎 Environment Variables
|
|
319
|
+
|
|
320
|
+
[**`.env.example`**](./.env.example):
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
# ENV=LOCAL
|
|
324
|
+
# DEBUG=false
|
|
325
|
+
# TZ=UTC
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 🧪 Running Tests
|
|
331
|
+
|
|
332
|
+
To run tests, run the following command:
|
|
333
|
+
|
|
334
|
+
```sh
|
|
335
|
+
# Install python test dependencies:
|
|
336
|
+
pip install .[test]
|
|
337
|
+
|
|
338
|
+
# Run tests:
|
|
339
|
+
python -m pytest -sv -o log_cli=true
|
|
340
|
+
# Or use the test script:
|
|
341
|
+
./scripts/test.sh -l -v -c
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## 🏗️ Build Package
|
|
345
|
+
|
|
346
|
+
To build the python package, run the following command:
|
|
347
|
+
|
|
348
|
+
```sh
|
|
349
|
+
# Install python build dependencies:
|
|
350
|
+
pip install -r ./requirements/requirements.build.txt
|
|
351
|
+
|
|
352
|
+
# Build python package:
|
|
353
|
+
python -m build
|
|
354
|
+
# Or use the build script:
|
|
355
|
+
./scripts/build.sh
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## 📝 Generate Docs
|
|
359
|
+
|
|
360
|
+
To build the documentation, run the following command:
|
|
361
|
+
|
|
362
|
+
```sh
|
|
363
|
+
# Install python documentation dependencies:
|
|
364
|
+
pip install -r ./requirements/requirements.docs.txt
|
|
365
|
+
|
|
366
|
+
# Serve documentation locally (for development):
|
|
367
|
+
mkdocs serve -a 0.0.0.0:8000
|
|
368
|
+
# Or use the docs script:
|
|
369
|
+
./scripts/docs.sh
|
|
370
|
+
|
|
371
|
+
# Or build documentation:
|
|
372
|
+
mkdocs build
|
|
373
|
+
# Or use the docs script:
|
|
374
|
+
./scripts/docs.sh -b
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## 📚 Documentation
|
|
378
|
+
|
|
379
|
+
- [Docs](./docs)
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 📑 References
|
|
384
|
+
|
|
385
|
+
- <https://packaging.python.org/en/latest/tutorials/packaging-projects>
|
|
386
|
+
- <https://python-packaging.readthedocs.io/en/latest>
|