Skip to content

Commit 5f9ff7d

Browse files
feat: add new commands "add", "remove", "lock" to manage both uv and pixi (optional) more conveniently
1 parent f4491a9 commit 5f9ff7d

File tree

13 files changed

+5423
-1613
lines changed

13 files changed

+5423
-1613
lines changed

afterpython/.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ repos:
2323
stages:
2424
- pre-commit
2525
- repo: https://github.com/astral-sh/ruff-pre-commit
26-
rev: v0.14.5
26+
rev: v0.14.6
2727
hooks:
2828
- id: ruff-check
2929
stages:

afterpython/cli/commands/add.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import subprocess
2+
3+
import click
4+
from click.exceptions import Exit
5+
6+
from afterpython.utils import has_pixi, has_uv
7+
8+
9+
@click.command()
10+
@click.option(
11+
"--optional",
12+
type=str,
13+
default=None,
14+
required=False,
15+
help="Add to an optional dependency group (pixi: mapped to 'optional' feature)",
16+
)
17+
@click.option(
18+
"--group",
19+
type=str,
20+
default=None,
21+
required=False,
22+
help="Add to a dependency group (pixi: mapped to same-named feature)",
23+
)
24+
@click.argument("lib", type=str, required=True)
25+
def add(optional: str | None, group: str | None, lib: str):
26+
"""Add a new dependency to the project (manages both uv and pixi if present)"""
27+
if not has_uv():
28+
click.echo("uv not found. Please install uv first.")
29+
return
30+
31+
if optional and group:
32+
click.echo("Error: Cannot specify both --optional and --group")
33+
raise Exit(1)
34+
35+
result = subprocess.run(
36+
[
37+
"uv",
38+
"add",
39+
*(["--optional", optional] if optional else []),
40+
*(["--group", group] if group else []),
41+
lib,
42+
],
43+
check=False,
44+
)
45+
if result.returncode != 0:
46+
raise Exit(result.returncode)
47+
48+
if has_pixi():
49+
# NOTE: pixi doesn't support --optional, so we need to use --feature instead
50+
if optional:
51+
feature_name = "optional"
52+
elif group:
53+
feature_name = group
54+
else:
55+
feature_name = None
56+
result = subprocess.run(
57+
[
58+
"pixi",
59+
"add",
60+
"--pypi",
61+
*(["--feature", feature_name] if feature_name else []),
62+
lib,
63+
],
64+
check=False,
65+
)
66+
if result.returncode != 0:
67+
raise Exit(result.returncode)

