diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57ae32d..790c510 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.12" - name: Install Pre-Commit run: python -m pip install pre-commit && pre-commit install @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.11"] + python-version: ["3.12"] timeout-minutes: 15 defaults: run: @@ -73,12 +73,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.12" - uses: pdm-project/setup-pdm@v3 name: Set up PDM with: - python-version: "3.11" + python-version: "3.12" allow-python-prereleases: true cache: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d04856f..a5b3eb1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,12 +22,12 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.12" - uses: pdm-project/setup-pdm@v3 name: Set up PDM with: - python-version: "3.11" + python-version: "3.12" allow-python-prereleases: true cache: true diff --git a/.pdm-python b/.pdm-python index e2e784d..15f5b02 100644 --- a/.pdm-python +++ b/.pdm-python @@ -1 +1 @@ -.venv/bin/python +/Users/jcoffee5/git/public/JacobCoffee/byte/.venv/bin/python diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e48d07b..0526059 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ default_language_version: - python: "3.11" + python: "3.12" default_install_hook_types: [commit-msg, pre-commit] repos: - repo: https://github.com/compilerla/conventional-pre-commit @@ -18,7 +18,7 @@ repos: - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.2.2" + rev: "v0.3.2" hooks: - id: ruff args: ["--fix"] @@ -88,7 +88,7 @@ repos: # discord-py, # ] - repo: https://github.com/RobertCraigie/pyright-python - rev: v1.1.351 + rev: v1.1.353 hooks: - id: pyright additional_dependencies: @@ -142,6 +142,7 @@ repos: asyncpg-stubs, polyfactory, discord-py, + types-python-dateutil, ] - repo: https://github.com/sphinx-contrib/sphinx-lint rev: "v0.9.1" diff --git a/.tool-versions b/.tool-versions index 504f142..647c783 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ python 3.11 +python 3.12 diff --git a/Makefile b/Makefile index 3fbf73f..de44e0a 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,34 @@ install: ## Install all dependencies @command -v $(PDM) > /dev/null || (echo "PDM not found. Installing..." && $(MAKE) install-pdm) $(MAKE) install-pre-commit +up-container: ## Start the Byte database container + @echo "=> Starting Byte database container" + @docker compose -f docker-compose.infra.yml up -d + @echo "=> Started Byte database container" + +clean-container: ## Stop, remove, and wipe the Byte database container and volume + @echo "=> Stopping and removing Byte database container" + @docker stop byte-db-1 + @docker rm byte-db-1 + @docker volume rm byte_db-data + @echo "=> Stopped and removed Byte database container" + +load-container: migrate ## Perform database migrations and load test data into the Byte database container + @echo "=> Loading database migrations and test data" + @echo "not yet implemented" + @echo "=> Loaded database migrations and test data" + +refresh-container: clean-container up-container load-container ## Refresh the Byte database container + + +.PHONY: refresh-lockfiles +refresh-lockfiles: ## Sync lockfiles with requirements files. + $(PDM) update --update-reuse -G:all + +.PHONY: lock +lock: ## Rebuild lockfiles from scratch, updating all dependencies + $(PDM) update --update-eager -G:all + # ============================================================================= # Tests, Linting, Coverage # ============================================================================= diff --git a/docs/byte/api/lib/common.rst b/docs/byte/api/lib/common.rst index c25558b..2e9d562 100644 --- a/docs/byte/api/lib/common.rst +++ b/docs/byte/api/lib/common.rst @@ -4,5 +4,14 @@ common Common functions and classes used by the other modules. -.. automodule:: src.byte.lib.common +.. automodule:: src.byte.lib.common.assets + :members: + +.. automodule:: src.byte.lib.common.colors + :members: + +.. automodule:: src.byte.lib.common.guilds + :members: + +.. automodule:: src.byte.lib.common.links :members: diff --git a/docs/byte/api/lib/logging.rst b/docs/byte/api/lib/logging.rst index b34b446..f6c2189 100644 --- a/docs/byte/api/lib/logging.rst +++ b/docs/byte/api/lib/logging.rst @@ -4,5 +4,5 @@ logging Logging configuration for Byte -.. automodule:: src.byte.lib.logging +.. automodule:: src.byte.lib.log :members: diff --git a/docs/byte/api/plugins/guild.rst b/docs/byte/api/plugins/guild.rst deleted file mode 100644 index f1ceb1b..0000000 --- a/docs/byte/api/plugins/guild.rst +++ /dev/null @@ -1,6 +0,0 @@ -===== -guild -===== - -.. automodule:: src.byte.plugins.guild - :members: diff --git a/docs/byte/api/plugins/index.rst b/docs/byte/api/plugins/index.rst index ec4c31b..0e2b0f5 100644 --- a/docs/byte/api/plugins/index.rst +++ b/docs/byte/api/plugins/index.rst @@ -14,5 +14,5 @@ This includes things like commands, event handlers, and other features. events general github - guild testing + python diff --git a/docs/byte/api/plugins/python.rst b/docs/byte/api/plugins/python.rst new file mode 100644 index 0000000..29e794f --- /dev/null +++ b/docs/byte/api/plugins/python.rst @@ -0,0 +1,6 @@ +====== +python +====== + +.. automodule:: src.byte.plugins.python + :members: diff --git a/docs/byte/api/views/abstract.rst b/docs/byte/api/views/abstract.rst new file mode 100644 index 0000000..370a393 --- /dev/null +++ b/docs/byte/api/views/abstract.rst @@ -0,0 +1,8 @@ +======== +abstract +======== + +Abstract views to be inherited by other views. + +.. automodule:: src.byte.views.abstract_views + :members: diff --git a/docs/byte/api/views/astral.rst b/docs/byte/api/views/astral.rst new file mode 100644 index 0000000..a0c4b33 --- /dev/null +++ b/docs/byte/api/views/astral.rst @@ -0,0 +1,8 @@ +====== +astral +====== + +Astral Inc. views, for things like Ruff embeds + +.. automodule:: src.byte.views.astral + :members: diff --git a/docs/byte/api/views/index.rst b/docs/byte/api/views/index.rst index 7d9c946..5829c87 100644 --- a/docs/byte/api/views/index.rst +++ b/docs/byte/api/views/index.rst @@ -10,3 +10,6 @@ components for your bot. :caption: Views forums + astral + python + abstract diff --git a/docs/byte/api/views/python.rst b/docs/byte/api/views/python.rst new file mode 100644 index 0000000..f4638d0 --- /dev/null +++ b/docs/byte/api/views/python.rst @@ -0,0 +1,8 @@ +====== +python +====== + +Python views for things like PEP embeds. + +.. automodule:: src.byte.views.python + :members: diff --git a/docs/byte/usage/byte.rst b/docs/byte/usage/byte.rst new file mode 100644 index 0000000..1ad21e3 --- /dev/null +++ b/docs/byte/usage/byte.rst @@ -0,0 +1,18 @@ +========== +About Byte +========== + +The Discord side of Byte is utilizing the `discord.py `_. framework. + +The structure of Byte is not too complex. There exists a ``bot.py`` file containing the main +:ref:`Byte` bot class, which includes some on-start utilities like loading cogs and ingesting +new guilds as they join into the database. The class is called via the :func:`run_bot` function +to start the bot. + +The structure of Byte is not dissimilar to the structure of the server side; it has a ``lib/`` +directory for common backend functionality like utilities, logging, and settings, a ``plugings/`` +directory for the cogs, and a ``views/`` directory for the UI components inside of Discord. + +For example, any functionality related to `Astral `_ is located in the +``plugins/astral/`` directory, such as ``ruff`` embeds, and it's related views are located in +``views/astral/``. diff --git a/docs/byte/usage/index.rst b/docs/byte/usage/index.rst index b9155c7..eefba43 100644 --- a/docs/byte/usage/index.rst +++ b/docs/byte/usage/index.rst @@ -1,5 +1,10 @@ +===== Usage ===== .. toctree:: :titlesonly: + :caption: Byte Usage Documentation + + byte + starting diff --git a/docs/byte/usage/starting.rst b/docs/byte/usage/starting.rst new file mode 100644 index 0000000..3154c3d --- /dev/null +++ b/docs/byte/usage/starting.rst @@ -0,0 +1,5 @@ +============= +Starting Byte +============= + +.. todo:: This section needs to be written. diff --git a/docs/conf.py b/docs/conf.py index 5bc74c6..dcaed6c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,5 @@ """Sphinx configuration.""" + from __future__ import annotations import importlib.metadata diff --git a/docs/web/api/domain/db/models.rst b/docs/web/api/domain/db/models.rst index c2fad62..fff8873 100644 --- a/docs/web/api/domain/db/models.rst +++ b/docs/web/api/domain/db/models.rst @@ -13,7 +13,7 @@ API Reference Model Reference --------------- -.. sqla-model:: server.domain.db.models.GuildConfig +.. sqla-model:: server.domain.db.models.Guild .. sqla-model:: server.domain.db.models.GitHubConfig diff --git a/docs/web/api/domain/github/helpers.rst b/docs/web/api/domain/github/helpers.rst new file mode 100644 index 0000000..02f44cd --- /dev/null +++ b/docs/web/api/domain/github/helpers.rst @@ -0,0 +1,8 @@ +======= +helpers +======= + +Helper functions for the GitHub domain. + +.. automodule:: src.server.domain.github.helpers + :members: diff --git a/docs/web/api/domain/github/index.rst b/docs/web/api/domain/github/index.rst new file mode 100644 index 0000000..209f660 --- /dev/null +++ b/docs/web/api/domain/github/index.rst @@ -0,0 +1,11 @@ +====== +github +====== + +.. toctree:: + :titlesonly: + :caption: GitHub Domain API Reference + :glob: + :hidden: + + * diff --git a/docs/web/api/domain/guilds/controllers.rst b/docs/web/api/domain/guilds/controllers.rst new file mode 100644 index 0000000..45e505f --- /dev/null +++ b/docs/web/api/domain/guilds/controllers.rst @@ -0,0 +1,8 @@ +=========== +controllers +=========== + +Controllers for the guilds domain. + +.. automodule:: src.server.domain.guilds.controllers + :members: diff --git a/docs/web/api/domain/guilds/dependencies.rst b/docs/web/api/domain/guilds/dependencies.rst new file mode 100644 index 0000000..20c7169 --- /dev/null +++ b/docs/web/api/domain/guilds/dependencies.rst @@ -0,0 +1,8 @@ +============ +dependencies +============ + +Dependencies for the guilds domain. + +.. automodule:: src.server.domain.guilds.dependencies + :members: diff --git a/docs/web/api/domain/guilds/helpers.rst b/docs/web/api/domain/guilds/helpers.rst new file mode 100644 index 0000000..19c47bd --- /dev/null +++ b/docs/web/api/domain/guilds/helpers.rst @@ -0,0 +1,8 @@ +======= +helpers +======= + +Helper functions for the guilds domain. + +.. automodule:: src.server.domain.guilds.helpers + :members: diff --git a/docs/web/api/domain/guilds/index.rst b/docs/web/api/domain/guilds/index.rst new file mode 100644 index 0000000..a991940 --- /dev/null +++ b/docs/web/api/domain/guilds/index.rst @@ -0,0 +1,11 @@ +====== +guilds +====== + +.. toctree:: + :titlesonly: + :caption: Guild Domain API Reference + :glob: + :hidden: + + * diff --git a/docs/web/api/domain/guilds/schemas.rst b/docs/web/api/domain/guilds/schemas.rst new file mode 100644 index 0000000..a5603df --- /dev/null +++ b/docs/web/api/domain/guilds/schemas.rst @@ -0,0 +1,8 @@ +======= +schemas +======= + +Schemas for the system domain. + +.. automodule:: src.server.domain.guilds.schemas + :members: diff --git a/docs/web/api/domain/guilds/services.rst b/docs/web/api/domain/guilds/services.rst new file mode 100644 index 0000000..46d92bf --- /dev/null +++ b/docs/web/api/domain/guilds/services.rst @@ -0,0 +1,8 @@ +======== +services +======== + +Service declaration for the guilds domain. + +.. automodule:: src.server.domain.guilds.services + :members: diff --git a/docs/web/api/domain/guilds/urls.rst b/docs/web/api/domain/guilds/urls.rst new file mode 100644 index 0000000..51a1cc5 --- /dev/null +++ b/docs/web/api/domain/guilds/urls.rst @@ -0,0 +1,8 @@ +==== +urls +==== + +URL declarations for the guilds domain routes. + +.. automodule:: src.server.domain.guilds.urls + :members: diff --git a/docs/web/api/domain/index.rst b/docs/web/api/domain/index.rst index 00bd11e..ca56653 100644 --- a/docs/web/api/domain/index.rst +++ b/docs/web/api/domain/index.rst @@ -11,5 +11,7 @@ domain init urls system/index + guilds/index + github/index web/index db/index diff --git a/docs/web/api/domain/system/dtos.rst b/docs/web/api/domain/system/dtos.rst new file mode 100644 index 0000000..33acffa --- /dev/null +++ b/docs/web/api/domain/system/dtos.rst @@ -0,0 +1,8 @@ +==== +dtos +==== + +DTOs for the system domain. + +.. automodule:: src.server.domain.system.dtos + :members: diff --git a/docs/web/api/domain/system/helpers.rst b/docs/web/api/domain/system/helpers.rst new file mode 100644 index 0000000..35b0079 --- /dev/null +++ b/docs/web/api/domain/system/helpers.rst @@ -0,0 +1,8 @@ +======= +helpers +======= + +Helper functions for the system domain. + +.. automodule:: src.server.domain.system.helpers + :members: diff --git a/docs/web/api/lib/constants.rst b/docs/web/api/lib/constants.rst new file mode 100644 index 0000000..0a948be --- /dev/null +++ b/docs/web/api/lib/constants.rst @@ -0,0 +1,6 @@ +========= +constants +========= + +.. automodule:: src.server.lib.constants + :members: diff --git a/docs/web/api/lib/dependencies.rst b/docs/web/api/lib/dependencies.rst new file mode 100644 index 0000000..4b7464e --- /dev/null +++ b/docs/web/api/lib/dependencies.rst @@ -0,0 +1,8 @@ +============ +dependencies +============ + +Dependency management + +.. automodule:: src.server.lib.dependencies + :members: diff --git a/docs/web/api/lib/dtos.rst b/docs/web/api/lib/dtos.rst new file mode 100644 index 0000000..6eb98c1 --- /dev/null +++ b/docs/web/api/lib/dtos.rst @@ -0,0 +1,10 @@ +==== +dtos +==== + +DTOs for the server. + + +.. automodule:: src.server.lib.dto + :members: + :noindex: diff --git a/docs/web/api/lib/index.rst b/docs/web/api/lib/index.rst index 27a7f4c..0bf1870 100644 --- a/docs/web/api/lib/index.rst +++ b/docs/web/api/lib/index.rst @@ -8,13 +8,19 @@ lib :caption: Library API Reference :hidden: + constants cors + dependencies + dtos exceptions openapi + repository schema serialization + service settings static_files + types template log/index db/index diff --git a/docs/web/api/lib/repository.rst b/docs/web/api/lib/repository.rst new file mode 100644 index 0000000..2a890cb --- /dev/null +++ b/docs/web/api/lib/repository.rst @@ -0,0 +1,6 @@ +========== +repository +========== + +.. automodule:: src.server.lib.repository + :members: diff --git a/docs/web/api/lib/service.rst b/docs/web/api/lib/service.rst new file mode 100644 index 0000000..5cd8abb --- /dev/null +++ b/docs/web/api/lib/service.rst @@ -0,0 +1,8 @@ +======= +service +======= + +Service layer + +.. automodule:: src.server.lib.service + :members: diff --git a/docs/web/api/lib/types.rst b/docs/web/api/lib/types.rst new file mode 100644 index 0000000..5212d80 --- /dev/null +++ b/docs/web/api/lib/types.rst @@ -0,0 +1,8 @@ +===== +types +===== + +Application types + +.. automodule:: src.server.lib.types + :members: diff --git a/docs/web/usage/index.rst b/docs/web/usage/index.rst index b9155c7..474393a 100644 --- a/docs/web/usage/index.rst +++ b/docs/web/usage/index.rst @@ -1,5 +1,9 @@ +===== Usage ===== .. toctree:: :titlesonly: + :caption: Web Usage Documentation + + server diff --git a/docs/web/usage/server.rst b/docs/web/usage/server.rst new file mode 100644 index 0000000..a2fc57d --- /dev/null +++ b/docs/web/usage/server.rst @@ -0,0 +1,33 @@ +================ +About the Server +================ + +The server side of Byte is utilizing the `Litestar `_ framework. +It is an immensely powerful, fast, and easy to use framework that is perfect for Byte. + +The server is responsible for handling all of the requests to and from the database, +as well as serving the frontend to the user; it utilizes the `Jinja2 `_ +templating engine to do so. + +There are a few key components to the server, structured like so: + +* ``app.py``: The main entry point for the server where the application factory is defined. +* ``cli.py``: The command line interface for the server. +* ``__main__.py``: The entry point for the server when running it via the command line. +* ``lib/``: A directory containing all of the server's logic, settings, and configuration. +* ``domain/``: A directory containing all of the server's domain logic, including the database models and routes. + +The domain is further broken down into logical components, such as: + +* ``db/``: The database models + + .. note:: These once were houses inside the respective domain components, but were moved to a separate directory to + make it easier to manage and maintain the database models (but also because circular imports are bad). +* ``github/``: The GitHub domain logic, including connectivity to GitHub via the + `githubkit `_ library. +* ``guilds/``: The guilds domain logic, including the guilds routes and any helper functions used to perform CRUD + operations on guilds. +* ``system/``: The system domain logic, mainly for dealing with tasks and healthchecks for the core system. +* ``web/``: The web domain logic, including the web routes, frontend resources, and templates + +This structure allows for a clear separation of concerns and makes it easy to find and maintain the server's logic. diff --git a/docs/web/usage/starting.rst b/docs/web/usage/starting.rst new file mode 100644 index 0000000..29d7be2 --- /dev/null +++ b/docs/web/usage/starting.rst @@ -0,0 +1,5 @@ +=============== +Starting Server +=============== + +.. todo:: This section needs to be written. diff --git a/pdm.lock b/pdm.lock index 934b060..7fce002 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,24 +5,25 @@ groups = ["default", "docs", "lint", "test"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:7098b3193b937e135dd887069755130b9528dc064cdd068424cf78340e091281" +content_hash = "sha256:56e9925849af7995247ba4070b4957ac69a8ed1f7c21b7112a671945ec5e0f54" [[package]] name = "accessible-pygments" -version = "0.0.4" +version = "0.0.5" +requires_python = ">=3.9" summary = "A collection of accessible pygments styles" groups = ["docs"] dependencies = [ "pygments>=1.5", ] files = [ - {file = "accessible-pygments-0.0.4.tar.gz", hash = "sha256:e7b57a9b15958e9601c7e9eb07a440c813283545a20973f2574a5f453d0e953e"}, - {file = "accessible_pygments-0.0.4-py2.py3-none-any.whl", hash = "sha256:416c6d8c1ea1c5ad8701903a20fcedf953c6e720d64f33dc47bfb2d3f2fa4e8d"}, + {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"}, + {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"}, ] [[package]] name = "advanced-alchemy" -version = "0.7.4" +version = "0.8.4" requires_python = ">=3.8" summary = "Ready-to-go SQLAlchemy concoctions." groups = ["default"] @@ -33,13 +34,13 @@ dependencies = [ "typing-extensions>=4.0.0", ] files = [ - {file = "advanced_alchemy-0.7.4-py3-none-any.whl", hash = "sha256:52502d1131963dfcdca52c0edd6935e1c21ca3b5612677769ccf48f214383053"}, - {file = "advanced_alchemy-0.7.4.tar.gz", hash = "sha256:d4097fc337d6c971fc673695fdc3ff5f0d69282c7fea3f4754ef7c345051c4c3"}, + {file = "advanced_alchemy-0.8.4-py3-none-any.whl", hash = "sha256:0ad4e2e33cfa6f8641ea7e9df50c8dbb18890000080b74d85305ba8821c2ce50"}, + {file = "advanced_alchemy-0.8.4.tar.gz", hash = "sha256:6436ebad0e0b92ef5093bf425961f5469020035c65953ff7f619aef24a08fc71"}, ] [[package]] name = "aiohttp" -version = "3.9.3" +version = "3.9.5" requires_python = ">=3.8" summary = "Async http client/server framework (asyncio)" groups = ["default"] @@ -51,37 +52,22 @@ dependencies = [ "yarl<2.0,>=1.0", ] files = [ - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, - {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, - {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, - {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, - {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, - {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [[package]] @@ -141,7 +127,7 @@ name = "anyio" version = "4.3.0" requires_python = ">=3.8" summary = "High level compatibility layer for multiple asynchronous event loop implementations" -groups = ["default"] +groups = ["default", "docs"] dependencies = [ "idna>=2.8", "sniffio>=1.1", @@ -194,36 +180,13 @@ files = [ {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, ] -[[package]] -name = "async-timeout" -version = "4.0.3" -requires_python = ">=3.7" -summary = "Timeout context manager for asyncio programs" -groups = ["default"] -marker = "python_version < \"3.12.0\"" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - [[package]] name = "asyncpg" version = "0.29.0" requires_python = ">=3.8.0" summary = "An asyncio PostgreSQL driver" groups = ["default"] -dependencies = [ - "async-timeout>=4.0.3; python_version < \"3.12.0\"", -] files = [ - {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, - {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, - {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, @@ -248,8 +211,8 @@ files = [ [[package]] name = "autodoc-pydantic" -version = "2.0.1" -requires_python = ">=3.7.1,<4.0.0" +version = "2.2.0" +requires_python = "<4.0.0,>=3.8.1" summary = "Seamlessly integrate pydantic models in your Sphinx documentation." groups = ["docs"] dependencies = [ @@ -258,8 +221,7 @@ dependencies = [ "pydantic<3.0.0,>=2.0", ] files = [ - {file = "autodoc_pydantic-2.0.1-py3-none-any.whl", hash = "sha256:d3c302fdb6d37edb5b721f0f540252fa79cea7018bc1a9a85bf70f33a68b0ce4"}, - {file = "autodoc_pydantic-2.0.1.tar.gz", hash = "sha256:7a125a4ff18e4903e27be71e4ddb3269380860eacab4a584d6cc2e212fa96991"}, + {file = "autodoc_pydantic-2.2.0-py3-none-any.whl", hash = "sha256:8c6a36fbf6ed2700ea9c6d21ea76ad541b621fbdf16b5a80ee04673548af4d95"}, ] [[package]] @@ -278,13 +240,13 @@ files = [ [[package]] name = "babel" -version = "2.14.0" -requires_python = ">=3.7" +version = "2.15.0" +requires_python = ">=3.8" summary = "Internationalization utilities" groups = ["docs"] files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] [[package]] @@ -383,7 +345,7 @@ name = "cfgv" version = "3.4.0" requires_python = ">=3.8" summary = "Validate configuration and produce human readable error messages." -groups = ["default", "lint"] +groups = ["lint"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -397,21 +359,6 @@ summary = "The Real First Universal Charset Detector. Open, modern and actively groups = ["docs"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, @@ -468,68 +415,48 @@ files = [ [[package]] name = "coverage" -version = "7.4.3" +version = "7.5.1" requires_python = ">=3.8" summary = "Code coverage measurement for Python" groups = ["test"] files = [ - {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, - {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, - {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, - {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, - {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, - {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, - {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, + {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, + {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, + {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, + {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, ] [[package]] name = "coverage" -version = "7.4.3" +version = "7.5.1" extras = ["toml"] requires_python = ">=3.8" summary = "Code coverage measurement for Python" groups = ["test"] dependencies = [ - "coverage==7.4.3", -] -files = [ - {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, - {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, - {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, - {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, - {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, - {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, - {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, + "coverage==7.5.1", +] +files = [ + {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, + {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, + {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, + {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, + {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, ] [[package]] @@ -578,13 +505,13 @@ files = [ [[package]] name = "cssutils" -version = "2.9.0" +version = "2.10.2" requires_python = ">=3.8" summary = "A CSS Cascading Style Sheets library for Python" groups = ["docs"] files = [ - {file = "cssutils-2.9.0-py3-none-any.whl", hash = "sha256:f8b013169e281c0c6083207366c5005f5dd4549055f7aba840384fb06a78745c"}, - {file = "cssutils-2.9.0.tar.gz", hash = "sha256:89477b3d17d790e97b9fb4def708767061055795aae6f7c82ae32e967c9be4cd"}, + {file = "cssutils-2.10.2-py3-none-any.whl", hash = "sha256:4ad7d2f29270b22cf199f65a6b5e795f2c3130f3b9fb50c3d45e5054ef86e41a"}, + {file = "cssutils-2.10.2.tar.gz", hash = "sha256:93cf92a350b1c123b17feff042e212f94d960975a3ed145743d84ebe8ccec7ab"}, ] [[package]] @@ -634,7 +561,7 @@ files = [ name = "distlib" version = "0.3.8" summary = "Distribution utilities" -groups = ["default", "lint"] +groups = ["lint"] files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, @@ -642,13 +569,13 @@ files = [ [[package]] name = "docutils" -version = "0.20.1" -requires_python = ">=3.7" +version = "0.21.2" +requires_python = ">=3.9" summary = "Docutils -- Python Documentation Utilities" groups = ["docs"] files = [ - {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, - {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, ] [[package]] @@ -691,7 +618,7 @@ files = [ [[package]] name = "faker" -version = "23.3.0" +version = "25.1.0" requires_python = ">=3.8" summary = "Faker is a Python package that generates fake data for you." groups = ["default"] @@ -699,8 +626,8 @@ dependencies = [ "python-dateutil>=2.4", ] files = [ - {file = "Faker-23.3.0-py3-none-any.whl", hash = "sha256:117ce1a2805c1bc5ca753b3dc6f9d567732893b2294b827d3164261ee8f20267"}, - {file = "Faker-23.3.0.tar.gz", hash = "sha256:458d93580de34403a8dec1e8d5e6be2fee96c4deca63b95d71df7a6a80a690de"}, + {file = "Faker-25.1.0-py3-none-any.whl", hash = "sha256:24e28dce0b89683bb9e017e042b971c8c4909cff551b6d46f1e207674c7c2526"}, + {file = "Faker-25.1.0.tar.gz", hash = "sha256:2107618cf306bb188dcfea3e5cfd94aa92d65c7293a2437c1e96a99c83274755"}, ] [[package]] @@ -730,13 +657,13 @@ files = [ [[package]] name = "filelock" -version = "3.13.1" +version = "3.14.0" requires_python = ">=3.8" summary = "A platform independent file lock." -groups = ["default", "docs", "lint"] +groups = ["docs", "lint"] files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, + {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] [[package]] @@ -746,21 +673,6 @@ requires_python = ">=3.8" summary = "A list-like structure which implements collections.abc.MutableSequence" groups = ["default"] files = [ - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, @@ -782,31 +694,35 @@ files = [ [[package]] name = "githubkit" -version = "0.11.1" -requires_python = ">=3.8,<4.0" -git = "https://github.com/yanyongyu/githubkit.git" -revision = "5d88fdc2349496a696d000ea31fef30efcae5e55" +version = "0.11.4" +requires_python = "<4.0,>=3.8" summary = "GitHub SDK for Python" groups = ["default"] dependencies = [ - "hishel<0.0.22,>=0.0.21", + "hishel<=0.0.26,>=0.0.21", "httpx<1.0.0,>=0.23.0", "pydantic!=2.5.0,!=2.5.1,<3.0.0,>=1.9.1", "typing-extensions<5.0.0,>=4.3.0", ] +files = [ + {file = "githubkit-0.11.4-py3-none-any.whl", hash = "sha256:281af76e9ec24a1f3dc1c99ed4e50785898220d47f84acb2f87be8d7a572769a"}, + {file = "githubkit-0.11.4.tar.gz", hash = "sha256:e629d833f98157cb4b1c14b69fdb54f7f960fb0da6906f1fa30fca14c347c0d4"}, +] [[package]] name = "githubkit" -version = "0.11.1" +version = "0.11.4" extras = ["auth-app"] -requires_python = ">=3.8,<4.0" -git = "https://github.com/yanyongyu/githubkit.git" -revision = "5d88fdc2349496a696d000ea31fef30efcae5e55" +requires_python = "<4.0,>=3.8" summary = "GitHub SDK for Python" groups = ["default"] dependencies = [ "PyJWT[crypto]<3.0.0,>=2.4.0", - "githubkit @ git+https://github.com/yanyongyu/githubkit.git@5d88fdc2349496a696d000ea31fef30efcae5e55", + "githubkit==0.11.4", +] +files = [ + {file = "githubkit-0.11.4-py3-none-any.whl", hash = "sha256:281af76e9ec24a1f3dc1c99ed4e50785898220d47f84acb2f87be8d7a572769a"}, + {file = "githubkit-0.11.4.tar.gz", hash = "sha256:e629d833f98157cb4b1c14b69fdb54f7f960fb0da6906f1fa30fca14c347c0d4"}, ] [[package]] @@ -817,15 +733,6 @@ summary = "Lightweight in-process concurrent programming" groups = ["default", "docs"] marker = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\" or sys_platform == \"darwin\"" files = [ - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, @@ -843,7 +750,7 @@ name = "h11" version = "0.14.0" requires_python = ">=3.7" summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -groups = ["default"] +groups = ["default", "docs"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -851,16 +758,17 @@ files = [ [[package]] name = "hishel" -version = "0.0.21" +version = "0.0.26" requires_python = ">=3.8" summary = "Persistent cache implementation for httpx and httpcore" groups = ["default"] dependencies = [ "httpx>=0.22.0", + "typing-extensions>=4.8.0", ] files = [ - {file = "hishel-0.0.21-py3-none-any.whl", hash = "sha256:2347180fb2396700f0616cdfd4bb9d04ba0c543694d19f2d0156226dfa5a511d"}, - {file = "hishel-0.0.21.tar.gz", hash = "sha256:62de30930e7fc02551d17d0f653f6e79499eaeffe32be9e36664ea7e0c0b82bd"}, + {file = "hishel-0.0.26-py3-none-any.whl", hash = "sha256:63cd3fee495124e0ed3461b374c43eab3e9deb2f01ba72eafe36d7df5fcf8b46"}, + {file = "hishel-0.0.26.tar.gz", hash = "sha256:f0ae2766214499cb0253a5ec7694f0d6e3835c9a35634356f8926fb7a1cf379e"}, ] [[package]] @@ -880,7 +788,7 @@ files = [ [[package]] name = "httpcore" -version = "1.0.4" +version = "1.0.5" requires_python = ">=3.8" summary = "A minimal low-level HTTP client." groups = ["default"] @@ -889,8 +797,8 @@ dependencies = [ "h11<0.15,>=0.13", ] files = [ - {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, - {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, ] [[package]] @@ -937,7 +845,7 @@ files = [ [[package]] name = "hypothesis" -version = "6.98.13" +version = "6.100.6" requires_python = ">=3.8" summary = "A library for property-based testing" groups = ["test"] @@ -946,30 +854,30 @@ dependencies = [ "sortedcontainers<3.0.0,>=2.1.0", ] files = [ - {file = "hypothesis-6.98.13-py3-none-any.whl", hash = "sha256:42ba2cc2d1fe04a65124fadfc6a305dbf62607aa9f8f94a10efadee9cfa1c4dd"}, - {file = "hypothesis-6.98.13.tar.gz", hash = "sha256:746b5316da2c7af4c3816c34af675909fcb1a6a0e5c7af5cfc36c450be2dca34"}, + {file = "hypothesis-6.100.6-py3-none-any.whl", hash = "sha256:416d2c2b248df6fe71e2ae94b4b475cbaaced4c949929039e0484463dfc30644"}, + {file = "hypothesis-6.100.6.tar.gz", hash = "sha256:726663fe65c793cd0c092b82ce737940f7da7fa1297a81da898c3317e5a19026"}, ] [[package]] name = "identify" -version = "2.5.35" +version = "2.5.36" requires_python = ">=3.8" summary = "File identification library for Python" -groups = ["default", "lint"] +groups = ["lint"] files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [[package]] name = "idna" -version = "3.6" +version = "3.7" requires_python = ">=3.5" summary = "Internationalized Domain Names in Applications (IDNA)" groups = ["default", "docs"] files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1010,7 +918,7 @@ files = [ [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" requires_python = ">=3.7" summary = "A very fast and expressive template engine." groups = ["default", "docs"] @@ -1018,8 +926,8 @@ dependencies = [ "MarkupSafe>=2.0", ] files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [[package]] @@ -1037,7 +945,7 @@ files = [ [[package]] name = "litestar" -version = "2.6.1" +version = "2.8.3" requires_python = "<4.0,>=3.8" summary = "Litestar - A production-ready, highly performant, extensible ASGI API Framework" groups = ["default"] @@ -1054,13 +962,13 @@ dependencies = [ "typing-extensions", ] files = [ - {file = "litestar-2.6.1-py3-none-any.whl", hash = "sha256:33451b705f04f1f1ee991c28502afc2ce1b5ca5df25f0ae6ae8cf11063397e42"}, - {file = "litestar-2.6.1.tar.gz", hash = "sha256:3232190578d81180cce765840dfe72ef6b5d7ec2aead6fc37d664e61d8e2f647"}, + {file = "litestar-2.8.3-py3-none-any.whl", hash = "sha256:bef5e8592b1afba24306f9a5834d84cef89826b12f58a97d3099cf49e2f3767d"}, + {file = "litestar-2.8.3.tar.gz", hash = "sha256:3764eef3885b331bfb15c478a2b9e2bc1980741ff58bd5512d599436f669549c"}, ] [[package]] name = "litestar" -version = "2.6.1" +version = "2.8.3" extras = ["jwt", "opentelemetry", "prometheus", "standard", "structlog"] requires_python = "<4.0,>=3.8" summary = "Litestar - A production-ready, highly performant, extensible ASGI API Framework" @@ -1070,7 +978,7 @@ dependencies = [ "fast-query-parsers>=1.0.2", "jinja2", "jsbeautifier", - "litestar==2.6.1", + "litestar==2.8.3", "opentelemetry-instrumentation-asgi", "prometheus-client", "python-jose", @@ -1079,27 +987,13 @@ dependencies = [ "uvloop>=0.18.0; sys_platform != \"win32\"", ] files = [ - {file = "litestar-2.6.1-py3-none-any.whl", hash = "sha256:33451b705f04f1f1ee991c28502afc2ce1b5ca5df25f0ae6ae8cf11063397e42"}, - {file = "litestar-2.6.1.tar.gz", hash = "sha256:3232190578d81180cce765840dfe72ef6b5d7ec2aead6fc37d664e61d8e2f647"}, -] - -[[package]] -name = "livereload" -version = "2.6.3" -summary = "Python LiveReload is an awesome tool for web developers" -groups = ["docs"] -dependencies = [ - "six", - "tornado; python_version > \"2.7\"", -] -files = [ - {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, - {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, + {file = "litestar-2.8.3-py3-none-any.whl", hash = "sha256:bef5e8592b1afba24306f9a5834d84cef89826b12f58a97d3099cf49e2f3767d"}, + {file = "litestar-2.8.3.tar.gz", hash = "sha256:3764eef3885b331bfb15c478a2b9e2bc1980741ff58bd5512d599436f669549c"}, ] [[package]] name = "mako" -version = "1.3.2" +version = "1.3.3" requires_python = ">=3.8" summary = "A super-fast templating language that borrows the best ideas from the existing templating languages." groups = ["default"] @@ -1107,8 +1001,8 @@ dependencies = [ "MarkupSafe>=0.9.2", ] files = [ - {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"}, - {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"}, + {file = "Mako-1.3.3-py3-none-any.whl", hash = "sha256:5324b88089a8978bf76d1629774fcc2f1c07b82acdf00f4c5dd8ceadfffc4b40"}, + {file = "Mako-1.3.3.tar.gz", hash = "sha256:e16c01d9ab9c11f7290eef1cfefc093fb5a45ee4a3da09e2fec2e4d1bae54e73"}, ] [[package]] @@ -1132,16 +1026,6 @@ requires_python = ">=3.7" summary = "Safely add untrusted strings to HTML/XML markup." groups = ["default", "docs"] files = [ - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, @@ -1168,34 +1052,23 @@ files = [ [[package]] name = "msgpack" -version = "1.0.7" +version = "1.0.8" requires_python = ">=3.8" summary = "MessagePack serializer" groups = ["docs"] files = [ - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, - {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, - {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, - {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, - {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, - {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, + {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, + {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, + {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] [[package]] @@ -1205,13 +1078,6 @@ requires_python = ">=3.8" summary = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." groups = ["default"] files = [ - {file = "msgspec-0.18.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e77e56ffe2701e83a96e35770c6adb655ffc074d530018d1b584a8e635b4f36f"}, - {file = "msgspec-0.18.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5351afb216b743df4b6b147691523697ff3a2fc5f3d54f771e91219f5c23aaa"}, - {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3232fabacef86fe8323cecbe99abbc5c02f7698e3f5f2e248e3480b66a3596b"}, - {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b524df6ea9998bbc99ea6ee4d0276a101bcc1aa8d14887bb823914d9f60d07"}, - {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37f67c1d81272131895bb20d388dd8d341390acd0e192a55ab02d4d6468b434c"}, - {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0feb7a03d971c1c0353de1a8fe30bb6579c2dc5ccf29b5f7c7ab01172010492"}, - {file = "msgspec-0.18.6-cp311-cp311-win_amd64.whl", hash = "sha256:41cf758d3f40428c235c0f27bc6f322d43063bc32da7b9643e3f805c21ed57b4"}, {file = "msgspec-0.18.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d86f5071fe33e19500920333c11e2267a31942d18fed4d9de5bc2fbab267d28c"}, {file = "msgspec-0.18.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce13981bfa06f5eb126a3a5a38b1976bddb49a36e4f46d8e6edecf33ccf11df1"}, {file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97dec6932ad5e3ee1e3c14718638ba333befc45e0661caa57033cd4cc489466"}, @@ -1229,21 +1095,6 @@ requires_python = ">=3.7" summary = "multidict implementation" groups = ["default"] files = [ - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, @@ -1265,7 +1116,7 @@ files = [ [[package]] name = "mypy" -version = "1.8.0" +version = "1.10.0" requires_python = ">=3.8" summary = "Optional static typing for Python" groups = ["lint"] @@ -1274,18 +1125,13 @@ dependencies = [ "typing-extensions>=4.1.0", ] files = [ - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, + {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, + {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, + {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, + {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, + {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, ] [[package]] @@ -1315,7 +1161,7 @@ name = "nodeenv" version = "1.8.0" requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" summary = "Node.js virtual environment builder" -groups = ["default", "lint"] +groups = ["lint"] dependencies = [ "setuptools", ] @@ -1397,40 +1243,40 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.0" requires_python = ">=3.7" summary = "Core utilities for Python packages" groups = ["docs", "test"] files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] name = "platformdirs" -version = "4.2.0" +version = "4.2.1" requires_python = ">=3.8" -summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -groups = ["default", "docs", "lint"] +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +groups = ["docs", "lint"] files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" requires_python = ">=3.8" summary = "plugin and hook calling mechanisms for python" groups = ["test"] files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [[package]] name = "polyfactory" -version = "2.14.1" +version = "2.15.0" requires_python = "<4.0,>=3.8" summary = "Mock data generation factories" groups = ["default"] @@ -1439,16 +1285,16 @@ dependencies = [ "typing-extensions>=4.6.0", ] files = [ - {file = "polyfactory-2.14.1-py3-none-any.whl", hash = "sha256:8aff3be75e046501ec5c411c78c23db284322c760fef50d560ee6ed683f217c8"}, - {file = "polyfactory-2.14.1.tar.gz", hash = "sha256:8c1d5f15dad1ebfd0845d65d4a55f9791cddfa6b3096ad9f9e2fd02a4804631b"}, + {file = "polyfactory-2.15.0-py3-none-any.whl", hash = "sha256:ff5b6a8742cbd6fbde9f81310b9732d5421fbec31916d6ede5a977753110fbe9"}, + {file = "polyfactory-2.15.0.tar.gz", hash = "sha256:a3ff5263756ad74acf4001f04c1b6aab7d1197cbaa070352df79573a8dcd85ec"}, ] [[package]] name = "pre-commit" -version = "3.6.2" +version = "3.7.0" requires_python = ">=3.9" summary = "A framework for managing and maintaining multi-language pre-commit hooks." -groups = ["default", "lint"] +groups = ["lint"] dependencies = [ "cfgv>=2.0.0", "identify>=1.0.0", @@ -1457,8 +1303,8 @@ dependencies = [ "virtualenv>=20.10.0", ] files = [ - {file = "pre_commit-3.6.2-py2.py3-none-any.whl", hash = "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c"}, - {file = "pre_commit-3.6.2.tar.gz", hash = "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e"}, + {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"}, + {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"}, ] [[package]] @@ -1507,73 +1353,60 @@ files = [ [[package]] name = "pydantic" -version = "2.6.2" +version = "2.7.1" requires_python = ">=3.8" summary = "Data validation using Python type hints" groups = ["default", "docs"] dependencies = [ "annotated-types>=0.4.0", - "pydantic-core==2.16.3", + "pydantic-core==2.18.2", "typing-extensions>=4.6.1", ] files = [ - {file = "pydantic-2.6.2-py3-none-any.whl", hash = "sha256:37a5432e54b12fecaa1049c5195f3d860a10e01bdfd24f1840ef14bd0d3aeab3"}, - {file = "pydantic-2.6.2.tar.gz", hash = "sha256:a09be1c3d28f3abe37f8a78af58284b236a92ce520105ddc91a6d29ea1176ba7"}, + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, ] [[package]] name = "pydantic-core" -version = "2.16.3" +version = "2.18.2" requires_python = ">=3.8" -summary = "" +summary = "Core functionality for Pydantic validation and serialization" groups = ["default", "docs"] dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] files = [ - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, - {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, - {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, - {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, - {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, - {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, - {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, - {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, ] [[package]] @@ -1614,13 +1447,13 @@ files = [ [[package]] name = "pygments" -version = "2.17.2" -requires_python = ">=3.7" +version = "2.18.0" +requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." groups = ["default", "docs"] files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [[package]] @@ -1652,7 +1485,7 @@ files = [ [[package]] name = "pyright" -version = "1.1.351" +version = "1.1.362" requires_python = ">=3.7" summary = "Command line wrapper for pyright" groups = ["lint"] @@ -1660,8 +1493,8 @@ dependencies = [ "nodeenv>=1.6.0", ] files = [ - {file = "pyright-1.1.351-py3-none-any.whl", hash = "sha256:83b44b25396ae20661fc5f133c3fce30928ff1296d4f2e5ff0bca5fcf03eb89d"}, - {file = "pyright-1.1.351.tar.gz", hash = "sha256:01124099714eebd7f6525d8cbfa350626b56dfaf771cfcd55c03e69f0f1efbbd"}, + {file = "pyright-1.1.362-py3-none-any.whl", hash = "sha256:969957cff45154d8a45a4ab1dae5bdc8223d8bd3c64654fa608ab3194dfff319"}, + {file = "pyright-1.1.362.tar.gz", hash = "sha256:6a477e448d4a07a6a0eab58b2a15a1bbed031eb3169fa809edee79cca168d83a"}, ] [[package]] @@ -1677,7 +1510,7 @@ files = [ [[package]] name = "pytest" -version = "8.0.2" +version = "8.2.0" requires_python = ">=3.8" summary = "pytest: simple powerful testing with Python" groups = ["test"] @@ -1685,16 +1518,16 @@ dependencies = [ "colorama; sys_platform == \"win32\"", "iniconfig", "packaging", - "pluggy<2.0,>=1.3.0", + "pluggy<2.0,>=1.5", ] files = [ - {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, - {file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, + {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, + {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, ] [[package]] name = "pytest-asyncio" -version = "0.23.5" +version = "0.23.6" requires_python = ">=3.8" summary = "Pytest support for asyncio" groups = ["test"] @@ -1702,8 +1535,8 @@ dependencies = [ "pytest<9,>=7.0.0", ] files = [ - {file = "pytest-asyncio-0.23.5.tar.gz", hash = "sha256:3a048872a9c4ba14c3e90cc1aa20cbc2def7d01c7c8db3777ec281ba9c057675"}, - {file = "pytest_asyncio-0.23.5-py3-none-any.whl", hash = "sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac"}, + {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, + {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, ] [[package]] @@ -1723,8 +1556,8 @@ files = [ [[package]] name = "pytest-cov" -version = "4.1.0" -requires_python = ">=3.7" +version = "5.0.0" +requires_python = ">=3.8" summary = "Pytest plugin for measuring coverage." groups = ["test"] dependencies = [ @@ -1732,8 +1565,8 @@ dependencies = [ "pytest>=4.6", ] files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [[package]] @@ -1752,21 +1585,21 @@ files = [ [[package]] name = "pytest-mock" -version = "3.12.0" +version = "3.14.0" requires_python = ">=3.8" summary = "Thin-wrapper around the mock package for easier use with pytest" groups = ["test"] dependencies = [ - "pytest>=5.0", + "pytest>=6.2.5", ] files = [ - {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"}, - {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"}, + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, ] [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" summary = "Extensions to the standard Python datetime module" groups = ["default"] @@ -1774,8 +1607,8 @@ dependencies = [ "six>=1.5", ] files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [[package]] @@ -1811,14 +1644,6 @@ requires_python = ">=3.6" summary = "YAML parser and emitter for Python" groups = ["default", "lint"] files = [ - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, @@ -1848,7 +1673,7 @@ files = [ [[package]] name = "rich" -version = "13.7.0" +version = "13.7.1" requires_python = ">=3.7.0" summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" groups = ["default"] @@ -1857,24 +1682,24 @@ dependencies = [ "pygments<3.0.0,>=2.13.0", ] files = [ - {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, - {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, ] [[package]] name = "rich-click" -version = "1.7.3" +version = "1.8.1" requires_python = ">=3.7" summary = "Format click help output nicely with rich" groups = ["default"] dependencies = [ "click>=7", - "rich>=10.7.0", + "rich>=10.7", "typing-extensions", ] files = [ - {file = "rich-click-1.7.3.tar.gz", hash = "sha256:bced1594c497dc007ab49508ff198bb437c576d01291c13a61658999066481f4"}, - {file = "rich_click-1.7.3-py3-none-any.whl", hash = "sha256:bc4163d4e2a3361e21c4d72d300eca6eb8896dfc978667923cb1d4937b8769a3"}, + {file = "rich_click-1.8.1-py3-none-any.whl", hash = "sha256:0cf0bf84404e78379bd2722db88cb07ffd0535440e20a05943d5b02249d90f8a"}, + {file = "rich_click-1.8.1.tar.gz", hash = "sha256:73c2ec88a66d7bf6b8c32783539d1c9c92c7c75847f14186092d27f83b206e8a"}, ] [[package]] @@ -1913,14 +1738,6 @@ summary = "C version of reader, parser and emitter for ruamel.yaml derived from groups = ["docs"] marker = "platform_python_implementation == \"CPython\" and python_version < \"3.13\"" files = [ - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, @@ -1934,39 +1751,39 @@ files = [ [[package]] name = "ruff" -version = "0.2.2" +version = "0.4.4" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." groups = ["default"] files = [ - {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, - {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, - {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, - {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, - {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, - {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, + {file = "ruff-0.4.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6"}, + {file = "ruff-0.4.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36"}, + {file = "ruff-0.4.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595"}, + {file = "ruff-0.4.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768"}, + {file = "ruff-0.4.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e"}, + {file = "ruff-0.4.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95"}, + {file = "ruff-0.4.4-py3-none-win32.whl", hash = "sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876"}, + {file = "ruff-0.4.4-py3-none-win_amd64.whl", hash = "sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae"}, + {file = "ruff-0.4.4-py3-none-win_arm64.whl", hash = "sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6"}, + {file = "ruff-0.4.4.tar.gz", hash = "sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af"}, ] [[package]] name = "setuptools" -version = "69.1.1" +version = "69.5.1" requires_python = ">=3.8" summary = "Easily download, build, install, upgrade, and uninstall Python packages" groups = ["default", "lint"] files = [ - {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, - {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [[package]] @@ -1999,7 +1816,7 @@ name = "sniffio" version = "1.3.1" requires_python = ">=3.7" summary = "Sniff out which async library your code is running under" -groups = ["default"] +groups = ["default", "docs"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2038,28 +1855,28 @@ files = [ [[package]] name = "sourcery" -version = "1.15.0" +version = "1.17.0" summary = "Magically refactor Python" groups = ["lint"] files = [ - {file = "sourcery-1.15.0-py2.py3-none-macosx_10_9_universal2.whl", hash = "sha256:5d8ab8f61d0a55418b5debb999f76538d8e946a9a69a9e0be66593c1e17ff20f"}, - {file = "sourcery-1.15.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:5c2d7723b7c1bbd3a904fa2d85ef074b9ef1091c917a4e8c57a88b70bcda0699"}, - {file = "sourcery-1.15.0-py2.py3-none-win_amd64.whl", hash = "sha256:ab3438770f4f1535a2a4d872f5a84d12773eeb00825b96a5f2ed321ec3912b5e"}, + {file = "sourcery-1.17.0-py2.py3-none-macosx_10_9_universal2.whl", hash = "sha256:1cd2fc122b63817f1e2337cace5c35f5012458dfa0f3db9c8840062bc2efbcd1"}, + {file = "sourcery-1.17.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:5464d0b5f14e84373b9c7e39a406cd77acaaa3a0c0cf3262ddd4bf93b5fe74ea"}, + {file = "sourcery-1.17.0-py2.py3-none-win_amd64.whl", hash = "sha256:0a07646e014c5b59fcfbcf142b1c283bbac2fa73f421850cb761d949f7a7bee6"}, ] [[package]] name = "sphinx" -version = "7.2.6" +version = "7.3.7" requires_python = ">=3.9" summary = "Python documentation generator" groups = ["docs"] dependencies = [ "Jinja2>=3.0", "Pygments>=2.14", - "alabaster<0.8,>=0.7", + "alabaster~=0.7.14", "babel>=2.9", "colorama>=0.4.5; sys_platform == \"win32\"", - "docutils<0.21,>=0.18.1", + "docutils<0.22,>=0.18.1", "imagesize>=1.3", "packaging>=21.0", "requests>=2.25.0", @@ -2072,38 +1889,41 @@ dependencies = [ "sphinxcontrib-serializinghtml>=1.1.9", ] files = [ - {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, - {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, + {file = "sphinx-7.3.7-py3-none-any.whl", hash = "sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3"}, + {file = "sphinx-7.3.7.tar.gz", hash = "sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc"}, ] [[package]] name = "sphinx-autobuild" -version = "2024.2.4" +version = "2024.4.16" requires_python = ">=3.9" -summary = "Rebuild Sphinx documentation on changes, with live-reload in the browser." +summary = "Rebuild Sphinx documentation on changes, with hot reloading in the browser." groups = ["docs"] dependencies = [ "colorama", - "livereload", "sphinx", + "starlette>=0.35", + "uvicorn>=0.25", + "watchfiles>=0.20", + "websockets>=11", ] files = [ - {file = "sphinx_autobuild-2024.2.4-py3-none-any.whl", hash = "sha256:63fd87ab7505872a89aef468ce6503f65e794a195f4ae62269db3b85b72d4854"}, - {file = "sphinx_autobuild-2024.2.4.tar.gz", hash = "sha256:cb9d2121a176d62d45471624872afc5fad7755ad662738abe400ecf4a7954303"}, + {file = "sphinx_autobuild-2024.4.16-py3-none-any.whl", hash = "sha256:f2522779d30fcbf0253e09714f274ce8c608cb6ebcd67922b1c54de59faba702"}, + {file = "sphinx_autobuild-2024.4.16.tar.gz", hash = "sha256:1c0ed37a1970eed197f9c5a66d65759e7c4e4cba7b5a5d77940752bf1a59f2c7"}, ] [[package]] name = "sphinx-autodoc-typehints" -version = "2.0.0" -requires_python = ">=3.8" +version = "2.1.0" +requires_python = ">=3.9" summary = "Type hints (PEP 484) support for the Sphinx autodoc extension" groups = ["docs"] dependencies = [ - "sphinx>=7.1.2", + "sphinx>=7.3.5", ] files = [ - {file = "sphinx_autodoc_typehints-2.0.0-py3-none-any.whl", hash = "sha256:12c0e161f6fe191c2cdfd8fa3caea271f5387d9fbc67ebcd6f4f1f24ce880993"}, - {file = "sphinx_autodoc_typehints-2.0.0.tar.gz", hash = "sha256:7f2cdac2e70fd9787926b6e9e541cd4ded1e838d2b46fda2a1bb0a75ec5b7f3a"}, + {file = "sphinx_autodoc_typehints-2.1.0-py3-none-any.whl", hash = "sha256:46f1a710b3ed35904f63a77c5e68334c5ee1c2e22828b75fdcd147f1c52c199b"}, + {file = "sphinx_autodoc_typehints-2.1.0.tar.gz", hash = "sha256:51bf8dc77c4fba747e32f0735002a91500747d0553cae616863848e8f5e49fe8"}, ] [[package]] @@ -2321,7 +2141,7 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.27" +version = "2.0.30" requires_python = ">=3.7" summary = "Database Abstraction Library" groups = ["default", "docs"] @@ -2330,24 +2150,30 @@ dependencies = [ "typing-extensions>=4.6.0", ] files = [ - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbcd77c4d94b23e0753c5ed8deba8c69f331d4fd83f68bfc9db58bc8983f49cd"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:680b9a36029b30cf063698755d277885d4a0eab70a2c7c6e71aab601323cba45"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win32.whl", hash = "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win_amd64.whl", hash = "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfc936870507da96aebb43e664ae3a71a7b96278382bcfe84d277b88e379b18"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4535c49d961fe9a77392e3a630a626af5baa967172d42732b7a43496c8b28876"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win32.whl", hash = "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win_amd64.whl", hash = "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd"}, - {file = "SQLAlchemy-2.0.27-py3-none-any.whl", hash = "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac"}, - {file = "SQLAlchemy-2.0.27.tar.gz", hash = "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"}, + {file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"}, + {file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"}, +] + +[[package]] +name = "starlette" +version = "0.37.2" +requires_python = ">=3.8" +summary = "The little ASGI library that shines." +groups = ["docs"] +dependencies = [ + "anyio<5,>=3.4.0", +] +files = [ + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, ] [[package]] @@ -2372,36 +2198,15 @@ files = [ {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, ] -[[package]] -name = "tornado" -version = "6.4" -requires_python = ">= 3.8" -summary = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -groups = ["docs"] -marker = "python_version > \"2.7\"" -files = [ - {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, - {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, - {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, - {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, - {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, -] - [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.11.0" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" groups = ["default", "docs", "lint"] files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] @@ -2417,22 +2222,22 @@ files = [ [[package]] name = "uvicorn" -version = "0.27.1" +version = "0.29.0" requires_python = ">=3.8" summary = "The lightning-fast ASGI server." -groups = ["default"] +groups = ["default", "docs"] dependencies = [ "click>=7.0", "h11>=0.8", ] files = [ - {file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"}, - {file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"}, + {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, + {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, ] [[package]] name = "uvicorn" -version = "0.27.1" +version = "0.29.0" extras = ["standard"] requires_python = ">=3.8" summary = "The lightning-fast ASGI server." @@ -2442,14 +2247,14 @@ dependencies = [ "httptools>=0.5.0", "python-dotenv>=0.13", "pyyaml>=5.1", - "uvicorn==0.27.1", + "uvicorn==0.29.0", "uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"", "watchfiles>=0.13", "websockets>=10.4", ] files = [ - {file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"}, - {file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"}, + {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, + {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, ] [[package]] @@ -2477,18 +2282,18 @@ files = [ [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.1" requires_python = ">=3.7" summary = "Virtual Python Environment builder" -groups = ["default", "lint"] +groups = ["lint"] dependencies = [ "distlib<1,>=0.3.7", "filelock<4,>=3.12.2", "platformdirs<5,>=3.9.1", ] files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, + {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, ] [[package]] @@ -2496,24 +2301,11 @@ name = "watchfiles" version = "0.21.0" requires_python = ">=3.8" summary = "Simple, modern and high performance file watching and code reload in python." -groups = ["default"] +groups = ["default", "docs"] dependencies = [ "anyio>=3.0.0", ] files = [ - {file = "watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7"}, - {file = "watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365"}, - {file = "watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400"}, - {file = "watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe"}, - {file = "watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078"}, {file = "watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a"}, {file = "watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1"}, {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a"}, @@ -2557,19 +2349,8 @@ name = "websockets" version = "12.0" requires_python = ">=3.8" summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -groups = ["default"] +groups = ["default", "docs"] files = [ - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, @@ -2642,21 +2423,6 @@ dependencies = [ "multidict>=4.0", ] files = [ - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, diff --git a/pyproject.toml b/pyproject.toml index 29d5529..2b4ac7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,14 +12,13 @@ dependencies = [ "litestar[jwt,opentelemetry,prometheus,standard,structlog]>=2.4.3", "pydantic-settings>=2.1.0", "anyio>=4.1.0", - "advanced-alchemy>=0.6.1", + "advanced-alchemy>=0.6.1, <0.9.0", "certifi>=2023.11.17", "asyncpg>=0.29.0", - "githubkit[auth-app] @ git+https://github.com/yanyongyu/githubkit.git", "PyJWT>=2.8.0", "alembic>=1.13.0", - "pre-commit>=3.6.2", "ruff>=0.1.7", + "githubkit[auth-app]>=0.11.2", ] requires-python = ">=3.11,<4.0" readme = "README.md" @@ -30,6 +29,7 @@ classifiers = [ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'Intended Audience :: System Administrators', @@ -82,7 +82,7 @@ changelog = "git cliff -c pyproject.toml -o docs/changelog.rst" # TODO: Move more from makefile here ci = { composite = ["lint", "test"] } -[tool.pdm.dev-dependencies] +[project.optional-dependencies] test = [ "pytest>=7.4.3", "coverage>=7.3.2", @@ -154,7 +154,7 @@ exclude = [ "tools", "docs", ] -pythonVersion = "3.11" +pythonVersion = "3.12" reportOptionalMemberAccess = false reportUnknownMemberType = false reportGeneralTypeIssues = true @@ -208,7 +208,7 @@ strict-imports = false [tool.ruff] line-length = 120 src = ["src", "tests"] -target-version = "py311" +target-version = "py312" [tool.ruff.lint] select = ["ALL"] @@ -236,7 +236,7 @@ classmethod-decorators = [ ] [tool.ruff.lint.isort] -known-first-party = ["src", "tests"] +known-first-party = ["src", "tests", "byte", "server", "app", "utils"] [tool.ruff.lint.per-file-ignores] # Tests can use magic values, assertions, and relative imports @@ -254,7 +254,7 @@ known-first-party = ["src", "tests"] ] "src/**/*.*" = ["PLR0913", "SLF001"] "src/server/lib/db/base.py" = ["E501"] -"src/server/lib/db/migrations/versions/*.*" = ["D", "INP", "PGH"] +"src/server/lib/db/migrations/versions/*.*" = ["D", "INP", "PGH", "N999"] "tests/**/*.*" = [ "S101", "D", diff --git a/src/__init__.py b/src/__init__.py index f9b6856..538e852 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,4 +1,5 @@ """Byte Bot.""" + from __future__ import annotations from rich import get_console diff --git a/src/__main__.py b/src/__main__.py index 2a7da61..ab78994 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -1,4 +1,5 @@ """Application Entrypoint.""" + from __future__ import annotations __all__ = ("run_cli",) diff --git a/src/app.py b/src/app.py index 0a9bd6a..0707eea 100644 --- a/src/app.py +++ b/src/app.py @@ -1,4 +1,5 @@ """ASGI application factory.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/src/byte/__init__.py b/src/byte/__init__.py index fe48f1b..44b0719 100644 --- a/src/byte/__init__.py +++ b/src/byte/__init__.py @@ -1,9 +1,10 @@ """Byte Bot Bot.""" + from __future__ import annotations -from byte import bot, lib +from byte import bot, lib, plugins, views from byte.lib.log import setup_logging -__all__ = ["bot", "lib"] +__all__ = ("bot", "lib", "views", "setup_logging", "plugins") setup_logging() diff --git a/src/byte/bot.py b/src/byte/bot.py index 8d26113..6d33e3d 100644 --- a/src/byte/bot.py +++ b/src/byte/bot.py @@ -1,4 +1,5 @@ """Byte Bot.""" + from __future__ import annotations import contextlib @@ -9,6 +10,7 @@ from discord import Activity, Forbidden, Intents, Member, Message, NotFound from discord.ext.commands import Bot, CommandError, Context, ExtensionAlreadyLoaded from dotenv import load_dotenv +from httpx import ConnectError from byte.lib import settings from byte.lib.log import get_logger @@ -22,18 +24,6 @@ load_dotenv() -async def on_member_join(member: Member) -> None: - """Handle member join event. - - Args: - member: Member object. - """ - await member.send( - f"Welcome to {member.guild.name}! Please make sure to read the rules if you haven't already. " - f"Feel free to ask any questions you have in the help channel." - ) - - class Byte(Bot): """Byte Bot Base Class.""" @@ -111,10 +101,11 @@ async def on_member_join(member: Member) -> None: Args: member: Member object. """ - await member.send( - f"Welcome to {member.guild.name}! Please make sure to read the rules if you haven't already. " - f"Feel free to ask any questions you have in the help channel." - ) + if not member.bot: + await member.send( + f"Welcome to {member.guild.name}! Please make sure to read the rules if you haven't already. " + f"Feel free to ask any questions you have in the help channel." + ) async def on_guild_join(self, guild: discord.Guild) -> None: """Handle guild join event. @@ -125,13 +116,33 @@ async def on_guild_join(self, guild: discord.Guild) -> None: await self.tree.sync(guild=guild) api_url = f"http://0.0.0.0:8000/api/guilds/create?guild_id={guild.id}&guild_name={guild.name}" - async with httpx.AsyncClient() as client: - response = await client.post(api_url) - - if response.status_code == httpx.codes.CREATED: - logger.info("successfully added guild %s (ID: %s)", guild.name, guild.id) - else: - logger.error("%s joined guild '%s' but was not added to database", self.user.name, guild.name) + try: + async with httpx.AsyncClient() as client: + response = await client.post(api_url) + + if response.status_code == httpx.codes.CREATED: + logger.info("successfully added guild %s (id: %s)", guild.name, guild.id) + embed = discord.Embed( + title="Guild Joined", + description=f"Joined guild {guild.name} (ID: {guild.id})", + color=discord.Color.green(), + ) + else: + embed = discord.Embed( + title="Guild Join Failed", + description=f"Joined guild, but failed to add guild {guild.name} (ID: {guild.id}) to database", + color=discord.Color.red(), + ) + + if dev_guild := self.get_guild(settings.discord.DEV_GUILD_ID): + if dev_channel := dev_guild.get_channel(settings.discord.DEV_GUILD_INTERNAL_ID): + await dev_channel.send(embed=embed) + else: + logger.error("dev channel not found.") + else: + logger.error("dev guild not found.") + except ConnectError: + logger.exception("failed to connect to api to add guild %s (id: %s)", guild.name, guild.id) def run_bot() -> None: diff --git a/src/byte/lib/__init__.py b/src/byte/lib/__init__.py index 28cfd13..412b7bf 100644 --- a/src/byte/lib/__init__.py +++ b/src/byte/lib/__init__.py @@ -1,10 +1,11 @@ """Byte library module.""" -from byte.lib import common, log, settings, utils +from byte.lib import common, log, settings, types, utils __all__ = [ "settings", "utils", "log", "common", + "types", ] diff --git a/src/byte/lib/checks.py b/src/byte/lib/checks.py new file mode 100644 index 0000000..e95a28d --- /dev/null +++ b/src/byte/lib/checks.py @@ -0,0 +1,65 @@ +""":doc:`Checks ` for Byte.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from discord.ext.commands import CheckFailure, Context, check + +from byte.lib import settings + +if TYPE_CHECKING: + from collections.abc import Callable + + from discord.ext.commands._types import Check + +__all__ = ("is_byte_dev", "is_guild_admin") + + +def is_guild_admin() -> Callable[[Context], Check]: + """Check if the user is a guild admin. + + Returns: + A check function. + """ + + async def predicate(ctx: Context) -> bool: + """Check if the user is a guild admin. + + Args: + ctx: Context object. + + Returns: + True if the user is a guild admin, False otherwise. + """ + if not (member := ctx.guild.get_member(ctx.author.id)): + msg = "Member not found in the guild." + raise CheckFailure(msg) + return member.guild_permissions.administrator + + return check(predicate) + + +def is_byte_dev() -> Callable[[Context], Check]: + """Determines if the user is a Byte developer or owner. + + Returns: + A check function. + """ + + async def predicate(ctx: Context) -> bool: + """Check if the user is a Byte developer or owner. + + Args: + ctx: Context object. + + Returns: + True if the user is a Byte developer or owner, False otherwise. + """ + return ( + await ctx.bot.is_owner(ctx.author) + or ctx.author.id == settings.discord.DEV_USER_ID + or any(role.name == "byte-dev" for role in ctx.author.roles) + ) + + return check(predicate) diff --git a/src/byte/lib/common/__init__.py b/src/byte/lib/common/__init__.py index 5a7f255..4c23612 100644 --- a/src/byte/lib/common/__init__.py +++ b/src/byte/lib/common/__init__.py @@ -3,11 +3,55 @@ .. todo:: temporary, these are not multi-guild friendly. """ -from byte.lib.common import assets, colors, guilds, links +from typing import Any + +from byte.lib.common import assets, colors, guilds, links, mention __all__ = ( "assets", "colors", "guilds", "links", + "config_options", + "mention", ) + +config_options: list[dict[str, Any]] = [ + { + "label": "Server Settings", + "description": "Configure overall server settings", + "sub_settings": [ + {"label": "Prefix", "field": "prefix", "data_type": "String"}, + {"label": "Help Channel ID", "field": "help_channel_id", "data_type": "Integer"}, + {"label": "Sync Label", "field": "sync_label", "data_type": "String"}, + {"label": "Issue Linking", "field": "issue_linking", "data_type": "True/False"}, + {"label": "Comment Linking", "field": "comment_linking", "data_type": "True/False"}, + {"label": "PEP Linking", "field": "pep_linking", "data_type": "True/False"}, + ], + }, + { + "label": "GitHub Settings", + "description": "Configure GitHub settings", + "sub_settings": [ + {"label": "Discussion Sync", "field": "discussion_sync", "data_type": "True/False"}, + {"label": "GitHub Organization", "field": "github_organization", "data_type": "String"}, + {"label": "GitHub Repository", "field": "github_repository", "data_type": "String"}, + ], + }, + { + "label": "StackOverflow Settings", + "description": "Configure StackOverflow settings", + "sub_settings": [ + {"label": "Tag Name", "field": "tag_name", "data_type": "Comma-Separated String"}, + ], + }, + { + "label": "Allowed Users", + "description": "Configure allowed users", + "sub_settings": [ + {"label": "User ID", "field": "user_id", "data_type": "Integer"}, + ], + }, + # Forum Settings: Configure help and showcase forum settings + # Byte Settings: Configure meta-level Byte features +] diff --git a/src/byte/lib/common/assets.py b/src/byte/lib/common/assets.py index d478841..ea5e52d 100644 --- a/src/byte/lib/common/assets.py +++ b/src/byte/lib/common/assets.py @@ -2,6 +2,7 @@ .. todo:: temporary, these are not multi-guild friendly. """ + from typing import Final # --- Assets diff --git a/src/byte/lib/common/colors.py b/src/byte/lib/common/colors.py index 166aa7a..622f0bb 100644 --- a/src/byte/lib/common/colors.py +++ b/src/byte/lib/common/colors.py @@ -2,11 +2,17 @@ .. todo:: temporary, these are not multi-guild friendly. """ + from typing import Final # --- Colors -litestar_blue: Final = 0x7289DA +litestar_blue: Final = 0x202235 +litestar_yellow: Final = 0xEDB641 + python_blue: Final = 0x4B8BBE python_yellow: Final = 0xFFD43B + astral_yellow: Final = 0xD7FF64 astral_purple: Final = 0x261230 + +byte_teal: Final = 0x42B1A8 diff --git a/src/byte/lib/common/guilds.py b/src/byte/lib/common/guilds.py index cee58ae..6f05964 100644 --- a/src/byte/lib/common/guilds.py +++ b/src/byte/lib/common/guilds.py @@ -2,6 +2,7 @@ .. todo:: temporary, these are not multi-guild friendly. """ + from typing import Final # --- Channel IDs diff --git a/src/byte/lib/common/links.py b/src/byte/lib/common/links.py index a3a9a96..28eb128 100644 --- a/src/byte/lib/common/links.py +++ b/src/byte/lib/common/links.py @@ -2,14 +2,10 @@ .. todo:: temporary, these are not multi-guild friendly. """ + from typing import Final # --- Links -litestar_issues: Final = "https://github.com/litestar-org/litestar/issues" -github_litestar_projects: Final = "https://github.com/orgs/litestar-org/litestar/projects" -github_org_projects: Final = "https://github.com/orgs/litestar-org/projects" -pypi_litestar: Final = "https://pypi.org/project/litestar/" -pypi_polyfactory: Final = "https://pypi.org/project/polyfactory/" mcve: Final = "https://stackoverflow.com/help/minimal-reproducible-example" pastebin: Final = "https://paste.pythondiscord.com" markdown_guide: Final = "https://support.discord.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-#h_01GY0DAKGXDEHE263BCAYEGFJA" diff --git a/src/byte/lib/common/mention.py b/src/byte/lib/common/mention.py new file mode 100644 index 0000000..f81c6ba --- /dev/null +++ b/src/byte/lib/common/mention.py @@ -0,0 +1,129 @@ +"""Helper functions for mentioning users, roles, and channels.""" + +from __future__ import annotations + +__all__ = ( + "mention_channel", + "mention_custom_emoji", + "mention_custom_emoji_animated", + "mention_guild_navigation", + "mention_role", + "mention_slash_command", + "mention_timestamp", + "mention_user", + "mention_user_nickname", +) + + +def mention_user(user_id: int) -> str: + """Mention a user by ID. + + Args: + user_id: The unique identifier for the user. + + Returns: + A formatted string that mentions the user. + """ + return f"<@{user_id}>" + + +def mention_user_nickname(user_id: int) -> str: + """Mention a user by ID with a nickname. + + Args: + user_id: The unique identifier for the user. + + Returns: + A formatted string that mentions the user with a nickname. + """ + return f"<@!{user_id}>" + + +def mention_channel(channel_id: int) -> str: + """Mention a channel by ID. + + Args: + channel_id: The unique identifier for the channel. + + Returns: + A formatted string that mentions the channel. + """ + return f"<#{channel_id}>" + + +def mention_role(role_id: int) -> str: + """Mention a role by ID. + + Args: + role_id: The unique identifier for the role. + + Returns: + A formatted string that mentions the role. + """ + return f"<@&{role_id}>" + + +def mention_slash_command(name: str, command_id: int) -> str: + """Mention a slash command by name and ID. + + Args: + name: The name of the slash command. + command_id: The unique identifier for the slash command. + + Returns: + A formatted string that mentions the slash command. + """ + return f"" + + +def mention_custom_emoji(name: str, emoji_id: int) -> str: + """Mention a custom emoji by name and ID. + + Args: + name: The name of the emoji. + emoji_id: The unique identifier for the emoji. + + Returns: + A formatted string that mentions the custom emoji. + """ + return f"<:{name}:{emoji_id}>" + + +def mention_custom_emoji_animated(name: str, emoji_id: int) -> str: + """Mention an animated custom emoji by name and ID. + + Args: + name: The name of the animated emoji. + emoji_id: The unique identifier for the animated emoji. + + Returns: + A formatted string that mentions the animated custom emoji. + """ + return f"" + + +def mention_timestamp(timestamp: int, style: str = "") -> str: + """Mention a timestamp, optionally with a style. + + Args: + timestamp: The Unix timestamp to format. + style: An optional string representing the timestamp style. + (Default `` ``, valid styles: ``t``, ``T``, ``d``, ``D``, ``f``, ``F``, ``R``) + + Returns: + A formatted string that represents the timestamp. + """ + return f"" if style else f"" + + +def mention_guild_navigation(guild_nav_type: str, guild_element_id: int) -> str: + """Mention a guild navigation element by type and ID. + + Args: + guild_nav_type: The type of the guild navigation element. + guild_element_id: The unique identifier for the element. + + Returns: + A formatted string that mentions the guild navigation element. + """ + return f"<{guild_element_id}:{guild_nav_type}>" diff --git a/src/byte/lib/log.py b/src/byte/lib/log.py index b42849e..c1373e3 100644 --- a/src/byte/lib/log.py +++ b/src/byte/lib/log.py @@ -1,4 +1,5 @@ """Centralized logging configuration.""" + import logging import logging.config import logging.handlers @@ -72,6 +73,16 @@ def setup_logging() -> None: "handlers": ["console", "file"], "propagate": False, }, + "httpcore": { + "level": settings.log.HTTP_CORE_LEVEL, + "handlers": ["console", "file"], + "propagate": False, + }, + "httpx": { + "level": settings.log.HTTPX_LEVEL, + "handlers": ["console", "file"], + "propagate": False, + }, "websockets": { "level": settings.log.WEBSOCKETS_LEVEL, "handlers": ["console", "file"], diff --git a/src/byte/lib/settings.py b/src/byte/lib/settings.py index 6c50c5b..1dc0985 100644 --- a/src/byte/lib/settings.py +++ b/src/byte/lib/settings.py @@ -1,4 +1,5 @@ """Project Settings.""" + from __future__ import annotations import os @@ -41,6 +42,8 @@ class DiscordSettings(BaseSettings): """Discord Guild ID for development.""" DEV_USER_ID: int """Discord User ID for development.""" + DEV_GUILD_INTERNAL_ID: int = 1136100160510902272 + """Internal channel ID for the development guild.""" PLUGINS_LOC: Path = PLUGINS_DIR """Base Path to plugins directory.""" PLUGINS_DIRS: list[Path] = [f"{PLUGINS_DIR}"] @@ -104,6 +107,10 @@ class LogSettings(BaseSettings): """Sets the log level for the websockets library.""" ASYNCIO_LEVEL: int = 20 """Sets the log level for the asyncio library.""" + HTTP_CORE_LEVEL: int = 20 + """Sets the log level for the httpcore library. (Used in cert. validation)""" + HTTPX_LEVEL: int = 30 + """Sets the log level for the httpx library.""" FORMAT: str = "[[ %(asctime)s ]] - [[ %(name)s ]] - [[ %(levelname)s ]] - %(message)s" """Log format string.""" FILE: Path = BASE_DIR / "logs" / "byte.log" diff --git a/src/byte/lib/types/__init__.py b/src/byte/lib/types/__init__.py new file mode 100644 index 0000000..7e42cd8 --- /dev/null +++ b/src/byte/lib/types/__init__.py @@ -0,0 +1,5 @@ +"""Types and similar facilities used throughout library code.""" + +from byte.lib.types import astral, python + +__all__ = ("astral", "python") diff --git a/src/byte/lib/types/astral.py b/src/byte/lib/types/astral.py new file mode 100644 index 0000000..cf0ee23 --- /dev/null +++ b/src/byte/lib/types/astral.py @@ -0,0 +1,32 @@ +"""Types for Astral views and plugins.""" + +from __future__ import annotations + +from typing import TypedDict + +__all__ = ("BaseRuffRule", "FormattedRuffRule", "RuffRule") + + +class BaseRuffRule(TypedDict): + """Base Ruff rule data.""" + + name: str + summary: str + fix: str + explanation: str + + +class RuffRule(BaseRuffRule): + """Ruff rule data.""" + + code: str + linter: str + message_formats: list[str] + preview: bool + + +class FormattedRuffRule(BaseRuffRule): + """Formatted Ruff rule data.""" + + rule_link: str + rule_anchor_link: str diff --git a/src/byte/lib/types/python.py b/src/byte/lib/types/python.py new file mode 100644 index 0000000..f9c3123 --- /dev/null +++ b/src/byte/lib/types/python.py @@ -0,0 +1,77 @@ +"""Types for Python related views and plugins.""" + +from __future__ import annotations + +from enum import StrEnum +from typing import TYPE_CHECKING, TypedDict + +if TYPE_CHECKING: + from datetime import datetime + +__all__ = ("PEP", "PEPHistoryItem", "PEPStatus", "PEPType") + + +class PEPType(StrEnum): + """Type of PEP. + + Based off of `PEP Types in PEP1 `_. + """ + + I = "Informational" # noqa: E741 + P = "Process" + S = "Standards Track" + + +class PEPStatus(StrEnum): + """Status of a PEP. + + .. note:: ``Active`` and ``Accepted`` both traditionally use ``A``, + but are differentiated here for clarity. + + Based off of `PEP Status in PEP1 `_. + """ + + A = "Active" + AA = "Accepted" + D = "Deferred" + __ = "Draft" + F = "Final" + P = "Provisional" + R = "Rejected" + S = "Superseded" + W = "Withdrawn" + + +class PEPHistoryItem(TypedDict, total=False): + """PEP history item. + + Sometimes these include a list of ``datetime`` objects, + other times they are a list of datetime and str + because they contain a date and an rST link. + """ + + date: str + link: str + + +class PEP(TypedDict): + """PEP data. + + Based off of the `PEPS API `_. + """ + + number: int + title: str + authors: list[str] | str + discussions_to: str + status: PEPStatus + type: PEPType + topic: str + created: datetime + python_version: list[float] | float + post_history: list[str] + resolution: str | None + requires: str | None + replaces: str | None + superseded_by: str | None + url: str diff --git a/src/byte/lib/utils.py b/src/byte/lib/utils.py index 777664e..8e60f75 100644 --- a/src/byte/lib/utils.py +++ b/src/byte/lib/utils.py @@ -1,171 +1,42 @@ """Byte utilities.""" + from __future__ import annotations +import datetime as dt import json import re import subprocess from datetime import UTC, datetime -from enum import StrEnum from itertools import islice -from typing import TYPE_CHECKING, TypedDict, TypeVar +from typing import TYPE_CHECKING, TypeVar import httpx from anyio import run_process -from discord.ext import commands from ruff.__main__ import find_ruff_bin # type: ignore[import-untyped] -from byte.lib import settings from byte.lib.common.links import pastebin +from byte.lib.types.python import PEP, PEPStatus, PEPType if TYPE_CHECKING: from collections.abc import Iterable - from typing import Any - from discord.ext.commands import Context - from discord.ext.commands._types import Check + from byte.lib.types.astral import FormattedRuffRule, RuffRule __all__ = ( - "BaseRuffRule", - "RuffRule", - "FormattedRuffRule", - "PEP", - "is_byte_dev", - "linker", - "mention_user", - "mention_user_nickname", - "mention_channel", - "mention_role", - "mention_slash_command", - "mention_custom_emoji", - "mention_custom_emoji_animated", - "mention_timestamp", - "mention_guild_navigation", + "chunk_sequence", + "format_resolution_link", "format_ruff_rule", - "query_all_ruff_rules", - "run_ruff_format", + "get_next_friday", + "linker", "paste", - "chunk_sequence", "query_all_peps", + "query_all_ruff_rules", + "run_ruff_format", ) _T = TypeVar("_T") -class BaseRuffRule(TypedDict): - """Base Ruff rule data.""" - - name: str - summary: str - fix: str - explanation: str - - -class RuffRule(BaseRuffRule): - """Ruff rule data.""" - - code: str - linter: str - message_formats: list[str] - preview: bool - - -class FormattedRuffRule(BaseRuffRule): - """Formatted Ruff rule data.""" - - rule_link: str - rule_anchor_link: str - - -class PEPType(StrEnum): - """Type of PEP. - - Based off of `PEP Types in PEP1 `_. - """ - - I = "Informational" # noqa: E741 - P = "Process" - S = "Standards Track" - - -class PEPStatus(StrEnum): - """Status of a PEP. - - .. note:: ``Active`` and ``Accepted`` both traditionally use ``A``, - but are differentiated here for clarity. - - Based off of `PEP Status in PEP1 `_. - """ - - A = "Active" - AA = "Accepted" - D = "Deferred" - __ = "Draft" - F = "Final" - P = "Provisional" - R = "Rejected" - S = "Superseded" - W = "Withdrawn" - - -class PEPHistoryItem(TypedDict, total=False): - """PEP history item. - - Sometimes these include a list of ``datetime`` objects, - other times they are a list of datetime and str - because they contain a date and an rST link. - """ - - date: str - link: str - - -class PEP(TypedDict): - """PEP data. - - Based off of the `PEPS API `_. - """ - - number: int - title: str - authors: list[str] | str - discussions_to: str - status: PEPStatus - type: PEPType - topic: str - created: datetime - python_version: list[float] | float - post_history: list[str] - resolution: str | None - requires: str | None - replaces: str | None - superseded_by: str | None - url: str - - -def is_byte_dev() -> Check[Any]: - """Check if the user is a Byte developer. - - Returns: - A check function. - """ - - async def predicate(ctx: Context) -> bool: - """Check if the user is a Byte Dev or Owner. - - Args: - ctx: Context object. - - Returns: - True if the user is a Byte Dev or Owner, False otherwise. - """ - if await ctx.bot.is_owner(ctx.author) or ctx.author.id == settings.discord.DEV_USER_ID: - return True - - return any(role.name == "byte-dev" for role in ctx.author.roles) # type: ignore[reportAttributeAccessIssue] - - return commands.check(predicate) - - def linker(title: str, link: str, show_embed: bool = False) -> str: """Create a Markdown link, optionally with an embed. @@ -180,120 +51,6 @@ def linker(title: str, link: str, show_embed: bool = False) -> str: return f"[{title}]({link})" if show_embed else f"[{title}](<{link}>)" -def mention_user(user_id: int) -> str: - """Mention a user by ID. - - Args: - user_id: The unique identifier for the user. - - Returns: - A formatted string that mentions the user. - """ - return f"<@{user_id}>" - - -def mention_user_nickname(user_id: int) -> str: - """Mention a user by ID with a nickname. - - Args: - user_id: The unique identifier for the user. - - Returns: - A formatted string that mentions the user with a nickname. - """ - return f"<@!{user_id}>" - - -def mention_channel(channel_id: int) -> str: - """Mention a channel by ID. - - Args: - channel_id: The unique identifier for the channel. - - Returns: - A formatted string that mentions the channel. - """ - return f"<#{channel_id}>" - - -def mention_role(role_id: int) -> str: - """Mention a role by ID. - - Args: - role_id: The unique identifier for the role. - - Returns: - A formatted string that mentions the role. - """ - return f"<@&{role_id}>" - - -def mention_slash_command(name: str, command_id: int) -> str: - """Mention a slash command by name and ID. - - Args: - name: The name of the slash command. - command_id: The unique identifier for the slash command. - - Returns: - A formatted string that mentions the slash command. - """ - return f"" - - -def mention_custom_emoji(name: str, emoji_id: int) -> str: - """Mention a custom emoji by name and ID. - - Args: - name: The name of the emoji. - emoji_id: The unique identifier for the emoji. - - Returns: - A formatted string that mentions the custom emoji. - """ - return f"<:{name}:{emoji_id}>" - - -def mention_custom_emoji_animated(name: str, emoji_id: int) -> str: - """Mention an animated custom emoji by name and ID. - - Args: - name: The name of the animated emoji. - emoji_id: The unique identifier for the animated emoji. - - Returns: - A formatted string that mentions the animated custom emoji. - """ - return f"" - - -def mention_timestamp(timestamp: int, style: str = "") -> str: - """Mention a timestamp, optionally with a style. - - Args: - timestamp: The Unix timestamp to format. - style: An optional string representing the timestamp style. - (Default `` ``, valid styles: ``t``, ``T``, ``d``, ``D``, ``f``, ``F``, ``R``) - - Returns: - A formatted string that represents the timestamp. - """ - return f"" if style else f"" - - -def mention_guild_navigation(guild_nav_type: str, guild_element_id: int) -> str: - """Mention a guild navigation element by type and ID. - - Args: - guild_nav_type: The type of the guild navigation element. - guild_element_id: The unique identifier for the element. - - Returns: - A formatted string that mentions the guild navigation element. - """ - return f"<{guild_element_id}:{guild_nav_type}>" - - def format_ruff_rule(rule_data: RuffRule) -> FormattedRuffRule: """Format ruff rule data for embed-friendly output and append rule link. @@ -442,3 +199,25 @@ async def query_all_peps() -> list[PEP]: } for pep_info in data.values() ] + + +def get_next_friday(now: datetime, delay: int | None = None) -> tuple[datetime, datetime]: + """Calculate the next Friday from ``now``. + + If ``delay``, calculate the Friday for ``delay`` weeks from now. + + Args: + now: The current date and time. + delay: The number of weeks to delay the calculation. + + Returns: + datetime: The next Friday, optionally for the week after next. + """ + days_ahead = 4 - now.weekday() + if days_ahead < 0: + days_ahead += 7 + if delay: + days_ahead += 7 * delay + start_dt = (now + dt.timedelta(days=days_ahead)).replace(hour=11, minute=0, second=0, microsecond=0) + end_dt = start_dt + dt.timedelta(hours=1) + return start_dt, end_dt diff --git a/src/byte/plugins/__init__.py b/src/byte/plugins/__init__.py index c0b33f0..9d78109 100644 --- a/src/byte/plugins/__init__.py +++ b/src/byte/plugins/__init__.py @@ -1,6 +1,6 @@ """Pluggable modules for Byte.""" -from byte.plugins import admin, custom, events, forums, general, testing +from byte.plugins import admin, astral, custom, events, forums, general, github, python, testing __all__ = [ "custom", @@ -9,4 +9,7 @@ "forums", "general", "testing", + "astral", + "python", + "github", ] diff --git a/src/byte/plugins/admin.py b/src/byte/plugins/admin.py index ac80a6a..a84e3bc 100644 --- a/src/byte/plugins/admin.py +++ b/src/byte/plugins/admin.py @@ -2,15 +2,16 @@ .. todo:: add an unload cog command. """ + from discord import Interaction from discord.app_commands import command as app_command from discord.ext import commands from discord.ext.commands import Bot, Cog, Context, command, group, is_owner -from byte.lib.utils import is_byte_dev - __all__ = ("AdminCommands", "setup") +from byte.lib.checks import is_byte_dev + class AdminCommands(Cog): """Admin command cog.""" diff --git a/src/byte/plugins/astral.py b/src/byte/plugins/astral.py index 5fb9f1e..89894f6 100644 --- a/src/byte/plugins/astral.py +++ b/src/byte/plugins/astral.py @@ -1,4 +1,5 @@ """Plugins for Astral Inc. related software, including Ruff, uv, etc.""" + from __future__ import annotations from typing import TYPE_CHECKING @@ -14,7 +15,7 @@ from byte.views.astral import RuffView if TYPE_CHECKING: - from byte.lib.utils import RuffRule + from byte.lib.types.astral import RuffRule __all__ = ("Astral", "setup") @@ -32,10 +33,10 @@ def __init__(self, bot: Bot, rules: list[RuffRule]) -> None: async def _rule_autocomplete(self, _: Interaction, current_rule: str) -> list[Choice[str]]: # TODO: this can and should be made faster, rn this is slow, slow like the maintainer return [ - Choice(name=f'{code} - {rule["name"]}', value=code) - for code, rule in self._rules.items() - if current_rule.lower() in code.lower() - ][:25] + Choice(name=f'{code} - {rule["name"]}', value=code) + for code, rule in self._rules.items() + if current_rule.lower() in code.lower() + ][:25] @app_command(name="ruff") @autocomplete(rule=_rule_autocomplete) diff --git a/src/byte/plugins/config.py b/src/byte/plugins/config.py new file mode 100644 index 0000000..5dfc7e3 --- /dev/null +++ b/src/byte/plugins/config.py @@ -0,0 +1,69 @@ +"""Plugins for guild admins to configure Byte and its features.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from discord.app_commands import Choice, autocomplete +from discord.app_commands import command as app_command +from discord.ext.commands import Bot, Cog + +from byte.lib.common import config_options +from byte.views.config import ConfigView + +if TYPE_CHECKING: + from discord import Interaction + +__all__ = ("Config", "setup") + + +class Config(Cog): + """Config cog.""" + + def __init__(self, bot: Bot) -> None: + """Initialize cog.""" + self.bot = bot + self.__cog_name__ = "Config Commands" + self.config_options = config_options + + async def _config_autocomplete(self, interaction: Interaction, current: str) -> list[Choice[str]]: # noqa: ARG002 + """Autocomplete config for the config dropdown (up?) slash command.""" + return [ + Choice(name=f"{option['label']} - {option['description']}", value=option["label"]) + for option in config_options + if current.lower() in option["label"].lower() + ][:25] + + @app_command(name="config") + @autocomplete(setting=_config_autocomplete) + async def config_rule(self, interaction: Interaction, setting: str | None = None) -> None: + """Slash command to configure Byte. + + Args: + interaction: Interaction object. + setting: The setting to configure. + """ + if setting: + if selected_option := next( + (option for option in config_options if option["label"] == setting), + None, + ): + view = ConfigView(preselected=selected_option["label"]) + await interaction.response.send_message( + f"Configure {selected_option['label']}:", + view=view, + ephemeral=True, + ) + else: + await interaction.response.send_message( + f"Invalid setting: {setting}. Please select a valid setting.", + ephemeral=True, + ) + else: + view = ConfigView() + await interaction.response.send_message("Select a configuration option:", view=view, ephemeral=True) + + +async def setup(bot: Bot) -> None: + """Set up the config cog.""" + await bot.add_cog(Config(bot)) diff --git a/src/byte/plugins/custom/litestar.py b/src/byte/plugins/custom/litestar.py index b3be2ef..efe458c 100644 --- a/src/byte/plugins/custom/litestar.py +++ b/src/byte/plugins/custom/litestar.py @@ -1,88 +1,22 @@ """Custom plugins for the Litestar Discord.""" + from __future__ import annotations -from typing import Self +import datetime -from discord import Embed, Interaction, Message, TextStyle, app_commands +from dateutil.zoneinfo import gettz +from discord import Embed, EntityType, Interaction, Object, PrivacyLevel +from discord.app_commands import command as app_command from discord.ext.commands import Bot, Cog, Context, command, group, is_owner -from discord.ui import Modal, TextInput -from discord.utils import MISSING -from httpx import codes -from byte.lib.utils import is_byte_dev, mention_role, mention_user -from server.domain.github.helpers import github_client +from byte.lib.checks import is_byte_dev +from byte.lib.common.colors import litestar_yellow +from byte.lib.common.mention import mention_role, mention_user +from byte.lib.utils import get_next_friday __all__ = ("LitestarCommands", "setup") -class GitHubIssue(Modal, title="Create GitHub Issue"): - """Modal for GitHub issue creation.""" - - title_ = TextInput[Self](label="title", placeholder="Title") - description = TextInput[Self]( - label="Description", - style=TextStyle.paragraph, - placeholder="Please enter an description of the bug you are encountering.", - ) - mcve = TextInput[Self]( - label="MCVE", - style=TextStyle.paragraph, - placeholder="Please provide a minimal, complete, and verifiable example of the issue.", - ) - logs = TextInput[Self]( - label="Logs", style=TextStyle.paragraph, placeholder="Please copy and paste any relevant log output." - ) - version = TextInput[Self]( - label="Litestar Version", placeholder="What version of Litestar are you using when encountering this issue?" - ) - - def __init__( - self, - *, - title: str = MISSING, - timeout: float | None = None, - custom_id: str = MISSING, - message: Message | None = None, - ) -> None: - # NOTE: check how else to set default - super().__init__(title=title, timeout=timeout, custom_id=custom_id) - if message: - self.description.default = message.content - - async def on_submit(self, interaction: Interaction) -> None: - issue_reporter = interaction.user - issue_body_lines = [ - "### Reported by", - f"[{issue_reporter.display_name}](https://discord.com/users/{issue_reporter.id}) in Discord: {interaction.channel.name}", # noqa: E501 - "", - "### Description", - f"{self.description.value.strip()}", - "", - "### MCVE", - f"{self.mcve.value.strip()}", - "", - "### Logs", - f"{self.logs.value.strip()}", - "", - "### Litestar Version", - f"{self.version.value.strip()}", - ] - issue_body = "\n".join(issue_body_lines) - try: - response_wrapper = await github_client.rest.issues.async_create( - owner="litestar-org", repo="litestar", data={"title": self.title_.value, "body": issue_body} - ) - if codes.is_success(response_wrapper.status_code): - await interaction.response.send_message( - f"GitHub Issue created: {response_wrapper.parsed_data.html_url}", ephemeral=False - ) - else: - await interaction.response.send_message("Issue creation failed.", ephemeral=True) - - except Exception as e: # noqa: BLE001 - await interaction.response.send_message(f"An error occurred: {e!s}", ephemeral=True) - - class LitestarCommands(Cog): """Litestar command cog.""" @@ -90,12 +24,6 @@ def __init__(self, bot: Bot) -> None: """Initialize cog.""" self.bot = bot self.__cog_name__ = "Litestar Commands" # type: ignore[misc] - self.context_menu = app_commands.ContextMenu( - # TODO: Name changed to not conflict with the other one, discord shows both - name="Create GitHub Issue", - callback=self.create_github_issue_modal, - ) - bot.tree.add_command(self.context_menu) @group(name="litestar") @is_byte_dev() @@ -118,7 +46,7 @@ async def apply_role_embed(self, ctx: Context[Bot]) -> None: Args: ctx: Context object. """ - embed = Embed(title="Litestar Roles", color=0x42B1A8) + embed = Embed(title="Litestar Roles", color=litestar_yellow) embed.add_field(name="Organization Roles", value="\u200b", inline=False) embed.add_field( @@ -164,14 +92,49 @@ async def apply_role_embed(self, ctx: Context[Bot]) -> None: await ctx.send(embed=embed) - async def create_github_issue_modal(self, interaction: Interaction, message: Message) -> None: - """Context menu command to create a GitHub issue from a Discord message. + @app_command( + name="schedule-office-hours", + description="Schedule Office Hours event for the upcoming or the week after next Friday.", + ) + async def schedule_office_hours(self, interaction: Interaction, delay: int | None = None) -> None: + """Schedule Office Hours event for the upcoming or ``delay`` weeks after next Friday. Args: interaction: Interaction object. - message: Message object. + delay: Optional. Number of weeks to delay the event. """ - await interaction.response.send_modal(GitHubIssue(message=message)) + now_cst = datetime.datetime.now(gettz("America/Chicago")) + start_dt, end_dt = get_next_friday(now_cst, delay) + existing_events = interaction.guild.scheduled_events + + for event in existing_events: + if ( + event.name == "Office Hours" + and event.start_time.astimezone(gettz("America/Chicago")).date() == start_dt.date() + ): + await interaction.response.send_message( + "An Office Hours event is already scheduled for that day.", ephemeral=True + ) + return + + await interaction.guild.create_scheduled_event( + name="Office Hours", + start_time=start_dt, + end_time=end_dt, + description="Join us for our weekly office hours!", + entity_type=EntityType.stage_instance, + privacy_level=PrivacyLevel.guild_only, + reason=f"Scheduled by {interaction.user} via /schedule-office-hours", + channel=Object(id=1215926860144443502), + ) + + formatted_date = f"" + start_time_formatted = f"" + end_time_formatted = f"" + + await interaction.response.send_message( + f"Office Hours event scheduled: {formatted_date} from {start_time_formatted} - {end_time_formatted}." + ) async def setup(bot: Bot) -> None: diff --git a/src/byte/plugins/events.py b/src/byte/plugins/events.py index 2a5c366..2a96621 100644 --- a/src/byte/plugins/events.py +++ b/src/byte/plugins/events.py @@ -1,4 +1,5 @@ """Plugins for events.""" + from threading import Thread from typing import cast @@ -46,7 +47,8 @@ async def on_thread_create(self, thread: Thread) -> None: inline=False, ) embed.set_thumbnail(url=litestar_logo_yellow) - view = HelpThreadView(author=thread.owner, bot=self.bot) + view = HelpThreadView(author=thread.owner, guild_id=thread.guild.id, bot=self.bot) + await view.setup() await thread.send(embed=embed, view=view) elif thread.parent.name == "forum": reply = f"Thanks for posting, {thread.owner.mention}!" diff --git a/src/byte/plugins/general.py b/src/byte/plugins/general.py index c034d71..17b155e 100644 --- a/src/byte/plugins/general.py +++ b/src/byte/plugins/general.py @@ -1,4 +1,5 @@ """General plugins to be used wherever.""" + from __future__ import annotations from discord import Embed, Interaction @@ -36,7 +37,7 @@ async def show_paste(self, interaction: Interaction) -> None: embed.add_field( name="Syntax Highlighting", value="You can also use backticks to format your code. Read about it in the " - f"{linker('Discord Markdown Guide', markdown_guide)}.", + f"{linker('Discord Markdown Guide', markdown_guide)}.", ) embed.set_thumbnail(url=litestar_logo_yellow) diff --git a/src/byte/plugins/github.py b/src/byte/plugins/github.py new file mode 100644 index 0000000..4033e03 --- /dev/null +++ b/src/byte/plugins/github.py @@ -0,0 +1,116 @@ +"""Plugins for GitHub interactions.""" + +from __future__ import annotations + +from typing import Self + +from discord import Interaction, Message, TextStyle, app_commands +from discord.ext.commands import Bot, Cog +from discord.ui import Modal, TextInput +from discord.utils import MISSING +from httpx import codes + +from server.domain.github.helpers import github_client + +__all__ = ("GitHubCommands", "setup") + + +class GitHubIssue(Modal, title="Create GitHub Issue"): + """Modal for GitHub issue creation.""" + + title_ = TextInput[Self](label="title", placeholder="Title") + description = TextInput[Self]( + label="Description", + style=TextStyle.paragraph, + placeholder="Please enter an description of the bug you are encountering.", + ) + mcve = TextInput[Self]( + label="MCVE", + style=TextStyle.paragraph, + placeholder="Please provide a minimal, complete, and verifiable example of the issue.", + ) + logs = TextInput[Self]( + label="Logs", style=TextStyle.paragraph, placeholder="Please copy and paste any relevant log output." + ) + version = TextInput[Self]( + label="Project Version", placeholder="What version of the project are you using when encountering this issue?" + ) + + def __init__( + self, + *, + title: str = MISSING, + timeout: float | None = None, + custom_id: str = MISSING, + message: Message | None = None, + ) -> None: + # NOTE: check how else to set default + super().__init__(title=title, timeout=timeout, custom_id=custom_id) + if message: + self.description.default = message.content + + async def on_submit(self, interaction: Interaction) -> None: + issue_reporter = interaction.user + issue_body_lines = [ + "### Reported by", + f"[{issue_reporter.display_name}](https://discord.com/users/{issue_reporter.id}) in Discord: {interaction.channel.name}", # noqa: E501 + "", + "### Description", + f"{self.description.value.strip()}", + "", + "### MCVE", + f"{self.mcve.value.strip()}", + "", + "### Logs", + f"{self.logs.value.strip()}", + "", + "### Project Version", + f"{self.version.value.strip()}", + ] + issue_body = "\n".join(issue_body_lines) + try: + response_wrapper = await github_client.rest.issues.async_create( + owner="litestar-org", repo="litestar", data={"title": self.title_.value, "body": issue_body} + ) + if codes.is_success(response_wrapper.status_code): + await interaction.response.send_message( + f"GitHub Issue created: {response_wrapper.parsed_data.html_url}", ephemeral=False + ) + else: + await interaction.response.send_message("Issue creation failed.", ephemeral=True) + + except Exception as e: # noqa: BLE001 + await interaction.response.send_message(f"An error occurred: {e!s}", ephemeral=True) + + +class GitHubCommands(Cog): + """GitHub command cog.""" + + def __init__(self, bot: Bot) -> None: + """Initialize cog.""" + self.bot = bot + self.__cog_name__ = "GitHub Commands" # type: ignore[misc] + self.context_menu = app_commands.ContextMenu( + # TODO: Name changed to not conflict with the other one, discord shows both + name="Create GitHub Issue", + callback=self.create_github_issue_modal, + ) + bot.tree.add_command(self.context_menu) + + async def create_github_issue_modal(self, interaction: Interaction, message: Message) -> None: + """Context menu command to create a GitHub issue from a Discord message. + + Args: + interaction: Interaction object. + message: Message object. + """ + await interaction.response.send_modal(GitHubIssue(message=message)) + + +async def setup(bot: Bot) -> None: + """Add cog to bot. + + Args: + bot: Bot object. + """ + await bot.add_cog(GitHubCommands(bot)) diff --git a/src/byte/plugins/python.py b/src/byte/plugins/python.py index 6b5f54f..b9b97ea 100644 --- a/src/byte/plugins/python.py +++ b/src/byte/plugins/python.py @@ -1,4 +1,5 @@ """Plugins for Python related things, including PyPI, PEPs, etc.""" + from __future__ import annotations from discord import Embed, Interaction @@ -9,7 +10,7 @@ from byte.lib.common.assets import python_logo from byte.lib.common.colors import python_blue, python_yellow from byte.lib.utils import PEP, query_all_peps -from byte.views.embed import ExtendedEmbed, Field +from byte.views.abstract_views import ExtendedEmbed, Field from byte.views.python import PEPView __all__ = ("Python", "setup") diff --git a/src/byte/plugins/testing.py b/src/byte/plugins/testing.py index b258db8..81734e1 100644 --- a/src/byte/plugins/testing.py +++ b/src/byte/plugins/testing.py @@ -1,4 +1,5 @@ """Plugins for testing purposes.""" + from discord.ext.commands import Bot, Cog, Context, command, group diff --git a/src/byte/views/__init__.py b/src/byte/views/__init__.py index 43a0e86..63f15fd 100644 --- a/src/byte/views/__init__.py +++ b/src/byte/views/__init__.py @@ -1,7 +1,10 @@ """Views for the bot.""" -from byte.views import forums +from byte.views import abstract_views, astral, forums, python __all__ = [ + "abstract_views", + "astral", "forums", + "python", ] diff --git a/src/byte/views/abstract_views.py b/src/byte/views/abstract_views.py index a8ff771..f25375a 100644 --- a/src/byte/views/abstract_views.py +++ b/src/byte/views/abstract_views.py @@ -1,23 +1,30 @@ """Inheritable views that include extra functionality for base Views classes.""" + from __future__ import annotations -from typing import TYPE_CHECKING +from copy import deepcopy +from typing import TYPE_CHECKING, Any, Literal, ParamSpec, TypedDict -from discord import ButtonStyle, Embed, Interaction +from discord import ButtonStyle, Colour, Embed, Interaction from discord.ui import Button, View, button if TYPE_CHECKING: - from typing import Self + from datetime import datetime + from typing import NotRequired, Self from discord.ext.commands import Bot -__all__ = ("BaseEmbedView",) +__all__ = ("ExtendedEmbed", "Field", "ButtonEmbedView") + +P = ParamSpec("P") -class BaseEmbedView(View): +class ButtonEmbedView(View): """Base view including common buttons.""" - def __init__(self, author: int, bot: Bot, original_embed: Embed, minified_embed: Embed, *args, **kwargs) -> None: + def __init__( + self, author: int, bot: Bot, original_embed: Embed, minified_embed: Embed, *args: P.args, **kwargs: P.kwargs + ) -> None: """Initialize the view. Args: @@ -34,8 +41,11 @@ def __init__(self, author: int, bot: Bot, original_embed: Embed, minified_embed: self.original_embed = original_embed self.minified_embed = minified_embed - async def interaction_check(self, interaction: Interaction) -> bool: - """Check if the user is the author or a guild admin. + async def delete_interaction_check(self, interaction: Interaction) -> bool: + """Check if the user is the author or a guild admin + + .. note:: Only checks for the ``delete`` button, as we want to expose + the ``learn more`` button to anyone. Args: interaction: Interaction object. @@ -56,7 +66,7 @@ async def delete_button_callback(self, interaction: Interaction) -> None: Args: interaction: Interaction object. """ - if await self.interaction_check(interaction): + if await self.delete_interaction_check(interaction): await interaction.message.delete() async def learn_more_button_callback(self, interaction: Interaction) -> None: @@ -86,3 +96,90 @@ async def learn_more_button(self, interaction: Interaction, _: Button[Self]) -> _: Button object. """ await self.learn_more_button_callback(interaction) + + +class Field(TypedDict): + """Field type for ``ExtendedEmbed``. + + .. note:: types are matching the ones in ``Embed.add_fields``. + """ + + name: Any + value: Any + inline: NotRequired[bool] + + +class ExtendedEmbed(Embed): + """Extended Embed class for discord.py.""" + + def add_field_dict(self, field: Field) -> Self: + """Add a field to the embed. + + Args: + field (Field): The field to add to the embed. + + Returns: + Self: The embed with the field added. + """ + self.add_field(**field) + return self + + def add_field_dicts(self, fields: list[Field]) -> Self: + """Add multiple fields to the embed. + + Args: + fields (list[Field]): A list of fields to add to the embed. + + Returns: + Self: The embed with the fields added. + """ + for field in fields: + self.add_field_dict(field) + return self + + @classmethod + def from_field_dicts( + cls, + colour: int | Colour | None = None, + color: int | Colour | None = None, + title: Any | None = None, + type: Literal["rich", "image", "video", "gifv", "article", "link"] = "rich", # noqa: A002 + url: Any | None = None, + description: Any | None = None, + timestamp: datetime | None = None, + fields: list[Field] | None = None, + ) -> Self: + """Create an embed from a list of fields. + + Args: + colour (int | Colour | None): The colour of the embed. + color (int | Colour | None): The colour of the embed. + title (Any | None): The title of the embed. + type (Literal["rich", "image", "video", "gifv", "article", "link"]): The type of the embed. + url (Any | None): The URL of the embed. + description (Any | None): The description of the embed. + timestamp (datetime | None): The timestamp of the embed. + fields (list[Field] | None): A list of fields to add to the embed. + + Returns: + Self: The embed with the fields added. + """ + embed = cls( + colour=colour, + color=color, + title=title, + type=type, + url=url, + description=description, + timestamp=timestamp, + ) + embed.add_field_dicts(fields or []) + return embed + + def deepcopy(self) -> Self: + """Create a deep copy of the embed. + + Returns: + Self: A deep copy of the embed. + """ + return deepcopy(self) diff --git a/src/byte/views/astral.py b/src/byte/views/astral.py index bc4c85f..47dc3ca 100644 --- a/src/byte/views/astral.py +++ b/src/byte/views/astral.py @@ -1,13 +1,14 @@ """Discord UI views used in Astral commands.""" + from __future__ import annotations from byte.lib.log import get_logger -from byte.views.abstract_views import BaseEmbedView +from byte.views.abstract_views import ButtonEmbedView __all__ = ("RuffView",) logger = get_logger() -class RuffView(BaseEmbedView): +class RuffView(ButtonEmbedView): """View for the Ruff embed.""" diff --git a/src/byte/views/config.py b/src/byte/views/config.py new file mode 100644 index 0000000..492dd27 --- /dev/null +++ b/src/byte/views/config.py @@ -0,0 +1,263 @@ +"""Discord UI views used in Byte config commands.""" + +from __future__ import annotations + +from typing import Any + +from discord import ButtonStyle, Interaction, SelectOption, TextStyle +from discord.ui import Button, Modal, Select, TextInput, View + +from byte.lib.common import config_options +from byte.lib.log import get_logger + +__all__ = ("ConfigView",) + +logger = get_logger() + + +class FinishButton(Button): + """Finish button.""" + + def __init__(self) -> None: + """Initialize button.""" + super().__init__(style=ButtonStyle.success, label="Finished") + + async def callback(self, interaction: Interaction) -> None: + """Callback for button. + + Args: + interaction: Interaction object. + """ + await interaction.response.send_message("Configuration complete!", ephemeral=True) + self.view.stop() + + +class BackButton(Button): + """Back button.""" + + def __init__(self) -> None: + """Initialize button.""" + super().__init__(style=ButtonStyle.secondary, label="Back") + + async def callback(self, interaction: Interaction) -> None: + """Callback for button. + + Args: + interaction: Interaction object. + """ + view = ConfigView() + await interaction.response.edit_message(content="Select a configuration option:", view=view) + + +class CancelButton(Button): + """Cancel button.""" + + def __init__(self) -> None: + """Initialize button.""" + super().__init__(style=ButtonStyle.danger, label="Cancel") + + async def callback(self, interaction: Interaction) -> None: + """Callback for button. + + Args: + interaction: Interaction object. + """ + await interaction.response.send_message("Configuration cancelled.", ephemeral=True) + self.view.stop() + + +class ConfigSelect(Select): + """Configuration select dropdown menu.""" + + def __init__(self, preselected: str | None = None) -> None: + """Initialize select. + + Args: + preselected: Preselected option, if given. + """ + options = [SelectOption(label=option["label"], description=option["description"]) for option in config_options] + super().__init__(placeholder="Choose a setting...", min_values=1, max_values=1, options=options) + + if preselected: + for option in options: + if option.label == preselected: + option.default = True + break + + async def callback(self, interaction: Interaction) -> None: + """Callback for select. + + Args: + interaction: Interaction object. + """ + selected_option = next(option for option in config_options if option["label"] == self.values[0]) + if "sub_settings" in selected_option: + view = ConfigKeyView(selected_option) + await interaction.response.edit_message(content=f"Select a key for {selected_option['label']}:", view=view) + else: + modal = ConfigModal(title=f"Configure {selected_option['label']}") + await interaction.response.send_modal(modal) + + +class ConfigKeySelect(Select): + """Configuration key select dropdown menu.""" + + def __init__(self, option: dict[str, Any]) -> None: + """Initialize select. + + Args: + option: The selected configuration option. + """ + self.option = option + options = [ + SelectOption(label=sub_setting["label"], description=sub_setting.get("description", "")) + for sub_setting in option["sub_settings"] + ] + super().__init__(placeholder="Choose a key...", min_values=1, max_values=1, options=options) + + async def callback(self, interaction: Interaction) -> None: + """Callback for select. + + Args: + interaction: Interaction object. + """ + selected_sub_setting = self.values[0] + selected_sub_setting = next( + sub_setting for sub_setting in self.option["sub_settings"] if sub_setting["label"] == selected_sub_setting + ) + modal = ConfigModal( + title=f"{self.option['label']} - {selected_sub_setting['label']}", + sub_setting=selected_sub_setting, + option=self.option, + ) + await interaction.response.send_modal(modal) + + +class ConfigView(View): + """Configuration view.""" + + def __init__(self, preselected: str | None = None) -> None: + """Initialize view. + + Args: + preselected: Preselected option, if given. + """ + super().__init__(timeout=None) + self.add_item(ConfigSelect(preselected)) + self.add_item(FinishButton()) + self.add_item(CancelButton()) + + +class ConfigKeyView(View): + """Configuration key view.""" + + def __init__(self, option: dict[str, Any]) -> None: + """Initialize view. + + Args: + option: The selected configuration option. + """ + super().__init__(timeout=None) + self.add_item(ConfigKeySelect(option)) + self.add_item(BackButton()) + self.add_item(CancelButton()) + + +class ConfigModal(Modal): + """Configuration modal.""" + + def __init__( + self, + title: str, + sub_setting: dict[str, str] | None = None, + sub_settings: list[dict[str, str]] | None = None, + option: dict[str, Any] | None = None, + ) -> None: + """Initialize modal. + + Args: + title: Title of modal. + sub_setting: The selected sub-setting, if applicable. + sub_settings: List of sub-settings, if configuring all keys. + option: The selected configuration option, if applicable. + """ + super().__init__(title=title + "\n\n") + self.option = option + + if sub_settings: + for sub_setting in sub_settings: + self.add_item( + TextInput( + label=sub_setting["label"], + style=TextStyle.short, + custom_id=sub_setting["field"], + placeholder=f"Enter {sub_setting['label']} ({sub_setting['data_type']})", + required=True, + min_length=4 if sub_setting["data_type"] == "True/False" else 1, + max_length=5 + if sub_setting["data_type"] == "True/False" + else 100 + if sub_setting["data_type"] in ["String", "Integer"] + else 300 + if sub_setting["data_type"] == "Comma-separated list" + else None, + ) + ) + elif sub_setting: + self.add_item( + TextInput( + label=sub_setting["label"], + style=TextStyle.short, + placeholder=f"Enter {sub_setting['label']} ({sub_setting['data_type']})", + required=True, + min_length=4 if sub_setting["data_type"] == "True/False" else 1, + max_length=5 + if sub_setting["data_type"] == "True/False" + else 100 + if sub_setting["data_type"] in ["String", "Integer"] + else 300 + if sub_setting["data_type"] == "Comma-separated list" + else None, + ) + ) + else: + self.add_item( + TextInput( + label="Configuration Value", + style=TextStyle.short, + placeholder="Enter your configuration value...", + required=True, + ) + ) + + async def on_submit(self, interaction: Interaction) -> None: + """Handle modal submission. + + Args: + interaction: Interaction object. + """ + config_values = {item.custom_id: item.value for item in self.children if hasattr(item, "custom_id")} + await interaction.response.send_message(f"Configuration values received: {config_values}", ephemeral=True) + + if self.option: + view = ConfigKeyView(self.option) + await interaction.followup.send( + f"Select another key for {self.option['label']} or click 'Back' to return to the main menu.", + view=view, + ephemeral=True, + ) + else: + view = ConfigView() + await interaction.followup.send( + "Select another setting or click 'Finished' when done.", view=view, ephemeral=True + ) + + async def on_error(self, interaction: Interaction, error: Exception) -> None: + """Handle modal submission error. + + Args: + interaction: Interaction object. + error: Error object. + """ + await interaction.response.send_message("Oops! Something went wrong.", ephemeral=True) + logger.exception("Error occurred while processing config modal submission", exc_info=error) diff --git a/src/byte/views/embed.py b/src/byte/views/embed.py deleted file mode 100644 index 9ed4846..0000000 --- a/src/byte/views/embed.py +++ /dev/null @@ -1,95 +0,0 @@ -"""Custom embed view with convenience methods.""" -from copy import deepcopy -from datetime import datetime -from typing import Any, Literal, Self, TypedDict - -from discord import Colour, Embed - -__all__ = ("ExtendedEmbed", "Field") - - -class Field(TypedDict): - """Field type for ``ExtendedEmbed``. - - .. note:: types are matching the ones in ``Embed.add_fields``. - """ - - name: Any - value: Any - inline: bool - - -class ExtendedEmbed(Embed): - """Extended Embed class for discord.py.""" - - def add_field_dict(self, field: Field) -> Self: - """Add a field to the embed. - - Args: - field (Field): The field to add to the embed. - - Returns: - Self: The embed with the field added. - """ - self.add_field(**field) - return self - - def add_field_dicts(self, fields: list[Field]) -> Self: - """Add multiple fields to the embed. - - Args: - fields (list[Field]): A list of fields to add to the embed. - - Returns: - Self: The embed with the fields added. - """ - for field in fields: - self.add_field_dict(field) - return self - - @classmethod - def from_field_dicts( - cls, - colour: int | Colour | None = None, - color: int | Colour | None = None, - title: Any | None = None, - type: Literal["rich", "image", "video", "gifv", "article", "link"] = "rich", # noqa: A002 - url: Any | None = None, - description: Any | None = None, - timestamp: datetime | None = None, - fields: list[Field] | None = None, - ) -> Self: - """Create an embed from a list of fields. - - Args: - colour (int | Colour | None): The colour of the embed. - color (int | Colour | None): The colour of the embed. - title (Any | None): The title of the embed. - type (Literal["rich", "image", "video", "gifv", "article", "link"]): The type of the embed. - url (Any | None): The URL of the embed. - description (Any | None): The description of the embed. - timestamp (datetime | None): The timestamp of the embed. - fields (list[Field] | None): A list of fields to add to the embed. - - Returns: - Self: The embed with the fields added. - """ - embed = cls( - colour=colour, - color=color, - title=title, - type=type, - url=url, - description=description, - timestamp=timestamp, - ) - embed.add_field_dicts(fields or []) - return embed - - def deepcopy(self) -> Self: - """Create a deep copy of the embed. - - Returns: - Self: A deep copy of the embed. - """ - return deepcopy(self) diff --git a/src/byte/views/forums.py b/src/byte/views/forums.py index 935db0e..59010ca 100644 --- a/src/byte/views/forums.py +++ b/src/byte/views/forums.py @@ -4,27 +4,48 @@ from discord.ext.commands import Bot from discord.ui import Button, View, button -from byte.lib.common.links import litestar_issues from byte.lib.log import get_logger +from server.domain.guilds.dependencies import provides_guilds_service +from server.lib.db import config __all__ = ("HelpThreadView",) - logger = get_logger() class HelpThreadView(View): """View for the help thread.""" - def __init__(self, author: Interaction.user, bot: Bot, *args: list, **kwargs: dict) -> None: + def __init__(self, author: Interaction.user, guild_id: int, bot: Bot, *args: list, **kwargs: dict) -> None: """Initialize the view.""" super().__init__(*args, **kwargs) self.author = author self.bot = bot + self.guild_id = guild_id - self.add_item(Button(label="Open GitHub Issue", style=ButtonStyle.blurple, url=f"{litestar_issues}/new/choose")) + async def setup(self) -> None: + """Asynchronously setup guild details and add button. - async def interaction_check(self, interaction: Interaction) -> bool: + .. todo:: Think about this more - If we plan on decoupling this + should be a call to an endpoint like we do in ``byte.bot.Byte.on_guild_join``. + """ + # noinspection PyBroadException + try: + async with config.get_session() as session: + guilds_service = await anext(provides_guilds_service(db_session=session)) + guild_settings = await guilds_service.get(self.guild_id, id_attribute="guild_id") + + if guild_settings and guild_settings.github_config: + guild_repo = guild_settings.github_config.github_repository + self.add_item( + Button(label="Open GitHub Issue", style=ButtonStyle.blurple, url=f"{guild_repo}/new/choose") + ) + else: + logger.warning("no github configuration found for guild %s", self.guild_id) + except Exception: + logger.exception("failed to setup view for guild %s", self.guild_id) + + async def delete_interaction_check(self, interaction: Interaction) -> bool: """Check if the user is the author or an admin. Args: @@ -55,6 +76,7 @@ async def solve_button_callback(self, interaction: Interaction, button: Button) "invoking solve command for %s by %s on thread %s", ctx.channel, interaction.user, interaction.channel ) + # noinspection PyBroadException try: await solve_command.invoke(ctx) await interaction.followup.send("Marked as solved and closed the help forum!", ephemeral=True) diff --git a/src/byte/views/python.py b/src/byte/views/python.py index 9f44916..00989d5 100644 --- a/src/byte/views/python.py +++ b/src/byte/views/python.py @@ -1,13 +1,14 @@ """Discord UI views used in Python commands.""" + from __future__ import annotations from byte.lib.log import get_logger -from byte.views.abstract_views import BaseEmbedView +from byte.views.abstract_views import ButtonEmbedView __all__ = ("PEPView",) logger = get_logger() -class PEPView(BaseEmbedView): +class PEPView(ButtonEmbedView): """View for the Python PEP embed.""" diff --git a/src/cli.py b/src/cli.py index 3792729..78534dc 100644 --- a/src/cli.py +++ b/src/cli.py @@ -1,4 +1,5 @@ """Project CLI.""" + from __future__ import annotations import multiprocessing diff --git a/src/server/__init__.py b/src/server/__init__.py index e2374c1..f3a871a 100644 --- a/src/server/__init__.py +++ b/src/server/__init__.py @@ -1,4 +1,5 @@ """Byte Bot Server.""" + from __future__ import annotations from server import domain, lib diff --git a/src/server/domain/__init__.py b/src/server/domain/__init__.py index e770574..bea0a22 100644 --- a/src/server/domain/__init__.py +++ b/src/server/domain/__init__.py @@ -1,4 +1,5 @@ """Application Modules.""" + from __future__ import annotations from typing import TYPE_CHECKING @@ -26,7 +27,7 @@ routes: list[ControllerRouterHandler] = [ system.controllers.system.SystemController, web.controllers.web.WebController, - guilds.controllers.GuildController, + guilds.controllers.GuildsController, ] """Routes for the application.""" diff --git a/src/server/domain/db/__init__.py b/src/server/domain/db/__init__.py index ea8a50c..c8a8fea 100644 --- a/src/server/domain/db/__init__.py +++ b/src/server/domain/db/__init__.py @@ -1,4 +1,5 @@ """Domain for database models.""" + from server.domain.db import models __all__ = ["models"] diff --git a/src/server/domain/db/models.py b/src/server/domain/db/models.py index 85d49dc..1390d4a 100644 --- a/src/server/domain/db/models.py +++ b/src/server/domain/db/models.py @@ -1,4 +1,5 @@ """Shared models.""" + from __future__ import annotations from uuid import UUID # noqa: TCH003 @@ -8,7 +9,7 @@ from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy from sqlalchemy.orm import Mapped, mapped_column, relationship -__all__ = ("GitHubConfig", "Guild", "SOTagsConfig", "User") +__all__ = ("GitHubConfig", "Guild", "SOTagsConfig", "User", "AllowedUsersConfig", "ForumConfig") class Guild(UUIDAuditBase): @@ -30,6 +31,7 @@ class Guild(UUIDAuditBase): guild_name: Mapped[str] = mapped_column(String(100)) prefix: Mapped[str] = mapped_column(String(5), server_default="!", default="!") help_channel_id: Mapped[int | None] = mapped_column(BigInteger) + showcase_channel_id: Mapped[int | None] = mapped_column(BigInteger) sync_label: Mapped[str | None] issue_linking: Mapped[bool] = mapped_column(default=False) comment_linking: Mapped[bool] = mapped_column(default=False) @@ -53,6 +55,11 @@ class Guild(UUIDAuditBase): back_populates="guild", cascade="save-update, merge, delete", ) + forum_config: Mapped[ForumConfig | None] = relationship( + lazy="noload", + back_populates="guild", + cascade="save-update, merge, delete", + ) class GitHubConfig(UUIDAuditBase): @@ -66,7 +73,7 @@ class GitHubConfig(UUIDAuditBase): __tablename__ = "github_config" # type: ignore[reportAssignmentType] __table_args__ = {"comment": "GitHub configuration for a guild."} - guild_id: Mapped[UUID] = mapped_column(ForeignKey("guild.id", ondelete="cascade")) + guild_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("guild.guild_id", ondelete="cascade")) discussion_sync: Mapped[bool] = mapped_column(default=False) github_organization: Mapped[str | None] github_repository: Mapped[str | None] @@ -85,13 +92,13 @@ class GitHubConfig(UUIDAuditBase): class SOTagsConfig(UUIDAuditBase): """SQLAlchemy association model for a guild's Stack Overflow tags config.""" - __tablename__ = "so_tags" # type: ignore[reportAssignmentType] + __tablename__ = "so_tags_config" # type: ignore[reportAssignmentType] __table_args__ = ( UniqueConstraint("guild_id", "tag_name"), {"comment": "Configuration for a Discord guild's Stack Overflow tags."}, ) - guild_id: Mapped[UUID] = mapped_column(ForeignKey("guild.id", ondelete="cascade")) + guild_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("guild.guild_id", ondelete="cascade")) guild_name: AssociationProxy[str] = association_proxy("guild", "guild_name") tag_name: Mapped[str] = mapped_column(String(50)) @@ -114,6 +121,8 @@ class AllowedUsersConfig(UUIDAuditBase): This model allows us to configure which users are allowed to perform administrative actions on Byte specifically without giving them full administrative access to the Discord guild. + + .. todo:: More preferably, this should be more generalized to a user OR role ID. """ __tablename__ = "allowed_users" # type: ignore[reportAssignmentType] @@ -122,7 +131,7 @@ class AllowedUsersConfig(UUIDAuditBase): {"comment": "Configuration for allowed users in a Discord guild."}, ) - guild_id: Mapped[int] = mapped_column(ForeignKey("guild.guild_id", ondelete="cascade")) + guild_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("guild.guild_id", ondelete="cascade")) user_id: Mapped[UUID] = mapped_column(ForeignKey("user.id", ondelete="cascade")) guild_name: AssociationProxy[str] = association_proxy("guild", "guild_name") @@ -171,3 +180,62 @@ class User(UUIDAuditBase): lazy="noload", cascade="save-update, merge, delete", ) + + +class ForumConfig(UUIDAuditBase): + """Forum configuration. + + A guild will be able to set whether they want help and/or showcase forums. + * If they already have them set up, they can configure the channel IDs for them. + * If they don't have them set up, they can configure the category and channel names for them + Byte will then create the channels for them. + * If they don't want them, they can disable them. + + Help forum settings include: + * Respond with help embed, including a link to 'Open a GitHub Issue' + if the `GitHubConfig:github_organization` and `GitHubConfig:github_repository` are set. + Also includes `Solve` button to mark as solved and close the thread. + * Automatic thread closing after a certain period of inactivity. + * Uploading of threads into GitHub discussions. + * Pinging of defined roles when a thread has not received a response from someone with those roles + after a certain period of time. + + Showcase forum settings include: + * Respond with showcase embed, including a link to 'Add to awesome-$repo' + if the `GitHubConfig:github_organization` and `GitHubConfig:github_awesome` are set. + * Automatic thread closing after a certain period of inactivity. + * Uploading of threads into GitHub discussions. + """ + + __tablename__ = "forum_config" # type: ignore[reportAssignmentType] + __table_args__ = {"comment": "Forum configuration for a guild."} + + guild_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("guild.guild_id", ondelete="cascade")) + + """Help forum settings.""" + help_forum: Mapped[bool] = mapped_column(default=False) + help_forum_category: Mapped[str | None] + help_channel_id: Mapped[int | None] = association_proxy("guild", "help_channel_id") + help_thread_auto_close: Mapped[bool] = mapped_column(default=False) + help_thread_auto_close_days: Mapped[int | None] + help_thread_notify: Mapped[bool] = mapped_column(default=False) + help_thread_notify_roles: Mapped[str | None] + help_thread_notify_days: Mapped[int | None] + help_thread_sync: Mapped[bool] = association_proxy("guild", "github_config.discussion_sync") + + """Showcase forum settings.""" + showcase_forum: Mapped[bool] = mapped_column(default=False) + showcase_forum_category: Mapped[str | None] + showcase_channel_id: Mapped[int | None] = association_proxy("guild", "showcase_channel_Id") + showcase_thread_auto_close: Mapped[bool] = mapped_column(default=False) + showcase_thread_auto_close_days: Mapped[int | None] + + # ================= + # ORM Relationships + # ================= + guild: Mapped[Guild] = relationship( + back_populates="forum_config", + innerjoin=True, + lazy="noload", + cascade="save-update, merge, delete", + ) diff --git a/src/server/domain/github/helpers.py b/src/server/domain/github/helpers.py index edf4bcc..fbab915 100644 --- a/src/server/domain/github/helpers.py +++ b/src/server/domain/github/helpers.py @@ -1,4 +1,5 @@ """Helper functions for use within the GitHub domain.""" + from __future__ import annotations from githubkit import AppInstallationAuthStrategy, GitHub # type: ignore[reportMissingImports] diff --git a/src/server/domain/guilds/controllers.py b/src/server/domain/guilds/controllers.py index 0f4e030..3c0b87f 100644 --- a/src/server/domain/guilds/controllers.py +++ b/src/server/domain/guilds/controllers.py @@ -1,29 +1,55 @@ """Guild controller.""" + from __future__ import annotations from typing import TYPE_CHECKING -from litestar import Controller, get, post +from litestar import Controller, get, patch, post from litestar.di import Provide from litestar.params import Dependency, Parameter -from server.domain import urls -from server.domain.guilds.dependencies import provides_guilds_service -from server.domain.guilds.schemas import GuildSchema -from server.domain.guilds.services import GuildsService # noqa: TCH001 +from server.domain.guilds import urls +from server.domain.guilds.dependencies import ( + provides_allowed_users_config_service, + provides_forum_config_service, + provides_github_config_service, + provides_guilds_service, + provides_sotags_config_service, +) +from server.domain.guilds.schemas import ( + AllowedUsersConfigSchema, + ForumConfigSchema, + GitHubConfigSchema, + GuildSchema, + SOTagsConfigSchema, + UpdateableGuildSetting, +) +from server.domain.guilds.services import ( + AllowedUsersConfigService, # noqa: TCH001 + ForumConfigService, # noqa: TCH001 + GitHubConfigService, # noqa: TCH001 + GuildsService, # noqa: TCH001 + SOTagsConfigService, # noqa: TCH001 +) if TYPE_CHECKING: from advanced_alchemy import FilterTypes from litestar.pagination import OffsetPagination -__all__ = ("GuildController",) +__all__ = ("GuildsController",) -class GuildController(Controller): +class GuildsController(Controller): """Controller for guild-based routes.""" tags = ["Guilds"] - dependencies = {"guilds_service": Provide(provides_guilds_service)} + dependencies = { + "guilds_service": Provide(provides_guilds_service), + "github_service": Provide(provides_github_config_service), + "sotags_service": Provide(provides_sotags_config_service), + "allowed_users_service": Provide(provides_allowed_users_config_service), + "forum_service": Provide(provides_forum_config_service), + } @get( operation_id="Guilds", @@ -79,3 +105,172 @@ async def create_guild( new_guild = {"guild_id": guild_id, "guild_name": guild_name} await guilds_service.create(new_guild) return f"Guild {guild_name} created." + + @patch( + operation_id="UpdateGuild", + name="guilds:update", + summary="Update a guild.", + path=urls.GUILD_UPDATE, + ) + async def update_guild( + self, + guilds_service: GuildsService, + guild_id: int = Parameter( + title="Guild ID", + description="The guild ID.", + ), + setting: UpdateableGuildSetting.as_enum() = Parameter( + title="Setting", + description="The setting to update.", + ), + value: str | int = Parameter( + title="Value", + description="The new value for the setting.", + ), + ) -> GuildSchema | OffsetPagination[GuildSchema]: + """Update a guild by ID. + + Args: + guilds_service (GuildsService): Guilds service + guild_id (Guild.guild_id): Guild ID + setting (UpdateableGuildSetting): Setting to update + value (str | int): New value for the setting + + Returns: + Guild: Updated guild object + """ + _guild = guilds_service.get(guild_id, id_attribute="guild_id") + # todo: this is a placeholder, update to grab whichever setting is being update, and update the corresponding + # tables value + await guilds_service.update(_guild, setting, {"some-config-thing": value}) + return guilds_service.to_schema(GuildSchema, _guild) + + @get( + operation_id="GuildDetail", + name="guilds:detail", + summary="Get guild details.", + path=urls.GUILD_DETAIL, + ) + async def get_guild( + self, + guilds_service: GuildsService, + guild_id: int = Parameter( + title="Guild ID", + description="The guild ID.", + ), + ) -> GuildSchema: + """Get a guild by ID. + + Args: + guilds_service (GuildsService): Guilds service + guild_id (int): Guild ID + + Returns: + Guild: Guild object + """ + result = await guilds_service.get(guild_id, id_attribute="guild_id") + return guilds_service.to_schema(GuildSchema, result) + + @get( + operation_id="GitHubDetail", + name="guilds:github-config", + summary="Get GitHub config for a guild.", + path=urls.GUILD_GITHUB_DETAIL, + ) + async def get_guild_github_config( + self, + github_service: GitHubConfigService, + guild_id: int = Parameter( + title="Guild ID", + description="The guild ID.", + ), + ) -> GitHubConfigSchema | OffsetPagination[GitHubConfigSchema]: + """Get a guild's GitHub config by ID. + + Args: + github_service (GitHubConfigService): GitHub config service + guild_id (int): Guild ID + + Returns: + GitHubConfig: GitHub config object + """ + result = await github_service.get(guild_id, id_attribute="guild_id") + return github_service.to_schema(GitHubConfigSchema, result) + + @get( + operation_id="SOTagsDetail", + name="guilds:sotags-config", + summary="Get StackOverflow tags config for a guild.", + path=urls.GUILD_SOTAGS_DETAIL, + ) + async def get_guild_sotags_config( + self, + sotags_service: SOTagsConfigService, + guild_id: int = Parameter( + title="Guild ID", + description="The guild ID.", + ), + ) -> SOTagsConfigSchema | OffsetPagination[SOTagsConfigSchema]: + """Get a guild's StackOverflow tags config by ID. + + Args: + sotags_service (SOTagsConfigService): StackOverflow tags config service + guild_id (int): Guild ID + + Returns: + SOTagsConfig: StackOverflow tags config object + """ + result = await sotags_service.get(guild_id, id_attribute="guild_id") + return sotags_service.to_schema(SOTagsConfigSchema, result) + + @get( + operation_id="AllowedUsersDetail", + name="guilds:allowed-users-config", + summary="Get allowed users config for a guild.", + path=urls.GUILD_ALLOWED_USERS_DETAIL, + ) + async def get_guild_allowed_users_config( + self, + allowed_users_service: AllowedUsersConfigService, + guild_id: int = Parameter( + title="Guild ID", + description="The guild ID.", + ), + ) -> AllowedUsersConfigSchema | OffsetPagination[AllowedUsersConfigSchema]: + """Get a guild's allowed users config by ID. + + Args: + allowed_users_service (AllowedUsersConfigService): Allowed users config service + guild_id (int): Guild ID + + Returns: + AllowedUsersConfig: Allowed users config object + """ + result = await allowed_users_service.get(guild_id, id_attribute="guild_id") + return allowed_users_service.to_schema(AllowedUsersConfigSchema, result) + + @get( + operation_id="ForumDetail", + name="guilds:forum-config", + summary="Get forum config for a guild.", + path=urls.GUILD_FORUM_DETAIL, + ) + async def get_guild_forum_config( + self, + forum_service: ForumConfigService, + guild_id: int = Parameter( + title="Guild ID", + description="The guild ID.", + ), + ) -> ForumConfigSchema | OffsetPagination[ForumConfigSchema]: + """Get a guild's forum config by ID. + + Args: + forum_service (ForumConfigService): Forum config service + guild_id (int): Guild ID + + Returns: + ForumConfig: Forum config object + """ + result = await forum_service.get(guild_id, id_attribute="guild_id") + return forum_service.to_schema(ForumConfigSchema, result) diff --git a/src/server/domain/guilds/dependencies.py b/src/server/domain/guilds/dependencies.py index 77cc803..0425d8c 100644 --- a/src/server/domain/guilds/dependencies.py +++ b/src/server/domain/guilds/dependencies.py @@ -1,12 +1,20 @@ """Dependencies for guilds.""" + from __future__ import annotations from typing import TYPE_CHECKING from sqlalchemy import select +from sqlalchemy.orm import joinedload, noload, selectinload -from server.domain.db.models import Guild -from server.domain.guilds.services import GuildsService +from server.domain.db.models import AllowedUsersConfig, ForumConfig, GitHubConfig, Guild, SOTagsConfig +from server.domain.guilds.services import ( + AllowedUsersConfigService, + ForumConfigService, + GitHubConfigService, + GuildsService, + SOTagsConfigService, +) from server.lib import log if TYPE_CHECKING: @@ -14,13 +22,19 @@ from sqlalchemy.ext.asyncio import AsyncSession -__all__ = ("provides_guilds_service",) +__all__ = ( + "provides_allowed_users_config_service", + "provides_forum_config_service", + "provides_github_config_service", + "provides_guilds_service", + "provides_sotags_config_service", +) logger = log.get_logger() async def provides_guilds_service(db_session: AsyncSession) -> AsyncGenerator[GuildsService, None]: - """Construct GuildConfig-based repository and service objects for the request. + """Construct Guilds-based repository and service objects for the request. Args: db_session (AsyncSession): SQLAlchemy AsyncSession @@ -28,7 +42,119 @@ async def provides_guilds_service(db_session: AsyncSession) -> AsyncGenerator[Gu Yields: GuildsService: GuildConfig-based service """ - async with GuildsService.new(session=db_session, statement=select(Guild).order_by(Guild.guild_id)) as service: + async with GuildsService.new( + session=db_session, + statement=select(Guild) + .order_by(Guild.guild_name) + .options( + selectinload(Guild.github_config).options( + joinedload(GitHubConfig.guild, innerjoin=True).options(noload("*")), + ), + selectinload(Guild.sotags_configs).options( + joinedload(SOTagsConfig.guild, innerjoin=True).options(noload("*")), + ), + selectinload(Guild.allowed_users).options( + joinedload(AllowedUsersConfig.guild, innerjoin=True).options(noload("*")), + ), + selectinload(Guild.forum_config).options( + joinedload(ForumConfig.guild, innerjoin=True).options(noload("*")), + ), + ), + ) as service: + try: + yield service + finally: + ... + + +async def provides_github_config_service(db_session: AsyncSession) -> AsyncGenerator[GitHubConfigService, None]: + """Construct GitHubConfig-based repository and service objects for the request. + + Args: + db_session (AsyncSession): SQLAlchemy AsyncSession + + Yields: + GitHubConfigService: GitHubConfig-based service + """ + async with GitHubConfigService.new( + session=db_session, + statement=select(GitHubConfig) + .order_by(GitHubConfig.github_organization) + .options( + selectinload(GitHubConfig.guild).options(noload("*")), + ), + ) as service: + try: + yield service + finally: + ... + + +async def provides_sotags_config_service(db_session: AsyncSession) -> AsyncGenerator[SOTagsConfigService, None]: + """Construct SOTagsConfig-based repository and service objects for the request. + + Args: + db_session (AsyncSession): SQLAlchemy AsyncSession + + Yields: + SOTagsConfigService: SOTagsConfig-based service + """ + async with SOTagsConfigService.new( + session=db_session, + statement=select(SOTagsConfig) + .order_by(SOTagsConfig.tag_name) + .options( + selectinload(SOTagsConfig.guild).options(noload("*")), + ), + ) as service: + try: + yield service + finally: + ... + + +async def provides_allowed_users_config_service( + db_session: AsyncSession, +) -> AsyncGenerator[AllowedUsersConfigService, None]: + """Construct AllowedUsersConfig-based repository and service objects for the request. + + Args: + db_session (AsyncSession): SQLAlchemy AsyncSession + + Yields: + AllowedUsersConfigService: AllowedUsersConfig-based service + """ + async with AllowedUsersConfigService.new( + session=db_session, + statement=select(AllowedUsersConfig) + .order_by(AllowedUsersConfig.user_id) + .options( + selectinload(AllowedUsersConfig.guild).options(noload("*")), + ), + ) as service: + try: + yield service + finally: + ... + + +async def provides_forum_config_service(db_session: AsyncSession) -> AsyncGenerator[ForumConfigService, None]: + """Construct ForumConfig-based repository and service objects for the request. + + Args: + db_session (AsyncSession): SQLAlchemy AsyncSession + + Yields: + ForumConfigService: ForumConfig-based service + """ + async with ForumConfigService.new( + session=db_session, + statement=select(ForumConfig) + .order_by(ForumConfig.help_forum) + .options( + selectinload(ForumConfig.guild).options(noload("*")), + ), + ) as service: try: yield service finally: diff --git a/src/server/domain/guilds/helpers.py b/src/server/domain/guilds/helpers.py new file mode 100644 index 0000000..e1edd15 --- /dev/null +++ b/src/server/domain/guilds/helpers.py @@ -0,0 +1,20 @@ +"""Helper functions to be used for interacting with Guild data.""" + +from __future__ import annotations + +from server.domain.guilds.dependencies import provides_guilds_service +from server.lib.db import config + +__all__ = ("get_byte_server_count",) + + +async def get_byte_server_count() -> int: + """Get the server count for Byte. + + Returns: + int: The server counts for Byte or 0 if there are none. + """ + async with config.get_session() as session: + guilds_service = await anext(provides_guilds_service(db_session=session)) + total = await guilds_service.count() + return total or 0 diff --git a/src/server/domain/guilds/schemas.py b/src/server/domain/guilds/schemas.py index 5cf11a3..f76c37b 100644 --- a/src/server/domain/guilds/schemas.py +++ b/src/server/domain/guilds/schemas.py @@ -1,13 +1,68 @@ """API Schemas for guild domain.""" + from __future__ import annotations +from enum import StrEnum, Enum +from typing import Type from uuid import UUID # noqa: TCH003 from pydantic import Field from server.lib.schema import CamelizedBaseModel -__all__ = ("GuildCreate", "GuildSchema", "GuildUpdate") +__all__ = ( + "AllowedUsersConfigSchema", + "ForumConfigSchema", + "GitHubConfigSchema", + "GuildCreate", + "GuildSchema", + "GuildUpdate", + "SOTagsConfigSchema", + "UpdateableGuildSetting", +) + +from server.lib.serialization import convert_camel_to_snake_case + + +class GitHubConfigSchema(CamelizedBaseModel): + """Schema for validating GitHub configuration.""" + + guild_id: UUID + discussion_sync: bool + github_organization: str | None + github_repository: str | None + + +class SOTagsConfigSchema(CamelizedBaseModel): + """Schema for validating StackOverflow tags configuration.""" + + guild_id: UUID + tag_name: str + + +class AllowedUsersConfigSchema(CamelizedBaseModel): + """Schema for validating allowed users for certain admin actions within a guild.""" + + guild_id: UUID + user_id: UUID + + +class ForumConfigSchema(CamelizedBaseModel): + """Schema for validating forum configuration.""" + + guild_id: UUID + help_forum: bool = Field(title="Help Forum", description="Is the help forum enabled.") + help_forum_category: str + help_thread_auto_close: bool + help_thread_auto_close_days: int + help_thread_notify: bool + help_thread_notify_roles: list[int] + help_thread_notify_days: int + help_thread_sync: bool + showcase_forum: bool + showcase_forum_category: str + showcase_thread_auto_close: bool + showcase_thread_auto_close_days: int class GuildSchema(CamelizedBaseModel): @@ -24,6 +79,18 @@ class GuildSchema(CamelizedBaseModel): issue_linking: bool | None = Field(title="Issue Linking", description="Is issue linking enabled.") comment_linking: bool | None = Field(title="Comment Linking", description="Is comment linking enabled.") pep_linking: bool | None = Field(title="PEP Linking", description="Is PEP linking enabled.") + github_config: GitHubConfigSchema | None = Field( + title="GitHub Config", description="The GitHub configuration for the guild." + ) + sotags_configs: list[SOTagsConfigSchema] = Field( + title="StackOverflow Tags Configs", description="The StackOverflow tags configuration for the guild." + ) + allowed_users: list[AllowedUsersConfigSchema] = Field( + title="Allowed Users", description="The allowed users configuration for the guild." + ) + forum_config: ForumConfigSchema | None = Field( + title="Forum Config", description="The forum configuration for the guild." + ) class GuildCreate(CamelizedBaseModel): @@ -48,3 +115,75 @@ class GuildUpdate(CamelizedBaseModel): issue_linking: bool | None = Field(title="Issue Linking", description="Is issue linking enabled.") comment_linking: bool | None = Field(title="Comment Linking", description="Is comment linking enabled.") pep_linking: bool | None = Field(title="PEP Linking", description="Is PEP linking enabled.") + + +class UpdateableGuildSetting(CamelizedBaseModel): + """Allowed settings that admins can update for their guild.""" + + """Guild Model Settings""" + prefix: str = Field(title="Prefix", description="The prefix for the guild.") + help_channel_id: int = Field(title="Help Channel ID", description="The channel ID for the help forum.") + showcase_channel_id: int = Field(title="Showcase Channel ID", description="The channel ID for the showcase forum.") + sync_label: str = Field(title="Sync Label", description="The forum label to use for GitHub discussion syncs.") + issue_linking: bool = Field(title="Issue Linking", description="Is issue linking enabled.") + comment_linking: bool = Field(title="Comment Linking", description="Is comment linking enabled.") + pep_linking: bool = Field(title="PEP Linking", description="Is PEP linking enabled.") + + """GitHub Config Settings""" + discussion_sync: bool = Field(title="Discussion Sync", description="Is GitHub discussion sync enabled.") + github_organization: str = Field(title="GitHub Organization", description="The GitHub organization to sync.") + github_repository: str = Field(title="GitHub Repository", description="The GitHub repository to sync.") + + """StackOverflow Tags Config Settings""" + tag_name: list[str] = Field( + title="StackOverflow Tag(s)", + description="The StackOverflow tag(s) to sync.", + examples=["litestar", "byte", "python"], + ) + + """Allowed Users Config Settings""" + allowed_user_id: int = Field(title="User ID", description="The user or role ID to allow.") + + """Forum Config Settings""" + """Help Forum""" + help_forum: bool = Field(title="Help Forum", description="Is the help forum enabled.") + help_forum_category: str = Field(title="Help Forum Category", description="The help forum category.") + help_thread_auto_close: bool = Field( + title="Help Thread Auto Close", description="Is the help thread auto close enabled." + ) + help_thread_auto_close_days: int = Field( + title="Help Thread Auto Close Days", description="The days to auto close help threads after inactivity." + ) + help_thread_notify: bool = Field( + title="Help Thread Notify", description="Whether to notify roles for unresponded help threads." + ) + help_thread_notify_roles: list[int] = Field( + title="Help Thread Notify Roles", description="The roles to notify for unresponded help threads." + ) + help_thread_notify_days: int = Field( + title="Help Thread Notify Days", description="The days to notify `notify_roles` after not receiving a response." + ) + help_thread_sync: bool = Field( + title="Help Thread Sync", description="Is the help thread GitHub discussions sync enabled." + ) + + """Showcase forum""" + showcase_forum: bool = Field(title="Showcase Forum", description="Is the showcase forum enabled.") + showcase_forum_category: str = Field(title="Showcase Forum Category", description="The showcase forum category.") + showcase_thread_auto_close: bool = Field( + title="Showcase Thread Auto Close", description="Is the showcase thread auto close enabled." + ) + showcase_thread_auto_close_days: int = Field( + title="Showcase Thread Auto Close Days", description="The days to auto close showcase threads after inactivity." + ) + + @classmethod + def as_enum(cls) -> type[Enum]: + """Helper to dynamically create an enum from the class fields.""" + return StrEnum( + cls.__name__, + { + convert_camel_to_snake_case(field.alias): convert_camel_to_snake_case(field.alias) + for field in cls.__fields__.values() + }, + ) diff --git a/src/server/domain/guilds/services.py b/src/server/domain/guilds/services.py index 1f7ee0f..d148d16 100644 --- a/src/server/domain/guilds/services.py +++ b/src/server/domain/guilds/services.py @@ -1,15 +1,26 @@ """Guild services.""" + from __future__ import annotations from typing import Any -from server.domain.db.models import Guild +from server.domain.db.models import AllowedUsersConfig, ForumConfig, GitHubConfig, Guild, SOTagsConfig from server.lib import log -from server.lib.repository import SQLAlchemyAsyncSlugRepository +from server.lib.repository import SQLAlchemyAsyncRepository, SQLAlchemyAsyncSlugRepository from server.lib.service import SQLAlchemyAsyncRepositoryService -__all__ = ("GuildsRepository", "GuildsService") - +__all__ = ( + "AllowedUsersConfigRepository", + "AllowedUsersConfigService", + "ForumConfigRepository", + "ForumConfigService", + "GitHubConfigRepository", + "GitHubConfigService", + "GuildsRepository", + "GuildsService", + "SOTagsConfigRepository", + "SOTagsConfigService", +) logger = log.get_logger() @@ -24,7 +35,7 @@ class GuildsService(SQLAlchemyAsyncRepositoryService[Guild]): """Handles basic operations for a guild.""" repository_type = GuildsRepository - match_fields = ["name"] + match_fields = ["guild_id"] async def to_model(self, data: Guild | dict[str, Any], operation: str | None = None) -> Guild: """Convert data to a model. @@ -37,3 +48,105 @@ async def to_model(self, data: Guild | dict[str, Any], operation: str | None = N Project: Converted model """ return await super().to_model(data, operation) + + +class GitHubConfigRepository(SQLAlchemyAsyncRepository[GitHubConfig]): + """GitHubConfig SQLAlchemy Repository.""" + + model_type = GitHubConfig + + +class GitHubConfigService(SQLAlchemyAsyncRepositoryService[GitHubConfig]): + """Handles basic operations for the guilds' GitHub config.""" + + repository_type = GitHubConfigRepository + match_fields = ["guild_id"] + + async def to_model(self, data: GitHubConfig | dict[str, Any], operation: str | None = None) -> GitHubConfig: + """Convert data to a model. + + Args: + data (GitHubConfig | dict[str, Any]): Data to convert to a model + operation (str | None): Operation to perform on the data + + Returns: + Project: Converted model + """ + return await super().to_model(data, operation) + + +class SOTagsConfigRepository(SQLAlchemyAsyncRepository[SOTagsConfig]): + """SOTagsConfig SQLAlchemy Repository.""" + + model_type = SOTagsConfig + + +class SOTagsConfigService(SQLAlchemyAsyncRepositoryService[SOTagsConfig]): + """Handles basic operations for the guilds' StackOverflow tags config.""" + + repository_type = SOTagsConfigRepository + match_fields = ["guild_id"] + + async def to_model(self, data: SOTagsConfig | dict[str, Any], operation: str | None = None) -> SOTagsConfig: + """Convert data to a model. + + Args: + data (SOTagsConfig | dict[str, Any]): Data to convert to a model + operation (str | None): Operation to perform on the data + + Returns: + Project: Converted model + """ + return await super().to_model(data, operation) + + +class AllowedUsersConfigRepository(SQLAlchemyAsyncRepository[AllowedUsersConfig]): + """AllowedUsersConfig SQLAlchemy Repository.""" + + model_type = AllowedUsersConfig + + +class AllowedUsersConfigService(SQLAlchemyAsyncRepositoryService[AllowedUsersConfig]): + """Handles basic operations for the guilds' Allowed Users config.""" + + repository_type = AllowedUsersConfigRepository + match_fields = ["guild_id"] + + async def to_model( + self, data: AllowedUsersConfig | dict[str, Any], operation: str | None = None + ) -> AllowedUsersConfig: + """Convert data to a model. + + Args: + data (AllowedUsersConfig | dict[str, Any]): Data to convert to a model + operation (str | None): Operation to perform on the data + + Returns: + Project: Converted model + """ + return await super().to_model(data, operation) + + +class ForumConfigRepository(SQLAlchemyAsyncRepository[ForumConfig]): + """ForumConfig SQLAlchemy Repository.""" + + model_type = ForumConfig + + +class ForumConfigService(SQLAlchemyAsyncRepositoryService[ForumConfig]): + """Handles basic operations for the guilds' Forum config.""" + + repository_type = AllowedUsersConfigRepository + match_fields = ["guild_id"] + + async def to_model(self, data: ForumConfig | dict[str, Any], operation: str | None = None) -> ForumConfig: + """Convert data to a model. + + Args: + data (ForumConfig | dict[str, Any]): Data to convert to a model + operation (str | None): Operation to perform on the data + + Returns: + Project: Converted model + """ + return await super().to_model(data, operation) diff --git a/src/server/domain/guilds/urls.py b/src/server/domain/guilds/urls.py new file mode 100644 index 0000000..d17e0fa --- /dev/null +++ b/src/server/domain/guilds/urls.py @@ -0,0 +1,29 @@ +"""Guild URLs.""" + +from __future__ import annotations + +from typing import Final + +from server.domain.urls import OPENAPI_SCHEMA + +# --- API + +# -- General +GUILD_CREATE: Final = f"{OPENAPI_SCHEMA}/guilds/create" +"""Create guild URL.""" +GUILD_LIST: Final = f"{OPENAPI_SCHEMA}/guilds/list" +"""Guild list URL.""" + +# -- Specific +GUILD_UPDATE: Final = f"{OPENAPI_SCHEMA}/guilds/{{guild_id:int}}/update" +"""Update guild URL.""" +GUILD_DETAIL: Final = f"{OPENAPI_SCHEMA}/guilds/{{guild_id:int}}/info" +"""Guild detail URL.""" +GUILD_GITHUB_DETAIL: Final = f"{OPENAPI_SCHEMA}/guilds/{{guild_id:int}}/github/info" +"""Guild GitHub detail URL.""" +GUILD_SOTAGS_DETAIL: Final = f"{OPENAPI_SCHEMA}/guilds/{{guild_id:int}}/sotags/info" +"""Guild StackOverflow tags detail URL.""" +GUILD_ALLOWED_USERS_DETAIL: Final = f"{OPENAPI_SCHEMA}/guilds/{{guild_id:int}}/allowed_users/info" +"""Guild allowed users detail URL.""" +GUILD_FORUM_DETAIL: Final = f"{OPENAPI_SCHEMA}/guilds/{{guild_id:int}}/forum/info" +"""Guild forum detail URL.""" diff --git a/src/server/domain/system/__init__.py b/src/server/domain/system/__init__.py index 4f38094..bf4e867 100644 --- a/src/server/domain/system/__init__.py +++ b/src/server/domain/system/__init__.py @@ -1,8 +1,11 @@ """System domain.""" + from __future__ import annotations -from server.domain.system import controllers +from server.domain.system import controllers, dtos, helpers __all__ = [ "controllers", + "dtos", + "helpers", ] diff --git a/src/server/domain/system/controllers/__init__.py b/src/server/domain/system/controllers/__init__.py index dc0d429..43c936a 100644 --- a/src/server/domain/system/controllers/__init__.py +++ b/src/server/domain/system/controllers/__init__.py @@ -1,4 +1,5 @@ """System domain controllers.""" + from __future__ import annotations from server.domain.system.controllers import system diff --git a/src/server/domain/system/controllers/system.py b/src/server/domain/system/controllers/system.py index a0cf6c3..d4fd57d 100644 --- a/src/server/domain/system/controllers/system.py +++ b/src/server/domain/system/controllers/system.py @@ -1,15 +1,71 @@ """System Controller.""" + from __future__ import annotations -from litestar import Controller +from typing import TYPE_CHECKING + +from litestar import Controller, MediaType, get +from litestar.response import Response + +from server.domain import urls +from server.domain.system.dtos import SystemHealth +from server.domain.system.helpers import check_byte_status, check_database_status +from server.lib import log + +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio import AsyncSession __all__ = ["SystemController"] +logger = log.get_logger() + class SystemController(Controller): """System Controller.""" opt = {"exclude_from_auth": True} - # TODO: Implement: - # - /health endpoint + @get( + operation_id="SystemHealth", + name="system:health", + path=urls.SYSTEM_HEALTH, + media_type=MediaType.JSON, + cache=False, + tags=["System"], + summary="Health Check", + description="Execute a health check against backend components including database and bot status.", + signature_namespace={"SystemHealth": SystemHealth}, + ) + async def check_system_health(self, db_session: AsyncSession) -> Response[SystemHealth]: + """Check the overall system health. + + Args: + db_session (AsyncSession): Database session. + + Returns: + Response[SystemHealth]: System health. + """ + database_status = await check_database_status(db_session) + byte_status = await check_byte_status() + statuses = [database_status, byte_status] + + if all(status == "offline" for status in statuses): + overall_status = "offline" + elif "offline" in statuses or "degraded" in statuses: + overall_status = "degraded" + else: + overall_status = "healthy" + + status_code = 200 if overall_status == "healthy" else 503 if overall_status == "degraded" else 500 + # noinspection PyTypeChecker + system_health_detail = SystemHealth( + database_status=database_status, + byte_status=byte_status, + overall_status=overall_status, + ) + + return Response( + content=system_health_detail, + status_code=status_code, + media_type=MediaType.JSON, + ) diff --git a/src/server/domain/system/dtos.py b/src/server/domain/system/dtos.py new file mode 100644 index 0000000..77f2c20 --- /dev/null +++ b/src/server/domain/system/dtos.py @@ -0,0 +1,24 @@ +"""System domain DTOS.""" + +from dataclasses import dataclass +from typing import Annotated, Literal + +from litestar.dto import DataclassDTO + +from server.lib import dto, settings + +__all__ = ["SystemHealth", "SystemHealthDTO"] + + +@dataclass +class SystemHealth: + """System Health.""" + + database_status: Literal["online", "offline", "degraded"] + byte_status: Literal["online", "offline", "degraded"] + overall_status: Literal["healthy", "offline", "degraded"] + app: str = settings.project.NAME + version: str = settings.project.BUILD_NUMBER + + +SystemHealthDTO = DataclassDTO[Annotated[SystemHealth, dto.config()]] diff --git a/src/server/domain/system/helpers.py b/src/server/domain/system/helpers.py new file mode 100644 index 0000000..abc93b8 --- /dev/null +++ b/src/server/domain/system/helpers.py @@ -0,0 +1,68 @@ +"""System domain helper functions.""" + +from __future__ import annotations + +from time import time +from typing import TYPE_CHECKING + +from sqlalchemy import text + +__all__ = ("check_byte_status", "check_database_status") + + +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio import AsyncSession + + from server.lib.types import Status + +DEGRADED_THRESHOLD = 2.0 +"""float: Threshold in seconds for degraded status.""" + + +async def check_database_status(db_session: AsyncSession) -> Status: + """Check database health. + + Args: + db_session (AsyncSession): Database session. + + Returns: + Status: Database status. + """ + try: + start_time = time() + await db_session.execute(text("select 1")) + end_time = time() + response_time = end_time - start_time + if response_time > DEGRADED_THRESHOLD: + return "degraded" + except ConnectionRefusedError: + return "offline" + return "online" + + +async def check_byte_status() -> Status: + """Check Byte status. + + .. todo:: This is a stub. Need to figure out how to call the current bot instance from here. + + .. code-block:: python + :caption: Example usage of ``check_byte_status`` + + async def healthcheck(self) -> Status: + latency = round(self.bot.latency * 1000, 2) + ratelimited = self.bot.is_ws_ratelimited() + ready = self.bot.is_ready() + if closed := self.bot.is_closed(): + return "offline" + + latency_threshold = 1000 + return ( + "degraded" + if not ready or ratelimited or latency > latency_threshold + else "online" + ) + + Returns: + Status: Byte status. + """ + return "offline" diff --git a/src/server/domain/urls.py b/src/server/domain/urls.py index e6ce434..810f0ba 100644 --- a/src/server/domain/urls.py +++ b/src/server/domain/urls.py @@ -1,4 +1,5 @@ """Domain URLs.""" + from __future__ import annotations from typing import Final @@ -17,13 +18,3 @@ # --- Bot # --- Reports - -# --- API -GUILD_CREATE: Final = f"{OPENAPI_SCHEMA}/guilds/create" -"""Create guild URL.""" -GUILD_UPDATE: Final = f"{OPENAPI_SCHEMA}/guilds/update" -"""Update guild URL.""" -GUILD_DETAIL: Final = f"{OPENAPI_SCHEMA}/guilds/{{guild_id}}" -"""Guild detail URL.""" -GUILD_LIST: Final = f"{OPENAPI_SCHEMA}/guilds/list" -"""Guild list URL.""" diff --git a/src/server/domain/web/__init__.py b/src/server/domain/web/__init__.py index 7720fb5..772e8e6 100644 --- a/src/server/domain/web/__init__.py +++ b/src/server/domain/web/__init__.py @@ -1,4 +1,5 @@ """Web domain.""" + from __future__ import annotations from server.domain.web import controllers diff --git a/src/server/domain/web/controllers/__init__.py b/src/server/domain/web/controllers/__init__.py index 378db02..ead8ddf 100644 --- a/src/server/domain/web/controllers/__init__.py +++ b/src/server/domain/web/controllers/__init__.py @@ -1,4 +1,5 @@ """Web domain controllers.""" + from __future__ import annotations from server.domain.web.controllers import web diff --git a/src/server/domain/web/controllers/web.py b/src/server/domain/web/controllers/web.py index 5771fa7..80bd2b3 100644 --- a/src/server/domain/web/controllers/web.py +++ b/src/server/domain/web/controllers/web.py @@ -1,13 +1,21 @@ """Web Controller.""" + from __future__ import annotations +from typing import TYPE_CHECKING + from litestar import Controller, get from litestar.response import Template from litestar.status_codes import HTTP_200_OK from server.domain import urls +from server.domain.guilds.helpers import get_byte_server_count +from server.domain.system.helpers import check_byte_status, check_database_status + +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio import AsyncSession -__all__ = ["WebController"] +__all__ = ("WebController",) class WebController(Controller): @@ -23,9 +31,23 @@ class WebController(Controller): include_in_schema=False, opt={"exclude_from_auth": True}, ) - async def index(self) -> Template: + async def index(self, db_session: AsyncSession) -> Template: """Serve site root.""" - return Template(template_name="index.html") + server_count = await get_byte_server_count() + byte_status = await check_byte_status() + database_status = await check_database_status(db_session) + statuses = [database_status, byte_status] + + if all(status == "offline" for status in statuses): + overall_status = "offline" + elif "offline" in statuses or "degraded" in statuses: + overall_status = "degraded" + else: + overall_status = "healthy" + + return Template( + template_name="index.html", context={"server_count": server_count, "overall_status": overall_status} + ) # add dashboard @get( diff --git a/src/server/domain/web/resources/style.css b/src/server/domain/web/resources/style.css index 8704762..bd6d064 100644 --- a/src/server/domain/web/resources/style.css +++ b/src/server/domain/web/resources/style.css @@ -1,5 +1,5 @@ /* -! tailwindcss v3.3.6 | MIT License | https://tailwindcss.com +! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com */ /* @@ -32,9 +32,11 @@ 4. Use the user's configured `sans` font-family by default. 5. Use the user's configured `sans` font-feature-settings by default. 6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS */ -html { +html, +:host { line-height: 1.5; /* 1 */ -webkit-text-size-adjust: 100%; @@ -44,26 +46,15 @@ html { -o-tab-size: 4; tab-size: 4; /* 3 */ - font-family: - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - "Helvetica Neue", - Arial, - "Noto Sans", - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol", + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */ font-feature-settings: normal; /* 5 */ font-variation-settings: normal; /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ } /* @@ -3342,11 +3333,6 @@ html { padding-bottom: 6rem; } -.py-36 { - padding-top: 9rem; - padding-bottom: 9rem; -} - .py-4 { padding-top: 1rem; padding-bottom: 1rem; @@ -3382,6 +3368,10 @@ html { padding-left: 2.25rem; } +.pt-12 { + padding-top: 3rem; +} + .text-center { text-align: center; } diff --git a/src/server/domain/web/templates/index.html b/src/server/domain/web/templates/index.html index 226c0d1..b7615a5 100644 --- a/src/server/domain/web/templates/index.html +++ b/src/server/domain/web/templates/index.html @@ -97,21 +97,19 @@

