Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 33 additions & 14 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,30 +1,49 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
labels:
- "dependencies"
- "type: misc"
commit-message:
prefix: "ci(github)"
schedule:
interval: "weekly"
- package-ecosystem: "pip"
day: "monday"
time: "08:00"
groups:
actions:
patterns:
- "*"
- package-ecosystem: "uv"
target-branch: "main"
directory: "/"
labels:
- "dependencies"
- "type: misc"
commit-message:
prefix: "build(deps)"
schedule:
interval: "daily"
allow:
- dependency-name: "ruff"
- dependency-name: "mypy"
- dependency-name: "pre-commit"
- dependency-name: "fastapi"
- dependency-name: "sqlmodel"
- dependency-name: "uvicorn"
- dependency-name: "pytest"
time: "08:00"
groups:
security:
patterns:
- "PyJWT"
- "passlib"
- "bcrypt"
quality:
patterns:
- "ruff"
- "ty"
- "prek"
- "types-*"
- "sqlalchemy-stubs"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
time: "08:00"
allow:
- dependency-name: "ghcr.io/astral-sh/uv"
- dependency-name: "localstack/localstack"
2 changes: 1 addition & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
- changed-files:
- any-glob-to-any-file:
- pyproject.toml
- poetry.lock
- uv.lock
- client/pyproject.toml
- client/setup.py

Expand Down
143 changes: 143 additions & 0 deletions .github/verify_deps_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Copyright (C) 2025-2026, François-Guillaume Fernandez.

# This program is licensed under the Apache License 2.0.
# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0> for full license details.

# /// script
# requires-python = ">=3.11"
# dependencies = [
# "pyyaml>=6.0",
# ]
# ///

import logging
import re
import sys
import tomllib
from pathlib import Path
from typing import Any

import yaml

DOCKERFILES = ["src/Dockerfile", "src/Dockerfile.test"]
PRECOMMIT_CONFIG = ".pre-commit-config.yaml"
PYPROJECTS = ["./pyproject.toml", "./client/pyproject.toml"]
TRACKED_DEPS = ("uv", "ruff", "ty", "prek")

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler(sys.stdout)
log_formatter = logging.Formatter("%(levelname)s: %(message)s")
stream_handler.setFormatter(log_formatter)
logger.addHandler(stream_handler)


def parse_dep_str(dep_str: str) -> dict[str, str]:
dep = dep_str.split(";", maxsplit=1)[0].strip()
if " @ " in dep:
pkg, version = dep.split(" @ ", maxsplit=1)
return {"pkg": pkg.strip(), "version": version.strip()}

version_match = re.search(r"(===|==|~=|!=|<=|>=|<|>).*$", dep)
version_idx = version_match.start() if version_match else len(dep)
pkg = dep[:version_idx].strip()
pkg = pkg.split("[", maxsplit=1)[0].strip()
return {"pkg": pkg, "version": dep[version_idx:].strip()}


def add_dep(deps: dict[str, list[dict[str, str]]], dep: str, file: str, group: str) -> None:
parsed = parse_dep_str(dep)
pkg = parsed["pkg"].lower()
if pkg in deps and parsed["version"]:
deps[pkg].append({"file": file, "version": parsed["version"], "group": group})


def add_pyproject_deps(deps: dict[str, list[dict[str, str]]], pyproject_path: str) -> None:
with Path(pyproject_path).open("rb") as f:
pyproject: dict[str, Any] = tomllib.load(f)

project = pyproject.get("project", {})
if isinstance(project, dict):
dependencies = project.get("dependencies", [])
if isinstance(dependencies, list):
for dep in dependencies:
add_dep(deps, dep, pyproject_path, "[project.dependencies]")

optional_dependencies = project.get("optional-dependencies", {})
if isinstance(optional_dependencies, dict):
for group, dependencies in optional_dependencies.items():
if isinstance(dependencies, list):
for dep in dependencies:
add_dep(deps, dep, pyproject_path, f"[project.optional-dependencies.{group}]")

dependency_groups = pyproject.get("dependency-groups", {})
if isinstance(dependency_groups, dict):
for group, dependencies in dependency_groups.items():
if isinstance(dependencies, list):
for dep in dependencies:
add_dep(deps, dep, pyproject_path, f"[dependency-groups.{group}]")


def main() -> None:
deps: dict[str, list[dict[str, str]]] = {dep: [] for dep in TRACKED_DEPS}

for dockerfile in DOCKERFILES:
dockerfile_content = Path(dockerfile).read_text(encoding="utf-8")
uv_versions = re.findall(r"ghcr\.io/astral-sh/uv:(\d+\.\d+\.\d+)", dockerfile_content)
deps["uv"].extend({"file": dockerfile, "version": f"=={version}", "group": "docker"} for version in uv_versions)

if Path(PRECOMMIT_CONFIG).exists():
with Path(PRECOMMIT_CONFIG).open("r", encoding="utf-8") as f:
precommit = yaml.safe_load(f)
for repo in precommit.get("repos", []):
repo_url = repo.get("repo")
if repo_url == "https://github.com/astral-sh/uv-pre-commit":
deps["uv"].append({
"file": PRECOMMIT_CONFIG,
"version": f"=={repo['rev'].lstrip('v')}",
"group": "pre-commit",
})
elif repo_url == "https://github.com/charliermarsh/ruff-pre-commit":
deps["ruff"].append({
"file": PRECOMMIT_CONFIG,
"version": f"=={repo['rev'].lstrip('v')}",
"group": "pre-commit",
})

