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.
Files changed (34) hide show
  1. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/PKG-INFO +1 -1
  2. fast_dev_cli-0.18.6/fast_dev_cli/__init__.py +1 -0
  3. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/fast_dev_cli/cli.py +131 -55
  4. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/pyproject.toml +1 -1
  5. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_bump.py +8 -8
  6. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_deps.py +3 -3
  7. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_fast_test.py +1 -1
  8. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_functions.py +8 -0
  9. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_lint.py +1 -1
  10. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_poetry_version_plugin.py +7 -0
  11. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_upgrade.py +14 -19
  12. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/utils.py +5 -3
  13. fast_dev_cli-0.18.4/fast_dev_cli/__init__.py +0 -1
  14. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/LICENSE +0 -0
  15. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/README.md +0 -0
  16. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/fast_dev_cli/__main__.py +0 -0
  17. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/fast_dev_cli/py.typed +0 -0
  18. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/pdm_build.py +0 -0
  19. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/scripts/check.py +0 -0
  20. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/scripts/deps.py +0 -0
  21. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/scripts/format.py +0 -0
  22. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/scripts/test.py +0 -0
  23. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/__init__.py +0 -0
  24. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/assets/uv-tx.lock +0 -0
  25. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/assets/uv.lock +0 -0
  26. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/conftest.py +0 -0
  27. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_exec.py +0 -0
  28. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_help.py +0 -0
  29. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_pypi.py +0 -0
  30. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_runserver.py +0 -0
  31. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_sync.py +0 -0
  32. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_tag.py +0 -0
  33. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/tests/test_upload.py +0 -0
  34. {fast_dev_cli-0.18.4 → fast_dev_cli-0.18.6}/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.4
3
+ Version: 0.18.6
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.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
- 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
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
- 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
200
+ return Shell(cmd, **kw).run(verbose=verbose, dry=dry)
145
201
 
146
202
 
147
203
  def check_call(cmd: str) -> bool:
148
- r = _run_shell(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
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
- 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
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 sync" + " --prod" * prod
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 sync -G :all --frozen"
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
- return check_call("ruff --version")
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
- bin_dir
1049
- if tool == "uv" and Path(bin_dir := ".venv/bin/").exists()
1050
- else (tool + " run ")
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 paths != "." and not (p := Path(paths)).suffix and not p.exists():
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 = f"{os.path.relpath(test_script, root)}"
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
- if e.filename == cmd.split()[0]:
1347
- echo(f"Command not found: {e.filename}")
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 sync " + ("--prod" if self._prod else "-G :all")
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:
@@ -42,7 +42,7 @@ dependencies = [
42
42
  "bumpversion2 >=1.4.3",
43
43
  "pytest >=8.2.0",
44
44
  ]
45
- version = "0.18.4"
45
+ version = "0.18.6"
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()
@@ -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 sync -G :all"
16
- assert MakeDeps("pdm", prod=True).gen() == "pdm sync --prod"
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 sync -G :all"
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 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.4"
File without changes
File without changes