Skip to content

Commit 04116af

Browse files
authored
Fix: 使用更好的管理器检测策略 (#41)
- 更新项目到 Python 3.10+ - 升级 ruff 版本到 0.15.0+ - 使用 NB-CLI 1.7.0 的管理器检测策略 - Dockerfile 模板中使用 uv 代替 pipx
1 parent 199cd31 commit 04116af

6 files changed

Lines changed: 133 additions & 196 deletions

File tree

nb_cli_plugin_docker/cli.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import cast
2-
from pathlib import Path
32

3+
import anyio
44
import click
55
from nb_cli import _
66
from nb_cli.config import ConfigManager
@@ -69,6 +69,7 @@ async def generate(ctx: click.Context, force: bool):
6969
"""Generate Dockerfile and docker-compose.yml."""
7070
python_path = await get_default_python()
7171
cwd = get_project_root()
72+
async_cwd = anyio.Path(cwd)
7273

7374
python_version = await get_python_version(python_path=python_path)
7475
python_version = f"{python_version['major']}.{python_version['minor']}"
@@ -84,18 +85,22 @@ async def generate(ctx: click.Context, force: bool):
8485
is_asgi=is_asgi,
8586
build_backend=build_backend,
8687
)
87-
await safe_write_file(cwd / "Dockerfile", dockerfile, force=force)
88+
await safe_write_file(async_cwd / "Dockerfile", dockerfile, force=force)
8889

8990
compose_file = await generate_compose_file(is_asgi=is_asgi)
90-
await safe_write_file(cwd / "docker-compose.yml", compose_file, force=force)
91+
await safe_write_file(
92+
async_cwd / "docker-compose.yml", compose_file, force=force
93+
)
9194

9295
await safe_copy_dir(
93-
Path(__file__).parent / "static" / "common", cwd, force=force
96+
anyio.Path(__file__).parent / "static" / "common", async_cwd, force=force
9497
)
9598

9699
if is_asgi:
97100
await safe_copy_dir(
98-
Path(__file__).parent / "static" / "reverse", cwd, force=force
101+
anyio.Path(__file__).parent / "static" / "reverse",
102+
async_cwd,
103+
force=force,
99104
)
100105
except CancelledError:
101106
ctx.exit()
@@ -118,8 +123,8 @@ async def up(ctx: click.Context, force: bool, compose_args: list[str]):
118123

119124
if (
120125
force
121-
or not Path(cwd, "Dockerfile").exists()
122-
or not Path(cwd, "docker-compose.yml").exists()
126+
or not await anyio.Path(cwd, "Dockerfile").exists()
127+
or not await anyio.Path(cwd, "docker-compose.yml").exists()
123128
):
124129
await run_sync(ctx.invoke)(generate)
125130

nb_cli_plugin_docker/handler.py

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import asyncio
33
from pathlib import Path
44
from dataclasses import dataclass
5-
from typing import IO, TYPE_CHECKING, Any, Union, Literal, Optional, cast
5+
from typing import IO, TYPE_CHECKING, Any, Literal, cast
66

77
from nb_cli import cache
88
from jinja2 import Environment, FileSystemLoader
@@ -14,6 +14,7 @@
1414
get_default_python,
1515
get_nonebot_config,
1616
ensure_process_terminated,
17+
probe_environment_manager,
1718
)
1819

1920
from .exception import GetDriverTypeError, ComposeNotAvailable
@@ -67,11 +68,11 @@ async def get_compose_command() -> Compose:
6768

