ruff-sync 0.0.5.dev1__tar.gz → 0.0.5.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/.github/workflows/complexity.yaml +8 -12
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/PKG-INFO +12 -10
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/README.md +10 -9
- ruff_sync-0.0.5.dev2/configs/fastapi/ruff.toml +150 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/pyproject.toml +9 -2
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/ruff_sync.py +88 -39
- ruff_sync-0.0.5.dev2/tests/conftest.py +12 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_basic.py +36 -21
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_check.py +6 -6
- ruff_sync-0.0.5.dev2/tests/test_deprecation.py +213 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_e2e.py +6 -6
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_scaffold.py +6 -6
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/uv.lock +3 -1
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/.agents/TESTING.md +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/.agents/workflows/add-test-case.md +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/.git-blame-ignore-revs +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/.github/dependabot.yml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/.github/workflows/ci.yaml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/.gitignore +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/.pre-commit-config.yaml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/AGENTS.md +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/LICENSE.md +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/codecov.yml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/configs/kitchen-sink/ruff.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/ruff_sync_banner.png +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/scripts/check_dogfood.sh +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/scripts/gitclone_dogfood.sh +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/scripts/pull_dogfood.sh +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tasks.py +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/__init__.py +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/standard_final.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/standard_initial.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/ruff.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_config_validation.py +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_corner_cases.py +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_git_fetch.py +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_project.py +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_toml_operations.py +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_url_handling.py +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/test_whitespace.py +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/wo_ruff_cfg/pyproject.toml +0 -0
- {ruff_sync-0.0.5.dev1 → ruff_sync-0.0.5.dev2}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
|
@@ -12,14 +12,13 @@ jobs:
|
|
|
12
12
|
|
|
13
13
|
steps:
|
|
14
14
|
- name: Checkout repository
|
|
15
|
-
uses: actions/checkout@
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
16
|
with:
|
|
17
17
|
fetch-depth: 0
|
|
18
|
-
ref: ${{ github.event.pull_request.head.ref }}
|
|
19
18
|
- name: Set up Python
|
|
20
|
-
uses: actions/setup-python@
|
|
19
|
+
uses: actions/setup-python@v5
|
|
21
20
|
with:
|
|
22
|
-
python-version: 3.
|
|
21
|
+
python-version: "3.10"
|
|
23
22
|
- name: Install Wily
|
|
24
23
|
run: pip install 'wily>=1.25.0'
|
|
25
24
|
- name: Build cache and diff
|
|
@@ -33,26 +32,23 @@ jobs:
|
|
|
33
32
|
DIFF="${DIFF//'%'/'%25'}"
|
|
34
33
|
DIFF="${DIFF//$'\n'/'%0A'}"
|
|
35
34
|
DIFF="${DIFF//$'\r'/'%0D'}"
|
|
36
|
-
echo "
|
|
37
|
-
- name: Find current PR
|
|
38
|
-
uses: jwalton/gh-find-current-pr@v1
|
|
39
|
-
id: findPr
|
|
35
|
+
echo "diff=$DIFF" >> "$GITHUB_OUTPUT"
|
|
40
36
|
- name: Add Wily PR Comment
|
|
41
37
|
uses: marocchino/sticky-pull-request-comment@v2
|
|
42
|
-
if:
|
|
38
|
+
if: github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.number && steps.wily.outputs.diff != ''
|
|
43
39
|
with:
|
|
44
40
|
recreate: true
|
|
45
|
-
number: ${{
|
|
41
|
+
number: ${{ github.event.pull_request.number }}
|
|
46
42
|
message: |
|
|
47
43
|
```
|
|
48
44
|
${{ steps.wily.outputs.diff }}
|
|
49
45
|
```
|
|
50
46
|
- name: Add Wily PR Comment
|
|
51
47
|
uses: marocchino/sticky-pull-request-comment@v2
|
|
52
|
-
if:
|
|
48
|
+
if: github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.number && steps.wily.outputs.diff == ''
|
|
53
49
|
with:
|
|
54
50
|
recreate: true
|
|
55
|
-
number: ${{
|
|
51
|
+
number: ${{ github.event.pull_request.number }}
|
|
56
52
|
message: |
|
|
57
53
|
```
|
|
58
54
|
Wily: No changes in complexity detected.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ruff-sync
|
|
3
|
-
Version: 0.0.5.
|
|
3
|
+
Version: 0.0.5.dev2
|
|
4
4
|
Summary: Synchronize Ruff linter configuration across projects
|
|
5
5
|
Project-URL: Homepage, https://github.com/Kilo59/ruff-sync
|
|
6
6
|
Project-URL: Documentation, https://github.com/Kilo59/ruff-sync#readme
|
|
@@ -26,6 +26,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
26
26
|
Requires-Python: >=3.10
|
|
27
27
|
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
28
28
|
Requires-Dist: tomlkit<2.0.0,>=0.12.3
|
|
29
|
+
Requires-Dist: typing-extensions>=4.5.0
|
|
29
30
|
Description-Content-Type: text/markdown
|
|
30
31
|
|
|
31
32
|
<p align="center">
|
|
@@ -56,7 +57,6 @@ Description-Content-Type: text/markdown
|
|
|
56
57
|
- [CI Integration](#ci-integration)
|
|
57
58
|
- [Example Workflow](#example-workflow)
|
|
58
59
|
- [Detailed Check Logic](#detailed-check-logic)
|
|
59
|
-
- [Contributing](#contributing)
|
|
60
60
|
- [Dogfooding](#dogfooding)
|
|
61
61
|
- [License](#license)
|
|
62
62
|
|
|
@@ -190,7 +190,7 @@ exclude = [
|
|
|
190
190
|
```
|
|
191
191
|
|
|
192
192
|
This sets the default upstream and exclusions so you don't need to pass them on the command line every time.
|
|
193
|
-
|
|
193
|
+
_Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`._
|
|
194
194
|
|
|
195
195
|
### Advanced Configuration
|
|
196
196
|
|
|
@@ -212,8 +212,11 @@ exclude = [
|
|
|
212
212
|
branch = "develop"
|
|
213
213
|
|
|
214
214
|
# A directory prefix to use when looking for a configuration file in a repository. (Default: "")
|
|
215
|
-
# Useful if the upstream
|
|
215
|
+
# Useful if the upstream config is not at the repository root.
|
|
216
216
|
path = "config/ruff"
|
|
217
|
+
|
|
218
|
+
# The local target directory or file to sync into. (Default: ".")
|
|
219
|
+
to = "."
|
|
217
220
|
```
|
|
218
221
|
|
|
219
222
|
## CI Integration
|
|
@@ -262,7 +265,7 @@ git commit -am "sync ruff config from upstream"
|
|
|
262
265
|
|
|
263
266
|
### Curated Examples
|
|
264
267
|
|
|
265
|
-
While `ruff-sync` is designed to sync from
|
|
268
|
+
While `ruff-sync` is designed to sync from _any_ repository or URL of your choosing, this repository also provides a few curated configurations in the [`configs/`](./configs/) directory that you can use directly.
|
|
266
269
|
|
|
267
270
|
For example, to sync an exhaustive "kitchen-sink" configuration that explicitly enables all rules and documents them:
|
|
268
271
|
|
|
@@ -287,8 +290,7 @@ By default, `ruff-sync` requires an existing configuration file (`pyproject.toml
|
|
|
287
290
|
ruff-sync pull https://github.com/my-org/standards --init
|
|
288
291
|
```
|
|
289
292
|
|
|
290
|
-
`ruff-sync` seamlessly supports both `pyproject.toml` and standalone `ruff.toml` (or `.ruff.toml`) files. If your upstream source or your local target is a `ruff.toml`, it will automatically adapt and sync the root configuration rather than looking for a `[tool.ruff]` section.
|
|
291
|
-
|
|
293
|
+
`ruff-sync` seamlessly supports both `pyproject.toml` and standalone `ruff.toml` (or `.ruff.toml`) files. If your local target is a directory, it will look for configuration files in the following order: `ruff.toml` -> `.ruff.toml` -> `pyproject.toml`. If your upstream source or your local target is a `ruff.toml`, it will automatically adapt and sync the root configuration rather than looking for a `[tool.ruff]` section.
|
|
292
294
|
|
|
293
295
|
## Detailed Check Logic
|
|
294
296
|
|
|
@@ -296,9 +298,9 @@ When you run `ruff-sync check`, it follows this process to determine if your pro
|
|
|
296
298
|
|
|
297
299
|
```mermaid
|
|
298
300
|
flowchart TD
|
|
299
|
-
Start([Start]) --> Local[Read Local
|
|
300
|
-
Local --> Upstream[Download Upstream
|
|
301
|
-
Upstream --> Extract[Extract tool.ruff section]
|
|
301
|
+
Start([Start]) --> Local[Read Local Configuration]
|
|
302
|
+
Local --> Upstream[Download Upstream Configuration]
|
|
303
|
+
Upstream --> Extract[Extract tool.ruff section if needed]
|
|
302
304
|
Extract --> Exclude[Apply Exclusions]
|
|
303
305
|
Exclude --> Merge[Perform in-memory Merge]
|
|
304
306
|
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
- [CI Integration](#ci-integration)
|
|
27
27
|
- [Example Workflow](#example-workflow)
|
|
28
28
|
- [Detailed Check Logic](#detailed-check-logic)
|
|
29
|
-
- [Contributing](#contributing)
|
|
30
29
|
- [Dogfooding](#dogfooding)
|
|
31
30
|
- [License](#license)
|
|
32
31
|
|
|
@@ -160,7 +159,7 @@ exclude = [
|
|
|
160
159
|
```
|
|
161
160
|
|
|
162
161
|
This sets the default upstream and exclusions so you don't need to pass them on the command line every time.
|
|
163
|
-
|
|
162
|
+
_Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`._
|
|
164
163
|
|
|
165
164
|
### Advanced Configuration
|
|
166
165
|
|
|
@@ -182,8 +181,11 @@ exclude = [
|
|
|
182
181
|
branch = "develop"
|
|
183
182
|
|
|
184
183
|
# A directory prefix to use when looking for a configuration file in a repository. (Default: "")
|
|
185
|
-
# Useful if the upstream
|
|
184
|
+
# Useful if the upstream config is not at the repository root.
|
|
186
185
|
path = "config/ruff"
|
|
186
|
+
|
|
187
|
+
# The local target directory or file to sync into. (Default: ".")
|
|
188
|
+
to = "."
|
|
187
189
|
```
|
|
188
190
|
|
|
189
191
|
## CI Integration
|
|
@@ -232,7 +234,7 @@ git commit -am "sync ruff config from upstream"
|
|
|
232
234
|
|
|
233
235
|
### Curated Examples
|
|
234
236
|
|
|
235
|
-
While `ruff-sync` is designed to sync from
|
|
237
|
+
While `ruff-sync` is designed to sync from _any_ repository or URL of your choosing, this repository also provides a few curated configurations in the [`configs/`](./configs/) directory that you can use directly.
|
|
236
238
|
|
|
237
239
|
For example, to sync an exhaustive "kitchen-sink" configuration that explicitly enables all rules and documents them:
|
|
238
240
|
|
|
@@ -257,8 +259,7 @@ By default, `ruff-sync` requires an existing configuration file (`pyproject.toml
|
|
|
257
259
|
ruff-sync pull https://github.com/my-org/standards --init
|
|
258
260
|
```
|
|
259
261
|
|
|
260
|
-
`ruff-sync` seamlessly supports both `pyproject.toml` and standalone `ruff.toml` (or `.ruff.toml`) files. If your upstream source or your local target is a `ruff.toml`, it will automatically adapt and sync the root configuration rather than looking for a `[tool.ruff]` section.
|
|
261
|
-
|
|
262
|
+
`ruff-sync` seamlessly supports both `pyproject.toml` and standalone `ruff.toml` (or `.ruff.toml`) files. If your local target is a directory, it will look for configuration files in the following order: `ruff.toml` -> `.ruff.toml` -> `pyproject.toml`. If your upstream source or your local target is a `ruff.toml`, it will automatically adapt and sync the root configuration rather than looking for a `[tool.ruff]` section.
|
|
262
263
|
|
|
263
264
|
## Detailed Check Logic
|
|
264
265
|
|
|
@@ -266,9 +267,9 @@ When you run `ruff-sync check`, it follows this process to determine if your pro
|
|
|
266
267
|
|
|
267
268
|
```mermaid
|
|
268
269
|
flowchart TD
|
|
269
|
-
Start([Start]) --> Local[Read Local
|
|
270
|
-
Local --> Upstream[Download Upstream
|
|
271
|
-
Upstream --> Extract[Extract tool.ruff section]
|
|
270
|
+
Start([Start]) --> Local[Read Local Configuration]
|
|
271
|
+
Local --> Upstream[Download Upstream Configuration]
|
|
272
|
+
Upstream --> Extract[Extract tool.ruff section if needed]
|
|
272
273
|
Extract --> Exclude[Apply Exclusions]
|
|
273
274
|
Exclude --> Merge[Perform in-memory Merge]
|
|
274
275
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# ruff-sync FastAPI Configuration
|
|
2
|
+
# https://github.com/Kilo59/ruff-sync
|
|
3
|
+
#
|
|
4
|
+
# This is a Ruff configuration tailored for FastAPI / Web App development.
|
|
5
|
+
# It enables rules directly relevant to web development and async Python.
|
|
6
|
+
#
|
|
7
|
+
# Usage (direct Ruff config, assuming this file is vendored into your repo):
|
|
8
|
+
# extend = "configs/fastapi/ruff.toml"
|
|
9
|
+
#
|
|
10
|
+
# Usage (with ruff-sync in pyproject.toml):
|
|
11
|
+
# [tool.ruff-sync]
|
|
12
|
+
# path = "configs/fastapi/ruff.toml"
|
|
13
|
+
|
|
14
|
+
# Same as Black.
|
|
15
|
+
line-length = 88
|
|
16
|
+
indent-width = 4
|
|
17
|
+
|
|
18
|
+
# Assume Python 3.10. Consumers should override this to match their project's Python version.
|
|
19
|
+
target-version = "py310"
|
|
20
|
+
|
|
21
|
+
[lint]
|
|
22
|
+
# Enable rules relevant to web development and async Python.
|
|
23
|
+
select = [
|
|
24
|
+
# https://docs.astral.sh/ruff/rules/#pyflakes-f
|
|
25
|
+
"F", # Pyflakes: Essential checks for Python bugs
|
|
26
|
+
# https://docs.astral.sh/ruff/rules/#error-e
|
|
27
|
+
"E", # pycodestyle errors: PEP8 styling
|
|
28
|
+
# https://docs.astral.sh/ruff/rules/#warning-w
|
|
29
|
+
"W", # pycodestyle warnings: PEP8 styling
|
|
30
|
+
# https://docs.astral.sh/ruff/rules/#mccabe-c90
|
|
31
|
+
"C90", # mccabe: Code complexity (cyclomatic complexity)
|
|
32
|
+
# https://docs.astral.sh/ruff/rules/#isort-i
|
|
33
|
+
"I", # isort: Import sorting
|
|
34
|
+
# https://docs.astral.sh/ruff/rules/#pep8-naming-n
|
|
35
|
+
"N", # pep8-naming: Naming conventions
|
|
36
|
+
# https://docs.astral.sh/ruff/rules/#pydocstyle-d
|
|
37
|
+
"D", # pydocstyle: Docstring conventions
|
|
38
|
+
# https://docs.astral.sh/ruff/rules/#pyupgrade-up
|
|
39
|
+
"UP", # pyupgrade: Upgrade syntax for newer Python versions
|
|
40
|
+
# https://docs.astral.sh/ruff/rules/#flake8-annotations-ann
|
|
41
|
+
"ANN", # flake8-annotations: Type annotation checks
|
|
42
|
+
# https://docs.astral.sh/ruff/rules/#flake8-async-async
|
|
43
|
+
"ASYNC", # flake8-async: Asynchronous code checks
|
|
44
|
+
# https://docs.astral.sh/ruff/rules/#flake8-bandit-s
|
|
45
|
+
"S", # flake8-bandit: Security testing
|
|
46
|
+
# https://docs.astral.sh/ruff/rules/#flake8-bugbear-b
|
|
47
|
+
"B", # flake8-bugbear: Finding likely bugs and design problems
|
|
48
|
+
# https://docs.astral.sh/ruff/rules/#flake8-pie-pie
|
|
49
|
+
"PIE", # flake8-pie: Misc. lints
|
|
50
|
+
# https://docs.astral.sh/ruff/rules/#flake8-print-t20
|
|
51
|
+
"T20", # flake8-print: Check for Print statements
|
|
52
|
+
# https://docs.astral.sh/ruff/rules/#flake8-return-ret
|
|
53
|
+
"RET", # flake8-return: Check return values
|
|
54
|
+
# https://docs.astral.sh/ruff/rules/#flake8-simplify-sim
|
|
55
|
+
"SIM", # flake8-simplify: Code simplification
|
|
56
|
+
# https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
|
|
57
|
+
"G", # flake8-logging-format: Validate logging format strings
|
|
58
|
+
# https://docs.astral.sh/ruff/rules/#flake8-quotes-q
|
|
59
|
+
"Q", # flake8-quotes: Lint for quotes
|
|
60
|
+
# https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt
|
|
61
|
+
"PT", # flake8-pytest-style: Pytest style checks
|
|
62
|
+
# https://docs.astral.sh/ruff/rules/#pylint-pl
|
|
63
|
+
"PL", # Pylint: Pylint rules
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
# Ignore rules that conflict with the Ruff formatter.
|
|
67
|
+
# See: https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
|
68
|
+
ignore = [
|
|
69
|
+
"W191", # tab-indentation
|
|
70
|
+
"E111", # indentation-with-invalid-multiple
|
|
71
|
+
"E114", # indentation-with-invalid-multiple-comment
|
|
72
|
+
"E117", # over-indented
|
|
73
|
+
"D206", # docstring-tab-indentation
|
|
74
|
+
"D300", # triple-single-quotes
|
|
75
|
+
"Q000", # bad-quotes-inline-string
|
|
76
|
+
"Q001", # bad-quotes-multiline-string
|
|
77
|
+
"Q002", # bad-quotes-docstring
|
|
78
|
+
"Q003", # avoidable-escaped-quote
|
|
79
|
+
"Q004", # unnecessary-escaped-quote
|
|
80
|
+
"COM812", # missing-trailing-comma
|
|
81
|
+
"COM819", # prohibited-trailing-comma
|
|
82
|
+
"ISC001", # single-line-implicit-string-concatenation
|
|
83
|
+
"ISC002", # multi-line-implicit-string-concatenation
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Allow autofix for all enabled rules.
|
|
87
|
+
fixable = ["ALL"]
|
|
88
|
+
|
|
89
|
+
# Allow unused variables when underscore-prefixed.
|
|
90
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
91
|
+
|
|
92
|
+
[lint.mccabe]
|
|
93
|
+
# Flag errors (C901) whenever the complexity level exceeds 10.
|
|
94
|
+
# Default: 10
|
|
95
|
+
max-complexity = 10
|
|
96
|
+
|
|
97
|
+
[lint.pydocstyle]
|
|
98
|
+
# Use Google-style docstrings (common in FastAPI projects).
|
|
99
|
+
# Default: "pep257"
|
|
100
|
+
convention = "google"
|
|
101
|
+
|
|
102
|
+
[lint.flake8-quotes]
|
|
103
|
+
# Use double quotes for inline strings (same as Black).
|
|
104
|
+
# Default: "double"
|
|
105
|
+
inline-quotes = "double"
|
|
106
|
+
# Use double quotes for multiline strings.
|
|
107
|
+
# Default: "double"
|
|
108
|
+
multiline-quotes = "double"
|
|
109
|
+
# Use double quotes for docstrings.
|
|
110
|
+
# Default: "double"
|
|
111
|
+
docstring-quotes = "double"
|
|
112
|
+
# Avoid escaping quotes if the other quote type would save an escape.
|
|
113
|
+
# Default: true
|
|
114
|
+
avoid-escape = true
|
|
115
|
+
|
|
116
|
+
[lint.flake8-pytest-style]
|
|
117
|
+
# Whether to require parentheses for pytest fixtures.
|
|
118
|
+
# Default: true
|
|
119
|
+
fixture-parentheses = true
|
|
120
|
+
# The type of pytest.mark.parametrize names to use: "tuple", "list", or "csv"
|
|
121
|
+
# Default: "tuple"
|
|
122
|
+
parametrize-names-type = "tuple"
|
|
123
|
+
|
|
124
|
+
[lint.pylint]
|
|
125
|
+
# The maximum number of arguments allowed for a function.
|
|
126
|
+
# Default: 5
|
|
127
|
+
max-args = 5
|
|
128
|
+
# The maximum number of branches allowed for a function.
|
|
129
|
+
# Default: 12
|
|
130
|
+
max-branches = 12
|
|
131
|
+
|
|
132
|
+
[lint.pep8-naming]
|
|
133
|
+
# Additional decorators that should be treated as classmethod.
|
|
134
|
+
# Pydantic v1/v2 decorators.
|
|
135
|
+
classmethod-decorators = [
|
|
136
|
+
"pydantic.validator",
|
|
137
|
+
"pydantic.root_validator",
|
|
138
|
+
"pydantic.field_validator",
|
|
139
|
+
"pydantic.model_validator",
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
[format]
|
|
143
|
+
# Like Black, use double quotes for strings.
|
|
144
|
+
quote-style = "double"
|
|
145
|
+
# Like Black, indent with spaces, rather than tabs.
|
|
146
|
+
indent-style = "space"
|
|
147
|
+
# Like Black, respect magic trailing commas.
|
|
148
|
+
skip-magic-trailing-comma = false
|
|
149
|
+
# Like Black, automatically detect the appropriate line ending.
|
|
150
|
+
line-ending = "auto"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ruff-sync"
|
|
3
|
-
version = "0.0.5.
|
|
3
|
+
version = "0.0.5.dev2"
|
|
4
4
|
description = "Synchronize Ruff linter configuration across projects"
|
|
5
5
|
keywords = ["ruff", "linter", "config", "synchronize", "python", "linting", "automation", "tomlkit"]
|
|
6
6
|
authors = [
|
|
@@ -26,6 +26,7 @@ classifiers = [
|
|
|
26
26
|
dependencies = [
|
|
27
27
|
"httpx>=0.27.0,<1.0.0",
|
|
28
28
|
"tomlkit>=0.12.3,<2.0.0",
|
|
29
|
+
"typing-extensions>=4.5.0",
|
|
29
30
|
]
|
|
30
31
|
|
|
31
32
|
[project.urls]
|
|
@@ -77,7 +78,13 @@ module = "tests.*"
|
|
|
77
78
|
disable_error_code = ['type-arg', 'no-untyped-def']
|
|
78
79
|
|
|
79
80
|
[tool.ruff-sync]
|
|
80
|
-
exclude = [
|
|
81
|
+
exclude = [
|
|
82
|
+
"target-version",
|
|
83
|
+
"line-length",
|
|
84
|
+
"lint.per-file-ignores",
|
|
85
|
+
"lint.ignore",
|
|
86
|
+
"extend-exclude",
|
|
87
|
+
]
|
|
81
88
|
|
|
82
89
|
[tool.ruff]
|
|
83
90
|
target-version = "py310"
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
import difflib
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
|
+
import os
|
|
7
8
|
import pathlib
|
|
8
9
|
import re
|
|
9
10
|
import subprocess
|
|
@@ -22,8 +23,9 @@ from httpx import URL
|
|
|
22
23
|
from tomlkit import TOMLDocument, table
|
|
23
24
|
from tomlkit.items import Table
|
|
24
25
|
from tomlkit.toml_file import TOMLFile
|
|
26
|
+
from typing_extensions import deprecated
|
|
25
27
|
|
|
26
|
-
__version__ = "0.0.5.
|
|
28
|
+
__version__ = "0.0.5.dev2"
|
|
27
29
|
|
|
28
30
|
_DEFAULT_EXCLUDE: Final[set[str]] = {"lint.per-file-ignores"}
|
|
29
31
|
_GITHUB_REPO_PATH_PARTS_COUNT: Final[int] = 2
|
|
@@ -59,7 +61,7 @@ class ColoredFormatter(logging.Formatter):
|
|
|
59
61
|
class Arguments(NamedTuple):
|
|
60
62
|
command: str
|
|
61
63
|
upstream: URL
|
|
62
|
-
|
|
64
|
+
to: pathlib.Path
|
|
63
65
|
exclude: Iterable[str]
|
|
64
66
|
verbose: int
|
|
65
67
|
branch: str = "main"
|
|
@@ -68,15 +70,21 @@ class Arguments(NamedTuple):
|
|
|
68
70
|
diff: bool = True
|
|
69
71
|
init: bool = False
|
|
70
72
|
|
|
73
|
+
@property
|
|
74
|
+
@deprecated("Use 'to' instead")
|
|
75
|
+
def source(self) -> pathlib.Path:
|
|
76
|
+
return self.to
|
|
77
|
+
|
|
71
78
|
@classmethod
|
|
72
79
|
@lru_cache(maxsize=1)
|
|
73
80
|
def fields(cls) -> set[str]:
|
|
74
|
-
return set(cls._fields)
|
|
81
|
+
return set(cls._fields) | {"source"}
|
|
75
82
|
|
|
76
83
|
|
|
77
84
|
class Config(TypedDict, total=False):
|
|
78
85
|
upstream: str
|
|
79
|
-
|
|
86
|
+
to: str
|
|
87
|
+
source: str # Deprecated
|
|
80
88
|
exclude: list[str]
|
|
81
89
|
verbose: int
|
|
82
90
|
branch: str
|
|
@@ -101,19 +109,20 @@ def get_config(
|
|
|
101
109
|
allowed_keys = set(Config.__annotations__.keys())
|
|
102
110
|
for arg, value in config.items():
|
|
103
111
|
if arg in allowed_keys:
|
|
112
|
+
if arg == "source":
|
|
113
|
+
LOGGER.warning(
|
|
114
|
+
"DeprecationWarning: [tool.ruff-sync] 'source' is deprecated. "
|
|
115
|
+
"Use 'to' instead."
|
|
116
|
+
)
|
|
104
117
|
cfg_result[arg] = value
|
|
105
118
|
else:
|
|
106
119
|
LOGGER.warning(f"Unknown ruff-sync configuration: {arg}")
|
|
120
|
+
# Ensure 'to' is populated if 'source' was used
|
|
121
|
+
if "source" in cfg_result and "to" not in cfg_result:
|
|
122
|
+
cfg_result["to"] = cfg_result["source"]
|
|
107
123
|
return cast("Config", cfg_result)
|
|
108
124
|
|
|
109
125
|
|
|
110
|
-
@lru_cache(maxsize=1)
|
|
111
|
-
def _resolve_source(source: str | pathlib.Path) -> pathlib.Path:
|
|
112
|
-
if isinstance(source, str):
|
|
113
|
-
source = pathlib.Path(source)
|
|
114
|
-
return source.resolve(strict=True)
|
|
115
|
-
|
|
116
|
-
|
|
117
126
|
def _get_cli_parser() -> ArgumentParser:
|
|
118
127
|
parser = ArgumentParser(
|
|
119
128
|
prog="ruff-sync",
|
|
@@ -148,12 +157,17 @@ def _get_cli_parser() -> ArgumentParser:
|
|
|
148
157
|
help="The URL to download the pyproject.toml file from."
|
|
149
158
|
" Optional if defined in [tool.ruff-sync].",
|
|
150
159
|
)
|
|
160
|
+
common_parser.add_argument(
|
|
161
|
+
"--to",
|
|
162
|
+
help="The directory or file to sync ruff configuration to. Default: .",
|
|
163
|
+
required=False,
|
|
164
|
+
)
|
|
165
|
+
# Add --source as a deprecated alias
|
|
151
166
|
common_parser.add_argument(
|
|
152
167
|
"--source",
|
|
153
|
-
|
|
154
|
-
default=".",
|
|
155
|
-
help="The directory to sync the pyproject.toml file to. Default: .",
|
|
168
|
+
help="Deprecated alias for --to.",
|
|
156
169
|
required=False,
|
|
170
|
+
metavar="PATH",
|
|
157
171
|
)
|
|
158
172
|
common_parser.add_argument(
|
|
159
173
|
"--exclude",
|
|
@@ -216,9 +230,32 @@ def _get_cli_parser() -> ArgumentParser:
|
|
|
216
230
|
return parser
|
|
217
231
|
|
|
218
232
|
|
|
219
|
-
def
|
|
233
|
+
def _resolve_target_path(to: pathlib.Path, upstream_url: str | None = None) -> pathlib.Path:
|
|
220
234
|
"""Resolve the target path for configuration files.
|
|
221
235
|
|
|
236
|
+
If 'to' is a file, it's used directly.
|
|
237
|
+
Otherwise, it looks for existing ruff/pyproject.toml in the 'to' directory.
|
|
238
|
+
If none found, it defaults to pyproject.toml unless the upstream is a ruff.toml.
|
|
239
|
+
"""
|
|
240
|
+
if to.is_file():
|
|
241
|
+
return to
|
|
242
|
+
|
|
243
|
+
# If it's a directory, look for common config files
|
|
244
|
+
for filename in ("ruff.toml", ".ruff.toml", "pyproject.toml"):
|
|
245
|
+
candidate = to / filename
|
|
246
|
+
if candidate.exists():
|
|
247
|
+
return candidate
|
|
248
|
+
|
|
249
|
+
# If upstream is specified and is a ruff.toml, default to ruff.toml
|
|
250
|
+
if upstream_url and is_ruff_toml_file(upstream_url):
|
|
251
|
+
return to / "ruff.toml"
|
|
252
|
+
|
|
253
|
+
return to / "pyproject.toml"
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _resolve_upstream_target_path(path: str | None) -> str:
|
|
257
|
+
"""Resolve the target path for configuration files in upstream repositories.
|
|
258
|
+
|
|
222
259
|
If the path indicates a .toml file, it's treated as a direct file path.
|
|
223
260
|
Otherwise, it appends 'pyproject.toml' to the path.
|
|
224
261
|
"""
|
|
@@ -261,7 +298,7 @@ def _convert_github_url(url: URL, branch: str = "main", path: str = "") -> URL:
|
|
|
261
298
|
path_parts = [p for p in url.path.split("/") if p]
|
|
262
299
|
if len(path_parts) == _GITHUB_REPO_PATH_PARTS_COUNT:
|
|
263
300
|
org, repo = path_parts
|
|
264
|
-
target_path =
|
|
301
|
+
target_path = _resolve_upstream_target_path(path)
|
|
265
302
|
raw_url = url.copy_with(
|
|
266
303
|
host=_GITHUB_RAW_HOST,
|
|
267
304
|
path=str(pathlib.PurePosixPath("/", org, repo, branch, target_path)),
|
|
@@ -302,7 +339,7 @@ def _convert_gitlab_url(url: URL, branch: str = "main", path: str = "") -> URL:
|
|
|
302
339
|
# Avoid empty paths or just a slash
|
|
303
340
|
path_prefix = url.path.rstrip("/")
|
|
304
341
|
if path_prefix:
|
|
305
|
-
target_path =
|
|
342
|
+
target_path = _resolve_upstream_target_path(path)
|
|
306
343
|
raw_url = url.copy_with(
|
|
307
344
|
path=str(pathlib.PurePosixPath(path_prefix, "-", "raw", branch, target_path))
|
|
308
345
|
)
|
|
@@ -414,7 +451,7 @@ async def fetch_upstream_config(
|
|
|
414
451
|
capture_output=True,
|
|
415
452
|
text=True,
|
|
416
453
|
)
|
|
417
|
-
target_path = pathlib.Path(
|
|
454
|
+
target_path = pathlib.Path(_resolve_upstream_target_path(path))
|
|
418
455
|
|
|
419
456
|
# Restore just the pyproject_toml file
|
|
420
457
|
restore_cmd = [
|
|
@@ -674,25 +711,13 @@ def merge_ruff_toml(
|
|
|
674
711
|
return source
|
|
675
712
|
|
|
676
713
|
|
|
677
|
-
def _resolve_target_path(args: Arguments) -> pathlib.Path:
|
|
678
|
-
if args.source.is_file():
|
|
679
|
-
return args.source
|
|
680
|
-
if (args.source / "ruff.toml").exists():
|
|
681
|
-
return args.source / "ruff.toml"
|
|
682
|
-
if (args.source / ".ruff.toml").exists():
|
|
683
|
-
return args.source / ".ruff.toml"
|
|
684
|
-
if args.upstream and is_ruff_toml_file(str(args.upstream)):
|
|
685
|
-
return args.source / "ruff.toml"
|
|
686
|
-
return args.source / "pyproject.toml"
|
|
687
|
-
|
|
688
|
-
|
|
689
714
|
async def check(
|
|
690
715
|
args: Arguments,
|
|
691
716
|
) -> int:
|
|
692
717
|
"""Check if the local pyproject.toml / ruff.toml is in sync with the upstream."""
|
|
693
718
|
print("🔍 Checking Ruff sync status...")
|
|
694
719
|
|
|
695
|
-
_source_toml_path = _resolve_target_path(args).resolve(strict=False)
|
|
720
|
+
_source_toml_path = _resolve_target_path(args.to, str(args.upstream)).resolve(strict=False)
|
|
696
721
|
if not _source_toml_path.exists():
|
|
697
722
|
print(
|
|
698
723
|
f"❌ Configuration file {_source_toml_path} does not exist. "
|
|
@@ -780,7 +805,7 @@ async def pull(
|
|
|
780
805
|
) -> int:
|
|
781
806
|
"""Pull the upstream ruff config and apply it to the source."""
|
|
782
807
|
print("🔄 Syncing Ruff...")
|
|
783
|
-
_source_toml_path = _resolve_target_path(args).resolve(strict=False)
|
|
808
|
+
_source_toml_path = _resolve_target_path(args.to, str(args.upstream)).resolve(strict=False)
|
|
784
809
|
|
|
785
810
|
source_toml_file = TOMLFile(_source_toml_path)
|
|
786
811
|
if _source_toml_path.exists():
|
|
@@ -888,13 +913,33 @@ def _resolve_path(args: Any, config: Mapping[str, Any]) -> str:
|
|
|
888
913
|
return ""
|
|
889
914
|
|
|
890
915
|
|
|
891
|
-
def
|
|
892
|
-
"""Resolve
|
|
916
|
+
def _resolve_to(args: Any, config: Mapping[str, Any], initial_to: pathlib.Path) -> pathlib.Path:
|
|
917
|
+
"""Resolve target path from CLI, config, or default."""
|
|
918
|
+
if args.source:
|
|
919
|
+
LOGGER.warning("DeprecationWarning: --source is deprecated. Use --to instead.")
|
|
920
|
+
return pathlib.Path(args.source)
|
|
921
|
+
if args.to:
|
|
922
|
+
return pathlib.Path(args.to)
|
|
923
|
+
if "to" in config:
|
|
924
|
+
target = pathlib.Path(config["to"])
|
|
925
|
+
# Resolve relative to the directory where we found the config file
|
|
926
|
+
base_dir = initial_to.parent if initial_to.is_file() else initial_to
|
|
927
|
+
resolved = base_dir / target
|
|
928
|
+
LOGGER.info(f"🎯 Using target path from [tool.ruff-sync]: {resolved}")
|
|
929
|
+
return resolved
|
|
930
|
+
return initial_to
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
def _resolve_args(
|
|
934
|
+
args: Any, config: Mapping[str, Any], initial_to: pathlib.Path
|
|
935
|
+
) -> tuple[URL, pathlib.Path, Iterable[str], str, str]:
|
|
936
|
+
"""Resolve upstream, to, exclude, branch, and path from CLI and config."""
|
|
893
937
|
upstream = _resolve_upstream(args, config)
|
|
938
|
+
to = _resolve_to(args, config, initial_to)
|
|
894
939
|
exclude = _resolve_exclude(args, config)
|
|
895
940
|
branch = _resolve_branch(args, config)
|
|
896
941
|
path = _resolve_path(args, config)
|
|
897
|
-
return upstream, exclude, branch, path
|
|
942
|
+
return upstream, to, exclude, branch, path
|
|
898
943
|
|
|
899
944
|
|
|
900
945
|
def main() -> int:
|
|
@@ -922,11 +967,15 @@ def main() -> int:
|
|
|
922
967
|
handler = logging.StreamHandler()
|
|
923
968
|
handler.setFormatter(ColoredFormatter())
|
|
924
969
|
LOGGER.addHandler(handler)
|
|
925
|
-
LOGGER.propagate =
|
|
970
|
+
LOGGER.propagate = "PYTEST_CURRENT_TEST" in os.environ # Allow capturing in tests
|
|
926
971
|
|
|
927
|
-
|
|
972
|
+
# Determine target 'to' from CLI or use default '.'
|
|
973
|
+
# Defer Path conversion to avoid pyfakefs issues with captured Path class
|
|
974
|
+
arg_to = args.to or args.source
|
|
975
|
+
initial_to = pathlib.Path(arg_to) if arg_to else pathlib.Path()
|
|
976
|
+
config: Config = get_config(initial_to)
|
|
928
977
|
|
|
929
|
-
upstream, exclude, branch, path = _resolve_args(args, config)
|
|
978
|
+
upstream, to_val, exclude, branch, path = _resolve_args(args, config, initial_to)
|
|
930
979
|
|
|
931
980
|
# Convert non-raw github/gitlab upstream url to the raw equivalent
|
|
932
981
|
upstream = resolve_raw_url(upstream, branch=branch, path=path)
|
|
@@ -935,7 +984,7 @@ def main() -> int:
|
|
|
935
984
|
exec_args = Arguments(
|
|
936
985
|
command=args.command,
|
|
937
986
|
upstream=upstream,
|
|
938
|
-
|
|
987
|
+
to=to_val,
|
|
939
988
|
exclude=exclude,
|
|
940
989
|
verbose=args.verbose,
|
|
941
990
|
branch=branch,
|