fast-dev-cli 0.21.0__tar.gz → 0.22.0__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 (36) hide show
  1. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/PKG-INFO +21 -10
  2. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/README.md +11 -5
  3. fast_dev_cli-0.22.0/fast_dev_cli/__init__.py +1 -0
  4. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/fast_dev_cli/cli.py +169 -49
  5. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/pyproject.toml +24 -46
  6. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_bump.py +20 -0
  7. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_deps.py +1 -1
  8. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_fast_test.py +1 -0
  9. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_lint.py +72 -33
  10. fast_dev_cli-0.22.0/tests/test_runserver.py +136 -0
  11. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_sync.py +14 -8
  12. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_upgrade.py +1 -1
  13. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_upload.py +1 -1
  14. fast_dev_cli-0.21.0/fast_dev_cli/__init__.py +0 -1
  15. fast_dev_cli-0.21.0/pdm_build.py +0 -39
  16. fast_dev_cli-0.21.0/scripts/check.py +0 -30
  17. fast_dev_cli-0.21.0/scripts/deps.py +0 -156
  18. fast_dev_cli-0.21.0/scripts/format.py +0 -13
  19. fast_dev_cli-0.21.0/scripts/test.py +0 -48
  20. fast_dev_cli-0.21.0/tests/test_runserver.py +0 -97
  21. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/LICENSE +0 -0
  22. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/fast_dev_cli/__main__.py +0 -0
  23. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/fast_dev_cli/py.typed +0 -0
  24. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/__init__.py +0 -0
  25. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/assets/uv-tx.lock +0 -0
  26. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/assets/uv-upload-time.lock +0 -0
  27. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/assets/uv.lock +0 -0
  28. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/conftest.py +0 -0
  29. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_exec.py +0 -0
  30. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_functions.py +0 -0
  31. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_help.py +0 -0
  32. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_poetry_version_plugin.py +0 -0
  33. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_pypi.py +0 -0
  34. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_tag.py +0 -0
  35. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/test_version.py +0 -0
  36. {fast_dev_cli-0.21.0 → fast_dev_cli-0.22.0}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fast-dev-cli
3
- Version: 0.21.0
3
+ Version: 0.22.0
4
4
  Summary: Python project development tool.
5
5
  Author-Email: Waket Zheng <waketzheng@gmail.com>>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -23,10 +23,15 @@ Project-URL: Homepage, https://github.com/waketzheng/fast-dev-cli
23
23
  Requires-Python: >=3.10
24
24
  Requires-Dist: typer<1,>=0.24.0
25
25
  Requires-Dist: tomli<3,>=2.0.1; python_version < "3.11"
26
- Requires-Dist: coverage>=7.5.1
27
- Requires-Dist: pytest>=8.2.0
28
- Provides-Extra: include-optional-dependencies
29
- Requires-Dist: all; extra == "include-optional-dependencies"
26
+ Provides-Extra: standard
27
+ Requires-Dist: coverage>=7.5.1; extra == "standard"
28
+ Requires-Dist: pytest>=8.2.0; extra == "standard"
29
+ Provides-Extra: all
30
+ Requires-Dist: emoji>=2.12.1; extra == "all"
31
+ Requires-Dist: packaging>=20.5; extra == "all"
32
+ Requires-Dist: bandit>=1.7.9; extra == "all"
33
+ Requires-Dist: pytest-mock>=3.14.0; extra == "all"
34
+ Requires-Dist: fast-dev-cli[standard]; extra == "all"
30
35
  Description-Content-Type: text/markdown
31
36
 
32
37
  <p align="center">
@@ -51,6 +56,12 @@ Description-Content-Type: text/markdown
51
56
  <a href="https://github.com/python/mypy" target="_blank">
52
57
  <img src="https://img.shields.io/badge/mypy-100%25-brightgreen.svg" alt="Mypy Coverage">
53
58
  </a>
