fast-dev-cli 0.18.3__tar.gz → 0.18.5__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.
Files changed (35) hide show
  1. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/PKG-INFO +1 -1
  2. fast_dev_cli-0.18.5/fast_dev_cli/__init__.py +1 -0
  3. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/fast_dev_cli/cli.py +151 -61
  4. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/pyproject.toml +1 -1
  5. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_bump.py +8 -8
  6. fast_dev_cli-0.18.5/tests/test_deps.py +50 -0
  7. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_fast_test.py +1 -1
  8. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_functions.py +8 -0
  9. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_poetry_version_plugin.py +7 -0
  10. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_upgrade.py +14 -19
  11. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/utils.py +5 -3
  12. fast_dev_cli-0.18.3/fast_dev_cli/__init__.py +0 -1
  13. fast_dev_cli-0.18.3/tests/test_deps.py +0 -33
  14. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/LICENSE +0 -0
  15. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/README.md +0 -0
  16. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/fast_dev_cli/__main__.py +0 -0
  17. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/fast_dev_cli/py.typed +0 -0
  18. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/pdm_build.py +0 -0
  19. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/scripts/check.py +0 -0
  20. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/scripts/deps.py +0 -0
  21. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/scripts/format.py +0 -0
  22. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/scripts/test.py +0 -0
  23. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/__init__.py +0 -0
  24. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/assets/uv-tx.lock +0 -0
  25. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/assets/uv.lock +0 -0
  26. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/conftest.py +0 -0
  27. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_exec.py +0 -0
  28. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_help.py +0 -0
  29. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_lint.py +0 -0
  30. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_pypi.py +0 -0
  31. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_runserver.py +0 -0
  32. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_sync.py +0 -0
  33. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_tag.py +0 -0
  34. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_upload.py +0 -0
  35. {fast_dev_cli-0.18.3 → fast_dev_cli-0.18.5}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fast-dev-cli
3
- Version: 0.18.3
3
+ Version: 0.18.5
4
4
  Summary: Python project development tool.
5
5
  Author-Email: Waket Zheng <waketzheng@gmail.com>>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -0,0 +1 @@
1
+ __version__ = "0.18.5"
@@ -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,6 +103,11 @@ 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:
106
112
  secho(msg, fg="yellow")
107
113
 
@@ -124,40 +130,92 @@ def is_venv() -> bool:
124
130
  )
125
131
 
126
132
 
