fastapi-modular 0.1.0__py3-none-any.whl

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 (69) hide show
  1. fastapi_modular-0.1.0.dist-info/METADATA +377 -0
  2. fastapi_modular-0.1.0.dist-info/RECORD +69 -0
  3. fastapi_modular-0.1.0.dist-info/WHEEL +4 -0
  4. fastapi_modular-0.1.0.dist-info/entry_points.txt +3 -0
  5. fastapi_modular-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. pymodular/__init__.py +74 -0
  7. pymodular/cli/__init__.py +0 -0
  8. pymodular/cli/clean.py +39 -0
  9. pymodular/cli/configure_env.py +569 -0
  10. pymodular/cli/cong_cu.py +111 -0
  11. pymodular/cli/info.py +62 -0
  12. pymodular/cli/install.py +83 -0
  13. pymodular/cli/main.py +247 -0
  14. pymodular/cli/new_module.py +492 -0
  15. pymodular/cli/new_project.py +471 -0
  16. pymodular/cli/serve.py +59 -0
  17. pymodular/core/__init__.py +0 -0
  18. pymodular/core/clock.py +15 -0
  19. pymodular/core/compat.py +39 -0
  20. pymodular/core/config.py +495 -0
  21. pymodular/core/container.py +354 -0
  22. pymodular/core/context.py +78 -0
  23. pymodular/core/controller.py +208 -0
  24. pymodular/core/error_handlers.py +272 -0
  25. pymodular/core/exceptions.py +104 -0
  26. pymodular/core/guards.py +117 -0
  27. pymodular/core/lifespan.py +150 -0
  28. pymodular/core/logging.py +88 -0
  29. pymodular/core/metrics.py +190 -0
  30. pymodular/core/schemas.py +105 -0
  31. pymodular/core/websocket/__init__.py +31 -0
  32. pymodular/core/websocket/adapter.py +192 -0
  33. pymodular/core/websocket/gateway.py +735 -0
  34. pymodular/core/websocket/namespace.py +148 -0
  35. pymodular/core/websocket/protocol.py +157 -0
  36. pymodular/core/websocket/server.py +175 -0
  37. pymodular/core/websocket/socket.py +241 -0
  38. pymodular/discovery.py +180 -0
  39. pymodular/factory.py +126 -0
  40. pymodular/infrastructure/__init__.py +1 -0
  41. pymodular/infrastructure/database/__init__.py +8 -0
  42. pymodular/infrastructure/database/base.py +228 -0
  43. pymodular/infrastructure/database/circuit.py +207 -0
  44. pymodular/infrastructure/database/factory.py +88 -0
  45. pymodular/infrastructure/database/memory.py +112 -0
  46. pymodular/infrastructure/database/mongo.py +186 -0
  47. pymodular/infrastructure/database/repository.py +188 -0
  48. pymodular/infrastructure/database/sql.py +520 -0
  49. pymodular/infrastructure/kafka/__init__.py +26 -0
  50. pymodular/infrastructure/kafka/broker.py +231 -0
  51. pymodular/infrastructure/kafka/consumers.py +371 -0
  52. pymodular/infrastructure/kafka/metrics.py +17 -0
  53. pymodular/infrastructure/mqtt/__init__.py +35 -0
  54. pymodular/infrastructure/mqtt/client.py +292 -0
  55. pymodular/infrastructure/mqtt/consumers.py +219 -0
  56. pymodular/infrastructure/mqtt/metrics.py +17 -0
  57. pymodular/infrastructure/mqtt/patterns.py +116 -0
  58. pymodular/infrastructure/rabbitmq/__init__.py +33 -0
  59. pymodular/infrastructure/rabbitmq/broker.py +616 -0
  60. pymodular/infrastructure/rabbitmq/consumers.py +450 -0
  61. pymodular/infrastructure/rabbitmq/metrics.py +34 -0
  62. pymodular/infrastructure/rabbitmq/patterns.py +64 -0
  63. pymodular/infrastructure/redis/__init__.py +31 -0
  64. pymodular/infrastructure/redis/client.py +362 -0
  65. pymodular/infrastructure/redis/metrics.py +20 -0
  66. pymodular/infrastructure/redis/pubsub.py +262 -0
  67. pymodular/middleware/__init__.py +0 -0
  68. pymodular/middleware/request_context.py +164 -0
  69. pymodular/py.typed +0 -0
