utkit 0.4.0__tar.gz → 0.5.0__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.
- utkit-0.5.0/PKG-INFO +106 -0
- utkit-0.5.0/README.md +77 -0
- utkit-0.5.0/pyproject.toml +44 -0
- utkit-0.5.0/src/utkit/__init__.py +6 -0
- utkit-0.5.0/src/utkit/api/rate_limit/__init__.py +9 -0
- utkit-0.5.0/src/utkit/api/rate_limit/error.py +4 -0
- utkit-0.5.0/src/utkit/api/rate_limit/middleware.py +4 -0
- utkit-0.5.0/src/utkit/api/schema/query.py +7 -0
- utkit-0.5.0/src/utkit/auth/__init__.py +16 -0
- utkit-0.5.0/src/utkit/core/logging.py +88 -0
- utkit-0.5.0/src/utkit/documentation/.cache/11038628458439117958 +4 -0
- utkit-0.5.0/src/utkit/documentation/.cache/11306067049371264785 +4 -0
- utkit-0.5.0/src/utkit/documentation/.cache/13386297405578974681 +4 -0
- utkit-0.5.0/src/utkit/documentation/.cache/15312684519617069940 +128 -0
- utkit-0.5.0/src/utkit/documentation/.cache/17695336329032716848 +4 -0
- utkit-0.5.0/src/utkit/documentation/.cache/17985671397626253671 +192 -0
- utkit-0.5.0/src/utkit/documentation/.cache/2940665334038842907 +307 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/.cache/326316130724058699 +1 -1
- utkit-0.5.0/src/utkit/documentation/.cache/3476900567878811119 +4 -0
- utkit-0.5.0/src/utkit/documentation/.cache/3541202890535126406 +259 -0
- utkit-0.5.0/src/utkit/documentation/.cache/360188204203718330 +4 -0
- utkit-0.5.0/src/utkit/documentation/.cache/403326901327389294 +143 -0
- utkit-0.5.0/src/utkit/documentation/.cache/5530722400779903482 +4 -0
- utkit-0.5.0/src/utkit/documentation/.cache/5696925338617156769 +111 -0
- utkit-0.5.0/src/utkit/documentation/.cache/5974781288462474643 +4 -0
- utkit-0.5.0/src/utkit/documentation/.cache/5976522500417319979 +111 -0
- utkit-0.5.0/src/utkit/documentation/.cache/6210600303880993421 +96 -0
- utkit-0.5.0/src/utkit/documentation/.cache/6916343365748484443 +111 -0
- utkit-0.5.0/src/utkit/documentation/.cache/9033698058957892258 +4 -0
- utkit-0.5.0/src/utkit/documentation/.cache/9811225078116513287 +4 -0
- utkit-0.5.0/src/utkit/documentation/docs/api/rate-limit.md +120 -0
- utkit-0.5.0/src/utkit/documentation/docs/api/schema.md +73 -0
- utkit-0.5.0/src/utkit/documentation/docs/auth.md +98 -0
- utkit-0.5.0/src/utkit/documentation/docs/images/logo-old.png +0 -0
- utkit-0.5.0/src/utkit/documentation/docs/images/logo.png +0 -0
- utkit-0.5.0/src/utkit/documentation/docs/index.md +207 -0
- utkit-0.5.0/src/utkit/documentation/docs/privacy/mask.md +128 -0
- utkit-0.5.0/src/utkit/documentation/docs/privacy/security.md +299 -0
- utkit-0.5.0/src/utkit/documentation/docs/store/redis.md +187 -0
- utkit-0.5.0/src/utkit/documentation/docs/stylesheets/extra.css +15 -0
- utkit-0.5.0/src/utkit/documentation/docs/template/render.md +138 -0
- utkit-0.5.0/src/utkit/documentation/docs/utils/performance.md +117 -0
- utkit-0.5.0/src/utkit/documentation/site/404.html +1082 -0
- utkit-0.5.0/src/utkit/documentation/site/api/rate-limit/index.html +1512 -0
- utkit-0.5.0/src/utkit/documentation/site/api/schema/index.html +1420 -0
- utkit-0.5.0/src/utkit/documentation/site/auth/index.html +1410 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/communication/mail/index.html +839 -172
- utkit-0.5.0/src/utkit/documentation/site/images/logo-old.png +0 -0
- utkit-0.5.0/src/utkit/documentation/site/images/logo.png +0 -0
- utkit-0.5.0/src/utkit/documentation/site/index.html +1846 -0
- utkit-0.5.0/src/utkit/documentation/site/privacy/mask/index.html +1577 -0
- utkit-0.5.0/src/utkit/documentation/site/privacy/security/index.html +2019 -0
- utkit-0.5.0/src/utkit/documentation/site/search.json +1 -0
- utkit-0.5.0/src/utkit/documentation/site/sitemap.xml +3 -0
- utkit-0.5.0/src/utkit/documentation/site/store/redis/index.html +1706 -0
- utkit-0.5.0/src/utkit/documentation/site/stylesheets/extra.css +15 -0
- utkit-0.5.0/src/utkit/documentation/site/template/render/index.html +1499 -0
- utkit-0.5.0/src/utkit/documentation/site/utils/performance/index.html +1468 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/zensical.toml +25 -4
- utkit-0.5.0/src/utkit/privacy/mask.py +57 -0
- utkit-0.5.0/src/utkit/privacy/security.py +167 -0
- utkit-0.5.0/src/utkit/store/redis/__init__.py +50 -0
- utkit-0.5.0/src/utkit/template/__init__.py +0 -0
- utkit-0.5.0/src/utkit/template/render.py +35 -0
- utkit-0.5.0/src/utkit/utils/performance.py +35 -0
- utkit-0.4.0/PKG-INFO +0 -11
- utkit-0.4.0/pyproject.toml +0 -20
- utkit-0.4.0/src/utkit/__init__.py +0 -2
- utkit-0.4.0/src/utkit/documentation/.cache/3476900567878811119 +0 -4
- utkit-0.4.0/src/utkit/documentation/.cache/3541202890535126406 +0 -95
- utkit-0.4.0/src/utkit/documentation/.cache/9033698058957892258 +0 -4
- utkit-0.4.0/src/utkit/documentation/docs/index.md +0 -68
- utkit-0.4.0/src/utkit/documentation/site/index.html +0 -697
- utkit-0.4.0/src/utkit/documentation/site/search.json +0 -1
- utkit-0.4.0/src/utkit/documentation/site/sitemap.xml +0 -13
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/cli/__init__.py +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/communication/__init__.py +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/communication/mail/__init__.py +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/communication/mail/smtp.py +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/.cache/.gitignore +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/.cache/15661711846603463071 +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/.cache/3226154870166803660 +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/.github/workflows/docs.yml +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/.gitignore +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/docs/communication/mail.md +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/assets/images/favicon.png +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/assets/javascripts/LICENSE +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/assets/javascripts/bundle.dbc0afdc.min.js +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/assets/javascripts/workers/search.e2d2d235.min.js +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/assets/stylesheets/classic/main.a2001754.min.css +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/assets/stylesheets/classic/palette.7dc9a0ad.min.css +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/assets/stylesheets/modern/main.1e981d71.min.css +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/assets/stylesheets/modern/palette.dfe2e883.min.css +0 -0
- {utkit-0.4.0 → utkit-0.5.0}/src/utkit/documentation/site/objects.inv +0 -0
- /utkit-0.4.0/README.md → /utkit-0.5.0/src/utkit/privacy/__init__.py +0 -0
utkit-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: utkit
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: core libraries for development
|
|
5
|
+
Author: TINS PJ
|
|
6
|
+
Author-email: TINS PJ <tinspj1997@gmail.com>
|
|
7
|
+
Requires-Dist: cryptography>=47.0.0
|
|
8
|
+
Requires-Dist: loguru>=0.7.3
|
|
9
|
+
Requires-Dist: pwdlib[argon2]>=0.3.0
|
|
10
|
+
Requires-Dist: pyjwt>=2.12.1
|
|
11
|
+
Requires-Dist: typer>=0.20.0
|
|
12
|
+
Requires-Dist: zensical>=0.0.37
|
|
13
|
+
Requires-Dist: slowapi>=0.1.9 ; extra == 'all'
|
|
14
|
+
Requires-Dist: pydantic>=2.13.3 ; extra == 'all'
|
|
15
|
+
Requires-Dist: psutil>=7.2.2 ; extra == 'all'
|
|
16
|
+
Requires-Dist: jinja2>=3.1.6 ; extra == 'all'
|
|
17
|
+
Requires-Dist: redis>=7.4.0 ; extra == 'all'
|
|
18
|
+
Requires-Dist: slowapi>=0.1.9 ; extra == 'api'
|
|
19
|
+
Requires-Dist: pydantic>=2.13.3 ; extra == 'api'
|
|
20
|
+
Requires-Dist: psutil>=7.2.2 ; extra == 'standard'
|
|
21
|
+
Requires-Dist: jinja2>=3.1.6 ; extra == 'standard'
|
|
22
|
+
Requires-Dist: redis>=7.4.0 ; extra == 'store'
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Provides-Extra: api
|
|
26
|
+
Provides-Extra: standard
|
|
27
|
+
Provides-Extra: store
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# utkit
|
|
31
|
+
|
|
32
|
+
<div style="text-align: center; margin: 1.5rem 0;">
|
|
33
|
+
<img src="src/utkit/documentation/docs/images/logo.png" alt="utkit" style="height: 100px; width: auto;" />
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
**utkit** is a collection of core libraries for Python development, providing ready-to-use utilities for common tasks such as authentication, email, encryption, caching, rate limiting, and more.
|
|
37
|
+
|
|
38
|
+
- **Version:** `0.4.0`
|
|
39
|
+
- **Author:** TINS PJ
|
|
40
|
+
- **Requires:** Python `>=3.12`
|
|
41
|
+
- **License:** MIT
|
|
42
|
+
- **PyPI:** [pypi.org/project/utkit](https://pypi.org/project/utkit/)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install utkit
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv add utkit
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Optional extras
|
|
59
|
+
|
|
60
|
+
| Extra | Included dependencies | Use for |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `api` | `slowapi`, `pydantic` | Rate limiting and API schema for FastAPI |
|
|
63
|
+
| `standard` | `psutil`, `jinja2` | Performance monitoring and HTML template rendering |
|
|
64
|
+
| `store` | `redis` | Redis cache and key-value store |
|
|
65
|
+
| `all` | all of the above | Install every optional dependency at once |
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install "utkit[api]"
|
|
69
|
+
pip install "utkit[standard]"
|
|
70
|
+
pip install "utkit[store]"
|
|
71
|
+
|
|
72
|
+
# All optional dependencies at once
|
|
73
|
+
pip install "utkit[all]"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Modules
|
|
79
|
+
|
|
80
|
+
### Core
|
|
81
|
+
|
|
82
|
+
| Module | Description |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `auth` | Password hashing and verification |
|
|
85
|
+
| `communication.mail` | Send HTML emails via SMTP |
|
|
86
|
+
| `privacy.mask` | Mask sensitive data (email, phone, card, string) |
|
|
87
|
+
| `privacy.security` | Fernet & RSA encryption, secret key generation, JWT |
|
|
88
|
+
|
|
89
|
+
### Optional
|
|
90
|
+
|
|
91
|
+
| Module | Extra | Description |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `api.rate_limit` | `api` | Rate limiting for FastAPI via SlowAPI |
|
|
94
|
+
| `api.schema` | `api` | Reusable Pydantic query models (pagination) |
|
|
95
|
+
| `utils.performance` | `standard` | Execution time decorator and memory usage |
|
|
96
|
+
| `template.render` | `standard` | HTML rendering from Jinja2 files and strings |
|
|
97
|
+
| `store.redis` | `store` | Singleton Redis client with JSON serialisation |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## CLI
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
utkit --version
|
|
105
|
+
utkit docs
|
|
106
|
+
```
|
utkit-0.5.0/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# utkit
|
|
2
|
+
|
|
3
|
+
<div style="text-align: center; margin: 1.5rem 0;">
|
|
4
|
+
<img src="src/utkit/documentation/docs/images/logo.png" alt="utkit" style="height: 100px; width: auto;" />
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
**utkit** is a collection of core libraries for Python development, providing ready-to-use utilities for common tasks such as authentication, email, encryption, caching, rate limiting, and more.
|
|
8
|
+
|
|
9
|
+
- **Version:** `0.4.0`
|
|
10
|
+
- **Author:** TINS PJ
|
|
11
|
+
- **Requires:** Python `>=3.12`
|
|
12
|
+
- **License:** MIT
|
|
13
|
+
- **PyPI:** [pypi.org/project/utkit](https://pypi.org/project/utkit/)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install utkit
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add utkit
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Optional extras
|
|
30
|
+
|
|
31
|
+
| Extra | Included dependencies | Use for |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `api` | `slowapi`, `pydantic` | Rate limiting and API schema for FastAPI |
|
|
34
|
+
| `standard` | `psutil`, `jinja2` | Performance monitoring and HTML template rendering |
|
|
35
|
+
| `store` | `redis` | Redis cache and key-value store |
|
|
36
|
+
| `all` | all of the above | Install every optional dependency at once |
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install "utkit[api]"
|
|
40
|
+
pip install "utkit[standard]"
|
|
41
|
+
pip install "utkit[store]"
|
|
42
|
+
|
|
43
|
+
# All optional dependencies at once
|
|
44
|
+
pip install "utkit[all]"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Modules
|
|
50
|
+
|
|
51
|
+
### Core
|
|
52
|
+
|
|
53
|
+
| Module | Description |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `auth` | Password hashing and verification |
|
|
56
|
+
| `communication.mail` | Send HTML emails via SMTP |
|
|
57
|
+
| `privacy.mask` | Mask sensitive data (email, phone, card, string) |
|
|
58
|
+
| `privacy.security` | Fernet & RSA encryption, secret key generation, JWT |
|
|
59
|
+
|
|
60
|
+
### Optional
|
|
61
|
+
|
|
62
|
+
| Module | Extra | Description |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `api.rate_limit` | `api` | Rate limiting for FastAPI via SlowAPI |
|
|
65
|
+
| `api.schema` | `api` | Reusable Pydantic query models (pagination) |
|
|
66
|
+
| `utils.performance` | `standard` | Execution time decorator and memory usage |
|
|
67
|
+
| `template.render` | `standard` | HTML rendering from Jinja2 files and strings |
|
|
68
|
+
| `store.redis` | `store` | Singleton Redis client with JSON serialisation |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## CLI
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
utkit --version
|
|
76
|
+
utkit docs
|
|
77
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "utkit"
|
|
3
|
+
version = "0.5.0"
|
|
4
|
+
description = "core libraries for development"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "TINS PJ", email = "tinspj1997@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cryptography>=47.0.0",
|
|
12
|
+
"loguru>=0.7.3",
|
|
13
|
+
"pwdlib[argon2]>=0.3.0",
|
|
14
|
+
"pyjwt>=2.12.1",
|
|
15
|
+
"typer>=0.20.0",
|
|
16
|
+
"zensical>=0.0.37",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
api=[
|
|
21
|
+
"slowapi>=0.1.9",
|
|
22
|
+
"pydantic>=2.13.3",
|
|
23
|
+
]
|
|
24
|
+
standard=[
|
|
25
|
+
"psutil>=7.2.2",
|
|
26
|
+
"jinja2>=3.1.6",
|
|
27
|
+
]
|
|
28
|
+
store=[
|
|
29
|
+
"redis>=7.4.0",
|
|
30
|
+
]
|
|
31
|
+
all=[
|
|
32
|
+
"slowapi>=0.1.9",
|
|
33
|
+
"pydantic>=2.13.3",
|
|
34
|
+
"psutil>=7.2.2",
|
|
35
|
+
"jinja2>=3.1.6",
|
|
36
|
+
"redis>=7.4.0",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
utkit = "utkit.cli:main"
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["uv_build >= 0.11.8, <0.12.0"]
|
|
44
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from slowapi import Limiter, _rate_limit_exceeded_handler as rate_limit_exceeded_handler
|
|
3
|
+
from slowapi.util import get_remote_address
|
|
4
|
+
|
|
5
|
+
__all__ = ["initialize_rate_limiter", "rate_limit_exceeded_handler"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def initialize_rate_limiter(rate_limit_default: str = "100/minute") -> Limiter:
|
|
9
|
+
return Limiter(key_func=get_remote_address, default_limits=[rate_limit_default])
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pwdlib import PasswordHash
|
|
4
|
+
|
|
5
|
+
password_hash = PasswordHash.recommended()
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"verify_password","create_password_hash"
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
def verify_password(plain_password, hashed_password):
|
|
12
|
+
return password_hash.verify(plain_password, hashed_password)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_password_hash(password):
|
|
16
|
+
return password_hash.hash(password)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Callable, List
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _add_class_name(record):
|
|
9
|
+
"""Patch logger records to include the caller's class name."""
|
|
10
|
+
if record["extra"].get("classname") is not None:
|
|
11
|
+
return
|
|
12
|
+
frame = inspect.currentframe()
|
|
13
|
+
try:
|
|
14
|
+
while frame:
|
|
15
|
+
frame = frame.f_back
|
|
16
|
+
if frame and frame.f_globals.get("__name__") not in (
|
|
17
|
+
"loguru._logger",
|
|
18
|
+
__name__,
|
|
19
|
+
):
|
|
20
|
+
cls = frame.f_locals.get("self") or frame.f_locals.get("cls")
|
|
21
|
+
if cls is not None:
|
|
22
|
+
record["extra"]["classname"] = (
|
|
23
|
+
cls.__name__ if isinstance(cls, type) else type(cls).__name__
|
|
24
|
+
)
|
|
25
|
+
else:
|
|
26
|
+
record["extra"]["classname"] = "Global"
|
|
27
|
+
break
|
|
28
|
+
else:
|
|
29
|
+
record["extra"]["classname"] = "Global"
|
|
30
|
+
finally:
|
|
31
|
+
del frame
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def setup_logging(
|
|
35
|
+
log_base_path="logs",
|
|
36
|
+
patchers: List[Callable] = [],
|
|
37
|
+
rotation: str = "1 day",
|
|
38
|
+
compression: str = "zip",
|
|
39
|
+
level: str = "INFO",
|
|
40
|
+
app_retention: str = "7 days",
|
|
41
|
+
meter_retention: str = "30 days",
|
|
42
|
+
**kwargs,
|
|
43
|
+
) -> None:
|
|
44
|
+
log_path = Path(log_base_path)
|
|
45
|
+
log_path.mkdir(exist_ok=True)
|
|
46
|
+
|
|
47
|
+
def default_patcher(record):
|
|
48
|
+
for patcher in patchers or []:
|
|
49
|
+
patcher(record)
|
|
50
|
+
|
|
51
|
+
patchers.append(_add_class_name)
|
|
52
|
+
logger.remove()
|
|
53
|
+
logger.configure(patcher=default_patcher)
|
|
54
|
+
|
|
55
|
+
# Common format string (DRY principle)
|
|
56
|
+
log_format = (
|
|
57
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
|
|
58
|
+
"<level>{level: <8}</level> | "
|
|
59
|
+
"<cyan>{name}</cyan>:<cyan>{extra[classname]}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
|
|
60
|
+
"{message} | <blue>Context: {extra}</blue>"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
log_kwargs = {
|
|
64
|
+
"rotation": rotation,
|
|
65
|
+
"compression": compression,
|
|
66
|
+
"format": log_format,
|
|
67
|
+
"level": level,
|
|
68
|
+
**kwargs,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# 2. Console (Everything INFO+)
|
|
72
|
+
logger.add(sys.stderr, format=log_format, level="INFO", colorize=True)
|
|
73
|
+
|
|
74
|
+
# 3. Application Logs (Daily Rotation)
|
|
75
|
+
logger.add(
|
|
76
|
+
log_path / "application_{time:YYYY-MM-DD}.log",
|
|
77
|
+
filter=lambda r: r["extra"].get("type") == "app",
|
|
78
|
+
retention=app_retention,
|
|
79
|
+
**log_kwargs,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# 4. Metering Logs
|
|
83
|
+
logger.add(
|
|
84
|
+
log_path / "metering_{time:YYYY-MM-DD}.log",
|
|
85
|
+
filter=lambda r: r["extra"].get("type") == "meter",
|
|
86
|
+
retention=meter_retention,
|
|
87
|
+
**log_kwargs,
|
|
88
|
+
)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": "\n<!doctype html>\n<html lang=\"en\" class=\"no-js\">\n <head>\n \n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n \n <meta name=\"description\" content=\"Core libraries for development.\">\n \n \n <meta name=\"author\" content=\"TINS PJ\">\n \n \n \n <link rel=\"prev\" href=\"../../store/redis/\">\n \n \n <link rel=\"next\" href=\"../../utils/performance/\">\n \n \n \n \n \n <link rel=\"icon\" href=\"../../images/logo.png\">\n <meta name=\"generator\" content=\"zensical-0.0.37\">\n \n \n \n <title>Template Rendering - Documentation</title>\n \n \n \n \n \n \n <link rel=\"stylesheet\" href=\"../../assets/stylesheets/modern/main.1e981d71.min.css\">\n \n \n \n \n <link rel=\"stylesheet\" href=\"../../assets/stylesheets/modern/palette.dfe2e883.min.css\">\n \n \n\n\n \n \n \n \n \n \n \n \n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback\">\n <style>:root{--md-text-font:\"Inter\";--md-code-font:\"JetBrains Mono\"}</style>\n \n \n \n <link rel=\"stylesheet\" href=\"../../stylesheets/extra.css\">\n \n <script>__md_scope=new URL(\"../..\",location),__md_hash=e=>[...e].reduce(((e,t)=>(e<<5)-e+t.charCodeAt(0)),0),__md_get=(e,t=localStorage,a=__md_scope)=>JSON.parse(t.getItem(a.pathname+\".\"+e)),__md_set=(e,t,a=localStorage,_=__md_scope)=>{try{a.setItem(_.pathname+\".\"+e,JSON.stringify(t))}catch(e){}},document.documentElement.setAttribute(\"data-platform\",navigator.platform)</script>\n \n \n\n \n \n </head>\n \n \n \n \n \n \n \n \n \n <body dir=\"ltr\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\">\n \n \n <input class=\"md-toggle\" data-md-toggle=\"drawer\" type=\"checkbox\" id=\"__drawer\" autocomplete=\"off\">\n <input class=\"md-toggle\" data-md-toggle=\"search\" type=\"checkbox\" id=\"__search\" autocomplete=\"off\">\n <label class=\"md-overlay\" for=\"__drawer\" aria-label=\"Navigation\"></label>\n <div data-md-component=\"skip\">\n \n \n <a href=\"#template-rendering\" class=\"md-skip\">\n Skip to content\n </a>\n \n </div>\n <div data-md-component=\"announce\">\n \n </div>\n \n \n \n\n \n\n<header class=\"md-header md-header--shadow\" data-md-component=\"header\">\n <nav class=\"md-header__inner md-grid\" aria-label=\"Header\">\n <a href=\"../..\" title=\"Documentation\" class=\"md-header__button md-logo\" aria-label=\"Documentation\" data-md-component=\"logo\">\n \n <img src=\"../../images/logo.png\" alt=\"Documentation\">\n\n </a>\n <label class=\"md-header__button md-icon\" for=\"__drawer\" aria-label=\"Navigation\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-menu\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16M4 12h16M4 19h16\"/></svg>\n </label>\n <div class=\"md-header__title\" data-md-component=\"header-title\">\n <div class=\"md-header__ellipsis\">\n <div class=\"md-header__topic\">\n <span class=\"md-ellipsis\">\n Documentation\n </span>\n </div>\n <div class=\"md-header__topic\" data-md-component=\"header-topic\">\n <span class=\"md-ellipsis\">\n \n Template Rendering\n \n </span>\n </div>\n </div>\n </div>\n \n \n <form class=\"md-header__option\" data-md-component=\"palette\">\n \n \n \n \n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to dark mode\" type=\"radio\" name=\"__palette\" id=\"__palette_0\">\n \n <label class=\"md-header__button md-icon\" title=\"Switch to dark mode\" for=\"__palette_1\" hidden>\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-sun\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg>\n </label>\n \n \n \n \n \n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"slate\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to light mode\" type=\"radio\" name=\"__palette\" id=\"__palette_1\">\n \n <label class=\"md-header__button md-icon\" title=\"Switch to light mode\" for=\"__palette_0\" hidden>\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-moon\" viewBox=\"0 0 24 24\"><path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\"/></svg>\n </label>\n \n \n</form>\n \n \n \n <script>var palette=__md_get(\"__palette\");if(palette&&palette.color){if(\"(prefers-color-scheme)\"===palette.color.media){var media=matchMedia(\"(prefers-color-scheme: light)\"),input=document.querySelector(media.matches?\"[data-md-color-media='(prefers-color-scheme: light)']\":\"[data-md-color-media='(prefers-color-scheme: dark)']\");palette.color.media=input.getAttribute(\"data-md-color-media\"),palette.color.scheme=input.getAttribute(\"data-md-color-scheme\"),palette.color.primary=input.getAttribute(\"data-md-color-primary\"),palette.color.accent=input.getAttribute(\"data-md-color-accent\")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute(\"data-md-color-\"+key,value)}</script>\n \n \n \n \n \n <label class=\"md-header__button md-icon\" for=\"__search\" aria-label=\"Search\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-search\" viewBox=\"0 0 24 24\"><path d=\"m21 21-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></svg>\n </label>\n <div class=\"md-search\" data-md-component=\"search\" role=\"dialog\" aria-label=\"Search\">\n <button type=\"button\" class=\"md-search__button\">\n Search\n </button>\n</div>\n \n \n <div class=\"md-header__source\">\n \n </div>\n </nav>\n \n</header>\n \n <div class=\"md-container\" data-md-component=\"container\">\n \n \n \n \n \n \n <main class=\"md-main\" data-md-component=\"main\">\n <div class=\"md-main__inner md-grid\">\n \n \n \n <div class=\"md-sidebar md-sidebar--primary\" data-md-component=\"sidebar\" data-md-type=\"navigation\" >\n <div class=\"md-sidebar__scrollwrap\">\n <div class=\"md-sidebar__inner\">\n \n\n\n\n<nav class=\"md-nav md-nav--primary\" aria-label=\"Navigation\" data-md-level=\"0\">\n <label class=\"md-nav__title\" for=\"__drawer\">\n <a href=\"../..\" title=\"Documentation\" class=\"md-nav__button md-logo\" aria-label=\"Documentation\" data-md-component=\"logo\">\n \n <img src=\"../../images/logo.png\" alt=\"Documentation\">\n\n </a>\n Documentation\n </label>\n \n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../..\" class=\"md-nav__link\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-rocket\" viewBox=\"0 0 24 24\"><path d=\"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09\"/><path d=\"M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z\"/><path d=\"M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Get started\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../../auth/\" class=\"md-nav__link\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-key-round\" viewBox=\"0 0 24 24\"><path d=\"M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z\"/><circle cx=\"16.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Auth\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--section md-nav__item--nested\">\n \n \n \n <input class=\"md-nav__toggle md-toggle \" type=\"checkbox\" id=\"__nav_3\" >\n \n \n <label class=\"md-nav__link\" for=\"__nav_3\" id=\"__nav_3_label\" tabindex=\"\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Communication\n \n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <nav class=\"md-nav\" data-md-level=\"1\" aria-labelledby=\"__nav_3_label\" aria-expanded=\"false\">\n <label class=\"md-nav__title\" for=\"__nav_3\">\n <span class=\"md-nav__icon md-icon\"></span>\n \n \n Communication\n \n\n </label>\n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../../communication/mail/\" class=\"md-nav__link\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-mail\" viewBox=\"0 0 24 24\"><path d=\"m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7\"/><rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Mail (SMTP)\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n </ul>\n </nav>\n \n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--section md-nav__item--nested\">\n \n \n \n <input class=\"md-nav__toggle md-toggle \" type=\"checkbox\" id=\"__nav_4\" >\n \n \n <label class=\"md-nav__link\" for=\"__nav_4\" id=\"__nav_4_label\" tabindex=\"\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n API\n \n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <nav class=\"md-nav\" data-md-level=\"1\" aria-labelledby=\"__nav_4_label\" aria-expanded=\"false\">\n <label class=\"md-nav__title\" for=\"__nav_4\">\n <span class=\"md-nav__icon md-icon\"></span>\n \n \n API\n \n\n </label>\n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../../api/rate-limit/\" class=\"md-nav__link\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-gauge\" viewBox=\"0 0 24 24\"><path d=\"m12 14 4-4M3.34 19a10 10 0 1 1 17.32 0\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Rate Limiting\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../../api/schema/\" class=\"md-nav__link\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-database\" viewBox=\"0 0 24 24\"><ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\"/><path d=\"M3 5v14a9 3 0 0 0 18 0V5\"/><path d=\"M3 12a9 3 0 0 0 18 0\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Schema\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n </ul>\n </nav>\n \n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--section md-nav__item--nested\">\n \n \n \n <input class=\"md-nav__toggle md-toggle \" type=\"checkbox\" id=\"__nav_5\" >\n \n \n <label class=\"md-nav__link\" for=\"__nav_5\" id=\"__nav_5_label\" tabindex=\"\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Privacy\n \n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <nav class=\"md-nav\" data-md-level=\"1\" aria-labelledby=\"__nav_5_label\" aria-expanded=\"false\">\n <label class=\"md-nav__title\" for=\"__nav_5\">\n <span class=\"md-nav__icon md-icon\"></span>\n \n \n Privacy\n \n\n </label>\n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../../privacy/mask/\" class=\"md-nav__link\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-eye-off\" viewBox=\"0 0 24 24\"><path d=\"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.8 10.8 0 0 1-1.444 2.49M14.084 14.158a3 3 0 0 1-4.242-4.242\"/><path d=\"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143M2 2l20 20\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Masking\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../../privacy/security/\" class=\"md-nav__link\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-lock\" viewBox=\"0 0 24 24\"><rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Security\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n </ul>\n </nav>\n \n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--section md-nav__item--nested\">\n \n \n \n <input class=\"md-nav__toggle md-toggle \" type=\"checkbox\" id=\"__nav_6\" >\n \n \n <label class=\"md-nav__link\" for=\"__nav_6\" id=\"__nav_6_label\" tabindex=\"\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Core\n \n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <nav class=\"md-nav\" data-md-level=\"1\" aria-labelledby=\"__nav_6_label\" aria-expanded=\"false\">\n <label class=\"md-nav__title\" for=\"__nav_6\">\n <span class=\"md-nav__icon md-icon\"></span>\n \n \n Core\n \n\n </label>\n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../../core/logging.md\" class=\"md-nav__link\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Logging\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n </ul>\n </nav>\n \n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--section md-nav__item--nested\">\n \n \n \n <input class=\"md-nav__toggle md-toggle \" type=\"checkbox\" id=\"__nav_7\" >\n \n \n <label class=\"md-nav__link\" for=\"__nav_7\" id=\"__nav_7_label\" tabindex=\"\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Store\n \n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <nav class=\"md-nav\" data-md-level=\"1\" aria-labelledby=\"__nav_7_label\" aria-expanded=\"false\">\n <label class=\"md-nav__title\" for=\"__nav_7\">\n <span class=\"md-nav__icon md-icon\"></span>\n \n \n Store\n \n\n </label>\n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../../store/redis/\" class=\"md-nav__link\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-database\" viewBox=\"0 0 24 24\"><ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\"/><path d=\"M3 5v14a9 3 0 0 0 18 0V5\"/><path d=\"M3 12a9 3 0 0 0 18 0\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Redis\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n </ul>\n </nav>\n \n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested\">\n \n \n \n <input class=\"md-nav__toggle md-toggle \" type=\"checkbox\" id=\"__nav_8\" checked>\n \n \n <label class=\"md-nav__link\" for=\"__nav_8\" id=\"__nav_8_label\" tabindex=\"\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Template\n \n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <nav class=\"md-nav\" data-md-level=\"1\" aria-labelledby=\"__nav_8_label\" aria-expanded=\"true\">\n <label class=\"md-nav__title\" for=\"__nav_8\">\n <span class=\"md-nav__icon md-icon\"></span>\n \n \n Template\n \n\n </label>\n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--active\">\n \n \n \n \n \n \n \n <label class=\"md-nav__link md-nav__link--active\" for=\"__toc\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-layout-template\" viewBox=\"0 0 24 24\"><rect width=\"18\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\"/><rect width=\"9\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\"/><rect width=\"5\" height=\"7\" x=\"16\" y=\"14\" rx=\"1\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Rendering\n \n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <a href=\"././\" class=\"md-nav__link md-nav__link--active\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-layout-template\" viewBox=\"0 0 24 24\"><rect width=\"18\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\"/><rect width=\"9\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\"/><rect width=\"5\" height=\"7\" x=\"16\" y=\"14\" rx=\"1\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Rendering\n \n\n \n </span>\n \n \n\n </a>\n \n \n\n\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\n \n \n \n \n \n \n <label class=\"md-nav__title\" for=\"__toc\">\n <span class=\"md-nav__icon md-icon\"></span>\n On this page\n </label>\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\n \n <li class=\"md-nav__item\">\n <a href=\"#installation\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n Installation\n </span>\n </span>\n </a>\n \n</li>\n \n <li class=\"md-nav__item\">\n <a href=\"#quick-start\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n Quick start\n </span>\n </span>\n </a>\n \n</li>\n \n <li class=\"md-nav__item\">\n <a href=\"#render_html_template\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n <code>render_html_template</code>\n </span>\n </span>\n </a>\n \n</li>\n \n <li class=\"md-nav__item\">\n <a href=\"#render_html_string\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n <code>render_html_string</code>\n </span>\n </span>\n </a>\n \n</li>\n \n <li class=\"md-nav__item\">\n <a href=\"#usage-with-mail\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n Usage with mail\n </span>\n </span>\n </a>\n \n</li>\n \n </ul>\n \n</nav>\n \n </li>\n \n\n \n \n </ul>\n </nav>\n \n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--section md-nav__item--nested\">\n \n \n \n <input class=\"md-nav__toggle md-toggle \" type=\"checkbox\" id=\"__nav_9\" >\n \n \n <label class=\"md-nav__link\" for=\"__nav_9\" id=\"__nav_9_label\" tabindex=\"\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Utils\n \n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <nav class=\"md-nav\" data-md-level=\"1\" aria-labelledby=\"__nav_9_label\" aria-expanded=\"false\">\n <label class=\"md-nav__title\" for=\"__nav_9\">\n <span class=\"md-nav__icon md-icon\"></span>\n \n \n Utils\n \n\n </label>\n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../../utils/performance/\" class=\"md-nav__link\">\n \n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-timer\" viewBox=\"0 0 24 24\"><path d=\"M10 2h4M12 14l3-3\"/><circle cx=\"12\" cy=\"14\" r=\"8\"/></svg>\n \n <span class=\"md-ellipsis\">\n \n \n Performance\n \n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n </ul>\n </nav>\n \n </li>\n \n\n \n </ul>\n</nav>\n </div>\n </div>\n </div>\n \n \n \n <div class=\"md-sidebar md-sidebar--secondary\" data-md-component=\"sidebar\" data-md-type=\"toc\" >\n <div class=\"md-sidebar__scrollwrap\">\n \n \n \n \n \n \n \n <input class=\"md-nav__toggle md-toggle\" type=\"checkbox\" id=\"__toc\">\n <div class=\"md-sidebar-button__wrapper\">\n <label class=\"md-sidebar-button\" for=\"__toc\"></label>\n </div>\n \n \n <div class=\"md-sidebar__inner\">\n \n\n\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\n \n \n \n \n \n \n <label class=\"md-nav__title\" for=\"__toc\">\n <span class=\"md-nav__icon md-icon\"></span>\n On this page\n </label>\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\n \n <li class=\"md-nav__item\">\n <a href=\"#installation\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n Installation\n </span>\n </span>\n </a>\n \n</li>\n \n <li class=\"md-nav__item\">\n <a href=\"#quick-start\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n Quick start\n </span>\n </span>\n </a>\n \n</li>\n \n <li class=\"md-nav__item\">\n <a href=\"#render_html_template\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n <code>render_html_template</code>\n </span>\n </span>\n </a>\n \n</li>\n \n <li class=\"md-nav__item\">\n <a href=\"#render_html_string\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n <code>render_html_string</code>\n </span>\n </span>\n </a>\n \n</li>\n \n <li class=\"md-nav__item\">\n <a href=\"#usage-with-mail\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n Usage with mail\n </span>\n </span>\n </a>\n \n</li>\n \n </ul>\n \n</nav>\n </div>\n </div>\n </div>\n \n \n \n <div class=\"md-content\" data-md-component=\"content\">\n \n \n\n\n\n \n\n\n <nav class=\"md-path\" aria-label=\"Navigation\" >\n <ol class=\"md-path__list\">\n \n \n \n \n <li class=\"md-path__item\">\n <a href=\"../..\" class=\"md-path__link\">\n \n <span class=\"md-ellipsis\">\n Get started\n </span>\n\n </a>\n </li>\n \n\n \n \n \n \n \n \n \n <li class=\"md-path__item\">\n <a href=\"././\" class=\"md-path__link\">\n \n <span class=\"md-ellipsis\">\n Template\n </span>\n\n </a>\n </li>\n \n \n\n \n </ol>\n </nav>\n\n \n <article class=\"md-content__inner md-typeset\">\n \n \n\n<h1 id=\"template-rendering\">Template Rendering<a class=\"headerlink\" href=\"#template-rendering\" title=\"Permanent link\">¶</a></h1>\n<p>The <code>utkit.template.render</code> module provides HTML rendering utilities powered by <a href=\"https://jinja.palletsprojects.com/\">Jinja2</a>. It supports rendering from template files on disk and from raw template strings.</p>\n<h2 id=\"installation\">Installation<a class=\"headerlink\" href=\"#installation\" title=\"Permanent link\">¶</a></h2>\n<p><code>jinja2</code> is part of the optional <code>standard</code> extras. Install <code>utkit</code> with the <code>standard</code> extra:</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a>pip<span class=\"w\"> </span>install<span class=\"w\"> </span><span class=\"s2\">"utkit[standard]"</span>\n</span></code></pre></div>\n<p>Or with <a href=\"https://docs.astral.sh/uv/\">uv</a>:</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a>uv<span class=\"w\"> </span>add<span class=\"w\"> </span><span class=\"s2\">"utkit[standard]"</span>\n</span></code></pre></div>\n<hr />\n<h2 id=\"quick-start\">Quick start<a class=\"headerlink\" href=\"#quick-start\" title=\"Permanent link\">¶</a></h2>\n<p><strong>From a file:</strong></p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">utkit.template.render</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">render_html_template</span>\n</span><span id=\"__span-2-2\"><a id=\"__codelineno-2-2\" name=\"__codelineno-2-2\" href=\"#__codelineno-2-2\"></a>\n</span><span id=\"__span-2-3\"><a id=\"__codelineno-2-3\" name=\"__codelineno-2-3\" href=\"#__codelineno-2-3\"></a><span class=\"n\">html</span> <span class=\"o\">=</span> <span class=\"n\">render_html_template</span><span class=\"p\">(</span>\n</span><span id=\"__span-2-4\"><a id=\"__codelineno-2-4\" name=\"__codelineno-2-4\" href=\"#__codelineno-2-4\"></a> <span class=\"n\">template_name</span><span class=\"o\">=</span><span class=\"s2\">"welcome.html"</span><span class=\"p\">,</span>\n</span><span id=\"__span-2-5\"><a id=\"__codelineno-2-5\" name=\"__codelineno-2-5\" href=\"#__codelineno-2-5\"></a> <span class=\"n\">context</span><span class=\"o\">=</span><span class=\"p\">{</span><span class=\"s2\">"name"</span><span class=\"p\">:</span> <span class=\"s2\">"Alice"</span><span class=\"p\">,</span> <span class=\"s2\">"app"</span><span class=\"p\">:</span> <span class=\"s2\">"utkit"</span><span class=\"p\">},</span>\n</span><span id=\"__span-2-6\"><a id=\"__codelineno-2-6\" name=\"__codelineno-2-6\" href=\"#__codelineno-2-6\"></a> <span class=\"n\">template_dir</span><span class=\"o\">=</span><span class=\"s2\">"templates"</span><span class=\"p\">,</span>\n</span><span id=\"__span-2-7\"><a id=\"__codelineno-2-7\" name=\"__codelineno-2-7\" href=\"#__codelineno-2-7\"></a><span class=\"p\">)</span>\n</span></code></pre></div>\n<p><strong>From a string:</strong></p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">utkit.template.render</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">render_html_string</span>\n</span><span id=\"__span-3-2\"><a id=\"__codelineno-3-2\" name=\"__codelineno-3-2\" href=\"#__codelineno-3-2\"></a>\n</span><span id=\"__span-3-3\"><a id=\"__codelineno-3-3\" name=\"__codelineno-3-3\" href=\"#__codelineno-3-3\"></a><span class=\"n\">html</span> <span class=\"o\">=</span> <span class=\"n\">render_html_string</span><span class=\"p\">(</span>\n</span><span id=\"__span-3-4\"><a id=\"__codelineno-3-4\" name=\"__codelineno-3-4\" href=\"#__codelineno-3-4\"></a> <span class=\"n\">template_str</span><span class=\"o\">=</span><span class=\"s2\">"<h1>Hello, {{ name }}!</h1>"</span><span class=\"p\">,</span>\n</span><span id=\"__span-3-5\"><a id=\"__codelineno-3-5\" name=\"__codelineno-3-5\" href=\"#__codelineno-3-5\"></a> <span class=\"n\">context</span><span class=\"o\">=</span><span class=\"p\">{</span><span class=\"s2\">"name"</span><span class=\"p\">:</span> <span class=\"s2\">"Alice"</span><span class=\"p\">},</span>\n</span><span id=\"__span-3-6\"><a id=\"__codelineno-3-6\" name=\"__codelineno-3-6\" href=\"#__codelineno-3-6\"></a><span class=\"p\">)</span>\n</span></code></pre></div>\n<hr />\n<h2 id=\"render_html_template\"><code>render_html_template</code><a class=\"headerlink\" href=\"#render_html_template\" title=\"Permanent link\">¶</a></h2>\n<p>Renders an HTML template file using Jinja2's <code>FileSystemLoader</code>. HTML and XML autoescaping is enabled automatically.</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-4-1\"><a id=\"__codelineno-4-1\" name=\"__codelineno-4-1\" href=\"#__codelineno-4-1\"></a><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">render_html_template</span><span class=\"p\">(</span>\n</span><span id=\"__span-4-2\"><a id=\"__codelineno-4-2\" name=\"__codelineno-4-2\" href=\"#__codelineno-4-2\"></a> <span class=\"n\">template_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n</span><span id=\"__span-4-3\"><a id=\"__codelineno-4-3\" name=\"__codelineno-4-3\" href=\"#__codelineno-4-3\"></a> <span class=\"n\">context</span><span class=\"p\">:</span> <span class=\"n\">Dict</span><span class=\"p\">,</span>\n</span><span id=\"__span-4-4\"><a id=\"__codelineno-4-4\" name=\"__codelineno-4-4\" href=\"#__codelineno-4-4\"></a> <span class=\"n\">template_dir</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span class=\"s2\">"templates"</span><span class=\"p\">,</span>\n</span><span id=\"__span-4-5\"><a id=\"__codelineno-4-5\" name=\"__codelineno-4-5\" href=\"#__codelineno-4-5\"></a><span class=\"p\">)</span> <span class=\"o\">-></span> <span class=\"nb\">str</span>\n</span></code></pre></div>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Type</th>\n<th>Default</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>template_name</code></td>\n<td><code>str</code></td>\n<td>—</td>\n<td>File name of the template relative to <code>template_dir</code> (e.g. <code>\"email.html\"</code>)</td>\n</tr>\n<tr>\n<td><code>context</code></td>\n<td><code>Dict</code></td>\n<td>—</td>\n<td>Dictionary of variables passed into the template</td>\n</tr>\n<tr>\n<td><code>template_dir</code></td>\n<td><code>str</code></td>\n<td><code>\"templates\"</code></td>\n<td>Path to the directory containing template files</td>\n</tr>\n</tbody>\n</table>\n<p><strong>Returns:</strong> <code>str</code> — the fully rendered HTML string.</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-5-1\"><a id=\"__codelineno-5-1\" name=\"__codelineno-5-1\" href=\"#__codelineno-5-1\"></a><span class=\"c1\"># templates/invoice.html</span>\n</span><span id=\"__span-5-2\"><a id=\"__codelineno-5-2\" name=\"__codelineno-5-2\" href=\"#__codelineno-5-2\"></a><span class=\"c1\"># <p>Invoice #{{ number }} for {{ customer }}</p></span>\n</span><span id=\"__span-5-3\"><a id=\"__codelineno-5-3\" name=\"__codelineno-5-3\" href=\"#__codelineno-5-3\"></a>\n</span><span id=\"__span-5-4\"><a id=\"__codelineno-5-4\" name=\"__codelineno-5-4\" href=\"#__codelineno-5-4\"></a><span class=\"n\">html</span> <span class=\"o\">=</span> <span class=\"n\">render_html_template</span><span class=\"p\">(</span>\n</span><span id=\"__span-5-5\"><a id=\"__codelineno-5-5\" name=\"__codelineno-5-5\" href=\"#__codelineno-5-5\"></a> <span class=\"s2\">"invoice.html"</span><span class=\"p\">,</span>\n</span><span id=\"__span-5-6\"><a id=\"__codelineno-5-6\" name=\"__codelineno-5-6\" href=\"#__codelineno-5-6\"></a> <span class=\"p\">{</span><span class=\"s2\">"number"</span><span class=\"p\">:</span> <span class=\"mi\">1042</span><span class=\"p\">,</span> <span class=\"s2\">"customer"</span><span class=\"p\">:</span> <span class=\"s2\">"Acme Corp"</span><span class=\"p\">},</span>\n</span><span id=\"__span-5-7\"><a id=\"__codelineno-5-7\" name=\"__codelineno-5-7\" href=\"#__codelineno-5-7\"></a> <span class=\"n\">template_dir</span><span class=\"o\">=</span><span class=\"s2\">"templates"</span><span class=\"p\">,</span>\n</span><span id=\"__span-5-8\"><a id=\"__codelineno-5-8\" name=\"__codelineno-5-8\" href=\"#__codelineno-5-8\"></a><span class=\"p\">)</span>\n</span></code></pre></div>\n<hr />\n<h2 id=\"render_html_string\"><code>render_html_string</code><a class=\"headerlink\" href=\"#render_html_string\" title=\"Permanent link\">¶</a></h2>\n<p>Renders HTML directly from a Jinja2 template string. Autoescaping is always enabled.</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-6-1\"><a id=\"__codelineno-6-1\" name=\"__codelineno-6-1\" href=\"#__codelineno-6-1\"></a><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">render_html_string</span><span class=\"p\">(</span><span class=\"n\">template_str</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">context</span><span class=\"p\">:</span> <span class=\"n\">Dict</span><span class=\"p\">)</span> <span class=\"o\">-></span> <span class=\"nb\">str</span>\n</span></code></pre></div>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Type</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>template_str</code></td>\n<td><code>str</code></td>\n<td>A Jinja2 template string</td>\n</tr>\n<tr>\n<td><code>context</code></td>\n<td><code>Dict</code></td>\n<td>Dictionary of variables passed into the template</td>\n</tr>\n</tbody>\n</table>\n<p><strong>Returns:</strong> <code>str</code> — the fully rendered HTML string.</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-7-1\"><a id=\"__codelineno-7-1\" name=\"__codelineno-7-1\" href=\"#__codelineno-7-1\"></a><span class=\"n\">html</span> <span class=\"o\">=</span> <span class=\"n\">render_html_string</span><span class=\"p\">(</span>\n</span><span id=\"__span-7-2\"><a id=\"__codelineno-7-2\" name=\"__codelineno-7-2\" href=\"#__codelineno-7-2\"></a> <span class=\"s2\">"<p>Welcome, <strong>{{ name }}</strong>!</p>"</span><span class=\"p\">,</span>\n</span><span id=\"__span-7-3\"><a id=\"__codelineno-7-3\" name=\"__codelineno-7-3\" href=\"#__codelineno-7-3\"></a> <span class=\"p\">{</span><span class=\"s2\">"name"</span><span class=\"p\">:</span> <span class=\"s2\">"Alice"</span><span class=\"p\">},</span>\n</span><span id=\"__span-7-4\"><a id=\"__codelineno-7-4\" name=\"__codelineno-7-4\" href=\"#__codelineno-7-4\"></a><span class=\"p\">)</span>\n</span><span id=\"__span-7-5\"><a id=\"__codelineno-7-5\" name=\"__codelineno-7-5\" href=\"#__codelineno-7-5\"></a><span class=\"c1\"># <p>Welcome, <strong>Alice</strong>!</p></span>\n</span></code></pre></div>\n<hr />\n<h2 id=\"usage-with-mail\">Usage with mail<a class=\"headerlink\" href=\"#usage-with-mail\" title=\"Permanent link\">¶</a></h2>\n<p><code>render_html_template</code> pairs naturally with <code>utkit.communication.mail.smtp</code> to send dynamic HTML emails:</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-8-1\"><a id=\"__codelineno-8-1\" name=\"__codelineno-8-1\" href=\"#__codelineno-8-1\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">utkit.template.render</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">render_html_template</span>\n</span><span id=\"__span-8-2\"><a id=\"__codelineno-8-2\" name=\"__codelineno-8-2\" href=\"#__codelineno-8-2\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">utkit.communication.mail.smtp</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">SMTPConfig</span><span class=\"p\">,</span> <span class=\"n\">MailMessage</span><span class=\"p\">,</span> <span class=\"n\">send_mail</span>\n</span><span id=\"__span-8-3\"><a id=\"__codelineno-8-3\" name=\"__codelineno-8-3\" href=\"#__codelineno-8-3\"></a>\n</span><span id=\"__span-8-4\"><a id=\"__codelineno-8-4\" name=\"__codelineno-8-4\" href=\"#__codelineno-8-4\"></a><span class=\"n\">config</span> <span class=\"o\">=</span> <span class=\"n\">SMTPConfig</span><span class=\"p\">(</span>\n</span><span id=\"__span-8-5\"><a id=\"__codelineno-8-5\" name=\"__codelineno-8-5\" href=\"#__codelineno-8-5\"></a> <span class=\"n\">host</span><span class=\"o\">=</span><span class=\"s2\">"smtp.gmail.com"</span><span class=\"p\">,</span>\n</span><span id=\"__span-8-6\"><a id=\"__codelineno-8-6\" name=\"__codelineno-8-6\" href=\"#__codelineno-8-6\"></a> <span class=\"n\">port</span><span class=\"o\">=</span><span class=\"mi\">587</span><span class=\"p\">,</span>\n</span><span id=\"__span-8-7\"><a id=\"__codelineno-8-7\" name=\"__codelineno-8-7\" href=\"#__codelineno-8-7\"></a> <span class=\"n\">username</span><span class=\"o\">=</span><span class=\"s2\">"you@gmail.com"</span><span class=\"p\">,</span>\n</span><span id=\"__span-8-8\"><a id=\"__codelineno-8-8\" name=\"__codelineno-8-8\" href=\"#__codelineno-8-8\"></a> <span class=\"n\">password</span><span class=\"o\">=</span><span class=\"s2\">"your-app-password"</span><span class=\"p\">,</span>\n</span><span id=\"__span-8-9\"><a id=\"__codelineno-8-9\" name=\"__codelineno-8-9\" href=\"#__codelineno-8-9\"></a><span class=\"p\">)</span>\n</span><span id=\"__span-8-10\"><a id=\"__codelineno-8-10\" name=\"__codelineno-8-10\" href=\"#__codelineno-8-10\"></a>\n</span><span id=\"__span-8-11\"><a id=\"__codelineno-8-11\" name=\"__codelineno-8-11\" href=\"#__codelineno-8-11\"></a><span class=\"n\">html</span> <span class=\"o\">=</span> <span class=\"n\">render_html_template</span><span class=\"p\">(</span>\n</span><span id=\"__span-8-12\"><a id=\"__codelineno-8-12\" name=\"__codelineno-8-12\" href=\"#__codelineno-8-12\"></a> <span class=\"s2\">"welcome_email.html"</span><span class=\"p\">,</span>\n</span><span id=\"__span-8-13\"><a id=\"__codelineno-8-13\" name=\"__codelineno-8-13\" href=\"#__codelineno-8-13\"></a> <span class=\"p\">{</span><span class=\"s2\">"name"</span><span class=\"p\">:</span> <span class=\"s2\">"Alice"</span><span class=\"p\">,</span> <span class=\"s2\">"app_url"</span><span class=\"p\">:</span> <span class=\"s2\">"https://example.com"</span><span class=\"p\">},</span>\n</span><span id=\"__span-8-14\"><a id=\"__codelineno-8-14\" name=\"__codelineno-8-14\" href=\"#__codelineno-8-14\"></a><span class=\"p\">)</span>\n</span><span id=\"__span-8-15\"><a id=\"__codelineno-8-15\" name=\"__codelineno-8-15\" href=\"#__codelineno-8-15\"></a>\n</span><span id=\"__span-8-16\"><a id=\"__codelineno-8-16\" name=\"__codelineno-8-16\" href=\"#__codelineno-8-16\"></a><span class=\"n\">message</span> <span class=\"o\">=</span> <span class=\"n\">MailMessage</span><span class=\"p\">(</span>\n</span><span id=\"__span-8-17\"><a id=\"__codelineno-8-17\" name=\"__codelineno-8-17\" href=\"#__codelineno-8-17\"></a> <span class=\"n\">subject</span><span class=\"o\">=</span><span class=\"s2\">"Welcome to utkit"</span><span class=\"p\">,</span>\n</span><span id=\"__span-8-18\"><a id=\"__codelineno-8-18\" name=\"__codelineno-8-18\" href=\"#__codelineno-8-18\"></a> <span class=\"n\">from_address</span><span class=\"o\">=</span><span class=\"s2\">"you@gmail.com"</span><span class=\"p\">,</span>\n</span><span id=\"__span-8-19\"><a id=\"__codelineno-8-19\" name=\"__codelineno-8-19\" href=\"#__codelineno-8-19\"></a> <span class=\"n\">to</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s2\">"alice@example.com"</span><span class=\"p\">],</span>\n</span><span id=\"__span-8-20\"><a id=\"__codelineno-8-20\" name=\"__codelineno-8-20\" href=\"#__codelineno-8-20\"></a> <span class=\"n\">html</span><span class=\"o\">=</span><span class=\"n\">html</span><span class=\"p\">,</span>\n</span><span id=\"__span-8-21\"><a id=\"__codelineno-8-21\" name=\"__codelineno-8-21\" href=\"#__codelineno-8-21\"></a><span class=\"p\">)</span>\n</span><span id=\"__span-8-22\"><a id=\"__codelineno-8-22\" name=\"__codelineno-8-22\" href=\"#__codelineno-8-22\"></a>\n</span><span id=\"__span-8-23\"><a id=\"__codelineno-8-23\" name=\"__codelineno-8-23\" href=\"#__codelineno-8-23\"></a><span class=\"n\">send_mail</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"p\">,</span> <span class=\"n\">message</span><span class=\"p\">)</span>\n</span></code></pre></div>\n\n\n\n\n\n\n\n\n\n \n \n\n\n\n\n\n\n \n </article>\n </div>\n \n \n <script>var tabs=__md_get(\"__tabs\");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(\".tabbed-set\")){var labels=set.querySelector(\".tabbed-labels\");for(var tab of tabs)for(var label of labels.getElementsByTagName(\"label\"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>\n\n<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith(\"__tabbed_\"))</script>\n </div>\n \n <button type=\"button\" class=\"md-top md-icon\" data-md-component=\"top\" hidden>\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-circle-arrow-up\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m16 12-4-4-4 4M12 16V8\"/></svg>\n Back to top\n</button>\n \n </main>\n \n <footer class=\"md-footer\">\n \n \n \n <nav class=\"md-footer__inner md-grid\" aria-label=\"Footer\" >\n \n \n <a href=\"../../store/redis/\" class=\"md-footer__link md-footer__link--prev\" aria-label=\"Previous: Redis\">\n <div class=\"md-footer__button md-icon\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-left\" viewBox=\"0 0 24 24\"><path d=\"m12 19-7-7 7-7M19 12H5\"/></svg>\n </div>\n <div class=\"md-footer__title\">\n <span class=\"md-footer__direction\">\n Previous\n </span>\n <div class=\"md-ellipsis\">\n Redis\n </div>\n </div>\n </a>\n \n \n \n <a href=\"../../utils/performance/\" class=\"md-footer__link md-footer__link--next\" aria-label=\"Next: Performance\">\n <div class=\"md-footer__title\">\n <span class=\"md-footer__direction\">\n Next\n </span>\n <div class=\"md-ellipsis\">\n Performance\n </div>\n </div>\n <div class=\"md-footer__button md-icon\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-right\" viewBox=\"0 0 24 24\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg>\n </div>\n </a>\n \n </nav>\n \n \n <div class=\"md-footer-meta md-typeset\">\n <div class=\"md-footer-meta__inner md-grid\">\n <div class=\"md-copyright\">\n \n <div class=\"md-copyright__highlight\">\n Copyright © 2026 TINS PJ. All rights reserved.\n\n </div>\n \n \n Made with\n <a href=\"https://zensical.org/\" target=\"_blank\" rel=\"noopener\">\n Zensical\n </a>\n \n</div>\n \n </div>\n </div>\n</footer>\n \n </div>\n <div class=\"md-dialog\" data-md-component=\"dialog\">\n <div class=\"md-dialog__inner md-typeset\"></div>\n </div>\n \n \n \n \n \n <script id=\"__config\" type=\"application/json\">{\"annotate\":null,\"base\":\"../..\",\"features\":[\"announce.dismiss\",\"content.code.annotate\",\"content.code.copy\",\"content.code.select\",\"content.footnote.tooltips\",\"content.tabs.link\",\"content.tooltips\",\"navigation.footer\",\"navigation.indexes\",\"navigation.instant\",\"navigation.instant.prefetch\",\"navigation.path\",\"navigation.sections\",\"navigation.top\",\"navigation.tracking\",\"search.highlight\"],\"search\":\"../../assets/javascripts/workers/search.e2d2d235.min.js\",\"tags\":null,\"translations\":{\"clipboard.copied\":\"Copied to clipboard\",\"clipboard.copy\":\"Copy to clipboard\",\"search.result.more.one\":\"1 more on this page\",\"search.result.more.other\":\"# more on this page\",\"search.result.none\":\"No matching documents\",\"search.result.one\":\"1 matching document\",\"search.result.other\":\"# matching documents\",\"search.result.placeholder\":\"Type to start searching\",\"search.result.term.missing\":\"Missing\",\"select.version\":\"Select version\"},\"version\":null}</script>\n \n \n <script src=\"../../assets/javascripts/bundle.dbc0afdc.min.js\"></script>\n \n \n </body>\n</html>",
|
|
3
|
+
"hash": 361807550503777080
|
|
4
|
+
}
|