fast-dev-cli 0.25.0__tar.gz → 0.25.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fast-dev-cli
3
- Version: 0.25.0
3
+ Version: 0.25.2
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.25.2"
@@ -114,13 +114,19 @@ def yellow_warn(msg: str) -> None:
114
114
  def load_bool(name: str, default: bool = False, *, verbose: bool = True) -> bool:
115
115
  if not (v := os.getenv(name)):
116
116
  return default
117
+ if (b := _convert_bool(v)) is not None:
118
+ return b
119
+ if verbose:
120
+ secho(f"WARNING: can not convert value({v!r}) of {name} to bool!")
121
+ return default
122
+
123
+
124
+ def _convert_bool(v: str) -> bool | None:
117
125
  if (lower := v.lower()) in ("0", "false", "f", "off", "no", "n"):
118
126
  return False
119
127
  elif lower in ("1", "true", "t", "on", "yes", "y"):
120
128
  return True
121
- if verbose:
122
- secho(f"WARNING: can not convert value({v!r}) of {name} to bool!")
123
- return default
129
+ return None
124
130
 
125
131
 
126
132
  def is_venv() -> bool:
@@ -141,7 +147,8 @@ class Shell:
141
147
  ) -> subprocess.CompletedProcess[str]:
142
148
  if isinstance(cmd, str):
143
149
  kw.setdefault("shell", True)
144
- return subprocess.run(cmd, **kw) # nosec:B603
150
+ check = kw.pop("check", False)
151
+ return subprocess.run(cmd, check=check, **kw) # nosec:B603
145
152
 
146
153
  @property
147
154
  def command(self) -> list[str] | str:
