fastapi-modular 0.2.1__tar.gz → 0.3.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 (210) hide show
  1. fastapi_modular-0.3.0/CHANGELOG.md +212 -0
  2. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/PKG-INFO +164 -12
  3. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/README.md +163 -11
  4. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/README.vi.md +159 -10
  5. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/README.md +7 -3
  6. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/architecture.md +49 -6
  7. fastapi_modular-0.3.0/docs/background.md +872 -0
  8. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/config.md +17 -1
  9. fastapi_modular-0.3.0/docs/database.md +2307 -0
  10. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/kafka.md +75 -29
  11. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/migrations.md +13 -1
  12. fastapi_modular-0.3.0/docs/mongodb.md +540 -0
  13. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/mqtt.md +59 -27
  14. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/operations.md +13 -0
  15. fastapi_modular-0.3.0/docs/providers.md +471 -0
  16. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/rabbitmq.md +338 -14
  17. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/redis.md +55 -18
  18. fastapi_modular-0.3.0/docs/rpc.md +487 -0
  19. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/__init__.py +32 -1
  20. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/cli/clean.py +16 -16
  21. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/cli/configure_env.py +152 -129
  22. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/cli/info.py +21 -21
  23. fastapi_modular-0.3.0/fastapi_modular/cli/install.py +110 -0
  24. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/cli/main.py +87 -74
  25. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/cli/new_module.py +11 -9
  26. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/cli/new_project.py +81 -50
  27. fastapi_modular-0.3.0/fastapi_modular/cli/new_provider.py +220 -0
  28. fastapi_modular-0.3.0/fastapi_modular/cli/requirements.py +162 -0
  29. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/cli/serve.py +10 -10
  30. fastapi_modular-0.2.1/fastapi_modular/cli/cong_cu.py → fastapi_modular-0.3.0/fastapi_modular/cli/tools.py +34 -34
  31. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/config.py +166 -12
  32. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/container.py +50 -5
  33. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/controller.py +27 -2
  34. fastapi_modular-0.3.0/fastapi_modular/core/cron.py +227 -0
  35. fastapi_modular-0.3.0/fastapi_modular/core/events.py +518 -0
  36. fastapi_modular-0.3.0/fastapi_modular/core/jobs.py +377 -0
  37. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/lifespan.py +83 -4
  38. fastapi_modular-0.3.0/fastapi_modular/core/locks.py +215 -0
  39. fastapi_modular-0.3.0/fastapi_modular/core/providers.py +300 -0
  40. fastapi_modular-0.3.0/fastapi_modular/core/rpc.py +445 -0
  41. fastapi_modular-0.3.0/fastapi_modular/core/scheduler.py +525 -0
  42. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/websocket/gateway.py +2 -2
  43. fastapi_modular-0.3.0/fastapi_modular/core/workers.py +791 -0
  44. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/discovery.py +3 -3
  45. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/factory.py +19 -6
  46. fastapi_modular-0.3.0/fastapi_modular/infrastructure/database/__init__.py +53 -0
  47. fastapi_modular-0.3.0/fastapi_modular/infrastructure/database/base.py +469 -0
  48. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/database/circuit.py +10 -0
  49. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/database/factory.py +5 -2
  50. fastapi_modular-0.3.0/fastapi_modular/infrastructure/database/memory.py +416 -0
  51. fastapi_modular-0.3.0/fastapi_modular/infrastructure/database/mongo.py +468 -0
  52. fastapi_modular-0.3.0/fastapi_modular/infrastructure/database/query.py +1843 -0
  53. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/database/repository.py +64 -2
  54. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/database/sql.py +423 -14
  55. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/kafka/__init__.py +8 -0
  56. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/kafka/broker.py +165 -3
  57. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/kafka/consumers.py +19 -19
  58. fastapi_modular-0.3.0/fastapi_modular/infrastructure/kafka/responders.py +372 -0
  59. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/mqtt/__init__.py +8 -0
  60. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/mqtt/client.py +124 -28
  61. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/mqtt/consumers.py +9 -9
  62. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/mqtt/patterns.py +31 -31
  63. fastapi_modular-0.3.0/fastapi_modular/infrastructure/mqtt/responders.py +236 -0
  64. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/rabbitmq/__init__.py +8 -0
  65. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/rabbitmq/broker.py +408 -21
  66. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/rabbitmq/consumers.py +78 -25
  67. fastapi_modular-0.3.0/fastapi_modular/infrastructure/rabbitmq/patterns.py +159 -0
  68. fastapi_modular-0.3.0/fastapi_modular/infrastructure/rabbitmq/responders.py +398 -0
  69. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/redis/__init__.py +8 -0
  70. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/redis/client.py +195 -40
  71. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/redis/pubsub.py +23 -23
  72. fastapi_modular-0.3.0/fastapi_modular/infrastructure/redis/responders.py +280 -0
  73. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/devices/entities/device_model.py +2 -2
  74. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/health/health_controller.py +2 -2
  75. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/kafka_test/kafka_consumer.py +9 -9
  76. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/kafka_test/kafka_controller.py +2 -2
  77. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/mqtt_test/mqtt_controller.py +2 -2
  78. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/mqtt_test/mqtt_listener.py +5 -5
  79. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/rabbitmq_test/rabbitmq_consumer.py +5 -5
  80. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/rabbitmq_test/rabbitmq_controller.py +3 -3
  81. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/redis_test/redis_controller.py +11 -11
  82. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/redis_test/redis_listener.py +7 -7
  83. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/users/entities/user_model.py +2 -2
  84. fastapi_modular-0.3.0/tests/fixtures/cron_vectors.json +1802 -0
  85. fastapi_modular-0.3.0/tests/fixtures/nestjs_patterns.json +2605 -0
  86. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_cli.py +171 -86
  87. fastapi_modular-0.3.0/tests/test_configure_env.py +241 -0
  88. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_container.py +3 -3
  89. fastapi_modular-0.3.0/tests/test_controller_dong_bo.py +266 -0
  90. fastapi_modular-0.3.0/tests/test_cron.py +130 -0
  91. fastapi_modular-0.3.0/tests/test_enum_parity.py +121 -0
  92. fastapi_modular-0.3.0/tests/test_events.py +357 -0
  93. fastapi_modular-0.3.0/tests/test_foreign_keys.py +492 -0
  94. fastapi_modular-0.3.0/tests/test_injection.py +171 -0
  95. fastapi_modular-0.3.0/tests/test_jobs.py +265 -0
  96. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_kafka.py +50 -12
  97. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_kafka_offline.py +72 -3
  98. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_lifespan_rieng.py +3 -3
  99. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_metrics.py +2 -2
  100. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_module_generator.py +1 -0
  101. fastapi_modular-0.3.0/tests/test_mongo_query.py +277 -0
  102. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_mqtt.py +76 -14
  103. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_mqtt_offline.py +50 -4
  104. fastapi_modular-0.3.0/tests/test_providers.py +482 -0
  105. fastapi_modular-0.3.0/tests/test_query_builder.py +1628 -0
  106. fastapi_modular-0.3.0/tests/test_rabbitmq.py +598 -0
  107. fastapi_modular-0.3.0/tests/test_rabbitmq_offline.py +715 -0
  108. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_redis.py +83 -14
  109. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_redis_offline.py +59 -2
  110. fastapi_modular-0.3.0/tests/test_requirements_ghi_nho.py +259 -0
  111. fastapi_modular-0.3.0/tests/test_rpc.py +208 -0
  112. fastapi_modular-0.3.0/tests/test_scheduler.py +316 -0
  113. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_schema_evolution.py +24 -4
  114. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_settings_mo_rong.py +6 -6
  115. fastapi_modular-0.3.0/tests/test_sqlite_pragmas.py +302 -0
  116. fastapi_modular-0.3.0/tests/test_transaction.py +279 -0
  117. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_tuong_thich_phien_ban.py +5 -5
  118. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_uniqueness.py +2 -2
  119. fastapi_modular-0.3.0/tests/test_workers.py +534 -0
  120. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_ws_adapter.py +9 -9
  121. fastapi_modular-0.2.1/CHANGELOG.md +0 -76
  122. fastapi_modular-0.2.1/docs/database.md +0 -703
  123. fastapi_modular-0.2.1/fastapi_modular/cli/install.py +0 -83
  124. fastapi_modular-0.2.1/fastapi_modular/infrastructure/database/__init__.py +0 -8
  125. fastapi_modular-0.2.1/fastapi_modular/infrastructure/database/base.py +0 -228
  126. fastapi_modular-0.2.1/fastapi_modular/infrastructure/database/memory.py +0 -112
  127. fastapi_modular-0.2.1/fastapi_modular/infrastructure/database/mongo.py +0 -186
  128. fastapi_modular-0.2.1/fastapi_modular/infrastructure/rabbitmq/patterns.py +0 -64
  129. fastapi_modular-0.2.1/tests/test_configure_env.py +0 -187
  130. fastapi_modular-0.2.1/tests/test_rabbitmq.py +0 -236
  131. fastapi_modular-0.2.1/tests/test_rabbitmq_offline.py +0 -273
  132. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/.gitignore +0 -0
  133. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/LICENSE +0 -0
  134. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/Makefile +0 -0
  135. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/alembic.ini +0 -0
  136. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/docs/websocket.md +0 -0
  137. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/cli/__init__.py +0 -0
  138. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/__init__.py +0 -0
  139. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/clock.py +0 -0
  140. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/compat.py +0 -0
  141. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/context.py +0 -0
  142. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/error_handlers.py +0 -0
  143. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/exceptions.py +0 -0
  144. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/guards.py +0 -0
  145. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/logging.py +0 -0
  146. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/metrics.py +0 -0
  147. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/schemas.py +0 -0
  148. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/websocket/__init__.py +0 -0
  149. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/websocket/adapter.py +0 -0
  150. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/websocket/namespace.py +0 -0
  151. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/websocket/protocol.py +0 -0
  152. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/websocket/server.py +0 -0
  153. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/core/websocket/socket.py +0 -0
  154. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/__init__.py +0 -0
  155. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/kafka/metrics.py +0 -0
  156. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/mqtt/metrics.py +0 -0
  157. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/rabbitmq/metrics.py +0 -0
  158. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/infrastructure/redis/metrics.py +0 -0
  159. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/middleware/__init__.py +0 -0
  160. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/middleware/request_context.py +0 -0
  161. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/fastapi_modular/py.typed +0 -0
  162. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/migrations/env.py +0 -0
  163. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/migrations/script.py.mako +0 -0
  164. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/migrations/versions/.gitkeep +0 -0
  165. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/migrations/versions/20260821_1020_tao_bang_users_va_devices.py +0 -0
  166. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/pyproject.toml +0 -0
  167. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/pytest.ini +0 -0
  168. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/ruff.toml +0 -0
  169. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/__init__.py +0 -0
  170. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/__init__.py +0 -0
  171. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/chat/__init__.py +0 -0
  172. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/chat/chat_controller.py +0 -0
  173. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/chat/chat_gateway.py +0 -0
  174. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/chat/dto/__init__.py +0 -0
  175. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/chat/dto/chat_dto.py +0 -0
  176. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/devices/__init__.py +0 -0
  177. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/devices/device_controller.py +0 -0
  178. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/devices/device_service.py +0 -0
  179. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/devices/dto/__init__.py +0 -0
  180. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/devices/dto/device_dto.py +0 -0
  181. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/devices/entities/__init__.py +0 -0
  182. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/health/__init__.py +0 -0
  183. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/kafka_test/__init__.py +0 -0
  184. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/metrics/__init__.py +0 -0
  185. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/metrics/metrics_controller.py +0 -0
  186. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/mqtt_test/__init__.py +0 -0
  187. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/rabbitmq_test/__init__.py +0 -0
  188. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/redis_test/__init__.py +0 -0
  189. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/users/__init__.py +0 -0
  190. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/users/dto/__init__.py +0 -0
  191. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/users/dto/user_dto.py +0 -0
  192. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/users/entities/__init__.py +0 -0
  193. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/users/user_controller.py +0 -0
  194. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/api/users/user_service.py +0 -0
  195. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/core/__init__.py +0 -0
  196. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/core/config.py +0 -0
  197. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/core/lifespan.py +0 -0
  198. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/src/main.py +0 -0
  199. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/conftest.py +0 -0
  200. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_circuit.py +0 -0
  201. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_cross_module.py +0 -0
  202. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_database.py +0 -0
  203. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_drivers.py +0 -0
  204. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_guards.py +0 -0
  205. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_ha_tang_that.py +0 -0
  206. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_health.py +0 -0
  207. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_resilience.py +0 -0
  208. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_schemas.py +0 -0
  209. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_users_api.py +0 -0
  210. {fastapi_modular-0.2.1 → fastapi_modular-0.3.0}/tests/test_websocket.py +0 -0