afterpython/cli/commands/init.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def init_ruff_toml():
2222
"repos": [
2323
{
2424
"repo": "https://github.com/astral-sh/ruff-pre-commit",
25-
"rev": "v0.14.5",
25+
"rev": "v0.14.6",
2626
"hooks": [
2727
{
2828
"id": "ruff-check",

afterpython/cli/commands/install.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import click
44
from click.exceptions import Exit
55

6-
from afterpython.utils import has_uv
6+
from afterpython.utils import has_pixi, has_uv
77

88

99
@click.command(
@@ -13,10 +13,16 @@
1313
)
1414
)
1515
def install():
16-
"""Run 'uv sync --all-extras --all-groups' to install all dependencies"""
16+
"""Install all dependencies (runs 'uv sync --all-extras --all-groups' and 'pixi install' if present)"""
1717
if not has_uv():
1818
click.echo("uv not found. Please install uv first.")
1919
return
20+
2021
result = subprocess.run(["uv", "sync", "--all-extras", "--all-groups"], check=False)
2122
if result.returncode != 0:
2223
raise Exit(result.returncode)
24+
25+
if has_pixi():
26+
result = subprocess.run(["pixi", "install"], check=False)
27+
if result.returncode != 0:
28+
raise Exit(result.returncode)

afterpython/cli/commands/lock.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import subprocess
2+
3+
import click
4+
from click.exceptions import Exit
5+
6+
from afterpython.utils import has_pixi, has_uv
7+
8+
9+
@click.command()
10+
def lock():
11+
"""Lock the dependencies (runs 'uv lock' and 'pixi lock' if present)"""
12+
if not has_uv():
13+
click.echo("uv not found. Please install uv first.")
14+
return
15+
16+
result = subprocess.run(["uv", "lock"], check=False)
17+
if result.returncode != 0:
18+
raise Exit(result.returncode)
19+
20+
if has_pixi():
21+
result = subprocess.run(["pixi", "lock"], check=False)
22+
if result.returncode != 0:
23+
raise Exit(result.returncode)

afterpython/cli/commands/remove.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import subprocess
2+
3+
import click
4+
from click.exceptions import Exit
5+
6+
from afterpython.utils import has_pixi, has_uv
7+
8+
9+
@click.command()
10+
@click.option(
11+
"--optional",
12+
type=str,
13+
default=None,
14+
required=False,
15+
help="Remove from an optional dependency group (pixi: mapped to 'optional' feature)",
16+
)
17+
@click.option(
18+
"--group",
19+
type=str,
20+
default=None,
21+
required=False,
22+
help="Remove from a dependency group (pixi: mapped to same-named feature)",
23+
)
24+
@click.argument("lib", type=str, required=True)
25+
def remove(optional: str | None, group: str | None, lib: str):
26+
"""Remove a dependency from the project (manages both uv and pixi if present)"""
27+
if not has_uv():
28+
click.echo("uv not found. Please install uv first.")
29+
return
30+
31+
if optional and group:
32+
click.echo("Error: Cannot specify both --optional and --group")
33+
raise Exit(1)
34+
35+
result = subprocess.run(
36+
[
37+
"uv",
38+
"remove",
39+
*(["--optional", optional] if optional else []),
40+
*(["--group", group] if group else []),
41+
lib,
42+
],
43+
check=False,
44+
)
45+
if result.returncode != 0:
46+
raise Exit(result.returncode)
47+
48+
if has_pixi():
49+
# NOTE: pixi doesn't support --optional, so we need to use --feature instead
50+
if optional:
51+
feature_name = "optional"
52+
elif group:
53+
feature_name = group
54+
else:
55+
feature_name = None
56+
result = subprocess.run(
57+
[
58+
"pixi",
59+
"remove",
60+
"--pypi",
61+
*(["--feature", feature_name] if feature_name else []),
62+
lib,
63+
],
64+
check=False,
65+
)
66+
if result.returncode != 0:
67+
raise Exit(result.returncode)

afterpython/cli/commands/update.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def update():
3434
def dependencies(upgrade: bool, all: bool):
3535
"""Update pyproject.toml dependencies to the latest version"""
3636
from afterpython.pcu import get_dependencies, update_dependencies
37-
from afterpython.utils import has_uv
37+
from afterpython.utils import has_pixi, has_uv
3838

3939
dependencies: Dependencies = get_dependencies()
4040
has_at_least_one_update = False
@@ -80,7 +80,9 @@ def dependencies(upgrade: bool, all: bool):
8080
raise Exit(result.returncode)
8181
click.echo(
8282
click.style(
83-
"✓ All dependencies upgraded successfully 🎉", fg="green", bold=True
83+
"✓ All dependencies in pyproject.toml upgraded successfully 🎉",
84+
fg="green",
85+
bold=True,
8486
)
8587
)
8688
else:
@@ -90,6 +92,26 @@ def dependencies(upgrade: bool, all: bool):
9092
if all:
9193
subprocess.run(["ap", "pre-commit", "autoupdate"])
9294
click.echo("All pre-commit hooks updated successfully.")
95+
if has_pixi():
96+
click.echo("Upgrading dependencies with pixi...")
97+
result = subprocess.run(
98+
["pixi", "upgrade", "--exclude", "python"], check=False
99+
)
100+
if result.returncode != 0:
101+
raise Exit(result.returncode)
102+
result = subprocess.run(["pixi", "lock"], check=False)
103+
if result.returncode != 0:
104+
raise Exit(result.returncode)
105+
result = subprocess.run(["pixi", "install"], check=False)
106+
if result.returncode != 0:
107+
raise Exit(result.returncode)
108+
click.echo(
109+
click.style(
110+
"✓ All dependencies in pixi.toml upgraded successfully 🎉",
111+
fg="green",
112+
bold=True,
113+
)
114+
)
93115

94116

95117
update.add_command(dependencies, name="deps") # alias for "dependencies"

afterpython/cli/main.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import afterpython as ap
1010
from afterpython import __version__
11+
from afterpython.cli.commands.add import add
1112
from afterpython.cli.commands.build import build
1213
from afterpython.cli.commands.bump import bump
1314
from afterpython.cli.commands.check import check
@@ -19,9 +20,11 @@
1920
from afterpython.cli.commands.init import init
2021
from afterpython.cli.commands.init_branch_rules import init_branch_rules
2122
from afterpython.cli.commands.install import install
23+
from afterpython.cli.commands.lock import lock
2224
from afterpython.cli.commands.pre_commit import pre_commit
2325
from afterpython.cli.commands.preview import preview
2426
from afterpython.cli.commands.release import release
27+
from afterpython.cli.commands.remove import remove
2528
from afterpython.cli.commands.start import blog, doc, example, guide, start, tutorial
2629
from afterpython.cli.commands.sync import sync
2730
from afterpython.cli.commands.update import update
@@ -124,3 +127,6 @@ def afterpython_group(ctx):
124127
afterpython_group.add_command(bump)
125128
afterpython_group.add_command(release)
126129
afterpython_group.add_command(init_branch_rules)
130+
afterpython_group.add_command(add)
131+
afterpython_group.add_command(remove)
132+
afterpython_group.add_command(lock)

afterpython/doc/references/roadmap.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ This roadmap is tentative and subject to change
1010
- full-text search engine using pagefind
1111
- incremental build, only build changed content (for `ap dev`)
1212
- integrate with `git-cliff` for changelog generation
13-
- integrate with `pixi`, supports `conda install`
1413
- supports docs built by different engines? e.g. Sphix, MkDocs
1514
- update `afterpython` itself using `ap update afterpython`
1615
- it merges the new defaults in a newer version of `afterpython` into your project
1716
- very difficult, need to create an interactive UX to show the diffs and let the user choose to merge or not
17+
- add type checker using `ty`
1818
- support python 3.14
19+
- integrate with `pixi`, supports `conda install`

0 commit comments

Comments
 (0)