Skip to content

Conversation

@Alex-Burmak
Copy link
Owner

@Alex-Burmak Alex-Burmak commented May 7, 2025

Summary by Sourcery

Migrate project to use uv for Python dependency management and build system, update project configuration, and modernize development workflows

Enhancements:

  • Simplify project configuration and dependency management
  • Improve Python version compatibility
  • Modernize project build and test infrastructure

Build:

  • Replace setup.py with pyproject.toml for modern Python packaging
  • Update Makefile to use uv for dependency management and task execution
  • Add support for Python 3.13 and update Python version matrix in CI

CI:

  • Replace actions/setup-python with astral-sh/setup-uv
  • Update GitHub Actions workflows to use uv
  • Update ClickHouse version matrix in test workflows

Chores:

  • Remove legacy configuration files like .isort.cfg and requirements.txt
  • Update Docker and CI configurations to use new build system

Summary by CodeRabbit

  • New Features

    • Added a pyproject.toml for unified project configuration, dependency management, and build system.
    • Introduced explicit Python version management via .python-version (now set to 3.10).
  • Improvements

    • Refactored Makefile and Dockerfiles to use the uv tool for consistent Python environment management.
    • Updated CI workflows to modernize Python setup, add Python 3.13, and update ClickHouse versions.
    • Increased minimum supported Python version to 3.9 across configuration files and packaging metadata.
    • Enhanced Docker and packaging scripts for simplified and more robust builds.
  • Bug Fixes

    • Improved error handling for specific threading issues in pipeline execution.
    • Ensured compatibility with both docker-compose and docker compose commands in integration tests.
  • Style

    • Standardized and reordered import statements and improved code formatting for readability.
  • Chores

    • Removed legacy configuration and dependency files (requirements.txt, requirements-dev.txt, setup.py, .isort.cfg, .codespellrc).
    • Updated .gitignore to exclude .venv/ and stop ignoring .python-version.
    • Cleaned up and standardized contributor information in the AUTHORS file.

@sourcery-ai
Copy link

sourcery-ai bot commented May 7, 2025

Reviewer's Guide

This pull request revamps the project's Python development and build infrastructure by migrating to uv for dependency management and task execution, and pyproject.toml (with hatchling) for package configuration and building. The Makefile has been extensively modified to leverage uv for operations like linting, testing, and building wheels. CI workflows in GitHub Actions were updated to use astral-sh/setup-uv, and the Dockerfile now installs the application from a uv-built wheel. Legacy configuration files (e.g., setup.py, requirements.txt) have been removed in favor of the new pyproject.toml standard.

Class Diagram: LockManager Update

classDiagram
  class LockManager {
    -_lock_conf: dict
    -_process_lockfile_path: str
    -_process_zk_lockfile_path: str
    -_zk_lock: Lock
    -_file: IO
    -_lock_id: str
    +__init__(config, name)
    +__enter__()
    +__exit__()
    -_flock()
    -_zk_flock()
  }
  note for LockManager "Changes in _flock():\n- File opened in 'r+' mode instead of 'r'.\n- flock() now uses the file object directly (self._file) instead of a file descriptor (self._fd).\n- _fd attribute removed.\nChange in __exit__():\n- self._file.close() used instead of os.close(self._fd)."
Loading

Flow Diagram: Makefile Tasks Execution via uv

flowchart TD
    subgraph Makefile Task Execution with uv
        User["User/CI"] -- "invokes 'make <target>' (e.g. lint, test-unit, build)" --> Makefile["Makefile"]

        Makefile -- "'make lint' triggers multiple 'uv run ...'" --> UV_Lint["uv run isort<BR>uv run black<BR>uv run ruff<BR>..."]
        UV_Lint --> Tools_Lint["Linters"]

        Makefile -- "'make test-unit' triggers 'uv run py.test ...'" --> UV_Test["uv run py.test ..."]
        UV_Test --> PyTest["pytest"]

        Makefile -- "'make build' triggers 'uv build ...'" --> UV_Build["uv build --python <version>"]
        UV_Build --> Build_Process["Python packaging (wheel/sdist)"]

        Makefile -- "'make setup' (dependency for many tasks)" --> UV_Check["uv check / uv sync (implicitly via uv run)"]
        UV_Check --> Dep_Management["Dependency Management"]
    end
Loading

File-Level Changes

Change Details Files
Migrated project to uv and pyproject.toml for dependency management and builds.
  • Replaced setup.py and requirements.txt with pyproject.toml using hatchling as the build backend.
  • Updated Makefile to use uv for dependency installation, environment management, linting, testing, and building Python packages.
  • Introduced uv.lock for precise dependency locking.
  • Added a .python-version file to specify the default Python version, utilized by the Makefile.
  • Consolidated configurations for tools like isort and codespell into pyproject.toml.
Makefile
pyproject.toml
uv.lock
.python-version
setup.py
requirements.txt
requirements-dev.txt
.isort.cfg
.codespellrc
Adapted CI workflows to use uv and updated Python version matrix.
  • Switched GitHub Actions from actions/setup-python to astral-sh/setup-uv.
  • Modified CI test execution commands to be run via uv.
  • Updated Python version matrix in CI: added Python 3.13, removed Python 3.7 and 3.8.
  • Removed the NO_VENV environment variable from CI configuration.
