Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
uses: astral-sh/setup-uv@v5

- name: Set up Python
run: uv python install 3.10
run: uv python install 3.11

- name: Install dependencies
run: uv sync --group dev --frozen
Expand All @@ -43,7 +43,7 @@ jobs:
uses: astral-sh/setup-uv@v5

- name: Set up Python
run: uv python install 3.10
run: uv python install 3.11

- name: Install dependencies
run: uv sync --group docs --frozen
Expand All @@ -55,7 +55,7 @@ jobs:
strategy:
fail-fast: ${{ github.event.pull_request.draft == true }}
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.11", "3.12", "3.13", "3.14"]

runs-on: ubuntu-latest

Expand Down Expand Up @@ -94,7 +94,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
python-version: "3.11"

- name: Install ruff-sync (no extras)
# We install only the base package without 'dev' or 'tui' groups.
Expand All @@ -118,7 +118,7 @@ jobs:
uses: astral-sh/setup-uv@v5

- name: Set up Python
run: uv python install 3.10
run: uv python install 3.11

- name: Build package
run: |
Expand Down Expand Up @@ -170,7 +170,7 @@ jobs:
uses: astral-sh/setup-uv@v5

- name: Set up Python
run: uv python install 3.10
run: uv python install 3.11

- name: Install dependencies
run: uv sync --group docs --frozen
Expand Down Expand Up @@ -233,7 +233,7 @@ jobs:
uses: astral-sh/setup-uv@v5

- name: Set up Python
run: uv python install 3.10
run: uv python install 3.11

- name: Build package
run: uv build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/complexity.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
python-version: "3.11"
- name: Install Wily
run: pip install 'wily>=1.25.0'
- name: Build cache and diff
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ repos:
- id: no-commit-to-branch
args: [--branch, develop, --branch, main]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.16.3"
rev: "v0.16.4"
hooks:
- id: ruff-check
args: ["--fix"]
Expand Down
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ Specific workflows, libraries, and tools are documented in `.agents/skills/`. Be

## Tech Stack

