Skip to content

Commit 0e44851

Browse files
committed
up
1 parent 711357e commit 0e44851

File tree

6 files changed

+139
-24
lines changed

6 files changed

+139
-24
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ Telegram:[@lgc2333](https://t.me/lgc2333)
119119

120120
## 📝 更新日志
121121

122+
### 0.4.0
123+
124+
- 创建虚拟环境时可选使用哪一个 Python
125+
- 不创建虚拟环境时也会提示是否安装依赖了
126+
- `nb shell` 当已处在虚拟环境内时会输出提示,并不会再开一层 Shell 了
127+
- 微调项目模板
128+
122129
### 0.3.1
123130

124131
- 修复无法配置 WebUI

nb_cli_plugin_bootstrap/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.3.1"
1+
__version__ = "0.4.0"

nb_cli_plugin_bootstrap/handlers/bootstrap.py

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import asyncio
12
import json
3+
import os
24
import shlex
35
import subprocess
46
import sys
57
import traceback
8+
from dataclasses import dataclass
69
from pathlib import Path
710
from typing import List
811

@@ -19,7 +22,7 @@
1922
from nb_cli.handlers.plugin import list_builtin_plugins
2023
from nb_cli.handlers.process import create_process
2124
from nb_cli.handlers.venv import create_virtualenv
22-
from noneprompt import CheckboxPrompt, Choice, ConfirmPrompt, InputPrompt
25+
from noneprompt import CheckboxPrompt, Choice, ConfirmPrompt, InputPrompt, ListPrompt
2326

2427
from ..const import INPUT_QUESTION
2528
from ..utils import call_pip_update_simp, validate_ip_v_any_addr, wait
@@ -29,10 +32,64 @@
2932
BOOTSTRAP_TEMPLATE_DIR = TEMPLATES_DIR / "bootstrap"
3033

3134

35+
@dataclass
36+
class PythonInfo:
37+
path: Path
38+
version: str
39+
40+
3241
def format_project_folder_name(project_name: str) -> str:
3342
return project_name.replace(" ", "-").lower()
3443

3544

45+
async def get_python_version(python_path: Path) -> str:
46+
proc = await create_process(
47+
python_path,
48+
"-c",
49+
(
50+
"import platform;"
51+
"print(f'{platform.python_implementation()} {platform.python_version()}')"
52+
),
53+
stdin=asyncio.subprocess.DEVNULL,
54+
stdout=asyncio.subprocess.PIPE,
55+
stderr=asyncio.subprocess.PIPE,
56+
)
57+
code, stdout, stderr = await wait(proc)
58+
if code == 0 and stdout:
59+
return stdout
60+
raise RuntimeError(
61+
f"Failed to get python version: [{code}] {stderr}, path: {python_path}",
62+
)
63+
64+
65+
async def find_python() -> List[PythonInfo]:
66+
env_path = os.environ["PATH"].split(";" if WINDOWS else ":")
67+
python_filenames = ("python.exe",) if WINDOWS else ("python", "python3")
68+
69+
founded: List[Path] = []
70+
for path_str in env_path:
71+
path = Path(path_str)
72+
for filename in python_filenames:
73+
if (p := (path / filename)).exists():
74+
founded.append(p)
75+
break
76+
77+
# founded = list(set(founded))
78+
if WINDOWS:
79+
founded = [x for x in founded if "WindowsApps" not in x.parts]
80+
# founded.sort()
81+
82+
python_versions = await asyncio.gather(
83+
*(get_python_version(x) for x in founded),
84+
return_exceptions=True,
85+
)
86+
return [
87+
PythonInfo(path, ver)
88+
for path, ver in zip(founded, python_versions)
89+
if isinstance(ver, str)
90+
]
91+
92+
3693
async def prompt_input_list(prompt: str, **kwargs) -> List[str]:
3794
click.secho(f"{prompt}(留空回车结束输入)", bold=True)
3895

@@ -258,13 +315,43 @@ async def post_project_render(
258315
"是否新建虚拟环境?",
259316
default_choice=True,
260317
).prompt_async(style=CLI_DEFAULT_STYLE)
318+
319+
python_infos = await find_python()
320+
selected_python = (
321+
(
322+
python_infos[0].path
323+
if len(python_infos) == 1
324+
else (
325+
await ListPrompt(
326+
question="请选择你想要用来创建虚拟环境的 Python 解释器",
327+
choices=[
328+
Choice(f"{info.version} ({info.path})", info)
329+
for info in python_infos
330+
],
331+
).prompt_async()
332+
).data.path
333+
)
334+
if python_infos
335+
else None
336+
)
337+
261338
project_dir_name = context.variables["project_name"].replace(" ", "-").lower()
262339
project_dir = Path.cwd() / project_dir_name
263340
if use_venv:
264341
venv_dir = project_dir / ".venv"
265-
click.secho(f"正在 {venv_dir} 中创建虚拟环境", fg="yellow")
342+
click.secho(
343+
(
344+
f"正在 {venv_dir} 中"
345+
f"{f'使用 Python {selected_python} ' if selected_python else ''}创建虚拟环境"
346+
),
347+
fg="yellow",
348+
)
266349
try:
267-
await create_virtualenv(venv_dir, prompt=project_dir_name)
350+
await create_virtualenv(
351+
venv_dir,
352+
prompt=project_dir_name,
353+
python_path=str(selected_python),
354+
)
268355
except Exception:
269356
click.secho(
270357
f"创建虚拟环境失败\n{traceback.format_exc()}",
@@ -286,14 +373,12 @@ async def post_project_render(
286373
await pip_index_handler(verbose=verbose)
287374

288375
manually_install_tip = "项目依赖已写入项目 pyproject.toml 中,请自行手动安装,或使用 pdm 等包管理器安装"
289-
if (not use_venv) or (
290-
not (
291-
yes
292-
or await ConfirmPrompt(
293-
"是否立即安装项目依赖?",
294-
default_choice=True,
295-
).prompt_async(style=CLI_DEFAULT_STYLE)
296-
)
376+
if not (
377+
yes
378+
or await ConfirmPrompt(
379+
f"是否立即安装项目依赖?{'' if use_venv else '(注意:将会安装到默认全局环境中!)'}",
380+
default_choice=True,
381+
).prompt_async(style=CLI_DEFAULT_STYLE)
297382
):
298383
click.secho(manually_install_tip, fg="green")
299384
return True

nb_cli_plugin_bootstrap/handlers/pip_index.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,5 @@ async def pip_index_handler(verbose: bool = False):
5656
elif code == 0:
5757
click.secho("PyPI 源配置成功", fg="green", bold=True)
5858
else:
59-
err = stderr if stderr else ""
59+
err = stderr or ""
6060
click.secho(f"PyPI 源配置失败!\n{err}", fg="red", bold=True, err=True)
Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
from contextlib import suppress
12
from pathlib import Path
23
from typing import Optional
34

45
import click
6+
from nb_cli.config import ConfigManager
57
from nb_cli.handlers import requires_project_root
8+
from nb_cli.handlers.meta import _get_env_python
69
from poetry.utils.env import VirtualEnv
710
from poetry.utils.shell import Shell
811

@@ -13,17 +16,33 @@
1316
# )
1417

1518

16-
def get_venv_dir(cwd: Path) -> Path:
17-
path = cwd / ".venv"
18-
if path.is_dir() and (path / "pyvenv.cfg").is_file():
19-
return path
19+
def find_venv_root(child_path: Path) -> Path:
20+
while True:
21+
if (child_path / "pyvenv.cfg").exists():
22+
return child_path
23+
child_path = child_path.parent
24+
if len(child_path.parts) <= 1:
25+
break
26+
raise FileNotFoundError("No virtual environment found")
27+
28+
29+
def get_venv_dir(cwd: Optional[Path]) -> Path:
30+
config = ConfigManager(working_dir=cwd, use_venv=True)
31+
if config.python_path:
32+
return find_venv_root(Path(config.python_path).parent)
2033
raise FileNotFoundError("No virtual environment found")
2134

2235

2336
@requires_project_root
2437
async def shell_handler(cwd: Optional[Path] = None):
25-
shell = Shell.get()
26-
venv_path = get_venv_dir(cwd or Path.cwd())
27-
venv = VirtualEnv(venv_path)
38+
venv_path = get_venv_dir(cwd)
39+
with suppress(FileNotFoundError):
40+
now_venv_path = find_venv_root(Path(await _get_env_python()).parent)
41+
if now_venv_path == venv_path:
42+
click.secho("您当前已在虚拟环境内", fg="yellow")
43+
return
44+
2845
click.secho(f"进入虚拟环境:{venv_path}", fg="green")
46+
venv = VirtualEnv(venv_path)
47+
shell = Shell.get()
2948
shell.activate(venv)

nb_cli_plugin_bootstrap/templates/bootstrap/{{cookiecutter.nonebot.folder_name}}/.env.prod

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@ LOCALSTORE_DATA_DIR=data/nonebot/data
1313
{%- endif %}
1414
{%- if cookiecutter.nonebot.use_logpile %}
1515

16-
# LogPile 配置项
17-
# LOGPILE_PATH=./log # 日志文件保存路径
18-
# LOGPILE_LEVEL=INFO # 日志输出等级,可以为列表
19-
# LOGPILE_RETENTION=14 # 日志保留天数
16+
# LogPile 日志文件保存路径
17+
# LOGPILE_PATH=./log
18+
19+
# LogPile 日志输出等级,可以为列表
20+
LOGPILE_LEVEL=INFO
21+
22+
# LogPile 日志保留天数
23+
# LOGPILE_RETENTION=14
2024
{%- endif %}
2125

2226
### NoneBot 配置项 ###

0 commit comments

Comments
 (0)