@@ -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
@@ -0,0 +1,69 @@
1
+ pymodular/__init__.py,sha256=fkq33egtR-FbqMJZD7O9L9f14OGlZLY6U17edBdHOO4,1959
2
+ pymodular/discovery.py,sha256=GZYNOd2x1slzPSI35cRyvMHm8mIQyvlhKDHORD7fs8w,6627
3
+ pymodular/factory.py,sha256=rSVx2eLw52YD4VSvtu1J-gVBOlfStMU-zJ_tUI0r6lk,4931
4
+ pymodular/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ pymodular/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ pymodular/cli/clean.py,sha256=-VtxXSfB4N1l5m56mgmGc6fYVgOB5MERGLRWA-aKgnk,1517
7
+ pymodular/cli/configure_env.py,sha256=oRhZHKAMgpeae8hHgUxbN_n66tSqUl6sT-wZbVxUfX8,24079
8
+ pymodular/cli/cong_cu.py,sha256=vjTA1D9Xi_7wMTyb-6mYHMxUSqfjVSNTIAdixv_oJtU,3602
9
+ pymodular/cli/info.py,sha256=6Zz4-mk2eupN9xVJdDGfIIVsm8qD3ku2IFeFWxM4PsY,2445
10
+ pymodular/cli/install.py,sha256=0riOfY4eAdGNpWwL_KCR4reOj4vZPs3Wbzrgycshdv0,3083
11
+ pymodular/cli/main.py,sha256=cxk196n4sQQRk_HFKeOb0Izl_jEY32e6jqnLf6gHCCY,10152
12
+ pymodular/cli/new_module.py,sha256=w1ieN-DVGs0OC-tttsMXVy9tjreATWlcIWRfy1bdE3c,19098
13
+ pymodular/cli/new_project.py,sha256=VX2SbmZaLCUeLP7BBAOH10ylfhTddEX0Kkl2Lm8pqbM,15256
14
+ pymodular/cli/serve.py,sha256=UnMWgyMilj8v2TC387mCwh9zgxGx92qrNm6mex9MDEY,2054
15
+ pymodular/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ pymodular/core/clock.py,sha256=e085ol6MF5ni9Fsr_mexNq3ZZ_wqE9sDbt2JK8FUzIg,392
17
+ pymodular/core/compat.py,sha256=JyP7fmNvJEqBR7npG4K8xY3wpuedjh2zgqQ-YBpUjaw,1578
18
+ pymodular/core/config.py,sha256=TjiPlBOojl9vrLlqYCvROqNOYtLHwNMnIzQYY7a43XA,21855
19
+ pymodular/core/container.py,sha256=BP8bjkQmcOYSxnjA1aeWD2u7CzL52zRcOhMfzEAMQFY,14253
20
+ pymodular/core/context.py,sha256=XZM7JyA9jyaSeA3m2dPTkjkBoHb02oO_Jo4Xw8JlqUM,2141
21
+ pymodular/core/controller.py,sha256=AqVEqlOMLK7SKrnf_sC1Y3cyqPYcXyGAAcTdOGq--to,7727
22
+ pymodular/core/error_handlers.py,sha256=iPOofUT3VAluaMRjgC4nZ5QTcSLoESjWEJTIWEIpESY,11148
23
+ pymodular/core/exceptions.py,sha256=2bKxkGFACjmVhQces9VgqlvfmqQ7vySHPrMORLK3pX0,2745
24
+ pymodular/core/guards.py,sha256=efM_SsTc2vdvewSXJ3t5cg8ZOJp3PKBPAyWk36LiB6I,4747
25
+ pymodular/core/lifespan.py,sha256=XX7P-5L8o91UTrJDWqqeZjMHOklSALTUqN26G8wtmIg,6003
26
+ pymodular/core/logging.py,sha256=W6bRZZl-i14-O8Uv-b_8ThvgIupHfS1FOmTq_ANCm7w,3009
27
+ pymodular/core/metrics.py,sha256=sDnqfPwCCzTbEjwZIma3tfHwo36m225PWDmzxRUBe9E,7202
28
+ pymodular/core/schemas.py,sha256=QRlmzEaFnub5DxhSz5OWAalu9fednbuxIolmEIS4jlQ,4211
29
+ pymodular/core/websocket/__init__.py,sha256=2LC-ZMJG1OScnJkYdzU42xbi6jXyPZG1ne0D9GPDEfA,1005
30
+ pymodular/core/websocket/adapter.py,sha256=66q_W3GQsmolpg6hnMms8m6fMw_JP8vHpnvW39hnR5g,7139
31
+ pymodular/core/websocket/gateway.py,sha256=OWye428-z8-tqIA6Ux7M_iSYRyhoFtkJdsToD0xz8Cw,28881
32
+ pymodular/core/websocket/namespace.py,sha256=tgX6Xt2tqv3hUVHtAHVMfJQfR51H7ddaq10O9yoX53k,5292
33
+ pymodular/core/websocket/protocol.py,sha256=mtZiB3v00pgXKvjuFL5__xVRxTnwV6rNn0hG7XstznE,5768
34
+ pymodular/core/websocket/server.py,sha256=nonHhYcAOBCktWnk-JxUSPkfKnYKflHF48Pp6Cb7vU4,6734
35
+ pymodular/core/websocket/socket.py,sha256=vW44UqDHzPAH7jQDJ_lY_odatJZAfjsSSCZHbyNhznM,9405
36
+ pymodular/infrastructure/__init__.py,sha256=sCB7r_YNMxrpALUH0pRWYv0dN3ItuJHCxoQjBTMjjLY,110
37
+ pymodular/infrastructure/database/__init__.py,sha256=RubEeUrWC1xt1xwMThgDQQ276f39LNKQHs50Vf7-T9M,293
38
+ pymodular/infrastructure/database/base.py,sha256=izj68h0CC0NGv-8vCrVqkKvI7r_zBHJB59-Yu8gBZ_k,9275
39
+ pymodular/infrastructure/database/circuit.py,sha256=odzERJsKjxS1DOPoofm7jbiCNyiunUiv_wuklKQe3co,8051
40
+ pymodular/infrastructure/database/factory.py,sha256=ht6UwkkR6s2eCPkBJJZo6kdloS0tpgyiIAr5qa1gLoc,3591
41
+ pymodular/infrastructure/database/memory.py,sha256=hN-zEuoDUH6PgRW5WGalCpFh1bvDLpVUt96jbGok_n8,3712
42
+ pymodular/infrastructure/database/mongo.py,sha256=0J3A1TP17RlC_8wOQ6ubYkl0_SlEBSOxzpX4ITxoXEc,6884
43
+ pymodular/infrastructure/database/repository.py,sha256=Bn8EGhO_s2essLuz8WXdeXQSUP8AZQ7RDlF15QcJem0,7214
44
+ pymodular/infrastructure/database/sql.py,sha256=Z-j5zocrSiS80iXbliROcUJjowKmY2-EhHBYgiPRzrw,20292
45
+ pymodular/infrastructure/kafka/__init__.py,sha256=eqSfsRNyOIz3sdvGPu9f5eT4G7JkKab9V2qcuEfxO9c,834
46
+ pymodular/infrastructure/kafka/broker.py,sha256=7wH86A5Bl_bVrDtwG4J0k7D4ZgA4oSoR85daA4TI12A,9373
47
+ pymodular/infrastructure/kafka/consumers.py,sha256=0ljwjc7igLqtcuWEgJkIM1MvCRmwbfFo0cuIovf2b1w,14897
48
+ pymodular/infrastructure/kafka/metrics.py,sha256=nijdJ9DqhgFjQt0d0pC2fjvOzGrS36AjSKqwzAbeq5U,731
49
+ pymodular/infrastructure/mqtt/__init__.py,sha256=zFZDVJCCYKMSTH6I3PaJwOpoGCyP1sEjCPuKU01QsKI,924
50
+ pymodular/infrastructure/mqtt/client.py,sha256=pwFif7qhnD_QBLTqRSUhpiGFe0k5nYus9mcX1X1kCCE,12044
51
+ pymodular/infrastructure/mqtt/consumers.py,sha256=SWwN4yFBJs6oCDvccb5IMNBBhHZ50QHZnFC6z2Guw34,8661
52
+ pymodular/infrastructure/mqtt/metrics.py,sha256=Kvu77x5klqKJrc0TmjuLoLZVp2_nqkF_RhtpnHzWhwU,692
53
+ pymodular/infrastructure/mqtt/patterns.py,sha256=saiM7-JEi85Jk_Q7ASWKdW8xpWIMU57TG6TUsV66m7w,4733
54
+ pymodular/infrastructure/rabbitmq/__init__.py,sha256=PEdKaF2-x3HABpiffBz0_UADB98PliXTU2CLJdqSZYc,1252
55
+ pymodular/infrastructure/rabbitmq/broker.py,sha256=_27rj0uBGe2bJ0XSnq-jAkGAAfAxiO841leqWZbdkwI,27608
56
+ pymodular/infrastructure/rabbitmq/consumers.py,sha256=HkkO-Br2G2pbpOm3DmCnzYwjrt_4zE_IYyE6LAdlwNo,19521
57
+ pymodular/infrastructure/rabbitmq/metrics.py,sha256=5mkCXLkA94piSn-okYqVcYiw7DKFHcvmEwelR-lKmPE,1447
58
+ pymodular/infrastructure/rabbitmq/patterns.py,sha256=u6BcS4zNIAnBTmOecxC9ZMzcOREdPyy4mvHzO1Kb4oY,2646
59
+ pymodular/infrastructure/redis/__init__.py,sha256=4ob8B-e-uA3NseNcu58l190ctuvGc0--mA3VyYAWgKs,1028
60
+ pymodular/infrastructure/redis/client.py,sha256=4rIjJ-QLl1RG-6_-spauw89VHwD4uWAhL84CYURK880,14500
61
+ pymodular/infrastructure/redis/metrics.py,sha256=JW-FglJf--jqvScsigDAq14wlr3zPdNa6KldOr_2Bg0,880
62
+ pymodular/infrastructure/redis/pubsub.py,sha256=ZAIKryWx3qbNZ4PDg70SRPnMXkIMTSmrnG6IUS_-2MY,10540
63
+ pymodular/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
+ pymodular/middleware/request_context.py,sha256=Xug7pOdaGS8I6ztf9K4VzOcwL8AEaK7Z4hC7KJTlhCQ,6243
65
+ fastapi_modular-0.1.0.dist-info/METADATA,sha256=VlnFkyPEZtTohiCGD_kX3Zrio_9UWhoaHEKwvs6BXbE,17075
66
+ fastapi_modular-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
67
+ fastapi_modular-0.1.0.dist-info/entry_points.txt,sha256=QpF54ARgRQ58MyF0v8LW0urajkMjZ03Ex6I7JbCdwUA,84
68
+ fastapi_modular-0.1.0.dist-info/licenses/LICENSE,sha256=2GmLoFfZHwTkURvEu7fEPxaciitDK-1elAhNfl_A9Mo,1062
69
+ fastapi_modular-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ pym = pymodular.cli.main:main
3
+ pymodular = pymodular.cli.main:main
@@ -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.
pymodular/__init__.py ADDED
@@ -0,0 +1,74 @@
1
+ """pymodular — FastAPI theo kiến trúc module kiểu NestJS.
2
+
3
+ from pymodular import create_app
4
+ app = create_app()
5
+
6
+ Những thứ hay dùng nhất được xuất thẳng ở đây; phần còn lại nằm trong
7
+ `pymodular.core`, `pymodular.infrastructure`.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pymodular.core.config import Settings, get_settings, use_settings
13
+ from pymodular.core.container import Lazy, Scope, container, entity, injectable
14
+ from pymodular.core.controller import controller, delete, get, patch, post, put
15
+ from pymodular.core.error_handlers import register_error_handlers
16
+ from pymodular.core.exceptions import (
17
+ AppError,
18
+ BadRequestError,
19
+ ComponentNotEnabledError,
20
+ ConflictError,
21
+ ForbiddenError,
22
+ NotFoundError,
23
+ ServiceUnavailableError,
24
+ UnauthorizedError,
25
+ )
26
+ from pymodular.core.lifespan import lifespan
27
+ from pymodular.core.logging import configure_logging, get_logger
28
+ from pymodular.core.schemas import Page
29
+ from pymodular.core.websocket import Socket, WebSocketServer, gateway, subscribe
30
+ from pymodular.discovery import DEFAULT_PACKAGE, register_routes
31
+ from pymodular.factory import add_middleware, bind_settings, create_app, new_fastapi
32
+
33
+ __version__ = "0.1.0"
34
+
35
+ __all__ = [
36
+ "DEFAULT_PACKAGE",
37
+ "AppError",
38
+ "BadRequestError",
39
+ "ComponentNotEnabledError",
40
+ "ConflictError",
41
+ "ForbiddenError",
42
+ "Lazy",
43
+ "NotFoundError",
44
+ "Page",
45
+ "Scope",
46
+ "ServiceUnavailableError",
47
+ "Settings",
48
+ "Socket",
49
+ "UnauthorizedError",
50
+ "WebSocketServer",
51
+ "__version__",
52
+ "add_middleware",
53
+ "bind_settings",
54
+ "configure_logging",
55
+ "container",
56
+ "controller",
57
+ "create_app",
58
+ "delete",
59
+ "entity",
60
+ "gateway",
61
+ "get",
62
+ "get_logger",
63
+ "get_settings",
64
+ "injectable",
65
+ "lifespan",
66
+ "new_fastapi",
67
+ "patch",
68
+ "post",
69
+ "put",
70
+ "register_error_handlers",
71
+ "register_routes",
72
+ "subscribe",
73
+ "use_settings",
74
+ ]
File without changes
pymodular/cli/clean.py ADDED
@@ -0,0 +1,39 @@
1
+ """`pym clean` — xoá cache và bản dựng, không đụng vào code hay dữ liệu."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ # Cố ý KHÔNG có `data/` hay `*.db`: đó là dữ liệu, không phải rác. Xoá dữ liệu
9
+ # phải là việc người ta gõ tay, không bao giờ là tác dụng phụ của "dọn cache".
10
+ THU_MUC = ("__pycache__", ".pytest_cache", ".ruff_cache", ".mypy_cache",
11
+ ".pytype", "htmlcov", "build", "dist", ".eggs")
12
+ DUOI_FILE = ("*.pyc", "*.pyo", ".coverage", "coverage.xml")
13
+
14
+
15
+ def clean(root: Path | None = None) -> int:
16
+ goc = (root or Path(".")).resolve()
17
+ xoa: list[str] = []
18
+
19
+ for ten in THU_MUC:
20
+ for d in goc.rglob(ten):
21
+ if d.is_dir() and ".venv" not in d.parts:
22
+ shutil.rmtree(d, ignore_errors=True)
23
+ xoa.append(str(d.relative_to(goc)))
24
+ for d in goc.rglob("*.egg-info"):
25
+ if ".venv" not in d.parts:
26
+ shutil.rmtree(d, ignore_errors=True) if d.is_dir() else d.unlink()
27
+ xoa.append(str(d.relative_to(goc)))
28
+ for mau in DUOI_FILE:
29
+ for f in goc.rglob(mau):
30
+ if ".venv" not in f.parts and f.is_file():
31
+ f.unlink()
32
+ xoa.append(str(f.relative_to(goc)))
33
+
34
+ print(f"Đã xoá {len(xoa)} thứ." if xoa else "Không có gì để xoá.")
35
+ for d in sorted(xoa)[:20]:
36
+ print(f" {d}")
37
+ if len(xoa) > 20:
38
+ print(f" ... và {len(xoa) - 20} thứ nữa")
39
+ return 0