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