- **Python** ≥ 3.10 (target version `py310`)
- **Python** ≥ 3.11 (target version `py311`)
- **Package Manager**: [uv](https://docs.astral.sh/uv/) — Use `uv run <command>` for all executions to ensure the correct environment.
- **Note on PATH**: On macOS, `uv` is often installed in `~/.local/bin`. If `uv` is not found, add this to your `PATH`: `export PATH="$PATH:$HOME/.local/bin"`.
- **Linter / Formatter**: [Ruff](https://docs.astral.sh/ruff/) (`>=0.15.0`)
- **Type Checker**: [mypy](https://mypy-lang.org/) (strict mode)
- **Test Framework**: [pytest](https://docs.pytest.org/) with `pytest-asyncio`, `respx`, `pyfakefs` (See [Testing Standards](.agents/TESTING.md))
- **Coverage**: `coverage` + Codecov
- **Pre-commit**: `pre-commit` / `prek` (see `.pre-commit-config.yaml`)
- **Pre-commit**: `prek` (see `.pre-commit-config.yaml`)
- **TOML Parsing**: [tomlkit](https://github.com/sdispater/tomlkit) — preserves formatting and comments
- **HTTP**: [httpx](https://www.python-httpx.org/) (async)

Expand Down Expand Up @@ -120,7 +120,7 @@ uv run ruff format .
uv run mypy .
```

- mypy is configured in strict mode with `python_version = "3.10"`.
- mypy is configured in strict mode with `python_version = "3.11"`.
- It checks `src/`, `tests/`, and `tasks.py`.
- Tests have relaxed rules: `type-arg` and `no-untyped-def` are disabled for `tests.*`.
- `tomlkit` returns complex union types — use `cast(Any, ...)` in tests when indexing parsed TOML documents to satisfy mypy without verbose type narrowing.
Expand Down Expand Up @@ -211,8 +211,8 @@ Defined in `tasks.py`. **ALWAYS** run these through uv: `uv run invoke <task>`

CI is defined in `.github/workflows/ci.yaml`:

- **Static Analysis**: runs `lint --check`, `fmt --check`, and `type-check` on Python 3.10.
- **Tests**: matrix across Python 3.10, 3.11, 3.12, 3.13, 3.14.
- **Static Analysis**: runs `lint --check`, `fmt --check`, and `type-check` on Python 3.11.
- **Tests**: matrix across Python 3.11, 3.12, 3.13, 3.14.
- **Pre-commit.ci**: auto-fixes and auto-updates hooks on PRs.

## Common Pitfalls
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Thank you for your interest in contributing! This guide covers everything you ne
```
5. **Install pre-commit hooks**:
```bash
uv run pre-commit install
uv run prek install
```

---
Expand Down Expand Up @@ -96,7 +96,7 @@ uv run pytest -vv
> [!IMPORTANT]
> **Do not add `# noqa` comments or suppress lint rules.** Fix the underlying issue instead.

CI runs the same checks across Python 3.10–3.14, so make sure everything passes locally first.
CI runs the same checks across Python 3.11–3.14, so make sure everything passes locally first.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This page covers the essentials. For the full contributor's guide, see [CONTRIBU
```
3. **Install pre-commit hooks**:
```bash
uv run pre-commit install
uv run prek install
```

---
Expand Down
19 changes: 9 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
[project]
name = "ruff-sync"
version = "0.1.8"
version = "0.1.9.dev0"
description = "Synchronize Ruff linter configuration across projects"
keywords = ["ruff", "linter", "config", "synchronize", "python", "linting", "automation", "tomlkit", "pre-commit"]
authors = [
{ name = "Gabriel Gore", email = "gabriel59kg@gmail.com" }
]
license = { text = "MIT" }
readme = "README.md"
requires-python = ">=3.10"
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
requires-python = ">=3.11"
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
Expand Down Expand Up @@ -48,18 +47,18 @@ dev = [
"coverage>=7.4.4",
"dirty-equals>=0.11",
"invoke>=2.2.0",
"mypy>=1.10.0",
"mypy>=2.3.0",
"packaging>=26.0",
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
"pre-commit>=3.7.0",
"prek>=0.4.14",
"pyfakefs>=5.4.1",
"pytest>=8.0.0",
"pytest>=9.1.1",
"pytest-asyncio>=0.23.5",
"pytest-codspeed>=4.3.0",
"pytest-codspeed>=5.0.3",
"pytest-icdiff>=0.9",
"pytest-textual-snapshot>=1.0.0",
"respx>=0.23.1",
"ruamel-yaml>=0.18.6",
"ruff>=0.15.0",
"ruff>=0.16.0",
"textual>=8.2.2",
"wily>=1.25.0",
]
Expand All @@ -86,7 +85,7 @@ build-backend = "hatchling.build"
packages = ["src/ruff_sync"]

[tool.mypy]
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
python_version = "3.10"
python_version = "3.11"
files = ["src", "tests/", "tasks.py", ".agents/"]
strict = true
warn_unused_configs = true
Expand Down Expand Up @@ -120,7 +119,7 @@ exclude = [
]

[tool.ruff]
target-version = "py310"
target-version = "py311"
line-length = 100

[tool.ruff.lint]
Expand Down
2 changes: 1 addition & 1 deletion src/ruff_sync/config_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


@enum.unique
class RuffConfigFileName(str, enum.Enum):
class RuffConfigFileName(enum.StrEnum):
"""Enumeration of Ruff configuration filenames."""

PYPROJECT_TOML = "pyproject.toml"
Expand Down
4 changes: 2 additions & 2 deletions src/ruff_sync/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class MissingType(enum.Enum):


@enum.unique
class OutputFormat(str, enum.Enum):
class OutputFormat(enum.StrEnum):
"""Output formats for the CLI."""

TEXT = "text"
Expand All @@ -63,7 +63,7 @@ def __str__(self) -> str:


@enum.unique
class ConfKey(str, enum.Enum):
class ConfKey(enum.StrEnum):
"""Centralized configuration keys for [tool.ruff-sync].

These are the canonical names used in the pyproject.toml configuration file.
Expand Down
61 changes: 17 additions & 44 deletions src/ruff_sync/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,51 +697,24 @@ async def fetch_upstreams_concurrently(
UpstreamError: If one or more upstreams fail to fetch.
"""
upstream_list = list(upstreams)
if sys.version_info >= (3, 11):
# Use structured concurrency on Python 3.11+
tasks: list[asyncio.Task[FetchResult]] = []
try:
async with asyncio.TaskGroup() as tg:
tasks = [
tg.create_task(fetch_upstream_config(url, client, branch, path))
for url in upstream_list
]
return [t.result() for t in tasks]
except BaseException as eg:
if isinstance(eg, (asyncio.CancelledError, KeyboardInterrupt)):
raise
# TODO: Use `except*` once Python 3.11+ is the minimum supported version.
# On Python 3.11+, TaskGroup raises an ExceptionGroup.
# Catching it as Exception is safe and compatible with Python 3.10 syntax.
errors = [
(upstream_list[i], t.exception())
for i, t in enumerate(tasks)
if t.done() and t.exception() is not None
tasks: list[asyncio.Task[FetchResult]] = []
try:
async with asyncio.TaskGroup() as tg:
tasks = [
tg.create_task(fetch_upstream_config(url, client, branch, path))
for url in upstream_list
]
if errors:
raise UpstreamError(errors) from eg
raise
else:
# Fallback for Python 3.10
fetch_tasks = [fetch_upstream_config(url, client, branch, path) for url in upstream_list]
results = await asyncio.gather(*fetch_tasks, return_exceptions=True)

errors_list: list[tuple[URL, BaseException]] = []
fetch_results: list[FetchResult] = []

for i, res in enumerate(results):
if isinstance(res, BaseException):
errors_list.append((upstream_list[i], res))
elif isinstance(res, FetchResult):
fetch_results.append(res)
else:
msg = f"Unexpected result type from fetch: {type(res)}"
raise TypeError(msg)

if errors_list:
raise UpstreamError(errors_list)

return fetch_results
return [t.result() for t in tasks]
except* Exception as eg:
errors: list[tuple[URL, BaseException]] = []
for i, t in enumerate(tasks):
if t.done() and not t.cancelled():
exc = t.exception()
if exc is not None:
errors.append((upstream_list[i], exc))
if errors:
raise UpstreamError(errors) from eg
raise


async def _merge_multiple_upstreams(
Expand Down
4 changes: 1 addition & 3 deletions src/ruff_sync/types_.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

from __future__ import annotations

from typing import Any, Literal, TypedDict

from typing_extensions import NotRequired
from typing import Any, Literal, NotRequired, TypedDict

RuleSyncStatus = Literal["Enabled", "Ignored", "Disabled"]

Expand Down
10 changes: 5 additions & 5 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def _get_current_version() -> str:
"""Read the current version from pyproject.toml."""
with PYPROJECT_TOML.open("r", encoding="utf-8") as f:
toml_content = TOMLFile(f.name).read()
return str(toml_content["project"]["version"]) # type: ignore[index]
return str(toml_content["project"]["version"])


def _get_pypi_versions() -> tuple[str | None, str | None]:
Expand Down Expand Up @@ -189,10 +189,10 @@ def new_lifecycle_tomls(ctx: Context, name: str, description: str | None = None)
}
if not description:
description = f"Sample project for {name}"
toml_dict["initial"]["tool"]["poetry"]["name"] = name # type: ignore[index]
toml_dict["final"]["tool"]["poetry"]["name"] = name # type: ignore[index]
toml_dict["initial"]["tool"]["poetry"]["description"] = description # type: ignore[index]
toml_dict["final"]["tool"]["poetry"]["description"] = description # type: ignore[index]
toml_dict["initial"]["tool"]["poetry"]["name"] = name
toml_dict["final"]["tool"]["poetry"]["name"] = name
toml_dict["initial"]["tool"]["poetry"]["description"] = description
toml_dict["final"]["tool"]["poetry"]["description"] = description

# write the new tomls
for stage, toml_doc in toml_dict.items():
Expand Down
2 changes: 1 addition & 1 deletion tests/lifecycle_tomls/multi_upstream_final.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ target-version = "py312"
line-length = 100

[tool.ruff.lint]
select = ["E", "F", "I"]
ignore = ["E501"]
select = ["E", "F", "I"]
extend-select = ["UP"]
Loading
Loading