.github/workflows/workflow.yml
.github/workflows/test_clickhouse_version.yml
Revised Dockerfile to use uv-built wheel for application installation.
  • Dockerfile now copies and installs the application from a pre-built Python wheel (.whl file from the dist directory) instead of installing from source and requirements.txt.
images/clickhouse/Dockerfile
Implemented minor code improvements and a bug workaround.
  • Added error handling in ch_backup/storage/async_pipeline/pipelines.py to gracefully manage a known stopit library threading issue.
  • Refactored file locking in ch_backup/logic/lock_manager.py to use the file object directly with flock instead of a file descriptor.
  • Updated tests/integration/modules/compose.py to dynamically use docker compose if docker-compose is not found.
  • Applied minor code formatting updates, such as grouping context managers and reordering imports in various test and source files.
ch_backup/storage/async_pipeline/pipelines.py
ch_backup/logic/lock_manager.py
tests/integration/modules/compose.py
tests/unit/test_pipeline.py
tests/unit/test_backup_tables.py
ch_backup/storage/async_pipeline/base_pipeline/exec_pool.py
ch_backup/storage/async_pipeline/pipeline_builder.py
Updated MyPy static analysis configuration.
  • Specified python_version = 3.9 in mypy.ini to align MyPy checks with the project's minimum Python version.
mypy.ini

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sonarqubecloud
Copy link

sonarqubecloud bot commented May 7, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
E Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@Alex-Burmak
Copy link
Owner Author

@sourcery-ai review

@Alex-Burmak
Copy link
Owner Author

@sourcery-ai summary

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Alex-Burmak - I've reviewed your changes - here's some feedback:

  • For the 'Invalid thread ID' ValueError in pipelines.py, consider adding an inline comment linking to any upstream stopit/pypeln issue for context.
  • The LockManager now opens its lockfile in r+ mode for LOCK_SH; clarify if this change from r mode was intentional, as r is usually sufficient.
Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- python: "3.13"
clickhouse: "latest"
runs-on: ubuntu-22.04
steps:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Propagating PYTHON_VERSION in CI workflows is a good practice.

Apply PYTHON_VERSION=${{ matrix.python }} to every step (unit and integration) so uv runs with the intended Python version, and verify consistency across commands to prevent mismatches.

Suggested implementation:

    - uses: astral-sh/setup-uv@v5
      with:
        python-version: ${{ matrix.python }}

Make sure that the setup action (astral-sh/setup-uv@v5) supports accepting a "python-version" parameter.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Alex-Burmak - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • The server.key file is generated in the Dockerfile, which is a security risk. (link)

  • The server.key file is copied into the Docker image, which is a security risk. (link)

  • Consider creating a tracking issue for the stopit library workaround in pipelines.py to explore a long-term fix.

Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🔴 Security: 2 blocking issues
  • 🟡 Testing: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +200 to +203
try:
itr = iter(pipeline)
exhaust_iterator(itr)
except ValueError as e:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (bug_risk): Exception handling in pipeline iteration.

Ensure stopit reliably raises ValueError with 'Invalid thread ID'; otherwise, other ValueErrors might be inadvertently skipped.

Comment on lines +167 to +170
with (
open(original_file_path, "rb") as orig_fobj,
open(backward_file_name, "rb") as res_fobj,
):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Add tests for pipelines.run() error handling to cover new ValueError logic.

Please add unit tests for these cases:

  1. When exhaust_iterator raises ValueError("Invalid thread ID"), confirm it’s caught, logs a warning with exc_info=True, and isn’t re-raised.
  2. When it raises ValueError with a different message, confirm it’s re-raised.
  3. (Optional) The normal path where no exception occurs.
    You’ll need to mock exhaust_iterator or the underlying iterator to simulate each scenario.

RUN openssl genrsa -out ${CH_TMP_DIR}/ssl/server.key 2048 && \
openssl req -new -key ${CH_TMP_DIR}/ssl/server.key -out ${CH_TMP_DIR}/ssl/server.csr \
COPY staging/images/{{ instance_name }}/ssl/ /var/tmp/ssl/
RUN openssl genrsa -out /var/tmp/ssl/server.key 2048 && \
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): The server.key file is generated in the Dockerfile, which is a security risk.

The server.key file should not be generated in the Dockerfile. Instead, it should be generated outside of the Dockerfile and then copied into the Dockerfile using the COPY command.

cp ${CH_TMP_DIR}/ssl/server.key /etc/clickhouse-server/ && \
cp ${CH_TMP_DIR}/ssl/server.crt /etc/clickhouse-server/ && \
cp ${CH_TMP_DIR}/ssl/rootCACert.pem /etc/clickhouse-server/allCAs.pem && \
cp /var/tmp/ssl/server.key /etc/clickhouse-server/ && \
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): The server.key file is copied into the Docker image, which is a security risk.

The server.key file should not be copied into the Docker image. Instead, it should be mounted as a volume at runtime.

from tests.unit.time_mocker import TimeMocker

from ch_backup.storage.async_pipeline.base_pipeline.rate_limiter import RateLimiter
from tests.unit.time_mocker import TimeMocker
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Don't import test modules. (dont-import-test-modules)

ExplanationDon't import test modules.

Tests should be self-contained and don't depend on each other.

If a helper function is used by multiple tests,
define it in a helper module,
instead of importing one test from the other.

