fast-dev-cli 0.25.1__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.
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.2}/PKG-INFO +1 -1
- fast_dev_cli-0.25.2/fast_dev_cli/__init__.py +1 -0
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.2}/fast_dev_cli/cli.py +111 -38
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.2}/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.2}/LICENSE +0 -0
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.2}/README.md +0 -0
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.2}/fast_dev_cli/__main__.py +0 -0
- {fast_dev_cli-0.25.1 → fast_dev_cli-0.25.2}/fast_dev_cli/py.typed +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.25.2"
|
|
@@ -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)
|
|
@@ -575,7 +605,10 @@ class BumpUp(DryRun):
|
|
|
575
605
|
part = self.get_part(a)
|
|
576
606
|
self.part = part
|
|
577
607
|
parse = r'--parse "(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"'
|
|
578
|
-
|
|
608
|
+
filename_arg = _quote_shell_arg(filename)
|
|
609
|
+
cmd = (
|
|
610
|
+
f'bumpversion {parse} --current-version="{_version}" {part} {filename_arg}'
|
|
611
|
+
)
|
|
579
612
|
if self.commit:
|
|
580
613
|
if part != "patch":
|
|
581
614
|
cmd += " --tag"
|
|
@@ -1088,7 +1121,10 @@ class GitTag(DryRun):
|
|
|
1088
1121
|
if self.has_v_prefix():
|
|
1089
1122
|
# Add `v` at prefix to compare with bumpversion tool
|
|
1090
1123
|
_version = "v" + _version
|
|
1091
|
-
cmd =
|
|
1124
|
+
cmd = (
|
|
1125
|
+
f"git tag -a {_quote_shell_arg(_version)} "
|
|
1126
|
+
f"-m {_quote_shell_arg(self.message)} && git push --tags"
|
|
1127
|
+
)
|
|
1092
1128
|
if self.should_push():
|
|
1093
1129
|
cmd += " && git push"
|
|
1094
1130
|
if should_sync and not self._no_sync and (sync := Project.get_sync_command()):
|
|
@@ -1190,7 +1226,7 @@ class LintCode(DryRun):
|
|
|
1190
1226
|
@classmethod
|
|
1191
1227
|
def to_cmd(
|
|
1192
1228
|
cls: type[Self],
|
|
1193
|
-
paths: str = ".",
|
|
1229
|
+
paths: str | list[str] = ".",
|
|
1194
1230
|
check_only: bool = False,
|
|
1195
1231
|
bandit: bool = False,
|
|
1196
1232
|
skip_mypy: bool = False,
|
|
@@ -1203,8 +1239,12 @@ class LintCode(DryRun):
|
|
|
1203
1239
|
prefer_ty: bool = False,
|
|
1204
1240
|
ruff_check_fix: bool = True,
|
|
1205
1241
|
) -> str:
|
|
1206
|
-
|
|
1207
|
-
|
|
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}"
|
|
1208
1248
|
ruff_rules = ["I", "B"]
|
|
1209
1249
|
if ruff_check_sim and not load_bool("FASTDEVCLI_NO_SIM"):
|
|
1210
1250
|
ruff_rules.append("SIM")
|
|
@@ -1294,7 +1334,7 @@ class LintCode(DryRun):
|
|
|
1294
1334
|
prefix += "--no-sync "
|
|
1295
1335
|
elif Path(bin_dir := ".venv/bin/").exists():
|
|
1296
1336
|
prefix = bin_dir
|
|
1297
|
-
if cls.prefer_dmypy(
|
|
1337
|
+
if cls.prefer_dmypy(quoted_paths, tools, use_dmypy=use_dmypy):
|
|
1298
1338
|
tools[-1] = "dmypy run"
|
|
1299
1339
|
cmd = " && ".join(
|
|
1300
1340
|
(
|
|
@@ -1308,7 +1348,7 @@ class LintCode(DryRun):
|
|
|
1308
1348
|
)
|
|
1309
1349
|
else prefix + tool
|
|
1310
1350
|
)
|
|
1311
|
-
+ f" {
|
|
1351
|
+
+ f" {quoted_paths}"
|
|
1312
1352
|
for tool in tools
|
|
1313
1353
|
)
|
|
1314
1354
|
if bandit or load_bool("FASTDEVCLI_BANDIT"):
|
|
@@ -1317,36 +1357,35 @@ class LintCode(DryRun):
|
|
|
1317
1357
|
toml_text = Project.load_toml_text()
|
|
1318
1358
|
if "[tool.bandit" in toml_text:
|
|
1319
1359
|
command += " -c pyproject.toml"
|
|
1320
|
-
if
|
|
1321
|
-
|
|
1322
|
-
command += f" -r {
|
|
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}"
|
|
1323
1363
|
cmd += " && " + command
|
|
1324
1364
|
return cmd
|
|
1325
1365
|
|
|
1326
1366
|
def gen(self) -> str:
|
|
1327
|
-
paths = "."
|
|
1367
|
+
paths = ["."]
|
|
1328
1368
|
if args := self.args:
|
|
1329
|
-
ps =
|
|
1369
|
+
ps = shlex.split(args) if isinstance(args, str) else [str(i) for i in args]
|
|
1330
1370
|
if len(ps) == 1:
|
|
1331
|
-
|
|
1371
|
+
path = ps[0]
|
|
1332
1372
|
if (
|
|
1333
|
-
|
|
1373
|
+
path != "."
|
|
1334
1374
|
# `Path("a.").suffix` got "." in py3.14 and got "" with py<3.14
|
|
1335
|
-
and (p := Path(
|
|
1375
|
+
and (p := Path(path)).suffix in ("", ".")
|
|
1336
1376
|
and not p.exists()
|
|
1337
1377
|
):
|
|
1338
1378
|
# e.g.:
|
|
1339
1379
|
# stem -> stem.py
|
|
1340
1380
|
# me. -> me.py
|
|
1341
|
-
if
|
|
1342
|
-
p = p.with_name(
|
|
1381
|
+
if path.endswith("."):
|
|
1382
|
+
p = p.with_name(path[:-1])
|
|
1343
1383
|
for suffix in (".py", ".html"):
|
|
1344
1384
|
p = p.with_suffix(suffix)
|
|
1345
1385
|
if p.exists():
|
|
1346
|
-
|
|
1386
|
+
ps[0] = p.name
|
|
1347
1387
|
break
|
|
1348
|
-
|
|
1349
|
-
paths = " ".join(ps)
|
|
1388
|
+
paths = ps
|
|
1350
1389
|
return self.to_cmd(
|
|
1351
1390
|
paths,
|
|
1352
1391
|
self.check_only,
|
|
@@ -1517,10 +1556,11 @@ class Sync(DryRun):
|
|
|
1517
1556
|
def gen(self) -> str:
|
|
1518
1557
|
extras, save = self.extras, self._save
|
|
1519
1558
|
should_remove = not Path.cwd().joinpath(self.filename).exists()
|
|
1559
|
+
filename = _quote_shell_arg(self.filename)
|
|
1520
1560
|
if not (tool := Project.get_manage_tool()):
|
|
1521
1561
|
if should_remove or not is_venv():
|
|
1522
1562
|
raise EnvError("There project is not managed by uv/pdm/poetry!")
|
|
1523
|
-
return f"python -m pip install -r {
|
|
1563
|
+
return f"python -m pip install -r {filename}"
|
|
1524
1564
|
prefix = ""
|
|
1525
1565
|
if not is_venv():
|
|
1526
1566
|
prefix = f"{tool} run " + "--no-sync " * (tool == "uv")
|
|
@@ -1533,7 +1573,7 @@ class Sync(DryRun):
|
|
|
1533
1573
|
if not UpgradeDependencies.should_with_dev():
|
|
1534
1574
|
export_cmd = export_cmd.replace(" --with=dev", "")
|
|
1535
1575
|
if extras and isinstance(extras, str | list):
|
|
1536
|
-
export_cmd += f" --{extras
|
|
1576
|
+
export_cmd += f" --extras={_quote_shell_arg(str(extras))}"
|
|
1537
1577
|
elif check_call(prefix + "python -m pip --version"):
|
|
1538
1578
|
ensure_pip = ""
|
|
1539
1579
|
elif check_call(prefix + "python -m pip --version"):
|
|
@@ -1543,7 +1583,7 @@ class Sync(DryRun):
|
|
|
1543
1583
|
)
|
|
1544
1584
|
if should_remove and not save:
|
|
1545
1585
|
install_cmd += " && rm -f {0}"
|
|
1546
|
-
return install_cmd.format(
|
|
1586
|
+
return install_cmd.format(filename, prefix, export_cmd)
|
|
1547
1587
|
|
|
1548
1588
|
|
|
1549
1589
|
@cli.command()
|
|
@@ -1573,11 +1613,11 @@ def test(dry: bool, ignore_script: bool = False) -> None:
|
|
|
1573
1613
|
if not _ensure_bool(ignore_script) and (
|
|
1574
1614
|
test_script := _should_run_test_script(script_dir)
|
|
1575
1615
|
):
|
|
1576
|
-
cmd = test_script.relative_to(root).as_posix()
|
|
1616
|
+
cmd = _quote_shell_arg(test_script.relative_to(root).as_posix())
|
|
1577
1617
|
if test_script.suffix == ".py":
|
|
1578
1618
|
cmd = "python " + cmd
|
|
1579
1619
|
if cwd != root:
|
|
1580
|
-
cmd = f"cd {root} && " + cmd
|
|
1620
|
+
cmd = f"cd {_quote_shell_arg(root)} && " + cmd
|
|
1581
1621
|
else:
|
|
1582
1622
|
cmd = 'coverage run -m pytest -s && coverage report --omit="tests/*" -m'
|
|
1583
1623
|
if not is_venv() or not check_call("coverage --version"):
|
|
@@ -1645,6 +1685,7 @@ def should_use_just() -> bool:
|
|
|
1645
1685
|
return _prefer_just_dev(f)
|
|
1646
1686
|
if d.joinpath("pyproject.toml").exists():
|
|
1647
1687
|
break
|
|
1688
|
+
d = d.parent
|
|
1648
1689
|
return False
|
|
1649
1690
|
|
|
1650
1691
|
|
|
@@ -1654,10 +1695,22 @@ def _prefer_just_dev(f: Path) -> bool:
|
|
|
1654
1695
|
dev_recipe = "dev *args:"
|
|
1655
1696
|
re_import = re.compile(r"import[?]? ")
|
|
1656
1697
|
has_import = False
|
|
1698
|
+
total = len(lines)
|
|
1657
1699
|
for i, line in enumerate(lines):
|
|
1658
1700
|
if line.startswith(dev_recipe):
|
|
1659
1701
|
# Avoid cycle callback
|
|
1660
|
-
|
|
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)
|
|
1661
1714
|
elif not has_import and re_import.match(line):
|
|
1662
1715
|
has_import = True
|
|
1663
1716
|
if has_import:
|
|
@@ -1669,6 +1722,17 @@ def _prefer_just_dev(f: Path) -> bool:
|
|
|
1669
1722
|
return False
|
|
1670
1723
|
|
|
1671
1724
|
|
|
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
|
+
|
|
1672
1736
|
def _parse_serve_file(
|
|
1673
1737
|
uvicorn: bool | None, filename: str, cmd: str, args: list[str]
|
|
1674
1738
|
) -> str:
|
|
@@ -1681,20 +1745,23 @@ def _parse_serve_file(
|
|
|
1681
1745
|
args.append(f"--host={h}")
|
|
1682
1746
|
args.append(f"--port={p}")
|
|
1683
1747
|
if uvicorn:
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
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"
|
|
1691
1758
|
return cmd
|
|
1692
1759
|
if uvicorn and ((filepath := Path(filename)).is_file() or filepath.suffix == ".py"):
|
|
1693
1760
|
filename = filepath.stem + ":app"
|
|
1694
1761
|
parent_names = [j for i in filepath.parents if (j := i.name)]
|
|
1695
1762
|
if parent_names:
|
|
1696
1763
|
filename = ".".join([*parent_names[::-1], filename])
|
|
1697
|
-
cmd += " " + filename
|
|
1764
|
+
cmd += " " + _quote_shell_arg(filename)
|
|
1698
1765
|
return cmd
|
|
1699
1766
|
|
|
1700
1767
|
|
|
@@ -1722,6 +1789,8 @@ def _runserver(
|
|
|
1722
1789
|
if port != 8000:
|
|
1723
1790
|
args.append(f"--port={port}")
|
|
1724
1791
|
no_port_yet = False
|
|
1792
|
+
elif uvicorn and (entrypoint := _load_fastapi_entrypoint()):
|
|
1793
|
+
cmd += " " + entrypoint
|
|
1725
1794
|
if no_port_yet and (port := getattr(port, "default", port)) and str(port) != "8000":
|
|
1726
1795
|
args.append(f"--port={port}")
|
|
1727
1796
|
if shutil.which("pdm") is not None:
|
|
@@ -1746,7 +1815,7 @@ def dev(
|
|
|
1746
1815
|
else:
|
|
1747
1816
|
cmd, args = _runserver(uvicorn, host, port, file)
|
|
1748
1817
|
if args:
|
|
1749
|
-
cmd += " " +
|
|
1818
|
+
cmd += " " + _join_shell_args(args)
|
|
1750
1819
|
exit_if_run_failed(cmd, dry=dry)
|
|
1751
1820
|
|
|
1752
1821
|
|
|
@@ -1835,9 +1904,13 @@ class MakeDeps(DryRun):
|
|
|
1835
1904
|
if opt not in cmd:
|
|
1836
1905
|
cmd += opt
|
|
1837
1906
|
if self._no_extra:
|
|
1838
|
-
cmd += " " + " ".join(
|
|
1907
|
+
cmd += " " + " ".join(
|
|
1908
|
+
f"--no-extra {_quote_shell_arg(i)}" for i in self._no_extra
|
|
1909
|
+
)
|
|
1839
1910
|
if self._no_group:
|
|
1840
|
-
cmd += " " + " ".join(
|
|
1911
|
+
cmd += " " + " ".join(
|
|
1912
|
+
f"--no-group {_quote_shell_arg(i)}" for i in self._no_group
|
|
1913
|
+
)
|
|
1841
1914
|
if self._frozen:
|
|
1842
1915
|
cmd += " --frozen"
|
|
1843
1916
|
if opts := os.getenv("FASTDEVCLI_DEPS_OPTS"):
|
|
@@ -1873,7 +1946,7 @@ class MakeDeps(DryRun):
|
|
|
1873
1946
|
elif self._tool == "uv":
|
|
1874
1947
|
uv_sync = "uv sync"
|
|
1875
1948
|
if project := self.get_package_name():
|
|
1876
|
-
uv_sync += f"
|
|
1949
|
+
uv_sync += " " + _quote_shell_arg(f"--reinstall-package={project}")
|
|
1877
1950
|
uv_sync += " --inexact" * self._inexact + " --active" * self._active
|
|
1878
1951
|
return uv_sync + (
|
|
1879
1952
|
" --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
|