@@ -0,0 +1,212 @@
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
+ ## [0.3.0] — 2026-08-27
9
+
10
+ Bản này thêm **query builder** (JOIN, khoá ngoại, transaction, dữ liệu lồng
11
+ nhau), **việc chạy nền** (`@worker`, `@interval`, `@cron`, `@job`, `@on_event`),
12
+ **RPC gửi-rồi-chờ-trả-lời** cho cả bốn hạ tầng, và **provider cắm được**.
13
+
14
+ Nâng cấp từ 0.2.x cần sửa ba chỗ, xem mục "Thay đổi phá vỡ" ở cuối.
15
+
16
+ ### Thêm
17
+
18
+ - **Provider cắm được** — chọn bản hiện thực bằng TÊN lúc chạy, thứ mà container
19
+ (tra theo kiểu) không làm được: cổng thanh toán lấy từ cột trong đơn hàng, nhà
20
+ mạng SMS lấy từ cấu hình, hãng camera lấy từ bản ghi thiết bị.
21
+
22
+ Dùng đúng khuôn `Repository[User]` — service khai **năng lực** nó cần:
23
+
24
+ ```python
25
+ def __init__(self, payments: Providers[PaymentGateway]) -> None: ...
26
+
27
+ cong = self._payments.get(don.cong_thanh_toan) # -> PaymentGateway
28
+ ```
29
+
30
+ Thêm bản hiện thực mới = thả một file mang `@provider("tên")` vào
31
+ `src/providers/<họ>/`. Không sửa service, không sửa `main.py`.
32
+
33
+ `get()` trả 404 nếu không có tên, **501** nếu có tên nhưng thiếu năng lực —
34
+ Hik không mở được cửa thì đó không phải bug của server. `names()` chỉ liệt kê
35
+ provider làm được việc của sổ đó. Xem `docs/providers.md`, trong đó có mục
36
+ "Viết năng lực cho dễ bảo trì": khi nào tách `capabilities.py` thành package,
37
+ và ba nguyên tắc đặt tên / chia nhỏ interface.
38
+
39
+ - **`fam provider <họ> <tên>`** — sinh khung. Lần đầu dựng cả họ; lần sau đọc
40
+ `capabilities.py` rồi sinh sẵn stub đúng chữ ký các method cần viết.
41
+
42
+ - **`container.build(cls, key=..., scope=...)`** — dựng một lớp có nối phụ thuộc
43
+ mà KHÔNG đăng ký vào sổ toàn cục. Cần cho provider: sổ toàn cục tra theo tên
44
+ class, trong khi hai họ có quyền cùng có một `OryzaProvider`.
45
+
46
+ ### Thay đổi
47
+
48
+ - `fam p` giờ **nhập nhằng** giữa `publish` và `provider` nên `fam` hỏi lại. Viết
49
+ tắt mới: `fam pu` cho publish, `fam pr` cho provider.
50
+ - `create_app()` và `src/main.py` sinh sẵn gọi `register_providers()` trước khi
51
+ dựng route. Không có `src/providers/` thì bỏ qua, không lỗi.
52
+
53
+ ### Thêm — query builder
54
+
55
+ - **`repo.query()`** sinh SQL thật: `join` / `left_join` / `right_join` /
56
+ `outer_join`, `where` nối tiếp là AND còn `or_where` mở nhánh OR, `group_by` +
57
+ `having`, `limit` / `offset` / `distinct`. Xem câu sinh ra bằng `.sql()`.
58
+ - **Toán tử thường trên cột**: `Camera.score >= 0.9` thay cho `score__gte=0.9`,
59
+ nhờ `class Camera(Entity)`. Bảy toán tử không có ký hiệu (`like`, `ilike`,
60
+ `in_`, `not_in`, `is_null`, `is_not_null`, `between`) có cả ở dạng method của
61
+ builder — `query().like(...)` — để IDE gợi ý được.
62
+ - **Khoá ngoại**: `field(metadata=reference(Camera, on_delete="CASCADE"))`, đủ
63
+ bốn hành vi `CASCADE` / `SET NULL` / `SET DEFAULT` / `RESTRICT`. Áp bởi chính
64
+ database với SQL; khung tự áp cho `memory` và `mongodb` để ba backend cùng
65
+ kết quả.
66
+ - **Transaction**: `async with db.transaction() as tx:` — khối lồng nhau thành
67
+ SAVEPOINT, `await tx.rollback()` huỷ mà không phải ném lỗi. HTTP handler đã
68
+ nằm sẵn trong một transaction của cả request.
69
+ - **Dữ liệu lồng nhau**: `include(X)` khai X trả về những cột nào,
70
+ `nest_under(A, B, C)` khai thứ tự lồng từ NGOÀI vào TRONG. Mỗi mức đúng một
71
+ câu lệnh, không phải một câu cho mỗi dòng.
72
+ - **`select(...)`** gộp cả `fields=` / `exclude=` / `rename=` / `add=`.
73
+ - **MongoDB** chạy được phần lớn builder (`docs/mongodb.md` liệt kê cái không có:
74
+ JOIN, `group_by`, `distinct`, transaction).
75
+ - **Chặn injection ở tầng dùng chung**: giá trị mang toán tử (`{"$ne": ""}` —
76
+ qua được cửa đăng nhập trên Mongo), khoá `$where` chạy JavaScript, và tên cột
77
+ không có thật (SQL trước đây âm thầm bỏ điều kiện, trả về cả bảng).
78
+ - **Soi khoá ngoại lúc khởi động** (`db.foreign_keys_stale`): thêm
79
+ `reference(...)` vào entity đã chạy rồi thì database không biết — cascade
80
+ dừng giữa chừng, cháu ở lại thành mồ côi, không lỗi không cảnh báo.
81
+
82
+ ### Thêm — việc chạy nền
83
+
84
+ - `@worker` (vòng lặp sống mãi, N bản, mỗi bản một tham số), `@interval` /
85
+ `@cron` / `@timeout` (theo lịch), `@job` (hàng đợi trong tiến trình),
86
+ `@on_event` + `EventBus` (fanout trong tiến trình). Cả bốn nhận `thread=True`
87
+ cho hàm chặn, và `ctx: WorkerContext` để dừng đúng cách.
88
+ - Khoá `flock` / Redis để nhiều worker không cùng chạy một việc định kỳ.
89
+
90
+ ### Thêm — RPC và hạ tầng
91
+
92
+ - **`emit` / `send` + `@*_responder`** cho RabbitMQ, Redis, MQTT, Kafka — khuôn
93
+ tin tương thích `@nestjs/microservices`, đã chạy đối chứng hai chiều với
94
+ NestJS 11.2.1.
95
+ - RabbitMQ: đủ 5 kiểu exchange, 3 dạng hạn dùng (TTL), `emit_many`.
96
+ - SQLite: mặc định WAL + `synchronous=NORMAL` — đo được nhanh gấp 20 lần
97
+ (68 → 1.269 ghi/s) mà vẫn không hỏng file khi mất điện.
98
+
99
+ ### Thêm — CLI
100
+
101
+ - **`fam install` ghi nhớ thành phần vào `requirements.txt`**
102
+ (`fastapi-modular[redis,sqlite]>=0.3.0`), để người clone repo về chỉ cần
103
+ `pip install -r requirements.txt`. `fam init` sinh sẵn file này. Dự án dùng
104
+ `pyproject.toml` đã khai fastapi-modular thì sửa ngay dòng đó. `fam install
105
+ dev` đi vào `requirements-dev.txt`.
106
+ - `fam module` sinh entity kế thừa `Entity`.
107
+
108
+ ### Sửa
109
+
110
+ - **Controller viết `def` thường** nổ `TypeError: object dict can't be used in
111
+ 'await' expression`. Khung bọc mọi method thành endpoint async rồi await
112
+ thẳng, nên mất luôn luật của FastAPI: `def` phải chạy ở thread pool. Guard
113
+ đồng bộ cũng nổ y hệt. Đo lại sau khi sửa: 4 request chặn 0,3s xong trong
114
+ 0,31s.
115
+ - **`fam install` chạy trước `fam init`** chỉ ghi khối database vào `.env`,
116
+ thiếu sạch `APP_NAME` / `APP_ENV` / `APP_DEBUG` / `APP_HOST` / `APP_PORT` —
117
+ app chạy bằng toàn giá trị mặc định, im lặng.
118
+ - **Lọc bằng `Enum` thường** (không phải `StrEnum`) chạy trên `memory` nhưng nổ
119
+ trên sqlite lẫn mongo. Chiều ngược lại cũng lệch: lọc bằng chuỗi `.value` thì
120
+ SQL khớp còn memory trượt.
121
+ - **`nest_under` + sắp theo cột của một lớp**: sqlite ném lỗi, memory và mongo
122
+ lặng lẽ bỏ qua.
123
+ - **Lồng ba tầng từ repo trong cùng**: `include(Camera)` ném lỗi dù chuỗi
124
+ `nest_under` nối được qua bảng giữa.
125
+ - **`include(X, name=...)`** bị bỏ qua khi X nằm trong chuỗi `nest_under`.
126
+ - **Hai transaction `memory` đồng thời**: task rollback cuốn luôn bản ghi task
127
+ khác vừa commit.
128
+ - **MongoDB cho ghi con trỏ tới cha không tồn tại**, trong khi SQL và memory từ
129
+ chối 409.
130
+ - `Ctrl+C` không thoát được khi có worker đang chạy; `@worker(thread=True)` mượn
131
+ pool dùng chung của event loop nên làm treo cả tiến trình.
132
+
133
+ ### Thay đổi phá vỡ
134
+
135
+ | 0.2.x | 0.3.0 |
136
+ |---|---|
137
+ | `.order_by("score")` | `.order_by_desc("score")` / `.order_by_asc("score")` — chiều nằm trong TÊN HÀM |
138
+ | `.fields([...])` · `.exclude([...])` | `.select(fields=[...], exclude=[...])` |
139
+ | `fam p` | nhập nhằng — `fam pu` (publish) hoặc `fam pr` (provider) |
140
+
141
+ Định danh trong thư viện đổi hết sang tiếng Anh; nếu bạn import hàm `_private`
142
+ nào của khung thì kiểm lại tên. API công khai không đổi.
143
+
144
+ ## [0.2.1] — 2026-08-22
145
+
146
+ ### Thay đổi
147
+
148
+ - Tác giả và chủ bản quyền: Oryza <developer@oryza.vn> -> quanglinh
149
+ <hackcoquanglinh2000@gmail.com>, ở cả `pyproject.toml` lẫn `LICENSE`.
150
+ - README tách làm hai bản song ngữ: `README.md` (tiếng Anh, là bản hiện trên
151
+ PyPI) và `README.vi.md` (tiếng Việt). Hai bản giữ cùng thứ tự mục.
152
+ - Metadata PyPI viết lại cho tìm kiếm: summary sang tiếng Anh, keywords từ 9 lên
153
+ 39 từ, thêm 7 classifier.
154
+
155
+ ### Tài liệu
156
+
157
+ - Viết lại `docs/websocket.md` theo hướng làm-theo thay vì tra-cứu: đưa "bốn
158
+ việc client bắt buộc phải làm" lên đầu trang, thêm client tối thiểu 30 dòng
159
+ chạy được ngay, giải thích cơ chế nhịp tim bằng sơ đồ thời gian, và thêm mục
160
+ tra sự cố theo triệu chứng.
161
+
162
+ ## [0.2.0] — 2026-08-22
163
+
164
+ **Đổi tên toàn bộ.** Tên `pymodular` bị PyPI từ chối vì trùng với project
165
+ `py-modular` đã có, nên cả dự án đổi sang `fastapi-modular` cho thống nhất từ
166
+ PyPI, GitHub, thư mục nguồn cho tới tên lệnh.
167
+
168
+ ### Phá vỡ
169
+
170
+ - Gói trên PyPI: `pymodular` -> **`fastapi-modular`**.
171
+ - Tên import: `import pymodular` -> **`import fastapi_modular`** (gạch dưới,
172
+ vì gạch ngang không hợp lệ trong tên module Python).
173
+ - Lệnh CLI: `pymodular` / `pym` -> **`fastapi-modular` / `fam`**.
174
+ - Thư mục nguồn `pymodular/` -> `fastapi_modular/`.
175
+ - `APP_NAME` mặc định: `pymodular` -> `fastapi-modular`.
176
+ - `APP_KAFKA__CLIENT_ID` mặc định: `pymodular` -> `fastapi-modular`.
177
+
178
+ Nâng cấp từ 0.1.0: gỡ gói cũ (`pip uninstall pymodular`), cài
179
+ `pip install fastapi-modular`, rồi đổi mọi `pymodular` trong import thành
180
+ `fastapi_modular` và mọi lệnh `pym` thành `fam`.
181
+
182
+ ### Sửa
183
+
184
+ - `fam lint` không tham số trỏ vào thư mục `app` không tồn tại nên lỗi ngay;
185
+ mặc định đổi thành `src`, đúng thứ `fam init` sinh ra.
186
+ - `fam migrate-create` trong docs/migrations.md không phải lệnh có thật; lệnh
187
+ đúng là `fam migrate create`.
188
+ - Link trong README đổi sang URL tuyệt đối: README là trang hiển thị trên PyPI,
189
+ ở đó link tương đối `docs/...` phân giải sai và trả 404.
190
+ - Job đóng gói trong CI không bao giờ xanh được: import sai đường dẫn
191
+ (`src.api.main`) và liệt kê route bằng `r.path`, hỏng từ FastAPI 0.141.
192
+
193
+ ## [0.1.0] — 2026-08-21
194
+
195
+ Bản đầu tiên. Cần **Python 3.10 trở lên**.
196
+
197
+ ### Có gì
198
+
199
+ - **Kiến trúc module kiểu NestJS**: DI container (`@injectable`, `Lazy[...]`,
200
+ `Scope.REQUEST`), controller dạng class (`@controller`, `@get`/`@post`/...),
201
+ tự quét module — thêm thư mục là có route, không phải đăng ký ở đâu cả.
202
+ - **Repository dùng chung cho 4 backend**: memory, SQLite, PostgreSQL, MongoDB.
203
+ Đổi backend không phải sửa service. Kèm circuit breaker và hạn thời gian.
204
+ - **WebSocket**: `@gateway` / `@subscribe`, phòng, gửi thẳng tới một người,
205
+ nhịp tim, giới hạn tần suất, adapter Redis để phát tin xuyên worker.
206
+ - **Bốn lớp hạ tầng tuỳ chọn, cùng một khuôn**: RabbitMQ, Redis, MQTT, Kafka.
207
+ Mặc định TẮT; thư viện chỉ được import khi bật. Tất cả tự nối lại.
208
+ - **CLI** `fastapi-modular`, gõ tắt là `fam`: `init` (dựng dự án ngay trong thư mục
209
+ hiện tại) · `new` · `dev` / `run` · `module` · `env` · `info` · `migrate` ·
210
+ `test` / `lint`. `init` không bao giờ ghi đè file đã có.
211
+ - **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õ
212
+ truyền gì, không truyền thì mặc định là gì.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fastapi-modular
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: NestJS-style modular architecture for FastAPI: DI container, class-based controllers, auto-discovered modules, WebSocket gateway, RabbitMQ/Redis/MQTT/Kafka
5
5
  Project-URL: Homepage, https://github.com/quanglinh2909/fastapi-modular
