From 7dcdb6afd87129b6f7fbfed89384d332f1ec6313 Mon Sep 17 00:00:00 2001 From: Adrian Rumpold Date: Thu, 31 Oct 2024 10:24:23 +0100 Subject: [PATCH] Add persistence layer to backend (#138) * feat(backend): Add persistence layer - SQLModel with SQLite database - Add alembic for migrations, automatically invoked in container - Pydantic settings for database connection - Add volume for database persistence in k8s deployment - Bump pre-commit hooks * feat(backend): Support file sync and hot reload in Skaffold * fix(backend): DB migration check on startup, optional automatic migration * fix(backend): Provide default path for SQLite DB in container image * tests(backend): Provide in-memory SQLite DB for tests * fix(backend): Always import SQLModel in db module This is needed by the Alembic environment setup. --- .pre-commit-config.yaml | 6 +- backend/.gitignore | 2 + backend/Dockerfile | 31 +++- backend/alembic.ini | 71 +++++++++ backend/deploy/jobq-server/README.md | 5 +- .../jobq-server/templates/deployment.yaml | 31 +++- backend/deploy/jobq-server/templates/pvc.yaml | 10 ++ backend/deploy/jobq-server/values.yaml | 12 +- backend/pyproject.toml | 13 +- backend/scripts/entrypoint.sh | 7 + backend/skaffold.yaml | 8 + backend/src/jobq_server/__main__.py | 33 ++++- backend/src/jobq_server/alembic/env.py | 83 +++++++++++ .../src/jobq_server/alembic/script.py.mako | 25 ++++ .../src/jobq_server/alembic/versions/.gitkeep | 0 backend/src/jobq_server/config.py | 12 ++ backend/src/jobq_server/db.py | 52 +++++++ .../src/jobq_server/dependencies/__init__.py | 9 ++ backend/tests/integration/conftest.py | 15 ++ backend/uv.lock | 139 +++++++++++++++++- client/tests/smoke/test_image_spec.py | 18 +-- docs/guide/server-deployment.md | 1 + 22 files changed, 540 insertions(+), 43 deletions(-) create mode 100644 backend/.gitignore create mode 100644 backend/alembic.ini create mode 100644 backend/deploy/jobq-server/templates/pvc.yaml create mode 100755 backend/scripts/entrypoint.sh create mode 100644 backend/src/jobq_server/alembic/env.py create mode 100644 backend/src/jobq_server/alembic/script.py.mako create mode 100644 backend/src/jobq_server/alembic/versions/.gitkeep create mode 100644 backend/src/jobq_server/config.py create mode 100644 backend/src/jobq_server/db.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f0a5032..27d7d5b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,13 +11,13 @@ repos: - id: end-of-file-fixer - id: mixed-line-ending - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.9 + rev: v0.7.1 hooks: - id: ruff args: [--fix] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.13.0 hooks: # See https://github.com/pre-commit/mirrors-mypy/blob/main/.pre-commit-hooks.yaml - id: mypy @@ -31,7 +31,7 @@ repos: --install-types, ] - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.4.19 + rev: 0.4.28 hooks: - id: uv-lock name: Lock project dependencies diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..ca6d9b5f --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,2 @@ +# Development database +jobq.db diff --git a/backend/Dockerfile b/backend/Dockerfile index b3f6d2e9..59158292 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -3,6 +3,14 @@ ARG PYTHON_VERSION=3.12-slim # Build stage FROM python:${PYTHON_VERSION} AS build +# Compile bytecode +# Ref: https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode +ENV UV_COMPILE_BYTECODE=1 + +# uv Cache +# Ref: https://docs.astral.sh/uv/guides/integration/docker/#caching +ENV UV_LINK_MODE=copy + RUN apt-get update && \ apt-get install -y git && \ rm -rf /var/lib/apt/lists/* @@ -13,20 +21,33 @@ RUN pip install --no-cache-dir --upgrade uv ENV SETUPTOOLS_SCM_PRETEND_VERSION_FOR_AAI_JOBQ_SERVER=0.0.0 WORKDIR /code -COPY ./uv.lock uv.lock -COPY ./pyproject.toml pyproject.toml -RUN uv sync --locked +COPY ./alembic.ini alembic.ini +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project +COPY ./pyproject.toml ./uv.lock ./alembic.ini /code/ COPY ./src /code/src -RUN uv pip install --no-deps . + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync # Runtime stage FROM python:${PYTHON_VERSION} WORKDIR /code +COPY scripts/entrypoint.sh /entrypoint.sh COPY --chown=nobody:nogroup --from=build /code /code USER nobody -CMD ["/code/.venv/bin/uvicorn", "jobq_server.__main__:app", "--host", "0.0.0.0", "--port", "8000"] +ENV PYTHONUNBUFFERED=1 + +VOLUME ["/data"] + +# Provide default path for embedded database +ENV DB_CONNECTION_STRING="sqlite:////tmp/jobq.db" + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 00000000..ae262436 --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,71 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = src/jobq_server/alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# timezone to use when rendering the date +# within the migration file as well as the filename. +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +#truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; this defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path +# version_locations = %(here)s/bar %(here)s/bat alembic/versions + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/deploy/jobq-server/README.md b/backend/deploy/jobq-server/README.md index 13a5bc38..5a291ba9 100644 --- a/backend/deploy/jobq-server/README.md +++ b/backend/deploy/jobq-server/README.md @@ -15,19 +15,16 @@ Helm chart for the jobq backend server | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | object | `{}` | | +| enableReload | bool | `false` | Enable hot reloading of code inside the container (useful for development) | | fullnameOverride | string | `""` | | | image.pullPolicy | string | `"IfNotPresent"` | | | image.repository | string | `"ghcr.io/aai-institute/jobq-server"` | | | image.tag | string | `""` | | | imagePullSecrets | list | `[]` | | -| livenessProbe.httpGet.path | string | `"/health"` | | -| livenessProbe.httpGet.port | string | `"http"` | | | nameOverride | string | `""` | | | nodeSelector | object | `{}` | | | podAnnotations | object | `{}` | | | podLabels | object | `{}` | | -| readinessProbe.httpGet.path | string | `"/health"` | | -| readinessProbe.httpGet.port | string | `"http"` | | | replicaCount | int | `1` | | | resources | object | `{}` | | | service.port | int | `8000` | | diff --git a/backend/deploy/jobq-server/templates/deployment.yaml b/backend/deploy/jobq-server/templates/deployment.yaml index 32ae1891..75436957 100644 --- a/backend/deploy/jobq-server/templates/deployment.yaml +++ b/backend/deploy/jobq-server/templates/deployment.yaml @@ -6,6 +6,7 @@ metadata: {{- include "jobq-server.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} + # FIXME: Consider the Recreate deployment strategy to prevent concurrent access to the database selector: matchLabels: {{- include "jobq-server.selectorLabels" . | nindent 6 }} @@ -39,24 +40,44 @@ spec: - ALL image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.enableReload }} + args: + - "--reload" + {{- end }} ports: - name: http containerPort: {{ .Values.service.port }} protocol: TCP + env: + - name: DB_CONNECTION_STRING + value: "sqlite:////data/jobq.db" + - name: AUTO_MIGRATE + value: "false" livenessProbe: - {{- toYaml .Values.livenessProbe | nindent 12 }} + httpGet: + path: /health + port: http + initialDelaySeconds: 2 readinessProbe: - {{- toYaml .Values.readinessProbe | nindent 12 }} + httpGet: + path: /health + port: http + initialDelaySeconds: 2 resources: {{- toYaml .Values.resources | nindent 12 }} - {{- with .Values.volumeMounts }} volumeMounts: + - name: db-volume + mountPath: /data + {{- with .Values.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.volumes }} volumes: + - name: db-volume + persistentVolumeClaim: + claimName: {{ include "jobq-server.fullname" . }} + {{- with .Values.volumes }} {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/backend/deploy/jobq-server/templates/pvc.yaml b/backend/deploy/jobq-server/templates/pvc.yaml new file mode 100644 index 00000000..72578780 --- /dev/null +++ b/backend/deploy/jobq-server/templates/pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "jobq-server.fullname" . }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 256Mi diff --git a/backend/deploy/jobq-server/values.yaml b/backend/deploy/jobq-server/values.yaml index d22eadec..92ac40af 100644 --- a/backend/deploy/jobq-server/values.yaml +++ b/backend/deploy/jobq-server/values.yaml @@ -41,15 +41,6 @@ resources: # cpu: 100m # memory: 128Mi -livenessProbe: - httpGet: - path: /health - port: http -readinessProbe: - httpGet: - path: /health - port: http - # Additional volumes on the output Deployment definition. volumes: [] # - name: foo @@ -63,6 +54,9 @@ volumeMounts: [] # mountPath: "/etc/foo" # readOnly: true +# -- Enable hot reloading of code inside the container (useful for development) +enableReload: false + nodeSelector: {} tolerations: [] diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8526e966..6394bc1e 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -16,7 +16,16 @@ maintainers = [ { name = "Adrian Rumpold", email = "a.rumpold@appliedai-institute.de" }, ] license = { text = "Apache-2.0" } -dependencies = ["fastapi", "uvicorn", "docker", "kubernetes", "aai-jobq"] +dependencies = [ + "fastapi", + "uvicorn", + "docker", + "kubernetes", + "aai-jobq", + "sqlmodel>=0.0.22", + "alembic>=1.13.3", + "pydantic-settings>=2.6.0", +] dynamic = ["version"] [project.optional-dependencies] @@ -38,6 +47,7 @@ root = ".." [tool.ruff] extend = "../pyproject.toml" src = ["src"] +extend-exclude = ["src/jobq_server/alembic"] [tool.mypy] ignore_missing_imports = true @@ -48,6 +58,7 @@ strict_optional = true warn_unreachable = true show_column_numbers = true show_absolute_path = true +exclude = ["src/jobq_server/alembic"] [tool.coverage.report] exclude_also = ["@overload", "raise NotImplementedError", "if TYPE_CHECKING:"] diff --git a/backend/scripts/entrypoint.sh b/backend/scripts/entrypoint.sh new file mode 100755 index 00000000..9d90ce86 --- /dev/null +++ b/backend/scripts/entrypoint.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -eux + +cd /code +/code/.venv/bin/alembic upgrade head +/code/.venv/bin/uvicorn jobq_server.__main__:app --host 0.0.0.0 --port 8000 "$@" diff --git a/backend/skaffold.yaml b/backend/skaffold.yaml index 00ff7149..fd3a4597 100644 --- a/backend/skaffold.yaml +++ b/backend/skaffold.yaml @@ -7,11 +7,19 @@ build: - image: ghcr.io/aai-institute/jobq-server docker: dockerfile: Dockerfile + sync: + manual: + - src: src/** + dest: /code/src + local: + useBuildkit: true deploy: helm: releases: - name: jobq-server chartPath: deploy/jobq-server + setValues: + enableReload: true valuesFiles: - deploy/jobq-server/values.yaml version: 0.1.0 diff --git a/backend/src/jobq_server/__main__.py b/backend/src/jobq_server/__main__.py index b73e3ce2..521450fa 100644 --- a/backend/src/jobq_server/__main__.py +++ b/backend/src/jobq_server/__main__.py @@ -1,22 +1,37 @@ import logging from contextlib import asynccontextmanager -from fastapi import FastAPI -from kubernetes import config +import kubernetes.config +from fastapi import FastAPI, Response +from sqlmodel import select +from jobq_server.config import settings +from jobq_server.db import check_migrations, get_engine, upgrade_migrations from jobq_server.routers import jobs @asynccontextmanager async def lifespan(app: FastAPI): logging.basicConfig(level=logging.DEBUG) - config.load_config() + + # Check if the database schema is up to date + needs_migrations = check_migrations() + if not needs_migrations: + if settings.AUTO_MIGRATE: + logging.info("Upgrading database schema") + upgrade_migrations() + else: + logging.error("Database migrations are not up to date. Exiting.") + raise SystemExit(1) + + kubernetes.config.load_config() + yield app = FastAPI( - title="the jobq cluster workflow management tool backend", - description="Backend service for the appliedAI infrastructure product", + title="jobq API", + description="Backend service API for the jobq workflow engine", lifespan=lifespan, ) @@ -25,7 +40,13 @@ async def lifespan(app: FastAPI): @app.get("/health", include_in_schema=False) async def health(): - return {"status": "ok"} + try: + with get_engine().connect() as conn: + conn.execute(select(1)) + return {"status": "ok"} + except Exception: + logging.error("Database connection failed", exc_info=True) + return Response(status_code=503) # URLs to be excluded from Uvicorn access logging diff --git a/backend/src/jobq_server/alembic/env.py b/backend/src/jobq_server/alembic/env.py new file mode 100644 index 00000000..38ec52bc --- /dev/null +++ b/backend/src/jobq_server/alembic/env.py @@ -0,0 +1,83 @@ +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config, pool + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +# target_metadata = None + +from jobq_server.config import settings # noqa +from jobq_server.db import SQLModel # noqa + +target_metadata = SQLModel.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_url(): + return str(settings.DB_CONNECTION_STRING) + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = get_url() + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True, compare_type=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + configuration = config.get_section(config.config_ini_section) + configuration["sqlalchemy.url"] = get_url() + connectable = engine_from_config( + configuration, + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata, compare_type=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/src/jobq_server/alembic/script.py.mako b/backend/src/jobq_server/alembic/script.py.mako new file mode 100644 index 00000000..217a9a8b --- /dev/null +++ b/backend/src/jobq_server/alembic/script.py.mako @@ -0,0 +1,25 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/backend/src/jobq_server/alembic/versions/.gitkeep b/backend/src/jobq_server/alembic/versions/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/backend/src/jobq_server/config.py b/backend/src/jobq_server/config.py new file mode 100644 index 00000000..7e3ee059 --- /dev/null +++ b/backend/src/jobq_server/config.py @@ -0,0 +1,12 @@ +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + AUTO_MIGRATE: bool = True + """Automatically upgrade the database schema to the latest version""" + + DB_CONNECTION_STRING: str = "sqlite:///./jobq.db" + """Database connection string""" + + +settings = Settings() diff --git a/backend/src/jobq_server/db.py b/backend/src/jobq_server/db.py new file mode 100644 index 00000000..25918ec9 --- /dev/null +++ b/backend/src/jobq_server/db.py @@ -0,0 +1,52 @@ +from threading import Lock + +from alembic import command +from alembic.config import Config +from alembic.runtime.migration import MigrationContext +from alembic.script import ScriptDirectory +from sqlalchemy import Engine +from sqlmodel import SQLModel as SQLModel +from sqlmodel import create_engine + +from jobq_server.config import settings + +connect_args = {"check_same_thread": False} + +_engine: Engine | None = None +engine_lock = Lock() + + +def get_engine() -> Engine: + global _engine + + # Double-checked locking pattern + if _engine is None: + with engine_lock: + if _engine is None: + _engine = create_engine( + settings.DB_CONNECTION_STRING, + connect_args=connect_args, + ) + return _engine + + +def check_migrations() -> bool: + """ + Check if the database is up to date with the migration scripts + """ + + # Get the latest available revision from migration scripts + config = Config("alembic.ini") + script = ScriptDirectory.from_config(config) + + # Get the current revision from the database + with get_engine().connect() as conn: + context = MigrationContext.configure(conn) + return set(context.get_current_heads()) == set(script.get_heads()) + + +def upgrade_migrations(): + """Perform a migration upgrade to the latest version""" + + alembic_cfg = Config("alembic.ini") + command.upgrade(alembic_cfg, "head") diff --git a/backend/src/jobq_server/dependencies/__init__.py b/backend/src/jobq_server/dependencies/__init__.py index 83c49e90..be9a382f 100644 --- a/backend/src/jobq_server/dependencies/__init__.py +++ b/backend/src/jobq_server/dependencies/__init__.py @@ -1,7 +1,10 @@ +from collections.abc import Generator from typing import Annotated from fastapi import Depends, HTTPException +from sqlmodel import Session +from jobq_server.db import get_engine from jobq_server.models import JobId from jobq_server.services.k8s import KubernetesService from jobq_server.utils.kueue import KueueWorkload @@ -22,5 +25,11 @@ def managed_workload( return wl +def get_session() -> Generator[Session, None, None]: + with Session(get_engine()) as session: + yield session + + ManagedWorkload = Annotated[KueueWorkload, Depends(managed_workload)] Kubernetes = Annotated[KubernetesService, Depends(k8s_service)] +DBSessionDep = Annotated[Session, Depends(get_session)] diff --git a/backend/tests/integration/conftest.py b/backend/tests/integration/conftest.py index d9903eba..9697aaff 100644 --- a/backend/tests/integration/conftest.py +++ b/backend/tests/integration/conftest.py @@ -2,9 +2,13 @@ import pytest from fastapi.testclient import TestClient from pytest_mock import MockFixture +from sqlmodel import Session +import jobq_server.db import jobq_server.services.k8s from jobq_server import app +from jobq_server.config import settings +from jobq_server.db import get_engine, upgrade_migrations @pytest.fixture @@ -16,3 +20,14 @@ def client(mocker: MockFixture) -> TestClient: mocker.patch.object(docker, "from_env") return TestClient(app) + + +@pytest.fixture(scope="session", autouse=True) +def db(): + # Use a SQLite in-memory database for testing + settings.DB_CONNECTION_STRING = "sqlite:///:memory:" + settings.AUTO_MIGRATE = True + + upgrade_migrations() + with Session(get_engine()) as session: + yield session diff --git a/backend/uv.lock b/backend/uv.lock index 9e429698..8064b14d 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -16,13 +16,16 @@ dependencies = [ [[package]] name = "aai-jobq-server" -version = "0.1.0rc8.dev34+g82b02d7.d20241025" +version = "0.1.0rc8.dev28+gff36c96.d20241030" source = { editable = "." } dependencies = [ { name = "aai-jobq" }, + { name = "alembic" }, { name = "docker" }, { name = "fastapi" }, { name = "kubernetes" }, + { name = "pydantic-settings" }, + { name = "sqlmodel" }, { name = "uvicorn" }, ] @@ -41,20 +44,37 @@ dev = [ [package.metadata] requires-dist = [ { name = "aai-jobq", git = "https://github.com/aai-institute/jobq?subdirectory=client&branch=main" }, + { name = "alembic", specifier = ">=1.13.3" }, { name = "build", marker = "extra == 'dev'" }, { name = "docker" }, { name = "fastapi" }, { name = "fastapi", extras = ["standard"], marker = "extra == 'dev'" }, { name = "kubernetes" }, { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "pydantic-settings", specifier = ">=2.6.0" }, { name = "pytest", marker = "extra == 'dev'" }, { name = "pytest-cov", marker = "extra == 'dev'" }, { name = "pytest-mock", marker = "extra == 'dev'" }, { name = "ruff", marker = "extra == 'dev'" }, + { name = "sqlmodel", specifier = ">=0.0.22" }, { name = "testcontainers", marker = "extra == 'dev'" }, { name = "uvicorn" }, ] +[[package]] +name = "alembic" +version = "1.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/a2/840c3b84382dce8624bc2f0ee67567fc74c32478d0c5a5aea981518c91c3/alembic-1.13.3.tar.gz", hash = "sha256:203503117415561e203aa14541740643a611f641517f0209fcae63e9fa09f1a2", size = 1921223 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/12/58f4f11385fddafef5d6f7bfaaf2f42899c8da6b4f95c04b7c3b744851a8/alembic-1.13.3-py3-none-any.whl", hash = "sha256:908e905976d15235fae59c9ac42c4c5b75cfcefe3d27c0fbf7ae15a37715d80e", size = 233217 }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -347,6 +367,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/1f/3a72917afcb0d5cd842cbccb81bf7a8a7b45b4c66d8dc4556ccb3b016bfc/google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f", size = 208968 }, ] +[[package]] +name = "greenlet" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479 }, + { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404 }, + { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813 }, + { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517 }, + { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831 }, + { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413 }, + { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619 }, + { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198 }, + { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930 }, + { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, + { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, + { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, + { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, + { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, + { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, + { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, + { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, + { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, + { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, + { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, +] + [[package]] name = "h11" version = "0.14.0" @@ -468,6 +530,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/a8/17f5e28cecdbd6d48127c22abdb794740803491f422a11905c4569d8e139/kubernetes-31.0.0-py2.py3-none-any.whl", hash = "sha256:bf141e2d380c8520eada8b351f4e319ffee9636328c137aa432bc486ca1200e1", size = 1857013 }, ] +[[package]] +name = "mako" +version = "1.3.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/0b/29bc5a230948bf209d3ed3165006d257e547c02c3c2a96f6286320dfe8dc/mako-1.3.6.tar.gz", hash = "sha256:9ec3a1583713479fae654f83ed9fa8c9a4c16b7bb0daba0e6bbebff50c0d983d", size = 390206 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/22/bc14c6f02e6dccaafb3eba95764c8f096714260c2aa5f76f654fd16a23dd/Mako-1.3.6-py3-none-any.whl", hash = "sha256:a91198468092a2f1a0de86ca92690fb0cfc43ca90ee17e15d93662b4c04b241a", size = 78557 }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -660,6 +734,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, ] +[[package]] +name = "pydantic-settings" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/66/5f1a9da10675bfb3b9da52f5b689c77e0a5612263fcce510cfac3e99a168/pydantic_settings-2.6.0.tar.gz", hash = "sha256:44a1804abffac9e6a30372bb45f6cafab945ef5af25e66b1c634c01dd39e0188", size = 75232 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/19/26bb6bdb9fdad5f0dfce538780814084fb667b4bc37fcb28459c14b8d3b5/pydantic_settings-2.6.0-py3-none-any.whl", hash = "sha256:4a819166f119b74d7f8c765196b165f95cc7487ce58ea27dec8a5a26be0970e0", size = 28578 }, +] + [[package]] name = "pygments" version = "2.18.0" @@ -901,6 +988,56 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] +[[package]] +name = "sqlalchemy" +version = "2.0.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(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')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/65/9cbc9c4c3287bed2499e05033e207473504dc4df999ce49385fb1f8b058a/sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5", size = 9574485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/4e/5a67963fd7cbc1beb8bd2152e907419f4c940ef04600b10151a751fe9e06/SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c", size = 2093782 }, + { url = "https://files.pythonhosted.org/packages/b3/24/30e33b6389ebb5a17df2a4243b091bc709fb3dfc9a48c8d72f8e037c943d/SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71", size = 2084180 }, + { url = "https://files.pythonhosted.org/packages/10/1e/70e9ed2143a27065246be40f78637ad5160ea0f5fd32f8cab819a31ff54d/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff", size = 3202469 }, + { url = "https://files.pythonhosted.org/packages/b4/5f/95e0ed74093ac3c0db6acfa944d4d8ac6284ef5e1136b878a327ea1f975a/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d", size = 3202464 }, + { url = "https://files.pythonhosted.org/packages/91/95/2cf9b85a6bc2ee660e40594dffe04e777e7b8617fd0c6d77a0f782ea96c9/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb", size = 3139508 }, + { url = "https://files.pythonhosted.org/packages/92/ea/f0c01bc646456e4345c0fb5a3ddef457326285c2dc60435b0eb96b61bf31/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8", size = 3159837 }, + { url = "https://files.pythonhosted.org/packages/a6/93/c8edbf153ee38fe529773240877bf1332ed95328aceef6254288f446994e/SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f", size = 2064529 }, + { url = "https://files.pythonhosted.org/packages/b1/03/d12b7c1d36fd80150c1d52e121614cf9377dac99e5497af8d8f5b2a8db64/SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959", size = 2089874 }, + { url = "https://files.pythonhosted.org/packages/b8/bf/005dc47f0e57556e14512d5542f3f183b94fde46e15ff1588ec58ca89555/SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4", size = 2092378 }, + { url = "https://files.pythonhosted.org/packages/94/65/f109d5720779a08e6e324ec89a744f5f92c48bd8005edc814bf72fbb24e5/SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855", size = 2082778 }, + { url = "https://files.pythonhosted.org/packages/60/f6/d9aa8c49c44f9b8c9b9dada1f12fa78df3d4c42aa2de437164b83ee1123c/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53", size = 3232191 }, + { url = "https://files.pythonhosted.org/packages/8a/ab/81d4514527c068670cb1d7ab62a81a185df53a7c379bd2a5636e83d09ede/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a", size = 3243044 }, + { url = "https://files.pythonhosted.org/packages/35/b4/f87c014ecf5167dc669199cafdb20a7358ff4b1d49ce3622cc48571f811c/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686", size = 3178511 }, + { url = "https://files.pythonhosted.org/packages/ea/09/badfc9293bc3ccba6ede05e5f2b44a760aa47d84da1fc5a326e963e3d4d9/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588", size = 3205147 }, + { url = "https://files.pythonhosted.org/packages/c8/60/70e681de02a13c4b27979b7b78da3058c49bacc9858c89ba672e030f03f2/SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e", size = 2062709 }, + { url = "https://files.pythonhosted.org/packages/b7/ed/f6cd9395e41bfe47dd253d74d2dfc3cab34980d4e20c8878cb1117306085/SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5", size = 2088433 }, + { url = "https://files.pythonhosted.org/packages/78/5c/236398ae3678b3237726819b484f15f5c038a9549da01703a771f05a00d6/SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef", size = 2087651 }, + { url = "https://files.pythonhosted.org/packages/a8/14/55c47420c0d23fb67a35af8be4719199b81c59f3084c28d131a7767b0b0b/SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8", size = 2078132 }, + { url = "https://files.pythonhosted.org/packages/3d/97/1e843b36abff8c4a7aa2e37f9bea364f90d021754c2de94d792c2d91405b/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b", size = 3164559 }, + { url = "https://files.pythonhosted.org/packages/7b/c5/07f18a897b997f6d6b234fab2bf31dccf66d5d16a79fe329aefc95cd7461/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2", size = 3177897 }, + { url = "https://files.pythonhosted.org/packages/b3/cd/e16f3cbefd82b5c40b33732da634ec67a5f33b587744c7ab41699789d492/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf", size = 3111289 }, + { url = "https://files.pythonhosted.org/packages/15/85/5b8a3b0bc29c9928aa62b5c91fcc8335f57c1de0a6343873b5f372e3672b/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c", size = 3139491 }, + { url = "https://files.pythonhosted.org/packages/a1/95/81babb6089938680dfe2cd3f88cd3fd39cccd1543b7cb603b21ad881bff1/SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436", size = 2060439 }, + { url = "https://files.pythonhosted.org/packages/c1/ce/5f7428df55660d6879d0522adc73a3364970b5ef33ec17fa125c5dbcac1d/SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88", size = 2084574 }, + { url = "https://files.pythonhosted.org/packages/b8/49/21633706dd6feb14cd3f7935fc00b60870ea057686035e1a99ae6d9d9d53/SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e", size = 1883787 }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/39/8641040ab0d5e1d8a1c2325ae89a01ae659fc96c61a43d158fb71c9a0bf0/sqlmodel-0.0.22.tar.gz", hash = "sha256:7d37c882a30c43464d143e35e9ecaf945d88035e20117bf5ec2834a23cbe505e", size = 116392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b1/3af5104b716c420e40a6ea1b09886cae3a1b9f4538343875f637755cae5b/sqlmodel-0.0.22-py3-none-any.whl", hash = "sha256:a1ed13e28a1f4057cbf4ff6cdb4fc09e85702621d3259ba17b3c230bfb2f941b", size = 28276 }, +] + [[package]] name = "starlette" version = "0.38.6" diff --git a/client/tests/smoke/test_image_spec.py b/client/tests/smoke/test_image_spec.py index bf6c2c22..d873b8e9 100644 --- a/client/tests/smoke/test_image_spec.py +++ b/client/tests/smoke/test_image_spec.py @@ -23,9 +23,9 @@ def test_build_image_from_yaml(): # Base image pattern = r"FROM python:3.12-slim" - assert ( - re.search(pattern, dockerfile) is not None - ), "Base image not found or incorrect" + assert re.search(pattern, dockerfile) is not None, ( + "Base image not found or incorrect" + ) # Wheel installation pattern = r"RUN .* pip install.*test\.whl" @@ -33,15 +33,15 @@ def test_build_image_from_yaml(): # Editable package install pattern = r"RUN .* pip install.*-e[ ]?[.]" - assert ( - re.search(pattern, dockerfile) is not None - ), "Editable package installation not found" + assert re.search(pattern, dockerfile) is not None, ( + "Editable package installation not found" + ) # Regular package install pattern = r"RUN .* pip install.*marker-package" - assert ( - re.search(pattern, dockerfile) is not None - ), "Marker package installation not found" + assert re.search(pattern, dockerfile) is not None, ( + "Marker package installation not found" + ) # Labels pattern = r"LABEL FOO=bar" diff --git a/docs/guide/server-deployment.md b/docs/guide/server-deployment.md index 0896f011..f50357f0 100644 --- a/docs/guide/server-deployment.md +++ b/docs/guide/server-deployment.md @@ -98,5 +98,6 @@ $ docker run \ --network host \ -v ${KUBECONFIG:-~/.kube/config}:/secrets/kubeconfig \ -e KUBECONFIG=/secrets/kubeconfig \ + -e DB_CONNECTION_STRING=sqlite:////data/jobq.db \ ghcr.io/aai-institute/jobq-server:main ```