59
+ <a href="https://github.com/microsoft/pyright" target="_blank">
60
+ <img src="https://img.shields.io/badge/pyright-checked-brightgreen.svg" alt="Pyright Checked">
61
+ </a>
62
+ <a href="https://github.com/astral-sh/ty" target="_blank">
63
+ <img src="https://img.shields.io/badge/ty-checked-brightgreen.svg" alt="ty Checked">
64
+ </a>
54
65
  <a href="https://github.com/astral-sh/ruff" target="_blank">
55
66
  <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
56
67
  </a>
@@ -84,8 +95,8 @@ Python 3.10+
84
95
  <div class="termy">
85
96
 
86
97
  ```bash
87
- uv tool install --python 3.14 fastdevcli-slim
88
- # Or: pipx install fastdevcli-slim
98
+ uv tool install fast-dev-cli
99
+ # Or: pipx install fast-dev-cli
89
100
  ```
90
101
  *Will create a `fast` command in ~/.local/bin
91
102
 
@@ -96,7 +107,7 @@ uv tool install --python 3.14 fastdevcli-slim
96
107
  <div class="termy">
97
108
 
98
109
  ```bash
99
- pip install fast-dev-cli
110
+ pip install "fast-dev-cli[standard]"
100
111
  ```
101
112
  *Will install: fast-dev-cli emoji typer packaging pytest coverage*
102
113
 
@@ -112,7 +123,7 @@ fast lint [/path/to/file-or-directory]
112
123
  ```bash
113
124
  fast check
114
125
  ```
115
- - Bump up version in pyproject.toml
126
+ - Bump up version in pyproject.toml(or package.json)
116
127
  ```bash
117
128
  fast bump patch # 0.1.0 -> 0.1.1
118
129
  fast bump minor # 0.1.0 -> 0.2.0
@@ -140,7 +151,7 @@ fast pypi
140
151
  ## Use it without installed
141
152
 
142
153
  ```bash
143
- uvx --from fastdevcli-slim fast
154
+ uvx --from fast-dev-cli fast
144
155
  ```
145
156
 
146
157
  ## Lint/Check by ty instead of mypy
@@ -20,6 +20,12 @@
20
20
  <a href="https://github.com/python/mypy" target="_blank">
21
21
  <img src="https://img.shields.io/badge/mypy-100%25-brightgreen.svg" alt="Mypy Coverage">
22
22
  </a>
23
+ <a href="https://github.com/microsoft/pyright" target="_blank">
24
+ <img src="https://img.shields.io/badge/pyright-checked-brightgreen.svg" alt="Pyright Checked">
25
+ </a>
26
+ <a href="https://github.com/astral-sh/ty" target="_blank">
27
+ <img src="https://img.shields.io/badge/ty-checked-brightgreen.svg" alt="ty Checked">
28
+ </a>
23
29
  <a href="https://github.com/astral-sh/ruff" target="_blank">
24
30
  <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
25
31
  </a>
@@ -53,8 +59,8 @@ Python 3.10+
53
59
  <div class="termy">
54
60
 
55
61
  ```bash
56
- uv tool install --python 3.14 fastdevcli-slim
57
- # Or: pipx install fastdevcli-slim
62
+ uv tool install fast-dev-cli
63
+ # Or: pipx install fast-dev-cli
58
64
  ```
59
65
  *Will create a `fast` command in ~/.local/bin
60
66
 
@@ -65,7 +71,7 @@ uv tool install --python 3.14 fastdevcli-slim
65
71
  <div class="termy">
66
72
 
67
73
  ```bash
68
- pip install fast-dev-cli
74
+ pip install "fast-dev-cli[standard]"
69
75
  ```
70
76
  *Will install: fast-dev-cli emoji typer packaging pytest coverage*
71
77
 
@@ -81,7 +87,7 @@ fast lint [/path/to/file-or-directory]
81
87
  ```bash
82
88
  fast check
83
89
  ```
84
- - Bump up version in pyproject.toml
90
+ - Bump up version in pyproject.toml(or package.json)
85
91
  ```bash
86
92
  fast bump patch # 0.1.0 -> 0.1.1
87
93
  fast bump minor # 0.1.0 -> 0.2.0
@@ -109,7 +115,7 @@ fast pypi
109
115
  ## Use it without installed
110
116
 
