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.
Files changed (161) hide show
  1. fastapi_modular-0.1.0/.gitignore +88 -0
  2. fastapi_modular-0.1.0/CHANGELOG.md +32 -0
  3. fastapi_modular-0.1.0/LICENSE +21 -0
  4. fastapi_modular-0.1.0/Makefile +118 -0
  5. fastapi_modular-0.1.0/PKG-INFO +377 -0
  6. fastapi_modular-0.1.0/README.md +314 -0
  7. fastapi_modular-0.1.0/alembic.ini +39 -0
  8. fastapi_modular-0.1.0/docs/README.md +68 -0
  9. fastapi_modular-0.1.0/docs/architecture.md +271 -0
  10. fastapi_modular-0.1.0/docs/config.md +223 -0
  11. fastapi_modular-0.1.0/docs/database.md +703 -0
  12. fastapi_modular-0.1.0/docs/kafka.md +236 -0
  13. fastapi_modular-0.1.0/docs/migrations.md +113 -0
  14. fastapi_modular-0.1.0/docs/mqtt.md +225 -0
  15. fastapi_modular-0.1.0/docs/operations.md +289 -0
  16. fastapi_modular-0.1.0/docs/rabbitmq.md +558 -0
  17. fastapi_modular-0.1.0/docs/redis.md +222 -0
  18. fastapi_modular-0.1.0/docs/websocket.md +772 -0
  19. fastapi_modular-0.1.0/migrations/env.py +79 -0
  20. fastapi_modular-0.1.0/migrations/script.py.mako +27 -0
  21. fastapi_modular-0.1.0/migrations/versions/.gitkeep +0 -0
  22. fastapi_modular-0.1.0/migrations/versions/20260821_1020_tao_bang_users_va_devices.py +67 -0
  23. fastapi_modular-0.1.0/pymodular/__init__.py +74 -0
  24. fastapi_modular-0.1.0/pymodular/cli/__init__.py +0 -0
  25. fastapi_modular-0.1.0/pymodular/cli/clean.py +39 -0
  26. fastapi_modular-0.1.0/pymodular/cli/configure_env.py +569 -0
  27. fastapi_modular-0.1.0/pymodular/cli/cong_cu.py +111 -0
  28. fastapi_modular-0.1.0/pymodular/cli/info.py +62 -0
  29. fastapi_modular-0.1.0/pymodular/cli/install.py +83 -0
  30. fastapi_modular-0.1.0/pymodular/cli/main.py +247 -0
  31. fastapi_modular-0.1.0/pymodular/cli/new_module.py +492 -0
  32. fastapi_modular-0.1.0/pymodular/cli/new_project.py +471 -0
  33. fastapi_modular-0.1.0/pymodular/cli/serve.py +59 -0
  34. fastapi_modular-0.1.0/pymodular/core/__init__.py +0 -0
  35. fastapi_modular-0.1.0/pymodular/core/clock.py +15 -0
  36. fastapi_modular-0.1.0/pymodular/core/compat.py +39 -0
  37. fastapi_modular-0.1.0/pymodular/core/config.py +495 -0
  38. fastapi_modular-0.1.0/pymodular/core/container.py +354 -0
  39. fastapi_modular-0.1.0/pymodular/core/context.py +78 -0
  40. fastapi_modular-0.1.0/pymodular/core/controller.py +208 -0
  41. fastapi_modular-0.1.0/pymodular/core/error_handlers.py +272 -0
  42. fastapi_modular-0.1.0/pymodular/core/exceptions.py +104 -0
  43. fastapi_modular-0.1.0/pymodular/core/guards.py +117 -0
  44. fastapi_modular-0.1.0/pymodular/core/lifespan.py +150 -0
  45. fastapi_modular-0.1.0/pymodular/core/logging.py +88 -0
  46. fastapi_modular-0.1.0/pymodular/core/metrics.py +190 -0
  47. fastapi_modular-0.1.0/pymodular/core/schemas.py +105 -0
  48. fastapi_modular-0.1.0/pymodular/core/websocket/__init__.py +31 -0
  49. fastapi_modular-0.1.0/pymodular/core/websocket/adapter.py +192 -0
  50. fastapi_modular-0.1.0/pymodular/core/websocket/gateway.py +735 -0
  51. fastapi_modular-0.1.0/pymodular/core/websocket/namespace.py +148 -0
  52. fastapi_modular-0.1.0/pymodular/core/websocket/protocol.py +157 -0
  53. fastapi_modular-0.1.0/pymodular/core/websocket/server.py +175 -0
  54. fastapi_modular-0.1.0/pymodular/core/websocket/socket.py +241 -0
  55. fastapi_modular-0.1.0/pymodular/discovery.py +180 -0
  56. fastapi_modular-0.1.0/pymodular/factory.py +126 -0
  57. fastapi_modular-0.1.0/pymodular/infrastructure/__init__.py +1 -0
  58. fastapi_modular-0.1.0/pymodular/infrastructure/database/__init__.py +8 -0
  59. fastapi_modular-0.1.0/pymodular/infrastructure/database/base.py +228 -0
  60. fastapi_modular-0.1.0/pymodular/infrastructure/database/circuit.py +207 -0
  61. fastapi_modular-0.1.0/pymodular/infrastructure/database/factory.py +88 -0
  62. fastapi_modular-0.1.0/pymodular/infrastructure/database/memory.py +112 -0
  63. fastapi_modular-0.1.0/pymodular/infrastructure/database/mongo.py +186 -0
  64. fastapi_modular-0.1.0/pymodular/infrastructure/database/repository.py +188 -0
  65. fastapi_modular-0.1.0/pymodular/infrastructure/database/sql.py +520 -0
  66. fastapi_modular-0.1.0/pymodular/infrastructure/kafka/__init__.py +26 -0
  67. fastapi_modular-0.1.0/pymodular/infrastructure/kafka/broker.py +231 -0
  68. fastapi_modular-0.1.0/pymodular/infrastructure/kafka/consumers.py +371 -0
  69. fastapi_modular-0.1.0/pymodular/infrastructure/kafka/metrics.py +17 -0
  70. fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/__init__.py +35 -0
  71. fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/client.py +292 -0
  72. fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/consumers.py +219 -0
  73. fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/metrics.py +17 -0
  74. fastapi_modular-0.1.0/pymodular/infrastructure/mqtt/patterns.py +116 -0
  75. fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/__init__.py +33 -0
  76. fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/broker.py +616 -0
  77. fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/consumers.py +450 -0
  78. fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/metrics.py +34 -0
  79. fastapi_modular-0.1.0/pymodular/infrastructure/rabbitmq/patterns.py +64 -0
  80. fastapi_modular-0.1.0/pymodular/infrastructure/redis/__init__.py +31 -0
  81. fastapi_modular-0.1.0/pymodular/infrastructure/redis/client.py +362 -0
  82. fastapi_modular-0.1.0/pymodular/infrastructure/redis/metrics.py +20 -0
  83. fastapi_modular-0.1.0/pymodular/infrastructure/redis/pubsub.py +262 -0
  84. fastapi_modular-0.1.0/pymodular/middleware/__init__.py +0 -0
  85. fastapi_modular-0.1.0/pymodular/middleware/request_context.py +164 -0
  86. fastapi_modular-0.1.0/pymodular/py.typed +0 -0
  87. fastapi_modular-0.1.0/pyproject.toml +76 -0
  88. fastapi_modular-0.1.0/pytest.ini +5 -0
  89. fastapi_modular-0.1.0/ruff.toml +30 -0
  90. fastapi_modular-0.1.0/src/__init__.py +6 -0
  91. fastapi_modular-0.1.0/src/api/__init__.py +7 -0
  92. fastapi_modular-0.1.0/src/api/chat/__init__.py +1 -0
  93. fastapi_modular-0.1.0/src/api/chat/chat_controller.py +46 -0
  94. fastapi_modular-0.1.0/src/api/chat/chat_gateway.py +118 -0
  95. fastapi_modular-0.1.0/src/api/chat/dto/__init__.py +0 -0
  96. fastapi_modular-0.1.0/src/api/chat/dto/chat_dto.py +54 -0
  97. fastapi_modular-0.1.0/src/api/devices/__init__.py +1 -0
  98. fastapi_modular-0.1.0/src/api/devices/device_controller.py +98 -0
  99. fastapi_modular-0.1.0/src/api/devices/device_service.py +71 -0
  100. fastapi_modular-0.1.0/src/api/devices/dto/__init__.py +0 -0
  101. fastapi_modular-0.1.0/src/api/devices/dto/device_dto.py +40 -0
  102. fastapi_modular-0.1.0/src/api/devices/entities/__init__.py +0 -0
  103. fastapi_modular-0.1.0/src/api/devices/entities/device_model.py +43 -0
  104. fastapi_modular-0.1.0/src/api/health/__init__.py +1 -0
  105. fastapi_modular-0.1.0/src/api/health/health_controller.py +83 -0
  106. fastapi_modular-0.1.0/src/api/kafka_test/__init__.py +0 -0
  107. fastapi_modular-0.1.0/src/api/kafka_test/kafka_consumer.py +71 -0
  108. fastapi_modular-0.1.0/src/api/kafka_test/kafka_controller.py +37 -0
  109. fastapi_modular-0.1.0/src/api/metrics/__init__.py +1 -0
  110. fastapi_modular-0.1.0/src/api/metrics/metrics_controller.py +46 -0
  111. fastapi_modular-0.1.0/src/api/mqtt_test/__init__.py +0 -0
  112. fastapi_modular-0.1.0/src/api/mqtt_test/mqtt_controller.py +47 -0
  113. fastapi_modular-0.1.0/src/api/mqtt_test/mqtt_listener.py +45 -0
  114. fastapi_modular-0.1.0/src/api/rabbitmq_test/__init__.py +0 -0
  115. fastapi_modular-0.1.0/src/api/rabbitmq_test/rabbitmq_consumer.py +69 -0
  116. fastapi_modular-0.1.0/src/api/rabbitmq_test/rabbitmq_controller.py +49 -0
  117. fastapi_modular-0.1.0/src/api/redis_test/__init__.py +0 -0
  118. fastapi_modular-0.1.0/src/api/redis_test/redis_controller.py +59 -0
  119. fastapi_modular-0.1.0/src/api/redis_test/redis_listener.py +61 -0
  120. fastapi_modular-0.1.0/src/api/users/__init__.py +1 -0
  121. fastapi_modular-0.1.0/src/api/users/dto/__init__.py +0 -0
  122. fastapi_modular-0.1.0/src/api/users/dto/user_dto.py +65 -0
  123. fastapi_modular-0.1.0/src/api/users/entities/__init__.py +0 -0
  124. fastapi_modular-0.1.0/src/api/users/entities/user_model.py +24 -0
  125. fastapi_modular-0.1.0/src/api/users/user_controller.py +72 -0
  126. fastapi_modular-0.1.0/src/api/users/user_service.py +93 -0
  127. fastapi_modular-0.1.0/src/core/__init__.py +4 -0
  128. fastapi_modular-0.1.0/src/core/config.py +31 -0
  129. fastapi_modular-0.1.0/src/core/lifespan.py +39 -0
  130. fastapi_modular-0.1.0/src/main.py +50 -0
  131. fastapi_modular-0.1.0/tests/conftest.py +106 -0
  132. fastapi_modular-0.1.0/tests/test_circuit.py +161 -0
  133. fastapi_modular-0.1.0/tests/test_cli.py +530 -0
  134. fastapi_modular-0.1.0/tests/test_configure_env.py +187 -0
  135. fastapi_modular-0.1.0/tests/test_container.py +158 -0
  136. fastapi_modular-0.1.0/tests/test_cross_module.py +64 -0
  137. fastapi_modular-0.1.0/tests/test_database.py +96 -0
  138. fastapi_modular-0.1.0/tests/test_drivers.py +148 -0
  139. fastapi_modular-0.1.0/tests/test_guards.py +143 -0
  140. fastapi_modular-0.1.0/tests/test_ha_tang_that.py +15 -0
  141. fastapi_modular-0.1.0/tests/test_health.py +14 -0
  142. fastapi_modular-0.1.0/tests/test_kafka.py +158 -0
  143. fastapi_modular-0.1.0/tests/test_kafka_offline.py +78 -0
  144. fastapi_modular-0.1.0/tests/test_lifespan_rieng.py +105 -0
  145. fastapi_modular-0.1.0/tests/test_metrics.py +128 -0
  146. fastapi_modular-0.1.0/tests/test_module_generator.py +195 -0
  147. fastapi_modular-0.1.0/tests/test_mqtt.py +132 -0
  148. fastapi_modular-0.1.0/tests/test_mqtt_offline.py +106 -0
  149. fastapi_modular-0.1.0/tests/test_rabbitmq.py +236 -0
  150. fastapi_modular-0.1.0/tests/test_rabbitmq_offline.py +273 -0
  151. fastapi_modular-0.1.0/tests/test_redis.py +137 -0
  152. fastapi_modular-0.1.0/tests/test_redis_offline.py +69 -0
  153. fastapi_modular-0.1.0/tests/test_resilience.py +230 -0
  154. fastapi_modular-0.1.0/tests/test_schema_evolution.py +134 -0
  155. fastapi_modular-0.1.0/tests/test_schemas.py +156 -0
  156. fastapi_modular-0.1.0/tests/test_settings_mo_rong.py +130 -0
  157. fastapi_modular-0.1.0/tests/test_tuong_thich_phien_ban.py +75 -0
  158. fastapi_modular-0.1.0/tests/test_uniqueness.py +118 -0
  159. fastapi_modular-0.1.0/tests/test_users_api.py +101 -0
  160. fastapi_modular-0.1.0/tests/test_websocket.py +508 -0
  161. 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