@@ -260,13 +267,12 @@ def read_version_from_file(
260
267
  for line in all_lines:
261
268
  if pattern.match(line):
262
269
  return _parse_version(line, pattern)
263
- else:
264
- pattern = re.compile(r"VERSION\s*=")
265
- for line in all_lines:
266
- if pattern.match(line):
267
- return _parse_version(line, pattern)
268
- secho(f"WARNING: can not find version pattern in {version_file}!")
269
- return "0.0.0"
270
+ pattern = re.compile(r"VERSION\s*=")
271
+ for line in all_lines:
272
+ if pattern.match(line):
273
+ return _parse_version(line, pattern)
274
+ secho(f"WARNING: can not find version pattern in {version_file}!")
275
+ return "0.0.0"
270
276
 
271
277
 
272
278
  def _get_frontend_version() -> tuple[Path, str] | None:
@@ -325,9 +331,9 @@ def get_current_version(
325
331
  if package_name is None:
326
332
  try:
327
333
  work_dir = Project.get_work_dir()
328
- except EnvError as e:
334
+ except EnvError:
329
335
  if (res := _get_frontend_version()) is None:
330
- raise e
336
+ raise
331
337
  current_version = res[1]
332
338
  if check_version:
333
339
  return False, current_version
@@ -372,6 +378,36 @@ def _ensure_str(value: str | OptionInfo | None) -> str | None:
372
378
  return getattr(value, "default", "")
373
379
 
374
380
 
381
+ def _quote_shell_arg(value: str | Path) -> str:
382
+ text = str(value)
383
+ if not is_windows():
384
+ return shlex.quote(text)
385
+ if text and not re.search(r'[\s"&|<>^()%!]', text):
386
+ return text
387
+
388
+ # Quote for the Windows C runtime while keeping cmd.exe metacharacters inert.
389
+ result = ['"']
390
+ backslashes = 0
391
+ for char in text:
392
+ if char == "\\":
393
+ backslashes += 1
394
+ elif char == '"':
395
+ result.append("\\" * (backslashes * 2 + 1))
396
+ result.append(char)
397
+ backslashes = 0
398
+ else:
399
+ result.append("\\" * backslashes)
400
+ result.append(char)
401
+ backslashes = 0
402
+ result.append("\\" * (backslashes * 2))
403
+ result.append('"')
404
+ return "".join(result)
405
+
406
+
407
+ def _join_shell_args(args: list[str]) -> str:
408
+ return " ".join(_quote_shell_arg(arg) for arg in args)
409
+
410
+
375
411
  class DryRun:
376
412
  def __init__(self, _exit: bool = False, dry: bool = False) -> None:
377
413
  self.dry = _ensure_bool(dry)
@@ -468,7 +504,7 @@ class BumpUp(DryRun):
468
504
  toml_text: str | None = None,
469
505
  work_dir: Path | None = None,
470
506
  package_name: str | None = None,
471
- context: dict | None = None,
507
+ context: dict[str, Any] | None = None,
472
508
  ) -> str:
473
509
  if context is None:
474
510
  if toml_text is None:
@@ -528,11 +564,11 @@ class BumpUp(DryRun):
528
564
  ds: list[Path] = []
529
565
  if package_name is not None:
530
566
  packages.insert(0, (package_name, ""))
531
- for package_name, source_dir in packages:
532
- ds.append(cwd / package_name)
533
- ds.append(cwd / "src" / package_name)
567
+ for pack_name, source_dir in packages:
568
+ ds.append(cwd / pack_name)
569
+ ds.append(cwd / "src" / pack_name)
534
570
  if source_dir and source_dir != "src":
535
- ds.append(cwd / source_dir / package_name)
571
+ ds.append(cwd / source_dir / pack_name)
536
572
  module_name = poetry_module_name(cwd.name)
537
573
  ds.extend([cwd / module_name, cwd / "src" / module_name, cwd])
538
574
  for d in ds:
@@ -569,7 +605,10 @@ class BumpUp(DryRun):
569
605
  part = self.get_part(a)
570
606
  self.part = part
571
607
  parse = r'--parse "(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"'
572
- cmd = f'bumpversion {parse} --current-version="{_version}" {part} {filename}'
608
+ filename_arg = _quote_shell_arg(filename)
609
+ cmd = (
610
+ f'bumpversion {parse} --current-version="{_version}" {part} {filename_arg}'
611
+ )
573
612
  if self.commit:
574
613
  if part != "patch":
575
614
  cmd += " --tag"
@@ -756,7 +795,7 @@ class Project:
756
795
  if skip_uv:
757
796
  for name in ("pdm", "poetry"):
758
797
  if f"[tool.{name}]" in text:
759
- cls._tool = cast(ToolName, name)
798
+ cls._tool = name
760
799
  return cls._tool
761
800
  work_dir = cls.get_work_dir(allow_cwd=True)
762
801
  for name in ("pdm", "poetry"):
@@ -791,11 +830,11 @@ class Project:
791
830
  if backend:
792
831
  for t in ("pdm", "poetry"):
793
832
  if t in backend:
794
- cls._tool = cast(ToolName, t)
833
+ cls._tool = t
795
834
  return cls._tool
796
835
  for name in ("pdm", "poetry"):
797
836
  if f"[tool.{name}]" in text:
798
- cls._tool = cast(ToolName, name)
837
+ cls._tool = name
799
838
  return cls._tool
800
839
  if x == 2:
801
840
  cls._tool = (
@@ -919,7 +958,7 @@ class UpgradeDependencies(Project, DryRun):
919
958
  elif v == "[":
920
959
  echo(f"Skip complex dependence: {line}")
921
960
  return True
922
- elif v.startswith(">") or v.startswith("<") or v[0].isdigit():
961
+ elif v.startswith((">", "<")) or v[0].isdigit():
923
962
  echo(f"Ignore bigger/smaller/equal: {line}")
924
963
  return True
925
964
  return False
@@ -1082,7 +1121,10 @@ class GitTag(DryRun):
1082
1121
  if self.has_v_prefix():
1083
1122
  # Add `v` at prefix to compare with bumpversion tool
1084
1123
  _version = "v" + _version
1085
- cmd = f"git tag -a {_version} -m {self.message!r} && git push --tags"
1124
+ cmd = (
1125
+ f"git tag -a {_quote_shell_arg(_version)} "
1126
+ f"-m {_quote_shell_arg(self.message)} && git push --tags"
1127
+ )
1086
1128
  if self.should_push():
1087
1129
  cmd += " && git push"
1088
1130
  if should_sync and not self._no_sync and (sync := Project.get_sync_command()):
@@ -1184,7 +1226,7 @@ class LintCode(DryRun):
1184
1226
  @classmethod
1185
1227
  def to_cmd(
1186
1228
  cls: type[Self],
1187
- paths: str = ".",
1229
+ paths: str | list[str] = ".",
1188
1230
  check_only: bool = False,
1189
1231
  bandit: bool = False,
1190
1232
  skip_mypy: bool = False,
@@ -1197,8 +1239,12 @@ class LintCode(DryRun):
1197
1239
  prefer_ty: bool = False,
1198
1240
  ruff_check_fix: bool = True,
1199
1241
  ) -> str:
1200
- if paths != "." and all(i.endswith(".html") for i in paths.split()):
1201
- return f"prettier -w {paths}"
1242
+ path_args = shlex.split(paths) if isinstance(paths, str) else paths
1243
+ if not path_args:
1244
+ path_args = ["."]
1245
+ quoted_paths = _join_shell_args(path_args)
1246
+ if path_args != ["."] and all(i.endswith(".html") for i in path_args):
1247
+ return f"prettier -w {quoted_paths}"
1202
1248
  ruff_rules = ["I", "B"]
1203
1249
  if ruff_check_sim and not load_bool("FASTDEVCLI_NO_SIM"):
1204
1250
  ruff_rules.append("SIM")
@@ -1288,7 +1334,7 @@ class LintCode(DryRun):
1288
1334
  prefix += "--no-sync "
1289
1335
  elif Path(bin_dir := ".venv/bin/").exists():
1290
1336
  prefix = bin_dir
1291
- if cls.prefer_dmypy(paths, tools, use_dmypy=use_dmypy):
1337
+ if cls.prefer_dmypy(quoted_paths, tools, use_dmypy=use_dmypy):
1292
1338
  tools[-1] = "dmypy run"
1293
1339
  cmd = " && ".join(
1294
1340
  (
@@ -1302,7 +1348,7 @@ class LintCode(DryRun):
1302
1348
  )
1303
1349
  else prefix + tool
1304
1350
  )
1305
- + f" {paths}"
1351
+ + f" {quoted_paths}"
1306
1352
  for tool in tools
1307
1353
  )
1308
1354
  if bandit or load_bool("FASTDEVCLI_BANDIT"):
@@ -1311,36 +1357,35 @@ class LintCode(DryRun):
1311
1357
  toml_text = Project.load_toml_text()
1312
1358
  if "[tool.bandit" in toml_text:
1313
1359
  command += " -c pyproject.toml"
1314
- if paths == "." and " -c " not in command:
1315
- paths = cls.get_package_name()
1316
- command += f" -r {paths}"
1360
+ if quoted_paths == "." and " -c " not in command:
1361
+ quoted_paths = _quote_shell_arg(cls.get_package_name())
1362
+ command += f" -r {quoted_paths}"
1317
1363
  cmd += " && " + command
1318
1364
  return cmd
1319
1365
 
1320
1366
  def gen(self) -> str:
1321
- paths = "."
1367
+ paths = ["."]
1322
1368
  if args := self.args:
1323
- ps = args.split() if isinstance(args, str) else [str(i) for i in args]
1369
+ ps = shlex.split(args) if isinstance(args, str) else [str(i) for i in args]
1324
1370
  if len(ps) == 1:
1325
- paths = ps[0]
1371
+ path = ps[0]
1326
1372
  if (
1327
- paths != "."
1373
+ path != "."
1328
1374
  # `Path("a.").suffix` got "." in py3.14 and got "" with py<3.14
1329
- and (p := Path(paths)).suffix in ("", ".")
1375
+ and (p := Path(path)).suffix in ("", ".")
1330
1376
  and not p.exists()
1331
1377
  ):
1332
1378
  # e.g.:
1333
1379
  # stem -> stem.py
1334
1380
  # me. -> me.py
1335
- if paths.endswith("."):
1336
- p = p.with_name(paths[:-1])
1381
+ if path.endswith("."):
1382
+ p = p.with_name(path[:-1])
1337
1383
  for suffix in (".py", ".html"):
1338
1384
  p = p.with_suffix(suffix)
1339
1385
  if p.exists():
1340
- paths = p.name
1386
+ ps[0] = p.name
1341
1387
  break
1342
- else:
1343
- paths = " ".join(ps)
1388
+ paths = ps
1344
1389
  return self.to_cmd(
1345
1390
  paths,
1346
1391
  self.check_only,
@@ -1423,6 +1468,14 @@ def check(
1423
1468
  ).run()
1424
1469
 
1425
1470
 
1471
+ def _should_bandit() -> bool:
1472
+ if v := os.getenv("FASTDEVCLI_BANDIT"):
1473
+ return _convert_bool(v) or True
1474
+ toml_text = Project.load_toml_text()
1475
+ lines = toml_text.splitlines()
1476
+ return any(i.startswith("[tool.bandit") for i in lines)
1477
+
1478
+
1426
1479
  @cli.command(name="lint")
1427
1480
  def make_style(
1428
1481
  files: list[str] | None = typer.Argument(default=None), # noqa:B008
@@ -1444,6 +1497,9 @@ def make_style(
1444
1497
  strict: bool = Option(False, help="Whether run mypy with --strict"),
1445
1498
  ty: bool = Option(False, help="Whether use ty instead of mypy"),
1446
1499
  fix: bool | None = Option(None, help="Whether ruff check with --fix"),
1500
+ auto_bandit: bool | None = Option(
1501
+ None, help="Whether to run bandit if `[tool.bandit]` in pyproject.toml"
1502
+ ),
1447
1503
  ) -> None:
1448
1504
  """Run: ruff check/format to reformat code and then mypy to check"""
1449
1505
  if getattr(files, "default", files) is None:
@@ -1452,7 +1508,9 @@ def make_style(
1452
1508
  files = [files]
1453
1509
  skip = _ensure_bool(skip_mypy)
1454
1510
  dmypy = _ensure_bool(use_dmypy)
1455
- bandit = _ensure_bool(bandit)
1511
+ bandit = _ensure_bool(bandit) or (
1512
+ auto_bandit is not None and _ensure_bool(auto_bandit) and _should_bandit()
1513
+ )
1456
1514
  tool = _ensure_str(tool) or ""
1457
1515
  up = _ensure_bool(up)
1458
1516
  sim = _ensure_bool(sim)
@@ -1498,10 +1556,11 @@ class Sync(DryRun):
1498
1556
  def gen(self) -> str:
1499
1557
  extras, save = self.extras, self._save
1500
1558
  should_remove = not Path.cwd().joinpath(self.filename).exists()
1559
+ filename = _quote_shell_arg(self.filename)
1501
1560
  if not (tool := Project.get_manage_tool()):
1502
1561
  if should_remove or not is_venv():
1503
1562
  raise EnvError("There project is not managed by uv/pdm/poetry!")
1504
- return f"python -m pip install -r {self.filename}"
1563
+ return f"python -m pip install -r {filename}"
1505
1564
  prefix = ""
1506
1565
  if not is_venv():
1507
1566
  prefix = f"{tool} run " + "--no-sync " * (tool == "uv")
@@ -1514,7 +1573,7 @@ class Sync(DryRun):
1514
1573
  if not UpgradeDependencies.should_with_dev():
1515
1574
  export_cmd = export_cmd.replace(" --with=dev", "")
1516
1575
  if extras and isinstance(extras, str | list):
1517
- export_cmd += f" --{extras=}".replace("'", '"')
1576
+ export_cmd += f" --extras={_quote_shell_arg(str(extras))}"
1518
1577
  elif check_call(prefix + "python -m pip --version"):
1519
1578
  ensure_pip = ""
1520
1579
  elif check_call(prefix + "python -m pip --version"):
@@ -1524,7 +1583,7 @@ class Sync(DryRun):
1524
1583
  )
1525
1584
  if should_remove and not save:
1526
1585
  install_cmd += " && rm -f {0}"
1527
- return install_cmd.format(self.filename, prefix, export_cmd)
1586
+ return install_cmd.format(filename, prefix, export_cmd)
1528
1587
 
1529
1588
 
1530
1589
  @cli.command()
@@ -1554,11 +1613,11 @@ def test(dry: bool, ignore_script: bool = False) -> None:
1554
1613
  if not _ensure_bool(ignore_script) and (
1555
1614
  test_script := _should_run_test_script(script_dir)
1556
1615
  ):
1557
- cmd = test_script.relative_to(root).as_posix()
1616
+ cmd = _quote_shell_arg(test_script.relative_to(root).as_posix())
1558
1617
  if test_script.suffix == ".py":
1559
1618
  cmd = "python " + cmd
1560
1619
  if cwd != root:
1561
- cmd = f"cd {root} && " + cmd
1620
+ cmd = f"cd {_quote_shell_arg(root)} && " + cmd
1562
1621
  else:
1563
1622
  cmd = 'coverage run -m pytest -s && coverage report --omit="tests/*" -m'
1564
1623
  if not is_venv() or not check_call("coverage --version"):
@@ -1585,7 +1644,7 @@ class Publish:
1585
1644
  twine = "python -m build && twine upload"
1586
1645
 
1587
1646
  @classmethod
1588
- def gen(cls, tool: str, verbose: bool) -> str:
1647
+ def gen(cls, tool: str | None, verbose: bool) -> str:
1589
1648
  if tool == "auto":
1590
1649
  tool = Project.get_manage_tool() or ""
1591
1650
  if tool:
@@ -1612,7 +1671,7 @@ def upload(
1612
1671
  dry: bool = DryOption,
1613
1672
  ) -> None:
1614
1673
  """Shortcut for package publish"""
1615
- cmd = Publish.gen(tool, verbose)
1674
+ cmd = Publish.gen(_ensure_str(tool), verbose)
1616
1675
  exit_if_run_failed(cmd, dry=dry)
1617
1676
 
1618
1677
 
@@ -1626,6 +1685,7 @@ def should_use_just() -> bool:
1626
1685
  return _prefer_just_dev(f)
1627
1686
  if d.joinpath("pyproject.toml").exists():
1628
1687
  break
1688
+ d = d.parent
1629
1689
  return False
1630
1690
 
1631
1691
 
@@ -1635,10 +1695,22 @@ def _prefer_just_dev(f: Path) -> bool:
1635
1695
  dev_recipe = "dev *args:"
1636
1696
  re_import = re.compile(r"import[?]? ")
1637
1697
  has_import = False
1698
+ total = len(lines)
1638
1699
  for i, line in enumerate(lines):
1639
1700
  if line.startswith(dev_recipe):
1640
1701
  # Avoid cycle callback
1641
- return "fast dev" not in lines[i + 1]
1702
+ command_lines = []
1703
+ for j in range(i + 1, total):
1704
+ try:
1705
+ s = lines[j]
1706
+ except IndexError:
1707
+ break
1708
+ if not s.startswith(" "):
1709
+ break
1710
+ command_lines.append(s)
1711
+ if not command_lines: # Invalid justfile
1712
+ return False
1713
+ return all("fast dev" not in i for i in command_lines)
1642
1714
  elif not has_import and re_import.match(line):
1643
1715
  has_import = True
1644
1716
  if has_import:
@@ -1650,7 +1722,20 @@ def _prefer_just_dev(f: Path) -> bool:
1650
1722
  return False
1651
1723
 
1652
1724
 
1653
- def _parse_serve_file(uvicorn, filename: str, cmd: str, args: list) -> str:
1725
+ def _load_fastapi_entrypoint() -> str:
1726
+ with contextlib.suppress(FileNotFoundError, KeyError):
1727
+ try:
1728
+ toml_text = Project.load_toml_text()
1729
+ except EnvError:
1730
+ return ""
1731
+ doc = tomllib.loads(toml_text)
1732
+ return doc["tool"]["fastapi"]["entrypoint"]
1733
+ return ""
1734
+
1735
+
1736
+ def _parse_serve_file(
1737
+ uvicorn: bool | None, filename: str, cmd: str, args: list[str]
1738
+ ) -> str:
1654
1739
  if m := re.search(r"(.*):(\d+)$", filename):
1655
1740
  h, p = m.group(1), m.group(2)
1656
1741
  if h and "--host" not in str(args):
@@ -1660,24 +1745,32 @@ def _parse_serve_file(uvicorn, filename: str, cmd: str, args: list) -> str:
1660
1745
  args.append(f"--host={h}")
1661
1746
  args.append(f"--port={p}")
1662
1747
  if uvicorn:
1663
- p = Path("main.py")
1664
- if p.exists():
1665
- cmd += " main:app"
1666
- elif Path("app", p.name).exists():
1667
- cmd += " app.main:app"
1668
- elif Path("app.py").exists():
1669
- cmd += " app:app"
1748
+ if entrypoint := _load_fastapi_entrypoint():
1749
+ cmd += " " + entrypoint
1750
+ else:
1751
+ p = Path("main.py")
1752
+ if p.exists():
1753
+ cmd += " main:app"
1754
+ elif Path("app", p.name).exists():
1755
+ cmd += " app.main:app"
1756
+ elif Path("app.py").exists():
1757
+ cmd += " app:app"
1670
1758
  return cmd
1671
1759
  if uvicorn and ((filepath := Path(filename)).is_file() or filepath.suffix == ".py"):
1672
1760
  filename = filepath.stem + ":app"
1673
1761
  parent_names = [j for i in filepath.parents if (j := i.name)]
1674
1762
  if parent_names:
1675
1763
  filename = ".".join([*parent_names[::-1], filename])
1676
- cmd += " " + filename
1764
+ cmd += " " + _quote_shell_arg(filename)
1677
1765
  return cmd
1678
1766
 
1679
1767
 
1680
- def _runserver(uvicorn, host, port, file) -> tuple[str, list[str]]:
1768
+ def _runserver(
1769
+ uvicorn: bool | None,
1770
+ host: OptionInfo | str | None,
1771
+ port: OptionInfo | int | None,
1772
+ file: ArgumentInfo | str | None,
1773
+ ) -> tuple[str, list[str]]:
1681
1774
  cmd = "uvicorn" if uvicorn else "fastapi dev"
1682
1775
  args = []
1683
1776
  if (host := getattr(host, "default", host)) and host not in (
@@ -1696,6 +1789,8 @@ def _runserver(uvicorn, host, port, file) -> tuple[str, list[str]]:
1696
1789
  if port != 8000:
1697
1790
  args.append(f"--port={port}")
1698
1791
  no_port_yet = False
1792
+ elif uvicorn and (entrypoint := _load_fastapi_entrypoint()):
1793
+ cmd += " " + entrypoint
1699
1794
  if no_port_yet and (port := getattr(port, "default", port)) and str(port) != "8000":
1700
1795
  args.append(f"--port={port}")
1701
1796
  if shutil.which("pdm") is not None:
@@ -1710,16 +1805,17 @@ def dev(
1710
1805
  uvicorn: bool | None = None,
1711
1806
  prod: bool | None = None,
1712
1807
  reload: bool | None = None,
1808
+ just: bool | None = None,
1713
1809
  file: str | None | ArgumentInfo = None,
1714
1810
  dry: bool = False,
1715
1811
  ) -> None:
1716
- if should_use_just():
1812
+ if just is not False and should_use_just():
1717
1813
  args = [i for i in sys.argv[2:] if i != "--dry"]
1718
1814
  cmd = "just dev"
1719
1815
  else:
1720
1816
  cmd, args = _runserver(uvicorn, host, port, file)
1721
1817
  if args:
1722
- cmd += " " + " ".join(args)
1818
+ cmd += " " + _join_shell_args(args)
1723
1819
  exit_if_run_failed(cmd, dry=dry)
1724
1820
 
1725
1821
 
@@ -1732,10 +1828,13 @@ def runserver(
1732
1828
  uvicorn: bool | None = None,
1733
1829
  prod: bool | None = None,
1734
1830
  reload: bool | None = None,
1831
+ just: bool | None = None,
1735
1832
  dry: bool = DryOption,
1736
1833
  ) -> None:
1737
1834
  """Start a fastapi server(only for fastapi>=0.111.0)"""
1738
- f = functools.partial(dev, port, host, fastapi, uvicorn, prod, reload, dry=dry)
1835
+ f = functools.partial(
1836
+ dev, port, host, fastapi, uvicorn, prod, reload, just, dry=dry
1837
+ )
1739
1838
  if getattr(file_or_port, "default", file_or_port):
1740
1839
  f(file=file_or_port)
1741
1840
  else:
@@ -1754,7 +1853,7 @@ def run_by_subprocess(cmd: str, dry: bool = DryOption) -> None:
1754
1853
  ):
1755
1854
  echo(f"Command not found: {command}")
1756
1855
  raise Exit(1) from None
1757
- raise e
1856
+ raise
1758
1857
  else:
1759
1858
  if rc:
1760
1859
  raise Exit(rc)
@@ -1805,9 +1904,13 @@ class MakeDeps(DryRun):
1805
1904
  if opt not in cmd:
1806
1905
  cmd += opt
1807
1906
  if self._no_extra:
1808
- cmd += " " + " ".join(f"--no-extra {i}" for i in self._no_extra)
1907
+ cmd += " " + " ".join(
1908
+ f"--no-extra {_quote_shell_arg(i)}" for i in self._no_extra
1909
+ )
1809
1910
  if self._no_group:
1810
- cmd += " " + " ".join(f"--no-group {i}" for i in self._no_group)
1911
+ cmd += " " + " ".join(
1912
+ f"--no-group {_quote_shell_arg(i)}" for i in self._no_group
1913
+ )
1811
1914
  if self._frozen:
1812
1915
  cmd += " --frozen"
1813
1916
  if opts := os.getenv("FASTDEVCLI_DEPS_OPTS"):
@@ -1824,15 +1927,17 @@ class MakeDeps(DryRun):
1824
1927
  tool_section = doc["tool"]
1825
1928
  uv_package = tool_section.get("uv", {}).get("package")
1826
1929
  if uv_package is not None:
1827
- return doc["project"]["name"] if uv_package else ""
1828
- match doc["build-system"]["build-backend"]:
1829
- case "pdm.backend":
1830
- if not tool_section.get("pdm", {}).get("distribution", True):
1831
- return ""
1832
- case x if x.startswith("poetry"):
1833
- if not tool_section.get("poetry", {}).get("package-mode", True):
1834
- return ""
1835
- return doc["project"]["name"]
1930
+ if not uv_package:
1931
+ return ""
1932
+ else:
1933
+ match doc["build-system"]["build-backend"]:
1934
+ case "pdm.backend":
1935
+ if not tool_section.get("pdm", {}).get("distribution", True):
1936
+ return ""
1937
+ case x if x.startswith("poetry"):
1938
+ if not tool_section.get("poetry", {}).get("package-mode", True):
1939
+ return ""
1940
+ return cast(str, doc["project"]["name"])
1836
1941
  return ""
1837
1942
 
1838
1943
  def _gen(self) -> str:
@@ -1841,7 +1946,7 @@ class MakeDeps(DryRun):
1841
1946
  elif self._tool == "uv":
1842
1947
  uv_sync = "uv sync"
1843
1948
  if project := self.get_package_name():
1844
- uv_sync += f" --reinstall-package={project}"
1949
+ uv_sync += " " + _quote_shell_arg(f"--reinstall-package={project}")
1845
1950
  uv_sync += " --inexact" * self._inexact + " --active" * self._active
1846
1951
  return uv_sync + (
1847
1952
  " --no-dev" if self._prod else " --all-extras --all-groups"
@@ -1948,7 +2053,7 @@ class UvPypi(DryRun):
1948
2053
 
1949
2054
  @staticmethod
1950
2055
  def get_target_content(
1951
- text: str, verbose: bool, target_registry, target_host: str
2056
+ text: str, verbose: bool, target_registry: str, target_host: str
1952
2057
  ) -> str | None:
1953
2058
  registry_pattern = r'(registry = ")(.*?)"'
1954
2059
  registry_urls = {i[1] for i in re.findall(registry_pattern, text)}
@@ -29,7 +29,7 @@ dependencies = [
29
29
  "typer>=0.24.0,<1",
30
30
  "tomli >=2.0.1,<3; python_version < '3.11'",
31
31
  ]
32
- version = "0.25.0"
32
+ version = "0.25.2"
33
33
 
34
34
  [project.urls]
35
35
  Homepage = "https://github.com/waketzheng/fast-dev-cli"
@@ -184,10 +184,13 @@ extend-select = [
184
184
  [tool.ruff.lint.per-file-ignores]
185
185
  "test_*.py" = [
186
186
  "E501",
187
+ "BLE001",
188
+ "PLW1510",
187
189
  ]
188
190
  "scripts/*.py" = [
189
191
  "UP009",
190
192
  "UP032",
193
+ "PLW1510",
191
194
  ]
192
195
  "fast_dev_cli/cli.py" = [
193
196
  "UP007",
@@ -1 +0,0 @@
1
- __version__ = "0.25.0"
File without changes
File without changes