Skip to content

Commit 310721a

Browse files
fix: ruff now uses afterpython/ruff.toml for the whole project, not only files inside afterpython/
- ap init adds [tool.ruff] extend = "afterpython/ruff.toml" to pyproject.toml, so plain ruff, ap format and editors find the config without --config - src = ["..", "../src"] in ruff.toml keeps import sorting correct for files inside afterpython/ - ap init skips the ruff setup if a root ruff.toml/.ruff.toml or [tool.ruff] already exists; new `ap init ruff` re-runs it - drop --config from ap check and the ruff pre-commit hooks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 71874ed commit 310721a

8 files changed

Lines changed: 106 additions & 33 deletions

File tree

‎afterpython/.pre-commit-config.yaml‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,9 @@ repos:
3131
- id: ruff-check
3232
stages:
3333
- pre-commit
34-
args:
35-
- --config
36-
- ./afterpython/ruff.toml
3734
- id: ruff-format
3835
stages:
3936
- pre-commit
40-
args:
41-
- --config
42-
- ./afterpython/ruff.toml
4337
- repo: local
4438
hooks:
4539
# same id twice so that SKIP=ap-commit skips both

‎afterpython/doc/package_maintenance/package_management.md‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,24 @@ Example wrapper:
2121
## Linting and Formatting
2222
`afterpython` uses [ruff] for linting and formatting, configured in `afterpython/ruff.toml`.
2323

24-
Example wrapper:
25-
- `ap check` (alias: `ap lint`) runs `ruff check --config ./afterpython/ruff.toml`.
26-
27-
Or you can just directly use `ruff` command as usual since it will automatically find the config file in `afterpython/ruff.toml`.
24+
`ap init` also adds this to your `pyproject.toml`, so that ruff always finds `afterpython/ruff.toml`:
25+
```toml
26+
[tool.ruff]
27+
extend = "afterpython/ruff.toml"
28+
```
29+
Keep all your ruff settings in `afterpython/ruff.toml`, not in `pyproject.toml` or a `ruff.toml` at the project root.
30+
If `ap init` finds an existing ruff config there, it skips the ruff setup; remove it and run `ap init ruff` to set it up again.
31+
32+
Wrappers (all arguments are passed through to ruff):
33+
- `ap check` (alias: `ap lint`) runs `ruff check`, e.g. `ap check --fix`.
34+
- `ap format` runs `ruff format`, e.g. `ap format --check`.
35+
36+
Or you can just directly use the `ruff` command (and ruff editor extensions) as usual, they use the same config.
37+
38+
:::{note}
39+
Relative paths in `afterpython/ruff.toml` are resolved from `afterpython/`, not the project root,
40+
e.g. use `"../tests/*"` in `per-file-ignores` to target your project's `tests/` folder.
41+
:::
2842

2943

3044
---

‎afterpython/ruff.toml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Ruff configuration
22
# See More: https://docs.astral.sh/ruff/configuration/
3+
#
4+
# NOTE: relative paths in this file are resolved from afterpython/, not the
5+
# project root, e.g. use "../tests/*" to target the project's tests/ folder.
6+
7+
# where your own (first-party) packages live, used by import sorting:
8+
# ".." = project root (flat layout), "../src" = src layout
9+
src = ["..", "../src"]
310

411
line-length = 88
512
target-version = "py311"

‎pyproject.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ module-root = "src"
7878

7979
[tool.pyright]
8080
typeCheckingMode = "basic"
81+
82+
[tool.ruff]
83+
extend = "afterpython/ruff.toml"

‎src/afterpython/cli/commands/check.py‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
)
1414
@click.pass_context
1515
def check(ctx):
16-
"""Run ruff linter (uses afterpython/ruff.toml if available)"""
16+
"""Run ruff linter"""
1717
from afterpython.utils import handle_passthrough_help
1818

1919
# Show both our options and ruff's help and exit
@@ -23,14 +23,7 @@ def check(ctx):
2323
show_underlying=True,
2424
)
2525

26-
paths = ctx.obj["paths"]
27-
ruff_toml = paths.afterpython_path / "ruff.toml"
28-
if ruff_toml.exists():
29-
click.echo(f"Using ruff configuration from {ruff_toml}")
30-
result = subprocess.run(
31-
["ruff", "check", "--config", str(ruff_toml), *ctx.args], check=False
32-
)
33-
else:
34-
result = subprocess.run(["ruff", "check", *ctx.args], check=False)
26+
# no --config: ruff finds afterpython/ruff.toml via pyproject.toml's [tool.ruff] extend
27+
result = subprocess.run(["ruff", "check", *ctx.args], check=False)
3528
if result.returncode != 0:
3629
raise Exit(result.returncode)

‎src/afterpython/cli/commands/init.py‎

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,65 @@
55

66
import afterpython as ap
77

