fastapi-startkit 0.67.0__tar.gz → 0.69.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_startkit-0.67.0 → fastapi_startkit-0.69.0}/PKG-INFO +1 -1
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/pyproject.toml +1 -1
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/application.py +1 -2
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/config.py +1 -1
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/provider.py +59 -62
- fastapi_startkit-0.69.0/src/fastapi_startkit/broadcasting/reverb/channel_manager.py +21 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/reverb/connection.py +10 -3
- fastapi_startkit-0.69.0/src/fastapi_startkit/broadcasting/reverb/server.py +119 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/broadcasting/reverb/signature.py +43 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/__init__.py +30 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/array_store.py +62 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/config/__init__.py +45 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/database_store.py +94 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/file_store.py +135 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/lock.py +65 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/manager.py +49 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/memo_store.py +48 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/migrations/2026_10_21_000000_create_caches_table.py +17 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/provider.py +48 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/redis_store.py +85 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/repository.py +124 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/store.py +29 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/cache/tagged_store.py +57 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/console/db_install_command.py +65 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/console/install_command.py +250 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/exceptions/__init__.py +2 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/exceptions/exceptions.py +16 -9
- fastapi_startkit-0.69.0/src/fastapi_startkit/facades/Cache.py +29 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/facades/Cache.pyi +25 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Vite.pyi +3 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/config/fastapi.py +1 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/exceptions.py +33 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/providers/fastapi_provider.py +5 -2
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/foundation/app_provider.py +2 -1
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/inertia.py +11 -2
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/plan.py +3 -1
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/DBSeedCommand.py +22 -25
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/MakeModelCommand.py +8 -11
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/MakeObserverCommand.py +1 -1
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/MigrateFreshCommand.py +7 -9
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/MigrateRefreshCommand.py +7 -9
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/stubs/create_seed.stub +1 -4
- fastapi_startkit-0.69.0/src/fastapi_startkit/masoniteorm/commands/stubs/model.stub +5 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/masoniteorm/commands/stubs/observer.stub +27 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/exceptions.py +3 -2
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/providers/DatabaseProvider.py +4 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/grammars/BaseGrammar.py +1 -6
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/BelongsToMany.py +16 -22
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/platforms/MSSQLPlatform.py +44 -6
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/platforms/PostgresPlatform.py +38 -29
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/drivers/local.py +8 -6
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/drivers/s3.py +9 -7
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/helper.py +7 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/storage.py +7 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/__init__.py +2 -0
- fastapi_startkit-0.69.0/src/fastapi_startkit/vite/middleware.py +25 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/providers/provider.py +7 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/vite.py +58 -22
- fastapi_startkit-0.67.0/src/fastapi_startkit/broadcasting/reverb/channel_manager.py +0 -17
- fastapi_startkit-0.67.0/src/fastapi_startkit/broadcasting/reverb/server.py +0 -59
- fastapi_startkit-0.67.0/src/fastapi_startkit/facades/Cache.py +0 -5
- fastapi_startkit-0.67.0/src/fastapi_startkit/masoniteorm/commands/stubs/model.stub +0 -9
- fastapi_startkit-0.67.0/src/fastapi_startkit/masoniteorm/commands/stubs/observer.stub +0 -101
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/README.md +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/.DS_Store +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/agent.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/ai.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/audio.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/audio_factory.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/config/ai.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/config/config.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/decorators.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/document.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/image.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/image_factory.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/judge.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/lab.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/pipeline.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/providers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/providers/ai_provider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/recording.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/runner.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/state.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/testing.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/tinker.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/ai/types.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/auth.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/channels.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/drivers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/drivers/log_driver.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/drivers/reverb_driver.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/event.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/helpers.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/manager.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/reverb/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/stubs/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/stubs/channels.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/carbon/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/carbon/carbon.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/config/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/config/app.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/configuration/Configuration.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/configuration/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/configuration/config.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/configuration/helpers.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/configuration/providers/ConfigurationProvider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/configuration/providers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/console/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/console/application.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/console/can_override_config.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/console/can_override_default_options.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/console/command.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/console/publish_command.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/container/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/container/container.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/environment/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/environment/environment.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/events/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/events/dispatcher.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/events/fake.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/events/helpers.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/events/listener.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/events/provider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/exceptions/handler.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/AI.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/AI.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Config.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Config.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Dump.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Dump.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Event.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Event.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Facade.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Gate.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Gate.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Hash.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Hash.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Loader.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Loader.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Mail.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Mail.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Notification.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Notification.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Queue.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Queue.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/RateLimiter.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/RateLimiter.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Redis.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Redis.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Request.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Request.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Response.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Response.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Session.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Session.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Url.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Url.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/View.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/View.pyi +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/Vite.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/facades/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/commands/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/commands/serve_command.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/config/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/events.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/middleware.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/referer.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/requests/model.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/routers/router.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/testing/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/testing/assertable_json.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/testing/test_case.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/fastapi/testing/test_response.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/constant.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/context.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/encoding.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/errors.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/middleware.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/props/props.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/props/resolver.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/props/scroll.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/provider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/redirect.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/session.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/ssr.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/inertia/testing.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/cli.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/init_command.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/prompts.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/README.md.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/app.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/artisan.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/bootstrap.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/controller.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/fastapi.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/gitignore.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/logging.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/provider.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/routes.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/base/test_application.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/database/config.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/database/seeder.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/frontend/app.css.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/frontend/config.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/frontend/controller.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/frontend/index.html.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/queue/README.md.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/queue/command.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/queue/config.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/queue/provider.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/queue/task.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/queue/test_application.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/queue/worker.py.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/react/Home.tsx.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/react/app.tsx.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/react/package.json.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/react/vite.config.ts.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/vue/Home.vue.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/vue/app.ts.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/vue/package.json.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/stubs/vue/vite.config.ts.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/installer/writer.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/jsonapi/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/jsonapi/response.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/loader/Loader.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/loader/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/ChannelFactory.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/channels/BaseChannel.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/channels/DailyChannel.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/channels/MultiBaseChannel.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/channels/SingleChannel.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/channels/SlackChannel.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/channels/StackChannel.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/channels/SyslogChannel.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/channels/TerminalChannel.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/channels/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/config/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/config/channels.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/config/logging.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/drivers/BaseDriver.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/drivers/LogSingleDriver.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/drivers/LogSlackDriver.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/drivers/LogSyslogDriver.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/drivers/LogTerminalDriver.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/drivers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/factory.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/file.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/handler.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/listeners.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/logger.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/managers/LoggingManager.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/managers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/providers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/logging/providers/log_provider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/collection/Collection.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/collection/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/DBMigrateCommand.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/MakeMigrationCommand.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/MakeSeedCommand.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/MigrateResetCommand.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/MigrateRollbackCommand.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/MigrateStatusCommand.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/ShellCommand.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/stubs/create_migration.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/commands/stubs/table_migration.stub +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/config/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/config/config.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/config/database.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/connections/connection.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/connections/factory.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/connections/manager.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/connections/manages_transactions.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/connections/mysql_connection.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/connections/postgres_connection.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/connections/sqlite_connection.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/connections/transactions.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/expressions/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/expressions/expressions.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/facades/DB.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/facades/Schema.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/facades/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/factory/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/factory/factory.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/helpers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/helpers/misc.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/migrations/Migration.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/migrations/Migrator.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/migrations/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/MigrationModel.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/attribute.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/builder.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/caster.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/fields.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/model.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/observer.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/pivot.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/registry.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/models/relationship.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/observers/ObservesEvents.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/observers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/pagination/BasePaginator.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/pagination/LengthAwarePaginator.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/pagination/SimplePaginator.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/pagination/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/providers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/EagerLoadMixin.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/EagerRelation.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/grammars/MSSQLGrammar.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/grammars/MySQLGrammar.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/grammars/PostgresGrammar.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/grammars/SQLiteGrammar.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/grammars/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/processors/MSSQLPostProcessor.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/processors/MySQLPostProcessor.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/processors/PostgresPostProcessor.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/processors/SQLitePostProcessor.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/processors/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/query/support.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/BaseRelationship.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/BelongsTo.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/HasMany.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/HasManyThrough.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/HasOne.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/HasOneThrough.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/MorphMany.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/MorphOne.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/MorphTo.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/MorphToMany.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/relationships/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/Blueprint.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/Column.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/ColumnDiff.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/Constraint.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/ForeignKeyConstraint.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/Index.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/Table.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/TableDiff.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/platforms/MySQLPlatform.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/platforms/Platform.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/platforms/SQLitePlatform.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/platforms/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/schema/schema.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/seeders/Seeder.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/seeders/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/stubs/create-migration.html +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/stubs/table-migration.html +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/testing/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/masoniteorm/testing/transaction.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/argument.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/prompt.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/protocol.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/providers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/providers/mcp_provider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/request.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/resource.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/response.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/server.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/mcp/tool.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/process/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/process/exception.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/process/fake.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/process/process.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/process/result.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/py.typed +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/redis/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/redis/config.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/redis/connection.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/redis/connector.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/redis/manager.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/redis/prefix.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/redis/provider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/adapters/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/adapters/base.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/adapters/claude.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/adapters/gemini.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/commands/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/commands/skills.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/parser.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/provider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/registry.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/stubs/.ai/fastapi-startkit/database/SKILL.md +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/stubs/.ai/fastapi-startkit/fastapi/SKILL.md +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/skills/stubs/.ai/fastapi-startkit/inertia/SKILL.md +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/config/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/config/storage.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/data/mime.types +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/drivers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/drivers/fake.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/file.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/filestream.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/storage/providers/provider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/support/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/support/collection.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/support/config.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/support/dataclass.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/support/providers/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/support/providers/provider.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/support/string.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/support/structures.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/support/uri.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/testing/__init__.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/testing/test_case.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/config/vite.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/exceptions.py +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/stubs/package.json +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/stubs/resources/css/app.css +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/stubs/resources/js/app.ts +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/stubs/resources/templates/index.html +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/stubs/tsconfig.json +0 -0
- {fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/vite/stubs/vite.config.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: fastapi-startkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.69.0
|
|
4
4
|
Summary: A modular, provider-driven framework for building async Python web applications with FastAPI — bundling a service container, configuration system, an async-first ORM, facades, and a Cleo-powered console.
|
|
5
5
|
Keywords: fastapi,framework,async,web,asgi,orm,sqlalchemy,pydantic,ioc-container,dependency-injection,service-provider,cli,console,configuration,microservices,starter-kit,boilerplate
|
|
6
6
|
Author: Bedram Tamang
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastapi-startkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.69.0"
|
|
4
4
|
description = "A modular, provider-driven framework for building async Python web applications with FastAPI — bundling a service container, configuration system, an async-first ORM, facades, and a Cleo-powered console."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = [
|
|
@@ -61,9 +61,8 @@ class Application(Container, Generic[TConfig]):
|
|
|
61
61
|
self.resolve_environment()
|
|
62
62
|
self.configure_config()
|
|
63
63
|
self.configure_paths()
|
|
64
|
-
self.register_providers()
|
|
65
|
-
|
|
66
64
|
self._fastapi: Optional["FastAPI"] = None
|
|
65
|
+
self.register_providers()
|
|
67
66
|
self.load_providers()
|
|
68
67
|
|
|
69
68
|
def set_environment(self, env: str):
|
{fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/config.py
RENAMED
|
@@ -8,7 +8,7 @@ from fastapi_startkit.environment import env
|
|
|
8
8
|
class ReverbConnectionConfig(Config):
|
|
9
9
|
driver: str = "reverb"
|
|
10
10
|
key: str = env("REVERB_APP_KEY", "local")
|
|
11
|
-
secret: str = env("REVERB_APP_SECRET", "
|
|
11
|
+
secret: str = env("REVERB_APP_SECRET", "")
|
|
12
12
|
app_id: str = env("REVERB_APP_ID", "1")
|
|
13
13
|
host: str = env("REVERB_HOST", "0.0.0.0")
|
|
14
14
|
port: int = env("REVERB_PORT", 8080)
|
{fastapi_startkit-0.67.0 → fastapi_startkit-0.69.0}/src/fastapi_startkit/broadcasting/provider.py
RENAMED
|
@@ -2,13 +2,19 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import importlib
|
|
4
4
|
import importlib.util
|
|
5
|
+
import json
|
|
5
6
|
import sys
|
|
7
|
+
from collections.abc import Mapping
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
|
|
8
10
|
from fastapi_startkit.support import Provider
|
|
9
11
|
from .config import BroadcastingConfig
|
|
10
12
|
from .manager import BroadcastManager
|
|
11
13
|
from .reverb.server import ReverbServer
|
|
14
|
+
from .reverb.signature import is_presence, requires_auth, sign, valid_channel, valid_socket_id
|
|
15
|
+
|
|
16
|
+
UNSAFE_SECRETS = ("", "secret")
|
|
17
|
+
INSECURE_ENVIRONMENTS = ("local", "testing")
|
|
12
18
|
|
|
13
19
|
|
|
14
20
|
class ReverbProvider(Provider):
|
|
@@ -36,7 +42,14 @@ class ReverbProvider(Provider):
|
|
|
36
42
|
config_data = self.resolve_config(BroadcastingConfig)
|
|
37
43
|
self.merge_config_from(config_data, "broadcasting")
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
reverb = config_data.get("connections", {}).get("reverb", {})
|
|
46
|
+
secret = reverb.get("secret") or ""
|
|
47
|
+
if secret in UNSAFE_SECRETS and self.app.env not in INSECURE_ENVIRONMENTS:
|
|
48
|
+
raise RuntimeError(
|
|
49
|
+
f"REVERB_APP_SECRET must be set to a private value when APP_ENV is {self.app.env!r}; "
|
|
50
|
+
"private and presence channel signatures would otherwise be forgeable."
|
|
51
|
+
)
|
|
52
|
+
server = ReverbServer(key=reverb.get("key", "local"), secret=secret)
|
|
40
53
|
manager = BroadcastManager(config_data, server)
|
|
41
54
|
|
|
42
55
|
self.app.bind("broadcasting", manager)
|
|
@@ -81,91 +94,75 @@ class ReverbProvider(Provider):
|
|
|
81
94
|
spec.loader.exec_module(module) # type: ignore[union-attr]
|
|
82
95
|
|
|
83
96
|
def _mount_websocket(self) -> None:
|
|
84
|
-
"""Mount the Reverb WebSocket endpoint on the FastAPI application.
|
|
85
|
-
|
|
86
|
-
The endpoint path is ``/app/{app_key}``, matching the Pusher protocol
|
|
87
|
-
expected by Laravel Echo. The ``app_key`` is taken from the
|
|
88
|
-
``reverb.connections.reverb.key`` config value (default: ``"local"``).
|
|
89
|
-
"""
|
|
90
97
|
try:
|
|
91
98
|
fastapi_app = self.app.fastapi
|
|
92
99
|
except RuntimeError:
|
|
93
|
-
# FastAPI not installed or app not yet booted — skip
|
|
94
100
|
return
|
|
95
101
|
|
|
96
|
-
config = self.app.make("config").get("broadcasting") or {}
|
|
97
|
-
connections = config.get("connections", {}) if isinstance(config, dict) else {}
|
|
98
|
-
reverb_conn = connections.get("reverb", {}) if isinstance(connections, dict) else {}
|
|
99
|
-
app_key = reverb_conn.get("key", "local") if isinstance(reverb_conn, dict) else "local"
|
|
100
|
-
|
|
101
102
|
server: ReverbServer = self.app.make("reverb.server")
|
|
102
103
|
|
|
103
|
-
from starlette.routing import WebSocketRoute
|
|
104
|
-
from starlette.applications import Starlette
|
|
105
|
-
|
|
106
104
|
async def ws_endpoint(websocket):
|
|
107
105
|
await server.handle(websocket)
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
fastapi_app.mount("", ws_app)
|
|
107
|
+
fastapi_app.router.add_websocket_route(f"/app/{server.key}", ws_endpoint)
|
|
111
108
|
|
|
112
109
|
def _mount_auth_endpoint(self) -> None:
|
|
113
|
-
"""Mount the ``/broadcasting/auth`` HTTP endpoint.
|
|
114
|
-
|
|
115
|
-
The endpoint handles the Laravel Echo authentication handshake for
|
|
116
|
-
private and presence channels. It:
|
|
117
|
-
|
|
118
|
-
1. Reads ``channel_name`` from the POST body (form or JSON).
|
|
119
|
-
2. Resolves the current user via the container's ``auth`` binding (if
|
|
120
|
-
available), falling back to ``request.state.user``.
|
|
121
|
-
3. Calls :meth:`~.auth.ChannelAuthRegistry.authorize` with the channel
|
|
122
|
-
name and the resolved user.
|
|
123
|
-
4. Returns **200** on success or **403** on denial.
|
|
124
|
-
"""
|
|
125
110
|
try:
|
|
126
111
|
fastapi_app = self.app.fastapi
|
|
127
112
|
except RuntimeError:
|
|
128
113
|
return
|
|
129
114
|
|
|
130
|
-
from
|
|
131
|
-
from
|
|
115
|
+
from starlette.requests import Request
|
|
116
|
+
from starlette.responses import JSONResponse
|
|
132
117
|
|
|
133
118
|
application = self.app
|
|
134
119
|
|
|
135
|
-
@fastapi_app.post("/broadcasting/auth")
|
|
136
120
|
async def broadcasting_auth(request: Request):
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
121
|
+
if "application/json" in request.headers.get("content-type", ""):
|
|
122
|
+
try:
|
|
123
|
+
body = await request.json()
|
|
124
|
+
except ValueError:
|
|
125
|
+
return JSONResponse({"error": "Malformed JSON body"}, status_code=400)
|
|
142
126
|
else:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
127
|
+
body = await request.form()
|
|
128
|
+
if not isinstance(body, Mapping):
|
|
129
|
+
return JSONResponse({"error": "Request body must be an object"}, status_code=422)
|
|
130
|
+
|
|
131
|
+
channel_name = body.get("channel_name", "")
|
|
132
|
+
socket_id = body.get("socket_id", "")
|
|
133
|
+
if not valid_socket_id(socket_id):
|
|
134
|
+
return JSONResponse({"error": "socket_id is invalid"}, status_code=422)
|
|
135
|
+
if not valid_channel(channel_name):
|
|
136
|
+
return JSONResponse({"error": "channel_name is invalid"}, status_code=422)
|
|
137
|
+
|
|
138
|
+
server: ReverbServer = application.make("reverb.server")
|
|
139
|
+
user = self._resolve_user(application, request)
|
|
140
|
+
if not server.secret or not requires_auth(channel_name) or user is None:
|
|
141
|
+
return JSONResponse({"error": "Forbidden"}, status_code=403)
|
|
148
142
|
|
|
149
|
-
# Resolve authenticated user.
|
|
150
|
-
# Prefer request.state.user (set by auth middleware), then fall
|
|
151
|
-
# back to the container's "auth" service.
|
|
152
|
-
user = getattr(request.state, "user", None)
|
|
153
|
-
if user is None:
|
|
154
|
-
try:
|
|
155
|
-
auth_service = application.make("auth")
|
|
156
|
-
user_attr = getattr(auth_service, "user", None)
|
|
157
|
-
if callable(user_attr):
|
|
158
|
-
user = user_attr()
|
|
159
|
-
else:
|
|
160
|
-
user = user_attr
|
|
161
|
-
except Exception:
|
|
162
|
-
user = None
|
|
163
|
-
|
|
164
|
-
# Authorise
|
|
165
143
|
manager: BroadcastManager = application.make("broadcasting")
|
|
166
|
-
|
|
144
|
+
if not await manager.channel_registry.authorize(channel_name, user):
|
|
145
|
+
return JSONResponse({"error": "Forbidden"}, status_code=403)
|
|
146
|
+
|
|
147
|
+
if not is_presence(channel_name):
|
|
148
|
+
return JSONResponse({"auth": sign(server.key, server.secret, socket_id, channel_name)})
|
|
167
149
|
|
|
168
|
-
|
|
150
|
+
user_id = getattr(user, "id", None)
|
|
151
|
+
if user_id is None:
|
|
169
152
|
return JSONResponse({"error": "Forbidden"}, status_code=403)
|
|
153
|
+
channel_data = json.dumps({"user_id": str(user_id)})
|
|
154
|
+
auth = sign(server.key, server.secret, socket_id, channel_name, channel_data)
|
|
155
|
+
return JSONResponse({"auth": auth, "channel_data": channel_data})
|
|
170
156
|
|
|
171
|
-
|
|
157
|
+
fastapi_app.router.add_route("/broadcasting/auth", broadcasting_auth, methods=["POST"])
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def _resolve_user(application, request):
|
|
161
|
+
user = getattr(request.state, "user", None)
|
|
162
|
+
if user is not None:
|
|
163
|
+
return user
|
|
164
|
+
try:
|
|
165
|
+
user_attr = getattr(application.make("auth"), "user", None)
|
|
166
|
+
except Exception:
|
|
167
|
+
return None
|
|
168
|
+
return user_attr() if callable(user_attr) else user_attr
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class ChannelManager:
|
|
2
|
+
def __init__(self):
|
|
3
|
+
self._subscriptions: dict[str, set[str]] = {}
|
|
4
|
+
|
|
5
|
+
def subscribe(self, channel: str, socket_id: str) -> None:
|
|
6
|
+
self._subscriptions.setdefault(channel, set()).add(socket_id)
|
|
7
|
+
|
|
8
|
+
def unsubscribe(self, channel: str, socket_id: str) -> None:
|
|
9
|
+
subscribers = self._subscriptions.get(channel)
|
|
10
|
+
if subscribers is None:
|
|
11
|
+
return
|
|
12
|
+
subscribers.discard(socket_id)
|
|
13
|
+
if not subscribers:
|
|
14
|
+
del self._subscriptions[channel]
|
|
15
|
+
|
|
16
|
+
def remove_socket(self, socket_id: str) -> None:
|
|
17
|
+
for channel in list(self._subscriptions):
|
|
18
|
+
self.unsubscribe(channel, socket_id)
|
|
19
|
+
|
|
20
|
+
def subscribers(self, channel: str) -> set[str]:
|
|
21
|
+
return set(self._subscriptions.get(channel, set()))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import secrets
|
|
2
|
+
|
|
2
3
|
from starlette.websockets import WebSocket
|
|
3
4
|
|
|
4
5
|
|
|
@@ -7,15 +8,21 @@ class ConnectionManager:
|
|
|
7
8
|
self._connections: dict[str, WebSocket] = {}
|
|
8
9
|
|
|
9
10
|
def add(self, websocket: WebSocket) -> str:
|
|
10
|
-
socket_id =
|
|
11
|
+
socket_id = self._new_socket_id()
|
|
12
|
+
while socket_id in self._connections:
|
|
13
|
+
socket_id = self._new_socket_id()
|
|
11
14
|
self._connections[socket_id] = websocket
|
|
12
15
|
return socket_id
|
|
13
16
|
|
|
17
|
+
@staticmethod
|
|
18
|
+
def _new_socket_id() -> str:
|
|
19
|
+
return f"{secrets.randbelow(10**10)}.{secrets.randbelow(10**10)}"
|
|
20
|
+
|
|
14
21
|
def remove(self, socket_id: str) -> None:
|
|
15
22
|
self._connections.pop(socket_id, None)
|
|
16
23
|
|
|
17
24
|
def get(self, socket_id: str):
|
|
18
25
|
return self._connections.get(socket_id)
|
|
19
26
|
|
|
20
|
-
def all(self) -> dict:
|
|
27
|
+
def all(self) -> dict[str, WebSocket]:
|
|
21
28
|
return dict(self._connections)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from starlette.applications import Starlette
|
|
4
|
+
from starlette.routing import WebSocketRoute
|
|
5
|
+
from starlette.websockets import WebSocket, WebSocketDisconnect
|
|
6
|
+
|
|
7
|
+
from .channel_manager import ChannelManager
|
|
8
|
+
from .connection import ConnectionManager
|
|
9
|
+
from .signature import requires_auth, valid_channel, verify
|
|
10
|
+
|
|
11
|
+
MESSAGE_TOO_BIG = 1009
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReverbServer:
|
|
15
|
+
max_message_size = 10_000
|
|
16
|
+
|
|
17
|
+
def __init__(self, key: str = "local", secret: str = ""):
|
|
18
|
+
self.key = key
|
|
19
|
+
self.secret = secret
|
|
20
|
+
self.connections = ConnectionManager()
|
|
21
|
+
self.channels = ChannelManager()
|
|
22
|
+
|
|
23
|
+
async def handle(self, websocket: WebSocket):
|
|
24
|
+
await websocket.accept()
|
|
25
|
+
socket_id = self.connections.add(websocket)
|
|
26
|
+
try:
|
|
27
|
+
await self._send(
|
|
28
|
+
websocket,
|
|
29
|
+
"pusher:connection_established",
|
|
30
|
+
json.dumps({"socket_id": socket_id, "activity_timeout": 120}),
|
|
31
|
+
)
|
|
32
|
+
await self._receive_loop(socket_id, websocket)
|
|
33
|
+
except WebSocketDisconnect:
|
|
34
|
+
pass
|
|
35
|
+
finally:
|
|
36
|
+
self.connections.remove(socket_id)
|
|
37
|
+
self.channels.remove_socket(socket_id)
|
|
38
|
+
|
|
39
|
+
async def _receive_loop(self, socket_id: str, websocket: WebSocket):
|
|
40
|
+
while True:
|
|
41
|
+
frame = await websocket.receive()
|
|
42
|
+
if frame["type"] == "websocket.disconnect":
|
|
43
|
+
return
|
|
44
|
+
text = frame.get("text")
|
|
45
|
+
if text is not None and len(text) > self.max_message_size:
|
|
46
|
+
await self._error(websocket, "Maximum message size exceeded")
|
|
47
|
+
await websocket.close(code=MESSAGE_TOO_BIG)
|
|
48
|
+
return
|
|
49
|
+
message = self._parse(text)
|
|
50
|
+
if message is None:
|
|
51
|
+
await self._error(websocket, "Invalid message format")
|
|
52
|
+
continue
|
|
53
|
+
await self._handle_message(socket_id, websocket, message)
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _parse(text: str | None) -> dict[str, object] | None:
|
|
57
|
+
if text is None:
|
|
58
|
+
return None
|
|
59
|
+
try:
|
|
60
|
+
message = json.loads(text)
|
|
61
|
+
except ValueError:
|
|
62
|
+
return None
|
|
63
|
+
if not isinstance(message, dict) or not isinstance(message.get("event", ""), str):
|
|
64
|
+
return None
|
|
65
|
+
return message
|
|
66
|
+
|
|
67
|
+
async def _handle_message(self, socket_id: str, websocket: WebSocket, message: dict[str, object]):
|
|
68
|
+
event = message.get("event", "")
|
|
69
|
+
if event == "pusher:ping":
|
|
70
|
+
await self._send(websocket, "pusher:pong", {})
|
|
71
|
+
elif event == "pusher:subscribe":
|
|
72
|
+
await self._subscribe(socket_id, websocket, message.get("data"))
|
|
73
|
+
|
|
74
|
+
async def _subscribe(self, socket_id: str, websocket: WebSocket, data: object):
|
|
75
|
+
if not isinstance(data, dict) or not isinstance(data.get("channel"), str) or not data["channel"]:
|
|
76
|
+
await self._error(websocket, "Invalid subscription")
|
|
77
|
+
return
|
|
78
|
+
channel = data["channel"]
|
|
79
|
+
if not valid_channel(channel):
|
|
80
|
+
await self._subscription_error(websocket, channel, "InvalidChannel", "Invalid channel name", 400)
|
|
81
|
+
return
|
|
82
|
+
if requires_auth(channel) and not verify(
|
|
83
|
+
self.key, self.secret, socket_id, channel, data.get("auth"), data.get("channel_data")
|
|
84
|
+
):
|
|
85
|
+
await self._subscription_error(websocket, channel, "AuthError", "Invalid signature", 401)
|
|
86
|
+
return
|
|
87
|
+
self.channels.subscribe(channel, socket_id)
|
|
88
|
+
await self._send(websocket, "pusher_internal:subscription_succeeded", {}, channel)
|
|
89
|
+
|
|
90
|
+
async def _subscription_error(self, websocket: WebSocket, channel: str, kind: str, error: str, status: int):
|
|
91
|
+
await self._send(
|
|
92
|
+
websocket, "pusher:subscription_error", {"type": kind, "error": error, "status": status}, channel
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
async def _error(self, websocket: WebSocket, message: str):
|
|
96
|
+
await self._send(websocket, "pusher:error", {"code": 4200, "message": message})
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
async def _send(websocket: WebSocket, event: str, data: object, channel: str | None = None):
|
|
100
|
+
payload: dict[str, object] = {"event": event, "data": data}
|
|
101
|
+
if channel is not None:
|
|
102
|
+
payload["channel"] = channel
|
|
103
|
+
await websocket.send_text(json.dumps(payload))
|
|
104
|
+
|
|
105
|
+
async def broadcast_to_channel(self, channel_name: str, event_name: str, data: dict[str, object]):
|
|
106
|
+
payload = json.dumps({"event": event_name, "channel": channel_name, "data": data})
|
|
107
|
+
for socket_id in self.channels.subscribers(channel_name):
|
|
108
|
+
ws = self.connections.get(socket_id)
|
|
109
|
+
if ws:
|
|
110
|
+
try:
|
|
111
|
+
await ws.send_text(payload)
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
def as_starlette_app(self, app_key: str = "local") -> Starlette:
|
|
116
|
+
async def endpoint(ws: WebSocket):
|
|
117
|
+
await self.handle(ws)
|
|
118
|
+
|
|
119
|
+
return Starlette(routes=[WebSocketRoute(f"/app/{app_key}", endpoint)])
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import hmac
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from typing_extensions import TypeIs
|
|
6
|
+
|
|
7
|
+
SOCKET_ID = re.compile(r"[0-9]+\.[0-9]+", re.ASCII)
|
|
8
|
+
CHANNEL_NAME = re.compile(r"[A-Za-z0-9_\-=@,.;]{1,200}", re.ASCII)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def valid_socket_id(socket_id: object) -> TypeIs[str]:
|
|
12
|
+
return isinstance(socket_id, str) and SOCKET_ID.fullmatch(socket_id) is not None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def valid_channel(channel: object) -> TypeIs[str]:
|
|
16
|
+
return isinstance(channel, str) and CHANNEL_NAME.fullmatch(channel) is not None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def requires_auth(channel: object) -> bool:
|
|
20
|
+
return isinstance(channel, str) and channel.startswith(("private-", "presence-"))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def is_presence(channel: str) -> bool:
|
|
24
|
+
return channel.startswith("presence-")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def sign(key: str, secret: str, socket_id: str, channel: str, channel_data: str | None = None) -> str:
|
|
28
|
+
parts = [socket_id, channel] if channel_data is None else [socket_id, channel, channel_data]
|
|
29
|
+
digest = hmac.new(secret.encode(), ":".join(parts).encode(), hashlib.sha256).hexdigest()
|
|
30
|
+
return f"{key}:{digest}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def verify(key: str, secret: str, socket_id: str, channel: str, auth: object, channel_data: object = None) -> bool:
|
|
34
|
+
if not secret or not isinstance(auth, str):
|
|
35
|
+
return False
|
|
36
|
+
if not valid_socket_id(socket_id) or not valid_channel(channel) or not requires_auth(channel):
|
|
37
|
+
return False
|
|
38
|
+
if not is_presence(channel):
|
|
39
|
+
channel_data = None
|
|
40
|
+
elif not isinstance(channel_data, str):
|
|
41
|
+
return False
|
|
42
|
+
expected = sign(key, secret, socket_id, channel, channel_data)
|
|
43
|
+
return hmac.compare_digest(auth.encode(), expected.encode())
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .array_store import ArrayStore
|
|
2
|
+
from .config import ArrayStoreConfig, CacheConfig, DatabaseStoreConfig, FileStoreConfig, RedisStoreConfig
|
|
3
|
+
from .file_store import FileStore
|
|
4
|
+
from .lock import Lock, LockTimeoutError
|
|
5
|
+
from .manager import CacheManager
|
|
6
|
+
from .memo_store import MemoStore
|
|
7
|
+
from .provider import CacheProvider
|
|
8
|
+
from .repository import Repository
|
|
9
|
+
from .store import MISSING, Store
|
|
10
|
+
from .tagged_store import TaggedStore, TagSet
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"MISSING",
|
|
14
|
+
"ArrayStore",
|
|
15
|
+
"ArrayStoreConfig",
|
|
16
|
+
"CacheConfig",
|
|
17
|
+
"CacheManager",
|
|
18
|
+
"CacheProvider",
|
|
19
|
+
"DatabaseStoreConfig",
|
|
20
|
+
"FileStore",
|
|
21
|
+
"FileStoreConfig",
|
|
22
|
+
"Lock",
|
|
23
|
+
"LockTimeoutError",
|
|
24
|
+
"MemoStore",
|
|
25
|
+
"RedisStoreConfig",
|
|
26
|
+
"Repository",
|
|
27
|
+
"Store",
|
|
28
|
+
"TagSet",
|
|
29
|
+
"TaggedStore",
|
|
30
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .lock import Lock
|
|
4
|
+
from .store import MISSING, expired, expires_at
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ArrayStore:
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
self.storage: dict[str, tuple[float | None, Any]] = {}
|
|
10
|
+
self.locks: dict[str, tuple[str, float | None]] = {}
|
|
11
|
+
|
|
12
|
+
async def get(self, key: str) -> Any:
|
|
13
|
+
entry = self.storage.get(key)
|
|
14
|
+
if entry is None:
|
|
15
|
+
return MISSING
|
|
16
|
+
|
|
17
|
+
expiration, value = entry
|
|
18
|
+
if expired(expiration):
|
|
19
|
+
del self.storage[key]
|
|
20
|
+
return MISSING
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
async def put(self, key: str, value: Any, seconds: float | None) -> bool:
|
|
24
|
+
self.storage[key] = (expires_at(seconds), value)
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
async def forget(self, key: str) -> bool:
|
|
28
|
+
return self.storage.pop(key, MISSING) is not MISSING
|
|
29
|
+
|
|
30
|
+
async def flush(self) -> bool:
|
|
31
|
+
self.storage.clear()
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
def lock(self, name: str, seconds: float = 0, owner: str | None = None) -> "ArrayLock":
|
|
35
|
+
return ArrayLock(self, name, seconds, owner)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ArrayLock(Lock):
|
|
39
|
+
def __init__(self, store: ArrayStore, name: str, seconds: float = 0, owner: str | None = None):
|
|
40
|
+
super().__init__(name, seconds, owner)
|
|
41
|
+
self.store = store
|
|
42
|
+
|
|
43
|
+
def holder(self) -> str | None:
|
|
44
|
+
entry = self.store.locks.get(self.name)
|
|
45
|
+
if entry is None or expired(entry[1]):
|
|
46
|
+
return None
|
|
47
|
+
return entry[0]
|
|
48
|
+
|
|
49
|
+
async def acquire(self) -> bool:
|
|
50
|
+
if self.holder() is not None:
|
|
51
|
+
return False
|
|
52
|
+
self.store.locks[self.name] = (self.owner, expires_at(self.ttl))
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
async def release(self) -> bool:
|
|
56
|
+
if self.holder() != self.owner:
|
|
57
|
+
return False
|
|
58
|
+
del self.store.locks[self.name]
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
async def force_release(self) -> None:
|
|
62
|
+
self.store.locks.pop(self.name, None)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
from fastapi_startkit.configuration import Config
|
|
4
|
+
from fastapi_startkit.environment import env
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ArrayStoreConfig(Config):
|
|
9
|
+
driver: str = "array"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class FileStoreConfig(Config):
|
|
14
|
+
driver: str = "file"
|
|
15
|
+
path: str = env("CACHE_FILE_PATH", "storage/framework/cache/data")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class DatabaseStoreConfig(Config):
|
|
20
|
+
driver: str = "database"
|
|
21
|
+
connection: str | None = env("DB_CACHE_CONNECTION", None, cast=False)
|
|
22
|
+
table: str = env("DB_CACHE_TABLE", "caches")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class RedisStoreConfig(Config):
|
|
27
|
+
driver: str = "redis"
|
|
28
|
+
connection: str = "cache"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class CacheConfig(Config):
|
|
33
|
+
default: str = env("CACHE_CONNECTION", "database")
|
|
34
|
+
prefix: str = env("CACHE_PREFIX", "cache")
|
|
35
|
+
stores: dict[str, Config] = field(
|
|
36
|
+
default_factory=lambda: {
|
|
37
|
+
"array": ArrayStoreConfig(),
|
|
38
|
+
"file": FileStoreConfig(),
|
|
39
|
+
"database": DatabaseStoreConfig(),
|
|
40
|
+
"redis": RedisStoreConfig(),
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = ["ArrayStoreConfig", "CacheConfig", "DatabaseStoreConfig", "FileStoreConfig", "RedisStoreConfig"]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import math
|
|
3
|
+
import pickle
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from sqlalchemy.exc import IntegrityError
|
|
9
|
+
|
|
10
|
+
from fastapi_startkit.masoniteorm.connections.connection import Connection
|
|
11
|
+
from fastapi_startkit.masoniteorm.models.builder import QueryBuilder
|
|
12
|
+
|
|
13
|
+
from .lock import Lock
|
|
14
|
+
from .store import MISSING, expired, expires_at
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def encode(value: Any) -> str:
|
|
18
|
+
return base64.b64encode(pickle.dumps(value)).decode()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DatabaseStore:
|
|
22
|
+
def __init__(self, connection: Callable[[], Connection], table: str = "caches"):
|
|
23
|
+
self.connection = connection
|
|
24
|
+
self.table = table
|
|
25
|
+
|
|
26
|
+
def query(self) -> QueryBuilder[Any]:
|
|
27
|
+
return self.connection().query().table(self.table)
|
|
28
|
+
|
|
29
|
+
async def get(self, key: str) -> Any:
|
|
30
|
+
builder = self.query().where("key", key).limit(1)
|
|
31
|
+
row = await builder.connection.select_one(builder.to_qmark(), list(builder.get_bindings()))
|
|
32
|
+
if row is None:
|
|
33
|
+
return MISSING
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
value = pickle.loads(base64.b64decode(row["value"]))
|
|
37
|
+
except Exception:
|
|
38
|
+
await self.forget(key)
|
|
39
|
+
return MISSING
|
|
40
|
+
|
|
41
|
+
if expired(row["expiration"]):
|
|
42
|
+
await self.forget(key)
|
|
43
|
+
return MISSING
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
async def put(self, key: str, value: Any, seconds: float | None) -> bool:
|
|
47
|
+
expiration = expires_at(seconds)
|
|
48
|
+
record = {"value": encode(value), "expiration": None if expiration is None else math.ceil(expiration)}
|
|
49
|
+
if await self.query().where("key", key).update(record):
|
|
50
|
+
return True
|
|
51
|
+
try:
|
|
52
|
+
await self.query().insert({"key": key, **record})
|
|
53
|
+
except IntegrityError:
|
|
54
|
+
await self.query().where("key", key).update(record)
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
async def forget(self, key: str) -> bool:
|
|
58
|
+
return bool(await self.query().where("key", key).delete())
|
|
59
|
+
|
|
60
|
+
async def flush(self) -> bool:
|
|
61
|
+
await self.query().delete()
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
def lock(self, name: str, seconds: float = 0, owner: str | None = None) -> "DatabaseLock":
|
|
65
|
+
return DatabaseLock(self, name, seconds, owner)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DatabaseLock(Lock):
|
|
69
|
+
def __init__(self, store: DatabaseStore, name: str, seconds: float = 0, owner: str | None = None):
|
|
70
|
+
super().__init__(name, seconds, owner)
|
|
71
|
+
self.store = store
|
|
72
|
+
self.key = f"lock:{name}"
|
|
73
|
+
|
|
74
|
+
async def acquire(self) -> bool:
|
|
75
|
+
expiration = expires_at(self.ttl)
|
|
76
|
+
record = {"value": encode(self.owner), "expiration": None if expiration is None else math.ceil(expiration)}
|
|
77
|
+
try:
|
|
78
|
+
await self.store.query().insert({"key": self.key, **record})
|
|
79
|
+
except IntegrityError:
|
|
80
|
+
expired_lock = self.store.query().where("key", self.key).where("expiration", "<=", time.time())
|
|
81
|
+
return bool(await expired_lock.update(record))
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
async def release(self) -> bool:
|
|
85
|
+
held = (
|
|
86
|
+
self.store.query()
|
|
87
|
+
.where("key", self.key)
|
|
88
|
+
.where("value", encode(self.owner))
|
|
89
|
+
.where(lambda query: query.where_null("expiration").or_where("expiration", ">", time.time()))
|
|
90
|
+
)
|
|
91
|
+
return bool(await held.delete())
|
|
92
|
+
|
|
93
|
+
async def force_release(self) -> None:
|
|
94
|
+
await self.store.query().where("key", self.key).delete()
|