127
- def _run_shell(cmd: list[str] | str, **kw: Any) -> subprocess.CompletedProcess[str]:
128
- if isinstance(cmd, str):
129
- kw.setdefault("shell", True)
130
- return subprocess.run(cmd, **kw) # nosec:B603
133
+ class Shell:
134
+ def __init__(self, cmd: list[str] | str, **kw: Any) -> None:
135
+ self._cmd = cmd
136
+ self._kw = kw
137
+
138
+ @staticmethod
139
+ def run_by_subprocess(
140
+ cmd: list[str] | str, **kw: Any
141
+ ) -> subprocess.CompletedProcess[str]:
142
+ if isinstance(cmd, str):
143
+ kw.setdefault("shell", True)
144
+ return subprocess.run(cmd, **kw) # nosec:B603
145
+
146
+ @property
147
+ def command(self) -> list[str] | str:
148
+ command: list[str] | str = self._cmd
149
+ if (
150
+ isinstance(command, str)
151
+ and "shell" not in self._kw
152
+ and not (set(self._cmd) & {"|", ">", "&"})
153
+ ):
154
+ command = shlex.split(command)
155
+ return command
156
+
157
+ def _run(self) -> subprocess.CompletedProcess[str]:
158
+ return self.run_by_subprocess(self.command, **self._kw)
159
+
160
+ def run(self, verbose: bool = False, dry: bool = False) -> int:
161
+ if verbose:
162
+ echo(f"--> {self._cmd}")
163
+ if dry:
164
+ return 0
165
+ return self._run().returncode
166
+
167
+ def check_call(self) -> bool:
168
+ self._kw.update(stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
169
+ return self.run() == 0
170
+
171
+ def capture_output(self, raises: bool = False) -> str:
172
+ self._kw.update(capture_output=True, encoding="utf-8")
173
+ r = self._run()
174
+ if raises and r.returncode != 0:
175
+ raise ShellCommandError(r.stderr)
176
+ return (r.stdout or r.stderr or "").strip()
177
+
178
+ def finish(
179
+ self, env: dict[str, str] | None = None, _exit: bool = False, dry=False
180
+ ) -> subprocess.CompletedProcess[str]:
181
+ self.run(verbose=True, dry=True)
182
+ if _ensure_bool(dry):
183
+ return subprocess.CompletedProcess("", 0)
184
+ if env is not None:
185
+ self._kw["env"] = {**os.environ, **env}
186
+ r = self._run()
187
+ if rc := r.returncode:
188
+ if _exit:
189
+ sys.exit(rc)
190
+ raise Exit(rc)
191
+ return r
131
192
 
132
193
 
133
194
  def run_and_echo(
134
195
  cmd: str, *, dry: bool = False, verbose: bool = True, **kw: Any
135
196
  ) -> int:
136
197
  """Run shell command with subprocess and print it"""
137
- if verbose:
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
198
+ return Shell(cmd, **kw).run(verbose=verbose, dry=dry)
145
199
 
146
200
 
147
201
  def check_call(cmd: str) -> bool:
148
- r = _run_shell(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
149
- return r.returncode == 0
202
+ return Shell(cmd).check_call()
150
203
 
151
204
 
152
205
  def capture_cmd_output(
153
206
  command: list[str] | str, *, raises: bool = False, **kw: Any
154
207
  ) -> str:
155
- if isinstance(command, str) and not kw.get("shell"):
156
- command = shlex.split(command)
157
- r = _run_shell(command, capture_output=True, encoding="utf-8", **kw)
158
- if raises and r.returncode != 0:
159
- raise ShellCommandError(r.stderr)
160
- return r.stdout.strip() or r.stderr
208
+ return Shell(command, **kw).capture_output(raises=raises)
209
+
210
+
211
+ def exit_if_run_failed(
212
+ cmd: str,
213
+ env: dict[str, str] | None = None,
214
+ _exit: bool = False,
215
+ dry: bool = False,
216
+ **kw: Any,
217
+ ) -> subprocess.CompletedProcess[str]:
218
+ return Shell(cmd, **kw).finish(env=env, _exit=_exit, dry=dry)
161
219
 
162
220
 
163
221
  def _parse_version(line: str, pattern: re.Pattern[str]) -> str:
@@ -276,26 +334,6 @@ def _ensure_str(value: str | OptionInfo | None) -> str:
276
334
  return value
277
335
 
278
336
 
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
337
  class DryRun:
300
338
  def __init__(self, _exit: bool = False, dry: bool = False) -> None:
301
339
  self.dry = _ensure_bool(dry)
@@ -497,6 +535,13 @@ def version() -> None:
497
535
  with contextlib.suppress(FileNotFoundError, KeyError):
498
536
  toml_text = Project.load_toml_text()
499
537
  doc = tomllib.loads(toml_text)
538
+ if value := doc.get("project", {}).get("version", ""):
539
+ styled = typer.style(value, bold=True, fg=typer.colors.CYAN)
540
+ if project_name := doc["project"].get("name", ""):
541
+ echo(f"{project_name} version: " + styled)
542
+ else:
543
+ echo(f"Got Version from {TOML_FILE}: " + styled)
544
+ return
500
545
  version_file = doc["tool"]["pdm"]["version"]["path"]
501
546
  text = Project.get_work_dir().joinpath(version_file).read_text()
502
547
  varname = "__version__"
@@ -554,6 +599,19 @@ class Project:
554
599
  def is_poetry_v2(text: str) -> bool:
555
600
  return 'build-backend = "poetry' in text
556
601
 
602
+ @staticmethod
603
+ def get_poetry_version(command: str = "poetry") -> str:
604
+ pattern = r"(\d+\.\d+\.\d+)"
605
+ text = capture_cmd_output(f"{command} --version")
606
+ for expr in (
607
+ rf"Poetry \(version {pattern}\)",
608
+ rf"Poetry.*version.*{pattern}.*\)",
609
+ rf"{pattern}",
610
+ ):
611
+ if m := re.search(expr, text):
612
+ return m.group(1)
613
+ return ""
614
+
557
615
  @staticmethod
558
616
  def work_dir(
559
617
  name: str, parent: Path, depth: int, be_file: bool = False
@@ -650,7 +708,7 @@ class Project:
650
708
  @classmethod
651
709
  def get_sync_command(cls, prod: bool = True, doc: dict | None = None) -> str:
652
710
  if cls.is_pdm_project():
653
- return "pdm sync" + " --prod" * prod
711
+ return "pdm install --frozen" + " --prod" * prod
654
712
  elif cls.manage_by_poetry(cache=True):
655
713
  cmd = "poetry install"
656
714
  if prod:
@@ -847,7 +905,7 @@ class UpgradeDependencies(Project, DryRun):
847
905
  deps = "uv sync --inexact --frozen --all-groups --all-extras"
848
906
  return f"{up} && {deps}"
849
907
  elif self._tool == "pdm":
850
- return "pdm update --verbose && pdm sync -G :all --frozen"
908
+ return "pdm update --verbose && pdm install -G :all --frozen"
851
909
  return self.gen_cmd() + " && poetry lock && poetry update"
852
910
 
853
911
 
@@ -1037,11 +1095,12 @@ class LintCode(DryRun):
1037
1095
  if tool == ToolOption.default:
1038
1096
  tool = Project.get_manage_tool() or ""
1039
1097
  if tool:
1040
- prefix = (
1041
- bin_dir
1042
- if tool == "uv" and Path(bin_dir := ".venv/bin/").exists()
1043
- else (tool + " run ")
1044
- )
1098
+ prefix = tool + " run "
1099
+ if tool == "uv":
1100
+ if is_windows():
1101
+ prefix += "--no-sync "
1102
+ elif Path(bin_dir := ".venv/bin/").exists():
1103
+ prefix = bin_dir
1045
1104
  if cls.prefer_dmypy(paths, tools, use_dmypy=use_dmypy):
1046
1105
  tools[-1] = "dmypy run"
1047
1106
  cmd += lint_them.format(prefix, paths, *tools)
@@ -1063,11 +1122,21 @@ class LintCode(DryRun):
1063
1122
  ps = args.split() if isinstance(args, str) else [str(i) for i in args]
1064
1123
  if len(ps) == 1:
1065
1124
  paths = ps[0]
1066
- if paths != "." and paths.endswith(".") and not Path(paths).exists():
1067
- for ft in ("py", "html"):
1068
- name = paths + ft
1069
- if Path(name).exists():
1070
- paths = name
1125
+ if (
1126
+ paths != "."
1127
+ # `Path("a.").suffix` got "." in py3.14 and got "" with py<3.14
1128
+ and (p := Path(paths)).suffix in ("", ".")
1129
+ and not p.exists()
1130
+ ):
1131
+ # e.g.:
1132
+ # stem -> stem.py
1133
+ # me. -> me.py
1134
+ if paths.endswith("."):
1135
+ p = p.with_name(paths[:-1])
1136
+ for suffix in (".py", ".html"):
1137
+ p = p.with_suffix(suffix)
1138
+ if p.exists():
1139
+ paths = p.name
1071
1140
  break
1072
1141
  else:
1073
1142
  paths = " ".join(ps)
@@ -1240,7 +1309,9 @@ def test(dry: bool, ignore_script: bool = False) -> None:
1240
1309
  if not _ensure_bool(ignore_script) and (
1241
1310
  test_script := _should_run_test_script(script_dir)
1242
1311
  ):
1243
- cmd = f"{os.path.relpath(test_script, root)}"
1312
+ cmd = test_script.relative_to(root).as_posix()
1313
+ if test_script.suffix == ".py":
1314
+ cmd = "python " + cmd
1244
1315
  if cwd != root:
1245
1316
  cmd = f"cd {root} && " + cmd
1246
1317
  else:
@@ -1331,8 +1402,11 @@ def run_by_subprocess(cmd: str, dry: bool = DryOption) -> None:
1331
1402
  try:
1332
1403
  rc = run_and_echo(cmd, verbose=True, dry=_ensure_bool(dry))
1333
1404
  except FileNotFoundError as e:
1334
- if e.filename == cmd.split()[0]:
1335
- echo(f"Command not found: {e.filename}")
1405
+ command = cmd.split()[0]
1406
+ if e.filename == command or (
1407
+ e.filename is None and "系统找不到指定的文件" in str(e)
1408
+ ):
1409
+ echo(f"Command not found: {command}")
1336
1410
  raise Exit(1) from None
1337
1411
  raise e
1338
1412
  else:
@@ -1341,9 +1415,18 @@ def run_by_subprocess(cmd: str, dry: bool = DryOption) -> None:
1341
1415
 
1342
1416
 
1343
1417
  class MakeDeps(DryRun):
1344
- def __init__(self, tool: str, prod: bool = False, dry: bool = False) -> None:
1418
+ def __init__(
1419
+ self,
1420
+ tool: str,
1421
+ prod: bool = False,
1422
+ dry: bool = False,
1423
+ active: bool = True,
1424
+ inexact: bool = True,
1425
+ ) -> None:
1345
1426
  self._tool = tool
1346
1427
  self._prod = prod
1428
+ self._active = active
1429
+ self._inexact = inexact
1347
1430
  super().__init__(dry=dry)
1348
1431
 
1349
1432
  def should_ensure_pip(self) -> bool:
@@ -1359,11 +1442,12 @@ class MakeDeps(DryRun):
1359
1442
 
1360
1443
  def gen(self) -> str:
1361
1444
  if self._tool == "pdm":
1362
- return "pdm sync " + ("--prod" if self._prod else "-G :all")
1445
+ return "pdm install --frozen " + ("--prod" if self._prod else "-G :all")
1363
1446
  elif self._tool == "uv":
1364
- return "uv sync --inexact --active" + (
1365
- "" if self._prod else " --all-extras --all-groups"
1366
- )
1447
+ uv_sync = "uv sync" + " --inexact" * self._inexact
1448
+ if self._active:
1449
+ uv_sync += " --active"
1450
+ return uv_sync + ("" if self._prod else " --all-extras --all-groups")
1367
1451
  elif self._tool == "poetry":
1368
1452
  return "poetry install " + (
1369
1453
  "--only=main" if self._prod else "--all-extras --all-groups"
@@ -1392,6 +1476,12 @@ def make_deps(
1392
1476
  use_pdm: bool = Option(False, "--pdm", help="Use `pdm` to install deps"),
1393
1477
  use_pip: bool = Option(False, "--pip", help="Use `pip` to install deps"),
1394
1478
  use_poetry: bool = Option(False, "--poetry", help="Use `poetry` to install deps"),
1479
+ active: bool = Option(
1480
+ True, help="Add `--active` to uv sync command(Only work for uv project)"
1481
+ ),
1482
+ inexact: bool = Option(
1483
+ True, help="Add `--inexact` to uv sync command(Only work for uv project)"
1484
+ ),
1395
1485
  dry: bool = DryOption,
1396
1486
  ) -> None:
1397
1487
  """Run: ruff check/format to reformat code and then mypy to check"""
@@ -1407,7 +1497,7 @@ def make_deps(
1407
1497
  tool = "poetry"
1408
1498
  elif tool == ToolOption.default:
1409
1499
  tool = Project.get_manage_tool(cache=True) or "pip"
1410
- MakeDeps(tool, prod, dry=dry).run()
1500
+ MakeDeps(tool, prod, active=active, inexact=inexact, dry=dry).run()
1411
1501
 
1412
1502
 
1413
1503
  class UvPypi(DryRun):
@@ -42,7 +42,7 @@ dependencies = [
42
42
  "bumpversion2 >=1.4.3",
43
43
  "pytest >=8.2.0",
44
44
  ]
45
- version = "0.18.3"
45
+ version = "0.18.5"
46
46
 
47
47
  [project.urls]
48
48
  Homepage = "https://github.com/waketzheng/fast-dev-cli"
@@ -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/__init__.py"
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/__init__.py"
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/hello/__init__.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 str(init_file) in out
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 str(version_file) in out
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 str(version_file) in out
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 str(version_file) in out
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 str(version_file) in out
403
+ assert version_file.as_posix() in out
404
404
  assert "0.3.1" in version_file.read_text()
@@ -0,0 +1,50 @@
1
+ from fast_dev_cli.cli import MakeDeps, capture_cmd_output
2
+
3
+
4
+ def test_make_deps_class():
5
+ assert (
6
+ MakeDeps("uv", prod=False).gen()
7
+ == "uv sync --inexact --active --all-extras --all-groups"
8
+ )
9
+ assert (
10
+ MakeDeps("uv", prod=False, active=False).gen()
11
+ == "uv sync --inexact --all-extras --all-groups"
12
+ )
13
+ assert MakeDeps("uv", prod=True).gen() == "uv sync --inexact --active"
14
+ assert MakeDeps("uv", prod=True, active=False).gen() == "uv sync --inexact"
15
+ assert MakeDeps("pdm", prod=False).gen() == "pdm install --frozen -G :all"
16
+ assert MakeDeps("pdm", prod=True).gen() == "pdm install --frozen --prod"
17
+ assert (
18
+ MakeDeps("poetry", prod=False).gen()
19
+ == "poetry install --all-extras --all-groups"
20
+ )
21
+ assert MakeDeps("poetry", prod=True).gen() == "poetry install --only=main"
22
+ assert (
23
+ MakeDeps("pip", prod=False).gen()
24
+ == "python -m ensurepip && python -m pip install --upgrade pip && python -m pip install -e . --group dev"
25
+ )
26
+ assert (
27
+ MakeDeps("pip", prod=True).gen()
28
+ == "python -m ensurepip && python -m pip install --upgrade pip && python -m pip install -e ."
29
+ )
30
+
31
+
32
+ def test_fast_deps():
33
+ out = capture_cmd_output("fast deps --dry")
34
+ assert out == "--> pdm install --frozen -G :all"
35
+ out = capture_cmd_output("fast deps --uv --prod --dry")
36
+ assert out == "--> uv sync --inexact --active"
37
+ out = capture_cmd_output("fast deps --uv --prod --dry --no-active")
38
+ assert out == "--> uv sync --inexact"
39
+ out = capture_cmd_output("fast deps --uv --prod --dry --no-inexact")
40
+ assert out == "--> uv sync --active"
41
+ out = capture_cmd_output("fast deps --uv --prod --dry --no-active --no-inexact")
42
+ assert out == "--> uv sync"
43
+ out = capture_cmd_output("fast deps --uv --dry")
44
+ assert out == "--> uv sync --inexact --active --all-extras --all-groups"
45
+ out = capture_cmd_output("fast deps --uv --dry --no-active")
46
+ assert out == "--> uv sync --inexact --all-extras --all-groups"
47
+ out = capture_cmd_output("fast deps --uv --dry --no-inexact")
48
+ assert out == "--> uv sync --active --all-extras --all-groups"
49
+ out = capture_cmd_output("fast deps --uv --dry --no-active --no-inexact")
50
+ assert out == "--> uv sync --all-extras --all-groups"
@@ -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
 
@@ -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 chdir(tmp_path):
89
- project = tmp_path / "project"
90
- run_and_echo(f"poetry new {project.name}")
91
- with chdir(project):
92
- if sys.version_info < (3, 13):
93
- p = Path("pyproject.toml")
94
- content = p.read_text()
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
- run_and_echo("poetry add --group=dev typer")
95
+ else:
96
+ toml_file = Path(TOML_FILE)
102
97
  assert UpgradeDependencies.should_with_dev()
103
- text = project.joinpath(TOML_FILE).read_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
- project.joinpath(TOML_FILE).write_text(new_text)
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 sync -G :all --frozen"
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.3"
@@ -1,33 +0,0 @@
1
- from fast_dev_cli.cli import MakeDeps, capture_cmd_output
2
-
3
-
4
- def test_make_deps_class():
5
- assert (
6
- MakeDeps("uv", prod=False).gen()
7
- == "uv sync --inexact --active --all-extras --all-groups"
8
- )
9
- assert MakeDeps("uv", prod=True).gen() == "uv sync --inexact --active"
10
- assert MakeDeps("pdm", prod=False).gen() == "pdm sync -G :all"
11
- assert MakeDeps("pdm", prod=True).gen() == "pdm sync --prod"
12
- assert (
13
- MakeDeps("poetry", prod=False).gen()
14
- == "poetry install --all-extras --all-groups"
15
- )
16
- assert MakeDeps("poetry", prod=True).gen() == "poetry install --only=main"
17
- assert (
18
- MakeDeps("pip", prod=False).gen()
19
- == "python -m ensurepip && python -m pip install --upgrade pip && python -m pip install -e . --group dev"
20
- )
21
- assert (
22
- MakeDeps("pip", prod=True).gen()
23
- == "python -m ensurepip && python -m pip install --upgrade pip && python -m pip install -e ."
24
- )
25
-
26
-
27
- def test_fast_deps():
28
- out = capture_cmd_output("fast deps --dry")
29
- assert out == "--> pdm sync -G :all"
30
- out = capture_cmd_output("fast deps --uv --prod --dry")
31
- assert out == "--> uv sync --inexact --active"
32
- out = capture_cmd_output("fast deps --uv --dry")
33
- assert out == "--> uv sync --inexact --active --all-extras --all-groups"
File without changes
File without changes