8+
# pyproject.toml's [tool.ruff] only points to afterpython/ruff.toml,
9+
# so that running `ruff` directly (or via editors) also uses afterpython/ruff.toml
10+
RUFF_EXTEND = "afterpython/ruff.toml"
11+
12+
13+
def _find_ruff_config_conflicts() -> list[str]:
14+
"""Find ruff configs at the project root that would compete with afterpython/ruff.toml."""
15+
from afterpython.tools.pyproject import read_pyproject
16+
17+
conflicts = [
18+
name
19+
for name in ("ruff.toml", ".ruff.toml")
20+
if (ap.paths.user_path / name).exists()
21+
]
22+
tool_ruff = read_pyproject().get("tool", {}).get("ruff")
23+
if tool_ruff is not None and tool_ruff.unwrap() != {"extend": RUFF_EXTEND}:
24+
conflicts.append("[tool.ruff] in pyproject.toml")
25+
return conflicts
26+
27+
28+
def init_ruff():
29+
import tomlkit
30+
31+
from afterpython.tools.pyproject import read_pyproject, write_pyproject
32+
33+
if conflicts := _find_ruff_config_conflicts():
34+
click.echo(
35+
click.style(
36+
f"Skipped ruff setup: found existing ruff config ({', '.join(conflicts)}).\n",
37+
fg="yellow",
38+
bold=True,
39+
)
40+
+ "afterpython keeps all ruff settings in afterpython/ruff.toml, "
41+
"and pyproject.toml's [tool.ruff] only points to it.\n"
42+
"Until then, `ap check` and `ap format` will use your existing ruff config, "
43+
"not afterpython's.\n"
44+
"To fix: remove the config(s) above (keep a copy of any settings you want), "
45+
"run `ap init ruff`, then add your settings to afterpython/ruff.toml."
46+
)
47+
return
848

9-
def init_ruff_toml():
1049
ruff_toml_path = ap.paths.afterpython_path / "ruff.toml"
1150
if ruff_toml_path.exists():
1251
click.echo(f"Ruff configuration file {ruff_toml_path} already exists")
13-
return
14-
ruff_template_path = ap.paths.templates_path / "ruff-template.toml"
15-
shutil.copy(ruff_template_path, ruff_toml_path)
16-
click.echo(f"Created {ruff_toml_path}")
52+
else:
53+
ruff_template_path = ap.paths.templates_path / "ruff-template.toml"
54+
shutil.copy(ruff_template_path, ruff_toml_path)
55+
click.echo(f"Created {ruff_toml_path}")
56+
57+
data = read_pyproject()
58+
if "ruff" in data.get("tool", {}):
59+
return # already points to afterpython/ruff.toml (checked above)
60+
if "tool" not in data:
61+
data["tool"] = tomlkit.table(is_super_table=True)
62+
ruff_table = tomlkit.table()
63+
ruff_table["extend"] = RUFF_EXTEND
64+
data["tool"]["ruff"] = ruff_table
65+
write_pyproject(data)
66+
click.echo(f'Added [tool.ruff] extend = "{RUFF_EXTEND}" to pyproject.toml')
1767

1868

1969
def init_faq():
@@ -110,9 +160,10 @@ def init(ctx, yes, skip_website: bool):
110160
init_prek()
111161

112162
if yes or click.confirm(
113-
f"\nCreate ruff.toml in {afterpython_path}?", default=True
163+
f"\nCreate ruff.toml in {afterpython_path} (and point pyproject.toml's [tool.ruff] to it)?",
164+
default=True,
114165
):
115-
init_ruff_toml()
166+
init_ruff()
116167

117168
if yes or click.confirm(
118169
"\nCreate release workflow in .github/workflows/release.yml?",
@@ -133,6 +184,16 @@ def init(ctx, yes, skip_website: bool):
133184
raise
134185

135186

187+
@init.command("ruff")
188+
def init_ruff_subcommand():
189+
"""Initialize ruff config (afterpython/ruff.toml + [tool.ruff] in pyproject.toml)
190+
191+
Use this to re-run the ruff setup, e.g. after removing a conflicting
192+
ruff config that made `ap init` skip it.
193+
"""
194+
init_ruff()
195+
196+
136197
@init.command("website")
137198
def init_website_subcommand():
138199
"""Initialize project website (MyST config, template, deploy workflow)

‎src/afterpython/templates/pre-commit-config-template.yaml‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,9 @@ repos:
2929
- id: ruff-check
3030
stages:
3131
- pre-commit
32-
args:
33-
- --config
34-
- ./afterpython/ruff.toml
3532
- id: ruff-format
3633
stages:
3734
- pre-commit
38-
args:
39-
- --config
40-
- ./afterpython/ruff.toml
4135
- repo: local
4236
hooks:
4337
# same id twice so that SKIP=ap-commit skips both

‎src/afterpython/templates/ruff-template.toml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Ruff configuration
22
# See More: https://docs.astral.sh/ruff/configuration/
3+
#
4+
# NOTE: relative paths in this file are resolved from afterpython/, not the
5+
# project root, e.g. use "../tests/*" to target the project's tests/ folder.
6+
7+
# where your own (first-party) packages live, used by import sorting:
8+
# ".." = project root (flat layout), "../src" = src layout
9+
src = ["..", "../src"]
310

411
line-length = 88
512
target-version = "py311"

0 commit comments

Comments
 (0)