111
117
  ```bash
112
- uvx --from fastdevcli-slim fast
118
+ uvx --from fast-dev-cli fast
113
119
  ```
114
120
 
115
121
  ## Lint/Check by ty instead of mypy
@@ -0,0 +1 @@
1
+ __version__ = "0.22.0"
@@ -75,11 +75,10 @@ def poetry_module_name(name: str) -> str:
75
75
  try:
76
76
  from packaging.utils import canonicalize_name
77
77
  except ImportError:
78
-
79
- def canonicalize_name(s: str) -> str: # type:ignore[misc]
80
- return re.sub(r"[-_.]+", "-", s)
81
-
82
- return canonicalize_name(name).replace("-", "_").replace(" ", "_")
78
+ module_name = re.sub(r"[-_.]+", "-", name)
79
+ else:
80
+ module_name = canonicalize_name(name)
81
+ return module_name.replace("-", "_").replace(" ", "_")
83
82
 
84
83
 
85
84
  def is_emoji(char: str) -> bool:
@@ -272,6 +271,26 @@ def read_version_from_file(
272
271
  return "0.0.0"
273
272
 
274
273
 
274
+ def _get_frontend_version() -> tuple[Path, str] | None:
275
+ try:
276
+ frontend_version_file = Project.get_work_dir("package.json", be_file=True)
277
+ except EnvError:
278
+ return None
279
+ try:
280
+ from asynctor.jsons import json_loads
281
+ except ImportError:
282
+ from json import loads as json_loads # type:ignore[assignment]
283
+ content = frontend_version_file.read_bytes()
284
+ metadata: dict[str, str] = json_loads(content) # type:ignore[assignment]
285
+ try:
286
+ current_version = metadata["version"]
287
+ except (KeyError, TypeError):
288
+ return None
289
+ with contextlib.suppress(ValueError):
290
+ frontend_version_file = frontend_version_file.relative_to(Path.cwd())
291
+ return frontend_version_file, current_version
292
+
293
+
275
294
  @overload
276
295
  def get_current_version(
277
296
  verbose: bool = False,
@@ -288,7 +307,7 @@ def get_current_version(
288
307
  is_poetry: bool | None = None,
289
308
  package_name: str | None = None,
290
309
  *,
291
- check_version: Literal[True] = True,
310
+ check_version: Literal[True],
292
311
  ) -> tuple[bool, str]: ...
293
312
 
294
313
 
@@ -310,11 +329,20 @@ def get_current_version(
310
329
  return out
311
330
  toml_text = work_dir = None
312
331
  if package_name is None:
313
- work_dir = Project.get_work_dir()
314
- toml_text = Project.load_toml_text()
315
- doc = tomllib.loads(toml_text)
316
- project_name = doc.get("project", {}).get("name", work_dir.name)
317
- package_name = re.sub(r"[- ]", "_", project_name)
332
+ try:
333
+ work_dir = Project.get_work_dir()
334
+ except EnvError as e:
335
+ if (res := _get_frontend_version()) is None:
336
+ raise e
337
+ current_version = res[1]
338
+ if check_version:
339
+ return False, current_version
340
+ return current_version
341
+ else:
342
+ toml_text = Project.load_toml_text()
343
+ doc = tomllib.loads(toml_text)
344
+ project_name = doc.get("project", {}).get("name", work_dir.name)
345
+ package_name = re.sub(r"[- ]", "_", project_name)
318
346
  local_version = read_version_from_file(package_name, work_dir, toml_text)
319
347
  try:
320
348
  installed_version = importlib_metadata.version(package_name)
@@ -330,15 +358,15 @@ def get_current_version(
330
358
 
331
359
 
332
360
  def _ensure_bool(value: bool | OptionInfo) -> bool:
333
- if not isinstance(value, bool):
334
- value = getattr(value, "default", False)
335
- return value
361
+ if isinstance(value, bool):
362
+ return value
363
+ return bool(getattr(value, "default", False))
336
364
 
337
365
 
338
- def _ensure_str(value: str | OptionInfo | None) -> str:
339
- if not isinstance(value, str):
340
- value = getattr(value, "default", "")
341
- return value
366
+ def _ensure_str(value: str | OptionInfo | None) -> str | None:
367
+ if isinstance(value, str) or value is None:
368
+ return value
369
+ return getattr(value, "default", "")
342
370
 
343
371
 
344
372
  class DryRun:
@@ -371,7 +399,13 @@ class BumpUp(DryRun):
371
399
  self.commit = commit
372
400
  self.part = part
373
401
  if filename is None:
374
- filename = self.parse_filename()
402
+ try:
403
+ filename = self.parse_filename()
404
+ except EnvError:
405
+ if (res := _get_frontend_version()) is not None:
406
+ filename = res[0].name
407
+ else:
408
+ raise
375
409
  self.filename = filename
376
410
  self._no_sync = no_sync
377
411
  self._emoji = emoji
@@ -467,7 +501,7 @@ class BumpUp(DryRun):
467
501
  try:
468
502
  project_name = context["project"]["name"]
469
503
  except KeyError:
470
- packages = []
504
+ packages: list[tuple[str, str]] = []
471
505
  else:
472
506
  packages = [(poetry_module_name(project_name), "")]
473
507
  else:
@@ -549,12 +583,23 @@ class BumpUp(DryRun):
549
583
  echo("You may want to pin tag by `fast tag`")
550
584
 
551
585
 
586
+ def _echo_version(version_file: Any, value: str) -> None:
587
+ styled = typer.style(value, bold=True)
588
+ echo(f"Version value in {version_file}: " + styled)
589
+
590
+
552
591
  @cli.command()
553
592
  def version() -> None:
554
593
  """Show the version of this tool"""
555
594
  echo("Fast Dev Cli Version: " + typer.style(__version__, fg=typer.colors.BLUE))
556
595
  with contextlib.suppress(FileNotFoundError, KeyError):
557
- toml_text = Project.load_toml_text()
596
+ try:
597
+ toml_text = Project.load_toml_text()
598
+ except EnvError:
599
+ if (res := _get_frontend_version()) is not None:
600
+ _echo_version(*res)
601
+ return
602
+ raise
558
603
  doc = tomllib.loads(toml_text)
559
604
  if value := doc.get("project", {}).get("version", ""):
560
605
  styled = typer.style(value, bold=True, fg=typer.colors.CYAN)
@@ -569,8 +614,7 @@ def version() -> None:
569
614
  for line in text.splitlines():
570
615
  if line.strip().startswith(varname):
571
616
  value = line.split("=", 1)[-1].strip().strip('"').strip("'")
572
- styled = typer.style(value, bold=True)
573
- echo(f"Version value in {version_file}: " + styled)
617
+ _echo_version(version_file, value)
574
618
  break
575
619
 
576
620
 
@@ -985,7 +1029,7 @@ def upgrade(
985
1029
  dry: bool = DryOption,
986
1030
  ) -> None:
987
1031
  """Upgrade dependencies in pyproject.toml to latest versions"""
988
- if not (tool := _ensure_str(tool)) or tool == ToolOption.default:
1032
+ if not (tool := _ensure_str(tool) or "") or tool == ToolOption.default:
989
1033
  tool = Project.get_manage_tool() or "uv"
990
1034
  if tool in get_args(ToolName):
991
1035
  UpgradeDependencies(dry=dry, tool=cast(ToolName, tool)).run()
@@ -1065,6 +1109,7 @@ class LintCode(DryRun):
1065
1109
  sim: bool = True,
1066
1110
  strict: bool = False,
1067
1111
  ty: bool = False,
1112
+ fix: bool = True,
1068
1113
  ) -> None:
1069
1114
  self.args = args
1070
1115
  self.check_only = check_only
@@ -1077,6 +1122,7 @@ class LintCode(DryRun):
1077
1122
  self._sim = sim
1078
1123
  self._strict = strict
1079
1124
  self._ty = _ensure_bool(ty)
1125
+ self._fix = _ensure_bool(fix)
1080
1126
  super().__init__(_exit, dry)
1081
1127
 
1082
1128
  @staticmethod
@@ -1123,11 +1169,13 @@ class LintCode(DryRun):
1123
1169
  ruff_check_sim: bool = True,
1124
1170
  mypy_strict: bool = False,
1125
1171
  prefer_ty: bool = False,
1172
+ ruff_check_fix: bool = True,
1126
1173
  ) -> str:
1127
1174
  if paths != "." and all(i.endswith(".html") for i in paths.split()):
1128
1175
  return f"prettier -w {paths}"
1129
1176
  cmd = ""
1130
- tools = ["ruff format", "ruff check --extend-select=I,B,SIM --fix", "mypy"]
1177
+ ruff_check = "ruff check --extend-select=I,B,SIM" + " --fix" * ruff_check_fix
1178
+ tools = ["ruff format", ruff_check, "mypy"]
1131
1179
  if check_only:
1132
1180
  tools[0] += " --check"
1133
1181
  if check_only or load_bool("NO_FIX"):
@@ -1256,6 +1304,7 @@ class LintCode(DryRun):
1256
1304
  ruff_check_sim=self._sim,
1257
1305
  mypy_strict=self._strict,
1258
1306
  prefer_ty=self._ty,
1307
+ ruff_check_fix=self._fix,
1259
1308
  )
1260
1309
 
1261
1310
 
@@ -1275,6 +1324,7 @@ def lint(
1275
1324
  sim: bool = True,
1276
1325
  strict: bool = False,
1277
1326
  ty: bool = False,
1327
+ fix: bool = True,
1278
1328
  ) -> None:
1279
1329
  if files is None:
1280
1330
  files = parse_files(sys.argv[1:])
@@ -1292,6 +1342,7 @@ def lint(
1292
1342
  sim=sim,
1293
1343
  strict=strict,
1294
1344
  ty=ty,
1345
+ fix=fix,
1295
1346
  ).run()
1296
1347
 
1297
1348
 
@@ -1343,6 +1394,7 @@ def make_style(
1343
1394
  sim: bool = Option(True, help="Whether ruff check with --extend-select=SIM"),
1344
1395
  strict: bool = Option(False, help="Whether run mypy with --strict"),
1345
1396
  ty: bool = Option(False, help="Whether use ty instead of mypy"),
1397
+ fix: bool | None = Option(None, help="Whether ruff check with --fix"),
1346
1398
  ) -> None:
1347
1399
  """Run: ruff check/format to reformat code and then mypy to check"""
1348
1400
  if getattr(files, "default", files) is None:
@@ -1352,13 +1404,18 @@ def make_style(
1352
1404
  skip = _ensure_bool(skip_mypy)
1353
1405
  dmypy = _ensure_bool(use_dmypy)
1354
1406
  bandit = _ensure_bool(bandit)
1355
- prefix = _ensure_bool(prefix)
1356
- tool = _ensure_str(tool)
1407
+ tool = _ensure_str(tool) or ""
1357
1408
  up = _ensure_bool(up)
1358
1409
  sim = _ensure_bool(sim)
1359
1410
  strict = _ensure_bool(strict)
1360
1411
  kwargs = {"dry": dry, "skip_mypy": skip, "dmypy": dmypy, "bandit": bandit}
1361
- run = check if _ensure_bool(check_only) else functools.partial(lint, prefix=prefix)
1412
+ if _ensure_bool(check_only):
1413
+ run = check
1414
+ else:
1415
+ prefix = _ensure_bool(prefix)
1416
+ if fix is None or not isinstance(fix, bool):
1417
+ fix = load_bool("FASTDEVCLI_FIX", True)
1418
+ run = functools.partial(lint, prefix=prefix, fix=fix)
1362
1419
  run(files, tool=tool, up=up, sim=sim, strict=strict, ty=ty, **kwargs)
1363
1420
 
1364
1421
 
@@ -1396,7 +1453,9 @@ class Sync(DryRun):
1396
1453
  if should_remove or not is_venv():
1397
1454
  raise EnvError("There project is not managed by uv/pdm/poetry!")
1398
1455
  return f"python -m pip install -r {self.filename}"
1399
- prefix = "" if is_venv() else f"{tool} run "
1456
+ prefix = ""
1457
+ if not is_venv():
1458
+ prefix = f"{tool} run " + "--no-sync " * (tool == "uv")
1400
1459
  ensure_pip = " {1}python -m ensurepip && {1}python -m pip install -U pip &&"
1401
1460
  export_cmd = "uv export --no-hashes --all-extras --all-groups --frozen"
1402
1461
  if tool in ("poetry", "pdm"):
@@ -1492,30 +1551,86 @@ def upload(
1492
1551
  exit_if_run_failed(cmd, dry=dry)
1493
1552
 
1494
1553
 
1554
+ def should_use_just() -> bool:
1555
+ if shutil.which("just") is None:
1556
+ return False
1557
+ d = Path.cwd()
1558
+ for _ in range(5):
1559
+ f = d / "justfile"
1560
+ if f.exists():
1561
+ text = f.read_text(encoding="utf-8")
1562
+ return any(i.startswith("dev *args:") for i in text.splitlines())
1563
+ if d.joinpath("pyproject.toml").exists():
1564
+ break
1565
+ return False
1566
+
1567
+
1495
1568
  def dev(
1496
1569
  port: int | None | OptionInfo,
1497
1570
  host: str | None | OptionInfo,
1571
+ fastapi: bool | None = None,
1572
+ uvicorn: bool | None = None,
1573
+ prod: bool | None = None,
1574
+ reload: bool | None = None,
1498
1575
  file: str | None | ArgumentInfo = None,
1499
1576
  dry: bool = False,
1500
1577
  ) -> None:
1501
- cmd = "fastapi dev"
1502
- no_port_yet = True
1503
- if file is not None:
1504
- try:
1505
- port = int(str(file))
1506
- except ValueError:
1507
- cmd += f" {file}"
1508
- else:
1509
- if port != 8000:
1510
- cmd += f" --port={port}"
1511
- no_port_yet = False
1512
- if no_port_yet and (port := getattr(port, "default", port)) and str(port) != "8000":
1513
- cmd += f" --port={port}"
1514
- if (host := getattr(host, "default", host)) and host not in (
1515
- "localhost",
1516
- "127.0.0.1",
1517
- ):
1518
- cmd += f" --host={host}"
1578
+ if should_use_just():
1579
+ args = [i for i in sys.argv[2:] if i != "--dry"]
1580
+ cmd = "just dev"
1581
+ else:
1582
+ cmd = "uvicorn" if uvicorn else "fastapi dev"
1583
+ args = []
1584
+ if (host := getattr(host, "default", host)) and host not in (
1585
+ "localhost",
1586
+ "127.0.0.1",
1587
+ ):
1588
+ args.append(f"--host={host}")
1589
+ no_port_yet = True
1590
+ if file is not None:
1591
+ try:
1592
+ port = int(str(file))
1593
+ except ValueError:
1594
+ if m := re.search(r"(.*):(\d+)$", str(file)):
1595
+ h, p = m.group(1), m.group(2)
1596
+ if h and "--host" not in str(args):
1597
+ if h == "0":
1598
+ args.append("--host=0.0.0.0")
1599
+ else:
1600
+ args.append(f"--host={h}")
1601
+ args.append(f"--port={p}")
1602
+ if uvicorn:
1603
+ p = Path("main.py")
1604
+ if p.exists():
1605
+ cmd += " main:app"
1606
+ elif Path("app", p.name).exists():
1607
+ cmd += " app.main:app"
1608
+ elif Path("app.py").exists():
1609
+ cmd += " app:app"
1610
+ else:
1611
+ if uvicorn and (
1612
+ (filepath := Path(str(file))).is_file()
1613
+ or filepath.suffix == ".py"
1614
+ ):
1615
+ file = filepath.stem + ":app"
1616
+ parent_names = [j for i in filepath.parents if (j := i.name)]
1617
+ if parent_names:
1618
+ file = ".".join([*parent_names[::-1], file])
1619
+ cmd += f" {file}"
1620
+ else:
1621
+ if port != 8000:
1622
+ args.append(f"--port={port}")
1623
+ no_port_yet = False
1624
+ if (
1625
+ no_port_yet
1626
+ and (port := getattr(port, "default", port))
1627
+ and str(port) != "8000"
1628
+ ):
1629
+ args.append(f"--port={port}")
1630
+ if shutil.which("pdm") is not None:
1631
+ cmd = "pdm run " + cmd
1632
+ if args:
1633
+ cmd += " " + " ".join(args)
1519
1634
  exit_if_run_failed(cmd, dry=dry)
1520
1635
 
1521
1636
 
@@ -1524,13 +1639,18 @@ def runserver(
1524
1639
  file_or_port: str | None = typer.Argument(default=None),
1525
1640
  port: int | None = Option(None, "-p", "--port"),
1526
1641
  host: str | None = Option(None, "-h", "--host"),
1642
+ fastapi: bool | None = None,
1643
+ uvicorn: bool | None = None,
1644
+ prod: bool | None = None,
1645
+ reload: bool | None = None,
1527
1646
  dry: bool = DryOption,
1528
1647
  ) -> None:
1529
1648
  """Start a fastapi server(only for fastapi>=0.111.0)"""
1649
+ f = functools.partial(dev, port, host, fastapi, uvicorn, prod, reload, dry=dry)
1530
1650
  if getattr(file_or_port, "default", file_or_port):
1531
- dev(port, host, file=file_or_port, dry=dry)
1651
+ f(file=file_or_port)
1532
1652
  else:
1533
- dev(port, host, dry=dry)
1653
+ f()
1534
1654
 
1535
1655
 
1536
1656
  @cli.command(name="exec")
@@ -1,9 +1,3 @@
1
- [build-system]
2
- requires = [
3
- "pdm-backend",
4
- ]
5
- build-backend = "pdm.backend"
6
-
7
1
  [project]
8
2
  name = "fast-dev-cli"
9
3
  dynamic = []
@@ -34,28 +28,41 @@ classifiers = [
34
28
  dependencies = [
35
29
  "typer>=0.24.0,<1",
36
30
  "tomli >=2.0.1,<3; python_version < '3.11'",
37
- "coverage >=7.5.1",
38
- "pytest >=8.2.0",
39
31
  ]
40
- version = "0.21.0"
32
+ version = "0.22.0"
41
33
 
42
34
  [project.urls]
43
35
  Homepage = "https://github.com/waketzheng/fast-dev-cli"
44
36
 
45
37
  [project.optional-dependencies]
46
- include-optional-dependencies = [
47
- "all",
38
+ standard = [
39
+ "coverage >=7.5.1",
40
+ "pytest >=8.2.0",
41
+ ]
42
+ all = [
43
+ "emoji >=2.12.1",
44
+ "packaging >=20.5",
45
+ "bandit>=1.7.9",
46
+ "pytest-mock >=3.14.0",
47
+ "fast-dev-cli[standard]",
48
48
  ]
49
49
 
50
50
  [project.scripts]
51
- fast = "fast_dev_cli:cli.main"
51
+ fast = "fast_dev_cli.cli:main"
52
52
 
53
53
  [dependency-groups]
54
54
  dev = [
55
55
  "twine>=6.1.0",
56
56
  "asynctor>=0.10.0",
57
+ "mypy>=1.19.1",
57
58
  ]
58
59
 
60
+ [build-system]
61
+ requires = [
62
+ "pdm-backend",
63
+ ]
64
+ build-backend = "pdm.backend"
65
+
59
66
  [tool.pdm]
60
67
  distribution = true
61
68
 
@@ -63,24 +70,14 @@ distribution = true
63
70
  source = "file"
64
71
  path = "fast_dev_cli/__init__.py"
65
72
 
66
- [tool.pdm.build]
67
- source-includes = [
68
- "scripts/",
69
- ]
70
-
71
- [tool.pdm.dev-dependencies]
72
- dev = [
73
- "-e file:///${PROJECT_ROOT}/#egg=fast-dev-cli[all]",
74
- ]
75
-
76
73
  [tool.pdm.scripts]
77
- up = "pdm update -G :all"
74
+ up = "just up {args}"
78
75
  tree = "pdm list --tree {args}"
79
- deps = "pdm run python scripts/deps.py {args}"
76
+ deps = "just deps {args}"
80
77
  prod = "pdm install --prod --frozen {args}"
81
78
  test = "pdm run fast test {args}"
82
- lint = "pdm run fast lint {args}"
83
- check = "pdm run fast check {args}"
79
+ lint = "just lint {args}"
80
+ check = "just check {args}"
84
81
  tag = "pdm run fast tag {args}"
85
82
  bump = "pdm run fast bump patch --commit {args}"
86
83
 
@@ -99,7 +96,7 @@ composite = [
99
96
 
100
97
  [tool.pdm.scripts.start]
101
98
  composite = [
102
- "pre-commit install",
99
+ "prek install",
103
100
  "deps",
104
101
  ]
105
102
 
@@ -109,25 +106,6 @@ composite = [
109
106
  "ruff check --fix --unsafe-fixes {args}",
110
107
  ]
111
108
 
112
- [tool.waketzheng._internal-slim-build.packages.fastdevcli-slim.project]
113
- name = "fastdevcli-slim"
114
-
115
- [tool.waketzheng._internal-slim-build.packages.fastdevcli-slim.project.scripts]
116
- fast = "fast_dev_cli:cli.main"
117
-
118
- [tool.waketzheng._internal-slim-build.packages.fast-dev-cli]
119
- include-optional-dependencies = [
120
- "standard",
121
- ]
122
-
123
- [tool.waketzheng._internal-slim-build.packages.fast-dev-cli.project.optional-dependencies]
124
- include-optional-dependencies = [
125
- "all",
126
- ]
127
-
128
- [tool.waketzheng._internal-slim-build.packages.fast-dev-cli.project.scripts]
129
- fast = "fast_dev_cli:cli.main"
130
-
131
109
  [tool.pytest.ini_options]
132
110
  testpaths = [
133
111
  "tests",
@@ -402,3 +402,23 @@ build-backend = "pdm.backend"
402
402
  out = capture_cmd_output("fast bump patch")
403
403
  assert version_file.as_posix() in out
404
404
  assert "0.3.1" in version_file.read_text()
405
+
406
+
407
+ def test_package_dot_json(tmp_work_dir):
408
+ p = Path("package.json")
409
+ p.write_text('{"version":"0.1.0"}', encoding="utf-8")
410
+ out = capture_cmd_output("fast bump patch")
411
+ assert p.name in out
412
+ assert "0.1.1" in p.read_text()
413
+ out = capture_cmd_output("fast bump patch --commit --dry")
414
+ assert (
415
+ 'bumpversion --parse "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)" --current-version="0.1.1" patch package.json --commit'
416
+ in out
417
+ )
418
+ run_and_echo("git init")
419
+ run_and_echo("git add .")
420
+ run_and_echo("git config user.name xxx")
421
+ run_and_echo("git config user.email xxx@a.com")
422
+ run_and_echo('git commit -m "xxx"')
423
+ out = capture_cmd_output("fast tag --dry")
424
+ assert "git tag -a 0.1.1 -m '' && git push --tags" in out
@@ -30,7 +30,7 @@ def test_make_deps_class():
30
30
 
31
31
 
32
32
  def test_fast_deps():
33
- out = capture_cmd_output("fast deps --dry")
33
+ out = capture_cmd_output("fast deps --pdm --dry")
34
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"
@@ -42,6 +42,7 @@ def test_cli_test(mocker, capsys):
42
42
  def test_test_with_pdm_run(mocker: MockerFixture, capsys):
43
43
  mocker.patch("fast_dev_cli.cli.check_call", return_value=False)
44
44
  mocker.patch("fast_dev_cli.cli._should_run_test_script", return_value=None)
45
+ mocker.patch("fast_dev_cli.cli.Project.get_manage_tool", return_value="pdm")
45
46
  unitcase(dry=True)
46
47
  assert (
47
48
  '--> pdm run coverage run -m pytest -s && pdm run coverage report --omit="tests/*" -m'