fastapi-modular 0.3.0__tar.gz → 0.3.1__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.3.1}/CHANGELOG.md +28 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/PKG-INFO +3 -3
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/README.md +2 -2
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/README.vi.md +2 -2
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/architecture.md +1 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/background.md +21 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/__init__.py +1 -1
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/container.py +22 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/workers.py +5 -1
- fastapi_modular-0.3.1/tests/test_ghi_du_lieu_tu_worker.py +254 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/.gitignore +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/LICENSE +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/Makefile +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/alembic.ini +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/README.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/config.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/database.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/kafka.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/migrations.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/mongodb.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/mqtt.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/operations.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/providers.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/rabbitmq.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/redis.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/rpc.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/docs/websocket.md +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/clean.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/configure_env.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/info.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/install.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/main.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/new_module.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/new_project.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/new_provider.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/requirements.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/serve.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/cli/tools.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/clock.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/compat.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/config.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/context.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/cron.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/error_handlers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/events.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/exceptions.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/guards.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/jobs.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/lifespan.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/locks.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/logging.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/providers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/rpc.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/scheduler.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/schemas.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/websocket/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/websocket/adapter.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/websocket/gateway.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/websocket/namespace.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/websocket/protocol.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/websocket/server.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/core/websocket/socket.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/discovery.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/factory.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/base.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/circuit.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/factory.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/memory.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/mongo.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/query.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/repository.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/sql.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/kafka/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/kafka/broker.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/kafka/consumers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/kafka/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/kafka/responders.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/mqtt/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/mqtt/client.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/mqtt/consumers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/mqtt/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/mqtt/patterns.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/mqtt/responders.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/rabbitmq/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/rabbitmq/broker.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/rabbitmq/consumers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/rabbitmq/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/rabbitmq/patterns.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/rabbitmq/responders.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/redis/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/redis/client.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/redis/metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/redis/pubsub.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/redis/responders.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/middleware/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/middleware/request_context.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/py.typed +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/migrations/env.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/migrations/script.py.mako +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/migrations/versions/.gitkeep +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/migrations/versions/20260821_1020_tao_bang_users_va_devices.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/pyproject.toml +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/pytest.ini +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/ruff.toml +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/chat/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/chat/chat_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/chat/chat_gateway.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/chat/dto/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/chat/dto/chat_dto.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/devices/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/devices/device_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/devices/device_service.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/devices/dto/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/devices/dto/device_dto.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/devices/entities/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/devices/entities/device_model.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/health/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/health/health_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/kafka_test/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/kafka_test/kafka_consumer.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/kafka_test/kafka_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/metrics/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/metrics/metrics_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/mqtt_test/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/mqtt_test/mqtt_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/mqtt_test/mqtt_listener.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/rabbitmq_test/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/rabbitmq_test/rabbitmq_consumer.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/rabbitmq_test/rabbitmq_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/redis_test/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/redis_test/redis_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/redis_test/redis_listener.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/users/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/users/dto/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/users/dto/user_dto.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/users/entities/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/users/entities/user_model.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/users/user_controller.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/api/users/user_service.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/core/__init__.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/core/config.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/core/lifespan.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/src/main.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/conftest.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/fixtures/cron_vectors.json +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/fixtures/nestjs_patterns.json +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_circuit.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_cli.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_configure_env.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_container.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_controller_dong_bo.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_cron.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_cross_module.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_database.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_drivers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_enum_parity.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_events.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_foreign_keys.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_guards.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_ha_tang_that.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_health.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_injection.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_jobs.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_kafka.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_kafka_offline.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_lifespan_rieng.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_metrics.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_module_generator.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_mongo_query.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_mqtt.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_mqtt_offline.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_providers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_query_builder.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_rabbitmq.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_rabbitmq_offline.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_redis.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_redis_offline.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_requirements_ghi_nho.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_resilience.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_rpc.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_scheduler.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_schema_evolution.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_schemas.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_settings_mo_rong.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_sqlite_pragmas.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_transaction.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_tuong_thich_phien_ban.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_uniqueness.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_users_api.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_websocket.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_workers.py +0 -0
- {fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/tests/test_ws_adapter.py +0 -0
|
@@ -5,6 +5,34 @@ Theo [Keep a Changelog](https://keepachangelog.com/vi/1.1.0/); phiên bản theo
|
|
|
5
5
|
|
|
6
6
|
## [Chưa phát hành]
|
|
7
7
|
|
|
8
|
+
## [0.3.1] — 2026-08-27
|
|
9
|
+
|
|
10
|
+
### Sửa
|
|
11
|
+
|
|
12
|
+
- **Worker ghi database không được commit.** `contextvars` được sao chép khi tạo
|
|
13
|
+
Task/Thread, nên `@worker` sinh ra từ trong một HTTP request — hoặc từ
|
|
14
|
+
`@interval`/`@job`, vốn cũng mở request scope — thừa hưởng đúng store của
|
|
15
|
+
request đó. Mà `SqlUnitOfWork` là provider request-scoped: nó mở transaction
|
|
16
|
+
rồi chỉ commit ở `on_request_end`. Worker sống lâu hơn request nên transaction
|
|
17
|
+
ấy không bao giờ được commit.
|
|
18
|
+
|
|
19
|
+
Kiểu hỏng này im lặng đến khó chịu:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
print("Deleted:", await repo.delete(row.id)) # -> True
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`True` là đúng — DELETE khớp một dòng, và câu SELECT ngay sau cũng thấy dữ
|
|
26
|
+
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
|
|
27
|
+
là mất sạch.
|
|
28
|
+
|
|
29
|
+
Nay worker được cắt khỏi request scope thừa hưởng (`detach_request_scope`),
|
|
30
|
+
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
|
|
31
|
+
`async with db.transaction():` — vẫn dùng được trong `ctx.run`.
|
|
32
|
+
|
|
33
|
+
Hệ quả có thể thấy: `container.resolve(<provider Scope.REQUEST>)` trong worker
|
|
34
|
+
giờ báo lỗi thay vì trả về một instance mồ côi. Đó là cố ý.
|
|
35
|
+
|
|
8
36
|
## [0.3.0] — 2026-08-27
|
|
9
37
|
|
|
10
38
|
Bản này thêm **query builder** (JOIN, khoá ngoại, transaction, dữ liệu lồng
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fastapi-modular
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -165,7 +165,7 @@ same job `package.json` does for `npm i`.
|
|
|
165
165
|
|
|
166
166
|
```
|
|
167
167
|
# requirements.txt, after `fam install sqlite` and `fam install redis`
|
|
168
|
-
fastapi-modular[redis,sqlite]>=0.3.
|
|
168
|
+
fastapi-modular[redis,sqlite]>=0.3.1
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
It records the extras, not the individual packages: the version ranges of
|
|
@@ -511,7 +511,7 @@ src/ SAMPLE APPLICATION — not shipped in the package; delete fr
|
|
|
511
511
|
core/config.py AppSettings: subclass Settings to add your own .env variables
|
|
512
512
|
core/lifespan.py application-specific startup / shutdown work
|
|
513
513
|
api/ business modules; every subdirectory is one module
|
|
514
|
-
tests/
|
|
514
|
+
tests/ 1101 tests that need no infrastructure, 292 more with real drivers/servers
|
|
515
515
|
docs/ reference documentation (Vietnamese)
|
|
516
516
|
```
|
|
517
517
|
|
|
@@ -95,7 +95,7 @@ same job `package.json` does for `npm i`.
|
|
|
95
95
|
|
|
96
96
|
```
|
|
97
97
|
# requirements.txt, after `fam install sqlite` and `fam install redis`
|
|
98
|
-
fastapi-modular[redis,sqlite]>=0.3.
|
|
98
|
+
fastapi-modular[redis,sqlite]>=0.3.1
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
It records the extras, not the individual packages: the version ranges of
|
|
@@ -441,7 +441,7 @@ src/ SAMPLE APPLICATION — not shipped in the package; delete fr
|
|
|
441
441
|
core/config.py AppSettings: subclass Settings to add your own .env variables
|
|
442
442
|
core/lifespan.py application-specific startup / shutdown work
|
|
443
443
|
api/ business modules; every subdirectory is one module
|
|
444
|
-
tests/
|
|
444
|
+
tests/ 1101 tests that need no infrastructure, 292 more with real drivers/servers
|
|
445
445
|
docs/ reference documentation (Vietnamese)
|
|
446
446
|
```
|
|
447
447
|
|
|
@@ -87,7 +87,7 @@ phần vào `requirements.txt`** để đồng nghiệp clone repo về chỉ c
|
|
|
87
87
|
|
|
88
88
|
```
|
|
89
89
|
# requirements.txt, sau khi `fam install sqlite` rồi `fam install redis`
|
|
90
|
-
fastapi-modular[redis,sqlite]>=0.3.
|
|
90
|
+
fastapi-modular[redis,sqlite]>=0.3.1
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
Nó ghi extras chứ không liệt kê từng gói con: khoảng phiên bản của
|
|
@@ -439,7 +439,7 @@ src/ ỨNG DỤNG MẪU — không nằm trong gói cài; xoá th
|
|
|
439
439
|
core/config.py AppSettings: kế thừa Settings để thêm biến .env của bạn
|
|
440
440
|
core/lifespan.py việc lúc khởi động / lúc tắt của riêng ứng dụng
|
|
441
441
|
api/ các module nghiệp vụ; mỗi thư mục con là một module
|
|
442
|
-
tests/
|
|
442
|
+
tests/ 1101 test chạy không cần hạ tầng, 292 test nữa cần driver/server thật
|
|
443
443
|
docs/ tài liệu tra cứu
|
|
444
444
|
```
|
|
445
445
|
|
|
@@ -288,7 +288,7 @@ truy vấn database thật (1–10 ms) thì dưới 2%.
|
|
|
288
288
|
fam lint # ruff trên `src` (mặc định): F, E, W, I, B, UP, SIM, RUF, BLE
|
|
289
289
|
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
290
|
fam lint --fix # tự sửa phần sửa được
|
|
291
|
-
fam test #
|
|
291
|
+
fam test # 1101 test trên backend memory (292 test nữa cần hạ tầng hoặc driver thật)
|
|
292
292
|
```
|
|
293
293
|
|
|
294
294
|
Cấu hình ở [`ruff.toml`](../ruff.toml). Rule `BLE` được bật có chủ ý: mỗi
|
|
@@ -642,6 +642,26 @@ ctx.run(self._repo.save(event))
|
|
|
642
642
|
Đừng gọi `asyncio.run()` — nó tạo một event loop MỚI, mà connection pool của
|
|
643
643
|
database thuộc về loop cũ, và nó sẽ hỏng theo những cách rất khó hiểu.
|
|
644
644
|
|
|
645
|
+
**Mỗi lời gọi tự commit.** Worker không nằm trong request nào, nên mỗi
|
|
646
|
+
`save`/`delete` là một transaction riêng, xong là xong. Muốn nhiều lệnh ghi
|
|
647
|
+
"cùng thành công hoặc cùng không" thì bọc lại — `db.transaction()` dùng được
|
|
648
|
+
trong `ctx.run` như thường:
|
|
649
|
+
|
|
650
|
+
```python
|
|
651
|
+
async def chuyen_kho(self, tu: str, den: str) -> None:
|
|
652
|
+
async with self._db.transaction():
|
|
653
|
+
await self._repo.save(...)
|
|
654
|
+
await self._repo.delete(...)
|
|
655
|
+
|
|
656
|
+
# trong worker:
|
|
657
|
+
ctx.run(self._service.chuyen_kho("A", "B"))
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
**Đừng resolve provider request-scoped trong worker.** Worker sống lâu hơn thứ
|
|
661
|
+
sinh ra nó, nên khung cắt nó khỏi request scope — `container.resolve(Principal)`
|
|
662
|
+
hay bất cứ provider `Scope.REQUEST` nào cũng sẽ báo lỗi ngay. Đó là cố ý: không
|
|
663
|
+
có request thì không có "danh tính của request này".
|
|
664
|
+
|
|
645
665
|
---
|
|
646
666
|
|
|
647
667
|
## Hỏng thì tra ở đây
|
|
@@ -663,6 +683,7 @@ database thuộc về loop cũ, và nó sẽ hỏng theo những cách rất kh
|
|
|
663
683
|
| **`ctx.blocking` chậm dần khi thêm worker** | vượt trần pool — nới `APP_WORKERS__THREAD_POOL_SIZE` |
|
|
664
684
|
| **`workers_restarted_total` tăng đều** | camera rớt mạng, hoặc vòng lặp ném lỗi mỗi lượt — xem log `worker.crashed` |
|
|
665
685
|
| **`worker.stop_timeout` lúc tắt app** | vòng lặp không kiểm `ctx.running`, hoặc lời gọi chặn không có timeout |
|
|
686
|
+
| **Ghi/xoá trong worker: `delete()` trả `True` mà dữ liệu vẫn còn** | Bản **trước 0.3.1**: worker thừa hưởng transaction của request sinh ra nó và không ai commit. Nâng cấp: `pip install -U fastapi-modular`. Nhận ra nó bằng cách đọc database bằng **công cụ khác** (`sqlite3 app.db "SELECT …"`), chứ hỏi lại repository thì vẫn thấy "xong rồi" |
|
|
666
687
|
| **Ctrl+C bấm mà không có gì xảy ra** | xem ngay dưới |
|
|
667
688
|
|
|
668
689
|
### Ctrl+C như không ăn
|
|
@@ -42,7 +42,7 @@ from fastapi_modular.discovery import DEFAULT_PACKAGE, register_routes
|
|
|
42
42
|
from fastapi_modular.factory import add_middleware, bind_settings, create_app, new_fastapi
|
|
43
43
|
from fastapi_modular.infrastructure.database.base import Entity, reference
|
|
44
44
|
|
|
45
|
-
__version__ = "0.3.
|
|
45
|
+
__version__ = "0.3.1"
|
|
46
46
|
|
|
47
47
|
__all__ = [
|
|
48
48
|
"DEFAULT_PACKAGE",
|
|
@@ -363,6 +363,28 @@ async def request_scope() -> AsyncIterator[dict[str, Any]]:
|
|
|
363
363
|
_request_store.reset(token)
|
|
364
364
|
|
|
365
365
|
|
|
366
|
+
def detach_request_scope() -> None:
|
|
367
|
+
"""Cắt khỏi request scope THỪA HƯỞNG từ nơi sinh ra, cho việc chạy nền.
|
|
368
|
+
|
|
369
|
+
Vì sao cần: `contextvars` được sao chép khi tạo Task hay Thread, nên một
|
|
370
|
+
`@worker` sinh ra từ trong một HTTP request — hoặc từ một `@interval` /
|
|
371
|
+
`@job`, vốn cũng mở request scope — sẽ thấy đúng cái store đó, dù request ấy
|
|
372
|
+
đã đóng từ lâu.
|
|
373
|
+
|
|
374
|
+
Hậu quả đo được, và nó im lặng đến khó chịu: `SqlUnitOfWork` là provider
|
|
375
|
+
request-scoped, mở một transaction rồi chỉ commit ở `on_request_end`. Worker
|
|
376
|
+
thừa hưởng nó thì mọi lệnh ghi rơi vào một transaction KHÔNG BAO GIỜ được
|
|
377
|
+
commit — `repo.delete()` vẫn trả `True` (đúng, nó khớp một dòng), câu SELECT
|
|
378
|
+
ngay sau đó vẫn thấy dữ liệu mới (cùng connection), nhưng trên đĩa không có
|
|
379
|
+
gì đổi và tắt app là mất sạch.
|
|
380
|
+
|
|
381
|
+
Cắt rồi thì `_conn()` rơi về nhánh "ngoài request": mỗi thao tác một
|
|
382
|
+
`engine.begin()`, tự commit — đúng như docs mô tả cho worker/job/script.
|
|
383
|
+
Cần gộp nhiều lệnh ghi thì bọc `async with db.transaction():`.
|
|
384
|
+
"""
|
|
385
|
+
_request_store.set(None)
|
|
386
|
+
|
|
387
|
+
|
|
366
388
|
def Inject(token: type[T] | str) -> Any:
|
|
367
389
|
"""Cầu nối container -> FastAPI Depends.
|
|
368
390
|
|
|
@@ -79,7 +79,7 @@ from dataclasses import dataclass
|
|
|
79
79
|
from typing import Any
|
|
80
80
|
|
|
81
81
|
from fastapi_modular.core.config import Settings
|
|
82
|
-
from fastapi_modular.core.container import container, injectable
|
|
82
|
+
from fastapi_modular.core.container import container, detach_request_scope, injectable
|
|
83
83
|
from fastapi_modular.core.context import new_request_id, reset_request_id, set_request_id
|
|
84
84
|
from fastapi_modular.core.exceptions import BadRequestError, ServiceUnavailableError
|
|
85
85
|
from fastapi_modular.core.locks import NoLock, SingleFlight, build_lock
|
|
@@ -621,6 +621,10 @@ class WorkerPool:
|
|
|
621
621
|
self, spec: WorkerSpec, instance: Any, data: Any, context: WorkerContext
|
|
622
622
|
) -> None:
|
|
623
623
|
"""Chạy vòng lặp, và dựng lại khi nó hỏng."""
|
|
624
|
+
# Worker sống lâu hơn thứ sinh ra nó, nên không được dùng chung request
|
|
625
|
+
# scope với nó — xem `detach_request_scope`. Đặt ở đây là đủ cho cả
|
|
626
|
+
# thread (`copy_context` lấy từ context của Task này) lẫn `ctx.run`.
|
|
627
|
+
detach_request_scope()
|
|
624
628
|
if spec.single and not await self._become_owner(spec, context):
|
|
625
629
|
return
|
|
626
630
|
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Worker ghi database thì dữ liệu phải THẬT SỰ tới đĩa.
|
|
2
|
+
|
|
3
|
+
Vì sao có file này. `contextvars` được sao chép khi tạo Task/Thread, nên một
|
|
4
|
+
`@worker` sinh ra từ trong một HTTP request — hoặc từ `@interval`/`@job`, vốn
|
|
5
|
+
cũng mở request scope — thừa hưởng đúng cái store của request đó. Mà
|
|
6
|
+
`SqlUnitOfWork` là provider request-scoped: nó mở một transaction rồi chỉ commit
|
|
7
|
+
ở `on_request_end`. Worker sống lâu hơn request, nên transaction ấy KHÔNG BAO
|
|
8
|
+
GIỜ được commit.
|
|
9
|
+
|
|
10
|
+
Kiểu hỏng này im lặng đến khó chịu, và người dùng thật đã mất buổi vì nó:
|
|
11
|
+
|
|
12
|
+
print("Deleted device offline:", await repo.delete(row.id))
|
|
13
|
+
# -> True
|
|
14
|
+
|
|
15
|
+
`True` là đúng: DELETE khớp một dòng. Câu SELECT ngay sau cũng thấy dữ liệu mới,
|
|
16
|
+
vì cùng một connection. Chỉ có điều trên đĩa không có gì đổi, và tắt app là mất
|
|
17
|
+
sạch.
|
|
18
|
+
|
|
19
|
+
Nên mọi phép kiểm ở đây đọc bằng **một kết nối sqlite KHÁC** (`tren_dia`), chứ
|
|
20
|
+
không hỏi lại chính repository — hỏi lại repository là hỏi đúng cái connection
|
|
21
|
+
đang giữ transaction chưa commit, và nó sẽ trả lời "xong rồi" trong mọi trường
|
|
22
|
+
hợp.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import importlib.util
|
|
28
|
+
import os
|
|
29
|
+
import sqlite3
|
|
30
|
+
import time
|
|
31
|
+
import uuid
|
|
32
|
+
from dataclasses import dataclass, field
|
|
33
|
+
from datetime import datetime
|
|
34
|
+
|
|
35
|
+
import pytest
|
|
36
|
+
from fastapi.testclient import TestClient
|
|
37
|
+
|
|
38
|
+
from fastapi_modular import Entity, entity, injectable
|
|
39
|
+
from fastapi_modular.core.clock import utcnow
|
|
40
|
+
from fastapi_modular.core.config import (
|
|
41
|
+
DatabaseSettings,
|
|
42
|
+
KafkaSettings,
|
|
43
|
+
MqttSettings,
|
|
44
|
+
RabbitSettings,
|
|
45
|
+
RedisSettings,
|
|
46
|
+
Settings,
|
|
47
|
+
WebSocketSettings,
|
|
48
|
+
)
|
|
49
|
+
from fastapi_modular.core.controller import build_router, controller, post
|
|
50
|
+
from fastapi_modular.core.workers import WorkerContext, worker
|
|
51
|
+
from fastapi_modular.factory import create_app
|
|
52
|
+
from fastapi_modular.infrastructure.database import Repository
|
|
53
|
+
from fastapi_modular.infrastructure.database.repository import Database
|
|
54
|
+
|
|
55
|
+
CO_SQLITE = importlib.util.find_spec("aiosqlite") is not None
|
|
56
|
+
pytestmark = pytest.mark.skipif(not CO_SQLITE, reason="cần aiosqlite")
|
|
57
|
+
|
|
58
|
+
DA_CHAY: set[str] = set()
|
|
59
|
+
DB_PATH = ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@entity(name="wk_rows")
|
|
63
|
+
@dataclass(slots=True)
|
|
64
|
+
class WkRow(Entity):
|
|
65
|
+
id: str
|
|
66
|
+
nguon: str = ""
|
|
67
|
+
created_at: datetime = field(default_factory=utcnow)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def tren_dia() -> list[str]:
|
|
71
|
+
"""Đọc bằng kết nối KHÁC — chỉ thấy thứ đã COMMIT."""
|
|
72
|
+
conn = sqlite3.connect(DB_PATH)
|
|
73
|
+
try:
|
|
74
|
+
return sorted(r[0] for r in conn.execute("SELECT id FROM wk_rows"))
|
|
75
|
+
finally:
|
|
76
|
+
conn.close()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@injectable
|
|
80
|
+
class WkService:
|
|
81
|
+
def __init__(self, repo: Repository[WkRow], db: Database) -> None:
|
|
82
|
+
self._repo = repo
|
|
83
|
+
self._db = db
|
|
84
|
+
|
|
85
|
+
async def them(self, id_: str) -> None:
|
|
86
|
+
await self._repo.save(WkRow(id=id_, nguon="worker"))
|
|
87
|
+
|
|
88
|
+
async def xoa(self, id_: str) -> bool:
|
|
89
|
+
return await self._repo.delete(id_)
|
|
90
|
+
|
|
91
|
+
async def them_roi_huy(self, id_: str) -> None:
|
|
92
|
+
"""Ghi trong transaction rồi ném lỗi — không được để lại gì."""
|
|
93
|
+
try:
|
|
94
|
+
async with self._db.transaction():
|
|
95
|
+
await self._repo.save(WkRow(id=id_, nguon="worker"))
|
|
96
|
+
raise RuntimeError("hỏng giữa chừng")
|
|
97
|
+
except RuntimeError:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@injectable
|
|
102
|
+
class WkWorkers:
|
|
103
|
+
def __init__(self, service: WkService) -> None:
|
|
104
|
+
self._service = service
|
|
105
|
+
|
|
106
|
+
@worker("wk-ghi", thread=True)
|
|
107
|
+
def ghi_trong_thread(self, data: dict, ctx: WorkerContext) -> None:
|
|
108
|
+
"""Đúng hình dạng của worker thật: `def` thường + `ctx.run(...)`."""
|
|
109
|
+
ctx.run(self._service.them(data["id"]))
|
|
110
|
+
DA_CHAY.add("da-ghi")
|
|
111
|
+
while ctx.running:
|
|
112
|
+
ctx.wait(0.02)
|
|
113
|
+
|
|
114
|
+
@worker("wk-xoa", thread=True)
|
|
115
|
+
def ghi_roi_xoa(self, data: dict, ctx: WorkerContext) -> None:
|
|
116
|
+
ctx.run(self._service.them(data["id"]))
|
|
117
|
+
DA_CHAY.add("da-ghi-lan-hai")
|
|
118
|
+
ctx.run(self._service.xoa(data["id"]))
|
|
119
|
+
DA_CHAY.add("da-xoa")
|
|
120
|
+
while ctx.running:
|
|
121
|
+
ctx.wait(0.02)
|
|
122
|
+
|
|
123
|
+
@worker("wk-tx", thread=True)
|
|
124
|
+
def ghi_roi_huy(self, data: dict, ctx: WorkerContext) -> None:
|
|
125
|
+
ctx.run(self._service.them_roi_huy(data["id"]))
|
|
126
|
+
DA_CHAY.add("da-huy")
|
|
127
|
+
while ctx.running:
|
|
128
|
+
ctx.wait(0.02)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@controller(prefix="/wk")
|
|
132
|
+
class WkController:
|
|
133
|
+
def __init__(self, workers: WkWorkers) -> None:
|
|
134
|
+
self._workers = workers
|
|
135
|
+
|
|
136
|
+
@post("/ghi")
|
|
137
|
+
async def ghi(self, id_: str) -> dict:
|
|
138
|
+
"""Sinh worker TỪ TRONG một HTTP request — ca nguy hiểm nhất.
|
|
139
|
+
|
|
140
|
+
Ở đây request scope chắc chắn đang mở, nên worker sao chép được nó.
|
|
141
|
+
"""
|
|
142
|
+
await self._workers.ghi_trong_thread(id_, {"id": id_})
|
|
143
|
+
return {"ok": True}
|
|
144
|
+
|
|
145
|
+
@post("/ghi-xoa")
|
|
146
|
+
async def ghi_xoa(self, id_: str) -> dict:
|
|
147
|
+
await self._workers.ghi_roi_xoa(id_, {"id": id_})
|
|
148
|
+
return {"ok": True}
|
|
149
|
+
|
|
150
|
+
@post("/ghi-huy")
|
|
151
|
+
async def ghi_huy(self, id_: str) -> dict:
|
|
152
|
+
await self._workers.ghi_roi_huy(id_, {"id": id_})
|
|
153
|
+
return {"ok": True}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@pytest.fixture
|
|
157
|
+
def client(tmp_path):
|
|
158
|
+
global DB_PATH
|
|
159
|
+
DB_PATH = str(tmp_path / f"{uuid.uuid4().hex}.db")
|
|
160
|
+
DA_CHAY.clear()
|
|
161
|
+
|
|
162
|
+
settings = Settings(
|
|
163
|
+
APP_ENV="local",
|
|
164
|
+
APP_DEBUG=True,
|
|
165
|
+
APP_DB=DatabaseSettings(driver="sqlite", dsn=f"sqlite+aiosqlite:///{DB_PATH}"),
|
|
166
|
+
APP_RABBITMQ=RabbitSettings(enabled=False),
|
|
167
|
+
APP_REDIS=RedisSettings(enabled=False),
|
|
168
|
+
APP_MQTT=MqttSettings(enabled=False),
|
|
169
|
+
APP_KAFKA=KafkaSettings(enabled=False),
|
|
170
|
+
APP_WS=WebSocketSettings(adapter="local"),
|
|
171
|
+
)
|
|
172
|
+
app = create_app(settings, package="tests")
|
|
173
|
+
app.include_router(build_router(WkController), prefix="/api")
|
|
174
|
+
with TestClient(app) as client:
|
|
175
|
+
yield client
|
|
176
|
+
assert os.path.exists(DB_PATH)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def cho(dau_moc: str, han: float = 5.0) -> None:
|
|
180
|
+
"""Chờ worker chạy tới mốc, tối đa `han` giây (đừng ngủ theo đồng hồ)."""
|
|
181
|
+
moc = time.monotonic()
|
|
182
|
+
while dau_moc not in DA_CHAY and time.monotonic() - moc < han:
|
|
183
|
+
time.sleep(0.01)
|
|
184
|
+
assert dau_moc in DA_CHAY, f"worker không tới được mốc {dau_moc!r}"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def cho_tren_dia(mong_doi: list[str], han: float = 5.0) -> None:
|
|
188
|
+
moc = time.monotonic()
|
|
189
|
+
while tren_dia() != mong_doi and time.monotonic() - moc < han:
|
|
190
|
+
time.sleep(0.01)
|
|
191
|
+
assert tren_dia() == mong_doi
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_worker_ghi_thi_du_lieu_toi_dia(client):
|
|
195
|
+
"""Ca chính: trước đây câu INSERT nằm trong transaction không ai commit."""
|
|
196
|
+
client.post("/api/wk/ghi", params={"id_": "r1"})
|
|
197
|
+
cho("da-ghi")
|
|
198
|
+
cho_tren_dia(["r1"])
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_worker_xoa_thi_du_lieu_bien_khoi_dia(client):
|
|
202
|
+
"""`delete()` trả True mà dòng vẫn còn trên đĩa — đúng lỗi người dùng gặp."""
|
|
203
|
+
client.post("/api/wk/ghi-xoa", params={"id_": "r2"})
|
|
204
|
+
cho("da-xoa")
|
|
205
|
+
cho_tren_dia([])
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_worker_khong_dung_chung_UnitOfWork_voi_request_sinh_ra_no(client):
|
|
209
|
+
"""Chốt đúng cơ chế, không chỉ chốt hệ quả.
|
|
210
|
+
|
|
211
|
+
Worker phải KHÔNG resolve được provider request-scoped: nó sống lâu hơn
|
|
212
|
+
request sinh ra nó, nên dùng chung là dùng một transaction đã mồ côi.
|
|
213
|
+
"""
|
|
214
|
+
from fastapi_modular.core.container import container
|
|
215
|
+
from fastapi_modular.infrastructure.database.sql import SqlUnitOfWork
|
|
216
|
+
|
|
217
|
+
ket_qua: list[str] = []
|
|
218
|
+
|
|
219
|
+
@injectable
|
|
220
|
+
class Soi:
|
|
221
|
+
@worker("wk-soi", thread=True)
|
|
222
|
+
def soi(self, data: dict, ctx: WorkerContext) -> None:
|
|
223
|
+
try:
|
|
224
|
+
container.resolve(SqlUnitOfWork)
|
|
225
|
+
ket_qua.append("resolve được")
|
|
226
|
+
except RuntimeError:
|
|
227
|
+
ket_qua.append("đã cắt")
|
|
228
|
+
DA_CHAY.add("da-soi")
|
|
229
|
+
while ctx.running:
|
|
230
|
+
ctx.wait(0.02)
|
|
231
|
+
|
|
232
|
+
@controller(prefix="/soi")
|
|
233
|
+
class SoiController:
|
|
234
|
+
def __init__(self, soi: Soi) -> None:
|
|
235
|
+
self._soi = soi
|
|
236
|
+
|
|
237
|
+
@post("")
|
|
238
|
+
async def chay(self) -> dict:
|
|
239
|
+
await self._soi.soi("k", {})
|
|
240
|
+
return {"ok": True}
|
|
241
|
+
|
|
242
|
+
client.app.include_router(build_router(SoiController), prefix="/api")
|
|
243
|
+
client.post("/api/soi")
|
|
244
|
+
cho("da-soi")
|
|
245
|
+
|
|
246
|
+
assert ket_qua == ["đã cắt"]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def test_transaction_trong_worker_van_huy_duoc(client):
|
|
250
|
+
"""Cắt request scope không được làm mất `db.transaction()` của worker."""
|
|
251
|
+
client.post("/api/wk/ghi-huy", params={"id_": "r3"})
|
|
252
|
+
cho("da-huy")
|
|
253
|
+
time.sleep(0.2) # cho nó cơ hội ghi sai, nếu có
|
|
254
|
+
assert tren_dia() == [], "khối transaction ném lỗi thì không được để lại gì"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/__init__.py
RENAMED
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/base.py
RENAMED
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/circuit.py
RENAMED
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/factory.py
RENAMED
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/memory.py
RENAMED
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/mongo.py
RENAMED
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/query.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/database/sql.py
RENAMED
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/kafka/__init__.py
RENAMED
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/kafka/broker.py
RENAMED
|
File without changes
|
{fastapi_modular-0.3.0 → fastapi_modular-0.3.1}/fastapi_modular/infrastructure/kafka/consumers.py
RENAMED
|
File without changes
|