6
6
  Project-URL: Documentation, https://github.com/quanglinh2909/fastapi-modular/tree/main/docs
@@ -97,14 +97,33 @@ public API, and this README, are in English. Start with
97
97
  | `@Module()` + module scanning | a directory under `src/api/`, auto-scanned |
98
98
  | `@Controller('users')` | `@controller(prefix="/users", tags=["users"])` |
99
99
  | `@Get()` `@Post()` `@Patch()` `@Delete()` | `@get()` `@post()` `@patch()` `@delete()` |
100
+ | sync handler runs on the main thread (Nest) | `def` handler runs in a thread pool, `async def` on the event loop — FastAPI's own rule |
100
101
  | `@Injectable()` | `@injectable` |
101
102
  | `@Injectable({scope: Scope.REQUEST})` | `@injectable(scope=Scope.REQUEST)` |
102
103
  | `forwardRef(() => X)` | `Lazy[X]` |
103
104
  | `@InjectRepository(X) repo: Repository<X>` | `repo: Repository[X]` |
105
+ | `@Transaction()` / `queryRunner.startTransaction()` | `async with db.transaction():` — nested blocks become SAVEPOINTs |
106
+ | `queryRunner.rollbackTransaction()` | automatic on exception; `await tx.rollback()` to bail out without raising |
107
+ | `repo.createQueryBuilder()` (TypeORM) | `repo.query().join(X).where(Event.score >= …)` — real SQL, `.sql()` to see it |
108
+ | `Repository.find({where: {score: MoreThan(…)}})` (TypeORM) | `class Event(Entity)` then `.where(Event.score >= …)`, or `.where(score__gte=…)` |
109
+ | `.groupBy().having()` (TypeORM) | `.group_by(Event.camera_id).select(n=count()).having(count() > 5)` |
110
+ | `.leftJoin()` / `.orWhere()` (TypeORM) | `.left_join(X)` / `.or_where(…)` — one method per join kind |
111
+ | `.orderBy('x', 'DESC')` (TypeORM) | `.order_by_desc("x")` — the direction is in the method name |
112
+ | `Like()` / `In()` / `IsNull()` (TypeORM) | `.like(X.name, "a%")` · `.in_(X.zone, [...])` · `.is_null(X.ip)` — right on the builder |
113
+ | `select([...])` / `AS` (TypeORM) | `.select(fields=…, exclude=…, rename={"new": "col"})` — same names on `include` |
114
+ | `addSelect()` (TypeORM) | `.select(add={"cam_name": Camera.name})` — keep every column, add one |
115
+ | `find({relations: {events: true}})` (TypeORM) | `.include(Event)` — nested rows, one extra query, not N+1 |
116
+ | `relations: {camera: {logs: {items: true}}}` (TypeORM) | `.nest_under(Camera, CameraLog, ItemLog)` — one query per level |
117
+ | *(no TypeORM equivalent)* | `.nest_under(Camera)` — filter on events, get cameras back with them nested |
118
+ | `@ManyToOne(…, {onDelete: 'CASCADE'})` (TypeORM) | `field(metadata=reference(Camera, on_delete="CASCADE"))` — a real FK in the database |
104
119
  | `@UseGuards()` | `guards=[...]` on the controller or a single route |
