fastapi-modular 0.3.0__tar.gz → 0.4.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 (208) hide show
  1. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/CHANGELOG.md +156 -1
  2. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/PKG-INFO +15 -8
  3. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/README.md +14 -7
  4. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/README.vi.md +14 -7
  5. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/README.md +5 -1
  6. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/architecture.md +52 -6
  7. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/background.md +170 -47
  8. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/config.md +15 -0
  9. fastapi_modular-0.4.0/docs/database.md +633 -0
  10. fastapi_modular-0.4.0/docs/entity.md +732 -0
  11. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/migrations.md +15 -1
  12. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/mongodb.md +23 -7
  13. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/operations.md +16 -0
  14. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/providers.md +36 -3
  15. fastapi_modular-0.4.0/docs/query.md +1030 -0
  16. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/rabbitmq.md +29 -29
  17. fastapi_modular-0.4.0/docs/repository.md +241 -0
  18. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/rpc.md +16 -9
  19. fastapi_modular-0.4.0/docs/transaction.md +194 -0
  20. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/websocket.md +5 -5
  21. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/__init__.py +3 -2
  22. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/main.py +7 -2
  23. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/new_module.py +27 -7
  24. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/requirements.py +2 -2
  25. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/serve.py +5 -1
  26. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/config.py +1 -1
  27. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/container.py +22 -0
  28. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/locks.py +1 -1
  29. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/scheduler.py +1 -1
  30. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/workers.py +5 -1
  31. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/__init__.py +9 -1
  32. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/base.py +178 -2
  33. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/circuit.py +10 -0
  34. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/memory.py +72 -10
  35. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/mongo.py +93 -5
  36. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/repository.py +162 -2
  37. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/sql.py +105 -8
  38. fastapi_modular-0.4.0/tests/test_column_types.py +466 -0
  39. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_drivers.py +37 -1
  40. fastapi_modular-0.4.0/tests/test_ghi_du_lieu_tu_worker.py +254 -0
  41. fastapi_modular-0.4.0/tests/test_id_types.py +249 -0
  42. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_module_generator.py +124 -0
  43. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_schema_evolution.py +37 -2
  44. fastapi_modular-0.4.0/tests/test_update.py +433 -0
  45. fastapi_modular-0.3.0/docs/database.md +0 -2307
  46. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/.gitignore +0 -0
  47. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/LICENSE +0 -0
  48. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/Makefile +0 -0
  49. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/alembic.ini +0 -0
  50. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/kafka.md +0 -0
  51. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/mqtt.md +0 -0
  52. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/redis.md +0 -0
  53. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/__init__.py +0 -0
  54. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/clean.py +0 -0
  55. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/configure_env.py +0 -0
  56. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/info.py +0 -0
  57. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/install.py +0 -0
  58. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/new_project.py +0 -0
  59. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/new_provider.py +0 -0
  60. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/tools.py +0 -0
  61. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/__init__.py +0 -0
  62. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/clock.py +0 -0
  63. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/compat.py +0 -0
  64. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/context.py +0 -0
  65. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/controller.py +0 -0
  66. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/cron.py +0 -0
  67. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/error_handlers.py +0 -0
  68. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/events.py +0 -0
  69. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/exceptions.py +0 -0
  70. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/guards.py +0 -0
  71. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/jobs.py +0 -0
  72. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/lifespan.py +0 -0
  73. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/logging.py +0 -0
  74. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/metrics.py +0 -0
  75. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/providers.py +0 -0
  76. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/rpc.py +0 -0
  77. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/schemas.py +0 -0
  78. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/__init__.py +0 -0
  79. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/adapter.py +0 -0
  80. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/gateway.py +0 -0
  81. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/namespace.py +0 -0
  82. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/protocol.py +0 -0
  83. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/server.py +0 -0
  84. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/socket.py +0 -0
  85. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/discovery.py +0 -0
  86. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/factory.py +0 -0
  87. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/__init__.py +0 -0
  88. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/factory.py +0 -0
  89. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/query.py +0 -0
  90. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/__init__.py +0 -0
  91. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/broker.py +0 -0
  92. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/consumers.py +0 -0
  93. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/metrics.py +0 -0
  94. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/responders.py +0 -0
  95. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/__init__.py +0 -0
  96. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/client.py +0 -0
  97. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/consumers.py +0 -0
  98. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/metrics.py +0 -0
  99. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/patterns.py +0 -0
  100. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/responders.py +0 -0
  101. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/__init__.py +0 -0
  102. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/broker.py +0 -0
  103. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/consumers.py +0 -0
  104. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/metrics.py +0 -0
  105. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/patterns.py +0 -0
  106. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/responders.py +0 -0
  107. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/__init__.py +0 -0
  108. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/client.py +0 -0
  109. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/metrics.py +0 -0
  110. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/pubsub.py +0 -0
  111. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/responders.py +0 -0
  112. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/middleware/__init__.py +0 -0
  113. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/middleware/request_context.py +0 -0
  114. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/py.typed +0 -0
  115. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/migrations/env.py +0 -0
  116. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/migrations/script.py.mako +0 -0
  117. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/migrations/versions/.gitkeep +0 -0
  118. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/migrations/versions/20260821_1020_tao_bang_users_va_devices.py +0 -0
  119. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/pyproject.toml +0 -0
  120. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/pytest.ini +0 -0
  121. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/ruff.toml +0 -0
  122. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/__init__.py +0 -0
  123. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/__init__.py +0 -0
  124. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/__init__.py +0 -0
  125. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/chat_controller.py +0 -0
  126. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/chat_gateway.py +0 -0
  127. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/dto/__init__.py +0 -0
  128. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/dto/chat_dto.py +0 -0
  129. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/__init__.py +0 -0
  130. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/device_controller.py +0 -0
  131. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/device_service.py +0 -0
  132. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/dto/__init__.py +0 -0
  133. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/dto/device_dto.py +0 -0
  134. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/entities/__init__.py +0 -0
  135. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/entities/device_model.py +0 -0
  136. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/health/__init__.py +0 -0
  137. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/health/health_controller.py +0 -0
  138. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/kafka_test/__init__.py +0 -0
  139. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/kafka_test/kafka_consumer.py +0 -0
  140. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/kafka_test/kafka_controller.py +0 -0
  141. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/metrics/__init__.py +0 -0
  142. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/metrics/metrics_controller.py +0 -0
  143. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/mqtt_test/__init__.py +0 -0
  144. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/mqtt_test/mqtt_controller.py +0 -0
  145. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/mqtt_test/mqtt_listener.py +0 -0
  146. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/rabbitmq_test/__init__.py +0 -0
  147. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/rabbitmq_test/rabbitmq_consumer.py +0 -0
  148. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/rabbitmq_test/rabbitmq_controller.py +0 -0
  149. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/redis_test/__init__.py +0 -0
  150. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/redis_test/redis_controller.py +0 -0
  151. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/redis_test/redis_listener.py +0 -0
  152. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/__init__.py +0 -0
  153. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/dto/__init__.py +0 -0
  154. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/dto/user_dto.py +0 -0
  155. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/entities/__init__.py +0 -0
  156. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/entities/user_model.py +0 -0
  157. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/user_controller.py +0 -0
  158. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/user_service.py +0 -0
  159. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/core/__init__.py +0 -0
  160. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/core/config.py +0 -0
  161. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/core/lifespan.py +0 -0
  162. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/main.py +0 -0
  163. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/conftest.py +0 -0
  164. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/fixtures/cron_vectors.json +0 -0
  165. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/fixtures/nestjs_patterns.json +0 -0
  166. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_circuit.py +0 -0
  167. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_cli.py +0 -0
  168. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_configure_env.py +0 -0
  169. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_container.py +0 -0
  170. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_controller_dong_bo.py +0 -0
  171. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_cron.py +0 -0
  172. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_cross_module.py +0 -0
  173. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_database.py +0 -0
  174. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_enum_parity.py +0 -0
  175. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_events.py +0 -0
  176. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_foreign_keys.py +0 -0
  177. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_guards.py +0 -0
  178. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_ha_tang_that.py +0 -0
  179. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_health.py +0 -0
  180. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_injection.py +0 -0
  181. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_jobs.py +0 -0
  182. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_kafka.py +0 -0
  183. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_kafka_offline.py +0 -0
  184. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_lifespan_rieng.py +0 -0
  185. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_metrics.py +0 -0
  186. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_mongo_query.py +0 -0
  187. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_mqtt.py +0 -0
  188. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_mqtt_offline.py +0 -0
  189. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_providers.py +0 -0
  190. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_query_builder.py +0 -0
  191. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_rabbitmq.py +0 -0
  192. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_rabbitmq_offline.py +0 -0
  193. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_redis.py +0 -0
  194. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_redis_offline.py +0 -0
  195. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_requirements_ghi_nho.py +0 -0
  196. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_resilience.py +0 -0
  197. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_rpc.py +0 -0
  198. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_scheduler.py +0 -0
  199. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_schemas.py +0 -0
  200. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_settings_mo_rong.py +0 -0
  201. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_sqlite_pragmas.py +0 -0
  202. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_transaction.py +0 -0
  203. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_tuong_thich_phien_ban.py +0 -0
  204. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_uniqueness.py +0 -0
  205. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_users_api.py +0 -0
  206. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_websocket.py +0 -0
  207. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_workers.py +0 -0
  208. {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_ws_adapter.py +0 -0
@@ -3,7 +3,162 @@
3
3
  Theo [Keep a Changelog](https://keepachangelog.com/vi/1.1.0/); phiên bản theo
4
4
  [SemVer](https://semver.org/lang/vi/).
5
5
 
6
- ## [Chưa phát hành]
6
+ ## [0.4.0] 2026-08-28
7
+
8
+ ### Thêm
9
+
10
+ - **`id: int` — khoá chính là số tự tăng.** Khai `id: int = 0` thay cho
11
+ `id: str` là database phát số; `id: str` vẫn là UUID khung sinh, không đổi gì.
12
+
13
+ ```python
14
+ @entity()
15
+ @dataclass(slots=True)
16
+ class Camera(Entity):
17
+ id: int = 0 # 0 = "chưa có"
18
+
19
+ cam = await repo.save(Camera(name="Cổng chính"))
20
+ print(cam.id) # 1
21
+ ```
22
+
23
+ `SERIAL` trên Postgres, `INTEGER PRIMARY KEY AUTOINCREMENT` trên SQLite, bộ
24
+ đếm `_fam_counters` (`$inc`, nguyên tử) trên MongoDB, bộ đếm trong bộ nhớ ở
25
+ `memory`. `get` / `update` / `delete` nhận cả chuỗi lẫn số.
26
+
27
+ Ba chỗ phải bắt để bốn backend cư xử như nhau:
28
+
29
+ - SQLite mặc định cấp `max(rowid) + 1`, nên xoá bản ghi cuối bảng rồi ghi tiếp
30
+ là số cũ QUAY LẠI (đo được: `1, 2`, xoá `2`, ghi ra `2`). Bảng có id số nay
31
+ được tạo với `AUTOINCREMENT`.
32
+ - MongoDB: bản ghi MỚI dùng `insert_one` thay cho `replace_one(upsert=True)`.
33
+ Bộ đếm lệch — vì có ai đó gán id tay — nay cho **409** chứ không lặng lẽ đè
34
+ lên document của người khác.
35
+ - Câu `INSERT` bỏ hẳn cột `id` thay vì gửi `0` xuống.
36
+
37
+ Nhớ đổi `<tên>_id: str` thành `int` ở controller và service, nếu không FastAPI
38
+ nhận `"12"` (chuỗi) và bạn được 404 khó hiểu. `fam module` ghi sẵn lời nhắc
39
+ này ngay cạnh trường `id`.
40
+
41
+ - **`column(length=50)` và `column(text=True)`** — đặt độ dài cột chữ, hoặc đổi
42
+ sang `TEXT`. Tương đương `@Column({length})` / `@Column({type: 'text'})` của
43
+ TypeORM:
44
+
45
+ ```python
46
+ code: str = field(default="", metadata=column(length=50)) # VARCHAR(50)
47
+ note: str = field(default="", metadata=column(text=True)) # TEXT
48
+ ```
49
+
50
+ Độ dài được kiểm **ở tầng khung**, trước khi câu lệnh xuống database, và cho
51
+ **400** kèm câu nói rõ chỗ sai. Vì sao không giao hết cho database: đo được,
52
+ ghi 60 ký tự vào `VARCHAR(50)` thì SQLite nhận bình thường, Postgres ném
53
+ `StringDataRightTruncation`, MongoDB không có khái niệm độ dài — tức là chạy
54
+ được lúc dev và đổ lúc chạy thật. Chặn ở cả `save()`, `update()` và
55
+ `update_where()`; Enum đếm theo `.value`.
56
+
57
+ Gộp với khoá ngoại trên cùng một cột bằng `|`:
58
+ `field(metadata=reference(Camera) | column(length=36))`.
59
+
60
+ Soi schema (`schema_mode="sync"`) nay kêu cả khi lệch độ dài
61
+ (`cameras.code: VARCHAR(8) -> VARCHAR(64)`), nhưng chỉ khi entity CÓ khai —
62
+ bảng cũ có `VARCHAR(50)` mà entity để `str` trơn thì không bị kêu oan.
63
+
64
+ - **`repo.update(id, changes)` và `repo.update_where(dieu_kien, changes)`** —
65
+ sửa thẳng dưới database, không phải đọc bản ghi về trước. Thay vòng ba bước
66
+ `get` -> sửa -> `save` bằng một câu lệnh:
67
+
68
+ ```python
69
+ cam = await cameras.update("cam-01", status="offline") # -> bản ghi đã sửa
70
+ n = await cameras.update_where({"zone": "Tầng 1"}, status="off") # -> số dòng khớp
71
+ ```
72
+
73
+ Cùng cặp với `delete` / `delete_where`. `update` trả về **chính bản ghi đã
74
+ sửa** (`None` nếu không có id đó) và chỉ tốn **một** câu lệnh — đo được: 1 câu
75
+ so với 2 của `get` + `save` — nhờ `UPDATE ... RETURNING *` (SQL) và
76
+ `find_one_and_update` (Mongo). Bản trả về đọc từ database sau khi ghi, không
77
+ phải bản trong bộ nhớ.
78
+
79
+ `update_where` trả **số dòng khớp** chứ không trả dữ liệu, cố ý: một câu lệnh
80
+ có thể khớp hàng trăm nghìn dòng.
81
+
82
+ Giá trị truyền bằng dict, kwargs, hay **thẳng DTO của PATCH**:
83
+
84
+ ```python
85
+ async def update(self, camera_id: str, payload: CameraUpdate) -> int:
86
+ return await self._repo.update(camera_id, payload)
87
+ ```
88
+
89
+ DTO đọc bằng `exclude_unset=True`, y như `apply_changes`: chỉ field client
90
+ THỰC SỰ gửi mới được ghi. `model_dump()` trần sẽ ghi `None` đè lên mọi cột
91
+ không gửi. Truyền entity thì bị từ chối, kèm lời chỉ đường sang `save(obj)`.
92
+ Trả về **số dòng khớp**.
93
+
94
+ - `fam module` sinh gợi ý `update_<tên>` theo API mới — `repo.update(id, payload)`
95
+ thay cho vòng `get` -> `apply_changes` -> `save`. Có test chép-dán-chạy-thật:
96
+ bỏ chú thích cả năm gợi ý, thêm một trường như bộ sinh dặn, rồi gọi qua HTTP.
97
+ Thứ tự tham số lấy đúng của TypeORM (`repo.update(criteria, partialEntity)`).
98
+
99
+ `updated_at` tự đóng dấu như `save()`. Ràng buộc vẫn được áp trên cả ba
100
+ backend: khoá ngoại trỏ tới cha không tồn tại, hay làm trùng cột `unique`,
101
+ đều bị từ chối.
102
+
103
+ Ba thứ bị chặn có chủ đích: đổi `id` (khoá ngoại của bảng khác đang trỏ vào),
104
+ `where` rỗng (gần như luôn là biến rỗng do lỗi lập trình — cố ý sửa cả bảng
105
+ thì nói rõ bằng `match=lambda _: True`), và cột không có thật (gõ sai mà im
106
+ lặng bỏ qua thì câu lệnh báo "đã sửa N dòng" nhưng không sửa gì).
107
+
108
+ Trên MongoDB đếm bằng `matched_count` chứ không phải `modified_count`: ghi
109
+ đúng giá trị đang có thì Mongo coi là không sửa gì và trả 0, trong khi SQL vẫn
110
+ đếm dòng đã khớp. Đo trên Mongo thật để chắc: `matched=1, modified=0`.
111
+
112
+ ### Sửa
113
+
114
+ - **Trường `X | None` sinh ra cột `VARCHAR`.** `int | None` không phải một
115
+ `type`, nên mọi phép `is datetime` / `issubclass(..., Enum)` đều trượt và cột
116
+ rơi vào nhánh mặc định. Đo được với `port: int | None`: `memory` đọc về `8080`,
117
+ SQLite đọc về `'8080'` (chuỗi), Postgres ném `DataError` NGAY LÚC GHI.
118
+ `datetime | None` mất luôn múi giờ, `Enum | None` không được ép lại thành
119
+ Enum. Nay `X | None` cho ra đúng cột của `X`, chỉ khác là nhận `NULL`.
120
+
121
+ **Nâng cấp:** bảng tạo bằng bản ≤ 0.3.1 có cột `VARCHAR` cho những trường ấy,
122
+ nên `schema_mode="sync"` sẽ cảnh báo `VARCHAR -> INTEGER`. Đổi cột bằng
123
+ migration; bảng SQLite cũ vẫn đọc ghi được, còn Postgres thì trước đây ném lỗi
124
+ ngay lúc ghi nên gần như chắc chắn chưa có dữ liệu kiểu đó.
125
+
126
+ - `update` / `update_where` nhận id số, không còn chặn "phải là chuỗi".
127
+
128
+ - Bảng test chạy trên Postgres nay mang hậu tố ngẫu nhiên và tự xoá sau khi
129
+ chạy. Trước đó `test_drivers` dùng bảng `users` của app mẫu và gọi
130
+ `delete_where()` không điều kiện — trỏ `TEST_POSTGRES_DSN` vào một database
131
+ đang dùng là xoá sạch bảng `users` của dự án khác.
132
+
133
+ - Bỏ hai file rác lọt vào gói: `ebsocket.md#...#`.
134
+
135
+ ## [0.3.1] — 2026-08-27
136
+
137
+ ### Sửa
138
+
139
+ - **Worker ghi database không được commit.** `contextvars` được sao chép khi tạo
140
+ Task/Thread, nên `@worker` sinh ra từ trong một HTTP request — hoặc từ
141
+ `@interval`/`@job`, vốn cũng mở request scope — thừa hưởng đúng store của
142
+ request đó. Mà `SqlUnitOfWork` là provider request-scoped: nó mở transaction
143
+ rồi chỉ commit ở `on_request_end`. Worker sống lâu hơn request nên transaction
144
+ ấy không bao giờ được commit.
145
+
146
+ Kiểu hỏng này im lặng đến khó chịu:
147
+
148
+ ```python
149
+ print("Deleted:", await repo.delete(row.id)) # -> True
150
+ ```
151
+
152
+ `True` là đúng — DELETE khớp một dòng, và câu SELECT ngay sau cũng thấy dữ
153
+ liệu mới vì cùng một connection. Chỉ có điều trên đĩa không đổi gì, và tắt app
154
+ là mất sạch.
155
+
156
+ Nay worker được cắt khỏi request scope thừa hưởng (`detach_request_scope`),
157
+ nên mỗi thao tác tự commit như docs vẫn mô tả. Gộp nhiều lệnh ghi thì bọc
158
+ `async with db.transaction():` — vẫn dùng được trong `ctx.run`.
159
+
160
+ Hệ quả có thể thấy: `container.resolve(<provider Scope.REQUEST>)` trong worker
161
+ giờ báo lỗi thay vì trả về một instance mồ côi. Đó là cố ý.
7
162
 
8
163
  ## [0.3.0] — 2026-08-27
9
164
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fastapi-modular
3
- Version: 0.3.0
3
+ Version: 0.4.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
@@ -96,7 +96,7 @@ public API, and this README, are in English. Start with
96
96
  |---|---|
97
97
  | `@Module()` + module scanning | a directory under `src/api/`, auto-scanned |
98
98
  | `@Controller('users')` | `@controller(prefix="/users", tags=["users"])` |
99
- | `@Get()` `@Post()` `@Patch()` `@Delete()` | `@get()` `@post()` `@patch()` `@delete()` |
99
+ | `@Get()` `@Post()` `@Put()` `@Patch()` `@Delete()` | `@get()` `@post()` `@put()` `@patch()` `@delete()` |
100
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 |
101
101
  | `@Injectable()` | `@injectable` |
102
102
  | `@Injectable({scope: Scope.REQUEST})` | `@injectable(scope=Scope.REQUEST)` |
@@ -104,6 +104,7 @@ public API, and this README, are in English. Start with
104
104
  | `@InjectRepository(X) repo: Repository<X>` | `repo: Repository[X]` |
105
105
  | `@Transaction()` / `queryRunner.startTransaction()` | `async with db.transaction():` — nested blocks become SAVEPOINTs |
106
106
  | `queryRunner.rollbackTransaction()` | automatic on exception; `await tx.rollback()` to bail out without raising |
107
+ | `repo.update(criteria, partialEntity)` (TypeORM) | `repo.update("cam-01", payload)` — takes the DTO directly and **returns the updated row** in one SQL statement; `repo.update_where({"zone": "T1"}, status="off")` for many rows |
107
108
  | `repo.createQueryBuilder()` (TypeORM) | `repo.query().join(X).where(Event.score >= …)` — real SQL, `.sql()` to see it |
108
109
  | `Repository.find({where: {score: MoreThan(…)}})` (TypeORM) | `class Event(Entity)` then `.where(Event.score >= …)`, or `.where(score__gte=…)` |
109
110
  | `.groupBy().having()` (TypeORM) | `.group_by(Event.camera_id).select(n=count()).having(count() > 5)` |
@@ -116,6 +117,8 @@ public API, and this README, are in English. Start with
116
117
  | `relations: {camera: {logs: {items: true}}}` (TypeORM) | `.nest_under(Camera, CameraLog, ItemLog)` — one query per level |
117
118
  | *(no TypeORM equivalent)* | `.nest_under(Camera)` — filter on events, get cameras back with them nested |
118
119
  | `@ManyToOne(…, {onDelete: 'CASCADE'})` (TypeORM) | `field(metadata=reference(Camera, on_delete="CASCADE"))` — a real FK in the database |
120
+ | `@PrimaryGeneratedColumn()` / `@PrimaryGeneratedColumn('uuid')` (TypeORM) | `id: int = 0` (database assigns 1, 2, 3…) / `id: str` (the framework generates a UUID) |
121
+ | `@Column({length: 50})` / `@Column({type: 'text'})` (TypeORM) | `field(metadata=column(length=50))` / `column(text=True)` — checked before the write on every backend |
119
122
  | `@UseGuards()` | `guards=[...]` on the controller or a single route |
120
123
  | `@WebSocketGateway()` | `@gateway(path="/ws/…")` |
121
124
  | `@SubscribeMessage('x')` | `@subscribe("x")` |
@@ -165,7 +168,7 @@ same job `package.json` does for `npm i`.
165
168
 
166
169
  ```
167
170
  # requirements.txt, after `fam install sqlite` and `fam install redis`
168
- fastapi-modular[redis,sqlite]>=0.3.0
171
+ fastapi-modular[redis,sqlite]>=0.4.0
169
172
  ```
170
173
 
171
174
  It records the extras, not the individual packages: the version ranges of
@@ -196,7 +199,7 @@ fam: lệnh 'm' chưa rõ — khớp với migrate, module. Gõ thêm vài chữ
196
199
  | `fam init [--name <n>]` | `fam ini` | scaffold **into the current directory**; never overwrites; name defaults to the directory name |
197
200
  | `fam new <name>` | `fam n` | scaffold into a new directory |
198
201
  | `fam dev` | `fam d` | run with autoreload |
199
- | `fam run --workers 4` | `fam r` | run in production mode |
202
+ | `fam run` | `fam r` | run in production mode (1 process; `--workers 4` for more) |
200
203
  | `fam module <name>` | `fam mo` | generate a module: controller + service + dto + entity |
201
204
  | `fam module <name> --gateway` | | plus a WebSocket gateway (`--consumer` for RabbitMQ) |
202
205
  | `fam module <name> --gateway-only` | | add a gateway to an **existing** module (`--consumer-only` for RabbitMQ) |
@@ -250,7 +253,7 @@ fam env sqlite # write .env only, install nothing
250
253
  fam info # what it is connected to right now
251
254
  ```
252
255
 
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).
256
+ Details: [docs/entity.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/entity.md) · [docs/repository.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/repository.md) · [docs/query.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/query.md) · [docs/mongodb.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/mongodb.md).
254
257
 
255
258
  ## Your own configuration
256
259
 
@@ -423,7 +426,7 @@ Write `while ctx.running:`, not `while True:` — a loop that never checks makes
423
426
  Ctrl+C look dead for the whole shutdown timeout. The framework says so at
424
427
  startup (`worker.endless_loop`) rather than letting you find out at 2am.
425
428
 
426
- `fam run` starts 4 workers, so a hand-written `while True: sleep(5)` runs
429
+ `fam run --workers 4` starts 4 processes, so a hand-written `while True: sleep(5)` runs
427
430
  **four times**. `single=True` (the default) locks it down: measured 5 runs
428
431
  across 1 process, versus 20 runs across 4 with the lock off. The lock is
429
432
  `flock` (one machine) or Redis (many), picked automatically.
@@ -511,7 +514,7 @@ src/ SAMPLE APPLICATION — not shipped in the package; delete fr
511
514
  core/config.py AppSettings: subclass Settings to add your own .env variables
512
515
  core/lifespan.py application-specific startup / shutdown work
513
516
  api/ business modules; every subdirectory is one module
514
- tests/ 1097 tests that need no infrastructure, 292 more with real drivers/servers
517
+ tests/ 1163 tests that need no infrastructure, 424 more with real drivers/servers
515
518
  docs/ reference documentation (Vietnamese)
516
519
  ```
517
520
 
@@ -550,7 +553,11 @@ Written in Vietnamese, organised for reference rather than reading front to back
550
553
 
551
554
  - [docs/architecture.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/architecture.md) — module layout, DI, the NestJS comparison
552
555
  - [docs/config.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/config.md) — Settings, precedence, adding your own variables
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
556
+ - [docs/database.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/database.md) — SQL: pick a driver (memory / SQLite / PostgreSQL), connections, schema sync
557
+ - [docs/entity.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/entity.md) — declare tables: `@entity`, foreign keys + `on_delete`, unique/index, column length
558
+ - [docs/repository.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/repository.md) — read/write in a service: `find`, `save`, `update`, `delete`
559
+ - [docs/query.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/query.md) — query builder: JOIN, comparisons, NULL, grouping, nested results
560
+ - [docs/transaction.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/transaction.md) — write several tables: all or nothing, SAVEPOINT
554
561
  - [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)
555
562
  - [docs/migrations.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/migrations.md) — Alembic: generate, run, roll back
556
563
  - [docs/websocket.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/websocket.md) — WebSocket gateway, rooms, Postman, Next.js
@@ -26,7 +26,7 @@ public API, and this README, are in English. Start with
26
26
  |---|---|
27
27
  | `@Module()` + module scanning | a directory under `src/api/`, auto-scanned |
28
28
  | `@Controller('users')` | `@controller(prefix="/users", tags=["users"])` |
29
- | `@Get()` `@Post()` `@Patch()` `@Delete()` | `@get()` `@post()` `@patch()` `@delete()` |
29
+ | `@Get()` `@Post()` `@Put()` `@Patch()` `@Delete()` | `@get()` `@post()` `@put()` `@patch()` `@delete()` |
30
30
  | 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 |
31
31
  | `@Injectable()` | `@injectable` |
32
32
  | `@Injectable({scope: Scope.REQUEST})` | `@injectable(scope=Scope.REQUEST)` |
@@ -34,6 +34,7 @@ public API, and this README, are in English. Start with
34
34
  | `@InjectRepository(X) repo: Repository<X>` | `repo: Repository[X]` |
35
35
  | `@Transaction()` / `queryRunner.startTransaction()` | `async with db.transaction():` — nested blocks become SAVEPOINTs |
36
36
  | `queryRunner.rollbackTransaction()` | automatic on exception; `await tx.rollback()` to bail out without raising |
37
+ | `repo.update(criteria, partialEntity)` (TypeORM) | `repo.update("cam-01", payload)` — takes the DTO directly and **returns the updated row** in one SQL statement; `repo.update_where({"zone": "T1"}, status="off")` for many rows |
37
38
  | `repo.createQueryBuilder()` (TypeORM) | `repo.query().join(X).where(Event.score >= …)` — real SQL, `.sql()` to see it |
38
39
  | `Repository.find({where: {score: MoreThan(…)}})` (TypeORM) | `class Event(Entity)` then `.where(Event.score >= …)`, or `.where(score__gte=…)` |
39
40
  | `.groupBy().having()` (TypeORM) | `.group_by(Event.camera_id).select(n=count()).having(count() > 5)` |
@@ -46,6 +47,8 @@ public API, and this README, are in English. Start with
46
47
  | `relations: {camera: {logs: {items: true}}}` (TypeORM) | `.nest_under(Camera, CameraLog, ItemLog)` — one query per level |
47
48
  | *(no TypeORM equivalent)* | `.nest_under(Camera)` — filter on events, get cameras back with them nested |
48
49
  | `@ManyToOne(…, {onDelete: 'CASCADE'})` (TypeORM) | `field(metadata=reference(Camera, on_delete="CASCADE"))` — a real FK in the database |
50
+ | `@PrimaryGeneratedColumn()` / `@PrimaryGeneratedColumn('uuid')` (TypeORM) | `id: int = 0` (database assigns 1, 2, 3…) / `id: str` (the framework generates a UUID) |
51
+ | `@Column({length: 50})` / `@Column({type: 'text'})` (TypeORM) | `field(metadata=column(length=50))` / `column(text=True)` — checked before the write on every backend |
49
52
  | `@UseGuards()` | `guards=[...]` on the controller or a single route |
50
53
  | `@WebSocketGateway()` | `@gateway(path="/ws/…")` |
51
54
  | `@SubscribeMessage('x')` | `@subscribe("x")` |
@@ -95,7 +98,7 @@ same job `package.json` does for `npm i`.
95
98
 
96
99
  ```
97
100
  # requirements.txt, after `fam install sqlite` and `fam install redis`
98
- fastapi-modular[redis,sqlite]>=0.3.0
101
+ fastapi-modular[redis,sqlite]>=0.4.0
99
102
  ```
100
103
 
101
104
  It records the extras, not the individual packages: the version ranges of
@@ -126,7 +129,7 @@ fam: lệnh 'm' chưa rõ — khớp với migrate, module. Gõ thêm vài chữ
126
129
  | `fam init [--name <n>]` | `fam ini` | scaffold **into the current directory**; never overwrites; name defaults to the directory name |
127
130
  | `fam new <name>` | `fam n` | scaffold into a new directory |
128
131
  | `fam dev` | `fam d` | run with autoreload |
129
- | `fam run --workers 4` | `fam r` | run in production mode |
132
+ | `fam run` | `fam r` | run in production mode (1 process; `--workers 4` for more) |
130
133
  | `fam module <name>` | `fam mo` | generate a module: controller + service + dto + entity |
131
134
  | `fam module <name> --gateway` | | plus a WebSocket gateway (`--consumer` for RabbitMQ) |
132
135
  | `fam module <name> --gateway-only` | | add a gateway to an **existing** module (`--consumer-only` for RabbitMQ) |
@@ -180,7 +183,7 @@ fam env sqlite # write .env only, install nothing
180
183
  fam info # what it is connected to right now
181
184
  ```
182
185
 
183
- 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).
186
+ Details: [docs/entity.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/entity.md) · [docs/repository.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/repository.md) · [docs/query.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/query.md) · [docs/mongodb.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/mongodb.md).
184
187
 
185
188
  ## Your own configuration
186
189
 
@@ -353,7 +356,7 @@ Write `while ctx.running:`, not `while True:` — a loop that never checks makes
353
356
  Ctrl+C look dead for the whole shutdown timeout. The framework says so at
354
357
  startup (`worker.endless_loop`) rather than letting you find out at 2am.
355
358
 
356
- `fam run` starts 4 workers, so a hand-written `while True: sleep(5)` runs
359
+ `fam run --workers 4` starts 4 processes, so a hand-written `while True: sleep(5)` runs
357
360
  **four times**. `single=True` (the default) locks it down: measured 5 runs
358
361
  across 1 process, versus 20 runs across 4 with the lock off. The lock is
359
362
  `flock` (one machine) or Redis (many), picked automatically.
@@ -441,7 +444,7 @@ src/ SAMPLE APPLICATION — not shipped in the package; delete fr
441
444
  core/config.py AppSettings: subclass Settings to add your own .env variables
442
445
  core/lifespan.py application-specific startup / shutdown work
443
446
  api/ business modules; every subdirectory is one module
444
- tests/ 1097 tests that need no infrastructure, 292 more with real drivers/servers
447
+ tests/ 1163 tests that need no infrastructure, 424 more with real drivers/servers
445
448
  docs/ reference documentation (Vietnamese)
446
449
  ```
447
450
 
@@ -480,7 +483,11 @@ Written in Vietnamese, organised for reference rather than reading front to back
480
483
 
481
484
  - [docs/architecture.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/architecture.md) — module layout, DI, the NestJS comparison
482
485
  - [docs/config.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/config.md) — Settings, precedence, adding your own variables
483
- - [docs/database.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/database.md) — SQL: memory / SQLite / PostgreSQL — entities, foreign keys, query builder, transactions
486
+ - [docs/database.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/database.md) — SQL: pick a driver (memory / SQLite / PostgreSQL), connections, schema sync
487
+ - [docs/entity.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/entity.md) — declare tables: `@entity`, foreign keys + `on_delete`, unique/index, column length
488
+ - [docs/repository.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/repository.md) — read/write in a service: `find`, `save`, `update`, `delete`
489
+ - [docs/query.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/query.md) — query builder: JOIN, comparisons, NULL, grouping, nested results
490
+ - [docs/transaction.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/transaction.md) — write several tables: all or nothing, SAVEPOINT
484
491
  - [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)
485
492
  - [docs/migrations.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/migrations.md) — Alembic: generate, run, roll back
486
493
  - [docs/websocket.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/websocket.md) — WebSocket gateway, rooms, Postman, Next.js
@@ -21,7 +21,7 @@ fam init && fam dev
21
21
  |---|---|
22
22
  | `@Module()` + module scanning | một thư mục dưới `src/api/`, tự quét |
23
23
  | `@Controller('users')` | `@controller(prefix="/users", tags=["users"])` |
24
- | `@Get()` `@Post()` `@Patch()` `@Delete()` | `@get()` `@post()` `@patch()` `@delete()` |
24
+ | `@Get()` `@Post()` `@Put()` `@Patch()` `@Delete()` | `@get()` `@post()` `@put()` `@patch()` `@delete()` |
25
25
  | handler đồng bộ chạy ngay luồng chính (Nest) | `def` thường chạy ở thread pool, `async def` chạy trên vòng lặp — đúng luật FastAPI |
26
26
  | `@Injectable()` | `@injectable` |
27
27
  | `@Injectable({scope: Scope.REQUEST})` | `@injectable(scope=Scope.REQUEST)` |
@@ -29,6 +29,7 @@ fam init && fam dev
29
29
  | `@InjectRepository(X) repo: Repository<X>` | `repo: Repository[X]` |
30
30
  | `@Transaction()` / `queryRunner.startTransaction()` | `async with db.transaction():` — khối lồng nhau thành SAVEPOINT |
31
31
  | `queryRunner.rollbackTransaction()` | tự động khi có exception; `await tx.rollback()` để huỷ mà không ném lỗi |
32
+ | `repo.update(criteria, partialEntity)` (TypeORM) | `repo.update("cam-01", payload)` — nhận thẳng DTO, **trả về bản ghi đã sửa** trong 1 câu SQL; `repo.update_where({"zone": "T1"}, status="off")` cho nhiều dòng |
32
33
  | `repo.createQueryBuilder()` (TypeORM) | `repo.query().join(X).where(Event.score >= …)` — SQL thật, xem bằng `.sql()` |
33
34
  | `Repository.find({where: {score: MoreThan(…)}})` (TypeORM) | `class Event(Entity)` rồi `.where(Event.score >= …)`, hoặc `.where(score__gte=…)` |
34
35
  | `.groupBy().having()` (TypeORM) | `.group_by(Event.camera_id).select(n=count()).having(count() > 5)` |
@@ -41,6 +42,8 @@ fam init && fam dev
41
42
  | `relations: {camera: {logs: {items: true}}}` (TypeORM) | `.nest_under(Camera, CameraLog, ItemLog)` — mỗi mức một câu lệnh |
42
43
  | *(TypeORM không có)* | `.nest_under(Camera)` — lọc theo sự kiện, nhận về camera kèm sự kiện bên trong |
43
44
  | `@ManyToOne(…, {onDelete: 'CASCADE'})` (TypeORM) | `field(metadata=reference(Camera, on_delete="CASCADE"))` — khoá ngoại THẬT dưới database |
45
+ | `@PrimaryGeneratedColumn()` / `@PrimaryGeneratedColumn('uuid')` (TypeORM) | `id: int = 0` (database phát số 1, 2, 3…) / `id: str` (khung sinh UUID) |
46
+ | `@Column({length: 50})` / `@Column({type: 'text'})` (TypeORM) | `field(metadata=column(length=50))` / `column(text=True)` — chặn ngay lúc ghi, trên mọi backend |
44
47
  | `@UseGuards()` | `guards=[...]` ở controller hoặc từng route |
45
48
  | `@WebSocketGateway()` | `@gateway(path="/ws/…")` |
46
49
  | `@SubscribeMessage('x')` | `@subscribe("x")` |
@@ -87,7 +90,7 @@ phần vào `requirements.txt`** để đồng nghiệp clone repo về chỉ c
87
90
 
88
91
  ```
89
92
  # requirements.txt, sau khi `fam install sqlite` rồi `fam install redis`
90
- fastapi-modular[redis,sqlite]>=0.3.0
93
+ fastapi-modular[redis,sqlite]>=0.4.0
91
94
  ```
92
95
 
93
96
  Nó ghi extras chứ không liệt kê từng gói con: khoảng phiên bản của
@@ -117,7 +120,7 @@ fam: lệnh 'm' chưa rõ — khớp với migrate, module. Gõ thêm vài chữ
117
120
  | `fam init [--name <tên>]` | `fam 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 |
118
121
  | `fam new <tên>` | `fam n` | dựng dự án trong một thư mục mới |
119
122
  | `fam dev` | `fam d` | chạy kèm autoreload |
120
- | `fam run --workers 4` | `fam r` | chạy chế độ production |
123
+ | `fam run` | `fam r` | chạy chế độ production (1 tiến trình; `--workers 4` để nhiều hơn) |
121
124
  | `fam module <tên>` | `fam mo` | sinh module: controller + service + dto + entity |
122
125
  | `fam module <tên> --gateway` | | kèm gateway WebSocket (`--consumer` cho RabbitMQ) |
123
126
  | `fam module <tên> --gateway-only` | | chỉ thêm gateway vào module **đã có** (`--consumer-only` cho RabbitMQ) |
@@ -172,7 +175,7 @@ thì dùng `fam env sqlite`.
172
175
  `fam 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à
173
176
  **mặc định là gì** nếu xoá dòng đi. `fam info` cho biết hiện đang nối vào đâu.
174
177
 
175
- Chi tiết: [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).
178
+ Chi tiết: [docs/entity.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/entity.md) · [docs/repository.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/repository.md) · [docs/query.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/query.md) · [docs/mongodb.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/mongodb.md).
176
179
 
177
180
  ## Cấu hình của riêng bạn
178
181
 
@@ -347,7 +350,7 @@ Viết `while ctx.running:`, đừng viết `while True:` — vòng lặp không
347
350
  làm Ctrl+C trông như chết suốt cả thời gian chờ tắt. Khung kêu ngay lúc khởi
348
351
  động (`worker.endless_loop`) chứ không để bạn phát hiện lúc 2 giờ sáng.
349
352
 
350
- `fam run` bật 4 worker, nên một vòng `while True: sleep(5)` viết tay sẽ chạy
353
+ `fam run --workers 4` chạy 4 tiến trình, nên một vòng `while True: sleep(5)` viết tay sẽ chạy
351
354
  **bốn lần**. Mặc định `single=True` khoá lại: đo được 5 lượt / 1 tiến trình,
352
355
  so với 20 lượt / 4 tiến trình khi tắt khoá. Khoá dùng `flock` (một máy) hoặc
353
356
  Redis (nhiều máy), tự chọn.
@@ -439,7 +442,7 @@ src/ ỨNG DỤNG MẪU — không nằm trong gói cài; xoá th
439
442
  core/config.py AppSettings: kế thừa Settings để thêm biến .env của bạn
440
443
  core/lifespan.py việc lúc khởi động / lúc tắt của riêng ứng dụng
441
444
  api/ các module nghiệp vụ; mỗi thư mục con là một module
442
- tests/ 1097 test chạy không cần hạ tầng, 292 test nữa cần driver/server thật
445
+ tests/ 1163 test chạy không cần hạ tầng, 424 test nữa cần driver/server thật
443
446
  docs/ tài liệu tra cứu
444
447
  ```
445
448
 
@@ -474,7 +477,11 @@ MIT — xem [LICENSE](https://github.com/quanglinh2909/fastapi-modular/blob/main
474
477
 
475
478
  - [docs/architecture.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/architecture.md) — cấu trúc module, DI, đối chiếu NestJS
476
479
  - [docs/config.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/config.md) — Settings, thứ tự ưu tiên, thêm biến của riêng bạn
477
- - [docs/database.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/database.md) — SQL: memory / SQLite / PostgreSQL — entity, khoá ngoại, query builder, transaction
480
+ - [docs/database.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/database.md) — SQL: chọn driver (memory / SQLite / PostgreSQL), kết nối, tự chỉnh schema
481
+ - [docs/entity.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/entity.md) — khai bảng: `@entity`, khoá ngoại + `on_delete`, unique/index, độ dài cột chữ
482
+ - [docs/repository.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/repository.md) — đọc/ghi trong service: `find`, `save`, `update`, `delete`
483
+ - [docs/query.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/query.md) — query builder: JOIN, lớn/bé, NULL, gộp nhóm, dữ liệu lồng nhau
484
+ - [docs/transaction.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/transaction.md) — ghi nhiều bảng: cùng thành công hoặc cùng không
478
485
  - [docs/mongodb.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/mongodb.md) — MongoDB: truy vấn, dữ liệu lồng nhau, và những thứ bên đó không có (không JOIN, không transaction)
479
486
  - [docs/migrations.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/migrations.md) — Alembic: sinh, chạy, lùi migration
480
487
  - [docs/websocket.md](https://github.com/quanglinh2909/fastapi-modular/blob/main/docs/websocket.md) — gateway WebSocket, phòng, Postman, Next.js
@@ -2,7 +2,11 @@
2
2
 
3
3
  | Tài liệu | Nội dung |
4
4
  |---|---|
5
- | [database.md](database.md) | SQL: memory, SQLite, PostgreSQL entity, khoá ngoại, query builder, transaction |
5
+ | [database.md](database.md) | SQL: chọn driver (memory / SQLite / PostgreSQL), kết nối, mất kết nối, tự chỉnh schema |
6
+ | [entity.md](entity.md) | Khai bảng dữ liệu: `@entity`, khoá ngoại + `on_delete`, unique/index, độ dài cột chữ, dấu thời gian |
7
+ | [repository.md](repository.md) | Đọc/ghi trong service: `find`, `save`, `update`/`update_where`, `delete` |
8
+ | [query.md](query.md) | Truy vấn phức tạp: JOIN, lớn/bé, NULL, gộp nhóm, dữ liệu lồng nhau, injection |
9
+ | [transaction.md](transaction.md) | Ghi nhiều bảng: cùng thành công hoặc cùng không, SAVEPOINT |
6
10
  | [mongodb.md](mongodb.md) | MongoDB: truy vấn, dữ liệu lồng nhau, và những thứ bên đó không có |
7
11
  | [config.md](config.md) | Settings: thứ tự ưu tiên, **thêm biến của riêng bạn**, .env |
8
12
  | [architecture.md](architecture.md) | Cấu trúc module, DI container, đối chiếu với NestJS |
@@ -68,15 +68,17 @@ quét nó đi". Xếp khác thì nói ra một lần trong `src/main.py`:
68
68
  | `@Module()` + module scanning | thư mục dưới `src/api/`, tự quét ở `fastapi_modular/discovery.py` |
69
69
  | `@UseGuards()` | `guards=[...]` ở `@controller` hoặc từng route |
70
70
  | `@Controller('users')` | `@controller(prefix="/users", tags=["users"])` |
71
- | `@Get()` `@Post()` `@Patch()` `@Delete()` | `@get()` `@post()` `@patch()` `@delete()` |
71
+ | `@Get()` `@Post()` `@Put()` `@Patch()` `@Delete()` | `@get()` `@post()` `@put()` `@patch()` `@delete()` |
72
72
  | `@Injectable()` | `@injectable` |
73
73
  | `@Injectable({scope: Scope.REQUEST})` | `@injectable(scope=Scope.REQUEST)` |
74
74
  | `forwardRef(() => X)` | `Lazy[X]` |
75
75
  | `@InjectRepository(X) repo: Repository<X>` | `repo: Repository[X]` |
76
- | `repo.createQueryBuilder()` (TypeORM) | `repo.query()` — `join`/`left_join`/`right_join`/`outer_join`, `where`/`or_where`, `group_by`/`having`; xem [database.md](database.md#truy-vấn-phức-tạp--join-lớnbé-null) |
76
+ | `repo.createQueryBuilder()` (TypeORM) | `repo.query()` — `join`/`left_join`/`right_join`/`outer_join`, `where`/`or_where`, `group_by`/`having`; xem [database.md](query.md#truy-vấn-phức-tạp--join-lớnbé-null) |
77
77
  | `find({relations: …})` (TypeORM) | `.include(Event, fields=…, exclude=…)` · `.nest_under(Camera)` — dữ liệu lồng nhau, hai chiều |
78
- | `@ManyToOne(…, {onDelete})` (TypeORM) | `reference(X, on_delete=…)` — xem [database.md](database.md#khoá-ngoại-nối-hai-bảng-với-nhau) |
78
+ | `@ManyToOne(…, {onDelete})` (TypeORM) | `reference(X, on_delete=…)` — xem [database.md](entity.md#khoá-ngoại-nối-hai-bảng-với-nhau) |
79
79
  | `@Entity()` | `@entity` |
80
+ | `@PrimaryGeneratedColumn()` (TypeORM) | `id: int = 0` — xem [entity.md](entity.md#id-chuỗi-uuid-hay-số-tự-tăng) |
81
+ | `@Column({length, type: 'text'})` (TypeORM) | `field(metadata=column(length=50))` — xem [entity.md](entity.md#độ-dài-cột-chữ-varchar50-và-text) |
80
82
  | `extends BaseEntity` | `class X(Entity)` — chỉ để lọc bằng toán tử: `.where(X.score >= 0.8)` |
81
83
  | `overrideProvider()` | `container.override()` |
82
84
  | `@WebSocketGateway()` | `@gateway(path="/ws/…")` |
@@ -194,8 +196,13 @@ chỉ thân hàm là chưa viết, gọi vào trả **501** kèm tên hàm:
194
196
  Ba việc còn lại của bạn:
195
197
 
196
198
  1. Thêm trường vào `entities/<tên>_model.py` và `dto/<tên>_schema.py`
197
- 2. Khai `unique=` / `indexes=` trong `@entity` nếu cần
198
- 3. Viết thân hàm trong `<tên>_service.py` — mỗi hàm có sẵn gợi ý dạng chú thích
199
+ 2. Khai `unique=` / `indexes=` trong `@entity`, độ dài cột chữ bằng `column(length=…)`
200
+ 3. Viết thân hàm trong `<tên>_service.py` — mỗi hàm có sẵn gợi ý dạng chú thích,
201
+ bỏ chú thích ra là chạy được (có test chép-dán-chạy-thật canh điều đó). Gợi ý
202
+ của `update_<tên>` dùng [`repo.update(id, payload)`](repository.md#sửa-dữ-liệu-không-cần-đọc-về-trước)
203
+ — nhận thẳng DTO, một lượt đi database. Cần đọc bản ghi cũ trước khi ghi
204
+ (kiểm trùng, so giá trị cũ) thì dùng `apply_changes` + `save` như
205
+ `src/api/users/user_service.py`.
199
206
 
200
207
  Không phải đăng ký ở đâu cả: không sửa `main.py`, không sửa `api/app.py`.
201
208
 
@@ -227,6 +234,45 @@ Hai lỗi hay gặp, cả hai đều được log cảnh báo lúc boot:
227
234
  - `api.module_without_controller` — thư mục không có `@controller` nào.
228
235
  - `controller.no_routes` — có controller nhưng chưa method nào mang `@get`/`@post`.
229
236
 
237
+ ### Tham số của `@controller`
238
+
239
+ | Tham số | Bắt buộc | Mặc định | Để làm gì |
240
+ |---|---|---|---|
241
+ | `prefix` | không | `""` | tiền tố đường dẫn cho MỌI route trong class: `"/alerts"` |
242
+ | `tags` | không | `None` | nhóm trong trang `/docs`; thường để đúng một tên module |
243
+ | `guards` | không | `()` | guard chạy trước MỌI route của class — xem [operations.md](operations.md#guard) |
244
+ | *(còn lại)* | không | — | mọi tham số khác chuyển thẳng cho `APIRouter` của FastAPI: `dependencies`, `responses`, `deprecated`… |
245
+
246
+ ### Tham số của `@get` / `@post` / `@put` / `@patch` / `@delete`
247
+
248
+ | Tham số | Bắt buộc | Mặc định | Để làm gì |
249
+ |---|---|---|---|
250
+ | `path` | không | `""` | phần đường dẫn nối sau `prefix`. `""` là chính `prefix`; `"/{alert_id}"` là route động |
251
+ | *(còn lại)* | không | — | chuyển thẳng cho `add_api_route` của FastAPI — hay dùng nhất: `response_model`, `status_code`, `summary`, `guards` |
252
+
253
+ `guards` khai ở route thì **cộng dồn** với `guards` của class, chạy sau.
254
+
255
+ ```python
256
+ @controller(prefix="/alerts", tags=["alerts"], guards=[RequireHeader])
257
+ class AlertController:
258
+ @get("/{alert_id}", response_model=AlertOut, summary="Một cảnh báo")
259
+ async def detail(self, alert_id: str) -> AlertOut: ...
260
+
261
+ @delete("/{alert_id}", status_code=204, guards=[ChiAdmin])
262
+ async def remove(self, alert_id: str) -> None: ...
263
+ ```
264
+
265
+ ### Tham số của `@injectable`
266
+
267
+ | Tham số | Bắt buộc | Mặc định | Để làm gì |
268
+ |---|---|---|---|
269
+ | `scope` | không | `Scope.SINGLETON` | `SINGLETON` = một bản cho cả app. `Scope.REQUEST` = mỗi request một bản mới |
270
+
271
+ **Singleton không được nhận provider request-scoped qua `__init__`** — nó sống
272
+ lâu hơn request nên sẽ giữ mãi bản của request đầu tiên. Khung chặn thẳng lúc
273
+ dựng, kèm câu chỉ đường: hoặc cho nó `scope=Scope.REQUEST`, hoặc gọi
274
+ `container.resolve()` ngay trong method.
275
+
230
276
  ### `def` thường hay `async def`?
231
277
 
232
278
  Đúng luật của FastAPI, khung không đổi gì:
@@ -288,7 +334,7 @@ truy vấn database thật (1–10 ms) thì dưới 2%.
288
334
  fam lint # ruff trên `src` (mặc định): F, E, W, I, B, UP, SIM, RUF, BLE
289
335
  fam lint fastapi_modular src tests # soi cả thư viện và test — dùng cái này khi phát triển repo
290
336
  fam lint --fix # tự sửa phần sửa được
291
- fam test # 1097 test trên backend memory (292 test nữa cần hạ tầng hoặc driver thật)
337
+ fam test # 1163 test trên backend memory (424 test nữa cần hạ tầng hoặc driver thật)
292
338
  ```
293
339
 
294
340
  Cấu hình ở [`ruff.toml`](../ruff.toml). Rule `BLE` được bật có chủ ý: mỗi