for pyproject_path in PYPROJECTS:
add_pyproject_deps(deps, pyproject_path)

for workflow_file in Path(".github/workflows").glob("*.yml"):
with workflow_file.open("r", encoding="utf-8") as f:
workflow = yaml.safe_load(f) or {}
env = workflow.get("env", {})
if "UV_VERSION" in env:
deps["uv"].append({
"file": str(workflow_file),
"version": f"=={env['UV_VERSION'].lstrip('v')}",
"group": "workflow",
})

troubles = []
for dep, versions in deps.items():
if not versions:
troubles.append(f"{dep}: no tracked version found")
continue

version_values = {entry["version"] for entry in versions}
if len(version_values) > 1:
inv_dict = {version: set() for version in version_values}
for version in versions:
inv_dict[version["version"]].add(f"{version['file']} {version['group']}")
troubles.extend([
f"{dep}:",
"\n".join(f"- {version}: {', '.join(sorted(files))}" for version, files in sorted(inv_dict.items())),
])

if troubles:
raise AssertionError("Some dependencies are out of sync:\n\n" + "\n".join(troubles))
logger.info("All dependencies are in sync!")


if __name__ == "__main__":
main()
10 changes: 3 additions & 7 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ on:

env:
PYTHON_VERSION: "3.11"
UV_VERSION: "0.5.13"
POETRY_VERSION: "1.8.3"

UV_VERSION: "0.11.14"

jobs:
docker:
Expand All @@ -21,11 +19,9 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: abatilo/actions-poetry@v4
- uses: astral-sh/setup-uv@v7
with:
poetry-version: ${{ env.POETRY_VERSION }}
- name: Resolve dependencies
run: poetry export -f requirements.txt --without-hashes --output requirements.txt
version: ${{ env.UV_VERSION }}
- name: Build, run & check docker
env:
SUPERADMIN_LOGIN: dummy_login
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
branches: main

env:
UV_VERSION: "0.5.13"
UV_VERSION: "0.11.14"

jobs:
gh-pages:
Expand Down Expand Up @@ -38,6 +38,6 @@ jobs:
uses: JamesIves/github-pages-deploy-action@v4.7.4
with:
branch: gh-pages
folder: 'client/docs/build'
commit-message: '[skip ci] Documentation updates'
folder: "client/docs/build"
commit-message: "[skip ci] Documentation updates"
clean: true
8 changes: 4 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ name: publish

on:
release:
types: [ published ]
types: [published]

env:
PYTHON_VERSION: "3.11"
UV_VERSION: "0.5.13"
UV_VERSION: "0.11.14"

jobs:
pypi:
Expand Down Expand Up @@ -35,7 +35,7 @@ jobs:

pypi-check:
if: "!github.event.release.prerelease"
needs: pypi-publish-client
needs: pypi
runs-on: ${{ matrix.os }}
strategy:
matrix:
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:

conda-check:
if: "!github.event.release.prerelease"
needs: conda-publish-client
needs: conda
runs-on: ${{ matrix.os }}
strategy:
matrix:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/pull_requests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches: main

env:
UV_VERSION: "0.5.13"
UV_VERSION: "0.11.14"

jobs:
docs-client:
Expand Down Expand Up @@ -36,6 +36,6 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v6
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
- uses: actions/labeler@v6
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
10 changes: 4 additions & 6 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ on:
push:
branches: main
tags:
- 'v*'
- "v*"

env:
BACKEND_IMAGE_NAME: alert-api
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_LOGIN }}
PYTHON_VERSION: "3.11"
POETRY_VERSION: "1.8.3"
UV_VERSION: "0.11.14"

jobs:
docker:
Expand All @@ -20,11 +20,9 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: abatilo/actions-poetry@v4
- uses: astral-sh/setup-uv@v7
with:
poetry-version: ${{ env.POETRY_VERSION }}
- name: Resolve dependencies
run: poetry export -f requirements.txt --without-hashes --output requirements.txt
version: ${{ env.UV_VERSION }}
- name: Resolve image tag
run: |
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
Expand Down
12 changes: 2 additions & 10 deletions .github/workflows/scripts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ on:

env:
PYTHON_VERSION: "3.11"
UV_VERSION: "0.5.13"
POETRY_VERSION: "1.8.3"
UV_VERSION: "0.11.14"

jobs:
end-to-end:
Expand All @@ -20,16 +19,9 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: abatilo/actions-poetry@v4
with:
poetry-version: ${{ env.POETRY_VERSION }}
- uses: astral-sh/setup-uv@v7
with:
version: ${{ env.UV_VERSION }}
- name: Resolve dependencies
run: |
poetry export -f requirements.txt --without-hashes --output requirements.txt
uv pip install --system -r scripts/requirements.txt
- name: Run the backend & the test
env:
SUPERUSER_LOGIN: dummy_login
Expand All @@ -39,4 +31,4 @@ jobs:
POSTGRES_DB: dummy_pg_db
run: |
docker compose -f docker-compose.dev.yml up -d --build --wait
python scripts/test_e2e.py
uv run --group e2e python scripts/test_e2e.py
Loading
Loading