105
120
  | `@WebSocketGateway()` | `@gateway(path="/ws/…")` |
106
121
  | `@SubscribeMessage('x')` | `@subscribe("x")` |
107
122
  | `@EventPattern('x')` (RabbitMQ) | `@rabbitmq_subscriber("events", "x", queue="…")` |
123
+ | `@MessagePattern('x')` | `@rabbitmq_responder("x", queue="…")` — the return value is sent back |
124
+ | `@Interval()` / `@Cron()` / `@Timeout()` | `@interval(seconds=5)` / `@cron("0 3 * * *")` / `@timeout(seconds=10)` |
125
+ | `@OnEvent('x')` + `EventEmitter2` | `@on_event("x")` + `EventBus.emit()` — in-process fanout |
126
+ | `client.emit(p, d)` / `client.send(p, d)` | `broker.emit(p, d, queue=…)` / `await broker.send(p, d, queue=…)` |
108
127
  | `CacheModule` / `CACHE_MANAGER` | `RedisClient.cached(key, factory, ttl=…)` |
109
128
  | socket.io Redis adapter | `APP_WS__ADAPTER=redis` |
110
129
 
@@ -139,8 +158,24 @@ fam install rabbitmq # or redis, mqtt, kafka
139
158
  fam install all # everything above
140
159
  ```
141
160
 
142
- `fam install` both installs the libraries and writes the matching variables into
143
- `.env`. Plain pip works too: `pip install "fastapi-modular[sqlite,rabbitmq]"`.
161
+ `fam install` does three things: installs the libraries, writes the matching
162
+ variables into `.env`, and **records the component in `requirements.txt`** so a
163
+ teammate who clones the repo only needs `pip install -r requirements.txt` — the
164
+ same job `package.json` does for `npm i`.
165
+
166
+ ```
167
+ # requirements.txt, after `fam install sqlite` and `fam install redis`
168
+ fastapi-modular[redis,sqlite]>=0.3.0
169
+ ```
170
+
171
+ It records the extras, not the individual packages: the version ranges of
172
+ `sqlalchemy`, `motor` and friends belong to fastapi-modular and change per
173
+ release, so a flattened snapshot would go stale silently. If the project uses
174
+ `pyproject.toml` and already lists fastapi-modular there, that line is updated
175
+ instead and no `requirements.txt` is created. `fam install dev` goes to
176
+ `requirements-dev.txt` — production should not have to install pytest.
177
+
178
+ Plain pip works too: `pip install "fastapi-modular[sqlite,rabbitmq]"`.
144
179
 
145
180
  ## Commands
146
181
 
@@ -166,9 +201,10 @@ fam: lệnh 'm' chưa rõ — khớp với migrate, module. Gõ thêm vài chữ
166
201
  | `fam module <name> --gateway` | | plus a WebSocket gateway (`--consumer` for RabbitMQ) |
167
202
  | `fam module <name> --gateway-only` | | add a gateway to an **existing** module (`--consumer-only` for RabbitMQ) |
168
203
  | `fam module <name> --entity <N>` | | set the entity class name; guessed from the module name otherwise |
204
+ | `fam provider <family> <name>` | `fam pr` | generate a pluggable provider: capability interfaces + implementation stub |
169
205
  | `fam env <component>` | `fam e` | only write config variables into `.env` (installs nothing) |
170
206
  | `fam clean` | `fam c` | remove caches and build output (leaves `data/` alone) |
171
- | `fam build` · `fam publish [--test]` | `fam b` · `fam p` | build wheel/sdist · upload to PyPI |
207
+ | `fam build` · `fam publish [--test]` | `fam b` · `fam pu` | build wheel/sdist · upload to PyPI |
172
208
  | `fam info` | `fam inf` | what it connects to, what is installed, production config warnings |
173
209
  | `fam migrate [up\|down\|history\|sql\|create]` | `fam mi` | Alembic |
174
210
  | `fam test` · `fam lint [--fix]` | `fam t` · `fam l` | pytest · ruff. Bare `fam lint` checks `src`; pass paths to check elsewhere |
@@ -177,7 +213,7 @@ fam: lệnh 'm' chưa rõ — khớp với migrate, module. Gõ thêm vài chữ
177
213
  | `fam install postgres` | `fam ins p` | PostgreSQL |
178
214
  | `fam install mongodb` | `fam ins mo` | MongoDB |
179
215
  | **Queues** | | *installs libraries **then** writes `.env`* |
180
- | `fam install rabbitmq` | `fam ins ra` | durable queues, retry + DLQ |
216
+ | `fam install rabbitmq` | `fam ins ra` | 5 exchange types, durable queues, TTL, retry + DLQ |
181
217
  | `fam install redis` | `fam ins re` | cache, atomic counters, pub/sub |
182
218
  | `fam install mqtt` | `fam ins mq` | IoT devices |
183
219
  | `fam install kafka` | `fam ins k` | replayable event log |
@@ -214,8 +250,7 @@ fam env sqlite # write .env only, install nothing
214
250
  fam info # what it is connected to right now
215
251
  ```