-
+
- Byte Logo + Byte Logo

Byte: - {{ byte_status | default('Unknown') }} + {{ overall_status | default('Unknown') | upper }} - .

-

Currently in
- probably 1 server + {{ server_count }} server{{ 's' if server_count != 1 else ''}}

None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=UserWarning) + with op.get_context().autocommit_block(): + schema_upgrades() + data_upgrades() + + +def downgrade() -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=UserWarning) + with op.get_context().autocommit_block(): + data_downgrades() + schema_downgrades() + + +def schema_upgrades() -> None: + """schema upgrade migrations go here.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "forum_config", + sa.Column("id", sa.GUID(length=16), nullable=False), + sa.Column("guild_id", sa.GUID(length=16), nullable=False), + sa.Column("help_forum", sa.Boolean(), nullable=False), + sa.Column("help_forum_category", sa.String(), nullable=True), + sa.Column("help_thread_auto_close", sa.Boolean(), nullable=False), + sa.Column("help_thread_auto_close_days", sa.Integer(), nullable=True), + sa.Column("help_thread_notify", sa.Boolean(), nullable=False), + sa.Column("help_thread_notify_roles", sa.String(), nullable=True), + sa.Column("help_thread_notify_days", sa.Integer(), nullable=True), + sa.Column("showcase_forum", sa.Boolean(), nullable=False), + sa.Column("showcase_forum_category", sa.String(), nullable=True), + sa.Column("showcase_thread_auto_close", sa.Boolean(), nullable=False), + sa.Column("showcase_thread_auto_close_days", sa.Integer(), nullable=True), + sa.Column("sa_orm_sentinel", sa.Integer(), nullable=True), + sa.Column("created_at", sa.DateTimeUTC(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTimeUTC(timezone=True), nullable=False), + sa.ForeignKeyConstraint( + ["guild_id"], ["guild.id"], name=op.f("fk_forum_config_guild_id_guild"), ondelete="cascade" + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_forum_config")), + ) + with op.batch_alter_table("allowed_users", schema=None) as batch_op: + batch_op.create_table_comment("Configuration for allowed users in a Discord guild.", existing_comment=None) + + with op.batch_alter_table("guild", schema=None) as batch_op: + batch_op.add_column(sa.Column("showcase_channel_id", sa.BigInteger(), nullable=True)) + batch_op.create_table_comment("Configuration for a Discord guild.", existing_comment=None) + + with op.batch_alter_table("so_tags", schema=None) as batch_op: + batch_op.create_table_comment("Configuration for a Discord guild's Stack Overflow tags.", existing_comment=None) + + # ### end Alembic commands ### + + +def schema_downgrades() -> None: + """schema downgrade migrations go here.""" + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("so_tags", schema=None) as batch_op: + batch_op.drop_table_comment(existing_comment="Configuration for a Discord guild's Stack Overflow tags.") + + with op.batch_alter_table("guild", schema=None) as batch_op: + batch_op.drop_table_comment(existing_comment="Configuration for a Discord guild.") + batch_op.drop_column("showcase_channel_id") + + with op.batch_alter_table("allowed_users", schema=None) as batch_op: + batch_op.drop_table_comment(existing_comment="Configuration for allowed users in a Discord guild.") + + op.drop_table("forum_config") + # ### end Alembic commands ### + + +def data_upgrades() -> None: + """Add any optional data upgrade migrations here!""" + + +def data_downgrades() -> None: + """Add any optional data downgrade migrations here!""" diff --git a/src/server/lib/db/migrations/versions/004_snowflake fixes.py b/src/server/lib/db/migrations/versions/004_snowflake fixes.py new file mode 100644 index 0000000..6a672ad --- /dev/null +++ b/src/server/lib/db/migrations/versions/004_snowflake fixes.py @@ -0,0 +1,178 @@ +# type: ignore +""" + +Revision ID: f32ee278015d +Revises: 73a26ceab2c4 +Create Date: 2024-03-11 05:17:53.372688+00:00 + +""" + +from __future__ import annotations + +import warnings + +import sqlalchemy as sa +from advanced_alchemy.types import GUID, ORA_JSONB, DateTimeUTC +from alembic import op +from sqlalchemy import Text # noqa: F401 + +__all__ = ["downgrade", "upgrade", "schema_upgrades", "schema_downgrades", "data_upgrades", "data_downgrades"] + +sa.GUID = GUID +sa.DateTimeUTC = DateTimeUTC +sa.ORA_JSONB = ORA_JSONB + +# revision identifiers, used by Alembic. +revision = "f32ee278015d" +down_revision = "73a26ceab2c4" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=UserWarning) + with op.get_context().autocommit_block(): + schema_upgrades() + data_upgrades() + + +def downgrade() -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=UserWarning) + with op.get_context().autocommit_block(): + data_downgrades() + schema_downgrades() + + +def schema_upgrades() -> None: + """schema upgrade migrations go here.""" + from sqlalchemy import inspect + + inspector = inspect(op.get_bind()) + if "forum_config" in inspector.get_table_names(): + op.drop_table("forum_config") + if "github_config" in inspector.get_table_names(): + op.drop_table("github_config") + if "so_tags" in inspector.get_table_names(): + op.drop_table("so_tags") + + # Recreate sub-tables with original schema + op.create_table( + "forum_config", + sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False), + sa.Column("guild_id", sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(["guild_id"], ["guild.guild_id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + + op.create_table( + "github_config", + sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False), + sa.Column("guild_id", sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(["guild_id"], ["guild.guild_id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + + op.create_table( + "so_tags_config", + sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False), + sa.Column("guild_id", sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(["guild_id"], ["guild.guild_id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("forum_config", schema=None) as batch_op: + batch_op.add_column(sa.Column("help_forum", sa.Boolean(), nullable=False)) + batch_op.add_column(sa.Column("help_forum_category", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("help_thread_auto_close", sa.Boolean(), nullable=False)) + batch_op.add_column(sa.Column("help_thread_auto_close_days", sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column("help_thread_notify", sa.Boolean(), nullable=False)) + batch_op.add_column(sa.Column("help_thread_notify_roles", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("help_thread_notify_days", sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column("showcase_forum", sa.Boolean(), nullable=False)) + batch_op.add_column(sa.Column("showcase_forum_category", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("showcase_thread_auto_close", sa.Boolean(), nullable=False)) + batch_op.add_column(sa.Column("showcase_thread_auto_close_days", sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column("sa_orm_sentinel", sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column("created_at", sa.DateTimeUTC(timezone=True), nullable=False)) + batch_op.add_column(sa.Column("updated_at", sa.DateTimeUTC(timezone=True), nullable=False)) + batch_op.drop_constraint("fk_forum_config_guild_id_guild", type_="foreignkey") + batch_op.create_foreign_key( + batch_op.f("fk_forum_config_guild_id_guild"), "guild", ["guild_id"], ["guild_id"], ondelete="cascade" + ) + batch_op.create_table_comment("Forum configuration for a guild.", existing_comment=None) + + with op.batch_alter_table("github_config", schema=None) as batch_op: + batch_op.add_column(sa.Column("discussion_sync", sa.Boolean(), nullable=False)) + batch_op.add_column(sa.Column("github_organization", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("github_repository", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("sa_orm_sentinel", sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column("created_at", sa.DateTimeUTC(timezone=True), nullable=False)) + batch_op.add_column(sa.Column("updated_at", sa.DateTimeUTC(timezone=True), nullable=False)) + batch_op.drop_constraint("fk_github_config_guild_id_guild", type_="foreignkey") + batch_op.create_foreign_key( + batch_op.f("fk_github_config_guild_id_guild"), "guild", ["guild_id"], ["guild_id"], ondelete="cascade" + ) + batch_op.create_table_comment("GitHub configuration for a guild.", existing_comment=None) + + with op.batch_alter_table("so_tags_config", schema=None) as batch_op: + batch_op.add_column(sa.Column("tag_name", sa.String(length=50), nullable=False)) + batch_op.add_column(sa.Column("sa_orm_sentinel", sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column("created_at", sa.DateTimeUTC(timezone=True), nullable=False)) + batch_op.add_column(sa.Column("updated_at", sa.DateTimeUTC(timezone=True), nullable=False)) + batch_op.create_unique_constraint(batch_op.f("uq_so_tags_config_guild_id"), ["guild_id", "tag_name"]) + batch_op.drop_constraint("fk_so_tags_config_guild_id_guild", type_="foreignkey") + batch_op.create_foreign_key( + batch_op.f("fk_so_tags_config_guild_id_guild"), "guild", ["guild_id"], ["guild_id"], ondelete="cascade" + ) + batch_op.create_table_comment("Configuration for a Discord guild's Stack Overflow tags.", existing_comment=None) + + # ### end Alembic commands ### + + +def schema_downgrades() -> None: + """if we have to downgrade we're fucked.""" + from sqlalchemy import inspect + + inspector = inspect(op.get_bind()) + if "forum_config" in inspector.get_table_names(): + op.drop_table("forum_config") + if "github_config" in inspector.get_table_names(): + op.drop_table("github_config") + if "so_tags_config" in inspector.get_table_names(): + op.drop_table("so_tags_config") + + # Recreate sub-tables with original schema + op.create_table( + "forum_config", + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("guild_id", sa.UUID(), nullable=False), + sa.ForeignKeyConstraint(["guild_id"], ["guild.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + + op.create_table( + "github_config", + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("guild_id", sa.UUID(), nullable=False), + sa.ForeignKeyConstraint(["guild_id"], ["guild.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + + op.create_table( + "so_tags", + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("guild_id", sa.UUID(), nullable=False), + sa.ForeignKeyConstraint(["guild_id"], ["guild.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + + +def data_upgrades() -> None: + """Add any optional data upgrade migrations here!""" + + +def data_downgrades() -> None: + """Add any optional data downgrade migrations here!""" diff --git a/src/server/lib/db/migrations/versions/initial.py b/src/server/lib/db/migrations/versions/initial.py index b54c66b..7ffad26 100644 --- a/src/server/lib/db/migrations/versions/initial.py +++ b/src/server/lib/db/migrations/versions/initial.py @@ -4,6 +4,7 @@ Create Date: 2023-11-26 20:11:48.777676+00:00 """ + from __future__ import annotations import warnings diff --git a/src/server/lib/db/orm.py b/src/server/lib/db/orm.py index ab21a6d..1615d81 100644 --- a/src/server/lib/db/orm.py +++ b/src/server/lib/db/orm.py @@ -1,4 +1,5 @@ """Application ORM configuration.""" + from __future__ import annotations from typing import Any diff --git a/src/server/lib/dependencies.py b/src/server/lib/dependencies.py index 4659445..236f3d3 100644 --- a/src/server/lib/dependencies.py +++ b/src/server/lib/dependencies.py @@ -1,4 +1,5 @@ -"""Dependency providers.""" +"""Application dependency providers.""" + from __future__ import annotations from datetime import datetime @@ -36,18 +37,46 @@ ] DTorNone = datetime | None +"""Aggregate type alias of the types supported for datetime filtering.""" StringOrNone = str | None +"""Aggregate type alias of the types supported for string filtering.""" UuidOrNone = UUID | None +"""Aggregate type alias of the types supported for UUID filtering.""" BooleanOrNone = bool | None +"""Aggregate type alias of the types supported for boolean filtering.""" SortOrderOrNone = Literal["asc", "desc"] | None """Aggregate type alias of the types supported for collection filtering.""" + FILTERS_DEPENDENCY_KEY = "filters" +"""Dependency key for filters.""" CREATED_FILTER_DEPENDENCY_KEY = "created_filter" +"""Dependency key for created filter.""" ID_FILTER_DEPENDENCY_KEY = "id_filter" +"""Dependency key for id filter.""" LIMIT_OFFSET_DEPENDENCY_KEY = "limit_offset" +"""Dependency key for limit/offset pagination.""" UPDATED_FILTER_DEPENDENCY_KEY = "updated_filter" +"""Dependency key for updated filter.""" ORDER_BY_DEPENDENCY_KEY = "order_by" +"""Dependency key for order by.""" SEARCH_FILTER_DEPENDENCY_KEY = "search_filter" +"""Dependency key for search filter.""" +ACTIVE_FILTER_DEPENDENCY_KEY = "active_filter" +"""Dependency key for active filter.""" + + +def provide_active_filter( + is_active: bool = Parameter(title="Is active filter", query="Active", default=True, required=False), +) -> bool: + """Return type consumed by ``Repository.filter_on_field()``. + + Arguments: + is_active(bool): Filter for active records. + + Returns: + bool + """ + return is_active def provide_id_filter( @@ -55,11 +84,11 @@ def provide_id_filter( ) -> CollectionFilter[UUID]: """Return type consumed by ``Repository.filter_in_collection()``. - Args: - ids (list[UUID] | None): Parsed out of a comma-separated list of values in query params. + Arguments: + ids(list[UUID] | None): List of IDs to filter on. Returns: - CollectionFilter[UUID]: Filter for a scoping query to a limited set of identities. + CollectionFilter[UUID] """ return CollectionFilter(field_name="id", values=ids or []) @@ -70,12 +99,12 @@ def provide_created_filter( ) -> BeforeAfter: """Return type consumed by ``Repository.filter_on_datetime_field()``. - Args: - before (DTorNone): Filter for records created before this date/time. - after (DTorNone): Filter for records created after this date/time. + Arguments: + before(datetime | None): Filter for records created before this date/time. + after(datetime | None): Filter for records created after this date/time. Returns: - BeforeAfter: Filter for a scoping query to instance creation date/time. + BeforeAfter """ return BeforeAfter("created_at", before, after) @@ -84,6 +113,7 @@ def provide_search_filter( field: StringOrNone = Parameter(title="Field to search", query="searchField", default=None, required=False), search: StringOrNone = Parameter(title="Field to search", query="searchString", default=None, required=False), ignore_case: BooleanOrNone = Parameter( + bool, title="Search should be case sensitive", query="searchIgnoreCase", default=None, @@ -94,13 +124,10 @@ def provide_search_filter( Return type consumed by ``Repository.apply_search_filter()``. - Args: - field (StringOrNone): Field name to search. - search (StringOrNone): Value to search for. - ignore_case (BooleanOrNone): Whether to ignore case when searching. - - Returns: - SearchFilter: Filter for searching fields. + Arguments: + field (str | None): Field to search. + search (str | None): String to search for. + ignore_case (bool | None): Whether to ignore case when searching. """ return SearchFilter(field_name=field, value=search, ignore_case=ignore_case or False) # type: ignore[arg-type] @@ -113,12 +140,12 @@ def provide_order_by( Return type consumed by ``Repository.apply_order_by()``. - Args: - field_name (StringOrNone): Field name to order by. - sort_order (SortOrderOrNone): Order field ascending ('asc') or descending ('desc) + Arguments: + field_name(int): LIMIT to apply to select. + sort_order(int): OFFSET to apply to select. Returns: - OrderBy: Order by for query. + OrderBy """ return OrderBy(field_name=field_name, sort_order=sort_order) # type: ignore[arg-type] @@ -129,14 +156,14 @@ def provide_updated_filter( ) -> BeforeAfter: """Add updated filter. - Return type consumed by ``Repository.filter_on_datetime_field()``. + Return type consumed by `Repository.filter_on_datetime_field()`. - Args: - before (DTorNone): Filter for records updated before this date/time. - after (DTorNone): Filter for records updated after this date/time. + Arguments: + before(datetime | None): Filter for records updated before this date/time. + after(datetime | None): Filter for records updated after this date/time. Returns: - BeforeAfter: Filter for scoping query to instance update date/time. + BeforeAfter """ return BeforeAfter("updated_at", before, after) @@ -154,12 +181,12 @@ def provide_limit_offset_pagination( Return type consumed by ``Repository.apply_limit_offset_pagination()``. - Args: - current_page (int): Page number to return. - page_size (int): Number of records per page. + Arguments: + current_page(int): Current page of results. + page_size(int): Number of results per page. Returns: - LimitOffset: Filter for query pagination. + LimitOffset """ return LimitOffset(page_size, page_size * (current_page - 1)) @@ -184,16 +211,16 @@ def get_collection_handler(filters: Filters) -> ...: ... The dependency is provided in the application layer, so only need to inject the dependency where necessary. - Args: - created_filter (BeforeAfter): Filter for a scoping query to instance creation date/time. - updated_filter (BeforeAfter): Filter for a scoping query to instance update date/time. - id_filter (CollectionFilter): Filter for a scoping query to a limited set of identities. - limit_offset (LimitOffset): Filter for query pagination. - search_filter (SearchFilter): Filter for searching fields. - order_by (OrderBy): Order by for query. + Arguments: + created_filter(BeforeAfter): Filter for records created before/after a certain date/time. + updated_filter(BeforeAfter): Filter for records updated before/after a certain date/time. + id_filter(CollectionFilter): Filter for records with a certain ID. + limit_offset(LimitOffset): Pagination filter. + search_filter(SearchFilter): Search filter. + order_by(OrderBy): Order by filter. Returns: - list[FilterTypes]: List of filters parsed from connection. + list[repository.FilterTypes]: List of filters to apply to query. """ filters: list[FilterTypes] = [] if id_filter.values: # noqa: PD011 @@ -210,12 +237,13 @@ def get_collection_handler(filters: Filters) -> ...: ... def create_collection_dependencies() -> dict[str, Provide]: """Create ORM dependencies. - Creates a dictionary of provides for pagination endpoints. + Creates a dictionary of ``provides`` for pagination endpoints. Returns: - dict[str, Provide]: Dictionary of provides for pagination endpoints. + dict[str, Provide]: Dictionary of ``provides``. """ return { + ACTIVE_FILTER_DEPENDENCY_KEY: Provide(provide_active_filter, sync_to_thread=False), LIMIT_OFFSET_DEPENDENCY_KEY: Provide(provide_limit_offset_pagination, sync_to_thread=False), UPDATED_FILTER_DEPENDENCY_KEY: Provide(provide_updated_filter, sync_to_thread=False), CREATED_FILTER_DEPENDENCY_KEY: Provide(provide_created_filter, sync_to_thread=False), diff --git a/src/server/lib/dto.py b/src/server/lib/dto.py new file mode 100644 index 0000000..13ce0c9 --- /dev/null +++ b/src/server/lib/dto.py @@ -0,0 +1,74 @@ +"""DTO Library layer module.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Literal, overload + +from advanced_alchemy.extensions.litestar.dto import SQLAlchemyDTO, SQLAlchemyDTOConfig +from litestar.dto import DataclassDTO, dto_field +from litestar.dto.config import DTOConfig + +if TYPE_CHECKING: + from collections.abc import Set as AbstractSet + + from litestar.dto import RenameStrategy + +__all__ = ["config", "dto_field", "DTOConfig", "SQLAlchemyDTO", "DataclassDTO"] + + +@overload +def config( + backend: Literal["sqlalchemy"] = "sqlalchemy", + exclude: AbstractSet[str] | None = None, + rename_fields: dict[str, str] | None = None, + rename_strategy: RenameStrategy | None = None, + max_nested_depth: int | None = None, + partial: bool | None = None, +) -> SQLAlchemyDTOConfig: ... + + +@overload +def config( + backend: Literal["dataclass"] = "dataclass", + exclude: AbstractSet[str] | None = None, + rename_fields: dict[str, str] | None = None, + rename_strategy: RenameStrategy | None = None, + max_nested_depth: int | None = None, + partial: bool | None = None, +) -> DTOConfig: ... + + +# noinspection PyUnusedLocal +def config( + backend: Literal["dataclass", "sqlalchemy"] = "dataclass", # noqa: ARG001 + exclude: AbstractSet[str] | None = None, + rename_fields: dict[str, str] | None = None, + rename_strategy: RenameStrategy | None = None, + max_nested_depth: int | None = None, + partial: bool | None = None, +) -> DTOConfig | SQLAlchemyDTOConfig: + """Construct a DTO config. + + Args: + backend (Literal["dataclass", "sqlalchemy"], optional): Backend to use. Defaults to "dataclass". + exclude (AbstractSet[str] | None, optional): Fields to exclude. Defaults to None. + rename_fields (dict[str, str] | None, optional): Fields to rename. Defaults to None. + rename_strategy (RenameStrategy | None, optional): Rename strategy to use. Defaults to None. + max_nested_depth (int | None, optional): Max nested depth. Defaults to None. + partial (bool | None, optional): Whether to make the DTO partial. Defaults to None. + + Returns: + DTOConfig: Configured DTO class + """ + default_kwargs = {"rename_strategy": "camel", "max_nested_depth": 2} + if exclude: + default_kwargs["exclude"] = exclude + if rename_fields: + default_kwargs["rename_fields"] = rename_fields + if rename_strategy: + default_kwargs["rename_strategy"] = rename_strategy + if max_nested_depth: + default_kwargs["max_nested_depth"] = max_nested_depth + if partial: + default_kwargs["partial"] = partial + return DTOConfig(**default_kwargs) # type: ignore[arg-type] diff --git a/src/server/lib/exceptions.py b/src/server/lib/exceptions.py index 8afb921..fa61ea2 100644 --- a/src/server/lib/exceptions.py +++ b/src/server/lib/exceptions.py @@ -3,6 +3,7 @@ Also, defines functions that translate service and repository exceptions into HTTP exceptions. """ + from __future__ import annotations import sys diff --git a/src/server/lib/log/controller.py b/src/server/lib/log/controller.py index 6e64f50..59a7d12 100644 --- a/src/server/lib/log/controller.py +++ b/src/server/lib/log/controller.py @@ -4,6 +4,7 @@ Adds a filter for health check route logs. """ + from __future__ import annotations import logging diff --git a/src/server/lib/log/utils.py b/src/server/lib/log/utils.py index 7e9a4dc..e6f5b1f 100644 --- a/src/server/lib/log/utils.py +++ b/src/server/lib/log/utils.py @@ -7,6 +7,7 @@ :class:`EventFilter` - A structlog processor that removes keys from the log event if they exist. """ + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/src/server/lib/openapi.py b/src/server/lib/openapi.py index 7f97d5d..1c65362 100644 --- a/src/server/lib/openapi.py +++ b/src/server/lib/openapi.py @@ -1,4 +1,5 @@ """OpenAPI Config.""" + from __future__ import annotations from litestar.openapi.config import OpenAPIConfig diff --git a/src/server/lib/repository.py b/src/server/lib/repository.py index 308595e..d822436 100644 --- a/src/server/lib/repository.py +++ b/src/server/lib/repository.py @@ -1,4 +1,5 @@ """Repository module.""" + from __future__ import annotations import random diff --git a/src/server/lib/schema.py b/src/server/lib/schema.py index 6855eb8..233608a 100644 --- a/src/server/lib/schema.py +++ b/src/server/lib/schema.py @@ -1,4 +1,5 @@ """Schema.""" + from __future__ import annotations from pydantic import BaseModel as _BaseModel diff --git a/src/server/lib/serialization.py b/src/server/lib/serialization.py index fb3163c..820f553 100644 --- a/src/server/lib/serialization.py +++ b/src/server/lib/serialization.py @@ -1,4 +1,5 @@ """Serialization Helpers.""" + from __future__ import annotations import datetime diff --git a/src/server/lib/service.py b/src/server/lib/service.py index bf9f5c6..882b0c9 100644 --- a/src/server/lib/service.py +++ b/src/server/lib/service.py @@ -3,6 +3,7 @@ RepositoryService object is generic on the domain model type, which should be an SQLAlchemy model. """ + from __future__ import annotations import contextlib @@ -41,8 +42,7 @@ class SQLAlchemyAsyncRepositoryService(_SQLAlchemyAsyncRepositoryService[ModelT] """ @overload - def to_dto(self, data: ModelT) -> ModelT: - ... + def to_dto(self, data: ModelT) -> ModelT: ... @overload def to_dto( @@ -50,8 +50,7 @@ def to_dto( data: Sequence[ModelT], total: int | None = None, *filters: FilterTypes | ColumnElement[bool], - ) -> OffsetPagination[ModelT]: - ... + ) -> OffsetPagination[ModelT]: ... def to_dto( self, @@ -82,8 +81,7 @@ def to_dto( ) @overload - def to_schema(self, dto: type[ModelDTOT], data: ModelT) -> ModelDTOT: - ... + def to_schema(self, dto: type[ModelDTOT], data: ModelT) -> ModelDTOT: ... @overload def to_schema( @@ -92,8 +90,7 @@ def to_schema( data: Sequence[ModelT], total: int | None = None, *filters: FilterTypes, - ) -> OffsetPagination[ModelDTOT]: - ... + ) -> OffsetPagination[ModelDTOT]: ... def to_schema( self, diff --git a/src/server/lib/settings.py b/src/server/lib/settings.py index 8515a32..d8828bf 100644 --- a/src/server/lib/settings.py +++ b/src/server/lib/settings.py @@ -1,4 +1,5 @@ """Project Settings.""" + from __future__ import annotations import base64 @@ -71,7 +72,7 @@ class ProjectSettings(BaseSettings): model_config = SettingsConfigDict(case_sensitive=True, env_file=".env", extra="ignore") - BUILD_NUMBER: str = "" + BUILD_NUMBER: str = version """Identifier for CI build.""" CHECK_DB_READY: bool = True """Check for database readiness on startup.""" diff --git a/src/server/lib/static_files.py b/src/server/lib/static_files.py index 524bb4b..c0dd33f 100644 --- a/src/server/lib/static_files.py +++ b/src/server/lib/static_files.py @@ -1,4 +1,5 @@ """Static files configuration.""" + from __future__ import annotations from pathlib import Path diff --git a/src/server/lib/template.py b/src/server/lib/template.py index f623233..2287430 100644 --- a/src/server/lib/template.py +++ b/src/server/lib/template.py @@ -2,6 +2,7 @@ See TemplateSettings for configuration. """ + from __future__ import annotations from litestar.template.config import TemplateConfig diff --git a/src/server/lib/types.py b/src/server/lib/types.py index 26f94ea..6fdfd07 100644 --- a/src/server/lib/types.py +++ b/src/server/lib/types.py @@ -1,7 +1,8 @@ """Library module for type definitions to be used in the application.""" + from __future__ import annotations -from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar +from typing import TYPE_CHECKING, Any, Literal, TypeAlias, TypeVar from advanced_alchemy import FilterTypes from advanced_alchemy.extensions.litestar import SQLAlchemyDTO @@ -37,3 +38,7 @@ """Type alias for model or dict DTOs.""" ModelDictListDTOT: TypeAlias = list[ModelT | dict[str, Any]] | list[dict[str, Any]] | DTOData """Type alias for model or dict DTOs.""" + +# -- App Types +Status: TypeAlias = Literal["online", "offline", "degraded"] +"""Type alias for health check status.""" diff --git a/src/utils.py b/src/utils.py index 3159fa0..cab0a9a 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,4 +1,5 @@ """General utility functions.""" + from __future__ import annotations import base64 diff --git a/tools/build_docs.py b/tools/build_docs.py index f15122d..c274b51 100644 --- a/tools/build_docs.py +++ b/tools/build_docs.py @@ -1,4 +1,5 @@ """Builds the documentation and copies it to the output directory.""" + from __future__ import annotations import argparse