fast-dev-cli 0.18.4__tar.gz → 0.18.6__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.
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/PKG-INFO +1 -1
- fast_dev_cli-0.18.6/fast_dev_cli/__init__.py +1 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/fast_dev_cli/cli.py +131 -55
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/pyproject.toml +1 -1
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_bump.py +8 -8
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_deps.py +3 -3
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_fast_test.py +1 -1
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_functions.py +8 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_lint.py +1 -1
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_poetry_version_plugin.py +7 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_upgrade.py +14 -19
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/utils.py +5 -3
- fast_dev_cli-0.18.4/fast_dev_cli/__init__.py +0 -1
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/LICENSE +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/README.md +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/fast_dev_cli/__main__.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/fast_dev_cli/py.typed +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/pdm_build.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/scripts/check.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/scripts/deps.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/scripts/format.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/scripts/test.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/__init__.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/assets/uv-tx.lock +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/assets/uv.lock +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/conftest.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_exec.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_help.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_pypi.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_runserver.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_sync.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_tag.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_upload.py +0 -0
- {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_version.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.18.6"
|
|
@@ -4,6 +4,7 @@ import contextlib
|
|
|
4
4
|
import functools
|
|
5
5
|
import importlib.metadata as importlib_metadata
|
|
6
6
|
import os
|
|
7
|
+
import platform
|
|
7
8
|
import re
|
|
8
9
|
import shlex
|
|
9
10
|
import shutil
|
|
@@ -102,7 +103,14 @@ def is_emoji(char: str) -> bool:
|
|
|
102
103
|
return not "\u4e00" <= char <= "\u9fff" # Chinese character
|
|
103
104
|
|
|
104
105
|
|
|
106
|
+
@functools.cache
|
|
107
|
+
def is_windows() -> bool:
|
|
108
|
+
return platform.system() == "Windows"
|
|
109
|
+
|
|
110
|
+
|
|
105
111
|
def yellow_warn(msg: str) -> None:
|
|
112
|
+
if is_windows() and (encoding := sys.stdout.encoding) != "utf-8":
|
|
113
|
+
msg = msg.encode(encoding, errors="ignore").decode(encoding)
|
|
106
114
|
secho(msg, fg="yellow")
|
|
107
115
|
|
|
108
116
|
|
|
@@ -124,44 +132,96 @@ def is_venv() -> bool:
|
|
|
124
132
|
)
|
|
125
133
|
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
class Shell:
|
|
136
|
+
def __init__(self, cmd: list[str] | str, **kw: Any) -> None:
|
|
137
|
+
self._cmd = cmd
|
|
138
|
+
self._kw = kw
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def run_by_subprocess(
|
|
142
|
+
cmd: list[str] | str, **kw: Any
|
|
143
|
+
) -> subprocess.CompletedProcess[str]:
|
|
144
|
+
if isinstance(cmd, str):
|
|
145
|
+
kw.setdefault("shell", True)
|
|
146
|
+
return subprocess.run(cmd, **kw) # nosec:B603
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def command(self) -> list[str] | str:
|
|
150
|
+
command: list[str] | str = self._cmd
|
|
151
|
+
if (
|
|
152
|
+
isinstance(command, str)
|
|
153
|
+
and "shell" not in self._kw
|
|
154
|
+
and not (set(self._cmd) & {"|", ">", "&"})
|
|
155
|
+
):
|
|
156
|
+
command = shlex.split(command)
|
|
157
|
+
return command
|
|
158
|
+
|
|
159
|
+
def _run(self) -> subprocess.CompletedProcess[str]:
|
|
160
|
+
return self.run_by_subprocess(self.command, **self._kw)
|
|
161
|
+
|
|
162
|
+
def run(self, verbose: bool = False, dry: bool = False) -> int:
|
|
163
|
+
if verbose:
|
|
164
|
+
echo(f"--> {self._cmd}")
|
|
165
|
+
if dry:
|
|
166
|
+
return 0
|
|
167
|
+
return self._run().returncode
|
|
168
|
+
|
|
169
|
+
def check_call(self) -> bool:
|
|
170
|
+
self._kw.update(stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
171
|
+
return self.run() == 0
|
|
172
|
+
|
|
173
|
+
def capture_output(self, raises: bool = False) -> str:
|
|
174
|
+
self._kw.update(capture_output=True, encoding="utf-8")
|
|
175
|
+
r = self._run()
|
|
176
|
+
if raises and r.returncode != 0:
|
|
177
|
+
raise ShellCommandError(r.stderr)
|
|
178
|
+
return (r.stdout or r.stderr or "").strip()
|
|
179
|
+
|
|
180
|
+
def finish(
|
|
181
|
+
self, env: dict[str, str] | None = None, _exit: bool = False, dry=False
|
|
182
|
+
) -> subprocess.CompletedProcess[str]:
|
|
183
|
+
self.run(verbose=True, dry=True)
|
|
184
|
+
if _ensure_bool(dry):
|
|
185
|
+
return subprocess.CompletedProcess("", 0)
|
|
186
|
+
if env is not None:
|
|
187
|
+
self._kw["env"] = {**os.environ, **env}
|
|
188
|
+
r = self._run()
|
|
189
|
+
if rc := r.returncode:
|
|
190
|
+
if _exit:
|
|
191
|
+
sys.exit(rc)
|
|
192
|
+
raise Exit(rc)
|
|
193
|
+
return r
|
|
131
194
|
|
|
132
195
|
|
|
133
196
|
def run_and_echo(
|
|
134
197
|
cmd: str, *, dry: bool = False, verbose: bool = True, **kw: Any
|
|
135
198
|
) -> int:
|
|
136
199
|
"""Run shell command with subprocess and print it"""
|
|
137
|
-
|
|
138
|
-
echo(f"--> {cmd}")
|
|
139
|
-
if dry:
|
|
140
|
-
return 0
|
|
141
|
-
command: list[str] | str = cmd
|
|
142
|
-
if "shell" not in kw and not (set(cmd) & {"|", ">", "&"}):
|
|
143
|
-
command = shlex.split(cmd)
|
|
144
|
-
return _run_shell(command, **kw).returncode
|
|
200
|
+
return Shell(cmd, **kw).run(verbose=verbose, dry=dry)
|
|
145
201
|
|
|
146
202
|
|
|
147
203
|
def check_call(cmd: str) -> bool:
|
|
148
|
-
|
|
149
|
-
return r.returncode == 0
|
|
204
|
+
return Shell(cmd).check_call()
|
|
150
205
|
|
|
151
206
|
|
|
152
207
|
def capture_cmd_output(
|
|
153
208
|
command: list[str] | str, *, raises: bool = False, **kw: Any
|
|
154
209
|
) -> str:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
210
|
+
return Shell(command, **kw).capture_output(raises=raises)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def exit_if_run_failed(
|
|
214
|
+
cmd: str,
|
|
215
|
+
env: dict[str, str] | None = None,
|
|
216
|
+
_exit: bool = False,
|
|
217
|
+
dry: bool = False,
|
|
218
|
+
**kw: Any,
|
|
219
|
+
) -> subprocess.CompletedProcess[str]:
|
|
220
|
+
return Shell(cmd, **kw).finish(env=env, _exit=_exit, dry=dry)
|
|
161
221
|
|
|
162
222
|
|
|
163
223
|
def _parse_version(line: str, pattern: re.Pattern[str]) -> str:
|
|
164
|
-
return pattern.sub("", line).split("#")[0].strip(" '\"")
|
|
224
|
+
return pattern.sub("", line).split("#")[0].strip().strip(" '\"")
|
|
165
225
|
|
|
166
226
|
|
|
167
227
|
def read_version_from_file(
|
|
@@ -276,26 +336,6 @@ def _ensure_str(value: str | OptionInfo | None) -> str:
|
|
|
276
336
|
return value
|
|
277
337
|
|
|
278
338
|
|
|
279
|
-
def exit_if_run_failed(
|
|
280
|
-
cmd: str,
|
|
281
|
-
env: dict[str, str] | None = None,
|
|
282
|
-
_exit: bool = False,
|
|
283
|
-
dry: bool = False,
|
|
284
|
-
**kw: Any,
|
|
285
|
-
) -> subprocess.CompletedProcess[str]:
|
|
286
|
-
run_and_echo(cmd, dry=True)
|
|
287
|
-
if _ensure_bool(dry):
|
|
288
|
-
return subprocess.CompletedProcess("", 0)
|
|
289
|
-
if env is not None:
|
|
290
|
-
env = {**os.environ, **env}
|
|
291
|
-
r = _run_shell(cmd, env=env, **kw)
|
|
292
|
-
if rc := r.returncode:
|
|
293
|
-
if _exit:
|
|
294
|
-
sys.exit(rc)
|
|
295
|
-
raise Exit(rc)
|
|
296
|
-
return r
|
|
297
|
-
|
|
298
|
-
|
|
299
339
|
class DryRun:
|
|
300
340
|
def __init__(self, _exit: bool = False, dry: bool = False) -> None:
|
|
301
341
|
self.dry = _ensure_bool(dry)
|
|
@@ -561,6 +601,19 @@ class Project:
|
|
|
561
601
|
def is_poetry_v2(text: str) -> bool:
|
|
562
602
|
return 'build-backend = "poetry' in text
|
|
563
603
|
|
|
604
|
+
@staticmethod
|
|
605
|
+
def get_poetry_version(command: str = "poetry") -> str:
|
|
606
|
+
pattern = r"(\d+\.\d+\.\d+)"
|
|
607
|
+
text = capture_cmd_output(f"{command} --version")
|
|
608
|
+
for expr in (
|
|
609
|
+
rf"Poetry \(version {pattern}\)",
|
|
610
|
+
rf"Poetry.*version.*{pattern}.*\)",
|
|
611
|
+
rf"{pattern}",
|
|
612
|
+
):
|
|
613
|
+
if m := re.search(expr, text):
|
|
614
|
+
return m.group(1)
|
|
615
|
+
return ""
|
|
616
|
+
|
|
564
617
|
@staticmethod
|
|
565
618
|
def work_dir(
|
|
566
619
|
name: str, parent: Path, depth: int, be_file: bool = False
|
|
@@ -657,7 +710,7 @@ class Project:
|
|
|
657
710
|
@classmethod
|
|
658
711
|
def get_sync_command(cls, prod: bool = True, doc: dict | None = None) -> str:
|
|
659
712
|
if cls.is_pdm_project():
|
|
660
|
-
return "pdm
|
|
713
|
+
return "pdm install --frozen" + " --prod" * prod
|
|
661
714
|
elif cls.manage_by_poetry(cache=True):
|
|
662
715
|
cmd = "poetry install"
|
|
663
716
|
if prod:
|
|
@@ -854,7 +907,7 @@ class UpgradeDependencies(Project, DryRun):
|
|
|
854
907
|
deps = "uv sync --inexact --frozen --all-groups --all-extras"
|
|
855
908
|
return f"{up} && {deps}"
|
|
856
909
|
elif self._tool == "pdm":
|
|
857
|
-
return "pdm update --verbose && pdm
|
|
910
|
+
return "pdm update --verbose && pdm install -G :all --frozen"
|
|
858
911
|
return self.gen_cmd() + " && poetry lock && poetry update"
|
|
859
912
|
|
|
860
913
|
|
|
@@ -952,7 +1005,11 @@ class LintCode(DryRun):
|
|
|
952
1005
|
|
|
953
1006
|
@staticmethod
|
|
954
1007
|
def check_lint_tool_installed() -> bool:
|
|
955
|
-
|
|
1008
|
+
try:
|
|
1009
|
+
return check_call("ruff --version")
|
|
1010
|
+
except FileNotFoundError:
|
|
1011
|
+
# Windows may raise FileNotFoundError when ruff not installed
|
|
1012
|
+
return False
|
|
956
1013
|
|
|
957
1014
|
@staticmethod
|
|
958
1015
|
def missing_mypy_exec() -> bool:
|
|
@@ -1019,7 +1076,7 @@ class LintCode(DryRun):
|
|
|
1019
1076
|
if not should_run_by_tool:
|
|
1020
1077
|
if is_venv() and Path(sys.argv[0]).parent != Path.home().joinpath(
|
|
1021
1078
|
".local/bin"
|
|
1022
|
-
):
|
|
1079
|
+
): # Virtual environment activated and fast-dev-cli is installed in it
|
|
1023
1080
|
if not ruff_exists:
|
|
1024
1081
|
should_run_by_tool = True
|
|
1025
1082
|
command = "pipx install ruff"
|
|
@@ -1038,17 +1095,26 @@ class LintCode(DryRun):
|
|
|
1038
1095
|
"You may need to run the following command"
|
|
1039
1096
|
f" to install lint tools:\n\n {command}\n"
|
|
1040
1097
|
)
|
|
1098
|
+
elif tool == ToolOption.default:
|
|
1099
|
+
root = Project.get_work_dir(allow_cwd=True)
|
|
1100
|
+
if py := shutil.which("python"):
|
|
1101
|
+
try:
|
|
1102
|
+
Path(py).relative_to(root)
|
|
1103
|
+
except ValueError:
|
|
1104
|
+
# Virtual environment not activated
|
|
1105
|
+
should_run_by_tool = True
|
|
1041
1106
|
else:
|
|
1042
1107
|
should_run_by_tool = True
|
|
1043
1108
|
if should_run_by_tool and tool:
|
|
1044
1109
|
if tool == ToolOption.default:
|
|
1045
1110
|
tool = Project.get_manage_tool() or ""
|
|
1046
1111
|
if tool:
|
|
1047
|
-
prefix =
|
|
1048
|
-
|
|
1049
|
-
if
|
|
1050
|
-
|
|
1051
|
-
|
|
1112
|
+
prefix = tool + " run "
|
|
1113
|
+
if tool == "uv":
|
|
1114
|
+
if is_windows():
|
|
1115
|
+
prefix += "--no-sync "
|
|
1116
|
+
elif Path(bin_dir := ".venv/bin/").exists():
|
|
1117
|
+
prefix = bin_dir
|
|
1052
1118
|
if cls.prefer_dmypy(paths, tools, use_dmypy=use_dmypy):
|
|
1053
1119
|
tools[-1] = "dmypy run"
|
|
1054
1120
|
cmd += lint_them.format(prefix, paths, *tools)
|
|
@@ -1070,7 +1136,12 @@ class LintCode(DryRun):
|
|
|
1070
1136
|
ps = args.split() if isinstance(args, str) else [str(i) for i in args]
|
|
1071
1137
|
if len(ps) == 1:
|
|
1072
1138
|
paths = ps[0]
|
|
1073
|
-
if
|
|
1139
|
+
if (
|
|
1140
|
+
paths != "."
|
|
1141
|
+
# `Path("a.").suffix` got "." in py3.14 and got "" with py<3.14
|
|
1142
|
+
and (p := Path(paths)).suffix in ("", ".")
|
|
1143
|
+
and not p.exists()
|
|
1144
|
+
):
|
|
1074
1145
|
# e.g.:
|
|
1075
1146
|
# stem -> stem.py
|
|
1076
1147
|
# me. -> me.py
|
|
@@ -1252,7 +1323,9 @@ def test(dry: bool, ignore_script: bool = False) -> None:
|
|
|
1252
1323
|
if not _ensure_bool(ignore_script) and (
|
|
1253
1324
|
test_script := _should_run_test_script(script_dir)
|
|
1254
1325
|
):
|
|
1255
|
-
cmd =
|
|
1326
|
+
cmd = test_script.relative_to(root).as_posix()
|
|
1327
|
+
if test_script.suffix == ".py":
|
|
1328
|
+
cmd = "python " + cmd
|
|
1256
1329
|
if cwd != root:
|
|
1257
1330
|
cmd = f"cd {root} && " + cmd
|
|
1258
1331
|
else:
|
|
@@ -1343,8 +1416,11 @@ def run_by_subprocess(cmd: str, dry: bool = DryOption) -> None:
|
|
|
1343
1416
|
try:
|
|
1344
1417
|
rc = run_and_echo(cmd, verbose=True, dry=_ensure_bool(dry))
|
|
1345
1418
|
except FileNotFoundError as e:
|
|
1346
|
-
|
|
1347
|
-
|
|
1419
|
+
command = cmd.split()[0]
|
|
1420
|
+
if e.filename == command or (
|
|
1421
|
+
e.filename is None and "系统找不到指定的文件" in str(e)
|
|
1422
|
+
):
|
|
1423
|
+
echo(f"Command not found: {command}")
|
|
1348
1424
|
raise Exit(1) from None
|
|
1349
1425
|
raise e
|
|
1350
1426
|
else:
|
|
@@ -1380,7 +1456,7 @@ class MakeDeps(DryRun):
|
|
|
1380
1456
|
|
|
1381
1457
|
def gen(self) -> str:
|
|
1382
1458
|
if self._tool == "pdm":
|
|
1383
|
-
return "pdm
|
|
1459
|
+
return "pdm install --frozen " + ("--prod" if self._prod else "-G :all")
|
|
1384
1460
|
elif self._tool == "uv":
|
|
1385
1461
|
uv_sync = "uv sync" + " --inexact" * self._inexact
|
|
1386
1462
|
if self._active:
|
|
@@ -230,16 +230,16 @@ version = "0"
|
|
|
230
230
|
another_dir = project_dir / "hello"
|
|
231
231
|
another_dir.mkdir()
|
|
232
232
|
shutil.copy(init_file, another_dir / init_file.name)
|
|
233
|
-
assert BumpUp.parse_filename() == "helloworld
|
|
233
|
+
assert BumpUp.parse_filename() == os.path.join("helloworld", "__init__.py")
|
|
234
234
|
toml_file.write_text(pyproject.strip() + '\npackages=[{include="hello"}]')
|
|
235
|
-
assert BumpUp.parse_filename() == "hello
|
|
235
|
+
assert BumpUp.parse_filename() == os.path.join("hello", "__init__.py")
|
|
236
236
|
toml_file.write_text(
|
|
237
237
|
pyproject.strip() + '\npackages=[{include="hello",from="py"}]'
|
|
238
238
|
)
|
|
239
239
|
from_dir = project_dir / "py"
|
|
240
240
|
from_dir.mkdir()
|
|
241
241
|
shutil.move(another_dir, from_dir)
|
|
242
|
-
assert BumpUp.parse_filename() == "py
|
|
242
|
+
assert BumpUp.parse_filename() == os.path.join("py", "hello", "__init__.py")
|
|
243
243
|
|
|
244
244
|
|
|
245
245
|
PDM_DYNAMIC_VERSION = """
|
|
@@ -269,7 +269,7 @@ def test_pdm_project(dynamic_pdm_project):
|
|
|
269
269
|
init_file = dynamic_pdm_project / "__init__.py"
|
|
270
270
|
init_file.write_text('__version__ = "0.2.0"')
|
|
271
271
|
out = capture_cmd_output("fast bump patch")
|
|
272
|
-
assert
|
|
272
|
+
assert init_file.as_posix() in out
|
|
273
273
|
assert "0.2.1" in init_file.read_text()
|
|
274
274
|
run_and_echo("git init")
|
|
275
275
|
shutil.copy(Path(__file__).parent.parent / ".gitignore", ".")
|
|
@@ -291,7 +291,7 @@ def test_installed_version_is_0_0_0(dynamic_pdm_project):
|
|
|
291
291
|
capture_cmd_output("pdm install")
|
|
292
292
|
version_file.write_text('__version__ = "0.3.0"')
|
|
293
293
|
out = capture_cmd_output("fast bump patch")
|
|
294
|
-
assert
|
|
294
|
+
assert version_file.as_posix() in out
|
|
295
295
|
assert "0.3.1" in version_file.read_text()
|
|
296
296
|
|
|
297
297
|
|
|
@@ -332,7 +332,7 @@ __description__ = "A next generation HTTP client, for Python 3."
|
|
|
332
332
|
__version__ = "0.28.1"
|
|
333
333
|
""")
|
|
334
334
|
out = capture_cmd_output("fast bump patch")
|
|
335
|
-
assert
|
|
335
|
+
assert version_file.as_posix() in out
|
|
336
336
|
assert "0.28.2" in version_file.read_text()
|
|
337
337
|
|
|
338
338
|
|
|
@@ -365,7 +365,7 @@ requires = ["pdm-backend"]
|
|
|
365
365
|
build-backend = "pdm.backend"
|
|
366
366
|
""")
|
|
367
367
|
out = capture_cmd_output("fast bump patch")
|
|
368
|
-
assert
|
|
368
|
+
assert version_file.as_posix() in out
|
|
369
369
|
assert "0.3.1" in version_file.read_text()
|
|
370
370
|
|
|
371
371
|
|
|
@@ -400,5 +400,5 @@ requires = ["pdm-backend"]
|
|
|
400
400
|
build-backend = "pdm.backend"
|
|
401
401
|
""")
|
|
402
402
|
out = capture_cmd_output("fast bump patch")
|
|
403
|
-
assert
|
|
403
|
+
assert version_file.as_posix() in out
|
|
404
404
|
assert "0.3.1" in version_file.read_text()
|
|
@@ -12,8 +12,8 @@ def test_make_deps_class():
|
|
|
12
12
|
)
|
|
13
13
|
assert MakeDeps("uv", prod=True).gen() == "uv sync --inexact --active"
|
|
14
14
|
assert MakeDeps("uv", prod=True, active=False).gen() == "uv sync --inexact"
|
|
15
|
-
assert MakeDeps("pdm", prod=False).gen() == "pdm
|
|
16
|
-
assert MakeDeps("pdm", prod=True).gen() == "pdm
|
|
15
|
+
assert MakeDeps("pdm", prod=False).gen() == "pdm install --frozen -G :all"
|
|
16
|
+
assert MakeDeps("pdm", prod=True).gen() == "pdm install --frozen --prod"
|
|
17
17
|
assert (
|
|
18
18
|
MakeDeps("poetry", prod=False).gen()
|
|
19
19
|
== "poetry install --all-extras --all-groups"
|
|
@@ -31,7 +31,7 @@ def test_make_deps_class():
|
|
|
31
31
|
|
|
32
32
|
def test_fast_deps():
|
|
33
33
|
out = capture_cmd_output("fast deps --dry")
|
|
34
|
-
assert out == "--> pdm
|
|
34
|
+
assert out == "--> pdm install --frozen -G :all"
|
|
35
35
|
out = capture_cmd_output("fast deps --uv --prod --dry")
|
|
36
36
|
assert out == "--> uv sync --inexact --active"
|
|
37
37
|
out = capture_cmd_output("fast deps --uv --prod --dry --no-active")
|
|
@@ -95,7 +95,7 @@ def test_run_script_in_sub_directory(mocker: MockerFixture, capsys, script_path)
|
|
|
95
95
|
mocker.patch("pathlib.Path.cwd", return_value=script_path.parent)
|
|
96
96
|
unitcase(dry=True)
|
|
97
97
|
out = capsys.readouterr().out
|
|
98
|
-
assert f"cd {script_path.parent.parent} && {TEST_SCRIPT}" in out
|
|
98
|
+
assert f"cd {script_path.parent.parent} && python {TEST_SCRIPT}" in out
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def test_fast_test(mocker, capsys):
|
|
@@ -80,8 +80,16 @@ def test_run_shell():
|
|
|
80
80
|
assert run_and_echo("echo foo", capture_output=True) == 0
|
|
81
81
|
|
|
82
82
|
with pytest.raises(SystemExit):
|
|
83
|
+
exit_if_run_failed(
|
|
84
|
+
"in_valid_command", _exit=True, capture_output=True, shell=True
|
|
85
|
+
)
|
|
86
|
+
with pytest.raises(FileNotFoundError):
|
|
83
87
|
exit_if_run_failed("in_valid_command", _exit=True, capture_output=True)
|
|
84
88
|
with pytest.raises(typer.Exit):
|
|
89
|
+
exit_if_run_failed(
|
|
90
|
+
"in_valid_command", _exit=False, capture_output=True, shell=True
|
|
91
|
+
)
|
|
92
|
+
with pytest.raises(FileNotFoundError):
|
|
85
93
|
exit_if_run_failed("in_valid_command", _exit=False, capture_output=True)
|
|
86
94
|
with pytest.raises(NotImplementedError):
|
|
87
95
|
|
|
@@ -183,7 +183,7 @@ def test_dmypy_run(monkeypatch):
|
|
|
183
183
|
def test_lint_with_prefix(mocker):
|
|
184
184
|
mocker.patch("fast_dev_cli.cli.is_venv", return_value=False)
|
|
185
185
|
with capture_stdout() as stream:
|
|
186
|
-
make_style(["."], check_only=False, dry=True)
|
|
186
|
+
make_style(["."], check_only=False, dry=True, prefix=True)
|
|
187
187
|
assert "pdm run" in stream.getvalue()
|
|
188
188
|
|
|
189
189
|
|
|
@@ -10,6 +10,7 @@ from fast_dev_cli.cli import (
|
|
|
10
10
|
TOML_FILE,
|
|
11
11
|
BumpUp,
|
|
12
12
|
ParseError,
|
|
13
|
+
Project,
|
|
13
14
|
poetry_module_name,
|
|
14
15
|
run_and_echo,
|
|
15
16
|
)
|
|
@@ -93,3 +94,9 @@ def test_version_plugin_include_defined(mark, tmp_path: Path) -> None:
|
|
|
93
94
|
assert BumpUp(part="patch", commit=False, dry=True).gen() == command
|
|
94
95
|
run_and_echo("poetry run fast bump patch")
|
|
95
96
|
assert init_file.read_text() == '__version__ = "0.0.2"\n'
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.parametrize("command", ["poetry", "uvx poetry"])
|
|
100
|
+
def test_poetry_version(command) -> None:
|
|
101
|
+
v = Project.get_poetry_version(command)
|
|
102
|
+
assert v >= "2.2.0"
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import re
|
|
4
|
-
import sys
|
|
5
3
|
from contextlib import redirect_stdout
|
|
6
4
|
from io import StringIO
|
|
7
5
|
from pathlib import Path
|
|
@@ -12,13 +10,14 @@ import typer
|
|
|
12
10
|
import fast_dev_cli
|
|
13
11
|
from fast_dev_cli.cli import (
|
|
14
12
|
TOML_FILE,
|
|
13
|
+
Project,
|
|
15
14
|
UpgradeDependencies,
|
|
16
15
|
capture_cmd_output,
|
|
17
16
|
run_and_echo,
|
|
18
17
|
upgrade,
|
|
19
18
|
)
|
|
20
19
|
|
|
21
|
-
from .utils import chdir
|
|
20
|
+
from .utils import chdir, prepare_poetry_project
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
def test_parse_value():
|
|
@@ -85,28 +84,24 @@ uvicorn = {version = "^0.23.2", platform = "linux", optional = true}
|
|
|
85
84
|
|
|
86
85
|
def test_dev_flag(tmp_path: Path):
|
|
87
86
|
assert UpgradeDependencies.should_with_dev() is False
|
|
88
|
-
with
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
pattern = r'(python = "\^3)\.\d+"'
|
|
96
|
-
new_content = re.sub(pattern, r'\1.9"', content)
|
|
97
|
-
p.write_text(new_content)
|
|
98
|
-
assert not UpgradeDependencies.should_with_dev()
|
|
99
|
-
run_and_echo("poetry add pytest")
|
|
87
|
+
with prepare_poetry_project(tmp_path) as poetry:
|
|
88
|
+
is_newer_poetry = Project.get_poetry_version(poetry) >= "2.2.0"
|
|
89
|
+
assert not UpgradeDependencies.should_with_dev()
|
|
90
|
+
run_and_echo(f"{poetry} add pytest")
|
|
91
|
+
assert not UpgradeDependencies.should_with_dev()
|
|
92
|
+
run_and_echo(f"{poetry} add --group=dev typer")
|
|
93
|
+
if is_newer_poetry:
|
|
100
94
|
assert not UpgradeDependencies.should_with_dev()
|
|
101
|
-
|
|
95
|
+
else:
|
|
96
|
+
toml_file = Path(TOML_FILE)
|
|
102
97
|
assert UpgradeDependencies.should_with_dev()
|
|
103
|
-
text =
|
|
98
|
+
text = toml_file.read_text()
|
|
104
99
|
DevFlag = UpgradeDependencies.DevFlag
|
|
105
100
|
if DevFlag.new in text:
|
|
106
101
|
new_text = text.replace(DevFlag.new, DevFlag.old)
|
|
107
102
|
else:
|
|
108
103
|
new_text = text.replace(DevFlag.old, DevFlag.new)
|
|
109
|
-
|
|
104
|
+
toml_file.write_text(new_text)
|
|
110
105
|
assert UpgradeDependencies.should_with_dev()
|
|
111
106
|
|
|
112
107
|
|
|
@@ -300,7 +295,7 @@ def test_upgrade_uv_project():
|
|
|
300
295
|
|
|
301
296
|
def test_upgrade_pdm_project():
|
|
302
297
|
cmd = "fast upgrade --tool=pdm --dry"
|
|
303
|
-
expected = "pdm update --verbose && pdm
|
|
298
|
+
expected = "pdm update --verbose && pdm install -G :all --frozen"
|
|
304
299
|
assert expected in capture_cmd_output(cmd)
|
|
305
300
|
assert expected in capture_cmd_output("pdm run " + cmd)
|
|
306
301
|
assert UpgradeDependencies(tool="pdm").gen() == expected
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import shutil
|
|
2
2
|
import sys
|
|
3
|
+
from collections.abc import Generator
|
|
3
4
|
from contextlib import contextmanager, redirect_stdout
|
|
4
5
|
from io import StringIO
|
|
5
6
|
from pathlib import Path
|
|
@@ -8,8 +9,9 @@ from asynctor import Shell
|
|
|
8
9
|
from asynctor.compat import chdir
|
|
9
10
|
|
|
10
11
|
__all__ = (
|
|
11
|
-
"mock_sys_argv",
|
|
12
12
|
"capture_stdout",
|
|
13
|
+
"mock_sys_argv",
|
|
14
|
+
"prepare_poetry_project",
|
|
13
15
|
"temp_file",
|
|
14
16
|
)
|
|
15
17
|
|
|
@@ -51,7 +53,7 @@ def temp_file(name: str, text=""):
|
|
|
51
53
|
|
|
52
54
|
|
|
53
55
|
@contextmanager
|
|
54
|
-
def prepare_poetry_project(tmp_path: Path):
|
|
56
|
+
def prepare_poetry_project(tmp_path: Path) -> Generator[str]:
|
|
55
57
|
py = "{}.{}".format(*sys.version_info)
|
|
56
58
|
poetry = "poetry"
|
|
57
59
|
if shutil.which(poetry) is None:
|
|
@@ -64,4 +66,4 @@ def prepare_poetry_project(tmp_path: Path):
|
|
|
64
66
|
f"{poetry} config --local virtualenvs.in-project true"
|
|
65
67
|
)
|
|
66
68
|
Shell.run_by_subprocess(f"{poetry} env use {py}")
|
|
67
|
-
yield
|
|
69
|
+
yield poetry
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.18.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|