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