fast-dev-cli 0.25.1__tar.gz → 0.25.3__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.25.1 → fast_dev_cli-0.25.3}/PKG-INFO +1 -1
- fast_dev_cli-0.25.3/fast_dev_cli/__init__.py +1 -0
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.3}/fast_dev_cli/cli.py +137 -42
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.3}/pyproject.toml +1 -1
- fast_dev_cli-0.25.1/fast_dev_cli/__init__.py +0 -1
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.3}/LICENSE +0 -0
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.3}/README.md +0 -0
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.3}/fast_dev_cli/__main__.py +0 -0
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.3}/fast_dev_cli/py.typed +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.25.3"
|
|
@@ -378,6 +378,36 @@ def _ensure_str(value: str | OptionInfo | None) -> str | None:
|
|
|
378
378
|
return getattr(value, "default", "")
|
|
379
379
|
|
|
380
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
|
+
|
|
381
411
|
class DryRun:
|
|
382
412
|
def __init__(self, _exit: bool = False, dry: bool = False) -> None:
|
|
383
413
|
self.dry = _ensure_bool(dry)
|
|
@@ -563,6 +593,29 @@ class BumpUp(DryRun):
|
|
|
563
593
|
echo(f"Invalid part: {s!r}")
|
|
564
594
|
raise Exit(1) from e
|
|
565
595
|
|
|
596
|
+
@staticmethod
|
|
597
|
+
def parse_new_version(part: str, version: str) -> str:
|
|
598
|
+
version_parts = version.split(".")
|
|
599
|
+
if not version_parts[-1].isdigit():
|
|
600
|
+
try:
|
|
601
|
+
p1, p2, p3 = version_parts[:3]
|
|
602
|
+
p2i = int(p2)
|
|
603
|
+
p1i = int(p1)
|
|
604
|
+
except ValueError:
|
|
605
|
+
...
|
|
606
|
+
else:
|
|
607
|
+
match part:
|
|
608
|
+
case "patch":
|
|
609
|
+
p3i = int(m.group()) if (m := re.match(r"\d+", p3)) else 0
|
|
610
|
+
if len(version_parts) == 3:
|
|
611
|
+
p3i += 1
|
|
612
|
+
return f"{p1}.{p2}.{p3i}"
|
|
613
|
+
case "minor":
|
|
614
|
+
return f"{p1}.{p2i + 1}.0"
|
|
615
|
+
case "major":
|
|
616
|
+
return f"{p1i + 1}.0.0"
|
|
617
|
+
return ""
|
|
618
|
+
|
|
566
619
|
def gen(self) -> str:
|
|
567
620
|
should_sync, _version = get_current_version(check_version=True)
|
|
568
621
|
filename = self.filename
|
|
@@ -575,7 +628,11 @@ class BumpUp(DryRun):
|
|
|
575
628
|
part = self.get_part(a)
|
|
576
629
|
self.part = part
|
|
577
630
|
parse = r'--parse "(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"'
|
|
578
|
-
|
|
631
|
+
filename_arg = _quote_shell_arg(filename)
|
|
632
|
+
cmd = f'bumpversion {parse} --current-version="{_version}" '
|
|
633
|
+
if new_version := self.parse_new_version(part, _version):
|
|
634
|
+
cmd += f'--new-version="{new_version}" '
|
|
635
|
+
cmd += f"{part} {filename_arg}"
|
|
579
636
|
if self.commit:
|
|
580
637
|
if part != "patch":
|
|
581
638
|
cmd += " --tag"
|
|
@@ -641,15 +698,13 @@ def version() -> None:
|
|
|
641
698
|
@cli.command(name="bump")
|
|
642
699
|
def bump_version(
|
|
643
700
|
part: BumpUp.PartChoices,
|
|
701
|
+
sync: bool = False,
|
|
644
702
|
commit: bool = Option(
|
|
645
703
|
False, "--commit", "-c", help="Whether run `git commit` after version changed"
|
|
646
704
|
),
|
|
647
705
|
emoji: bool | None = Option(
|
|
648
706
|
None, "--emoji", help="Whether add emoji prefix to commit message"
|
|
649
707
|
),
|
|
650
|
-
no_sync: bool = Option(
|
|
651
|
-
False, "--no-sync", help="Do not run sync command to update version"
|
|
652
|
-
),
|
|
653
708
|
dry: bool = DryOption,
|
|
654
709
|
) -> None:
|
|
655
710
|
"""Bump up version string in pyproject.toml"""
|
|
@@ -658,7 +713,7 @@ def bump_version(
|
|
|
658
713
|
return BumpUp(
|
|
659
714
|
_ensure_bool(commit),
|
|
660
715
|
getattr(part, "value", part),
|
|
661
|
-
no_sync=_ensure_bool(
|
|
716
|
+
no_sync=not _ensure_bool(sync),
|
|
662
717
|
emoji=emoji,
|
|
663
718
|
dry=dry,
|
|
664
719
|
).run()
|
|
@@ -1088,7 +1143,10 @@ class GitTag(DryRun):
|
|
|
1088
1143
|
if self.has_v_prefix():
|
|
1089
1144
|
# Add `v` at prefix to compare with bumpversion tool
|
|
1090
1145
|
_version = "v" + _version
|
|
1091
|
-
cmd =
|
|
1146
|
+
cmd = (
|
|
1147
|
+
f"git tag -a {_quote_shell_arg(_version)} "
|
|
1148
|
+
f"-m {_quote_shell_arg(self.message)} && git push --tags"
|
|
1149
|
+
)
|
|
1092
1150
|
if self.should_push():
|
|
1093
1151
|
cmd += " && git push"
|
|
1094
1152
|
if should_sync and not self._no_sync and (sync := Project.get_sync_command()):
|
|
@@ -1190,7 +1248,7 @@ class LintCode(DryRun):
|
|
|
1190
1248
|
@classmethod
|
|
1191
1249
|
def to_cmd(
|
|
1192
1250
|
cls: type[Self],
|
|
1193
|
-
paths: str = ".",
|
|
1251
|
+
paths: str | list[str] = ".",
|
|
1194
1252
|
check_only: bool = False,
|
|
1195
1253
|
bandit: bool = False,
|
|
1196
1254
|
skip_mypy: bool = False,
|
|
@@ -1203,8 +1261,12 @@ class LintCode(DryRun):
|
|
|
1203
1261
|
prefer_ty: bool = False,
|
|
1204
1262
|
ruff_check_fix: bool = True,
|
|
1205
1263
|
) -> str:
|
|
1206
|
-
|
|
1207
|
-
|
|
1264
|
+
path_args = shlex.split(paths) if isinstance(paths, str) else paths
|
|
1265
|
+
if not path_args:
|
|
1266
|
+
path_args = ["."]
|
|
1267
|
+
quoted_paths = _join_shell_args(path_args)
|
|
1268
|
+
if path_args != ["."] and all(i.endswith(".html") for i in path_args):
|
|
1269
|
+
return f"prettier -w {quoted_paths}"
|
|
1208
1270
|
ruff_rules = ["I", "B"]
|
|
1209
1271
|
if ruff_check_sim and not load_bool("FASTDEVCLI_NO_SIM"):
|
|
1210
1272
|
ruff_rules.append("SIM")
|
|
@@ -1294,7 +1356,7 @@ class LintCode(DryRun):
|
|
|
1294
1356
|
prefix += "--no-sync "
|
|
1295
1357
|
elif Path(bin_dir := ".venv/bin/").exists():
|
|
1296
1358
|
prefix = bin_dir
|
|
1297
|
-
if cls.prefer_dmypy(
|
|
1359
|
+
if cls.prefer_dmypy(quoted_paths, tools, use_dmypy=use_dmypy):
|
|
1298
1360
|
tools[-1] = "dmypy run"
|
|
1299
1361
|
cmd = " && ".join(
|
|
1300
1362
|
(
|
|
@@ -1308,7 +1370,7 @@ class LintCode(DryRun):
|
|
|
1308
1370
|
)
|
|
1309
1371
|
else prefix + tool
|
|
1310
1372
|
)
|
|
1311
|
-
+ f" {
|
|
1373
|
+
+ f" {quoted_paths}"
|
|
1312
1374
|
for tool in tools
|
|
1313
1375
|
)
|
|
1314
1376
|
if bandit or load_bool("FASTDEVCLI_BANDIT"):
|
|
@@ -1317,36 +1379,35 @@ class LintCode(DryRun):
|
|
|
1317
1379
|
toml_text = Project.load_toml_text()
|
|
1318
1380
|
if "[tool.bandit" in toml_text:
|
|
1319
1381
|
command += " -c pyproject.toml"
|
|
1320
|
-
if
|
|
1321
|
-
|
|
1322
|
-
command += f" -r {
|
|
1382
|
+
if quoted_paths == "." and " -c " not in command:
|
|
1383
|
+
quoted_paths = _quote_shell_arg(cls.get_package_name())
|
|
1384
|
+
command += f" -r {quoted_paths}"
|
|
1323
1385
|
cmd += " && " + command
|
|
1324
1386
|
return cmd
|
|
1325
1387
|
|
|
1326
1388
|
def gen(self) -> str:
|
|
1327
|
-
paths = "."
|
|
1389
|
+
paths = ["."]
|
|
1328
1390
|
if args := self.args:
|
|
1329
|
-
ps =
|
|
1391
|
+
ps = shlex.split(args) if isinstance(args, str) else [str(i) for i in args]
|
|
1330
1392
|
if len(ps) == 1:
|
|
1331
|
-
|
|
1393
|
+
path = ps[0]
|
|
1332
1394
|
if (
|
|
1333
|
-
|
|
1395
|
+
path != "."
|
|
1334
1396
|
# `Path("a.").suffix` got "." in py3.14 and got "" with py<3.14
|
|
1335
|
-
and (p := Path(
|
|
1397
|
+
and (p := Path(path)).suffix in ("", ".")
|
|
1336
1398
|
and not p.exists()
|
|
1337
1399
|
):
|
|
1338
1400
|
# e.g.:
|
|
1339
1401
|
# stem -> stem.py
|
|
1340
1402
|
# me. -> me.py
|
|
1341
|
-
if
|
|
1342
|
-
p = p.with_name(
|
|
1403
|
+
if path.endswith("."):
|
|
1404
|
+
p = p.with_name(path[:-1])
|
|
1343
1405
|
for suffix in (".py", ".html"):
|
|
1344
1406
|
p = p.with_suffix(suffix)
|
|
1345
1407
|
if p.exists():
|
|
1346
|
-
|
|
1408
|
+
ps[0] = p.name
|
|
1347
1409
|
break
|
|
1348
|
-
|
|
1349
|
-
paths = " ".join(ps)
|
|
1410
|
+
paths = ps
|
|
1350
1411
|
return self.to_cmd(
|
|
1351
1412
|
paths,
|
|
1352
1413
|
self.check_only,
|
|
@@ -1517,10 +1578,11 @@ class Sync(DryRun):
|
|
|
1517
1578
|
def gen(self) -> str:
|
|
1518
1579
|
extras, save = self.extras, self._save
|
|
1519
1580
|
should_remove = not Path.cwd().joinpath(self.filename).exists()
|
|
1581
|
+
filename = _quote_shell_arg(self.filename)
|
|
1520
1582
|
if not (tool := Project.get_manage_tool()):
|
|
1521
1583
|
if should_remove or not is_venv():
|
|
1522
1584
|
raise EnvError("There project is not managed by uv/pdm/poetry!")
|
|
1523
|
-
return f"python -m pip install -r {
|
|
1585
|
+
return f"python -m pip install -r {filename}"
|
|
1524
1586
|
prefix = ""
|
|
1525
1587
|
if not is_venv():
|
|
1526
1588
|
prefix = f"{tool} run " + "--no-sync " * (tool == "uv")
|
|
@@ -1533,7 +1595,7 @@ class Sync(DryRun):
|
|
|
1533
1595
|
if not UpgradeDependencies.should_with_dev():
|
|
1534
1596
|
export_cmd = export_cmd.replace(" --with=dev", "")
|
|
1535
1597
|
if extras and isinstance(extras, str | list):
|
|
1536
|
-
export_cmd += f" --{extras
|
|
1598
|
+
export_cmd += f" --extras={_quote_shell_arg(str(extras))}"
|
|
1537
1599
|
elif check_call(prefix + "python -m pip --version"):
|
|
1538
1600
|
ensure_pip = ""
|
|
1539
1601
|
elif check_call(prefix + "python -m pip --version"):
|
|
@@ -1543,7 +1605,7 @@ class Sync(DryRun):
|
|
|
1543
1605
|
)
|
|
1544
1606
|
if should_remove and not save:
|
|
1545
1607
|
install_cmd += " && rm -f {0}"
|
|
1546
|
-
return install_cmd.format(
|
|
1608
|
+
return install_cmd.format(filename, prefix, export_cmd)
|
|
1547
1609
|
|
|
1548
1610
|
|
|
1549
1611
|
@cli.command()
|
|
@@ -1573,11 +1635,11 @@ def test(dry: bool, ignore_script: bool = False) -> None:
|
|
|
1573
1635
|
if not _ensure_bool(ignore_script) and (
|
|
1574
1636
|
test_script := _should_run_test_script(script_dir)
|
|
1575
1637
|
):
|
|
1576
|
-
cmd = test_script.relative_to(root).as_posix()
|
|
1638
|
+
cmd = _quote_shell_arg(test_script.relative_to(root).as_posix())
|
|
1577
1639
|
if test_script.suffix == ".py":
|
|
1578
1640
|
cmd = "python " + cmd
|
|
1579
1641
|
if cwd != root:
|
|
1580
|
-
cmd = f"cd {root} && " + cmd
|
|
1642
|
+
cmd = f"cd {_quote_shell_arg(root)} && " + cmd
|
|
1581
1643
|
else:
|
|
1582
1644
|
cmd = 'coverage run -m pytest -s && coverage report --omit="tests/*" -m'
|
|
1583
1645
|
if not is_venv() or not check_call("coverage --version"):
|
|
@@ -1645,6 +1707,7 @@ def should_use_just() -> bool:
|
|
|
1645
1707
|
return _prefer_just_dev(f)
|
|
1646
1708
|
if d.joinpath("pyproject.toml").exists():
|
|
1647
1709
|
break
|
|
1710
|
+
d = d.parent
|
|
1648
1711
|
return False
|
|
1649
1712
|
|
|
1650
1713
|
|
|
@@ -1654,10 +1717,22 @@ def _prefer_just_dev(f: Path) -> bool:
|
|
|
1654
1717
|
dev_recipe = "dev *args:"
|
|
1655
1718
|
re_import = re.compile(r"import[?]? ")
|
|
1656
1719
|
has_import = False
|
|
1720
|
+
total = len(lines)
|
|
1657
1721
|
for i, line in enumerate(lines):
|
|
1658
1722
|
if line.startswith(dev_recipe):
|
|
1659
1723
|
# Avoid cycle callback
|
|
1660
|
-
|
|
1724
|
+
command_lines = []
|
|
1725
|
+
for j in range(i + 1, total):
|
|
1726
|
+
try:
|
|
1727
|
+
s = lines[j]
|
|
1728
|
+
except IndexError:
|
|
1729
|
+
break
|
|
1730
|
+
if not s.startswith(" "):
|
|
1731
|
+
break
|
|
1732
|
+
command_lines.append(s)
|
|
1733
|
+
if not command_lines: # Invalid justfile
|
|
1734
|
+
return False
|
|
1735
|
+
return all("fast dev" not in i for i in command_lines)
|
|
1661
1736
|
elif not has_import and re_import.match(line):
|
|
1662
1737
|
has_import = True
|
|
1663
1738
|
if has_import:
|
|
@@ -1669,6 +1744,17 @@ def _prefer_just_dev(f: Path) -> bool:
|
|
|
1669
1744
|
return False
|
|
1670
1745
|
|
|
1671
1746
|
|
|
1747
|
+
def _load_fastapi_entrypoint() -> str:
|
|
1748
|
+
with contextlib.suppress(FileNotFoundError, KeyError):
|
|
1749
|
+
try:
|
|
1750
|
+
toml_text = Project.load_toml_text()
|
|
1751
|
+
except EnvError:
|
|
1752
|
+
return ""
|
|
1753
|
+
doc = tomllib.loads(toml_text)
|
|
1754
|
+
return doc["tool"]["fastapi"]["entrypoint"]
|
|
1755
|
+
return ""
|
|
1756
|
+
|
|
1757
|
+
|
|
1672
1758
|
def _parse_serve_file(
|
|
1673
1759
|
uvicorn: bool | None, filename: str, cmd: str, args: list[str]
|
|
1674
1760
|
) -> str:
|
|
@@ -1681,20 +1767,23 @@ def _parse_serve_file(
|
|
|
1681
1767
|
args.append(f"--host={h}")
|
|
1682
1768
|
args.append(f"--port={p}")
|
|
1683
1769
|
if uvicorn:
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1770
|
+
if entrypoint := _load_fastapi_entrypoint():
|
|
1771
|
+
cmd += " " + entrypoint
|
|
1772
|
+
else:
|
|
1773
|
+
p = Path("main.py")
|
|
1774
|
+
if p.exists():
|
|
1775
|
+
cmd += " main:app"
|
|
1776
|
+
elif Path("app", p.name).exists():
|
|
1777
|
+
cmd += " app.main:app"
|
|
1778
|
+
elif Path("app.py").exists():
|
|
1779
|
+
cmd += " app:app"
|
|
1691
1780
|
return cmd
|
|
1692
1781
|
if uvicorn and ((filepath := Path(filename)).is_file() or filepath.suffix == ".py"):
|
|
1693
1782
|
filename = filepath.stem + ":app"
|
|
1694
1783
|
parent_names = [j for i in filepath.parents if (j := i.name)]
|
|
1695
1784
|
if parent_names:
|
|
1696
1785
|
filename = ".".join([*parent_names[::-1], filename])
|
|
1697
|
-
cmd += " " + filename
|
|
1786
|
+
cmd += " " + _quote_shell_arg(filename)
|
|
1698
1787
|
return cmd
|
|
1699
1788
|
|
|
1700
1789
|
|
|
@@ -1722,6 +1811,8 @@ def _runserver(
|
|
|
1722
1811
|
if port != 8000:
|
|
1723
1812
|
args.append(f"--port={port}")
|
|
1724
1813
|
no_port_yet = False
|
|
1814
|
+
elif uvicorn and (entrypoint := _load_fastapi_entrypoint()):
|
|
1815
|
+
cmd += " " + entrypoint
|
|
1725
1816
|
if no_port_yet and (port := getattr(port, "default", port)) and str(port) != "8000":
|
|
1726
1817
|
args.append(f"--port={port}")
|
|
1727
1818
|
if shutil.which("pdm") is not None:
|
|
@@ -1746,7 +1837,7 @@ def dev(
|
|
|
1746
1837
|
else:
|
|
1747
1838
|
cmd, args = _runserver(uvicorn, host, port, file)
|
|
1748
1839
|
if args:
|
|
1749
|
-
cmd += " " +
|
|
1840
|
+
cmd += " " + _join_shell_args(args)
|
|
1750
1841
|
exit_if_run_failed(cmd, dry=dry)
|
|
1751
1842
|
|
|
1752
1843
|
|
|
@@ -1835,9 +1926,13 @@ class MakeDeps(DryRun):
|
|
|
1835
1926
|
if opt not in cmd:
|
|
1836
1927
|
cmd += opt
|
|
1837
1928
|
if self._no_extra:
|
|
1838
|
-
cmd += " " + " ".join(
|
|
1929
|
+
cmd += " " + " ".join(
|
|
1930
|
+
f"--no-extra {_quote_shell_arg(i)}" for i in self._no_extra
|
|
1931
|
+
)
|
|
1839
1932
|
if self._no_group:
|
|
1840
|
-
cmd += " " + " ".join(
|
|
1933
|
+
cmd += " " + " ".join(
|
|
1934
|
+
f"--no-group {_quote_shell_arg(i)}" for i in self._no_group
|
|
1935
|
+
)
|
|
1841
1936
|
if self._frozen:
|
|
1842
1937
|
cmd += " --frozen"
|
|
1843
1938
|
if opts := os.getenv("FASTDEVCLI_DEPS_OPTS"):
|
|
@@ -1873,7 +1968,7 @@ class MakeDeps(DryRun):
|
|
|
1873
1968
|
elif self._tool == "uv":
|
|
1874
1969
|
uv_sync = "uv sync"
|
|
1875
1970
|
if project := self.get_package_name():
|
|
1876
|
-
uv_sync += f"
|
|
1971
|
+
uv_sync += " " + _quote_shell_arg(f"--reinstall-package={project}")
|
|
1877
1972
|
uv_sync += " --inexact" * self._inexact + " --active" * self._active
|
|
1878
1973
|
return uv_sync + (
|
|
1879
1974
|
" --no-dev" if self._prod else " --all-extras --all-groups"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.25.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|