Compare commits

...
8 Commits
Author SHA1 Message Date
CI Bot fa4ff1f03c chore: release v2.0.3 [skip ci] 2026-07-10 12:06:36 +00:00
brian 0a39151ba0 Merge pull request '[patch] Use structured fields for adapter debug and info logs' (#46) from cursor/structured-adapter-logging into main
Release on merge to main / release (push) Successful in 11s
Code Quality Pipeline / code-quality (push) Successful in 23s
Test Python Package / coverage-report (push) Successful in 8s
Test Python Package / unit-tests (push) Successful in 10s
Test Python Package / integration-tests (push) Successful in 23s
Reviewed-on: LilleVemmelund/python-repositories#46
2026-07-10 14:06:23 +02:00
Brian Bjarke JensenandCursor 82a702db0c Fix mypy error in Minio adapter log assertion.
Test Python Package / unit-tests (pull_request) Successful in 10s
Code Quality Pipeline / code-quality (pull_request) Successful in 33s
Test Python Package / integration-tests (pull_request) Successful in 20s
PR Title Check / check-title (pull_request) Successful in 42s
Test Python Package / coverage-report (pull_request) Successful in 14s
Narrow optional bucket name before checking it appears in captured log text.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 14:04:36 +02:00
Brian Bjarke JensenandCursor b886a7c147 Use structured fields for adapter debug and info logs.
PR Title Check / check-title (pull_request) Successful in 6s
Test Python Package / unit-tests (pull_request) Successful in 9s
Code Quality Pipeline / code-quality (pull_request) Failing after 14s
Test Python Package / integration-tests (pull_request) Successful in 1m2s
Test Python Package / coverage-report (pull_request) Successful in 11s
Replace f-string log messages with structlog keyword fields so events aggregate cleanly and Redis payloads are not logged verbatim.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 14:00:34 +02:00
brian 4518a93a3a Merge pull request 'Migrate Python workflows to python-repositories-ci runner' (#41) from cursor/migrate-python-repositories-ci into main
Release on merge to main / release (push) Successful in 6s
Test Python Package / unit-tests (push) Successful in 15s
Code Quality Pipeline / code-quality (push) Successful in 28s
Test Python Package / integration-tests (push) Successful in 1m20s
Test Python Package / coverage-report (push) Successful in 11s
Reviewed-on: LilleVemmelund/python-repositories#41
2026-07-10 13:48:49 +02:00
Brian Bjarke JensenandCursor a8b7b59484 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 13s
Code Quality Pipeline / code-quality (pull_request) Successful in 23s
Test Python Package / integration-tests (pull_request) Successful in 26s
Test Python Package / coverage-report (pull_request) Successful in 16s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 13:46:10 +02:00
brian 0e6941dccf Merge pull request 'Use lowercase CI image registry owner for Docker compatibility' (#45) from cursor/fix-ci-registry-owner-lowercase into main
Release on merge to main / release (push) Successful in 8s
Test Python Package / integration-tests (push) Successful in 29s
Code Quality Pipeline / code-quality (push) Successful in 48s
Test Python Package / unit-tests (push) Successful in 19s
Test Python Package / coverage-report (push) Successful in 15s
Reviewed-on: LilleVemmelund/python-repositories#45
2026-07-10 13:13:01 +02:00
Brian Bjarke JensenandCursor a6428ccd29 Use lowercase CI image registry owner for Docker compatibility.
PR Title Check / check-title (pull_request) Successful in 7s
Test Python Package / unit-tests (pull_request) Successful in 1m12s
Code Quality Pipeline / code-quality (pull_request) Successful in 1m31s
Test Python Package / integration-tests (pull_request) Successful in 2m25s
Test Python Package / coverage-report (pull_request) Successful in 22s
Docker requires lowercase repository names; Gitea maps lillevemmelund to the LilleVemmelund org.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 13:08:28 +02:00
16 changed files with 80 additions and 114 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ on:
env:
REGISTRY: gitea.lille-vemmelund.dk
REGISTRY_USER: ci-bot
IMAGE: gitea.lille-vemmelund.dk/LilleVemmelund/python-repositories-ci
IMAGE: gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci
jobs:
build-and-push:
+5 -11
View File
@@ -8,23 +8,17 @@ on:
jobs:
code-quality:
runs-on: ubuntu-latest
runs-on: python-repositories-ci
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: .python-version
- name: Install uv
run: pip install uv
- name: Install dependencies
- name: Sync dependencies
env:
UV_LINK_MODE: copy
run: uv sync --all-extras
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv sync --all-extras --frozen
- name: Type check with mypy
run: uv run mypy .
+4 -9
View File
@@ -7,22 +7,17 @@ on:
jobs:
update-check:
runs-on: ubuntu-latest
runs-on: python-repositories-ci
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.CI_RUNNER_TOKEN }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: .python-version
- name: Install uv
run: pip install uv
- name: Upgrade dependencies
env:
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv lock --upgrade
- name: Commit and push changes
+9 -10
View File
@@ -9,25 +9,24 @@ on:
jobs:
build-and-publish:
runs-on: ubuntu-latest
runs-on: python-repositories-ci
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: .python-version
- name: Install uv
run: pip install uv
- name: Update version in pyproject.toml to match tag
env:
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: scripts/ci/bump-version.sh --from-tag "${GITHUB_REF##*/}"
- name: Build package
env:
UV_LINK_MODE: copy
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: |
uv sync --no-dev
uv sync --no-dev --frozen
uv build
- name: Publish to Gitea Package Registry
+4 -11
View File
@@ -8,7 +8,7 @@ on:
jobs:
release:
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
runs-on: ubuntu-latest
runs-on: python-repositories-ci
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -22,19 +22,12 @@ jobs:
COMMIT_MSG: ${{ github.event.head_commit.message }}
run: scripts/ci/parse-merge-commit.sh "$COMMIT_MSG"
- name: Set up Python
if: steps.meta.outputs.bump != 'skip'
uses: actions/setup-python@v5
with:
python-version-file: .python-version
- name: Install uv
if: steps.meta.outputs.bump != 'skip'
run: pip install uv
- name: Bump version
if: steps.meta.outputs.bump != 'skip'
id: bump
env:
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: scripts/ci/bump-version.sh "${{ steps.meta.outputs.bump }}"
- name: Generate release notes
+5 -11
View File
@@ -7,23 +7,17 @@ on:
jobs:
safety:
runs-on: ubuntu-latest
runs-on: python-repositories-ci
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: .python-version
- name: Install uv
run: pip install uv
- name: Install dependencies
- name: Sync dependencies
env:
UV_LINK_MODE: copy
run: uv sync
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv sync --frozen
- name: Run safety check
run: uv run safety check
+15 -33
View File
@@ -8,23 +8,17 @@ on:
jobs:
unit-tests:
runs-on: ubuntu-latest
runs-on: python-repositories-ci
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: .python-version
- name: Install uv
run: pip install uv
- name: Install dependencies
- name: Sync dependencies
env:
UV_LINK_MODE: copy
run: uv sync --all-extras
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv sync --all-extras --frozen
- name: Run unit tests
run: |
@@ -43,23 +37,17 @@ jobs:
compression-level: 0
integration-tests:
runs-on: ubuntu-latest
runs-on: python-repositories-ci
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: .python-version
- name: Install uv
run: pip install uv
- name: Install dependencies
- name: Sync dependencies
env:
UV_LINK_MODE: copy
run: uv sync --all-extras
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv sync --all-extras --frozen
- name: Verify Docker
run: docker info
@@ -83,24 +71,18 @@ jobs:
coverage-report:
# Merges unit + integration coverage and enforces fail_under from pyproject.toml.
needs: [unit-tests, integration-tests]
runs-on: ubuntu-latest
runs-on: python-repositories-ci
if: github.event_name == 'pull_request' || github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: .python-version
- name: Install uv
run: pip install uv
- name: Install dependencies
- name: Sync dependencies
env:
UV_LINK_MODE: copy
run: uv sync --all-extras
UV_INDEX_GITEA_USERNAME: ci-bot
UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
run: uv sync --all-extras --frozen
- name: Download unit coverage
uses: https://github.com/christopherHX/gitea-download-artifact@v4
+5 -5
View File
@@ -14,10 +14,10 @@ Built from [`docker/ci/Dockerfile`](../docker/ci/Dockerfile):
- Docker CLI (integration tests via testcontainers)
- Dev dependencies from `uv.lock` (`uv sync --all-extras --no-install-project`)
Published to the Gitea container registry:
Published to the Gitea container registry (owner segment must be lowercase for Docker):
- `gitea.lille-vemmelund.dk/LilleVemmelund/python-repositories-ci:latest`
- `gitea.lille-vemmelund.dk/LilleVemmelund/python-repositories-ci:YYYYMMDDHHmm` (timestamped rollback tag)
- `gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci:latest`
- `gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci:YYYYMMDDHHmm` (timestamped rollback tag)
## 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):
```yaml
python-repositories-ci:docker://gitea.lille-vemmelund.dk/LilleVemmelund/python-repositories-ci:latest
python-repositories-ci:docker://gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci:latest
```
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
`gitea.lille-vemmelund.dk/LilleVemmelund/python-repositories-ci:latest` from the registry;
`gitea.lille-vemmelund.dk/lillevemmelund/python-repositories-ci:latest` from the registry;
they do not care which git branch built it.
Override registry settings if needed:
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "python-repositories"
version = "2.0.2"
version = "2.0.3"
description = "Various python repository interfaces exposed as a python package."
authors = [
{ name = "Brian Bjarke Jensen", email = "schnitzelen@gmail.com" }
@@ -73,7 +73,7 @@ class ConnectionAwareAdapter(ConnectionAwareInterface, ContextAwareInterface):
self._invalidate_health_cache()
return
if self._is_client_ready() and self.is_connected():
self.logger.info(f"Already connected to {self.connection_name}")
self.logger.info("Already connected", connection_name=self.connection_name)
return
self.disconnect()
self._establish_connection()
+11 -6
View File
@@ -88,7 +88,7 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
raise ConnectionError(
f"Bucket '{bucket}' does not exist on Minio at {endpoint}"
)
self.logger.info(f"Creating bucket '{bucket}'")
self.logger.info("Creating bucket", bucket=bucket)
client.make_bucket(bucket)
self._client = client
self._bucket_name = bucket
@@ -138,7 +138,7 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
content_type=content_type,
)
self.logger.debug(
f"Put object '{object_name}' into bucket '{self._bucket_name}'"
"Put object", object_name=object_name, bucket=self._bucket_name
)
def get(self, object_name: str) -> BytesIO | None:
@@ -163,13 +163,15 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
buffer.write(chunk)
buffer.seek(0)
self.logger.debug(
f"Got object '{object_name}' from bucket '{self._bucket_name}'"
"Got object", object_name=object_name, bucket=self._bucket_name
)
return buffer
except minio.S3Error as exc:
if exc.code == "NoSuchKey":
self.logger.warning(
f"Object '{object_name}' not found in bucket '{self._bucket_name}'"
"Object not found",
object_name=object_name,
bucket=self._bucket_name,
)
return None
raise
@@ -193,7 +195,7 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
object_name=object_name,
)
self.logger.debug(
f"Deleted object '{object_name}' from bucket '{self._bucket_name}'"
"Deleted object", object_name=object_name, bucket=self._bucket_name
)
def list_objects(self, prefix: str = "") -> list[str]:
@@ -215,6 +217,9 @@ class MinioAdapter(ObjectRepositoryInterface, ConnectionAwareAdapter):
obj.object_name for obj in objects if obj.object_name is not None
]
self.logger.debug(
f"Listed {len(object_names)} object(s) in bucket '{self._bucket_name}' with prefix '{prefix}'"
"Listed objects",
count=len(object_names),
bucket=self._bucket_name,
prefix=prefix,
)
return object_names
@@ -101,7 +101,7 @@ class RedisAdapter(JsonRepositoryInterface, ConnectionAwareAdapter):
assert self._client is not None
# Set data
self._client.json().set(key, self.path, data)
self.logger.debug(f"Set {key} to {data}")
self.logger.debug("Set key", key=key, data_keys=list(data.keys()))
def get(self, key: str) -> dict[str, Any] | None:
"""Get a JSON object from Redis."""
@@ -116,7 +116,7 @@ class RedisAdapter(JsonRepositoryInterface, ConnectionAwareAdapter):
dict[str, Any] | None,
self._client.json().get(key),
)
self.logger.debug(f"Got {data} from {key}")
self.logger.debug("Got value", key=key, found=data is not None)
return data
def delete(self, key: str) -> None:
@@ -129,7 +129,7 @@ class RedisAdapter(JsonRepositoryInterface, ConnectionAwareAdapter):
assert self._client is not None
# Delete data
self._client.json().delete(key)
self.logger.debug(f"Deleted {key}")
self.logger.debug("Deleted key", key=key)
def _validate_pattern(self, pattern: str) -> None:
if not isinstance(pattern, str) or len(pattern) == 0:
@@ -147,7 +147,7 @@ class RedisAdapter(JsonRepositoryInterface, ConnectionAwareAdapter):
self._client.keys(pattern),
)
keys: list[str] = [key.decode(self.encoding) for key in keys_raw]
self.logger.debug(f"Got {keys} matching {pattern}")
self.logger.debug("Listed keys", pattern=pattern, count=len(keys))
return keys
def scan_keys(
+2 -2
View File
@@ -15,7 +15,7 @@
# Optional overrides:
# REGISTRY=gitea.lille-vemmelund.dk
# REGISTRY_USER=ci-bot
# IMAGE_OWNER=LilleVemmelund
# IMAGE_OWNER=lillevemmelund
# IMAGE_NAME=python-repositories-ci
set -euo pipefail
@@ -24,7 +24,7 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
REGISTRY="${REGISTRY:-gitea.lille-vemmelund.dk}"
REGISTRY_USER="${REGISTRY_USER:-ci-bot}"
IMAGE_OWNER="${IMAGE_OWNER:-LilleVemmelund}"
IMAGE_OWNER="${IMAGE_OWNER:-lillevemmelund}"
IMAGE_NAME="${IMAGE_NAME:-python-repositories-ci}"
IMAGE="${IMAGE:-${REGISTRY}/${IMAGE_OWNER}/${IMAGE_NAME}}"
+9 -6
View File
@@ -102,7 +102,8 @@ def test_should_log_info_when_already_connected(
"""Test that the MinioAdapter logs info when connect is called while already connected."""
with caplog.at_level(logging.INFO):
minio_adapter.connect()
assert "Already connected to Minio" in caplog.text
assert "Already connected" in caplog.text
assert "Minio" in caplog.text
def test_should_raise_connection_error_when_unable_to_connect() -> None:
@@ -150,7 +151,8 @@ def test_connect_creates_bucket_when_create_bucket_if_missing_enabled(
with caplog.at_level(logging.INFO):
adapter.connect()
assert f"Creating bucket '{bucket_name}'" in caplog.text
assert "Creating bucket" in caplog.text
assert bucket_name in caplog.text
def test_should_log_error_on_exception_during_exit(
@@ -233,10 +235,11 @@ def test_should_log_warning_when_getting_nonexistent_object(
with caplog.at_level("WARNING"):
result = adapter.get(object_name)
assert result is None
assert (
f"Object '{object_name}' not found in bucket '{adapter._bucket_name}'"
in caplog.text
)
assert "Object not found" in caplog.text
assert object_name in caplog.text
bucket_name = adapter._bucket_name
assert bucket_name is not None
assert bucket_name in caplog.text
def test_should_reraise_s3error_other_than_no_such_key() -> None:
+2 -1
View File
@@ -56,7 +56,8 @@ def test_should_log_info_when_already_connected(
"""Test that the RedisAdapter logs info when connect is called while already connected."""
with caplog.at_level(logging.INFO):
redis_adapter.connect()
assert "Already connected to Redis" in caplog.text
assert "Already connected" in caplog.text
assert "Redis" in caplog.text
def test_should_raise_connection_error_when_unable_to_connect() -> None:
Generated
+2 -2
View File
@@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 2
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.15'",
@@ -1056,7 +1056,7 @@ wheels = [
[[package]]
name = "python-repositories"
version = "2.0.2"
version = "2.0.3"
source = { editable = "." }
dependencies = [
{ name = "python-dotenv" },