216
252
 
217
- Details:
218
- [docs/database.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/database.md).
253
+ Details: [docs/database.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/database.md) (SQL) · [docs/mongodb.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/mongodb.md) (MongoDB).
219
254
 
220
255
  ## Your own configuration
221
256
 
@@ -306,12 +341,125 @@ async def send_mail(self, payload: AlertCreated) -> None: ...
306
341
  @rabbitmq_subscriber("events", "alert.created", queue="alert-mailer",
307
342
  max_retries=3, dead_letter=True)
308
343
  async def send_mail(self, payload: AlertCreated) -> None: ...
344
+
345
+ # All 5 exchange types: topic (default), direct, fanout, headers, default
346
+ @rabbitmq_subscriber("cache-events", queue=f"drop-cache-{HOSTNAME}", exchange_type="fanout")
347
+ # hostname in the queue name -> every worker gets a copy, instead of taking turns
348
+ async def drop_cache(self, payload: dict) -> None: ...
349
+
350
+ # Time to live: per message (ttl), per queue (message_ttl), for the queue itself
351
+ await self._mq.publish("events", "car.position", {"lat": 21.0}, ttl=5)
309
352
  ```
310
353
 
311
354
  Not installed and not enabled means it behaves as if it never existed. If the
312
355
  broker goes down the app keeps serving and reconnects on its own. Details:
313
356
  [docs/rabbitmq.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/rabbitmq.md).
314
357
 
358
+ ## Background work
359
+
360
+ Four different things, none of which needs any infrastructure:
361
+
362
+ ```python
363
+ # on a SCHEDULE — @nestjs/schedule
364
+ @interval(seconds=5)
365
+ async def update_cameras(self) -> None: ...
366
+
367
+ @cron("0 3 * * *", timezone="Asia/Ho_Chi_Minh") # defaults to UTC!
368
+ async def clean_logs(self) -> None: ...
369
+
370
+ # on DEMAND — an in-process asyncio.Queue, processed in order
371
+ @job("detect", thread=True) # thread: runs in a thread, for YOLO
372
+ def detect(self, payload: dict, ctx: WorkerContext) -> None:
373
+ ctx.run(self._db.save(...)) # write to the DB from inside the thread
374
+
375
+ await self._jobs.submit("detect", {"path": p}) # returns immediately
376
+
377
+ # a LONG-RUNNING LOOP — N instances, one per key
378
+ @worker("camera")
379
+ async def watch(self, data: dict, ctx: WorkerContext) -> None:
380
+ cap = await ctx.blocking(cv2.VideoCapture, data["ip"]) # setup, OUTSIDE the loop
381
+ while ctx.running:
382
+ frame = await ctx.blocking(cap.read) # blocking call -> a thread
383
+ await self._db.save(...) # plain await
384
+
385
+ for camera in cameras:
386
+ await service.watch(camera.id, {"ip": camera.ip}) # key + data at call time
387
+
388
+ await self.watch.stop(camera.id) # stops ONE instance, waits for its cleanup
389
+
390
+ # FANOUT inside the process — one event, N listeners, in PARALLEL
391
+ @on_event("order.paid") # also "order.*" / "camera.#"
392
+ async def send_receipt(self, data: dict) -> None: ...
393
+
394
+ @on_event("order.paid") # a second listener is normal here
395
+ async def update_stats(self, data: dict) -> None: ...
396
+
397
+ await self._events.emit("order.paid", {"id": id}) # waits for all of them
398
+ self._events.dispatch("order.paid", {"id": id}) # returns immediately
399
+ ```
400
+
401
+ All five decorators come in two shapes: `async def` (the default) and
402
+ `thread=True` for bodies that are all blocking calls. `ctx` is optional — take
403
+ it when you need `ctx.running` to leave a loop, `ctx.blocking(...)` to call
404
+ blocking code, or `ctx.run(...)` to write to the DB from inside a thread.
405
+
406
+ `@on_event` covers what `@job` cannot: `@job` is one name, **one** handler,
407
+ processed in order — a work queue. `@on_event` is one event, **many** handlers,
408
+ running in parallel — nobody owns the work, and the emitter doesn't know who is
409
+ listening. One listener raising doesn't stop the others. It is `fanout` /
410
+ `EventEmitter`, but in-process only: with `fam run --workers 4` an event does
411
+ not cross to the other three processes.
412
+
413
+ `@worker` covers what `@interval` and `@job` cannot: a setup phase **before**
414
+ the loop (open the camera, load the model) and a body that runs until you stop
415
+ it. Crashes restart with backoff; calling it again with the same `key` returns
416
+ the running instance instead of opening a second stream.
417
+
418
+ `stop()` waits for the loop's `finally:` to finish, so anything you write after
419
+ it runs with the camera already closed — put resource cleanup in `finally:` and
420
+ business cleanup after the call.
421
+
422
+ Write `while ctx.running:`, not `while True:` — a loop that never checks makes
423
+ Ctrl+C look dead for the whole shutdown timeout. The framework says so at
424
+ startup (`worker.endless_loop`) rather than letting you find out at 2am.
425
+
426
+ `fam run` starts 4 workers, so a hand-written `while True: sleep(5)` runs
427
+ **four times**. `single=True` (the default) locks it down: measured 5 runs
428
+ across 1 process, versus 20 runs across 4 with the lock off. The lock is
429
+ `flock` (one machine) or Redis (many), picked automatically.
430
+
431
+ The `@job` queue lives in RAM — **shutdown loses whatever hasn't run**, and the
432
+ framework logs that count instead of hiding it. Work that must not be lost
433
+ belongs in `@rabbitmq_subscriber`. Details:
434
+ [docs/background.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/background.md).
435
+
436
+ ## Request/response, NestJS-compatible
437
+
438
+ `publish`/`emit` fire and forget. `send` waits for an answer — the NestJS
439
+ `client.send()` / `@MessagePattern()` pair, same wire format:
440
+
441
+ ```python
442
+ # the side that answers — a normal service that happens to `return`
443
+ @rabbitmq_responder("sum", queue="math")
444
+ async def add(self, data: list[int]) -> int:
445
+ return sum(data)
446
+
447
+ # the side that calls
448
+ total = await self._mq.send("sum", [1, 2, 3, 4], queue="math") # -> 10
449
+ ```
450
+
451
+ Available on **all four**: `@rabbitmq_responder`, `@redis_responder`,
452
+ `@mqtt_responder`, `@kafka_responder`.
453
+
454
+ The packet layout is taken from `@nestjs/microservices` source, so a NestJS
455
+ microservice and a service built on this library talk to each other with no
456
+ translation layer — verified both ways, on all four transports, against NestJS
457
+ 11.2.1. Object patterns (`{"cmd": "sum"}`) included, key ordering and all.
458
+
459
+ `send` turns a queue into a network call, which brings back everything queues
460
+ exist to avoid — [docs/rpc.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/rpc.md)
461
+ says when not to use it.
462
+
315
463
  ## Redis, MQTT, Kafka (also optional)
316
464
 
317
465
  Same shape as RabbitMQ: one package under `infrastructure/`, one `APP_<NAME>__*`
@@ -354,8 +502,8 @@ Guards, circuit breaker, metrics and tracing:
354
502
  fastapi_modular/ THE LIBRARY — what gets packaged and installed
355
503
  core/ DI, controllers, config, WebSocket, guards, metrics
356
504
  infrastructure/ database, rabbitmq, redis, mqtt, kafka (one package each)
357
- cli/ init · new · module · dev · run · install · env · info
358
- migrate · test · lint · clean · build · publish
505
+ cli/ init · new · module · provider · dev · run · install · env
506
+ info · migrate · test · lint · clean · build · publish
359
507
  factory.py create_app()
360
508
  discovery.py scans the application package and builds routers
361
509
  src/ SAMPLE APPLICATION — not shipped in the package; delete freely
@@ -363,7 +511,7 @@ src/ SAMPLE APPLICATION — not shipped in the package; delete fr
363
511
  core/config.py AppSettings: subclass Settings to add your own .env variables
364
512
  core/lifespan.py application-specific startup / shutdown work
365
513
  api/ business modules; every subdirectory is one module
366
- tests/ 341 tests that need no infrastructure, 40 more when servers exist
514
+ tests/ 1097 tests that need no infrastructure, 292 more with real drivers/servers
367
515
  docs/ reference documentation (Vietnamese)
368
516
  ```
