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.
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/CHANGELOG.md +156 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/PKG-INFO +15 -8
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/README.md +14 -7
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/README.vi.md +14 -7
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/README.md +5 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/architecture.md +52 -6
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/background.md +170 -47
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/config.md +15 -0
- fastapi_modular-0.4.0/docs/database.md +633 -0
- fastapi_modular-0.4.0/docs/entity.md +732 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/migrations.md +15 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/mongodb.md +23 -7
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/operations.md +16 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/providers.md +36 -3
- fastapi_modular-0.4.0/docs/query.md +1030 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/rabbitmq.md +29 -29
- fastapi_modular-0.4.0/docs/repository.md +241 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/rpc.md +16 -9
- fastapi_modular-0.4.0/docs/transaction.md +194 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/websocket.md +5 -5
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/__init__.py +3 -2
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/main.py +7 -2
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/new_module.py +27 -7
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/requirements.py +2 -2
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/serve.py +5 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/config.py +1 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/container.py +22 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/locks.py +1 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/scheduler.py +1 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/workers.py +5 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/__init__.py +9 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/base.py +178 -2
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/circuit.py +10 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/memory.py +72 -10
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/mongo.py +93 -5
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/repository.py +162 -2
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/sql.py +105 -8
- fastapi_modular-0.4.0/tests/test_column_types.py +466 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_drivers.py +37 -1
- fastapi_modular-0.4.0/tests/test_ghi_du_lieu_tu_worker.py +254 -0
- fastapi_modular-0.4.0/tests/test_id_types.py +249 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_module_generator.py +124 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_schema_evolution.py +37 -2
- fastapi_modular-0.4.0/tests/test_update.py +433 -0
- fastapi_modular-0.3.0/docs/database.md +0 -2307
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/.gitignore +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/LICENSE +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/Makefile +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/alembic.ini +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/kafka.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/mqtt.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/docs/redis.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/clean.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/configure_env.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/info.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/install.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/new_project.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/new_provider.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/cli/tools.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/clock.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/compat.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/context.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/cron.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/error_handlers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/events.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/exceptions.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/guards.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/jobs.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/lifespan.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/logging.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/providers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/rpc.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/schemas.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/adapter.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/gateway.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/namespace.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/protocol.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/server.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/core/websocket/socket.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/discovery.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/factory.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/factory.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/database/query.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/broker.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/consumers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/kafka/responders.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/client.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/consumers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/patterns.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/mqtt/responders.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/broker.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/consumers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/patterns.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/rabbitmq/responders.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/client.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/pubsub.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/infrastructure/redis/responders.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/middleware/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/middleware/request_context.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/fastapi_modular/py.typed +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/migrations/env.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/migrations/script.py.mako +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/migrations/versions/.gitkeep +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/migrations/versions/20260821_1020_tao_bang_users_va_devices.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/pyproject.toml +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/pytest.ini +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/ruff.toml +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/chat_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/chat_gateway.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/dto/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/chat/dto/chat_dto.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/device_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/device_service.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/dto/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/dto/device_dto.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/entities/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/devices/entities/device_model.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/health/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/health/health_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/kafka_test/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/kafka_test/kafka_consumer.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/kafka_test/kafka_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/metrics/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/metrics/metrics_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/mqtt_test/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/mqtt_test/mqtt_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/mqtt_test/mqtt_listener.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/rabbitmq_test/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/rabbitmq_test/rabbitmq_consumer.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/rabbitmq_test/rabbitmq_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/redis_test/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/redis_test/redis_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/redis_test/redis_listener.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/dto/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/dto/user_dto.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/entities/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/entities/user_model.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/user_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/api/users/user_service.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/core/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/core/config.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/core/lifespan.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/src/main.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/conftest.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/fixtures/cron_vectors.json +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/fixtures/nestjs_patterns.json +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_circuit.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_cli.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_configure_env.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_container.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_controller_dong_bo.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_cron.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_cross_module.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_database.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_enum_parity.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_events.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_foreign_keys.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_guards.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_ha_tang_that.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_health.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_injection.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_jobs.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_kafka.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_kafka_offline.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_lifespan_rieng.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_mongo_query.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_mqtt.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_mqtt_offline.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_providers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_query_builder.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_rabbitmq.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_rabbitmq_offline.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_redis.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_redis_offline.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_requirements_ghi_nho.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_resilience.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_rpc.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_scheduler.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_schemas.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_settings_mo_rong.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_sqlite_pragmas.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_transaction.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_tuong_thich_phien_ban.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_uniqueness.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_users_api.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_websocket.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.4.0}/tests/test_workers.py +0 -0
- {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
|
-
## [
|
|
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
|
+
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.
|
|
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
|
|
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/
|
|
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
|
|
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/
|
|
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
|
|
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.
|
|
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
|
|
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/
|
|
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
|
|
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/
|
|
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
|
|
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.
|
|
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
|
|
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/
|
|
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`
|
|
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/
|
|
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
|
|
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
|
|
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](
|
|
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](
|
|
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
|
|
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 #
|
|
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
|