from tests.unit.time_mocker import TimeMocker

from ch_backup.storage.engine.s3.s3_retry import retry
from tests.unit.time_mocker import TimeMocker
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Don't import test modules. (dont-import-test-modules)

ExplanationDon't import test modules.

Tests should be self-contained and don't depend on each other.

If a helper function is used by multiple tests,
define it in a helper module,
instead of importing one test from the other.


from ch_backup.clickhouse.models import Table
from ch_backup.clickhouse.schema import rewrite_table_schema
from tests.unit.utils import parametrize
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Don't import test modules. (dont-import-test-modules)

ExplanationDon't import test modules.

Tests should be self-contained and don't depend on each other.

If a helper function is used by multiple tests,
define it in a helper module,
instead of importing one test from the other.

from ch_backup.backup_context import BackupContext
from ch_backup.clickhouse.models import Database
from ch_backup.logic.upload_part_observer import UploadPartObserver
from tests.unit.utils import parametrize
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Don't import test modules. (dont-import-test-modules)

ExplanationDon't import test modules.

Tests should be self-contained and don't depend on each other.

If a helper function is used by multiple tests,
define it in a helper module,
instead of importing one test from the other.

@Alex-Burmak
Copy link
Owner Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented May 8, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented May 8, 2025

Walkthrough

This update transitions the project’s Python environment management from manual virtual environments to the uv tool, updates the minimum Python version to 3.9, and refactors CI workflows accordingly. It removes legacy configuration and requirements files, consolidates project metadata and dependencies into a new pyproject.toml, and standardizes Docker and Makefile processes. Minor code and test formatting improvements are also included.

Changes

Files/Groups Change Summary
.codespellrc, .isort.cfg, requirements.txt, requirements-dev.txt, setup.py Deleted legacy configuration and requirements files for spell checking, import sorting, and Python dependencies. Removed setup script.
.gitignore Removed .python-version from ignore list; added .venv/ to ignore virtual environments.
.pylintrc, mypy.ini, debian/control Updated minimum Python version to 3.9 for linting, type checking, and packaging.
.python-version Added file specifying Python 3.10 for environment management.
pyproject.toml Added project metadata, dependencies, dev dependencies, build system, CLI entry point, and tool configs.
Makefile Refactored to use uv for Python environment and package management. Removed manual venv logic; updated targets and help messages; renamed and streamlined test environment targets.
.github/workflows/workflow.yml, .github/workflows/test_clickhouse_version.yml Switched from actions/setup-python to astral-sh/setup-uv. Updated test matrix (removed 3.7/3.8, added 3.13), ClickHouse versions, and environment variables.
Dockerfile-deb-build, images/clickhouse/Dockerfile Installed uv in build Dockerfile; standardized temp directories and switched from source to wheel installation in ClickHouse image. Adjusted SSL and config file handling.
AUTHORS Standardized contributor names/emails, removed duplicates, corrected spelling, and added three new contributors.
ch_backup/clickhouse/control.py, tests/integration/modules/clickhouse.py, ... (see below) Switched version parsing from pkg_resources to packaging.version in multiple modules.
ch_backup/logic/lock_manager.py Simplified lock file management: removed raw file descriptor, used file object directly, updated flock/close logic.
ch_backup/storage/async_pipeline/base_pipeline/exec_pool.py, ch_backup/storage/async_pipeline/pipeline_builder.py Added trailing commas for function call/definition style consistency.
ch_backup/storage/async_pipeline/pipelines.py Added targeted error handling for thread ID errors during pipeline iteration exhaustion.
debian/rules Replaced $(PYTHON) variable with hardcoded python3 for version checks.
tests/integration/features/backup_replicated.feature Fixed YAML indentation in test scenario.
tests/integration/modules/compose.py Made Docker Compose command detection dynamic (supports both docker-compose and docker compose).
tests/integration/modules/utils.py Switched version parsing import to packaging.version.
tests/integration/modules/zookeeper.py, tests/integration/steps/ch_backup.py, ... (see below) Added blank lines for import group separation in test files.
tests/unit/test_access.py, tests/unit/test_cli.py, ... (see below) Reordered import statements for style consistency in unit tests.
tests/unit/test_backup_tables.py, tests/unit/test_pipeline.py Refactored context manager syntax for patching and file opening in tests.

(Files grouped for brevity; see raw summary for exhaustive details.)

Sequence Diagram(s)

sequenceDiagram
    participant Developer
    participant Makefile
    participant uv
    participant Python
    participant TestRunner

    Developer->>Makefile: make test-unit
    Makefile->>uv: run --python $(PYTHON_VERSION) pytest tests/unit
    uv->>Python: Setup Python environment (3.10/.python-version)
    Python->>TestRunner: Run unit tests
    TestRunner-->>Python: Results
    Python-->>uv: Exit
    uv-->>Makefile: Exit
    Makefile-->>Developer: Test results/output
Loading
sequenceDiagram
    participant CI
    participant GitHub Actions
    participant astral-sh/setup-uv
    participant uv
    participant Python
    participant Makefile

    CI->>GitHub Actions: Trigger workflow
    GitHub Actions->>astral-sh/setup-uv: Setup environment
    astral-sh/setup-uv->>uv: Install uv tool
    uv->>Python: Setup Python environment (matrix version)
    GitHub Actions->>Makefile: Run lint/test/build steps via uv
    Makefile->>uv: Execute commands (e.g., pytest, ruff)
    uv-->>Makefile: Command output
    Makefile-->>GitHub Actions: Step results
    GitHub Actions-->>CI: Workflow status
Loading

Poem

🐇

A hop, a skip, a uv leap,
The old venvs now fast asleep.
With pyproject’s tidy charm,
And Makefile’s streamlined arm,
Our code runs clean, our tests all pass,
New friends join in—what a class!
The garden’s fresh, let’s munch some grass.

🌱✨

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (8)
tests/unit/test_cli.py (1)

5-7: Avoid importing test modules in tests
The test still imports parametrize from tests.unit.utils, which couples tests to each other. Consider moving shared utilities into a separate helper or conftest to keep tests self-contained.

tests/unit/test_retry_exponetial.py (1)

9-11: Avoid importing test modules in tests
The test continues to import TimeMocker from tests.unit.time_mocker, creating inter-test dependencies. Please extract this utility to a shared helper location to keep tests independent.

tests/unit/test_schema.py (1)

7-7: LGTM: Import order adjustment.

The import reordering is consistent with the code style changes in this pull request.

tests/unit/test_pipeline.py (1)

1-338: Missing tests for ValueError handling

Add tests for pipelines.run() error handling to cover new ValueError logic.

Please add unit tests for these cases:

  1. When exhaust_iterator raises ValueError("Invalid thread ID"), confirm it's caught, logs a warning with exc_info=True, and isn't re-raised.
  2. When it raises ValueError with a different message, confirm it's re-raised.
  3. (Optional) The normal path where no exception occurs.
    You'll need to mock exhaust_iterator or the underlying iterator to simulate each scenario.
ch_backup/storage/async_pipeline/pipelines.py (1)

200-211: Improved error handling for thread termination issue.

The added try-except block addresses a specific threading-related issue during pipeline iteration exhaustion. The change properly suppresses only the specific "Invalid thread ID" error from the stopit library while re-raising all other ValueError exceptions, improving robustness without changing core functionality.

This change directly addresses the previous review comment that noted: "Ensure stopit reliably raises ValueError with 'Invalid thread ID'; otherwise, other ValueErrors might be inadvertently skipped." The implementation correctly handles this specific case.

.github/workflows/workflow.yml (2)

19-19: Propagate Python version to setup-uv action
The astral-sh/setup-uv@v5 step in the lint job should explicitly receive the Python version from the matrix to avoid any mismatch between the runner’s default Python and the one used by uv.

Consider:

-    - uses: astral-sh/setup-uv@v5
+    - uses: astral-sh/setup-uv@v5
+      with:
+        python-version: ${{ matrix.python }}

67-67: Propagate Python version to setup-uv action in test job
Same as in the lint job, the test job’s astral-sh/setup-uv@v5 step should take python-version: ${{ matrix.python }} to guarantee consistent environments.

images/clickhouse/Dockerfile (1)

61-76: 🚨 SECURITY: Avoid generating and storing private keys in image
The Dockerfile still generates server.key inside the image and copies it into the container. This reproduces the security risk noted in previous reviews. Private keys should be generated outside the Docker build and mounted at runtime.

🧹 Nitpick comments (4)
mypy.ini (1)

2-2: Align Mypy Python version with project default
The python_version = 3.9 setting should match the version in your .python-version file (3.10) to avoid discrepancies between type-checking and runtime. Consider updating this line or deriving the version dynamically.

images/clickhouse/Dockerfile (1)

53-55: Install pre-built ch_backup wheel from /var/tmp
Switching to wheel installation is a nice improvement. Consider cleaning up /var/tmp after install to reduce image size:

 RUN cd /var/tmp/ && \
     pip3 install ch_backup*.whl && \
+    rm ch_backup*.whl && \
     mkdir -p /etc/yandex/ch-backup && \
pyproject.toml (1)

15-15: Nitpick: Verify Trove classifier for MacOS
The classifier "Operating System :: MacOS" may not match PyPI’s accepted trove list (usually "Operating System :: MacOS" vs "macOS"). It’s worth double-checking against the official classifier list.

Makefile (1)

100-108: Test targets: add explicit Python version for behave
The test-unit target correctly uses uv run --python. For test-integration, consider adding --python $(PYTHON_VERSION) to the uv run behave invocation for consistency:

-    uv run behave --show-timings --stop -D skip_setup $(BEHAVE_ARGS) @tests/integration/ch_backup.featureset
+    uv run --python $(PYTHON_VERSION) behave --show-timings --stop -D skip_setup $(BEHAVE_ARGS) @tests/integration/ch_backup.featureset
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e360f2 and 5f57891.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (44)
  • .codespellrc (0 hunks)
  • .github/workflows/test_clickhouse_version.yml (1 hunks)
  • .github/workflows/workflow.yml (2 hunks)
  • .gitignore (1 hunks)
  • .isort.cfg (0 hunks)
  • .pylintrc (1 hunks)
  • .python-version (1 hunks)
  • AUTHORS (1 hunks)
  • Dockerfile-deb-build (1 hunks)
  • Makefile (7 hunks)
  • ch_backup/clickhouse/control.py (1 hunks)
  • ch_backup/logic/lock_manager.py (2 hunks)
  • ch_backup/storage/async_pipeline/base_pipeline/exec_pool.py (1 hunks)
  • ch_backup/storage/async_pipeline/pipeline_builder.py (1 hunks)
  • ch_backup/storage/async_pipeline/pipelines.py (2 hunks)
  • debian/control (1 hunks)
  • debian/rules (1 hunks)
  • images/clickhouse/Dockerfile (2 hunks)
  • mypy.ini (1 hunks)
  • pyproject.toml (1 hunks)
  • requirements-dev.txt (0 hunks)
  • requirements.txt (0 hunks)
  • setup.py (0 hunks)
  • tests/integration/features/backup_replicated.feature (1 hunks)
  • tests/integration/modules/clickhouse.py (1 hunks)
  • tests/integration/modules/compose.py (2 hunks)
  • tests/integration/modules/utils.py (1 hunks)
  • tests/integration/modules/zookeeper.py (1 hunks)
  • tests/integration/steps/ch_backup.py (1 hunks)
  • tests/integration/steps/clickhouse.py (1 hunks)
  • tests/integration/steps/common.py (1 hunks)
  • tests/integration/steps/s3.py (1 hunks)
  • tests/integration/steps/zookeeper.py (1 hunks)
  • tests/unit/test_access.py (1 hunks)
  • tests/unit/test_backup_tables.py (1 hunks)
  • tests/unit/test_cli.py (1 hunks)
  • tests/unit/test_clickhouse_encryption.py (1 hunks)
  • tests/unit/test_control.py (1 hunks)
  • tests/unit/test_disks.py (1 hunks)
  • tests/unit/test_pipeline.py (1 hunks)
  • tests/unit/test_rate_limiter.py (1 hunks)
  • tests/unit/test_retry_exponetial.py (1 hunks)
  • tests/unit/test_schema.py (1 hunks)
  • tests/unit/test_upload_part_observer.py (1 hunks)
💤 Files with no reviewable changes (5)
  • .isort.cfg
  • .codespellrc
  • setup.py
  • requirements-dev.txt
  • requirements.txt
🧰 Additional context used
🧬 Code Graph Analysis (10)
tests/unit/test_upload_part_observer.py (1)
tests/unit/utils.py (1)
  • parametrize (16-74)
tests/unit/test_cli.py (1)
tests/unit/utils.py (1)
  • parametrize (16-74)
tests/unit/test_control.py (1)
tests/unit/utils.py (1)
  • parametrize (16-74)
tests/unit/test_retry_exponetial.py (1)
tests/unit/time_mocker.py (1)
  • TimeMocker (6-14)
tests/unit/test_clickhouse_encryption.py (1)
tests/unit/utils.py (2)
  • assert_equal (154-160)
  • parametrize (16-74)
tests/unit/test_rate_limiter.py (1)
tests/unit/time_mocker.py (1)
  • TimeMocker (6-14)
tests/unit/test_disks.py (1)
tests/unit/utils.py (2)
  • assert_equal (154-160)
  • parametrize (16-74)
tests/unit/test_access.py (1)
tests/unit/utils.py (1)
  • parametrize (16-74)
tests/unit/test_schema.py (1)
tests/unit/utils.py (1)
  • parametrize (16-74)
ch_backup/storage/async_pipeline/pipelines.py (2)
ch_backup/util.py (1)
  • exhaust_iterator (409-413)
ch_backup/logging.py (1)
  • warning (150-155)
🔇 Additional comments (54)
tests/integration/steps/common.py (1)

6-7: Import grouping formatting improvement
The added blank line after the behave imports enhances readability and aligns with the grouping style used across other integration step files.

tests/integration/modules/zookeeper.py (1)

9-11: Import grouping formatting improvement
The blank line added after the kazoo.exceptions import matches the import grouping conventions elsewhere and improves clarity.

tests/integration/steps/s3.py (1)

6-7: Import grouping formatting improvement
Inserting a blank line after the hamcrest imports maintains consistent grouping and mirrors the style updates in other integration steps.

tests/unit/test_rate_limiter.py (1)

9-10: Consistent import grouping
The TimeMocker import is now placed after the third-party RateLimiter import, improving separation between external and local test utility imports.

tests/integration/steps/zookeeper.py (1)

6-7: Add blank line for import separation
A blank line was inserted after the hamcrest import to clearly separate third-party dependencies from local module imports, aligning with the project’s import grouping conventions.

tests/integration/steps/clickhouse.py (1)

8-9: Insert blank line between import groups
This formatting change adds a blank line after the tenacity import, improving readability by distinguishing external libraries from application-specific modules.

tests/integration/steps/ch_backup.py (1)

16-18: Insert blank line post-import block
A blank line was added after the Hamcrest matcher imports to consistently group and separate external dependencies from local test modules.

tests/unit/test_clickhouse_encryption.py (1)

11-12: Reorder test utility imports
The imports of assert_equal and parametrize from tests.unit.utils have been moved below the application imports, ensuring that third-party and application imports precede test helper utilities.

tests/unit/test_control.py (1)

2-2: LGTM: Import reordering aligns with project standardization

This change is part of a broader standardization of imports across the codebase, moving imports from test utilities to appear after imports from main application modules.

tests/unit/test_upload_part_observer.py (1)

11-11: LGTM: Import reordering follows project convention

This change is consistent with the standardization of import order across the codebase as part of the migration to use the uv tool and modern Python packaging.

tests/unit/test_disks.py (1)

13-13: LGTM: Standardized import order

This import reordering places test utility imports after main application module imports, consistent with other changes in the PR.

tests/unit/test_access.py (1)

11-11: LGTM: Consistent import ordering

Moving the test utility import below main application imports follows the standardized pattern being applied across the codebase.

tests/unit/test_backup_tables.py (1)

109-112: Improved syntax for multiple context managers.

The improved syntax for using multiple context managers in a single with statement enhances code readability. This is a good Python practice.

ch_backup/storage/async_pipeline/base_pipeline/exec_pool.py (1)

57-57: Added trailing comma in parameter list.

Adding a trailing comma after the last parameter in a multi-line parameter list is good practice. It improves maintainability by making future parameter additions cleaner.

ch_backup/storage/async_pipeline/pipeline_builder.py (1)

245-245: Added trailing comma after unpacked arguments.

Adding a trailing comma after the *stages unpacking is good practice and consistent with modern Python style guidelines.

ch_backup/logic/lock_manager.py (2)

96-97: Good simplification of file management.

This change improves the code by using the file object's close method directly instead of closing a file descriptor with os.close(). This makes the code more consistent and eliminates potential file descriptor management issues.


111-112: Improved file locking implementation.

The changes to use the "r+" mode when opening the file and apply the lock directly on the file object rather than using a file descriptor is more Pythonic and consistent with the rest of the implementation. This eliminates mixing different file handling approaches and simplifies the code.

AUTHORS (2)

1-2: Name and format standardization looks good.

The standardization of email formats and name ordering improves consistency in the contributor list.

Also applies to: 6-6


18-20: New contributors added correctly.

The addition of the new contributors is properly formatted and consistent with the rest of the file.

.pylintrc (1)

92-92: Python version update aligns with project modernization.

Updating the minimum Python version from 3.6 to 3.9 for Pylint checks is appropriate as part of the project's migration to modern Python standards. This ensures Pylint will apply the correct syntax and feature checks based on Python 3.9+ compatibility.

.python-version (1)

1-1: Good addition for standardizing Python environments.

Adding a .python-version file with a specific Python version (3.10) is an excellent practice for ensuring consistent development environments. This works with tools like pyenv and aligns with the project's move to the uv tool for Python dependency management.

debian/control (1)

5-5: Minimum Python version bump is consistent
Requiring python3 (>= 3.9) aligns with the CI, mypy, and packaging changes elsewhere in the project.

ch_backup/clickhouse/control.py (1)

15-15: Use packaging.version.parse for version comparisons
Switching from pkg_resources to packaging.version.parse is a solid modernization—packaging is lighter and the recommended API for version handling.

tests/integration/modules/utils.py (1)

13-13: Replace pkg_resources with packaging.version
Updating the import to from packaging.version import parse as parse_version keeps version parsing consistent across modules and removes the setuptools dependency.

.gitignore (1)

14-14: Ignore the new local virtual environment directory
Adding .venv/ is appropriate given the switch to uv-managed environments.

tests/integration/modules/clickhouse.py (1)

11-11: Updated import for version parsing

The change from pkg_resources to packaging.version improves dependency handling by using a more modern and maintained package. This is consistent with the PR objective of updating dependency management to use uv.

tests/integration/features/backup_replicated.feature (1)

867-867: Fixed indentation in YAML configuration

The indentation correction improves readability and ensures proper parsing of the YAML block.

Dockerfile-deb-build (1)

30-35: Added uv tool installation

The Docker build now installs and configures the uv tool, which aligns with the PR's goal of migrating to modern Python dependency management. The symbolic links ensure proper system-wide access.

tests/unit/test_pipeline.py (1)

167-170: Improved readability for multiple context managers

The refactored syntax using parentheses to group multiple open() calls makes the code more readable while maintaining the same functionality.

.github/workflows/test_clickhouse_version.yml (1)

23-23: LGTM! The migration to uv for Python environment management looks good.

This change aligns with the PR objectives of migrating to the uv tool for Python dependency management. Replacing the explicit Python setup step with astral-sh/setup-uv@v5 is the correct approach for integrating with the new tooling system.

tests/integration/modules/compose.py (2)

7-7: LGTM! Added import for the new dynamic Docker Compose detection.

The added import of shutil is properly used for the dynamic Docker Compose command detection implementation.


187-190: LGTM! Great compatibility improvement for Docker Compose.

The implementation intelligently detects whether to use the legacy docker-compose command or the newer docker compose syntax based on command availability. This ensures compatibility across different environments where Docker Compose might be installed differently.

debian/rules (1)

4-5: LGTM! Standardization to explicit python3 usage.

This change simplifies the build process by removing the dependency on the $(PYTHON) variable and directly using python3 to determine version components. This aligns with the overall project modernization goals and standardizes the Python tooling approach.

ch_backup/storage/async_pipeline/pipelines.py (1)

9-9: LGTM! Added import for the enhanced error handling.

The added import of the logging module is properly used in the error handling enhancement.

.github/workflows/workflow.yml (2)

51-63: Approve updated test matrix entries
The ClickHouse matrix has been expanded to include versions 24.3.18.7, 24.8.14.39, 25.3.3.42, and Python 3.13. This aligns with the updated compatibility goals.


69-71: Passing PYTHON_VERSION to Make targets
Great to see PYTHON_VERSION=${{ matrix.python }} being set for both make test-unit and make test-integration. This ensures the Makefile picks up the intended Python version.

images/clickhouse/Dockerfile (1)

35-36: Keyring file copy and apt sources configuration look good
The clickhouse-keyring is correctly copied into /usr/share/keyrings, and the new RUN block configures the apt source list securely with signed-by.

🧰 Tools
🪛 Hadolint (2.12.0)

[error] 35-35: COPY with more than 2 arguments requires the last argument to end with /

(DL3021)

pyproject.toml (5)

1-14: Project metadata is well-defined
The [project] section correctly centralizes name, description, license, dynamic versioning, and Python compatibility (>=3.9).


33-55: Runtime dependencies look accurate and conditional bounds are clear
Version constraints for boto3, botocore, requests, and others correctly account for Python-version-specific compatibility.


57-81: Development dependency-group is comprehensive
All linting, testing, and packaging tools have been migrated into dev. Conditional bounds for docker and websocket-client are properly set.


83-89: Build system and entry point configuration is correct
Using hatchling as the build-backend and exposing the ch-backup script aligns with modern Python packaging standards.


98-103: Tool-specific configurations migrated successfully
The isort profile and codespell ignore-words-list are brought into pyproject.toml, removing legacy config files.

Makefile (12)

3-8: Environment variable exports fine
Reading PYTHON_VERSION from .python-version, plus setting encoding, COMPOSE_HTTP_TIMEOUT, and CLICKHOUSE_VERSION, provides a clear configuration surface.


28-33: Project directory and tool variables
Defining SRC_DIR, TESTS_DIR, VENV, SESSION_FILE, and INSTALL_DIR improves readability and DRYness.


36-43: Build and setup targets use uv correctly
The build target now depends on setup and runs uv build --python $(PYTHON_VERSION), and setup checks environment before generating version.


45-51: all and lint target chaining looks solid
Aggregating lint, test-unit, build, and test-integration under all, and making lint depend on the newly updated lint sub-targets is consistent.


53-92: Lint sub-targets updated to use uv run --python
All linters (isort, black, codespell, ruff, pylint, mypy, bandit) invoke uv run --python $(PYTHON_VERSION), ensuring they run in the correct managed environment.


94-98: Format target consistency
The format target invokes isort and black via uv run. This aligns with the CI and keeps local workflows uniform.


111-119: Cleanup targets remain effective
clean and clean-pycache remove artifacts including .venv, caches, and generated version files. This ensures a fresh state.


121-144: Install/uninstall targets for packaging
Using a Python venv in INSTALL_DIR to install the wheel simplifies the packaging step and preserves isolation. Uninstall cleans up accordingly.


146-161: Debian package build targets are in place
build-deb-package, build-deb-package-local, and prepare-changelog targets correctly wrap the deb build scripts, leveraging the version file.


170-189: Integration test environment lifecycle
create-test-env, start-test-env, stop-test-env, and clean-test-env encapsulate test environment management cleanly with INTEGRATION_TEST_TOOL.


193-204: Version file generation and environment check
Generating ch_backup/version.txt via git, and verifying the presence of uv and a non-empty PYTHON_VERSION provides a robust guardrail.


207-236: Help target updated to reflect new commands
The help target lists all current make targets and environment variables, removing outdated references.

@Alex-Burmak
Copy link
Owner Author

@CodiumAI-Agent /describe

@QodoAI-Agent
Copy link

Title

Test PR


User description

Summary by Sourcery

Migrate project to use uv for Python dependency management and build system, update project configuration, and modernize development workflows

Enhancements:

  • Simplify project configuration and dependency management
  • Improve Python version compatibility
  • Modernize project build and test infrastructure

Build:

  • Replace setup.py with pyproject.toml for modern Python packaging
  • Update Makefile to use uv for dependency management and task execution
  • Add support for Python 3.13 and update Python version matrix in CI

CI:

  • Replace actions/setup-python with astral-sh/setup-uv
  • Update GitHub Actions workflows to use uv
  • Update ClickHouse version matrix in test workflows

Chores:

  • Remove legacy configuration files like .isort.cfg and requirements.txt
  • Update Docker and CI configurations to use new build system

Summary by CodeRabbit

  • New Features

    • Added a pyproject.toml for unified project configuration, dependency management, and build system.
    • Introduced explicit Python version management via .python-version (now set to 3.10).
  • Improvements

    • Refactored Makefile and Dockerfiles to use the uv tool for consistent Python environment management.
    • Updated CI workflows to modernize Python setup, add Python 3.13, and update ClickHouse versions.
    • Increased minimum supported Python version to 3.9 across configuration files and packaging metadata.
    • Enhanced Docker and packaging scripts for simplified and more robust builds.
  • Bug Fixes

    • Improved error handling for specific threading issues in pipeline execution.
    • Ensured compatibility with both docker-compose and docker compose commands in integration tests.
  • Style

    • Standardized and reordered import statements and improved code formatting for readability.
  • Chores

    • Removed legacy configuration and dependency files (requirements.txt, requirements-dev.txt, setup.py, .isort.cfg, .codespellrc).
    • Updated .gitignore to exclude .venv/ and stop ignoring .python-version.
    • Cleaned up and standardized contributor information in the AUTHORS file.

