fastapi-modular 0.1.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.
- fastapi_modular-0.1.0/.gitignore +88 -0
- fastapi_modular-0.1.0/CHANGELOG.md +32 -0
- fastapi_modular-0.1.0/LICENSE +21 -0
- fastapi_modular-0.1.0/Makefile +118 -0
- fastapi_modular-0.1.0/PKG-INFO +377 -0
- fastapi_modular-0.1.0/README.md +314 -0
- fastapi_modular-0.1.0/alembic.ini +39 -0
- fastapi_modular-0.1.0/docs/README.md +68 -0
- fastapi_modular-0.1.0/docs/architecture.md +271 -0
- fastapi_modular-0.1.0/docs/config.md +223 -0
- fastapi_modular-0.1.0/docs/database.md +703 -0
- fastapi_modular-0.1.0/docs/kafka.md +236 -0
- fastapi_modular-0.1.0/docs/migrations.md +113 -0
- fastapi_modular-0.1.0/docs/mqtt.md +225 -0
- fastapi_modular-0.1.0/docs/operations.md +289 -0
- fastapi_modular-0.1.0/docs/rabbitmq.md +558 -0
- fastapi_modular-0.1.0/docs/redis.md +222 -0
- fastapi_modular-0.1.0/docs/websocket.md +772 -0
- fastapi_modular-0.1.0/migrations/env.py +79 -0
- fastapi_modular-0.1.0/migrations/script.py.mako +27 -0
- fastapi_modular-0.1.0/migrations/versions/.gitkeep +0 -0
- fastapi_modular-0.1.0/migrations/versions/20260821_1020_tao_bang_users_va_devices.py +67 -0
- fastapi_modular-0.1.0/pymodular/__init__.py +74 -0
- fastapi_modular-0.1.0/pymodular/cli/__init__.py +0 -0
- fastapi_modular-0.1.0/pymodular/cli/clean.py +39 -0
- fastapi_modular-0.1.0/pymodular/cli/configure_env.py +569 -0
- fastapi_modular-0.1.0/pymodular/cli/cong_cu.py +111 -0
- fastapi_modular-0.1.0/pymodular/cli/info.py +62 -0
- fastapi_modular-0.1.0/pymodular/cli/install.py +83 -0
- fastapi_modular-0.1.0/pymodular/cli/main.py +247 -0
- fastapi_modular-0.1.0/pymodular/cli/new_module.py +492 -0
- fastapi_modular-0.1.0/pymodular/cli/new_project.py +471 -0
- fastapi_modular-0.1.0/pymodular/cli/serve.py +59 -0
- fastapi_modular-0.1.0/pymodular/core/__init__.py +0 -0
- fastapi_modular-0.1.0/pymodular/core/clock.py +15 -0
- fastapi_modular-0.1.0/pymodular/core/compat.py +39 -0
- fastapi_modular-0.1.0/pymodular/core/config.py +495 -0
- fastapi_modular-0.1.0/pymodular/core/container.py +354 -0
- fastapi_modular-0.1.0/pymodular/core/context.py +78 -0
- fastapi_modular-0.1.0/pymodular/core/controller.py +208 -0
- fastapi_modular-0.1.0/pymodular/core/error_handlers.py +272 -0
- fastapi_modular-0.1.0/pymodular/core/exceptions.py +104 -0
- fastapi_modular-0.1.0/pymodular/core/guards.py +117 -0
- fastapi_modular-0.1.0/pymodular/core/lifespan.py +150 -0
- fastapi_modular-0.1.0/pymodular/core/logging.py +88 -0
- fastapi_modular-0.1.0/pymodular/core/metrics.py +190 -0
- fastapi_modular-0.1.0/pymodular/core/schemas.py +105 -0
- fastapi_modular-0.1.0/pymodular/core/websocket/__init__.py +31 -0
- fastapi_modular-0.1.0/pymodular/core/websocket/adapter.py +192 -0
- fastapi_modular-0.1.0/pymodular/core/websocket/gateway.py +735 -0
- fastapi_modular-0.1.0/pymodular/core/websocket/namespace.py +148 -0
- fastapi_modular-0.1.0/pymodular/core/websocket/protocol.py +157 -0
- fastapi_modular-0.1.0/pymodular/core/websocket/server.py +175 -0
- fastapi_modular-0.1.0/pymodular/core/websocket/socket.py +241 -0
- fastapi_modular-0.1.0/pymodular/discovery.py +180 -0
- fastapi_modular-0.1.0/pymodular/factory.py +126 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/__init__.py +1 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/database/__init__.py +8 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/database/base.py +228 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/database/circuit.py +207 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/database/factory.py +88 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/database/memory.py +112 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/database/mongo.py +186 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/database/repository.py +188 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/database/sql.py +520 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/kafka/__init__.py +26 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/kafka/broker.py +231 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/kafka/consumers.py +371 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/kafka/metrics.py +17 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/__init__.py +35 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/client.py +292 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/consumers.py +219 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/metrics.py +17 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/patterns.py +116 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/__init__.py +33 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/broker.py +616 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/consumers.py +450 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/metrics.py +34 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/patterns.py +64 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/redis/__init__.py +31 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/redis/client.py +362 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/redis/metrics.py +20 -0
- fastapi_modular-0.1.0/pymodular/infrastructure/redis/pubsub.py +262 -0
- fastapi_modular-0.1.0/pymodular/middleware/__init__.py +0 -0
- fastapi_modular-0.1.0/pymodular/middleware/request_context.py +164 -0
- fastapi_modular-0.1.0/pymodular/py.typed +0 -0
- fastapi_modular-0.1.0/pyproject.toml +76 -0
- fastapi_modular-0.1.0/pytest.ini +5 -0
- fastapi_modular-0.1.0/ruff.toml +30 -0
- fastapi_modular-0.1.0/src/__init__.py +6 -0
- fastapi_modular-0.1.0/src/api/__init__.py +7 -0
- fastapi_modular-0.1.0/src/api/chat/__init__.py +1 -0
- fastapi_modular-0.1.0/src/api/chat/chat_controller.py +46 -0
- fastapi_modular-0.1.0/src/api/chat/chat_gateway.py +118 -0
- fastapi_modular-0.1.0/src/api/chat/dto/__init__.py +0 -0
- fastapi_modular-0.1.0/src/api/chat/dto/chat_dto.py +54 -0
- fastapi_modular-0.1.0/src/api/devices/__init__.py +1 -0
- fastapi_modular-0.1.0/src/api/devices/device_controller.py +98 -0
- fastapi_modular-0.1.0/src/api/devices/device_service.py +71 -0
- fastapi_modular-0.1.0/src/api/devices/dto/__init__.py +0 -0
- fastapi_modular-0.1.0/src/api/devices/dto/device_dto.py +40 -0
- fastapi_modular-0.1.0/src/api/devices/entities/__init__.py +0 -0
- fastapi_modular-0.1.0/src/api/devices/entities/device_model.py +43 -0
- fastapi_modular-0.1.0/src/api/health/__init__.py +1 -0
- fastapi_modular-0.1.0/src/api/health/health_controller.py +83 -0
- fastapi_modular-0.1.0/src/api/kafka_test/__init__.py +0 -0
- fastapi_modular-0.1.0/src/api/kafka_test/kafka_consumer.py +71 -0
- fastapi_modular-0.1.0/src/api/kafka_test/kafka_controller.py +37 -0
- fastapi_modular-0.1.0/src/api/metrics/__init__.py +1 -0
- fastapi_modular-0.1.0/src/api/metrics/metrics_controller.py +46 -0
- fastapi_modular-0.1.0/src/api/mqtt_test/__init__.py +0 -0
- fastapi_modular-0.1.0/src/api/mqtt_test/mqtt_controller.py +47 -0
- fastapi_modular-0.1.0/src/api/mqtt_test/mqtt_listener.py +45 -0
- fastapi_modular-0.1.0/src/api/rabbitmq_test/__init__.py +0 -0
- fastapi_modular-0.1.0/src/api/rabbitmq_test/rabbitmq_consumer.py +69 -0
- fastapi_modular-0.1.0/src/api/rabbitmq_test/rabbitmq_controller.py +49 -0
- fastapi_modular-0.1.0/src/api/redis_test/__init__.py +0 -0
- fastapi_modular-0.1.0/src/api/redis_test/redis_controller.py +59 -0
- fastapi_modular-0.1.0/src/api/redis_test/redis_listener.py +61 -0
- fastapi_modular-0.1.0/src/api/users/__init__.py +1 -0
- fastapi_modular-0.1.0/src/api/users/dto/__init__.py +0 -0
- fastapi_modular-0.1.0/src/api/users/dto/user_dto.py +65 -0
- fastapi_modular-0.1.0/src/api/users/entities/__init__.py +0 -0
- fastapi_modular-0.1.0/src/api/users/entities/user_model.py +24 -0
- fastapi_modular-0.1.0/src/api/users/user_controller.py +72 -0
- fastapi_modular-0.1.0/src/api/users/user_service.py +93 -0
- fastapi_modular-0.1.0/src/core/__init__.py +4 -0
- fastapi_modular-0.1.0/src/core/config.py +31 -0
- fastapi_modular-0.1.0/src/core/lifespan.py +39 -0
- fastapi_modular-0.1.0/src/main.py +50 -0
- fastapi_modular-0.1.0/tests/conftest.py +106 -0
- fastapi_modular-0.1.0/tests/test_circuit.py +161 -0
- fastapi_modular-0.1.0/tests/test_cli.py +530 -0
- fastapi_modular-0.1.0/tests/test_configure_env.py +187 -0
- fastapi_modular-0.1.0/tests/test_container.py +158 -0
- fastapi_modular-0.1.0/tests/test_cross_module.py +64 -0
- fastapi_modular-0.1.0/tests/test_database.py +96 -0
- fastapi_modular-0.1.0/tests/test_drivers.py +148 -0
- fastapi_modular-0.1.0/tests/test_guards.py +143 -0
- fastapi_modular-0.1.0/tests/test_ha_tang_that.py +15 -0
- fastapi_modular-0.1.0/tests/test_health.py +14 -0
- fastapi_modular-0.1.0/tests/test_kafka.py +158 -0
- fastapi_modular-0.1.0/tests/test_kafka_offline.py +78 -0
- fastapi_modular-0.1.0/tests/test_lifespan_rieng.py +105 -0
- fastapi_modular-0.1.0/tests/test_metrics.py +128 -0
- fastapi_modular-0.1.0/tests/test_module_generator.py +195 -0
- fastapi_modular-0.1.0/tests/test_mqtt.py +132 -0
- fastapi_modular-0.1.0/tests/test_mqtt_offline.py +106 -0
- fastapi_modular-0.1.0/tests/test_rabbitmq.py +236 -0
- fastapi_modular-0.1.0/tests/test_rabbitmq_offline.py +273 -0
- fastapi_modular-0.1.0/tests/test_redis.py +137 -0
- fastapi_modular-0.1.0/tests/test_redis_offline.py +69 -0
- fastapi_modular-0.1.0/tests/test_resilience.py +230 -0
- fastapi_modular-0.1.0/tests/test_schema_evolution.py +134 -0
- fastapi_modular-0.1.0/tests/test_schemas.py +156 -0
- fastapi_modular-0.1.0/tests/test_settings_mo_rong.py +130 -0
- fastapi_modular-0.1.0/tests/test_tuong_thich_phien_ban.py +75 -0
- fastapi_modular-0.1.0/tests/test_uniqueness.py +118 -0
- fastapi_modular-0.1.0/tests/test_users_api.py +101 -0
- fastapi_modular-0.1.0/tests/test_websocket.py +508 -0
- fastapi_modular-0.1.0/tests/test_ws_adapter.py +139 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# ---------------------------------------------------------------- Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
|
|
8
|
+
# Gói và bản dựng
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
sdist/
|
|
12
|
+
wheels/
|
|
13
|
+
*.egg
|
|
14
|
+
*.egg-info
|
|
15
|
+
.eggs/
|
|
16
|
+
MANIFEST
|
|
17
|
+
pip-wheel-metadata/
|
|
18
|
+
|
|
19
|
+
# Môi trường ảo
|
|
20
|
+
.venv/
|
|
21
|
+
venv/
|
|
22
|
+
ENV/
|
|
23
|
+
env/
|
|
24
|
+
.python-version
|
|
25
|
+
|
|
26
|
+
# Test, độ phủ, kiểm kiểu, lint
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.ruff_cache/
|
|
29
|
+
.mypy_cache/
|
|
30
|
+
.dmypy.json
|
|
31
|
+
.pytype/
|
|
32
|
+
.pyre/
|
|
33
|
+
.tox/
|
|
34
|
+
.nox/
|
|
35
|
+
.coverage
|
|
36
|
+
.coverage.*
|
|
37
|
+
coverage.xml
|
|
38
|
+
htmlcov/
|
|
39
|
+
*.cover
|
|
40
|
+
.hypothesis/
|
|
41
|
+
.cache/
|
|
42
|
+
|
|
43
|
+
# Jupyter
|
|
44
|
+
.ipynb_checkpoints/
|
|
45
|
+
profile_default/
|
|
46
|
+
ipython_config.py
|
|
47
|
+
|
|
48
|
+
# ------------------------------------------------------------- Dự án này
|
|
49
|
+
# .env chứa DSN và mật khẩu thật — KHÔNG BAO GIỜ commit.
|
|
50
|
+
.env
|
|
51
|
+
.env.*
|
|
52
|
+
!.env.example
|
|
53
|
+
|
|
54
|
+
data/
|
|
55
|
+
*.db
|
|
56
|
+
*.sqlite
|
|
57
|
+
*.sqlite3
|
|
58
|
+
*.log
|
|
59
|
+
logs/
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------------- IDE
|
|
62
|
+
.idea/
|
|
63
|
+
.vscode/
|
|
64
|
+
# Muốn chia sẻ cấu hình cho cả nhóm thì bỏ dòng .vscode/ ở trên và ignore
|
|
65
|
+
# từng file: .vscode/* rồi !.vscode/settings.json, !.vscode/extensions.json
|
|
66
|
+
.fleet/
|
|
67
|
+
.zed/
|
|
68
|
+
*.sublime-project
|
|
69
|
+
*.sublime-workspace
|
|
70
|
+
*.swp
|
|
71
|
+
*.swo
|
|
72
|
+
*~
|
|
73
|
+
.\#*
|
|
74
|
+
\#*\#
|
|
75
|
+
|
|
76
|
+
# --------------------------------------------------------- Hệ điều hành
|
|
77
|
+
.DS_Store
|
|
78
|
+
.AppleDouble
|
|
79
|
+
.LSOverride
|
|
80
|
+
._*
|
|
81
|
+
Thumbs.db
|
|
82
|
+
ehthumbs.db
|
|
83
|
+
Desktop.ini
|
|
84
|
+
$RECYCLE.BIN/
|
|
85
|
+
|
|
86
|
+
# --------------------------------------------------------------- Công cụ
|
|
87
|
+
.direnv/
|
|
88
|
+
node_modules/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Thay đổi
|
|
2
|
+
|
|
3
|
+
Theo [Keep a Changelog](https://keepachangelog.com/vi/1.1.0/); phiên bản theo
|
|
4
|
+
[SemVer](https://semver.org/lang/vi/).
|
|
5
|
+
|
|
6
|
+
## [Chưa phát hành]
|
|
7
|
+
|
|
8
|
+
### Sửa
|
|
9
|
+
|
|
10
|
+
- `pym lint` không tham số trỏ vào thư mục `app` không tồn tại nên lỗi ngay;
|
|
11
|
+
mặc định đổi thành `src`, đúng thứ `pym init` sinh ra.
|
|
12
|
+
|
|
13
|
+
## [0.1.0] — 2026-08-21
|
|
14
|
+
|
|
15
|
+
Bản đầu tiên. Cần **Python 3.10 trở lên**.
|
|
16
|
+
|
|
17
|
+
### Có gì
|
|
18
|
+
|
|
19
|
+
- **Kiến trúc module kiểu NestJS**: DI container (`@injectable`, `Lazy[...]`,
|
|
20
|
+
`Scope.REQUEST`), controller dạng class (`@controller`, `@get`/`@post`/...),
|
|
21
|
+
tự quét module — thêm thư mục là có route, không phải đăng ký ở đâu cả.
|
|
22
|
+
- **Repository dùng chung cho 4 backend**: memory, SQLite, PostgreSQL, MongoDB.
|
|
23
|
+
Đổi backend không phải sửa service. Kèm circuit breaker và hạn thời gian.
|
|
24
|
+
- **WebSocket**: `@gateway` / `@subscribe`, phòng, gửi thẳng tới một người,
|
|
25
|
+
nhịp tim, giới hạn tần suất, adapter Redis để phát tin xuyên worker.
|
|
26
|
+
- **Bốn lớp hạ tầng tuỳ chọn, cùng một khuôn**: RabbitMQ, Redis, MQTT, Kafka.
|
|
27
|
+
Mặc định TẮT; thư viện chỉ được import khi bật. Tất cả tự nối lại.
|
|
28
|
+
- **CLI** `pymodular`, gõ tắt là `pym`: `init` (dựng dự án ngay trong thư mục
|
|
29
|
+
hiện tại) · `new` · `dev` / `run` · `module` · `env` · `info` · `migrate` ·
|
|
30
|
+
`test` / `lint`. `init` không bao giờ ghi đè file đã có.
|
|
31
|
+
- **Tài liệu tiếng Việt** trong `docs/`, viết theo lối tra cứu: mỗi hàm nói rõ
|
|
32
|
+
truyền gì, không truyền thì mặc định là gì.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oryza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
VENV := .venv
|
|
2
|
+
PY := $(VENV)/bin/python
|
|
3
|
+
PIP := $(PY) -m pip
|
|
4
|
+
|
|
5
|
+
# Nạp .env để make dùng chung biến với app (pydantic Settings cũng đọc file này).
|
|
6
|
+
-include .env
|
|
7
|
+
export
|
|
8
|
+
|
|
9
|
+
APP_HOST ?= 0.0.0.0
|
|
10
|
+
APP_PORT ?= 8000
|
|
11
|
+
|
|
12
|
+
.DEFAULT_GOAL := help
|
|
13
|
+
.PHONY: help dev run test lint lint-fix module gateway consumer migrate migrate-create migrate-down migrate-history migrate-sql install install-dev install-sqlite install-postgres install-mongo install-redis install-ws-redis install-rabbitmq install-mqtt install-kafka info build publish publish-test clean
|
|
14
|
+
|
|
15
|
+
help: ## Danh sách lệnh
|
|
16
|
+
@grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
|
|
17
|
+
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
|
|
18
|
+
|
|
19
|
+
# Makefile này chỉ là lối tắt cho `pym` khi làm việc TRONG repo. Người dùng thư
|
|
20
|
+
# viện không có nó, nên mọi lệnh đều phải chạy được qua `pym` — và cách chắc
|
|
21
|
+
# chắn nhất để giữ điều đó đúng là ở đây không tự viết gì cả, chỉ gọi lại.
|
|
22
|
+
PYM := $(PY) -m pymodular.cli.main
|
|
23
|
+
|
|
24
|
+
dev: ## Chạy API kèm autoreload
|
|
25
|
+
$(PYM) dev
|
|
26
|
+
|
|
27
|
+
run: ## Chạy API chế độ production (nhiều worker — cần driver DB thật)
|
|
28
|
+
$(PYM) run --workers $(or $(WORKERS),4)
|
|
29
|
+
|
|
30
|
+
module: ## Sinh khung module mới — dùng: make module name=alerts [entity=alert] [ws=1] [mq=1]
|
|
31
|
+
@test -n "$(name)" || { echo 'Thiếu tên. Dùng: make module name=alerts'; exit 1; }
|
|
32
|
+
$(PYM) module $(name) $(if $(entity),--entity $(entity),) $(if $(ws),--gateway,) $(if $(mq),--consumer,)
|
|
33
|
+
|
|
34
|
+
gateway: ## Thêm gateway WebSocket vào module đã có — dùng: make gateway name=alerts
|
|
35
|
+
@test -n "$(name)" || { echo 'Thiếu tên. Dùng: make gateway name=alerts'; exit 1; }
|
|
36
|
+
$(PYM) module $(name) --gateway-only $(if $(entity),--entity $(entity),)
|
|
37
|
+
|
|
38
|
+
consumer: ## Thêm consumer RabbitMQ vào module đã có — dùng: make consumer name=alerts
|
|
39
|
+
@test -n "$(name)" || { echo 'Thiếu tên. Dùng: make consumer name=alerts'; exit 1; }
|
|
40
|
+
$(PYM) module $(name) --consumer-only $(if $(entity),--entity $(entity),)
|
|
41
|
+
|
|
42
|
+
migrate: ## Chạy migration lên bản mới nhất
|
|
43
|
+
$(PYM) migrate up
|
|
44
|
+
|
|
45
|
+
migrate-create: ## Sinh migration từ thay đổi entity — dùng: make migrate-create m="them cot phone"
|
|
46
|
+
@test -n "$(m)" || { echo 'Thiếu tên. Dùng: make migrate-create m="mô tả thay đổi"'; exit 1; }
|
|
47
|
+
$(PYM) migrate create -m "$(m)"
|
|
48
|
+
|
|
49
|
+
migrate-down: ## Lùi lại một bản
|
|
50
|
+
$(PYM) migrate down
|
|
51
|
+
|
|
52
|
+
migrate-history: ## Lịch sử migration và bản đang áp dụng
|
|
53
|
+
@$(PYM) migrate history
|
|
54
|
+
|
|
55
|
+
migrate-sql: ## In câu SQL thay vì chạy (để DBA duyệt trước)
|
|
56
|
+
$(PYM) migrate sql
|
|
57
|
+
|
|
58
|
+
lint: ## Soi lỗi tĩnh
|
|
59
|
+
$(PYM) lint pymodular src tests
|
|
60
|
+
|
|
61
|
+
lint-fix: ## Soi và tự sửa những lỗi sửa được
|
|
62
|
+
$(PYM) lint --fix pymodular src tests
|
|
63
|
+
|
|
64
|
+
test: ## Chạy test
|
|
65
|
+
$(PYM) test
|
|
66
|
+
|
|
67
|
+
install: ## Cài khung ở chế độ chỉnh sửa được (chưa có driver database)
|
|
68
|
+
$(PIP) install -e .
|
|
69
|
+
|
|
70
|
+
install-dev: ## Cài kèm công cụ phát triển (pytest, httpx, ruff, build, twine)
|
|
71
|
+
$(PIP) install -e .
|
|
72
|
+
$(PYM) install dev
|
|
73
|
+
$(PIP) install build twine
|
|
74
|
+
|
|
75
|
+
install-sqlite: ## Cài driver SQLite + ghi biến vào .env
|
|
76
|
+
$(PYM) install sqlite
|
|
77
|
+
|
|
78
|
+
install-postgres: ## Cài driver PostgreSQL + ghi biến vào .env
|
|
79
|
+
$(PYM) install postgres
|
|
80
|
+
|
|
81
|
+
install-mongo: ## Cài driver MongoDB + ghi biến vào .env
|
|
82
|
+
$(PYM) install mongodb
|
|
83
|
+
|
|
84
|
+
install-redis: ## Cài Redis (cache, đếm, pub/sub) + ghi biến vào .env
|
|
85
|
+
$(PYM) install redis
|
|
86
|
+
|
|
87
|
+
install-ws-redis: ## Bật adapter Redis cho WebSocket nhiều worker + ghi biến vào .env
|
|
88
|
+
$(PYM) install ws-redis
|
|
89
|
+
|
|
90
|
+
install-rabbitmq: ## Cài client RabbitMQ + ghi biến vào .env
|
|
91
|
+
$(PYM) install rabbitmq
|
|
92
|
+
|
|
93
|
+
install-mqtt: ## Cài client MQTT + ghi biến vào .env
|
|
94
|
+
$(PYM) install mqtt
|
|
95
|
+
|
|
96
|
+
install-kafka: ## Cài client Kafka + ghi biến vào .env
|
|
97
|
+
$(PYM) install kafka
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
info: ## Đang nối vào đâu, thư viện nào đã cài
|
|
106
|
+
@$(PYM) info
|
|
107
|
+
|
|
108
|
+
build: ## Dựng wheel + sdist vào dist/
|
|
109
|
+
$(PYM) build
|
|
110
|
+
|
|
111
|
+
publish-test: build ## Đẩy lên TestPyPI (thử trước khi đẩy thật)
|
|
112
|
+
$(PYM) publish --test
|
|
113
|
+
|
|
114
|
+
publish: build ## Đẩy lên PyPI
|
|
115
|
+
$(PYM) publish
|
|
116
|
+
|
|
117
|
+
clean: ## Xoá cache và bản dựng
|
|
118
|
+
$(PYM) clean
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fastapi-modular
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI theo kiến trúc module kiểu NestJS: DI container, controller dạng class, WebSocket, RabbitMQ/Redis/MQTT/Kafka
|
|
5
|
+
Project-URL: Homepage, https://github.com/quanglinh2909/pymodular
|
|
6
|
+
Project-URL: Documentation, https://github.com/quanglinh2909/pymodular/tree/main/docs
|
|
7
|
+
Project-URL: Source, https://github.com/quanglinh2909/pymodular
|
|
8
|
+
Project-URL: Issues, https://github.com/quanglinh2909/pymodular/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/quanglinh2909/pymodular/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Oryza <developer@oryza.vn>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: dependency-injection,fastapi,kafka,module,mqtt,nestjs,rabbitmq,redis,websocket
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: fastapi<1.0.0,>=0.115.0
|
|
25
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.6.0
|
|
26
|
+
Requires-Dist: pydantic<3.0.0,>=2.9.0
|
|
27
|
+
Requires-Dist: structlog>=24.4.0
|
|
28
|
+
Requires-Dist: uvicorn[standard]<1.0.0,>=0.32.0
|
|
29
|
+
Provides-Extra: all
|
|
30
|
+
Requires-Dist: aio-pika<10.0.0,>=9.4.0; extra == 'all'
|
|
31
|
+
Requires-Dist: aiokafka<0.13.0,>=0.10.0; extra == 'all'
|
|
32
|
+
Requires-Dist: aiomqtt<3.0.0,>=2.0.0; extra == 'all'
|
|
33
|
+
Requires-Dist: aiosqlite>=0.20.0; extra == 'all'
|
|
34
|
+
Requires-Dist: alembic>=1.13.0; extra == 'all'
|
|
35
|
+
Requires-Dist: asyncpg>=0.29.0; extra == 'all'
|
|
36
|
+
Requires-Dist: motor<4.0.0,>=3.6.0; extra == 'all'
|
|
37
|
+
Requires-Dist: redis<7.0.0,>=5.0.0; extra == 'all'
|
|
38
|
+
Requires-Dist: sqlalchemy[asyncio]<3.0.0,>=2.0.30; extra == 'all'
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: httpx>=0.27.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest>=8.3.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
44
|
+
Provides-Extra: kafka
|
|
45
|
+
Requires-Dist: aiokafka<0.13.0,>=0.10.0; extra == 'kafka'
|
|
46
|
+
Provides-Extra: mongodb
|
|
47
|
+
Requires-Dist: motor<4.0.0,>=3.6.0; extra == 'mongodb'
|
|
48
|
+
Provides-Extra: mqtt
|
|
49
|
+
Requires-Dist: aiomqtt<3.0.0,>=2.0.0; extra == 'mqtt'
|
|
50
|
+
Provides-Extra: postgres
|
|
51
|
+
Requires-Dist: alembic>=1.13.0; extra == 'postgres'
|
|
52
|
+
Requires-Dist: asyncpg>=0.29.0; extra == 'postgres'
|
|
53
|
+
Requires-Dist: sqlalchemy[asyncio]<3.0.0,>=2.0.30; extra == 'postgres'
|
|
54
|
+
Provides-Extra: rabbitmq
|
|
55
|
+
Requires-Dist: aio-pika<10.0.0,>=9.4.0; extra == 'rabbitmq'
|
|
56
|
+
Provides-Extra: redis
|
|
57
|
+
Requires-Dist: redis<7.0.0,>=5.0.0; extra == 'redis'
|
|
58
|
+
Provides-Extra: sqlite
|
|
59
|
+
Requires-Dist: aiosqlite>=0.20.0; extra == 'sqlite'
|
|
60
|
+
Requires-Dist: alembic>=1.13.0; extra == 'sqlite'
|
|
61
|
+
Requires-Dist: sqlalchemy[asyncio]<3.0.0,>=2.0.30; extra == 'sqlite'
|
|
62
|
+
Description-Content-Type: text/markdown
|
|
63
|
+
|
|
64
|
+
# pymodular
|
|
65
|
+
|
|
66
|
+
FastAPI theo kiến trúc module kiểu NestJS: DI container, controller dạng class,
|
|
67
|
+
repository chung cho nhiều loại database, gateway WebSocket có phòng, và bốn lớp
|
|
68
|
+
hạ tầng tuỳ chọn: RabbitMQ, Redis, MQTT, Kafka.
|
|
69
|
+
|
|
70
|
+
> **In English:** pymodular brings NestJS-style modular architecture to FastAPI —
|
|
71
|
+
> a DI container, class-based controllers, auto-discovered modules, a shared
|
|
72
|
+
> repository over four databases, a WebSocket gateway with rooms, and optional
|
|
73
|
+
> RabbitMQ / Redis / MQTT / Kafka layers that stay dormant until enabled.
|
|
74
|
+
> **Documentation is in Vietnamese**; the public API is English.
|
|
75
|
+
|
|
76
|
+
## Bắt đầu
|
|
77
|
+
|
|
78
|
+
Cần **Python 3.10+**.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
python -m venv .venv && . .venv/bin/activate
|
|
82
|
+
pip install fastapi-modular
|
|
83
|
+
|
|
84
|
+
pym init # đổ file vào THƯ MỤC HIỆN TẠI, không tạo thêm cấp
|
|
85
|
+
pym dev
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Mở http://localhost:8000/docs — đã có sẵn một module `health` chạy được.
|
|
89
|
+
|
|
90
|
+
`pym init` lấy tên dự án theo **tên thư mục hiện tại**; đặt tên khác bằng
|
|
91
|
+
`pym init --name ten-khac`. Nó không ghi đè file nào đã có, nên chạy được cả
|
|
92
|
+
trong thư mục đang có sẵn code. Muốn nó tự tạo thư mục thì `pym new <tên>`.
|
|
93
|
+
|
|
94
|
+
Lõi **không kéo theo** driver database hay client hàng đợi nào. Cần cái gì thì
|
|
95
|
+
thêm cái đó:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pym install sqlite # hoặc postgres, mongodb
|
|
99
|
+
pym install rabbitmq # hoặc redis, mqtt, kafka
|
|
100
|
+
pym install all # tất cả
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`pym install` vừa cài thư viện vừa ghi biến vào `.env`. Muốn tự cài bằng pip
|
|
104
|
+
cũng được: `pip install "fastapi-modular[sqlite,rabbitmq]"`.
|
|
105
|
+
|
|
106
|
+
## Lệnh
|
|
107
|
+
|
|
108
|
+
Một chương trình, hai tên: `pymodular` (đầy đủ) và `pym` (gõ tắt). Dưới đây dùng
|
|
109
|
+
`pym` cho gọn.
|
|
110
|
+
|
|
111
|
+
Tên lệnh rút gọn được tới khi nào tiền tố còn chỉ đúng một lệnh — `pym mo alerts`
|
|
112
|
+
chạy y hệt `pym module alerts`. Nhập nhằng thì `pym` hỏi lại chứ không đoán:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
$ pym m
|
|
116
|
+
pym: lệnh 'm' chưa rõ — khớp với migrate, module. Gõ thêm vài chữ cho rõ.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Lệnh | Rút gọn | Làm gì |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `pym init [--name <tên>]` | `pym ini` | dựng dự án **trong thư mục hiện tại**, không ghi đè file nào đã có; tên dự án mặc định lấy theo tên thư mục |
|
|
122
|
+
| `pym new <tên>` | `pym n` | dựng dự án trong một thư mục mới |
|
|
123
|
+
| `pym dev` | `pym d` | chạy kèm autoreload |
|
|
124
|
+
| `pym run --workers 4` | `pym r` | chạy chế độ production |
|
|
125
|
+
| `pym module <tên>` | `pym mo` | sinh module: controller + service + dto + entity |
|
|
126
|
+
| `pym module <tên> --gateway` | | kèm gateway WebSocket (`--consumer` cho RabbitMQ) |
|
|
127
|
+
| `pym module <tên> --gateway-only` | | chỉ thêm gateway vào module **đã có** (`--consumer-only` cho RabbitMQ) |
|
|
128
|
+
| `pym module <tên> --entity <Tên>` | | đặt tên lớp entity; mặc định đoán từ tên module |
|
|
129
|
+
| `pym env <thành-phần>` | `pym e` | chỉ ghi biến cấu hình vào `.env` (không cài gì) |
|
|
130
|
+
| `pym clean` | `pym c` | xoá cache và bản dựng (không đụng `data/`) |
|
|
131
|
+
| `pym build` · `pym publish [--test]` | `pym b` · `pym p` | dựng wheel/sdist · đẩy lên PyPI |
|
|
132
|
+
| `pym info` | `pym inf` | đang nối vào đâu, thư viện nào đã cài, cảnh báo cấu hình prod |
|
|
133
|
+
| `pym migrate [up\|down\|history\|sql\|create]` | `pym mi` | Alembic |
|
|
134
|
+
| `pym test` · `pym lint [--fix]` | `pym t` · `pym l` | pytest · ruff. `pym lint` không tham số soi `src`; truyền đường dẫn để soi chỗ khác |
|
|
135
|
+
| **Thêm database** | | *cài thư viện **rồi** ghi biến vào `.env`* |
|
|
136
|
+
| `pym install sqlite` | `pym ins s` | file `.db`, không cần server |
|
|
137
|
+
| `pym install postgres` | `pym ins p` | PostgreSQL |
|
|
138
|
+
| `pym install mongodb` | `pym ins mo` | MongoDB |
|
|
139
|
+
| **Thêm hàng đợi** | | *cài thư viện **rồi** ghi biến vào `.env`* |
|
|
140
|
+
| `pym install rabbitmq` | `pym ins ra` | hàng đợi bền, thử lại + DLQ |
|
|
141
|
+
| `pym install redis` | `pym ins re` | cache, đếm nguyên tử, pub/sub |
|
|
142
|
+
| `pym install mqtt` | `pym ins mq` | thiết bị IoT |
|
|
143
|
+
| `pym install kafka` | `pym ins k` | nhật ký sự kiện đọc lại được |
|
|
144
|
+
| `pym install ws-redis` | `pym ins w` | phát tin WebSocket xuyên nhiều worker |
|
|
145
|
+
| `pym install dev` | `pym ins d` | pytest · pytest-asyncio · httpx · ruff — cần cho `pym test` / `pym lint` |
|
|
146
|
+
| `pym install all` | `pym ins a` | tất cả những thứ trên, **trừ** `dev` |
|
|
147
|
+
|
|
148
|
+
Tham số dạng danh sách cũng rút gọn theo cùng luật đó: `pym ins sq`,
|
|
149
|
+
`pym e post`, `pym mi h`. Còn giá trị bạn tự đặt thì không bị đụng tới —
|
|
150
|
+
`pym mo ins` tạo module tên đúng là `ins`.
|
|
151
|
+
|
|
152
|
+
Host và cổng lấy từ `APP_HOST` / `APP_PORT` trong `.env`, nên `pym dev` không cần
|
|
153
|
+
tham số. `pym --help` cho danh sách đầy đủ.
|
|
154
|
+
|
|
155
|
+
## Thêm module
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
pym module alerts # controller + service + dto + entities
|
|
159
|
+
pym module alerts --gateway # kèm gateway WebSocket
|
|
160
|
+
pym module alerts --consumer # kèm consumer RabbitMQ
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Route xuất hiện ngay, bảng được tạo ngay, validate chạy ngay — chỉ thân hàm là
|
|
164
|
+
chưa viết (gọi vào trả 501 kèm tên hàm). Việc của bạn: thêm trường vào entity và
|
|
165
|
+
DTO, rồi viết thân hàm trong service.
|
|
166
|
+
|
|
167
|
+
Không phải sửa file nào khác. Chi tiết: [docs/architecture.md](docs/architecture.md#thêm-module-mới).
|
|
168
|
+
|
|
169
|
+
## Chọn database
|
|
170
|
+
|
|
171
|
+
`pym install sqlite` (hoặc `postgres`, `mongodb`) làm cả hai việc: cài thư viện
|
|
172
|
+
của đúng driver đó, rồi ghi biến vào `.env`. Chỉ muốn ghi `.env` mà không cài gì
|
|
173
|
+
thì dùng `pym env sqlite`.
|
|
174
|
+
|
|
175
|
+
`pym env` ghi mỗi biến kèm giải thích, cho biết nó **bắt buộc hay tuỳ chọn** và
|
|
176
|
+
**mặc định là gì** nếu xoá dòng đi. `pym info` cho biết hiện đang nối vào đâu.
|
|
177
|
+
|
|
178
|
+
Chi tiết: [docs/database.md](docs/database.md).
|
|
179
|
+
|
|
180
|
+
## Cấu hình của riêng bạn
|
|
181
|
+
|
|
182
|
+
Kế thừa `Settings` là thêm được biến vào `.env`, không phải sửa gì trong khung:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
# src/core/config.py — pym init sinh sẵn file này
|
|
186
|
+
class AppSettings(Settings):
|
|
187
|
+
team_name: str = Field(default="", alias="APP_TEAM_NAME")
|
|
188
|
+
jwt: JwtSettings = Field(default_factory=JwtSettings, alias="APP_JWT") # -> APP_JWT__SECRET
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Service nhận `AppSettings` qua DI với gợi ý kiểu đầy đủ.
|
|
192
|
+
Chi tiết: [docs/config.md](docs/config.md).
|
|
193
|
+
|
|
194
|
+
## Điểm vào là file của bạn
|
|
195
|
+
|
|
196
|
+
`pym init` sinh ra `src/main.py` với từng bước lắp ráp bày ra hết — thêm
|
|
197
|
+
middleware, đổi CORS, gắn router bên thứ ba thì sửa thẳng ở đó:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
settings = bind_settings(AppSettings())
|
|
201
|
+
configure_logging(settings.log)
|
|
202
|
+
|
|
203
|
+
app = new_fastapi(settings, lifespan=lifespan)
|
|
204
|
+
add_middleware(app, settings) # CORS + request-id + access log
|
|
205
|
+
register_error_handlers(app, debug=settings.debug)
|
|
206
|
+
register_routes(app, prefix=settings.api_prefix) # quét src/api/
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Chưa cần sửa gì thì cả khối rút lại còn `app = create_app(AppSettings())` —
|
|
210
|
+
`create_app` chạy đúng dãy trên, không hơn.
|
|
211
|
+
|
|
212
|
+
Vòng đời cũng vậy: `src/core/lifespan.py` là của bạn, chỉ **bọc** phần hạ tầng
|
|
213
|
+
của khung lại:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
@asynccontextmanager
|
|
217
|
+
async def lifespan(app):
|
|
218
|
+
async with framework_lifespan(app): # khung mở database, hàng đợi
|
|
219
|
+
await warm_cache() # việc riêng — database đã dùng được
|
|
220
|
+
try:
|
|
221
|
+
yield
|
|
222
|
+
finally:
|
|
223
|
+
await flush_ledger() # việc riêng — database VẪN CÒN
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Đo trên log thật: `db.connected` → `app.started` → **`app.ready`** → …phục vụ… →
|
|
227
|
+
**`app.closing`** → `app.stopping` → `app.stopped`.
|
|
228
|
+
|
|
229
|
+
## Realtime (WebSocket)
|
|
230
|
+
|
|
231
|
+
Một client một kết nối, vào phòng để nhận tin theo nhóm, hoặc nhận tin gửi
|
|
232
|
+
thẳng cho riêng mình:
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
@gateway(path="/ws/alerts", guards=[WsJwt], client_rooms=True)
|
|
236
|
+
class AlertGateway:
|
|
237
|
+
@subscribe("alert.ack")
|
|
238
|
+
async def ack(self, socket: Socket, payload: AlertAck) -> dict:
|
|
239
|
+
return {"ok": True}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
pym dev
|
|
244
|
+
# ws://localhost:8000/ws/chat?client_id=an
|
|
245
|
+
|
|
246
|
+
pym module alerts --gateway-only # thêm gateway vào module đã có
|
|
247
|
+
pym install ws-redis # bắt buộc khi chạy nhiều worker
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Đẩy tin từ REST hay tác vụ nền: nhận `WebSocketServer` qua `__init__` rồi gọi
|
|
251
|
+
`to_room` / `to_user` / `to_socket`.
|
|
252
|
+
|
|
253
|
+
Hướng dẫn đầy đủ (kèm cách dùng bằng **Postman** và client **Next.js**):
|
|
254
|
+
[docs/websocket.md](docs/websocket.md).
|
|
255
|
+
|
|
256
|
+
## Hàng đợi (RabbitMQ — tuỳ chọn)
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
await self._mq.publish("events", "alert.created.hanoi", {"id": "A1"})
|
|
260
|
+
|
|
261
|
+
# Mặc định: đúng MỘT hàng đợi trên broker, hỏng là bỏ (có log).
|
|
262
|
+
@rabbitmq_subscriber("events", "alert.created", queue="alert-mailer")
|
|
263
|
+
async def gui_mail(self, payload: AlertCreated) -> None: ...
|
|
264
|
+
|
|
265
|
+
# Tự bật khi tin đáng tiền -> thêm alert-mailer.retry và alert-mailer.dlq
|
|
266
|
+
@rabbitmq_subscriber("events", "alert.created", queue="alert-mailer",
|
|
267
|
+
max_retries=3, dead_letter=True)
|
|
268
|
+
async def gui_mail(self, payload: AlertCreated) -> None: ...
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
pym install rabbitmq # cài aio-pika + ghi APP_RABBITMQ__* vào .env
|
|
273
|
+
pym module alerts --consumer # module mới kèm consumer
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Không cài, không bật thì mọi thứ chạy y như chưa từng có nó. Broker rớt thì app
|
|
277
|
+
vẫn phục vụ và tự nối lại. Chi tiết: [docs/rabbitmq.md](docs/rabbitmq.md).
|
|
278
|
+
|
|
279
|
+
## Redis, MQTT, Kafka (cũng tuỳ chọn)
|
|
280
|
+
|
|
281
|
+
Cùng một khuôn với RabbitMQ: một package riêng dưới `infrastructure/`, một nhóm
|
|
282
|
+
biến `APP_<TÊN>__*`, mặc định **tắt**, thư viện chỉ import khi bật, và luôn tự
|
|
283
|
+
nối lại.
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
pym install redis # cache, đếm, pub/sub -> docs/redis.md
|
|
287
|
+
pym install mqtt # thiết bị IoT -> docs/mqtt.md
|
|
288
|
+
pym install kafka # nhật ký sự kiện -> docs/kafka.md
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
await redis.cached("bao-cao:A", tinh_that, ttl=30) # trượt thì tính, trúng thì thôi
|
|
293
|
+
await mqtt.publish("thiet-bi/bep/den", "ON", qos=1, retain=True)
|
|
294
|
+
await kafka.publish("don-hang", don, key=don.ma_don) # cùng key = cùng thứ tự
|
|
295
|
+
|
|
296
|
+
@redis_subscriber("gia:*") # Redis: mọi worker một bản sao
|
|
297
|
+
@mqtt_subscriber("thiet-bi/+/nhiet-do", qos=1) # MQTT: + một tầng, # mọi tầng
|
|
298
|
+
@kafka_subscriber("don-hang", group="kho-van") # Kafka: mỗi nhóm một con trỏ đọc
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
| Cần gì | Dùng gì |
|
|
302
|
+
|---|---|
|
|
303
|
+
| tin không được mất, chia việc cho worker | RabbitMQ |
|
|
304
|
+
| nhanh, mọi worker nhận một bản sao, mất vài tin cũng được | Redis |
|
|
305
|
+
| thiết bị, mạng chập chờn, kết nối lâu | MQTT |
|
|
306
|
+
| đọc lại được lịch sử, nhiều nhóm đọc độc lập | Kafka |
|
|
307
|
+
|
|
308
|
+
## Vận hành
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
curl localhost:8000/api/health # liveness
|
|
312
|
+
curl localhost:8000/api/health/ready # readiness, có ping database
|
|
313
|
+
curl localhost:8000/api/metrics # số đo dạng Prometheus
|
|
314
|
+
pym migrate # chạy migration (SQL)
|
|
315
|
+
pym info # cấu hình đang dùng + cảnh báo prod
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Chi tiết: [docs/operations.md](docs/operations.md).
|
|
319
|
+
|
|
320
|
+
## Cấu trúc repo này
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
pymodular/ THƯ VIỆN — thứ được đóng gói và cài về
|
|
324
|
+
core/ DI, controller, config, WebSocket, guard, số đo
|
|
325
|
+
infrastructure/ database, rabbitmq, redis, mqtt, kafka (mỗi thứ một package)
|
|
326
|
+
cli/ init · new · module · dev · run · install · env · info
|
|
327
|
+
migrate · test · lint · clean · build · publish
|
|
328
|
+
factory.py create_app()
|
|
329
|
+
discovery.py tự quét package ứng dụng, dựng router
|
|
330
|
+
src/ ỨNG DỤNG MẪU — không nằm trong gói cài; xoá thoải mái
|
|
331
|
+
main.py điểm vào: lắp ráp app — file của bạn, không phải của khung
|
|
332
|
+
core/config.py AppSettings: kế thừa Settings để thêm biến .env của bạn
|
|
333
|
+
core/lifespan.py việc lúc khởi động / lúc tắt của riêng ứng dụng
|
|
334
|
+
api/ các module nghiệp vụ; mỗi thư mục con là một module
|
|
335
|
+
tests/ 341 test chạy không cần hạ tầng, 40 test nữa bật khi có server thật
|
|
336
|
+
docs/ tài liệu tra cứu
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`pymodular/` không import gì từ `src/`. Nó chỉ biết "có một package tên
|
|
340
|
+
`src.api`, quét nó đi" — nên dự án xếp khác cũng được, khai một lần trong
|
|
341
|
+
`src/main.py`: `register_routes(app, package="cong_ty.dich_vu")`.
|
|
342
|
+
|
|
343
|
+
## Đóng góp
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
git clone <repo> && cd pymodular
|
|
347
|
+
pip install -e ".[all,dev]"
|
|
348
|
+
pym dev # chạy ứng dụng mẫu trong src/
|
|
349
|
+
pym test
|
|
350
|
+
pym lint pymodular src tests
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Nhóm test cần hạ tầng thật chỉ chạy khi có biến môi trường tương ứng:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
docker run -d -p 6379:6379 redis:7-alpine
|
|
357
|
+
TEST_REDIS_URL=redis://localhost:6379/0 pym test
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Xem đầu mỗi file `tests/test_<tên>.py` để biết lệnh Docker và biến cần đặt.
|
|
361
|
+
|
|
362
|
+
## Giấy phép
|
|
363
|
+
|
|
364
|
+
MIT — xem [LICENSE](LICENSE).
|
|
365
|
+
|
|
366
|
+
## Tài liệu
|
|
367
|
+
|
|
368
|
+
- [docs/architecture.md](docs/architecture.md) — cấu trúc module, DI, đối chiếu NestJS
|
|
369
|
+
- [docs/config.md](docs/config.md) — Settings, thứ tự ưu tiên, thêm biến của riêng bạn
|
|
370
|
+
- [docs/database.md](docs/database.md) — memory / SQLite / PostgreSQL / MongoDB
|
|
371
|
+
- [docs/migrations.md](docs/migrations.md) — Alembic: sinh, chạy, lùi migration
|
|
372
|
+
- [docs/websocket.md](docs/websocket.md) — gateway WebSocket, phòng, Postman, Next.js
|
|
373
|
+
- [docs/rabbitmq.md](docs/rabbitmq.md) — exchange, topic, consumer nền, `.retry` / `.dlq`
|
|
374
|
+
- [docs/redis.md](docs/redis.md) — cache, đếm nguyên tử, pub/sub
|
|
375
|
+
- [docs/mqtt.md](docs/mqtt.md) — QoS, retain, luật khớp topic `+` và `#`
|
|
376
|
+
- [docs/kafka.md](docs/kafka.md) — nhóm consumer, phân vùng, `.dlt`
|
|
377
|
+
- [docs/operations.md](docs/operations.md) — guard, circuit breaker, metrics, trace
|