369
517
 
@@ -402,11 +550,15 @@ Written in Vietnamese, organised for reference rather than reading front to back
402
550
 
403
551
  - [docs/architecture.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/architecture.md) — module layout, DI, the NestJS comparison
404
552
  - [docs/config.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/config.md) — Settings, precedence, adding your own variables
405
- - [docs/database.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/database.md) — memory / SQLite / PostgreSQL / MongoDB
553
+ - [docs/database.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/database.md) — SQL: memory / SQLite / PostgreSQL entities, foreign keys, query builder, transactions
554
+ - [docs/mongodb.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/mongodb.md) — MongoDB: queries, nested data, and what is not there (no JOIN, no transactions)
406
555
  - [docs/migrations.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/migrations.md) — Alembic: generate, run, roll back
407
556
  - [docs/websocket.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/websocket.md) — WebSocket gateway, rooms, Postman, Next.js
408
- - [docs/rabbitmq.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/rabbitmq.md) — exchanges, topics, background consumers, `.retry` / `.dlq`
557
+ - [docs/rabbitmq.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/rabbitmq.md) — all 5 exchange types, TTL, background consumers, `.retry` / `.dlq`
558
+ - [docs/background.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/background.md) — scheduled work (`@interval`/`@cron`/`@timeout`), an in-process job queue (`@job`), long-running loops (`@worker`) and in-process fanout (`@on_event`)
559
+ - [docs/rpc.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/rpc.md) — `emit` / `send` / `@rabbitmq_responder`, NestJS-compatible wire format
409
560
  - [docs/redis.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/redis.md) — cache, atomic counters, pub/sub
410
561
  - [docs/mqtt.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/mqtt.md) — QoS, retain, `+` and `#` topic matching
411
562
  - [docs/kafka.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/kafka.md) — consumer groups, partitions, `.dlt`
563
+ - [docs/providers.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/providers.md) — pluggable providers: pick an implementation by name at runtime
412
564
  - [docs/operations.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/operations.md) — guards, circuit breaker, metrics, tracing