diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5dec5269..c18bbbe8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: check-toml - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.8.6 + rev: v0.9.1 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fccc2ad..cd09926d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,22 @@ Collect fragments into this file with: scriv collect --version X.Y.Z + + +## 0.14.0 (2025-01-13) + +### Other changes + +- Times Square now uses Alembic to manage database schema versioning and migrations. + +- Begin SQLAlchemy 2 adoption with the new `DeclarativeBase`, `mapped_column`, and the `Mapped` type. + +- Update the `make update` command in the Makefile to use the `--universal` flag with `uv pip compile`. + +- Fix type checking issues for Pydantic fields to use empty lists as the default, rather than using a default factory. + +- Explicitly set `asyncio_default_fixture_loop_scope` to `function`. An explicit setting is now required by pytest-asyncio. + ## 0.13.0 (2024-09-12) diff --git a/Dockerfile b/Dockerfile index 3eb8f8d5..2c5d27e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ # - Runs a non-root user. # - Sets up the entrypoint and port. -FROM python:3.12.6-slim-bookworm as base-image +FROM python:3.12.6-slim-bookworm AS base-image # Update system packages COPY scripts/install-base-packages.sh . @@ -57,9 +57,20 @@ COPY --from=install-image /opt/venv /opt/venv COPY scripts/start-api.sh /start-api.sh +# Copy the Alembic configuration and migrations, and set that path as the +# working directory so that Alembic can be run with a simple entry command +# and no extra configuration. +COPY --from=install-image /workdir/alembic.ini /app/alembic.ini +COPY --from=install-image /workdir/alembic /app/alembic +WORKDIR /app + # Make sure we use the virtualenv ENV PATH="/opt/venv/bin:$PATH" +# Set environment variable for Alembic config; other variables are set +# via Kubernetes. +ENV TS_ALEMBIC_CONFIG_PATH="/app/alembic.ini" + # Switch to the non-root user. USER appuser diff --git a/README.md b/README.md index 633937a4..0cadaf6a 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,20 @@ Excellent applications for Times Square include: - Quick-look data previewing - Reports that incorporate live data sources +## Deployments of Times Square + +| Link | User guide | Purpose | +| --------------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------- | +| [USDF RSP (dev)](https://usdf-rsp-dev.slac.stanford.edu/times-square) | [Documentation](https://rsp.lsst.io/v/usdfdev/guides/times-square/index.html) | For Rubin Observatory staff | +| [IDF (dev)](https://data-dev.lsst.cloud/times-square) | [Documentation](https://rsp.lsst.io/v/idfdev/guides/times-square/index.html) | For SQuaRE developers | + +## Design and development + The design and architecture of Times Square is described in [SQR-062: The Times Square service for publishing parameterized Jupyter Notebooks in the Rubin Science platform](https://sqr-062.lsst.io). Times Square uses [Noteburst](https://noteburst.lsst.io) ([GitHub](https://github.com/lsst-sqre/noteburst), [SQR-065](https://sqr-065.lsst.io)) to execute Jupyter Notebooks in Nublado (JupyterLab) instances, thereby mechanizing the RSP's notebook aspect. This Times Square API service is developed at https://github.com/lsst-sqre/times-square. -It's user interface is part of [Squareone](https://github.com/lsst-sqre/squareone). +Its user interface is part of [Squareone](https://github.com/lsst-sqre/squareone). Times Square is deployed with [Phalanx](https://phalanx.lsst.io/applications/times-square/index.html). + +REST API and developer documentation is at [times-square.lsst.io](https://times-square.lsst.io) and deployment documentation is [available at phalanx.lsst.io](https://phalanx.lsst.io/applications/times-square/index.html). diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 00000000..86357dda --- /dev/null +++ b/alembic.ini @@ -0,0 +1,15 @@ +[alembic] +script_location = %(here)s/alembic +file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s +prepend_sys_path = . +timezone = UTC +version_path_separator = os + +[post_write_hooks] +hooks = ruff ruff_format +ruff.type = exec +ruff.executable = ruff +ruff.options = check --fix REVISION_SCRIPT_FILENAME +ruff_format.type = exec +ruff_format.executable = ruff +ruff_format.options = format REVISION_SCRIPT_FILENAME diff --git a/alembic/README b/alembic/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/alembic/env.py b/alembic/env.py new file mode 100644 index 00000000..6fbd927f --- /dev/null +++ b/alembic/env.py @@ -0,0 +1,22 @@ +"""Alembic migration environment.""" + +from alembic import context +from safir.database import run_migrations_offline, run_migrations_online +from safir.logging import configure_alembic_logging, configure_logging + +from timessquare.config import config +from timessquare.dbschema import Base + +# Configure structlog. +configure_logging(name="timessquare", log_level=config.log_level) +configure_alembic_logging() + +# Run the migrations. +if context.is_offline_mode(): + run_migrations_offline(Base.metadata, config.database_url) +else: + run_migrations_online( + Base.metadata, + config.database_url, + config.database_password, + ) diff --git a/alembic/script.py.mako b/alembic/script.py.mako new file mode 100644 index 00000000..6fcfd30e --- /dev/null +++ b/alembic/script.py.mako @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} +""" + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: str | None = ${repr(down_revision)} +branch_labels: str | Sequence[str] | None = ${repr(branch_labels)} +depends_on: str | Sequence[str] | None = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/alembic/versions/20250110_2121_487195933fee_initial_schema.py b/alembic/versions/20250110_2121_487195933fee_initial_schema.py new file mode 100644 index 00000000..beb28e6c --- /dev/null +++ b/alembic/versions/20250110_2121_487195933fee_initial_schema.py @@ -0,0 +1,69 @@ +"""Initial schema. + +Revision ID: 487195933fee +Revises: +Create Date: 2025-01-10 21:21:26.706763+00:00 +""" + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "487195933fee" +down_revision: str | None = None +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + op.create_table( + "pages", + sa.Column("name", sa.Unicode(length=32), nullable=False), + sa.Column("ipynb", sa.UnicodeText(), nullable=False), + sa.Column("parameters", sa.JSON(), nullable=False), + sa.Column("title", sa.UnicodeText(), nullable=False), + sa.Column("date_added", sa.DateTime(), nullable=False), + sa.Column("authors", sa.JSON(), nullable=False), + sa.Column("tags", sa.JSON(), nullable=False), + sa.Column("uploader_username", sa.Unicode(length=64), nullable=True), + sa.Column("date_deleted", sa.DateTime(), nullable=True), + sa.Column("description", sa.UnicodeText(), nullable=True), + sa.Column("cache_ttl", sa.Integer(), nullable=True), + sa.Column("github_owner", sa.Unicode(length=255), nullable=True), + sa.Column("github_repo", sa.Unicode(length=255), nullable=True), + sa.Column("github_commit", sa.Unicode(length=40), nullable=True), + sa.Column( + "repository_path_prefix", sa.Unicode(length=2048), nullable=True + ), + sa.Column( + "repository_display_path_prefix", + sa.Unicode(length=2048), + nullable=True, + ), + sa.Column( + "repository_path_stem", sa.Unicode(length=255), nullable=True + ), + sa.Column( + "repository_source_extension", + sa.Unicode(length=255), + nullable=True, + ), + sa.Column( + "repository_sidecar_extension", + sa.Unicode(length=255), + nullable=True, + ), + sa.Column( + "repository_source_sha", sa.Unicode(length=40), nullable=True + ), + sa.Column( + "repository_sidecar_sha", sa.Unicode(length=40), nullable=True + ), + sa.PrimaryKeyConstraint("name"), + ) + + +def downgrade() -> None: + op.drop_table("pages") diff --git a/changelog.d/20250106_164329_jsick_DM_48307.md b/changelog.d/20250106_164329_jsick_DM_48307.md deleted file mode 100644 index e92f2871..00000000 --- a/changelog.d/20250106_164329_jsick_DM_48307.md +++ /dev/null @@ -1,20 +0,0 @@ - - -### Backwards-incompatible changes - -- - -### New features - -- - -### Bug fixes - -- - -### Other changes - -- Update the `make update` command in the Makefile to use the `--universal` flag with `uv pip compile`. -- Begin SQLAlchemy 2 migration by adopting the new `DeclarativeBase`, `mapped_column`, and the `Mapped` type. -- Fix type checking issues for Pydantic fields to use empty lists as the default, rather than using a default factory. -- Explicitly set `asyncio_default_fixture_loop_scope` to function. An explicit setting is now required by pytest-asyncio. diff --git a/docs/_rst_epilog.rst b/docs/_rst_epilog.rst index c8414744..0130bffe 100644 --- a/docs/_rst_epilog.rst +++ b/docs/_rst_epilog.rst @@ -4,4 +4,5 @@ .. _scriv: https://scriv.readthedocs.io/en/latest/ .. _semver: https://semver.org .. _tox: https://tox.readthedocs.io/en/latest/ +.. _Alembic: https://alembic.sqlalchemy.org/en/latest/ diff --git a/docs/dev/development.rst b/docs/dev/development.rst index 9cf2ef7c..f86e2944 100644 --- a/docs/dev/development.rst +++ b/docs/dev/development.rst @@ -24,12 +24,14 @@ Setting up a local development environment Times Square is a Python project that should be developed within a virtual environment. If you already have a Python virtual environment set up in your shell, you can use the :command:`make init` command to install Times Square and its development dependencies into it. +This complete procedure shows how to use uv to create a virtual environment and install Times Square into it: .. code-block:: sh git clone https://github.com/lsst-sqre/times-square.git cd times-square - pip install tox + uv venv + source .venv/bin/activate make init .. _pre-commit-hooks: @@ -39,12 +41,7 @@ Pre-commit The pre-commit hooks, which are automatically installed by running the :command:`make init` command on :ref:`set up `, ensure that files are valid and properly formatted. Some pre-commit hooks automatically reformat code: - -``ruff`` - Sorts Python imports and automatically fixes some common Python issues. - -``black`` - Automatically formats Python code. +The ``ruff`` hook automatically fixes some common Python issues and sorts Python imports. When these hooks fail, your Git commit will be aborted. To proceed, stage the new modifications and proceed with your Git commit. @@ -68,6 +65,14 @@ To see a listing of specific tox sessions, run: Times Square requires Docker to run its tests. +Database migrations +=================== + +Times Square uses Alembic_ for database migrations. +If your work involves changing the database schema (in :file:`/src/timessquare/dbschema`) you will need to prepare an Alembic migration in the same PR. +This process is outlined in the `Safir documentation `__. +Note that in Times Square the :file:`docker-compose.yaml` is hosted in the root of the repository rather than in the :file:`alembic` directory. + Building documentation ====================== @@ -98,7 +103,7 @@ When preparing a pull request, run .. code-block:: sh - scrive create + scriv create This will create a change log fragment in :file:`changelog.d`. Edit that fragment, removing the sections that do not apply and adding entries for your pull request. diff --git a/pyproject.toml b/pyproject.toml index e8e86ddd..c0d27eeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,6 +110,13 @@ extend-exclude = [ ] [tool.ruff.lint.per-file-ignores] +"alembic/env.py" = [ + "INP001", # Alembic config isn't a package +] +"alembic/versions/**" = [ + "INP001", # Alembic config isn't a package + "D103", # Alembic migrations don't need docstrings +] "src/timessquare/config.py" = [ "FBT001", # Pydantic validators take boolean arguments ] @@ -123,8 +130,10 @@ extend-exclude = [ "S106", # tests are allowed to hard-code dummy passwords "SLF001", # tests are allowed to access private members "T201", # tests are allowed to use print + "ASYNC221", # async tests are allowed to use sync functions ] [tool.ruff.lint.isort] known-first-party = ["timessquare", "tests"] +known-third-party = ["alembic"] split-on-trailing-comma = false diff --git a/requirements/dev.txt b/requirements/dev.txt index b26564c4..98d360c1 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -392,7 +392,7 @@ gitpython==3.1.44 \ --hash=sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110 \ --hash=sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269 # via documenteer -greenlet==3.1.1 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64') \ +greenlet==3.1.1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') \ --hash=sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e \ --hash=sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7 \ --hash=sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01 \ @@ -841,9 +841,9 @@ pycparser==2.22 ; implementation_name == 'pypy' \ # via # -c requirements/main.txt # cffi -pydantic==2.10.4 \ - --hash=sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d \ - --hash=sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06 +pydantic==2.10.5 \ + --hash=sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff \ + --hash=sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53 # via # -c requirements/main.txt # documenteer @@ -975,9 +975,9 @@ pytest==8.3.4 \ # pytest-asyncio # pytest-cov # pytest-mock -pytest-asyncio==0.25.1 \ - --hash=sha256:79be8a72384b0c917677e00daa711e07db15259f4d23203c59012bcd989d4aee \ - --hash=sha256:c84878849ec63ff2ca509423616e071ef9cd8cc93c053aa33b5b8fb70a990671 +pytest-asyncio==0.25.2 \ + --hash=sha256:0d0bb693f7b99da304a0634afc0a4b19e49d5e0de2d670f38dc4bfa5727c5075 \ + --hash=sha256:3f8ef9a98f45948ea91a0ed3dc4268b5326c0e7bce73892acc654df4262ad45f # via -r requirements/dev.in pytest-cov==6.0.0 \ --hash=sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35 \ @@ -1324,9 +1324,9 @@ scriv==1.5.1 \ --hash=sha256:30ae9ff8d144f8e0cf394c4e1d379542f1b3823767642955b54ec40dc00b32b6 \ --hash=sha256:a3adc657733b4124fcb54527a5f3daab0d3c300de82d0fd2b9b297b243151b78 # via -r requirements/dev.in -setuptools==75.7.0 \ - --hash=sha256:84fb203f278ebcf5cd08f97d3fb96d3fbed4b629d500b29ad60d11e00769b183 \ - --hash=sha256:886ff7b16cd342f1d1defc16fc98c9ce3fde69e087a4e1983d7ab634e5f41f4f +setuptools==75.8.0 \ + --hash=sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6 \ + --hash=sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3 # via # documenteer # sphinxcontrib-bibtex @@ -1456,64 +1456,64 @@ sphinxext-rediraffe==0.2.7 \ --hash=sha256:651dcbfae5ffda9ffd534dfb8025f36120e5efb6ea1a33f5420023862b9f725d \ --hash=sha256:9e430a52d4403847f4ffb3a8dd6dfc34a9fe43525305131f52ed899743a5fd8c # via documenteer -sqlalchemy==2.0.36 \ - --hash=sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763 \ - --hash=sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436 \ - --hash=sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2 \ - --hash=sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588 \ - --hash=sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e \ - --hash=sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959 \ - --hash=sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d \ - --hash=sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575 \ - --hash=sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908 \ - --hash=sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8 \ - --hash=sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8 \ - --hash=sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545 \ - --hash=sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7 \ - --hash=sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971 \ - --hash=sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855 \ - --hash=sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c \ - --hash=sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71 \ - --hash=sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d \ - --hash=sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb \ - --hash=sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72 \ - --hash=sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f \ - --hash=sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5 \ - --hash=sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346 \ - --hash=sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24 \ - --hash=sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e \ - --hash=sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5 \ - --hash=sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08 \ - --hash=sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793 \ - --hash=sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88 \ - --hash=sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686 \ - --hash=sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b \ - --hash=sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2 \ - --hash=sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28 \ - --hash=sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d \ - --hash=sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5 \ - --hash=sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a \ - --hash=sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a \ - --hash=sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3 \ - --hash=sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf \ - --hash=sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5 \ - --hash=sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef \ - --hash=sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689 \ - --hash=sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c \ - --hash=sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b \ - --hash=sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07 \ - --hash=sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa \ - --hash=sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06 \ - --hash=sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1 \ - --hash=sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff \ - --hash=sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa \ - --hash=sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687 \ - --hash=sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4 \ - --hash=sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb \ - --hash=sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44 \ - --hash=sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c \ - --hash=sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e \ - --hash=sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53 +sqlalchemy==2.0.37 \ + --hash=sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278 \ + --hash=sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6 \ + --hash=sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb \ + --hash=sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098 \ + --hash=sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd \ + --hash=sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2 \ + --hash=sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef \ + --hash=sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94 \ + --hash=sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b \ + --hash=sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4 \ + --hash=sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b \ + --hash=sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84 \ + --hash=sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342 \ + --hash=sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989 \ + --hash=sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b \ + --hash=sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b \ + --hash=sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb \ + --hash=sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1 \ + --hash=sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39 \ + --hash=sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff \ + --hash=sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33 \ + --hash=sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72 \ + --hash=sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce \ + --hash=sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4 \ + --hash=sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0 \ + --hash=sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9 \ + --hash=sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c \ + --hash=sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba \ + --hash=sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a \ + --hash=sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b \ + --hash=sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658 \ + --hash=sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49 \ + --hash=sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636 \ + --hash=sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9 \ + --hash=sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8 \ + --hash=sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2 \ + --hash=sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087 \ + --hash=sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1 \ + --hash=sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb \ + --hash=sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5 \ + --hash=sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f \ + --hash=sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0 \ + --hash=sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7 \ + --hash=sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb \ + --hash=sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01 \ + --hash=sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8 \ + --hash=sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2 \ + --hash=sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f \ + --hash=sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e \ + --hash=sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9 \ + --hash=sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7 \ + --hash=sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb \ + --hash=sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4 \ + --hash=sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1 \ + --hash=sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069 \ + --hash=sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761 \ + --hash=sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4 # via # -c requirements/main.txt # -r requirements/dev.in diff --git a/requirements/main.in b/requirements/main.in index 2d1bac8d..09664920 100644 --- a/requirements/main.in +++ b/requirements/main.in @@ -14,6 +14,7 @@ uvicorn[standard] # Other dependencies. # git+https://github.com/lsst-sqre/safir.git@main#egg=safir[db,arq,redis] safir[db,arq,redis] +alembic[tz] pydantic[email] pydantic_settings click diff --git a/requirements/main.txt b/requirements/main.txt index ad4dffbf..cb5051e0 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -40,7 +40,9 @@ aiokafka==0.12.0 \ alembic==1.14.0 \ --hash=sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25 \ --hash=sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b - # via safir + # via + # -r requirements/main.in + # safir annotated-types==0.7.0 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 @@ -55,9 +57,9 @@ anyio==4.8.0 \ # sse-starlette # starlette # watchfiles -arq==0.26.1 \ - --hash=sha256:789d12ca7d69919bd2e641e44f3f14a38bd854daa7ded22cdd725796e8c65352 \ - --hash=sha256:a4db5395c3aaefe0610074b0aa125ff3392aa90a2214973be566796a5819d9cf +arq==0.26.3 \ + --hash=sha256:362063ea3c726562fb69c723d5b8ee80827fdefda782a8547da5be3d380ac4b1 \ + --hash=sha256:9f4b78149a58c9dc4b88454861a254b7c4e7a159f2c973c89b548288b77e9005 # via safir-arq async-timeout==5.0.1 \ --hash=sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c \ @@ -322,9 +324,9 @@ fastjsonschema==2.21.1 \ --hash=sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4 \ --hash=sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667 # via nbformat -faststream==0.5.33 \ - --hash=sha256:24b3b48dff4195c63141864fae24608c4e3e08c9172faccc74b0c46d6d88f665 \ - --hash=sha256:d40761c9f5db9b2cee69748832c4ead20d61805c439d3fbe3107fe4c9a066b2d +faststream==0.5.34 \ + --hash=sha256:84615968c5768ebaa89b72ae66b53e5302c08e7d18b341ef5193e54cb6ba8623 \ + --hash=sha256:aa7f61d6968a68f13ebf755cce9e8bf11b00717c28b2ef66e896b5d652a6c6a2 # via safir gidgethub==5.3.0 \ --hash=sha256:4dd92f2252d12756b13f9dd15cde322bfb0d625b6fb5d680da1567ec74b462c0 \ @@ -772,9 +774,9 @@ pycparser==2.22 ; implementation_name == 'pypy' or platform_python_implementatio --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc # via cffi -pydantic==2.10.4 \ - --hash=sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d \ - --hash=sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06 +pydantic==2.10.5 \ + --hash=sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff \ + --hash=sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53 # via # -r requirements/main.in # fast-depends @@ -1250,64 +1252,64 @@ soupsieve==2.6 \ --hash=sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb \ --hash=sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9 # via beautifulsoup4 -sqlalchemy==2.0.36 \ - --hash=sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763 \ - --hash=sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436 \ - --hash=sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2 \ - --hash=sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588 \ - --hash=sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e \ - --hash=sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959 \ - --hash=sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d \ - --hash=sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575 \ - --hash=sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908 \ - --hash=sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8 \ - --hash=sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8 \ - --hash=sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545 \ - --hash=sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7 \ - --hash=sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971 \ - --hash=sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855 \ - --hash=sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c \ - --hash=sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71 \ - --hash=sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d \ - --hash=sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb \ - --hash=sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72 \ - --hash=sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f \ - --hash=sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5 \ - --hash=sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346 \ - --hash=sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24 \ - --hash=sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e \ - --hash=sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5 \ - --hash=sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08 \ - --hash=sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793 \ - --hash=sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88 \ - --hash=sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686 \ - --hash=sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b \ - --hash=sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2 \ - --hash=sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28 \ - --hash=sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d \ - --hash=sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5 \ - --hash=sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a \ - --hash=sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a \ - --hash=sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3 \ - --hash=sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf \ - --hash=sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5 \ - --hash=sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef \ - --hash=sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689 \ - --hash=sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c \ - --hash=sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b \ - --hash=sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07 \ - --hash=sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa \ - --hash=sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06 \ - --hash=sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1 \ - --hash=sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff \ - --hash=sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa \ - --hash=sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687 \ - --hash=sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4 \ - --hash=sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb \ - --hash=sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44 \ - --hash=sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c \ - --hash=sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e \ - --hash=sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53 +sqlalchemy==2.0.37 \ + --hash=sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278 \ + --hash=sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6 \ + --hash=sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb \ + --hash=sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098 \ + --hash=sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd \ + --hash=sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2 \ + --hash=sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef \ + --hash=sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94 \ + --hash=sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b \ + --hash=sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4 \ + --hash=sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b \ + --hash=sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84 \ + --hash=sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342 \ + --hash=sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989 \ + --hash=sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b \ + --hash=sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b \ + --hash=sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb \ + --hash=sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1 \ + --hash=sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39 \ + --hash=sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff \ + --hash=sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33 \ + --hash=sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72 \ + --hash=sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce \ + --hash=sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4 \ + --hash=sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0 \ + --hash=sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9 \ + --hash=sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c \ + --hash=sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba \ + --hash=sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a \ + --hash=sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b \ + --hash=sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658 \ + --hash=sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49 \ + --hash=sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636 \ + --hash=sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9 \ + --hash=sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8 \ + --hash=sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2 \ + --hash=sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087 \ + --hash=sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1 \ + --hash=sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb \ + --hash=sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5 \ + --hash=sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f \ + --hash=sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0 \ + --hash=sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7 \ + --hash=sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb \ + --hash=sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01 \ + --hash=sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8 \ + --hash=sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2 \ + --hash=sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f \ + --hash=sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e \ + --hash=sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9 \ + --hash=sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7 \ + --hash=sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb \ + --hash=sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4 \ + --hash=sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1 \ + --hash=sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069 \ + --hash=sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761 \ + --hash=sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4 # via # alembic # safir @@ -1420,78 +1422,78 @@ uvloop==0.21.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'c --hash=sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816 \ --hash=sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2 # via uvicorn -watchfiles==1.0.3 \ - --hash=sha256:0179252846be03fa97d4d5f8233d1c620ef004855f0717712ae1c558f1974a16 \ - --hash=sha256:06ce08549e49ba69ccc36fc5659a3d0ff4e3a07d542b895b8a9013fcab46c2dc \ - --hash=sha256:0b90651b4cf9e158d01faa0833b073e2e37719264bcee3eac49fc3c74e7d304b \ - --hash=sha256:0d1ec043f02ca04bf21b1b32cab155ce90c651aaf5540db8eb8ad7f7e645cba8 \ - --hash=sha256:0fe4e740ea94978b2b2ab308cbf9270a246bcbb44401f77cc8740348cbaeac3d \ - --hash=sha256:127de3883bdb29dbd3b21f63126bb8fa6e773b74eaef46521025a9ce390e1073 \ - --hash=sha256:1550be1a5cb3be08a3fb84636eaafa9b7119b70c71b0bed48726fd1d5aa9b868 \ - --hash=sha256:160eff7d1267d7b025e983ca8460e8cc67b328284967cbe29c05f3c3163711a3 \ - --hash=sha256:16db2d7e12f94818cbf16d4c8938e4d8aaecee23826344addfaaa671a1527b07 \ - --hash=sha256:1c6cf7709ed3e55704cc06f6e835bf43c03bc8e3cb8ff946bf69a2e0a78d9d77 \ - --hash=sha256:1da46bb1eefb5a37a8fb6fd52ad5d14822d67c498d99bda8754222396164ae42 \ - --hash=sha256:1df924ba82ae9e77340101c28d56cbaff2c991bd6fe8444a545d24075abb0a87 \ - --hash=sha256:1e263cc718545b7f897baeac1f00299ab6fabe3e18caaacacb0edf6d5f35513c \ - --hash=sha256:228e2247de583475d4cebf6b9af5dc9918abb99d1ef5ee737155bb39fb33f3c0 \ - --hash=sha256:275c1b0e942d335fccb6014d79267d1b9fa45b5ac0639c297f1e856f2f532552 \ - --hash=sha256:29b9cb35b7f290db1c31fb2fdf8fc6d3730cfa4bca4b49761083307f441cac5a \ - --hash=sha256:2b4691234d31686dca133c920f94e478b548a8e7c750f28dbbc2e4333e0d3da9 \ - --hash=sha256:2b961b86cd3973f5822826017cad7f5a75795168cb645c3a6b30c349094e02e3 \ - --hash=sha256:2dcc3f60c445f8ce14156854a072ceb36b83807ed803d37fdea2a50e898635d6 \ - --hash=sha256:2f492d2907263d6d0d52f897a68647195bc093dafed14508a8d6817973586b6b \ - --hash=sha256:310505ad305e30cb6c5f55945858cdbe0eb297fc57378f29bacceb534ac34199 \ - --hash=sha256:34e87c7b3464d02af87f1059fedda5484e43b153ef519e4085fe1a03dd94801e \ - --hash=sha256:418c5ce332f74939ff60691e5293e27c206c8164ce2b8ce0d9abf013003fb7fe \ - --hash=sha256:46e86ed457c3486080a72bc837300dd200e18d08183f12b6ca63475ab64ed651 \ - --hash=sha256:48681c86f2cb08348631fed788a116c89c787fdf1e6381c5febafd782f6c3b44 \ - --hash=sha256:489b80812f52a8d8c7b0d10f0d956db0efed25df2821c7a934f6143f76938bd6 \ - --hash=sha256:48c9f3bc90c556a854f4cab6a79c16974099ccfa3e3e150673d82d47a4bc92c9 \ - --hash=sha256:49bc1bc26abf4f32e132652f4b3bfeec77d8f8f62f57652703ef127e85a3e38d \ - --hash=sha256:52bb50a4c4ca2a689fdba84ba8ecc6a4e6210f03b6af93181bb61c4ec3abaf86 \ - --hash=sha256:5691340f259b8f76b45fb31b98e594d46c36d1dc8285efa7975f7f50230c9093 \ - --hash=sha256:62691f1c0894b001c7cde1195c03b7801aaa794a837bd6eef24da87d1542838d \ - --hash=sha256:632a52dcaee44792d0965c17bdfe5dc0edad5b86d6a29e53d6ad4bf92dc0ff49 \ - --hash=sha256:65ab1fb635476f6170b07e8e21db0424de94877e4b76b7feabfe11f9a5fc12b5 \ - --hash=sha256:6a5bc3ca468bb58a2ef50441f953e1f77b9a61bd1b8c347c8223403dc9b4ac9a \ - --hash=sha256:6a76494d2c5311584f22416c5a87c1e2cb954ff9b5f0988027bc4ef2a8a67181 \ - --hash=sha256:6f8dc09ae69af50bead60783180f656ad96bd33ffbf6e7a6fce900f6d53b08f1 \ - --hash=sha256:703aa5e50e465be901e0e0f9d5739add15e696d8c26c53bc6fc00eb65d7b9469 \ - --hash=sha256:713f67132346bdcb4c12df185c30cf04bdf4bf6ea3acbc3ace0912cab6b7cb8c \ - --hash=sha256:75d3bcfa90454dba8df12adc86b13b6d85fda97d90e708efc036c2760cc6ba44 \ - --hash=sha256:7ca05cacf2e5c4a97d02a2878a24020daca21dbb8823b023b978210a75c79098 \ - --hash=sha256:80bf4b459d94a0387617a1b499f314aa04d8a64b7a0747d15d425b8c8b151da0 \ - --hash=sha256:84fac88278f42d61c519a6c75fb5296fd56710b05bbdcc74bdf85db409a03780 \ - --hash=sha256:889a37e2acf43c377b5124166bece139b4c731b61492ab22e64d371cce0e6e80 \ - --hash=sha256:8af4b582d5fc1b8465d1d2483e5e7b880cc1a4e99f6ff65c23d64d070867ac58 \ - --hash=sha256:90b0fe1fcea9bd6e3084b44875e179b4adcc4057a3b81402658d0eb58c98edf8 \ - --hash=sha256:93436ed550e429da007fbafb723e0769f25bae178fbb287a94cb4ccdf42d3af3 \ - --hash=sha256:995c374e86fa82126c03c5b4630c4e312327ecfe27761accb25b5e1d7ab50ec8 \ - --hash=sha256:9af037d3df7188ae21dc1c7624501f2f90d81be6550904e07869d8d0e6766655 \ - --hash=sha256:9e080cf917b35b20c889225a13f290f2716748362f6071b859b60b8847a6aa43 \ - --hash=sha256:a2ec98e31e1844eac860e70d9247db9d75440fc8f5f679c37d01914568d18721 \ - --hash=sha256:abd85de513eb83f5ec153a802348e7a5baa4588b818043848247e3e8986094e8 \ - --hash=sha256:ac1be85fe43b4bf9a251978ce5c3bb30e1ada9784290441f5423a28633a958a7 \ - --hash=sha256:be37f9b1f8934cd9e7eccfcb5612af9fb728fecbe16248b082b709a9d1b348bf \ - --hash=sha256:bfcae6aecd9e0cb425f5145afee871465b98b75862e038d42fe91fd753ddd780 \ - --hash=sha256:c05b021f7b5aa333124f2a64d56e4cb9963b6efdf44e8d819152237bbd93ba15 \ - --hash=sha256:c14a07bdb475eb696f85c715dbd0f037918ccbb5248290448488a0b4ef201aad \ - --hash=sha256:c18f3502ad0737813c7dad70e3e1cc966cc147fbaeef47a09463bbffe70b0a00 \ - --hash=sha256:c2e9fe695ff151b42ab06501820f40d01310fbd58ba24da8923ace79cf6d702d \ - --hash=sha256:c68be72b1666d93b266714f2d4092d78dc53bd11cf91ed5a3c16527587a52e29 \ - --hash=sha256:ca94c85911601b097d53caeeec30201736ad69a93f30d15672b967558df02885 \ - --hash=sha256:cf745cbfad6389c0e331786e5fe9ae3f06e9d9c2ce2432378e1267954793975c \ - --hash=sha256:d9dd2b89a16cf7ab9c1170b5863e68de6bf83db51544875b25a5f05a7269e678 \ - --hash=sha256:ddff3f8b9fa24a60527c137c852d0d9a7da2a02cf2151650029fdc97c852c974 \ - --hash=sha256:e153a690b7255c5ced17895394b4f109d5dcc2a4f35cb809374da50f0e5c456a \ - --hash=sha256:ea2b51c5f38bad812da2ec0cd7eec09d25f521a8b6b6843cbccedd9a1d8a5c15 \ - --hash=sha256:ef9ec8068cf23458dbf36a08e0c16f0a2df04b42a8827619646637be1769300a \ - --hash=sha256:f280b02827adc9d87f764972fbeb701cf5611f80b619c20568e1982a277d6146 \ - --hash=sha256:f3ff7da165c99a5412fe5dd2304dd2dbaaaa5da718aad942dcb3a178eaa70c56 \ - --hash=sha256:f58d3bfafecf3d81c15d99fc0ecf4319e80ac712c77cf0ce2661c8cf8bf84066 \ - --hash=sha256:f79fe7993e230a12172ce7d7c7db061f046f672f2b946431c81aff8f60b2758b \ - --hash=sha256:ffe709b1d0bc2e9921257569675674cafb3a5f8af689ab9f3f2b3f88775b960f +watchfiles==1.0.4 \ + --hash=sha256:02a526ee5b5a09e8168314c905fc545c9bc46509896ed282aeb5a8ba9bd6ca27 \ + --hash=sha256:05d341c71f3d7098920f8551d4df47f7b57ac5b8dad56558064c3431bdfc0b74 \ + --hash=sha256:076f293100db3b0b634514aa0d294b941daa85fc777f9c698adb1009e5aca0b1 \ + --hash=sha256:0799ae68dfa95136dde7c472525700bd48777875a4abb2ee454e3ab18e9fc712 \ + --hash=sha256:0986902677a1a5e6212d0c49b319aad9cc48da4bd967f86a11bde96ad9676ca1 \ + --hash=sha256:0bc80d91ddaf95f70258cf78c471246846c1986bcc5fd33ccc4a1a67fcb40f9a \ + --hash=sha256:13c2ce7b72026cfbca120d652f02c7750f33b4c9395d79c9790b27f014c8a5a2 \ + --hash=sha256:1941b4e39de9b38b868a69b911df5e89dc43767feeda667b40ae032522b9b5f1 \ + --hash=sha256:1eacd91daeb5158c598fe22d7ce66d60878b6294a86477a4715154990394c9b3 \ + --hash=sha256:229e6ec880eca20e0ba2f7e2249c85bae1999d330161f45c78d160832e026ee2 \ + --hash=sha256:22bb55a7c9e564e763ea06c7acea24fc5d2ee5dfc5dafc5cfbedfe58505e9f90 \ + --hash=sha256:278aaa395f405972e9f523bd786ed59dfb61e4b827856be46a42130605fd0899 \ + --hash=sha256:2a9f93f8439639dc244c4d2902abe35b0279102bca7bbcf119af964f51d53c19 \ + --hash=sha256:308ac265c56f936636e3b0e3f59e059a40003c655228c131e1ad439957592303 \ + --hash=sha256:31f1a379c9dcbb3f09cf6be1b7e83b67c0e9faabed0471556d9438a4a4e14202 \ + --hash=sha256:32b026a6ab64245b584acf4931fe21842374da82372d5c039cba6bf99ef722f3 \ + --hash=sha256:342622287b5604ddf0ed2d085f3a589099c9ae8b7331df3ae9845571586c4f3d \ + --hash=sha256:39f4914548b818540ef21fd22447a63e7be6e24b43a70f7642d21f1e73371590 \ + --hash=sha256:3f68d8e9d5a321163ddacebe97091000955a1b74cd43724e346056030b0bacee \ + --hash=sha256:43b168bba889886b62edb0397cab5b6490ffb656ee2fcb22dec8bfeb371a9e12 \ + --hash=sha256:47eb32ef8c729dbc4f4273baece89398a4d4b5d21a1493efea77a17059f4df8a \ + --hash=sha256:4810ea2ae622add560f4aa50c92fef975e475f7ac4900ce5ff5547b2434642d8 \ + --hash=sha256:4e997802d78cdb02623b5941830ab06f8860038faf344f0d288d325cc9c5d2ff \ + --hash=sha256:4ebbeca9360c830766b9f0df3640b791be569d988f4be6c06d6fae41f187f105 \ + --hash=sha256:4f8c4998506241dedf59613082d1c18b836e26ef2a4caecad0ec41e2a15e4226 \ + --hash=sha256:55ccfd27c497b228581e2838d4386301227fc0cb47f5a12923ec2fe4f97b95af \ + --hash=sha256:5717021b199e8353782dce03bd8a8f64438832b84e2885c4a645f9723bf656d9 \ + --hash=sha256:5c11ea22304d17d4385067588123658e9f23159225a27b983f343fcffc3e796a \ + --hash=sha256:5e0227b8ed9074c6172cf55d85b5670199c99ab11fd27d2c473aa30aec67ee42 \ + --hash=sha256:62c9953cf85529c05b24705639ffa390f78c26449e15ec34d5339e8108c7c407 \ + --hash=sha256:6ba473efd11062d73e4f00c2b730255f9c1bdd73cd5f9fe5b5da8dbd4a717205 \ + --hash=sha256:740d103cd01458f22462dedeb5a3382b7f2c57d07ff033fbc9465919e5e1d0f3 \ + --hash=sha256:74cb3ca19a740be4caa18f238298b9d472c850f7b2ed89f396c00a4c97e2d9ff \ + --hash=sha256:7b75fee5a16826cf5c46fe1c63116e4a156924d668c38b013e6276f2582230f0 \ + --hash=sha256:7cf684aa9bba4cd95ecb62c822a56de54e3ae0598c1a7f2065d51e24637a3c5d \ + --hash=sha256:8012bd820c380c3d3db8435e8cf7592260257b378b649154a7948a663b5f84e9 \ + --hash=sha256:857f5fc3aa027ff5e57047da93f96e908a35fe602d24f5e5d8ce64bf1f2fc733 \ + --hash=sha256:8b1f135238e75d075359cf506b27bf3f4ca12029c47d3e769d8593a2024ce161 \ + --hash=sha256:8d0d0630930f5cd5af929040e0778cf676a46775753e442a3f60511f2409f48f \ + --hash=sha256:90192cdc15ab7254caa7765a98132a5a41471cf739513cc9bcf7d2ffcc0ec7b2 \ + --hash=sha256:95b42cac65beae3a362629950c444077d1b44f1790ea2772beaea95451c086bb \ + --hash=sha256:9745a4210b59e218ce64c91deb599ae8775c8a9da4e95fb2ee6fe745fc87d01a \ + --hash=sha256:9d1ef56b56ed7e8f312c934436dea93bfa3e7368adfcf3df4c0da6d4de959a1e \ + --hash=sha256:9eea33ad8c418847dd296e61eb683cae1c63329b6d854aefcd412e12d94ee235 \ + --hash=sha256:9f25d0ba0fe2b6d2c921cf587b2bf4c451860086534f40c384329fb96e2044d1 \ + --hash=sha256:9fe37a2de80aa785d340f2980276b17ef697ab8db6019b07ee4fd28a8359d2f3 \ + --hash=sha256:a38320582736922be8c865d46520c043bff350956dfc9fbaee3b2df4e1740a4b \ + --hash=sha256:a462490e75e466edbb9fc4cd679b62187153b3ba804868452ef0577ec958f5ff \ + --hash=sha256:a5ae5706058b27c74bac987d615105da17724172d5aaacc6c362a40599b6de43 \ + --hash=sha256:aa216f87594f951c17511efe5912808dfcc4befa464ab17c98d387830ce07b60 \ + --hash=sha256:ab0311bb2ffcd9f74b6c9de2dda1612c13c84b996d032cd74799adb656af4e8b \ + --hash=sha256:ab594e75644421ae0a2484554832ca5895f8cab5ab62de30a1a57db460ce06c6 \ + --hash=sha256:aee397456a29b492c20fda2d8961e1ffb266223625346ace14e4b6d861ba9c80 \ + --hash=sha256:b045c800d55bc7e2cadd47f45a97c7b29f70f08a7c2fa13241905010a5493f94 \ + --hash=sha256:b77d5622ac5cc91d21ae9c2b284b5d5c51085a0bdb7b518dba263d0af006132c \ + --hash=sha256:ba5bb3073d9db37c64520681dd2650f8bd40902d991e7b4cfaeece3e32561d08 \ + --hash=sha256:bdef5a1be32d0b07dcea3318a0be95d42c98ece24177820226b56276e06b63b0 \ + --hash=sha256:c2acfa49dd0ad0bf2a9c0bb9a985af02e89345a7189be1efc6baa085e0f72d7c \ + --hash=sha256:c7cce76c138a91e720d1df54014a047e680b652336e1b73b8e3ff3158e05061e \ + --hash=sha256:cc27a65069bcabac4552f34fd2dce923ce3fcde0721a16e4fb1b466d63ec831f \ + --hash=sha256:cdbd912a61543a36aef85e34f212e5d2486e7c53ebfdb70d1e0b060cc50dd0bf \ + --hash=sha256:cdcc92daeae268de1acf5b7befcd6cfffd9a047098199056c72e4623f531de18 \ + --hash=sha256:d3452c1ec703aa1c61e15dfe9d482543e4145e7c45a6b8566978fbb044265a21 \ + --hash=sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc \ + --hash=sha256:d8d3d9203705b5797f0af7e7e5baa17c8588030aaadb7f6a86107b7247303817 \ + --hash=sha256:e0611d244ce94d83f5b9aff441ad196c6e21b55f77f3c47608dcf651efe54c4a \ + --hash=sha256:f12969a3765909cf5dc1e50b2436eb2c0e676a3c75773ab8cc3aa6175c16e902 \ + --hash=sha256:f44a39aee3cbb9b825285ff979ab887a25c5d336e5ec3574f1506a4671556a8d \ + --hash=sha256:f9ce064e81fe79faa925ff03b9f4c1a98b0bbb4a1b8c1b015afa93030cb21a49 \ + --hash=sha256:fb2c46e275fbb9f0c92e7654b231543c7bbfa1df07cdc4b99fa73bedfde5c844 \ + --hash=sha256:fc2eb5d14a8e0d5df7b36288979176fbb39672d45184fc4b1c004d7c3ce29317 # via uvicorn wcwidth==0.2.13 \ --hash=sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859 \ diff --git a/requirements/tox.txt b/requirements/tox.txt index 5658fc2d..e2c5853d 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -220,24 +220,24 @@ urllib3==2.3.0 \ # -c requirements/dev.txt # docker # requests -uv==0.5.14 \ - --hash=sha256:02fa1c9223ccb90e686a02c5c37a3357cac00165263b41f5f53119f815335157 \ - --hash=sha256:03c0fe3f1257fb73650230e13e1fd928422b708276539e816242953681561bd7 \ - --hash=sha256:12f041458a9d00c84a29eab79d3b5bc53dfd50947787b7c0f628eda7678621a7 \ - --hash=sha256:2a6ecf961d96896f32fd6c198baa25d99f20ed12d6aeb5e514aa5874ae5000c3 \ - --hash=sha256:2dd71f94881d2d402896ab4e94f47dd5e6f4f097c6ae4115345030e263910911 \ - --hash=sha256:42d1e3849d3594b2d3a93f335a776597db8a1d3dcee43dbd4ec49f1ad1f63093 \ - --hash=sha256:484e7921268057011264352696877e1e0c4fd1e0dab5057da73623c0a9d21067 \ - --hash=sha256:54672772289c01811cf1efca4425fa2ae70bfaff020ec197288b7f6c969357e3 \ - --hash=sha256:5fe0748a1c045dc1e95e77a8181ce9bfb848ebbeb175e277ad256a7befbbb61d \ - --hash=sha256:786e16620d679cfa98cea1dd126405a82b63ee43e63994bc208abd0ab68cc529 \ - --hash=sha256:90731cdc9ecb5fa7366696e07b81adf211d54417757f240ddd600f8b7b0107c7 \ - --hash=sha256:93a187ed9da2df5788b755016c77559486c29cf3c87b9ca46822946e3c0734a4 \ - --hash=sha256:a436e948f5ea149b9cd0eb30f923eca190b2d05bb48d9543d4373e1132da1b91 \ - --hash=sha256:b4aa2131c1fc6db62311b26e768987aa582fe6c9256951b5fc9f6f56fb3e2cb9 \ - --hash=sha256:d78a1289d51a9a605703540712963703e58c43380f9645738f6fea0ab4657788 \ - --hash=sha256:f83af784fc842535c382aa58870a8c61aba0dfaa4bf4f2fdf5df03ec934060e2 \ - --hash=sha256:ff362cb0ed592af69433680d9597f34e62c22fe7658a894212e80e24f7c506b6 +uv==0.5.16 \ + --hash=sha256:2cf976b5d29f67bd8e5aa0c9b9c481c7df1580839ac107cbf58811c894a7e6a6 \ + --hash=sha256:2e6f4fbd1cef0227e15374ab56a59ffcbd10c66f263b4e22ac43ca37061539d1 \ + --hash=sha256:3419888b178b82511faebd8d1ca9cb9f5920a7142406898d76878adaffe8dfb1 \ + --hash=sha256:598a0fa168ffe2786e11bfe0103fe22c06e90fe193ced172b843daf9abb42212 \ + --hash=sha256:8fb0b26b9885d57609724fd4efb65785e6896b9bd4ecb865480962999d4247d4 \ + --hash=sha256:9522aad257223c9470bb93b78f08dc473acf73a4a1403b999b2f1871a06343a6 \ + --hash=sha256:9e73ae821678e7395635be0fec810f242e42f47b5f9cb82a7fc3d279bc37edb5 \ + --hash=sha256:a7508b271a461603a38095eaca27b1766bd71f0a02232868a8faae0d8e8a1ae3 \ + --hash=sha256:ba5f364809a46e590368a3f85638b71118952f30c79125f87144d9f00be0c9cb \ + --hash=sha256:c609bb564f4d5822acd25d8881d76123c2e9aa26b634a6444b56142daff851b8 \ + --hash=sha256:c6d3b45d66adedebe5c56602970b2c0fdf6d1977f53464691d428f1a7daa911b \ + --hash=sha256:c7ee83877f4e324df4a61e7c79a476d26725e415894a755ce45de87709fc9581 \ + --hash=sha256:c8310f40b8834812ddfb195fd62aafaed44b2993c71de8fa75ca0960c739d04e \ + --hash=sha256:d540bbc1831d4187a29a9e8f7219e2093c491a45bcbcf2a6196f81c6d2162691 \ + --hash=sha256:d54777d6c87a12213a59d2109289e4e35374cc79f88e29617ff764189b8b9cad \ + --hash=sha256:e49b2c242cbc201665670dcc3ffa509fa6e131ebcf0423c59df91f2f21eca9d7 \ + --hash=sha256:e764721c425ca6cde184ae69517748e47c4ea7f0d9c7dafa78721feeabc58e01 # via tox-uv virtualenv==20.28.1 \ --hash=sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb \ diff --git a/ruff-shared.toml b/ruff-shared.toml index 0702eaf1..e5e4cdb7 100644 --- a/ruff-shared.toml +++ b/ruff-shared.toml @@ -58,9 +58,9 @@ ignore = [ "S607", # using PATH is not a security vulnerability "SIM102", # sometimes the formatting of nested if statements is clearer "SIM117", # sometimes nested with contexts are clearer - "TCH001", # we decided to not maintain separate TYPE_CHECKING blocks - "TCH002", # we decided to not maintain separate TYPE_CHECKING blocks - "TCH003", # we decided to not maintain separate TYPE_CHECKING blocks + "TC001", # we decided to not maintain separate TYPE_CHECKING blocks + "TC002", # we decided to not maintain separate TYPE_CHECKING blocks + "TC003", # we decided to not maintain separate TYPE_CHECKING blocks "TD003", # we don't require issues be created for TODOs "TID252", # if we're going to use relative imports, use them always "TRY003", # good general advice but lint is way too aggressive diff --git a/src/timessquare/cli.py b/src/timessquare/cli.py index cc842919..d16ae6a1 100644 --- a/src/timessquare/cli.py +++ b/src/timessquare/cli.py @@ -2,15 +2,23 @@ from __future__ import annotations +import asyncio +import subprocess +from pathlib import Path + import click import structlog import uvicorn from redis.asyncio import Redis from safir.asyncio import run_with_asyncio -from safir.database import create_database_engine, initialize_database +from safir.database import ( + create_database_engine, + is_database_current, + stamp_database, +) from .config import config -from .dbschema import Base +from .database import init_database from .storage.nbhtmlcache import NbHtmlCacheStore @@ -53,20 +61,56 @@ def develop(port: int) -> None: @main.command() +@click.option( + "--alembic-config-path", + envvar="TS_ALEMBIC_CONFIG_PATH", + type=click.Path(path_type=Path), + help="Alembic configuration file.", +) @click.option( "--reset", is_flag=True, help="Delete all existing database data." ) +def init(*, alembic_config_path: Path, reset: bool) -> None: + """Initialize the SQL database storage.""" + logger = structlog.get_logger("timessquare") + logger.debug("Initializing database") + asyncio.run(init_database(config, logger, reset=reset)) + stamp_database(alembic_config_path) + logger.debug("Finished initializing data stores") + + +@main.command() +@click.option( + "--alembic-config-path", + envvar="TS_ALEMBIC_CONFIG_PATH", + type=click.Path(path_type=Path), + help="Alembic configuration file.", +) +def update_db_schema(*, alembic_config_path: Path) -> None: + """Update the SQL database schema.""" + subprocess.run( + ["alembic", "upgrade", "head"], + check=True, + cwd=str(alembic_config_path.parent), + ) + + +@main.command() +@click.option( + "--alembic-config-path", + envvar="TS_ALEMBIC_CONFIG_PATH", + type=click.Path(path_type=Path), + help="Alembic configuration file.", +) @run_with_asyncio -async def init(*, reset: bool) -> None: - """Initialize the database storage.""" - logger = structlog.get_logger(config.logger_name) +async def validate_db_schema(*, alembic_config_path: Path) -> None: + """Validate that the SQL database schema is current.""" engine = create_database_engine( - str(config.database_url), config.database_password.get_secret_value() - ) - await initialize_database( - engine, logger, schema=Base.metadata, reset=reset + config.database_url, config.database_password ) - await engine.dispose() + logger = structlog.get_logger("timessquare") + if not await is_database_current(engine, logger, alembic_config_path): + raise click.ClickException("Database schema is not current") @main.command("reset-html") diff --git a/src/timessquare/database.py b/src/timessquare/database.py new file mode 100644 index 00000000..6b0a92bc --- /dev/null +++ b/src/timessquare/database.py @@ -0,0 +1,51 @@ +"""Database management functions.""" + +from __future__ import annotations + +from safir.database import create_database_engine, initialize_database +from sqlalchemy.ext.asyncio import AsyncEngine +from structlog.stdlib import BoundLogger + +from .config import Config +from .dbschema import Base + +__all__ = ["init_database"] + + +async def init_database( + config: Config, + logger: BoundLogger, + engine: AsyncEngine | None = None, + *, + reset: bool = False, +) -> None: + """Initialize the database. + + This is the internal async implementation details of the ``init`` command, + except for the Alembic parts. Alembic has to run outside of a running + asyncio loop, hence this separation. Always stamp the database with + Alembic after calling this function. + + Parameters + ---------- + config + Application configuration. + logger + Logger to use for status reporting. + engine + If given, database engine to use, which avoids the need to create + another one. + reset + Whether to reset the database. + """ + engine_created = False + if not engine: + engine = create_database_engine( + config.database_url, config.database_password + ) + engine_created = True + await initialize_database( + engine, logger, schema=Base.metadata, reset=reset + ) + if engine_created: + await engine.dispose() diff --git a/src/timessquare/main.py b/src/timessquare/main.py index b2dc99ba..07a29745 100644 --- a/src/timessquare/main.py +++ b/src/timessquare/main.py @@ -17,6 +17,7 @@ from fastapi import FastAPI from fastapi.openapi.utils import get_openapi +from safir.database import create_database_engine, is_database_current from safir.dependencies.arq import arq_dependency from safir.dependencies.db_session import db_session_dependency from safir.dependencies.http_client import http_client_dependency @@ -41,6 +42,13 @@ async def lifespan(app: FastAPI) -> AsyncIterator: logger = get_logger(__name__) logger.debug("Times Square is starting up.") + engine = create_database_engine( + config.database_url, config.database_password + ) + if not await is_database_current(engine, logger): + raise RuntimeError("Database schema out of date") + await engine.dispose() + await db_session_dependency.initialize( str(config.database_url), config.database_password.get_secret_value() ) diff --git a/src/timessquare/worker/main.py b/src/timessquare/worker/main.py index eeba1712..49a91a74 100644 --- a/src/timessquare/worker/main.py +++ b/src/timessquare/worker/main.py @@ -9,6 +9,7 @@ import arq import httpx import structlog +from safir.database import create_database_engine, is_database_current from safir.dependencies.db_session import db_session_dependency from safir.logging import configure_logging from safir.slack.blockkit import SlackMessage, SlackTextField @@ -58,6 +59,14 @@ async def startup(ctx: dict[Any, Any]) -> None: ctx["logger"] = logger + # Check if the database schema is up to date + engine = create_database_engine( + config.database_url, config.database_password + ) + if not await is_database_current(engine, logger): + raise RuntimeError("Database schema out of date") + await engine.dispose() + # Set up FastAPI dependencies; we can use them "manually" with # arq to provide resources similarly to FastAPI endpoints await db_session_dependency.initialize( diff --git a/tests/conftest.py b/tests/conftest.py index cfa2b403..6d0614ab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,11 @@ from fastapi import FastAPI from gidgethub.httpx import GitHubAPI from httpx import ASGITransport, AsyncClient -from safir.database import create_database_engine, initialize_database +from safir.database import ( + create_database_engine, + initialize_database, + stamp_database_async, +) from timessquare import main from timessquare.config import config @@ -25,10 +29,12 @@ async def app() -> AsyncIterator[FastAPI]: events are sent during test execution. """ logger = structlog.get_logger(config.logger_name) + engine = create_database_engine( config.database_url, config.database_password.get_secret_value() ) await initialize_database(engine, logger, schema=Base.metadata, reset=True) + await stamp_database_async(engine) await engine.dispose() async with LifespanManager(main.app): yield main.app diff --git a/tests/schema_test.py b/tests/schema_test.py new file mode 100644 index 00000000..1d2e0606 --- /dev/null +++ b/tests/schema_test.py @@ -0,0 +1,23 @@ +"""Test the schema of the database.""" + +import subprocess + +import pytest +from safir.database import create_database_engine, drop_database + +from timessquare.config import config +from timessquare.dbschema import Base + + +@pytest.mark.asyncio +async def test_schema() -> None: + """Ensure that the SQLAlchemy model is consistent with the current + Alembic revision. + """ + engine = create_database_engine( + config.database_url, config.database_password + ) + await drop_database(engine, Base.metadata) + await engine.dispose() + subprocess.run(["alembic", "upgrade", "head"], check=True) + subprocess.run(["alembic", "check"], check=True) diff --git a/tox.ini b/tox.ini index fe682e08..420692df 100644 --- a/tox.ini +++ b/tox.ini @@ -45,6 +45,7 @@ setenv = # environment variables; pytest can override these for individual test runs SAFIR_LOG_LEVEL = DEBUG SAFIR_PROFILE = development + TS_ALEMBIC_CONFIG_PATH = {toxinidir}/alembic.ini TS_ENVIRONMENT_URL = https://test.example.com TS_DATABASE_URL = postgresql://timessquare@localhost:5433/timessquare TS_DATABASE_PASSWORD = INSECURE-PASSWORD @@ -81,6 +82,42 @@ deps = pre-commit commands = pre-commit run --all-files +[testenv:alembic] +description = Run Alembic against a test database +setenv = + TS_ENVIRONMENT_URL = https://test.example.com + TS_PATH_PREFIX = /times-square/api + TS_ALEMBIC_CONFIG_PATH = {toxinidir}/alembic.ini + TS_DATABASE_URL = postgresql://timessquare@127.0.0.1:5432/timessquare + TS_DATABASE_PASSWORD = INSECURE-PASSWORD + TS_REDIS_URL = redis://localhost:6379/0 + TS_REDIS_QUEUE_URL = redis://localhost:6379/1 + TS_GAFAELFAWR_TOKEN = gt-eOfLolxU8FJ1xr08U7RTbg.Jr-KHSeISXwR5GXHiLemhw + TS_GITHUB_APP_PRIVATE_KEY = foo + TS_GITHUB_WEBHOOK_SECRET = foo + TS_ENABLE_GITHUB_APP = false +deps = + ruff # For Alembic revision formatting +commands = + alembic {posargs} + +[testenv:cli] +description = Run command-line tool against a test database +commands = + timessquare {posargs} +setenv = + TS_ENVIRONMENT_URL = https://test.example.com + TS_PATH_PREFIX = /times-square/api + TS_ALEMBIC_CONFIG_PATH = {toxinidir}/alembic.ini + TS_DATABASE_URL = postgresql://timessquare@127.0.0.1:5432/timessquare + TS_DATABASE_PASSWORD = INSECURE-PASSWORD + TS_REDIS_URL = redis://localhost:6379/0 + TS_REDIS_QUEUE_URL = redis://localhost:6379/1 + TS_GAFAELFAWR_TOKEN = gt-eOfLolxU8FJ1xr08U7RTbg.Jr-KHSeISXwR5GXHiLemhw + TS_GITHUB_APP_PRIVATE_KEY = foo + TS_GITHUB_WEBHOOK_SECRET = foo + TS_ENABLE_GITHUB_APP = false + [testenv:run] description = Run the development server with auto-reload for code changes. usedevelop = true @@ -90,6 +127,7 @@ setenv = SAFIR_PROFILE = development TS_ENVIRONMENT_URL = https://test.example.com TS_PATH_PREFIX = /times-square/api + TS_ALEMBIC_CONFIG_PATH = {toxinidir}/alembic.ini TS_DATABASE_URL = postgresql://timessquare@127.0.0.1:5432/timessquare TS_DATABASE_PASSWORD = INSECURE-PASSWORD TS_REDIS_URL = redis://localhost:6379/0