Syncronize ruff linter config settings accross projects
Project description
ruff-sync
Keep your Ruff config consistent across every repo — automatically.
ruff-sync is a CLI tool that pulls a canonical Ruff configuration from an upstream pyproject.toml (hosted anywhere — GitHub, GitLab, a raw URL) and merges it into your local project, preserving your comments, formatting, and project-specific overrides.
The Problem
If you maintain more than one Python project, you've probably copy-pasted your [tool.ruff] config between repos more than once. When you decide to enable a new rule or bump your target Python version, you get to do it again — in every repo. Configs drift, standards diverge, and your "shared" style guide becomes a polite suggestion.
How Other Ecosystems Solve This
| Ecosystem | Mechanism | Limitation for Ruff users |
|---|---|---|
| ESLint | Shareable configs — publish an npm package, then extends: ["my-org-config"] |
Requires a package registry (npm). Python doesn't have an equivalent convention. |
| Prettier | Shared configs — same npm-package pattern, referenced via "prettier": "@my-org/prettier-config" in package.json |
Same — tightly coupled to npm. |
| Ruff | extend — extend from a local file path (great for monorepos) |
Only supports local paths. No native remote URL support (requested in astral-sh/ruff#12352). |
Ruff's extend is perfect inside a monorepo, but if your projects live in separate repositories, there's no built-in way to inherit config from a central source.
That's what ruff-sync does.
How It Works
┌─────────────────────────────┐
│ Upstream repo │
│ (your "source of truth") │
│ │
│ pyproject.toml │
│ [tool.ruff] │
│ target-version = "py310" │
│ lint.select = [...] │
└──────────┬──────────────────┘
│ ruff-sync downloads
│ & extracts [tool.ruff]
▼
┌─────────────────────────────┐
│ Your local project │
│ │
│ pyproject.toml │
│ [tool.ruff] ◄── merged │
│ # your comments kept ✓ │
│ # formatting kept ✓ │
│ # per-file-ignores kept ✓│
└─────────────────────────────┘
- You point
ruff-syncat the URL of your canonicalpyproject.toml. - It downloads the file, extracts the
[tool.ruff]section. - It merges the upstream config into your local
pyproject.toml— updating values that changed, adding new rules, but preserving your local comments, whitespace, and any sections you've chosen to exclude (likeper-file-ignores).
No package registry. No publishing step. Just a URL.
Quick Start
Install
From Git (with uv):
uv tool install git+https://github.com/Kilo59/ruff-sync
From Git (with pipx — recommended):
pipx install git+https://github.com/Kilo59/ruff-sync
Or with pip:
pip install git+https://github.com/Kilo59/ruff-sync
Usage
# Sync from a GitHub URL (blob URLs are auto-converted to raw)
ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
# Sync into a specific project directory
ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml --source ./my-project
# Exclude specific sections from being overwritten using dotted paths
ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml --exclude lint.per-file-ignores lint.ignore
CLI Reference
usage: ruff-sync [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] upstream
positional arguments:
upstream The URL to download the pyproject.toml file from.
optional arguments:
-h, --help show this help message and exit
--source SOURCE The directory to sync the pyproject.toml file to. Default: .
--exclude EXCLUDE [EXCLUDE ...]
Exclude certain ruff configs. Default: lint.per-file-ignores
Key Features
- Format-preserving merges — Uses tomlkit under the hood, so your comments, whitespace, and TOML structure are preserved. No reformatting surprises.
- GitHub URL support — Paste a GitHub blob URL and it will automatically convert it to the raw content URL.
- Selective exclusions — Keep project-specific overrides (like
target-version) from being clobbered by the upstream config. - Works with any host — GitHub, GitLab, Bitbucket, or any raw URL that serves a
pyproject.toml.
Configuration
You can configure ruff-sync itself in your pyproject.toml:
[tool.ruff-sync]
# Use simple names for top-level keys, and dotted paths for nested keys
exclude = [
"target-version", # A top-level key under [tool.ruff]
"lint.per-file-ignores", # A nested key under [tool.ruff.lint]
"lint.ignore"
]
This sets the default exclusions so you don't need to pass --exclude on the command line every time.
Note: Any explicitly provided CLI arguments will override the list in pyproject.toml.
Example Workflow
A typical setup for an organization:
- Create a "standards" repo with your canonical
pyproject.tomlcontaining your shared[tool.ruff]config. - In each project, run
ruff-syncpointing at that repo — either manually, in a Makefile, or as a CI step. - When you update the standard, re-run
ruff-syncin each project to pull the changes. Your local comments andper-file-ignoresstay intact.
# In each project repo:
ruff-sync https://github.com/my-org/python-standards/blob/main/pyproject.toml
git diff pyproject.toml # review the changes
git commit -am "sync ruff config from upstream"
Contributing
This project uses:
- uv for dependency management
- Ruff for linting and formatting
- mypy for type checking (strict mode)
- pytest for testing
# Setup
uv sync --group dev
# Run checks
uv run ruff check . --fix # lint
uv run ruff format . # format
uv run mypy . # type check
uv run pytest -vv # test
Dogfooding
To see ruff-sync in action on a complex, real-world configuration, you can "dogfood" it by syncing this project's own pyproject.toml with a large upstream config like Pydantic's.
We've provided a script to make this easy:
./scripts/dogfood.sh
This will download Pydantic's Ruff configuration and merge it into the local pyproject.toml. You can then use git diff to see how it merged the keys while preserving the existing structure and comments.
To revert the changes after testing:
git checkout pyproject.toml
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ruff_sync-0.0.1.dev2.tar.gz.
File metadata
- Download URL: ruff_sync-0.0.1.dev2.tar.gz
- Upload date:
- Size: 88.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d91a0ee5901cb438feb6f7b1443d1bd5cab852cd684dbb6ff32adf21528ff748
|
|
| MD5 |
dad01eb63daebc78fe7892179f60eb59
|
|
| BLAKE2b-256 |
4ca41c53a49e26d2067a01fb4fb4db8827d1d04c4886657f3633ae4e7ea2fc11
|
File details
Details for the file ruff_sync-0.0.1.dev2-py3-none-any.whl.
File metadata
- Download URL: ruff_sync-0.0.1.dev2-py3-none-any.whl
- Upload date:
- Size: 8.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3eca132022002d9362e361b31bd124b665d384721fded539c33ca34591bdbca
|
|
| MD5 |
6a2a0fb5fe6deb59354b46d78d9825ab
|
|
| BLAKE2b-256 |
f2b3667c4765949bacae177bce7e527b3b2579d8301d0d83f3469f3c71a84bf9
|