Compare commits

..
1 Commits
Author SHA1 Message Date
Brian Bjarke JensenandCursor aebddb60a2 Migrate Python workflows to python-repositories-ci runner label.
PR Title Check / check-title (pull_request) Successful in 6s
Test Python Package / unit-tests (pull_request) Successful in 59s
Code Quality Pipeline / code-quality (pull_request) Successful in 24s
Test Python Package / integration-tests (pull_request) Successful in 1m47s
Test Python Package / coverage-report (pull_request) Successful in 16s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 22:56:38 +02:00
29 changed files with 77 additions and 158 deletions
+2 -2
View File
@@ -17,7 +17,7 @@ on:
env: env:
REGISTRY: gitea.lille-vemmelund.dk REGISTRY: gitea.lille-vemmelund.dk
REGISTRY_USER: ci-bot REGISTRY_USER: ci-bot
IMAGE: gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci IMAGE: gitea.lille-vemmelund.dk/brian/python-repositories-ci
jobs: jobs:
build-and-push: build-and-push:
@@ -37,7 +37,7 @@ jobs:
CI_RUNNER_TOKEN: ${{ secrets.CI_RUNNER_TOKEN }} CI_RUNNER_TOKEN: ${{ secrets.CI_RUNNER_TOKEN }}
run: | run: |
echo "$CI_RUNNER_TOKEN" > /tmp/uv_token echo "$CI_RUNNER_TOKEN" > /tmp/uv_token
docker build --network=host -f docker/ci/Dockerfile \ docker build -f docker/ci/Dockerfile \
--secret id=uv_token,src=/tmp/uv_token \ --secret id=uv_token,src=/tmp/uv_token \
-t "${IMAGE}:latest" \ -t "${IMAGE}:latest" \
. .
@@ -14,38 +14,12 @@ jobs:
with: with:
token: ${{ secrets.CI_RUNNER_TOKEN }} token: ${{ secrets.CI_RUNNER_TOKEN }}
- name: Sync dependencies
env:
UV_LINK_MODE: copy
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv sync --all-extras
- name: Upgrade dependencies - name: Upgrade dependencies
env: env:
UV_INDEX_GITEA_USERNAME: ci-bot UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }} UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv lock --upgrade run: uv lock --upgrade
- name: Update pre-commit hook revs
env:
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv run pre-commit autoupdate
- name: Sync upgraded dependencies
env:
UV_LINK_MODE: copy
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv sync --all-extras
- name: Smoke check
run: |
uv run ruff check .
uv run ruff format --check .
uv run mypy .
- name: Commit and push changes - name: Commit and push changes
env: env:
API_URL: ${{ vars.API_URL }} API_URL: ${{ vars.API_URL }}
-20
View File
@@ -74,23 +74,3 @@ jobs:
-H "Authorization: token ${CI_RUNNER_TOKEN}" \ -H "Authorization: token ${CI_RUNNER_TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$PAYLOAD" -d "$PAYLOAD"
# Publish here instead of relying on publish.yml tag trigger: release commits use
# [skip ci], which suppresses tag-push workflows, and Gitea does not emit release
# events for releases created by the CI bot account.
- name: Build package
if: steps.meta.outputs.bump != 'skip'
env:
UV_LINK_MODE: copy
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: |
uv sync --no-dev --frozen
uv build
- name: Publish to Gitea Package Registry
if: steps.meta.outputs.bump != 'skip'
env:
UV_PUBLISH_TOKEN: ${{ secrets.CI_RUNNER_TOKEN }}
UV_PUBLISH_URL: ${{ vars.REPOSITORY_URL }}
run: uv publish
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
UV_LINK_MODE: copy UV_LINK_MODE: copy
UV_INDEX_GITEA_USERNAME: ci-bot UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }} UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv sync --all-extras --frozen run: uv sync --frozen
- name: Run safety check - name: Run safety check
run: uv run safety check run: uv run safety check
+17 -19
View File
@@ -11,30 +11,28 @@ repos:
- id: name-tests-test - id: name-tests-test
- id: check-merge-conflict - id: check-merge-conflict
# Python tooling via uv (versions pinned in uv.lock) # Python linting and formatting with Ruff
- repo: local - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.8
hooks: hooks:
- id: ruff-check - id: ruff-check
name: ruff check args: [--fix]
entry: uv run ruff check --fix
language: system
types: [python]
- id: ruff-format - id: ruff-format
name: ruff format
entry: uv run ruff format # Static type checking with mypy
language: system - repo: https://github.com/pre-commit/mirrors-mypy
types: [python] rev: v1.8.0
hooks:
- id: mypy - id: mypy
name: mypy additional_dependencies:
entry: uv run mypy . - types-redis
language: system
types: [python] # Python syntax modernization with pyupgrade
pass_filenames: false - repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade - id: pyupgrade
name: pyupgrade args: ["--py312-plus"]
entry: uv run pyupgrade --py312-plus
language: system
types: [python]
# Formatting for Markdown, JSON, and YAML with Prettier # Formatting for Markdown, JSON, and YAML with Prettier
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
+2 -2
View File
@@ -198,8 +198,8 @@ Releases are automated when a pull request is merged to `main`. CI reads the **m
1. Open a PR targeting `main` (see [`.gitea/PULL_REQUEST_TEMPLATE.md`](.gitea/PULL_REQUEST_TEMPLATE.md)). 1. Open a PR targeting `main` (see [`.gitea/PULL_REQUEST_TEMPLATE.md`](.gitea/PULL_REQUEST_TEMPLATE.md)).
2. If the PR changes files under `python_repositories/`, the title **must** start with a version bump prefix (enforced by CI). 2. If the PR changes files under `python_repositories/`, the title **must** start with a version bump prefix (enforced by CI).
3. On merge, [`release.yml`](.gitea/workflows/release.yml) bumps [`pyproject.toml`](pyproject.toml) and syncs [`uv.lock`](uv.lock), commits, tags `vX.Y.Z`, creates a Gitea release with auto-generated notes, pushes the tag, and publishes the package to the Gitea Package Registry. 3. On merge, [`release.yml`](.gitea/workflows/release.yml) bumps [`pyproject.toml`](pyproject.toml) and syncs [`uv.lock`](uv.lock), commits, tags `vX.Y.Z`, creates a Gitea release with auto-generated notes, and pushes the tag.
4. [`publish.yml`](.gitea/workflows/publish.yml) handles manual `v*.*.*` tag pushes only (automated releases publish from `release.yml` because release commits use `[skip ci]`, which suppresses tag-push workflows). 4. [`publish.yml`](.gitea/workflows/publish.yml) builds and publishes the package to the Gitea Package Registry.
Docs-, CI-, and test-only PRs do not need a prefix and will not trigger a release. Docs-, CI-, and test-only PRs do not need a prefix and will not trigger a release.
+1 -1
View File
@@ -5,10 +5,10 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
curl \ curl \
docker.io \
git \ git \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
COPY --from=docker:27-cli /usr/local/bin/docker /usr/local/bin/docker
COPY --from=ghcr.io/astral-sh/uv:0.7.0 /uv /usr/local/bin/uv COPY --from=ghcr.io/astral-sh/uv:0.7.0 /uv /usr/local/bin/uv
ENV UV_PYTHON_INSTALL_DIR=/opt/uv-python \ ENV UV_PYTHON_INSTALL_DIR=/opt/uv-python \
+5 -5
View File
@@ -14,10 +14,10 @@ Built from [`docker/ci/Dockerfile`](../docker/ci/Dockerfile):
- Docker CLI (integration tests via testcontainers) - Docker CLI (integration tests via testcontainers)
- Dev dependencies from `uv.lock` (`uv sync --all-extras --no-install-project`) - Dev dependencies from `uv.lock` (`uv sync --all-extras --no-install-project`)
Published to the Gitea container registry (owner segment must be lowercase for Docker): Published to the Gitea container registry:
- `gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci:latest` - `gitea.lille-vemmelund.dk/brian/python-repositories-ci:latest`
- `gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci:YYYYMMDDHHmm` (timestamped rollback tag) - `gitea.lille-vemmelund.dk/brian/python-repositories-ci:YYYYMMDDHHmm` (timestamped rollback tag)
## Rebuild triggers ## Rebuild triggers
@@ -108,7 +108,7 @@ homelab-platform
[`platform/gitea-runners/values.yaml`](https://gitea.lille-vemmelund.dk/LilleVemmelund/homelab-platform/src/branch/main/platform/gitea-runners/values.yaml): [`platform/gitea-runners/values.yaml`](https://gitea.lille-vemmelund.dk/LilleVemmelund/homelab-platform/src/branch/main/platform/gitea-runners/values.yaml):
```yaml ```yaml
python-repositories-ci:docker://gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci:latest python-repositories-ci:docker://gitea.lille-vemmelund.dk/brian/python-repositories-ci:latest
``` ```
After label changes, roll runner pods so they re-register with Gitea. After label changes, roll runner pods so they re-register with Gitea.
@@ -137,7 +137,7 @@ bash scripts/ci/build-ci-image.sh --push
``` ```
Run from the repo root on the branch you want to test. Runners pull Run from the repo root on the branch you want to test. Runners pull
`gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci:latest` from the registry; `gitea.lille-vemmelund.dk/brian/python-repositories-ci:latest` from the registry;
they do not care which git branch built it. they do not care which git branch built it.
Override registry settings if needed: Override registry settings if needed:
+5 -17
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "python-repositories" name = "python-repositories"
version = "2.0.4" version = "2.0.2"
description = "Various python repository interfaces exposed as a python package." description = "Various python repository interfaces exposed as a python package."
authors = [ authors = [
{ name = "Brian Bjarke Jensen", email = "schnitzelen@gmail.com" } { name = "Brian Bjarke Jensen", email = "schnitzelen@gmail.com" }
@@ -60,18 +60,6 @@ name = "gitea"
url = "https://gitea.lille-vemmelund.dk/api/packages/brian/pypi/simple/" url = "https://gitea.lille-vemmelund.dk/api/packages/brian/pypi/simple/"
explicit = true explicit = true
[tool.ruff]
line-length = 88
target-version = "py312"
[tool.ruff.lint]
extend-select = ["I"]
[tool.ruff.lint.isort]
known-first-party = ["python_repositories", "tests"]
combine-as-imports = true
force-sort-within-sections = true
[tool.mypy] [tool.mypy]
python_version = "3.12" python_version = "3.12"
warn_return_any = true # nudge to use stricter types warn_return_any = true # nudge to use stricter types
@@ -99,12 +87,12 @@ ignore_missing_imports = true
[dependency-groups] [dependency-groups]
dev = [ dev = [
"mypy>=2.1.0", "mypy>=1.17.1",
"pre-commit>=4.6.0", "pre-commit>=4.3.0",
"pytest>=8.4.1", "pytest>=8.4.1",
"pytest-cov>=7.0.0", "pytest-cov>=7.0.0",
"pyupgrade>=3.21.2", "pyupgrade>=3.20.0",
"ruff>=0.15.20", "ruff>=0.12.11",
"safety>=3.6.0", "safety>=3.6.0",
"testcontainers>=4.13.0", "testcontainers>=4.13.0",
"types-redis>=4.6.0.20241004", "types-redis>=4.6.0.20241004",
@@ -2,8 +2,8 @@
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod
import time import time
from abc import abstractmethod
from typing import Self from typing import Self
import structlog import structlog
@@ -73,7 +73,7 @@ class ConnectionAwareAdapter(ConnectionAwareInterface, ContextAwareInterface):
self._invalidate_health_cache() self._invalidate_health_cache()
return return
if self._is_client_ready() and self.is_connected(): if self._is_client_ready() and self.is_connected():
self.logger.info("Already connected", connection_name=self.connection_name) self.logger.info(f"Already connected to {self.connection_name}")
return return
self.disconnect() self.disconnect()
self._establish_connection() self._establish_connection()
+6 -12
View File
@@ -1,7 +1,6 @@
"""Definition of MinioAdapter class.""" """Definition of MinioAdapter class."""
from __future__ import annotations from __future__ import annotations
from io import BytesIO from io import BytesIO
from python_repositories.adapters.connection_aware_adapter import ( from python_repositories.adapters.connection_aware_adapter import (
@@ -89,7 +88,7 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
raise ConnectionError( raise ConnectionError(
f"Bucket '{bucket}' does not exist on Minio at {endpoint}" f"Bucket '{bucket}' does not exist on Minio at {endpoint}"
) )
self.logger.info("Creating bucket", bucket=bucket) self.logger.info(f"Creating bucket '{bucket}'")
client.make_bucket(bucket) client.make_bucket(bucket)
self._client = client self._client = client
self._bucket_name = bucket self._bucket_name = bucket
@@ -139,7 +138,7 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
content_type=content_type, content_type=content_type,
) )
self.logger.debug( self.logger.debug(
"Put object", object_name=object_name, bucket=self._bucket_name f"Put object '{object_name}' into bucket '{self._bucket_name}'"
) )
def get(self, object_name: str) -> BytesIO | None: def get(self, object_name: str) -> BytesIO | None:
@@ -164,15 +163,13 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
buffer.write(chunk) buffer.write(chunk)
buffer.seek(0) buffer.seek(0)
self.logger.debug( self.logger.debug(
"Got object", object_name=object_name, bucket=self._bucket_name f"Got object '{object_name}' from bucket '{self._bucket_name}'"
) )
return buffer return buffer
except minio.S3Error as exc: except minio.S3Error as exc:
if exc.code == "NoSuchKey": if exc.code == "NoSuchKey":
self.logger.warning( self.logger.warning(
"Object not found", f"Object '{object_name}' not found in bucket '{self._bucket_name}'"
object_name=object_name,
bucket=self._bucket_name,
) )
return None return None
raise raise
@@ -196,7 +193,7 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
object_name=object_name, object_name=object_name,
) )
self.logger.debug( self.logger.debug(
"Deleted object", object_name=object_name, bucket=self._bucket_name f"Deleted object '{object_name}' from bucket '{self._bucket_name}'"
) )
def list_objects(self, prefix: str = "") -> list[str]: def list_objects(self, prefix: str = "") -> list[str]:
@@ -218,9 +215,6 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
obj.object_name for obj in objects if obj.object_name is not None obj.object_name for obj in objects if obj.object_name is not None
] ]
self.logger.debug( self.logger.debug(
"Listed objects", f"Listed {len(object_names)} object(s) in bucket '{self._bucket_name}' with prefix '{prefix}'"
count=len(object_names),
bucket=self._bucket_name,
prefix=prefix,
) )
return object_names return object_names
@@ -1,7 +1,6 @@
"""Definition of RedisAdapter class.""" """Definition of RedisAdapter class."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Iterator from collections.abc import Iterator
from typing import Any, cast from typing import Any, cast
@@ -102,7 +101,7 @@ class RedisAdapter(JsonRepositoryInterface, ConnectionAwareAdapter):
assert self._client is not None assert self._client is not None
# Set data # Set data
self._client.json().set(key, self.path, data) self._client.json().set(key, self.path, data)
self.logger.debug("Set key", key=key, data_keys=list(data.keys())) self.logger.debug(f"Set {key} to {data}")
def get(self, key: str) -> dict[str, Any] | None: def get(self, key: str) -> dict[str, Any] | None:
"""Get a JSON object from Redis.""" """Get a JSON object from Redis."""
@@ -117,7 +116,7 @@ class RedisAdapter(JsonRepositoryInterface, ConnectionAwareAdapter):
dict[str, Any] | None, dict[str, Any] | None,
self._client.json().get(key), self._client.json().get(key),
) )
self.logger.debug("Got value", key=key, found=data is not None) self.logger.debug(f"Got {data} from {key}")
return data return data
def delete(self, key: str) -> None: def delete(self, key: str) -> None:
@@ -130,7 +129,7 @@ class RedisAdapter(JsonRepositoryInterface, ConnectionAwareAdapter):
assert self._client is not None assert self._client is not None
# Delete data # Delete data
self._client.json().delete(key) self._client.json().delete(key)
self.logger.debug("Deleted key", key=key) self.logger.debug(f"Deleted {key}")
def _validate_pattern(self, pattern: str) -> None: def _validate_pattern(self, pattern: str) -> None:
if not isinstance(pattern, str) or len(pattern) == 0: if not isinstance(pattern, str) or len(pattern) == 0:
@@ -148,7 +147,7 @@ class RedisAdapter(JsonRepositoryInterface, ConnectionAwareAdapter):
self._client.keys(pattern), self._client.keys(pattern),
) )
keys: list[str] = [key.decode(self.encoding) for key in keys_raw] keys: list[str] = [key.decode(self.encoding) for key in keys_raw]
self.logger.debug("Listed keys", pattern=pattern, count=len(keys)) self.logger.debug(f"Got {keys} matching {pattern}")
return keys return keys
def scan_keys( def scan_keys(
+1 -1
View File
@@ -2,8 +2,8 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
import os import os
from dataclasses import dataclass
from python_utils import check_env from python_utils import check_env
+1 -1
View File
@@ -2,8 +2,8 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
import os import os
from dataclasses import dataclass
from python_utils import check_env from python_utils import check_env
@@ -1,7 +1,7 @@
"""Definition of JsonRepositoryInterface abstract base class.""" """Definition of JsonRepositoryInterface abstract base class."""
from abc import ABC, abstractmethod
from collections.abc import Iterator from collections.abc import Iterator
from abc import ABC, abstractmethod
from typing import Any from typing import Any
+4 -4
View File
@@ -15,7 +15,7 @@
# Optional overrides: # Optional overrides:
# REGISTRY=gitea.lille-vemmelund.dk # REGISTRY=gitea.lille-vemmelund.dk
# REGISTRY_USER=ci-bot # REGISTRY_USER=ci-bot
# IMAGE_OWNER=lillevemmelund # IMAGE_OWNER=brian
# IMAGE_NAME=python-repositories-ci # IMAGE_NAME=python-repositories-ci
set -euo pipefail set -euo pipefail
@@ -24,7 +24,7 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
REGISTRY="${REGISTRY:-gitea.lille-vemmelund.dk}" REGISTRY="${REGISTRY:-gitea.lille-vemmelund.dk}"
REGISTRY_USER="${REGISTRY_USER:-ci-bot}" REGISTRY_USER="${REGISTRY_USER:-ci-bot}"
IMAGE_OWNER="${IMAGE_OWNER:-lillevemmelund}" IMAGE_OWNER="${IMAGE_OWNER:-brian}"
IMAGE_NAME="${IMAGE_NAME:-python-repositories-ci}" IMAGE_NAME="${IMAGE_NAME:-python-repositories-ci}"
IMAGE="${IMAGE:-${REGISTRY}/${IMAGE_OWNER}/${IMAGE_NAME}}" IMAGE="${IMAGE:-${REGISTRY}/${IMAGE_OWNER}/${IMAGE_NAME}}"
@@ -52,7 +52,7 @@ cd "$REPO_ROOT"
if [[ "$MODE" == "local" ]]; then if [[ "$MODE" == "local" ]]; then
echo "=== Building local CI image (no push): python-repositories-ci:local ===" echo "=== Building local CI image (no push): python-repositories-ci:local ==="
docker build --network=host -f docker/ci/Dockerfile \ docker build -f docker/ci/Dockerfile \
--secret "id=uv_token,src=${TOKEN_FILE}" \ --secret "id=uv_token,src=${TOKEN_FILE}" \
-t python-repositories-ci:local \ -t python-repositories-ci:local \
. .
@@ -64,7 +64,7 @@ echo "=== Logging in to ${REGISTRY} as ${REGISTRY_USER} ==="
echo "$CI_RUNNER_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin echo "$CI_RUNNER_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
echo "=== Building ${IMAGE}:latest ===" echo "=== Building ${IMAGE}:latest ==="
docker build --network=host -f docker/ci/Dockerfile \ docker build -f docker/ci/Dockerfile \
--secret "id=uv_token,src=${TOKEN_FILE}" \ --secret "id=uv_token,src=${TOKEN_FILE}" \
-t "${IMAGE}:latest" \ -t "${IMAGE}:latest" \
. .
+3 -9
View File
@@ -11,16 +11,15 @@ set -euo pipefail
BRANCH="${BRANCH:-renovate/auto-deps-update}" BRANCH="${BRANCH:-renovate/auto-deps-update}"
BASE_BRANCH="${BASE_BRANCH:-main}" BASE_BRANCH="${BASE_BRANCH:-main}"
LOCKFILE="${LOCKFILE:-uv.lock}" LOCKFILE="${LOCKFILE:-uv.lock}"
PRE_COMMIT_CONFIG="${PRE_COMMIT_CONFIG:-.pre-commit-config.yaml}"
if git diff --quiet "$LOCKFILE" && git diff --quiet "$PRE_COMMIT_CONFIG"; then if git diff --quiet "$LOCKFILE"; then
echo "No dependency updates available." echo "No dependency updates available."
exit 0 exit 0
fi fi
git config user.name "CI Bot" git config user.name "CI Bot"
git config user.email "ci-bot@example.com" git config user.email "ci-bot@example.com"
git add "$LOCKFILE" "$PRE_COMMIT_CONFIG" git add "$LOCKFILE"
git commit -m "chore(deps): update dependencies [automated]" git commit -m "chore(deps): update dependencies [automated]"
git remote set-url origin "https://x-access-token:${CI_RUNNER_TOKEN}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git" git remote set-url origin "https://x-access-token:${CI_RUNNER_TOKEN}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git"
@@ -49,12 +48,7 @@ print(
json.dumps( json.dumps(
{ {
"title": "chore(deps): update dependencies", "title": "chore(deps): update dependencies",
"body": ( "body": "This PR was created automatically to update dependencies.",
"This PR was created automatically to update dependencies.\n\n"
"- `uv.lock`: upgraded Python dependencies\n"
"- `.pre-commit-config.yaml`: updated remote hook revs via "
"`pre-commit autoupdate`"
),
"head": os.environ["BRANCH"], "head": os.environ["BRANCH"],
"base": os.environ["BASE_BRANCH"], "base": os.environ["BASE_BRANCH"],
} }
+2 -2
View File
@@ -1,13 +1,13 @@
"""Integration tests configuration.""" """Integration tests configuration."""
from collections.abc import Generator
import logging import logging
from collections.abc import Generator
from typing import Any, cast from typing import Any, cast
from minio import Minio
import pytest import pytest
import redis import redis
import structlog import structlog
from minio import Minio
from testcontainers.core.container import DockerContainer from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import WaitStrategy, WaitStrategyTarget from testcontainers.core.waiting_utils import WaitStrategy, WaitStrategyTarget
from testcontainers.minio import MinioContainer from testcontainers.minio import MinioContainer
+1 -1
View File
@@ -5,8 +5,8 @@ from io import BytesIO
import os import os
import random import random
from minio import Minio
import pytest import pytest
from minio import Minio
from python_repositories.examples.artifact_object_repository import ( from python_repositories.examples.artifact_object_repository import (
ArtifactObjectRepository, ArtifactObjectRepository,
+8 -11
View File
@@ -2,13 +2,13 @@
from collections.abc import Generator from collections.abc import Generator
from dataclasses import replace from dataclasses import replace
from io import BytesIO
import logging import logging
import random import random
from io import BytesIO
from unittest.mock import MagicMock from unittest.mock import MagicMock
from minio import Minio, S3Error
import pytest import pytest
from minio import Minio, S3Error
from urllib3.response import BaseHTTPResponse from urllib3.response import BaseHTTPResponse
from python_repositories.adapters.minio_adapter import MinioAdapter from python_repositories.adapters.minio_adapter import MinioAdapter
@@ -102,8 +102,7 @@ def test_should_log_info_when_already_connected(
"""Test that the MinioAdapter logs info when connect is called while already connected.""" """Test that the MinioAdapter logs info when connect is called while already connected."""
with caplog.at_level(logging.INFO): with caplog.at_level(logging.INFO):
minio_adapter.connect() minio_adapter.connect()
assert "Already connected" in caplog.text assert "Already connected to Minio" in caplog.text
assert "Minio" in caplog.text
def test_should_raise_connection_error_when_unable_to_connect() -> None: def test_should_raise_connection_error_when_unable_to_connect() -> None:
@@ -151,8 +150,7 @@ def test_connect_creates_bucket_when_create_bucket_if_missing_enabled(
with caplog.at_level(logging.INFO): with caplog.at_level(logging.INFO):
adapter.connect() adapter.connect()
assert "Creating bucket" in caplog.text assert f"Creating bucket '{bucket_name}'" in caplog.text
assert bucket_name in caplog.text
def test_should_log_error_on_exception_during_exit( def test_should_log_error_on_exception_during_exit(
@@ -235,11 +233,10 @@ def test_should_log_warning_when_getting_nonexistent_object(
with caplog.at_level("WARNING"): with caplog.at_level("WARNING"):
result = adapter.get(object_name) result = adapter.get(object_name)
assert result is None assert result is None
assert "Object not found" in caplog.text assert (
assert object_name in caplog.text f"Object '{object_name}' not found in bucket '{adapter._bucket_name}'"
bucket_name = adapter._bucket_name in caplog.text
assert bucket_name is not None )
assert bucket_name in caplog.text
def test_should_reraise_s3error_other_than_no_such_key() -> None: def test_should_reraise_s3error_other_than_no_such_key() -> None:
+1 -2
View File
@@ -56,8 +56,7 @@ def test_should_log_info_when_already_connected(
"""Test that the RedisAdapter logs info when connect is called while already connected.""" """Test that the RedisAdapter logs info when connect is called while already connected."""
with caplog.at_level(logging.INFO): with caplog.at_level(logging.INFO):
redis_adapter.connect() redis_adapter.connect()
assert "Already connected" in caplog.text assert "Already connected to Redis" in caplog.text
assert "Redis" in caplog.text
def test_should_raise_connection_error_when_unable_to_connect() -> None: def test_should_raise_connection_error_when_unable_to_connect() -> None:
+2 -2
View File
@@ -3,12 +3,12 @@
from __future__ import annotations from __future__ import annotations
import builtins import builtins
from collections.abc import Callable, Mapping, Sequence
import importlib import importlib
import os import os
from pathlib import Path
import subprocess import subprocess
import sys import sys
from collections.abc import Callable, Mapping, Sequence
from pathlib import Path
from types import ModuleType from types import ModuleType
from unittest.mock import patch from unittest.mock import patch
+1 -1
View File
@@ -4,9 +4,9 @@ from __future__ import annotations
from unittest.mock import MagicMock from unittest.mock import MagicMock
from minio import Minio
import pytest import pytest
import redis import redis
from minio import Minio
from python_repositories.adapters.minio_adapter import MinioAdapter from python_repositories.adapters.minio_adapter import MinioAdapter
from python_repositories.adapters.redis_adapter import RedisAdapter from python_repositories.adapters.redis_adapter import RedisAdapter
@@ -1,7 +1,6 @@
"""Unit tests for ConnectionAwareInterface.""" """Unit tests for ConnectionAwareInterface."""
import pytest import pytest
from python_repositories.interfaces.connection_aware_interface import ( from python_repositories.interfaces.connection_aware_interface import (
ConnectionAwareInterface, ConnectionAwareInterface,
) )
@@ -3,7 +3,6 @@
from __future__ import annotations from __future__ import annotations
import pytest import pytest
from python_repositories.interfaces.context_aware_interface import ContextAwareInterface from python_repositories.interfaces.context_aware_interface import ContextAwareInterface
@@ -3,7 +3,6 @@
from typing import Any from typing import Any
import pytest import pytest
from python_repositories.interfaces.json_repository_interface import ( from python_repositories.interfaces.json_repository_interface import (
JsonRepositoryInterface, JsonRepositoryInterface,
) )
+1 -1
View File
@@ -5,8 +5,8 @@ from __future__ import annotations
from dataclasses import replace from dataclasses import replace
from unittest.mock import MagicMock from unittest.mock import MagicMock
from minio import Minio, S3Error
import pytest import pytest
from minio import Minio, S3Error
from urllib3.response import BaseHTTPResponse from urllib3.response import BaseHTTPResponse
from python_repositories.adapters.minio_adapter import MinioAdapter from python_repositories.adapters.minio_adapter import MinioAdapter
@@ -3,7 +3,6 @@
from io import BytesIO from io import BytesIO
import pytest import pytest
from python_repositories.interfaces.object_repository_interface import ( from python_repositories.interfaces.object_repository_interface import (
ObjectRepositoryInterface, ObjectRepositoryInterface,
) )
Generated
+6 -6
View File
@@ -1,5 +1,5 @@
version = 1 version = 1
revision = 2 revision = 3
requires-python = ">=3.12" requires-python = ">=3.12"
resolution-markers = [ resolution-markers = [
"python_full_version >= '3.15'", "python_full_version >= '3.15'",
@@ -1056,7 +1056,7 @@ wheels = [
[[package]] [[package]]
name = "python-repositories" name = "python-repositories"
version = "2.0.4" version = "2.0.2"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "python-dotenv" }, { name = "python-dotenv" },
@@ -1097,12 +1097,12 @@ provides-extras = ["redis", "minio"]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "mypy", specifier = ">=2.1.0" }, { name = "mypy", specifier = ">=1.17.1" },
{ name = "pre-commit", specifier = ">=4.6.0" }, { name = "pre-commit", specifier = ">=4.3.0" },
{ name = "pytest", specifier = ">=8.4.1" }, { name = "pytest", specifier = ">=8.4.1" },
{ name = "pytest-cov", specifier = ">=7.0.0" }, { name = "pytest-cov", specifier = ">=7.0.0" },
{ name = "pyupgrade", specifier = ">=3.21.2" }, { name = "pyupgrade", specifier = ">=3.20.0" },
{ name = "ruff", specifier = ">=0.15.20" }, { name = "ruff", specifier = ">=0.12.11" },
{ name = "safety", specifier = ">=3.6.0" }, { name = "safety", specifier = ">=3.6.0" },
{ name = "testcontainers", specifier = ">=4.13.0" }, { name = "testcontainers", specifier = ">=4.13.0" },
{ name = "types-redis", specifier = ">=4.6.0.20241004" }, { name = "types-redis", specifier = ">=4.6.0.20241004" },