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.
Files changed (43) hide show
  1. {potato_util-0.0.2/src/potato_util.egg-info → potato_util-0.0.4}/PKG-INFO +197 -19
  2. potato_util-0.0.4/README.md +386 -0
  3. {potato_util-0.0.2 → potato_util-0.0.4}/pyproject.toml +15 -17
  4. potato_util-0.0.4/requirements/requirements.dev.txt +2 -0
  5. {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.test.txt +0 -1
  6. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/__init__.py +3 -5
  7. potato_util-0.0.4/src/potato_util/__version__.py +1 -0
  8. potato_util-0.0.4/src/potato_util/_base.py +57 -0
  9. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/dt.py +128 -104
  10. potato_util-0.0.2/src/potato_util/secure.py → potato_util-0.0.4/src/potato_util/generator.py +3 -28
  11. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/_base.py +3 -1
  12. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/fastapi.py +1 -1
  13. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/io/_async.py +36 -24
  14. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/io/_sync.py +40 -24
  15. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/sanitizer.py +12 -3
  16. potato_util-0.0.4/src/potato_util/secure.py +37 -0
  17. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/validator.py +12 -6
  18. {potato_util-0.0.2 → potato_util-0.0.4/src/potato_util.egg-info}/PKG-INFO +197 -19
  19. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util.egg-info/SOURCES.txt +1 -0
  20. potato_util-0.0.2/README.md +0 -208
  21. potato_util-0.0.2/requirements/requirements.dev.txt +0 -6
  22. potato_util-0.0.2/src/potato_util/__version__.py +0 -1
  23. potato_util-0.0.2/src/potato_util/_base.py +0 -117
  24. {potato_util-0.0.2 → potato_util-0.0.4}/.python-version +0 -0
  25. {potato_util-0.0.2 → potato_util-0.0.4}/LICENSE.txt +0 -0
  26. {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.async.txt +0 -0
  27. {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.build.txt +0 -0
  28. {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.docs.txt +0 -0
  29. {potato_util-0.0.2 → potato_util-0.0.4}/requirements/requirements.fastapi.txt +0 -0
  30. {potato_util-0.0.2 → potato_util-0.0.4}/requirements.txt +0 -0
  31. {potato_util-0.0.2 → potato_util-0.0.4}/setup.cfg +0 -0
  32. {potato_util-0.0.2 → potato_util-0.0.4}/setup.py +0 -0
  33. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/constants/__init__.py +0 -0
  34. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/constants/_base.py +0 -0
  35. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/constants/_enum.py +0 -0
  36. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/constants/_regex.py +0 -0
  37. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/__init__.py +0 -0
  38. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/_async.py +0 -0
  39. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/http/_sync.py +0 -0
  40. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util/io/__init__.py +0 -0
  41. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util.egg-info/dependency_links.txt +0 -0
  42. {potato_util-0.0.2 → potato_util-0.0.4}/src/potato_util.egg-info/requires.txt +0 -0
  43. {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.2
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.python-utils
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.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
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
- # Potato Utils (Python Utils)
80
+ # Python Utils (potato-util)
81
81
 
82
82
  [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit)
83
- [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bybatkhuu/module.python-utils/2.build-publish.yml?logo=GitHub)](https://github.com/bybatkhuu/module.python-utils/actions/workflows/2.build-publish.yml)
84
- [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/bybatkhuu/module.python-utils?logo=GitHub&color=blue)](https://github.com/bybatkhuu/module.python-utils/releases)
83
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bybatkhuu/module-python-utils/2.build-publish.yml?logo=GitHub)](https://github.com/bybatkhuu/module-python-utils/actions/workflows/2.build-publish.yml)
84
+ [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/bybatkhuu/module-python-utils?logo=GitHub&color=blue)](https://github.com/bybatkhuu/module-python-utils/releases)
85
+ [![PyPI](https://img.shields.io/pypi/v/potato-util?logo=PyPi)](https://pypi.org/project/potato-util)
86
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/potato-util?logo=Python)](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
- - File I/O utilities
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) ([video tutorial](https://www.youtube.com/watch?v=snCP3c7wXw0))
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.python-utils.git && \
134
- cd module.python-utils
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.python-utils.git && \
141
- cd module.python-utils
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.python-utils/releases).
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.python-utils.git
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.python-utils/releases)
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
+ [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit)
4
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bybatkhuu/module-python-utils/2.build-publish.yml?logo=GitHub)](https://github.com/bybatkhuu/module-python-utils/actions/workflows/2.build-publish.yml)
5
+ [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/bybatkhuu/module-python-utils?logo=GitHub&color=blue)](https://github.com/bybatkhuu/module-python-utils/releases)
6
+ [![PyPI](https://img.shields.io/pypi/v/potato-util?logo=PyPi)](https://pypi.org/project/potato-util)
7
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/potato-util?logo=Python)](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>