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.
- {fast_dev_cli-0.25.0 → 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.0 → fast_dev_cli-0.25.2}/fast_dev_cli/cli.py +183 -78
- {fast_dev_cli-0.25.0 → fast_dev_cli-0.25.2}/pyproject.toml +4 -1
- fast_dev_cli-0.25.0/fast_dev_cli/__init__.py +0 -1
- {fast_dev_cli-0.25.0 → fast_dev_cli-0.25.2}/LICENSE +0 -0
- {fast_dev_cli-0.25.0 → fast_dev_cli-0.25.2}/README.md +0 -0
- {fast_dev_cli-0.25.0 → fast_dev_cli-0.25.2}/fast_dev_cli/__main__.py +0 -0
- {fast_dev_cli-0.25.0 → fast_dev_cli-0.25.2}/fast_dev_cli/py.typed +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
|
334
|
+
except EnvError:
|
|
329
335
|
if (res := _get_frontend_version()) is None:
|
|
330
|
-
raise
|
|
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
|
|
532
|
-
ds.append(cwd /
|
|
533
|
-
ds.append(cwd / "src" /
|
|
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 /
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(">"
|
|
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 =
|
|
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
|
-
|
|
1201
|
-
|
|
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(
|
|
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" {
|
|
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
|
|
1315
|
-
|
|
1316
|
-
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}"
|
|
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 =
|
|
1369
|
+
ps = shlex.split(args) if isinstance(args, str) else [str(i) for i in args]
|
|
1324
1370
|
if len(ps) == 1:
|
|
1325
|
-
|
|
1371
|
+
path = ps[0]
|
|
1326
1372
|
if (
|
|
1327
|
-
|
|
1373
|
+
path != "."
|
|
1328
1374
|
# `Path("a.").suffix` got "." in py3.14 and got "" with py<3.14
|
|
1329
|
-
and (p := Path(
|
|
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
|
|
1336
|
-
p = p.with_name(
|
|
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
|
-
|
|
1386
|
+
ps[0] = p.name
|
|
1341
1387
|
break
|
|
1342
|
-
|
|
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 {
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
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(
|
|
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 += " " +
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
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"
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|