From 3a052124220eff3063d497c4240fc25057dfef6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 1 Mar 2025 13:08:14 +0100 Subject: [PATCH 1/5] Upgrade dependencies, use python 3.13 --- .github/workflows/ci.yml | 2 +- Dockerfile | 2 +- README.md | 2 +- pyproject.toml | 4 +-- requirements-mypy.txt | 2 +- requirements-test.txt | 8 ++--- requirements.txt | 78 +++++++++++++++++++++------------------- 7 files changed, 51 insertions(+), 47 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1b7308..5171d61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.12"] + python-version: ["3.13"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 diff --git a/Dockerfile b/Dockerfile index dd2ebe0..dbf4a5a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12 +FROM python:3.13 LABEL maintainer="Stéphane Bidoul" diff --git a/README.md b/README.md index 58a3bbd..fb3b756 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ For running the builds: For running the controller (runboat itself): -- Python 3.12 +- Python 3.13 - sqlite3 >= 3.25 - `kubectl` - A `KUBECONFIG` or an in-cluster service account that provides access to the namespace diff --git a/pyproject.toml b/pyproject.toml index a0fabb0..c026c59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ classifiers = [ ] dependencies = [ "ansi2html", - "fastapi>=0.93", + "fastapi[all]>=0.112", "gunicorn", "httpx", "jinja2", @@ -22,7 +22,7 @@ dependencies = [ "sse-starlette", "uvicorn", ] -requires-python = "==3.12.*" +requires-python = "==3.13.*" dynamic = ["version", "description"] [project.optional-dependencies] diff --git a/requirements-mypy.txt b/requirements-mypy.txt index c69fce2..0066acc 100644 --- a/requirements-mypy.txt +++ b/requirements-mypy.txt @@ -1,4 +1,4 @@ # frozen requirements generated by pip-deepfreeze -mypy==1.10.1 +mypy==1.15.0 mypy-extensions==1.0.0 types-urllib3==1.26.25.14 diff --git a/requirements-test.txt b/requirements-test.txt index 1a0fafe..e373b28 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,9 +1,9 @@ # frozen requirements generated by pip-deepfreeze -coverage==7.5.4 +coverage==7.6.12 iniconfig==2.0.0 pluggy==1.5.0 -pytest==8.2.2 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-asyncio==0.25.3 +pytest-cov==6.0.0 pytest-dotenv==0.5.2 pytest-mock==3.14.0 diff --git a/requirements.txt b/requirements.txt index 5a88cee..d8cd09d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,55 +1,59 @@ # frozen requirements generated by pip-deepfreeze annotated-types==0.7.0 ansi2html==1.9.2 -anyio==4.4.0 -cachetools==5.3.3 -certifi==2024.7.4 -charset-normalizer==3.3.2 -click==8.1.7 -dnspython==2.6.1 +anyio==4.8.0 +cachetools==5.5.2 +certifi==2025.1.31 +charset-normalizer==3.4.1 +click==8.1.8 +dnspython==2.7.0 +durationpy==0.9 email-validator==2.2.0 -fastapi==0.111.0 -fastapi-cli==0.0.4 -google-auth==2.31.0 -gunicorn==22.0.0 +fastapi==0.115.10 +fastapi-cli==0.0.7 +google-auth==2.38.0 +gunicorn==23.0.0 h11==0.14.0 -httpcore==1.0.5 -httptools==0.6.1 -httpx==0.27.0 -idna==3.7 -jinja2==3.1.4 -kubernetes==30.1.0 +httpcore==1.0.7 +httptools==0.6.4 +httpx==0.28.1 +idna==3.10 +itsdangerous==2.2.0 +jinja2==3.1.5 +kubernetes==32.0.1 markdown-it-py==3.0.0 -markupsafe==2.1.5 +markupsafe==3.0.2 mdurl==0.1.2 oauthlib==3.2.2 -orjson==3.10.6 -packaging==24.1 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pydantic-settings==2.3.4 -pygments==2.18.0 +orjson==3.10.15 +packaging==24.2 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.10.6 +pydantic-core==2.27.2 +pydantic-extra-types==2.10.2 +pydantic-settings==2.8.1 +pygments==2.19.1 python-dateutil==2.9.0.post0 python-dotenv==1.0.1 -python-multipart==0.0.9 -pyyaml==6.0.1 +python-multipart==0.0.20 +pyyaml==6.0.2 requests==2.32.3 requests-oauthlib==2.0.0 -rich==13.7.1 +rich==13.9.4 +rich-toolkit==0.13.2 rsa==4.9 shellingham==1.5.4 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 -sse-starlette==2.1.2 -starlette==0.37.2 -typer==0.12.3 +sse-starlette==2.2.1 +starlette==0.46.0 +typer==0.15.2 typing-extensions==4.12.2 ujson==5.10.0 -urllib3==2.2.2 -uvicorn==0.30.1 -uvloop==0.19.0 -watchfiles==0.22.0 +urllib3==2.3.0 +uvicorn==0.34.0 +uvloop==0.21.0 +watchfiles==1.0.4 websocket-client==1.8.0 -websockets==12.0 +websockets==15.0 From 90a2994dbe008b2020942bbf0c76e41be13bdab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 23 Nov 2024 18:04:36 +0100 Subject: [PATCH 2/5] pre-commit autoupdate --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7323d59..23c4abf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,14 +2,14 @@ default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-toml - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 + rev: v0.9.9 hooks: - id: ruff - id: ruff-format From 3fdaec3b47b5cceee29df39a6bfe25d9d0de3d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 23 Nov 2024 18:04:51 +0100 Subject: [PATCH 3/5] ci: add fastapi linter --- pyproject.toml | 1 + src/runboat/webhooks.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c026c59..ef226f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ select = [ "T20", # flake8-print "PLE", # pylint errors "RUF", + "FAST", # fastapi ] [tool.ruff.lint.mccabe] diff --git a/src/runboat/webhooks.py b/src/runboat/webhooks.py index ec81d26..f3e5cbc 100644 --- a/src/runboat/webhooks.py +++ b/src/runboat/webhooks.py @@ -1,5 +1,6 @@ import hmac import logging +from typing import Annotated from fastapi import APIRouter, BackgroundTasks, Header, Request @@ -31,8 +32,8 @@ def _verify_github_signature( async def receive_payload( background_tasks: BackgroundTasks, request: Request, - x_github_event: str = Header(...), - x_hub_signature_256: str | None = Header(None), + x_github_event: Annotated[str, Header(...)], + x_hub_signature_256: Annotated[str | None, Header(None)], ) -> None: body = await request.body() if not _verify_github_signature( From 5040f8dee7cc5020bf636ae03bf59586c0ce8466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 1 Mar 2025 13:11:07 +0100 Subject: [PATCH 4/5] Fix FastAPI Header annotation --- src/runboat/webhooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runboat/webhooks.py b/src/runboat/webhooks.py index f3e5cbc..61c8f25 100644 --- a/src/runboat/webhooks.py +++ b/src/runboat/webhooks.py @@ -33,7 +33,7 @@ async def receive_payload( background_tasks: BackgroundTasks, request: Request, x_github_event: Annotated[str, Header(...)], - x_hub_signature_256: Annotated[str | None, Header(None)], + x_hub_signature_256: Annotated[str | None, Header(...)] = None, ) -> None: body = await request.body() if not _verify_github_signature( From 56918f7472851f8e54207db2fff99992c8a8e2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 1 Mar 2025 13:14:55 +0100 Subject: [PATCH 5/5] ruff auto fixes --- src/runboat/api.py | 2 +- src/runboat/app.py | 2 +- src/runboat/k8s.py | 12 ++++++------ src/runboat/utils.py | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/runboat/api.py b/src/runboat/api.py index 018681a..8e141c0 100644 --- a/src/runboat/api.py +++ b/src/runboat/api.py @@ -224,7 +224,7 @@ class BuildEventSource: return self.queue.put_nowait(self._serialize(event, build)) - async def events(self) -> AsyncGenerator[str, None]: + async def events(self) -> AsyncGenerator[str]: for build in controller.db.search( repo=self.repo, target_branch=self.target_branch, diff --git a/src/runboat/app.py b/src/runboat/app.py index 20f052e..8734299 100644 --- a/src/runboat/app.py +++ b/src/runboat/app.py @@ -7,7 +7,7 @@ from . import __version__, api, controller, k8s, webhooks, webui @asynccontextmanager -async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: +async def lifespan(app: FastAPI) -> AsyncGenerator[None]: await k8s.load_kube_config() await controller.controller.start() yield diff --git a/src/runboat/k8s.py b/src/runboat/k8s.py index e1dbbbe..9233343 100644 --- a/src/runboat/k8s.py +++ b/src/runboat/k8s.py @@ -86,7 +86,7 @@ class WatchException(Exception): def _watch( list_method: Callable[..., Any], *args: Any, **kwargs: Any -) -> Generator[tuple[str | None, Any], None, None]: +) -> Generator[tuple[str | None, Any]]: while True: try: # perform a first query @@ -121,7 +121,7 @@ def _watch( @sync_to_async_iterator -def watch_deployments() -> Generator[V1Deployment, None, None]: +def watch_deployments() -> Generator[V1Deployment]: appsv1 = client.AppsV1Api() yield from _watch( appsv1.list_namespaced_deployment, namespace=settings.build_namespace @@ -129,7 +129,7 @@ def watch_deployments() -> Generator[V1Deployment, None, None]: @sync_to_async_iterator -def watch_jobs() -> Generator[V1Job, None, None]: +def watch_jobs() -> Generator[V1Job]: batchv1 = client.BatchV1Api() yield from _watch(batchv1.list_namespaced_job, namespace=settings.build_namespace) @@ -178,7 +178,7 @@ def make_deployment_vars( @contextmanager -def _get_kubefiles_path(kubefiles_path: Path | None) -> Generator[Path, None, None]: +def _get_kubefiles_path(kubefiles_path: Path | None) -> Generator[Path]: if kubefiles_path: yield kubefiles_path else: @@ -191,7 +191,7 @@ def _get_kubefiles_path(kubefiles_path: Path | None) -> Generator[Path, None, No @contextmanager def _render_kubefiles( kubefiles_path: Path | None, deployment_vars: DeploymentVars -) -> Generator[Path, None, None]: +) -> Generator[Path]: with ( _get_kubefiles_path(kubefiles_path) as kubefiles_path, tempfile.TemporaryDirectory() as tmp_dir, @@ -299,7 +299,7 @@ def log(build_name: str, job_kind: DeploymentMode | None) -> str | None: container=pod.metadata.annotations.get( "kubectl.kubernetes.io/default-container" ), - tail_lines=None if job_kind else None, + tail_lines=None, follow=False, ), ) diff --git a/src/runboat/utils.py b/src/runboat/utils.py index 361c283..753b4d7 100644 --- a/src/runboat/utils.py +++ b/src/runboat/utils.py @@ -28,8 +28,8 @@ def sync_to_async(func: Callable[P, R]) -> Callable[P, Awaitable[R]]: def sync_to_async_iterator( - iterator_func: Callable[P, Generator[R, None, None]], -) -> Callable[P, AsyncGenerator[R, None]]: + iterator_func: Callable[P, Generator[R]], +) -> Callable[P, AsyncGenerator[R]]: @sync_to_async def async_next(iterator: Iterator[R]) -> R: try: @@ -38,11 +38,11 @@ def sync_to_async_iterator( raise StopAsyncIteration() from e @sync_to_async - def async_iterator_func(*args: Any, **kwargs: Any) -> Generator[R, None, None]: + def async_iterator_func(*args: Any, **kwargs: Any) -> Generator[R]: return iterator_func(*args, **kwargs) @wraps(iterator_func) - async def inner(*args: Any, **kwargs: Any) -> AsyncGenerator[R, None]: + async def inner(*args: Any, **kwargs: Any) -> AsyncGenerator[R]: iterator = await async_iterator_func(*args, **kwargs) while True: try: