diff --git a/.gemini/system.md b/.gemini/system.md new file mode 100644 index 0000000..b1eb811 --- /dev/null +++ b/.gemini/system.md @@ -0,0 +1,10 @@ +You have my permissions to + +- read any file on the machine if it's readable to the current user +- make changes to files under this project only +- to create and/or remove temporary files under this project only +- to run shell command `uv`, `fish -n` and `fish_indent` to lint or test Python and fish scripts +- to run the shell commands which does NOT change any files + +However, +please do NOT commit changes so that I can review the diff locally. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..84b2de8 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,56 @@ +# GitHub REST API Project + +A simple Python wrapper for GitHub REST APIs, optimized for use in GitHub Actions automation. + +## Project Overview + +- **Purpose:** Provide a streamlined interface for interacting with GitHub's REST API + and performing Git operations within automation scripts. +- **Main Technologies:** + - **Python 3.11+**: Core language. + - **requests**: For HTTP interactions with the GitHub API. + - **dulwich**: A pure-Python implementation of Git for repository operations. + - **psutil**: For system and process utilities. +- **Architecture:** + - `github_rest_api/github.py`: Contains the `GitHub` class for handling API requests (GET, POST, DELETE, PUT, PATCH). + - `github_rest_api/actions/`: Focused utilities for GitHub Actions, including branch management and pushing changes. + - `github_rest_api/actions/cargo/`: Specific support for Rust projects (benchmarking and profiling). + - `github_rest_api/utils.py`: General-purpose utilities (versioning, partitioning). + +## Building and Running + +This project uses `uv` for dependency and environment management. + +- **Setup Environment:** + ```bash + uv sync --all-extras + ``` +- **Code Formatting:** + ```bash + uv run ruff format ./ + ``` +- **Linting:** + ```bash + uv run ruff check github_rest_api/ tests/ + ``` +- **Type Checking:** + ```bash + uv run ty check + ``` +- **Dependency Analysis:** + ```bash + uv run deptry . + ``` +- **Running Tests:** + ```bash + uv run pytest + ``` + +## Development Conventions + +- **Code Style:** Strictly follows `ruff` formatting and linting rules. +- **Type Safety:** Uses `ty` (in addition to standard type hints) to ensure type correctness. +- **CI/CD:** Automated linting and formatting checks are performed + on `push` to `dev`/`main` branches and on `pull_request` to `dev`. +- **Git Operations:** Prefers `dulwich` for programmatic Git interactions + to avoid dependency on a system Git installation where possible. diff --git a/github_rest_api/utils.py b/github_rest_api/utils.py index 9eb45e9..41c0f4b 100644 --- a/github_rest_api/utils.py +++ b/github_rest_api/utils.py @@ -13,14 +13,43 @@ def partition(pred, iterable): def strip_patch_version(version: str) -> str: - parts = version.split(".") - match len(parts): - case 1: - return parts[0] + ".0.0" - case 2 | 3: - return ".".join(parts[:2]) + ".0" - case _: - raise ValueError("Invalid version semantic provided!") + """Strip the patch version. + + For example, + - strip_patch_version("5.4.0") ==> "5.4.0" + - strip_patch_version("5.4.6") ==> "5.4.0" + - strip_patch_version("2.3") ==> "2.3.0" + - strip_patch_version("1") ==> "1.0.0" + """ + parts = version.strip().split(".") + n = len(parts) + if n < 1 or n > 3: + raise ValueError("Invalid version semantic provided!") + while len(parts) < 3: + parts.append("0") + parts[2] = "0" + return ".".join(parts) + + +def next_minor_or_strip_patch(version: str, patch_to_bump: int) -> str: + """Get the next minor version of strip the patch version. + + If the patch version is less than patch_to_bump, then strip the patch version; + otherwise, bump version to the next minor one. + For example, + - next_minor_or_strip_patch("5.4.6", 4) ==> "5.5.0" + - next_minor_or_strip_patch("5.4.6", 8) ==> "5.4.0" + """ + parts = version.strip().split(".") + n = len(parts) + if n < 1 or n > 3: + raise ValueError("Invalid version semantic provided!") + while len(parts) < 3: + parts.append("0") + major, minor, patch = map(int, parts) + if patch >= patch_to_bump: + minor += 1 + return f"{major}.{minor}.0" def strip_minor_version(version: str) -> str: diff --git a/pyproject.toml b/pyproject.toml index a180332..2701db9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "github_rest_api" -version = "0.31.0" +version = "0.32.0" description = "Simple wrapper of GitHub REST APIs." authors = [{ name = "Ben Du", email = "longendu@yahoo.com" }] requires-python = ">=3.11,<4" diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..3db890e --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,33 @@ +import pytest +from github_rest_api.utils import next_minor_or_strip_patch, strip_patch_version + + +def test_next_minor_or_strip_patch(): + # Examples from docstring + assert next_minor_or_strip_patch("5.4.6", 4) == "5.5.0" + assert next_minor_or_strip_patch("5.4.6", 8) == "5.4.0" + + # Edge cases + assert next_minor_or_strip_patch("1.0.0", 0) == "1.1.0" + assert next_minor_or_strip_patch("1.0.0", 1) == "1.0.0" + assert next_minor_or_strip_patch("1.2.3", 3) == "1.3.0" + assert next_minor_or_strip_patch("1.2.3", 4) == "1.2.0" + + # Different lengths + assert next_minor_or_strip_patch("1", 0) == "1.1.0" + assert next_minor_or_strip_patch("1", 1) == "1.0.0" + assert next_minor_or_strip_patch("1.2", 0) == "1.3.0" + assert next_minor_or_strip_patch("1.2", 1) == "1.2.0" + + with pytest.raises(ValueError): + next_minor_or_strip_patch("1.2.3.4", 1) + with pytest.raises(ValueError): + next_minor_or_strip_patch("", 1) + + +def test_strip_patch_version(): + assert strip_patch_version("1") == "1.0.0" + assert strip_patch_version("1.2") == "1.2.0" + assert strip_patch_version("1.2.3") == "1.2.0" + with pytest.raises(ValueError): + strip_patch_version("1.2.3.4")