6869
@ensure_process_terminated
6970
async def call_compose(
70-
compose_args: Optional[list[str]] = None,
71-
cwd: Optional[Path] = None,
72-
stdin: Optional[Union[IO[Any], int]] = None,
73-
stdout: Optional[Union[IO[Any], int]] = None,
74-
stderr: Optional[Union[IO[Any], int]] = None,
71+
compose_args: list[str] | None = None,
72+
cwd: Path | None = None,
73+
stdin: IO[Any] | int | None = None,
74+
stdout: IO[Any] | int | None = None,
75+
stderr: IO[Any] | int | None = None,
7576
) -> asyncio.subprocess.Process:
7677
if cwd is None:
7778
cwd = get_project_root()
@@ -88,11 +89,11 @@ async def call_compose(
8889

8990

9091
async def compose_up(
91-
compose_args: Optional[list[str]] = None,
92-
cwd: Optional[Path] = None,
93-
stdin: Optional[Union[IO[Any], int]] = None,
94-
stdout: Optional[Union[IO[Any], int]] = None,
95-
stderr: Optional[Union[IO[Any], int]] = None,
92+
compose_args: list[str] | None = None,
93+
cwd: Path | None = None,
94+
stdin: IO[Any] | int | None = None,
95+
stdout: IO[Any] | int | None = None,
96+
stderr: IO[Any] | int | None = None,
9697
) -> asyncio.subprocess.Process:
9798
return await call_compose(
9899
["up", "-d", "--build", *(compose_args or [])],
@@ -104,11 +105,11 @@ async def compose_up(
104105

105106

106107
async def compose_down(
107-
compose_args: Optional[list[str]] = None,
108-
cwd: Optional[Path] = None,
109-
stdin: Optional[Union[IO[Any], int]] = None,
110-
stdout: Optional[Union[IO[Any], int]] = None,
111-
stderr: Optional[Union[IO[Any], int]] = None,
108+
compose_args: list[str] | None = None,
109+
cwd: Path | None = None,
110+
stdin: IO[Any] | int | None = None,
111+
stdout: IO[Any] | int | None = None,
112+
stderr: IO[Any] | int | None = None,
112113
) -> asyncio.subprocess.Process:
113114
return await call_compose(
114115
["down", *(compose_args or [])],
@@ -120,11 +121,11 @@ async def compose_down(
120121

121122

122123
async def compose_build(
123-
compose_args: Optional[list[str]] = None,
124-
cwd: Optional[Path] = None,
125-
stdin: Optional[Union[IO[Any], int]] = None,
126-
stdout: Optional[Union[IO[Any], int]] = None,
127-
stderr: Optional[Union[IO[Any], int]] = None,
124+
compose_args: list[str] | None = None,
125+
cwd: Path | None = None,
126+
stdin: IO[Any] | int | None = None,
127+
stdout: IO[Any] | int | None = None,
128+
stderr: IO[Any] | int | None = None,
128129
) -> asyncio.subprocess.Process:
129130
return await call_compose(
130131
["build", *(compose_args or [])],
@@ -136,11 +137,11 @@ async def compose_build(
136137

137138

138139
async def compose_logs(
139-
compose_args: Optional[list[str]] = None,
140-
cwd: Optional[Path] = None,
141-
stdin: Optional[Union[IO[Any], int]] = None,
142-
stdout: Optional[Union[IO[Any], int]] = None,
143-
stderr: Optional[Union[IO[Any], int]] = None,
140+
compose_args: list[str] | None = None,
141+
cwd: Path | None = None,
142+
stdin: IO[Any] | int | None = None,
143+
stdout: IO[Any] | int | None = None,
144+
stderr: IO[Any] | int | None = None,
144145
) -> asyncio.subprocess.Process:
145146
return await call_compose(
146147
["logs", *(compose_args or [])],
@@ -152,11 +153,11 @@ async def compose_logs(
152153

153154

154155
async def compose_ps(
155-
compose_args: Optional[list[str]] = None,
156-
cwd: Optional[Path] = None,
157-
stdin: Optional[Union[IO[Any], int]] = None,
158-
stdout: Optional[Union[IO[Any], int]] = None,
159-
stderr: Optional[Union[IO[Any], int]] = None,
156+
compose_args: list[str] | None = None,
157+
cwd: Path | None = None,
158+
stdin: IO[Any] | int | None = None,
159+
stdout: IO[Any] | int | None = None,
160+
stderr: IO[Any] | int | None = None,
160161
) -> asyncio.subprocess.Process:
161162
return await call_compose(
162163
["ps", *(compose_args or [])],
@@ -169,10 +170,10 @@ async def compose_ps(
169170

170171
@requires_nonebot
171172
async def get_driver_type(
172-
adapters: Optional[list[SimpleInfo]] = None,
173-
builtin_plugins: Optional[list[str]] = None,
174-
python_path: Optional[str] = None,
175-
cwd: Optional[Path] = None,
173+
adapters: list[SimpleInfo] | None = None,
174+
builtin_plugins: list[str] | None = None,
175+
python_path: str | None = None,
176+
cwd: Path | None = None,
176177
) -> bool:
177178
bot_config = get_nonebot_config()
178179
if adapters is None:
@@ -206,23 +207,21 @@ async def get_driver_type(
206207

207208

208209
async def get_build_backend(
209-
config_manager: Optional[ConfigManager] = None,
210-
) -> Optional[Literal["poetry", "pdm", "pip"]]:
210+
config_manager: ConfigManager | None = None,
211+
) -> Literal["poetry", "pdm", "uv", "pip"] | None:
211212
if config_manager is None:
212213
config_manager = GLOBAL_CONFIG
213214

214-
if data := config_manager._get_data():
215-
backend = data.get("build-system", {}).get("build-backend", "")
216-
if "poetry" in backend:
217-
return "poetry"
218-
elif "pdm" in backend:
219-
return "pdm"
215+
inferred, _ = await probe_environment_manager(cwd=config_manager.working_dir)
216+
217+
if inferred != "pip":
218+
return cast(Literal["uv", "pdm", "poetry"], inferred)
220219
if (config_manager.project_root / "requirements.txt").exists():
221220
return "pip"
222221

223222

224223
async def generate_dockerfile(
225-
python_version: str, is_asgi: bool, build_backend: Optional[str]
224+
python_version: str, is_asgi: bool, build_backend: str | None
226225
):
227226
t = templates.get_template(
228227
"docker/reverse.Dockerfile.jinja"

nb_cli_plugin_docker/template/docker/_helpers.Dockerfile.jinja

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,26 @@ FROM python:{{ python_version }} as requirements_stage
33

44
WORKDIR /wheel
55

6-
RUN python -m pip install --user pipx
6+
RUN python -m pip install --user uv
77

8-
COPY ./pyproject.toml{% if build_backend == "poetry" %} \
9-
./poetry.lock{% elif build_backend == "pdm" %} \
10-
./pdm.lock{% elif build_backend == "pip" %} \
8+
COPY ./pyproject.toml{% if build_backend == "uv" %} \
9+
./uv.lock{% elif build_backend == "pdm" %} \
10+
./pdm.lock{% elif build_backend == "poetry" %}\
11+
./poetry.lock{% elif build_backend == "pip" %} \
1112
./requirements.txt{% endif %} \
1213
/wheel/
1314

1415
{% if build_backend == "poetry" %}
15-
RUN python -m pipx run --no-cache poetry export -f requirements.txt --output requirements.txt --without-hashes
16+
RUN python -m uv tool run --no-cache poetry export -f requirements.txt --output requirements.txt --without-hashes
1617
{% elif build_backend == "pdm" %}
17-
RUN python -m pipx run --no-cache pdm export -f requirements --output requirements.txt --without-hashes
18+
RUN python -m uv tool run --no-cache pdm export -f requirements --output requirements.txt --without-hashes
19+
{% elif build_backend == "uv" %}
20+
RUN python -m uv export -f requirements --output requirements.txt --without-hashes
1821
{% endif %}
1922

2023
{% if build_backend %}
2124
RUN python -m pip wheel --wheel-dir=/wheel --no-cache-dir --requirement ./requirements.txt
2225
{% endif %}
2326

24-
RUN python -m pipx run --no-cache nb-cli generate -f /tmp/bot.py
27+
RUN python -m uv tool run --no-cache --from nb-cli nb generate -f /tmp/bot.py
2528
{% endmacro %}

nb_cli_plugin_docker/utils.py

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
from pathlib import Path
2-
from typing import Union, Optional
3-
1+
from anyio import Path
42
from noneprompt import ConfirmPrompt
53

64

75
async def safe_write_file(
8-
file_path: Path, content: Union[str, bytes], force: bool = False
9-
) -> Optional[int]:
6+
file_path: Path, content: str | bytes, force: bool = False
7+
) -> int | None:
108
directory = file_path.parent
11-
if not directory.exists():
12-
directory.mkdir(exist_ok=True, parents=True)
13-
elif not directory.is_dir():
9+
if not await directory.exists():
10+
await directory.mkdir(exist_ok=True, parents=True)
11+
elif not await directory.is_dir():
1412
raise RuntimeError(f"File {directory} already exists and is not a directory")
1513

1614
if (
17-
file_path.exists()
15+
await file_path.exists()
1816
and not force
1917
and not await ConfirmPrompt(
2018
f"File {file_path} already exists, overwrite?",
@@ -24,27 +22,27 @@ async def safe_write_file(
2422
return
2523

2624
return (
27-
file_path.write_text(content, encoding="utf-8")
25+
await file_path.write_text(content, encoding="utf-8")
2826
if isinstance(content, str)
29-
else file_path.write_bytes(content)
27+
else await file_path.write_bytes(content)
3028
)
3129

3230

3331
async def safe_copy_dir(source: Path, destination: Path, force: bool = False):
34-
if not source.exists():
32+
if not await source.exists():
3533
raise RuntimeError(f"Directory {source} does not exist")
36-
if not source.is_dir():
34+
if not await source.is_dir():
3735
raise RuntimeError(f"Path {source} is not a directory")
3836

39-
if not destination.exists():
40-
destination.mkdir(exist_ok=True, parents=True)
41-
elif not destination.is_dir():
37+
if not await destination.exists():
38+
await destination.mkdir(exist_ok=True, parents=True)
39+
elif not await destination.is_dir():
4240
raise RuntimeError(f"File {destination} already exists and is not a directory")
4341

44-
for file in source.iterdir():
45-
if file.is_dir():
42+
async for file in source.iterdir():
43+
if await file.is_dir():
4644
await safe_copy_dir(file, destination / file.name, force=force)
4745
else:
4846
await safe_write_file(
49-
destination / file.name, file.read_bytes(), force=force
47+
destination / file.name, await file.read_bytes(), force=force
5048
)

0 commit comments

Comments
 (0)