PR Type

Enhancement, Bug fix, Tests


Description

  • Migrate build to uv and adopt pyproject.toml

  • Update CI workflows to use setup-uv, add Python 3.13

  • Fix LockManager file locking cleanup

  • Handle "Invalid thread ID" error in pipelines

  • Replace pkg_resources.parse_version with packaging.parse


Changes walkthrough 📝

Relevant files
Dependencies
4 files
control.py
Use `packaging.parse_version` instead of pkg_resources     
+1/-1     
Dockerfile-deb-build
Install `uv` and ensure `python` symlink                                 
+6/-3     
clickhouse.py
Switch to `packaging.parse_version` in tests                         
+1/-1     
utils.py
Switch to `packaging.parse_version` in utils                         
+1/-1     
Bug fix
1 files
lock_manager.py
Fix file locking cleanup using `file.close()`                       
+3/-5     
Error handling
1 files
pipelines.py
Handle "Invalid thread ID" iteration error                             
+13/-2   
Configuration changes
8 files
setup.py
Remove legacy `setup.py` packaging script                               
+0/-67   
Makefile
Switch build and test targets to `uv` commands                     
+84/-95 
pyproject.toml
Add modern project metadata and dependencies                         
+102/-0 
requirements.txt
Remove legacy requirements file                                                   
+0/-20   
requirements-dev.txt
Remove legacy dev requirements                                                     
+0/-21   
.python-version
Pin default Python version to 3.10                                             
+1/-0     
rules
Use `python3` explicitly in Debian rules                                 
+2/-2     
Dockerfile
Install wheel and simplify ClickHouse image build               
+16/-22 
Ci changes
2 files
test_clickhouse_version.yml
Use `astral-sh/setup-uv` in integration CI                             
+1/-4     
workflow.yml
Replace setup-python with `setup-uv`, update matrix           
+10/-17 
Packaging changes
1 files
control
Bump build-dep python3 version to >=3.9                                   
+1/-1     
Enhancement
1 files
compose.py
Support fallback to `docker compose` command                         
+7/-2     
Formatting
2 files
zookeeper.py
Adjust import formatting in zookeeper module                         
+1/-0     
exec_pool.py
Add trailing comma in function signature                                 
+1/-1     
Additional files
23 files
.codespellrc +0/-3     
.isort.cfg +0/-4     
.pylintrc +1/-1     
AUTHORS +6/-5     
pipeline_builder.py +1/-1     
mypy.ini +1/-0     
backup_replicated.feature +2/-2     
ch_backup.py +1/-0     
clickhouse.py +1/-0     
common.py +1/-0     
s3.py +1/-0     
zookeeper.py +1/-0     
test_access.py +1/-2     
test_backup_tables.py +3/-2     
test_cli.py +1/-2     
test_clickhouse_encryption.py +1/-2     
test_control.py +1/-2     
test_disks.py +1/-1     
test_pipeline.py +4/-3     
test_rate_limiter.py +1/-1     
test_retry_exponetial.py +1/-1     
test_schema.py +1/-2     
test_upload_part_observer.py +1/-2     

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @Alex-Burmak
    Copy link
    Owner Author

    @CodiumAI-Agent /review

    @QodoAI-Agent
    Copy link

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Brittle Error Handling

    Catching ValueError based on substring matching may swallow other errors or miss variations; consider catching a more specific exception or refining the match logic.

    try:
        itr = iter(pipeline)
        exhaust_iterator(itr)
    except ValueError as e:
        if "Invalid thread ID" in str(e):
            logging.warning(
                "Thread ID error during iteration exhaustion due to"
                " incorrect thread termination in stopit library, skipping",
                exc_info=True,
            )
        else:
            raise
    Unused kwargs

    The submit method now accepts **kwargs but does not forward them to the underlying executor or func, potentially dropping arguments; verify intended usage.

    **kwargs: Any,
    File Lock Handling

    Opening the lock file in 'r+' mode and using flock on the file object requires proper imports and error handling; ensure flock and LOCK_SH are imported and handle errors.

    self._file = open(self._process_lockfile_path, "r+", encoding="utf-8")
    flock(self._file, LOCK_SH)

    @Alex-Burmak
    Copy link
    Owner Author

    @CodiumAI-Agent /improve

    @QodoAI-Agent
    Copy link

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Pass file descriptor to flock

    Use the file descriptor instead of passing the file object directly to flock, as it
    expects an integer file descriptor. This prevents type errors and ensures the lock
    is correctly applied.

    ch_backup/logic/lock_manager.py [112]

    -flock(self._file, LOCK_SH)
    +flock(self._file.fileno(), LOCK_SH)
    Suggestion importance[1-10]: 9

    __

    Why: The call to flock on a file object will raise a TypeError because it expects an integer file descriptor, so using self._file.fileno() corrects this bug.

    High

    @Alex-Burmak Alex-Burmak force-pushed the main branch 5 times, most recently from bcef9a7 to d45fbce Compare May 18, 2025 14:54
    @Alex-Burmak Alex-Burmak force-pushed the main branch 2 times, most recently from 6c1cd87 to 6b13808 Compare May 18, 2025 15:14
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants