fast-dev-cli 0.21.1__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.1 → fast_dev_cli-0.22.0}/PKG-INFO +15 -10
  2. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/README.md +5 -5
  3. fast_dev_cli-0.22.0/fast_dev_cli/__init__.py +1 -0
  4. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/fast_dev_cli/cli.py +84 -21
  5. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/pyproject.toml +24 -46
  6. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_deps.py +1 -1
  7. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_fast_test.py +1 -0
  8. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_lint.py +72 -33
  9. fast_dev_cli-0.22.0/tests/test_runserver.py +136 -0
  10. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_sync.py +14 -8
  11. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_upload.py +1 -1
  12. fast_dev_cli-0.21.1/fast_dev_cli/__init__.py +0 -1
  13. fast_dev_cli-0.21.1/pdm_build.py +0 -39
  14. fast_dev_cli-0.21.1/scripts/check.py +0 -30
  15. fast_dev_cli-0.21.1/scripts/deps.py +0 -156
  16. fast_dev_cli-0.21.1/scripts/format.py +0 -13
  17. fast_dev_cli-0.21.1/scripts/test.py +0 -48
  18. fast_dev_cli-0.21.1/tests/test_runserver.py +0 -97
  19. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/LICENSE +0 -0
  20. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/fast_dev_cli/__main__.py +0 -0
  21. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/fast_dev_cli/py.typed +0 -0
  22. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/__init__.py +0 -0
  23. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/assets/uv-tx.lock +0 -0
  24. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/assets/uv-upload-time.lock +0 -0
  25. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/assets/uv.lock +0 -0
  26. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/conftest.py +0 -0
  27. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_bump.py +0 -0
  28. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_exec.py +0 -0
  29. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_functions.py +0 -0
  30. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_help.py +0 -0
  31. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_poetry_version_plugin.py +0 -0
  32. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_pypi.py +0 -0
  33. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_tag.py +0 -0
  34. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_upgrade.py +0 -0
  35. {fast_dev_cli-0.21.1 → fast_dev_cli-0.22.0}/tests/test_version.py +0 -0
  36. {fast_dev_cli-0.21.1 → 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.1
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">
@@ -90,8 +95,8 @@ Python 3.10+
90
95
  <div class="termy">
91
96
 
92
97
  ```bash
93
- uv tool install --python 3.14 fastdevcli-slim
94
- # Or: pipx install fastdevcli-slim
98
+ uv tool install fast-dev-cli
99
+ # Or: pipx install fast-dev-cli
95
100
  ```
96
101
  *Will create a `fast` command in ~/.local/bin
97
102
 
@@ -102,7 +107,7 @@ uv tool install --python 3.14 fastdevcli-slim
102
107
  <div class="termy">
103
108
 
104
109
  ```bash
105
- pip install fast-dev-cli
110
+ pip install "fast-dev-cli[standard]"
106
111
  ```
107
112
  *Will install: fast-dev-cli emoji typer packaging pytest coverage*
108
113
 
@@ -118,7 +123,7 @@ fast lint [/path/to/file-or-directory]
118
123
  ```bash
119
124
  fast check
120
125
  ```
121
- - Bump up version in pyproject.toml
126
+ - Bump up version in pyproject.toml(or package.json)
122
127
  ```bash
123
128
  fast bump patch # 0.1.0 -> 0.1.1
124
129
  fast bump minor # 0.1.0 -> 0.2.0
@@ -146,7 +151,7 @@ fast pypi
146
151
  ## Use it without installed
147
152
 
148
153
  ```bash
149
- uvx --from fastdevcli-slim fast
154
+ uvx --from fast-dev-cli fast
150
155
  ```
151
156
 
152
157
  ## Lint/Check by ty instead of mypy
@@ -59,8 +59,8 @@ Python 3.10+
59
59
  <div class="termy">
60
60
 
61
61
  ```bash
62
- uv tool install --python 3.14 fastdevcli-slim
63
- # Or: pipx install fastdevcli-slim
62
+ uv tool install fast-dev-cli
63
+ # Or: pipx install fast-dev-cli
64
64
  ```
65
65
  *Will create a `fast` command in ~/.local/bin
66
66
 
@@ -71,7 +71,7 @@ uv tool install --python 3.14 fastdevcli-slim
71
71
  <div class="termy">
72
72
 
73
73
  ```bash
74
- pip install fast-dev-cli
74
+ pip install "fast-dev-cli[standard]"
75
75
  ```
76
76
  *Will install: fast-dev-cli emoji typer packaging pytest coverage*
77
77
 
@@ -87,7 +87,7 @@ fast lint [/path/to/file-or-directory]
87
87
  ```bash
88
88
  fast check
89
89
  ```
90
- - Bump up version in pyproject.toml
90
+ - Bump up version in pyproject.toml(or package.json)
91
91
  ```bash
92
92
  fast bump patch # 0.1.0 -> 0.1.1
93
93
  fast bump minor # 0.1.0 -> 0.2.0
@@ -115,7 +115,7 @@ fast pypi
115
115
  ## Use it without installed
116
116
 
117
117
  ```bash
118
- uvx --from fastdevcli-slim fast
118
+ uvx --from fast-dev-cli fast
119
119
  ```
120
120
 
121
121
  ## Lint/Check by ty instead of mypy
@@ -0,0 +1 @@
1
+ __version__ = "0.22.0"
@@ -1453,7 +1453,9 @@ class Sync(DryRun):
1453
1453
  if should_remove or not is_venv():
1454
1454
  raise EnvError("There project is not managed by uv/pdm/poetry!")
1455
1455
  return f"python -m pip install -r {self.filename}"
1456
- prefix = "" if is_venv() else f"{tool} run "
1456
+ prefix = ""
1457
+ if not is_venv():
1458
+ prefix = f"{tool} run " + "--no-sync " * (tool == "uv")
1457
1459
  ensure_pip = " {1}python -m ensurepip && {1}python -m pip install -U pip &&"
1458
1460
  export_cmd = "uv export --no-hashes --all-extras --all-groups --frozen"
1459
1461
  if tool in ("poetry", "pdm"):
@@ -1549,30 +1551,86 @@ def upload(
1549
1551
  exit_if_run_failed(cmd, dry=dry)
1550
1552
 
1551
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
+
1552
1568
  def dev(
1553
1569
  port: int | None | OptionInfo,
1554
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,
1555
1575
  file: str | None | ArgumentInfo = None,
1556
1576
  dry: bool = False,
1557
1577
  ) -> None:
1558
- cmd = "fastapi dev"
1559
- no_port_yet = True
1560
- if file is not None:
1561
- try:
1562
- port = int(str(file))
1563
- except ValueError:
1564
- cmd += f" {file}"
1565
- else:
1566
- if port != 8000:
1567
- cmd += f" --port={port}"
1568
- no_port_yet = False
1569
- if no_port_yet and (port := getattr(port, "default", port)) and str(port) != "8000":
1570
- cmd += f" --port={port}"
1571
- if (host := getattr(host, "default", host)) and host not in (
1572
- "localhost",
1573
- "127.0.0.1",
1574
- ):
1575
- 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)
1576
1634
  exit_if_run_failed(cmd, dry=dry)
1577
1635
 
1578
1636
 
@@ -1581,13 +1639,18 @@ def runserver(
1581
1639
  file_or_port: str | None = typer.Argument(default=None),
1582
1640
  port: int | None = Option(None, "-p", "--port"),
1583
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,
1584
1646
  dry: bool = DryOption,
1585
1647
  ) -> None:
1586
1648
  """Start a fastapi server(only for fastapi>=0.111.0)"""
1649
+ f = functools.partial(dev, port, host, fastapi, uvicorn, prod, reload, dry=dry)
1587
1650
  if getattr(file_or_port, "default", file_or_port):
1588
- dev(port, host, file=file_or_port, dry=dry)
1651
+ f(file=file_or_port)
1589
1652
  else:
1590
- dev(port, host, dry=dry)
1653
+ f()
1591
1654
 
1592
1655
 
1593
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.1"
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",
@@ -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'
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import platform
4
+ import re
3
5
  import shutil
4
6
  from pathlib import Path
5
7
 
@@ -54,6 +56,11 @@ def mock_ignore_missing_imports_0(monkeypatch):
54
56
  monkeypatch.setenv("IGNORE_MISSING_IMPORTS", "0")
55
57
 
56
58
 
59
+ @pytest.fixture
60
+ def mock_ty_0(monkeypatch):
61
+ monkeypatch.setenv("FASTDEVCLI_TY", "0")
62
+
63
+
57
64
  SEP = " && "
58
65
  _CMD = "ruff format{} . && ruff check --extend-select=I,B,SIM{} . && mypy ."
59
66
  LINT_CMD = _CMD.format("", " --fix")
@@ -178,16 +185,12 @@ def test_lint_cmd(mock_no_dmypy, monkeypatch):
178
185
  command = capture_cmd_output(f"{lint_cmd} . --dry")
179
186
  for cmd in LINT_CMD.split(SEP):
180
187
  assert cmd in command
181
- assert (
182
- capture_cmd_output(f"{lint_cmd} --dry")
183
- == capture_cmd_output(f"{run}fast lint --dry")
184
- == command
185
- )
186
- assert (
187
- capture_cmd_output(f"{lint_cmd} .")
188
- == capture_cmd_output(f"{lint_cmd}")
189
- == capture_cmd_output(f"{run}fast lint")
190
- )
188
+ assert capture_cmd_output(f"{lint_cmd} --dry") == command
189
+ out = capture_cmd_output(f"{run}fast lint --dry")
190
+ assert out == "--> " + LINT_CMD
191
+ assert capture_cmd_output(f"{lint_cmd} .") == capture_cmd_output(f"{lint_cmd}")
192
+ out = capture_cmd_output(f"{run}fast lint")
193
+ assert LINT_CMD in out
191
194
  assert "mypy --strict" in capture_cmd_output("fast lint --strict --dry")
192
195
  monkeypatch.setenv("FASTDEVCLI_STRICT", "1")
193
196
  assert "mypy --strict" in capture_cmd_output("fast lint --dry")
@@ -241,7 +244,8 @@ def test_lint_with_prefix(mocker):
241
244
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=False)
242
245
  with capture_stdout() as stream:
243
246
  make_style(["."], check_only=False, dry=True, prefix=True)
244
- assert "pdm run" in stream.getvalue()
247
+ prefix = "uv run" if platform.system() == "Windows" else ".venv/bin/mypy"
248
+ assert prefix in stream.getvalue()
245
249
 
246
250
 
247
251
  def test_lint_auto_suffix(tmp_work_dir):
@@ -262,44 +266,62 @@ def test_fast_lint_with_uv():
262
266
 
263
267
  def test_make_style(mock_skip_mypy_0, mocker, mock_no_dmypy):
264
268
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=True)
269
+ expected = LINT_CMD
270
+ check_expected = CHECK_CMD
271
+ if shutil.which("mypy") is None:
272
+ prefix = "uv run " if platform.system() == "Windows" else ".venv/bin/"
273
+ expected = expected.replace("&& mypy", f"&& {prefix}mypy")
274
+ check_expected = check_expected.replace("&& mypy", f"&& {prefix}mypy")
265
275
  with capture_stdout() as stream:
266
276
  make_style(check_only=False, dry=True)
267
- assert LINT_CMD in stream.getvalue()
277
+ assert expected in stream.getvalue()
268
278
  with capture_stdout() as stream:
269
279
  make_style(["."], check_only=False, dry=True)
270
- assert LINT_CMD in stream.getvalue()
280
+ assert expected in stream.getvalue()
271
281
  with capture_stdout() as stream:
272
282
  make_style(".", check_only=False, dry=True) # type:ignore[arg-type]
273
- assert LINT_CMD in stream.getvalue()
283
+ assert expected in stream.getvalue()
274
284
  with capture_stdout() as stream:
275
285
  make_style(["."], check_only=True, dry=True)
276
- assert CHECK_CMD in stream.getvalue()
286
+ assert check_expected in stream.getvalue()
277
287
  with capture_stdout() as stream:
278
288
  only_check(dry=True)
279
- assert CHECK_CMD in stream.getvalue()
289
+ assert check_expected in stream.getvalue()
280
290
 
281
291
 
282
292
  def test_lint_class(mocker, mock_no_dmypy):
283
293
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=True)
284
- assert LintCode(".").gen() == LINT_CMD
294
+ expected = LINT_CMD
295
+ check_expected = CHECK_CMD
296
+ if shutil.which("mypy") is None:
297
+ prefix = "uv run " if platform.system() == "Windows" else ".venv/bin/"
298
+ expected = expected.replace("&& mypy", f"&& {prefix}mypy")
299
+ check_expected = check_expected.replace("&& mypy", f"&& {prefix}mypy")
300
+ assert LintCode(".").gen() == expected
285
301
  check = LintCode(".", check_only=True)
286
- assert check.gen() == CHECK_CMD
302
+ assert check.gen() == check_expected
287
303
  mocker.patch("fast_dev_cli.cli.Project.work_dir", return_value=None)
288
304
  assert LintCode(".").gen() == LINT_CMD
289
305
 
290
306
 
291
- def test_lint_func(mocker, mock_no_dmypy):
307
+ def test_lint_func(mocker, mock_no_dmypy, mock_ty_0):
292
308
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=True)
309
+ expected = LINT_CMD
310
+ if shutil.which("mypy") is None:
311
+ prefix = "uv run " if platform.system() == "Windows" else ".venv/bin/"
312
+ expected = expected.replace("&& mypy", f"&& {prefix}mypy")
293
313
  with capture_stdout() as stream:
294
314
  lint(".", dry=True)
295
- assert LINT_CMD in stream.getvalue()
315
+ assert expected in stream.getvalue()
296
316
  with mock_sys_argv(["tests"]), capture_stdout() as stream:
297
317
  lint(dry=True)
298
- assert LINT_CMD.replace(" .", " tests") in stream.getvalue()
318
+ with_dir = re.sub(r" \.$", " tests", expected.replace(" . ", " tests "))
319
+ out = stream.getvalue()
320
+ assert with_dir in out
299
321
  with capture_stdout() as stream:
300
322
  lint(["lint"], dry=True)
301
- assert LINT_CMD in stream.getvalue()
302
- assert LINT_CMD in capture_cmd_output("pdm run python -m fast_dev_cli lint --dry")
323
+ assert expected in stream.getvalue()
324
+ assert expected in capture_cmd_output("pdm run python -m fast_dev_cli lint --dry")
303
325
 
304
326
 
305
327
  def test_lint_without_ruff_installed(mocker, mock_no_dmypy):
@@ -332,7 +354,11 @@ def test_lint_without_mypy_installed(mocker, mock_no_dmypy):
332
354
 
333
355
  def test_no_fix(mock_no_fix, mocker, mock_no_dmypy):
334
356
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=True)
335
- assert LintCode(".").gen() == LINT_CMD.replace(" --fix", "")
357
+ expected = LINT_CMD.replace(" --fix", "")
358
+ if shutil.which("mypy") is None:
359
+ prefix = "uv run " if platform.system() == "Windows" else ".venv/bin/"
360
+ expected = expected.replace("&& mypy", f"&& {prefix}mypy")
361
+ assert LintCode(".").gen() == expected
336
362
 
337
363
 
338
364
  def test_skip_mypy(mock_skip_mypy, mocker):
@@ -360,34 +386,47 @@ def test_skip_mypy_fast_lint(mock_skip_mypy_0, mocker):
360
386
 
361
387
  def test_skip_mypy_0(mock_skip_mypy_0, mocker, mock_no_dmypy):
362
388
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=True)
363
- assert LintCode(".").gen() == LINT_CMD
389
+ expected = LINT_CMD
390
+ if shutil.which("mypy") is None:
391
+ prefix = "uv run " if platform.system() == "Windows" else ".venv/bin/"
392
+ expected = expected.replace("&& mypy", f"&& {prefix}mypy")
393
+ assert LintCode(".").gen() == expected
364
394
 
365
395
 
366
396
  def test_ignore_missing_imports(mock_ignore_missing_imports, mocker, mock_no_dmypy):
367
397
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=True)
368
- assert LintCode(".").gen() == LINT_CMD.replace(
369
- "mypy ", "mypy --ignore-missing-imports "
370
- )
398
+ expected = LINT_CMD.replace("mypy ", "mypy --ignore-missing-imports ")
399
+ if shutil.which("mypy") is None:
400
+ prefix = "uv run " if platform.system() == "Windows" else ".venv/bin/"
401
+ expected = expected.replace("&& mypy", f"&& {prefix}mypy")
402
+ assert LintCode(".").gen() == expected
371
403
 
372
404
 
373
405
  def test_ignore_missing_imports_0(mock_ignore_missing_imports_0, mocker, mock_no_dmypy):
374
406
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=True)
375
- assert LintCode(".").gen() == LINT_CMD
407
+ expected = LINT_CMD
408
+ if shutil.which("mypy") is None:
409
+ prefix = "uv run " if platform.system() == "Windows" else ".venv/bin/"
410
+ expected = expected.replace("&& mypy", f"&& {prefix}mypy")
411
+ assert LintCode(".").gen() == expected
376
412
 
377
413
 
378
- def test_not_in_root(mocker, mock_no_dmypy):
414
+ def test_not_in_root(mocker, mock_no_dmypy, mock_ty_0):
379
415
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=True)
380
416
  root = Path(__file__).parent.parent
417
+ expected = LINT_CMD
418
+ if shutil.which("mypy") is None:
419
+ expected = expected.replace("&& mypy", "&& uv run mypy")
381
420
  with chdir(root / "fast_dev_cli"):
382
- assert LintCode(".").gen() == LINT_CMD
421
+ assert LintCode(".").gen() == expected
383
422
  with chdir(root / "tests"):
384
- assert LintCode(".").gen() == LINT_CMD
423
+ assert LintCode(".").gen() == expected
385
424
  sub = Path("temp_dir")
386
425
  sub.mkdir()
387
426
  with chdir(sub):
388
427
  cmd = LintCode(".").gen()
389
428
  sub.rmdir()
390
- assert cmd == LINT_CMD
429
+ assert cmd == expected
391
430
 
392
431
 
393
432
  def test_get_manage_tool(tmp_path):
@@ -0,0 +1,136 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+
5
+ import fast_dev_cli
6
+ from fast_dev_cli.cli import dev, main, run_and_echo, runserver
7
+
8
+
9
+ def test_runserver(capsys):
10
+ runserver(dry=True)
11
+ out = capsys.readouterr().out.strip()
12
+ assert out.replace("--> ", "") == "pdm run fastapi dev"
13
+ runserver(port=8000, dry=True)
14
+ out = capsys.readouterr().out.strip()
15
+ assert out.replace("--> ", "") == "pdm run fastapi dev"
16
+ runserver(port=9000, dry=True)
17
+ out = capsys.readouterr().out.strip()
18
+ assert out.replace("--> ", "") == "pdm run fastapi dev --port=9000"
19
+ runserver(host="0.0.0.0", dry=True)
20
+ out = capsys.readouterr().out.strip()
21
+ assert out.replace("--> ", "") == "pdm run fastapi dev --host=0.0.0.0"
22
+ runserver(port=9000, host="0.0.0.0", dry=True)
23
+ out = capsys.readouterr().out.strip()
24
+ assert out.replace("--> ", "") == "pdm run fastapi dev --host=0.0.0.0 --port=9000"
25
+ runserver("9000", host="0.0.0.0", dry=True)
26
+ out = capsys.readouterr().out.strip()
27
+ assert out.replace("--> ", "") == "pdm run fastapi dev --host=0.0.0.0 --port=9000"
28
+ runserver("app.py", host="0.0.0.0", dry=True)
29
+ out = capsys.readouterr().out.strip()
30
+ assert out.replace("--> ", "") == "pdm run fastapi dev app.py --host=0.0.0.0"
31
+
32
+
33
+ def test_runserver_no_pdm(capsys, mocker):
34
+ mocker.patch("shutil.which", return_value=None)
35
+ runserver(dry=True)
36
+ out = capsys.readouterr().out.strip()
37
+ assert out.replace("--> ", "") == "fastapi dev"
38
+ runserver(file_or_port=":8000", dry=True)
39
+ out = capsys.readouterr().out.strip()
40
+ assert out.replace("--> ", "") == "fastapi dev --port=8000"
41
+ runserver(file_or_port="0:9000", dry=True)
42
+ out = capsys.readouterr().out.strip()
43
+ assert out.replace("--> ", "") == "fastapi dev --host=0.0.0.0 --port=9000"
44
+ runserver(file_or_port="0.0.0.0:8000", dry=True)
45
+ out = capsys.readouterr().out.strip()
46
+ assert out.replace("--> ", "") == "fastapi dev --host=0.0.0.0 --port=8000"
47
+ runserver(port=9000, host="0.0.0.0", dry=True)
48
+ out = capsys.readouterr().out.strip()
49
+ assert out.replace("--> ", "") == "fastapi dev --host=0.0.0.0 --port=9000"
50
+ runserver("9000", host="0.0.0.0", dry=True)
51
+ out = capsys.readouterr().out.strip()
52
+ assert out.replace("--> ", "") == "fastapi dev --host=0.0.0.0 --port=9000"
53
+ runserver("app.py", uvicorn=True, host="0.0.0.0", dry=True)
54
+ out = capsys.readouterr().out.strip()
55
+ assert out.replace("--> ", "") == "uvicorn app:app --host=0.0.0.0"
56
+ runserver(
57
+ "tests/test_runserver.py", uvicorn=True, port=9999, host="0.0.0.0", dry=True
58
+ )
59
+ out = capsys.readouterr().out.strip()
60
+ assert (
61
+ out.replace("--> ", "")
62
+ == "uvicorn tests.test_runserver:app --host=0.0.0.0 --port=9999"
63
+ )
64
+
65
+
66
+ def test_dev(capsys):
67
+ dev(None, None, dry=True)
68
+ out = capsys.readouterr().out.strip()
69
+ assert out.replace("--> ", "") == "pdm run fastapi dev"
70
+ dev(port=8000, host="", dry=True)
71
+ out = capsys.readouterr().out.strip()
72
+ assert out.replace("--> ", "") == "pdm run fastapi dev"
73
+ dev(port=9000, host=None, dry=True)
74
+ out = capsys.readouterr().out.strip()
75
+ assert out.replace("--> ", "") == "pdm run fastapi dev --port=9000"
76
+ dev(8000, host="0.0.0.0", dry=True)
77
+ out = capsys.readouterr().out.strip()
78
+ assert out.replace("--> ", "") == "pdm run fastapi dev --host=0.0.0.0"
79
+ dev(port=9000, host="0.0.0.0", dry=True)
80
+ out = capsys.readouterr().out.strip()
81
+ assert out.replace("--> ", "") == "pdm run fastapi dev --host=0.0.0.0 --port=9000"
82
+ dev(8001, host="0.0.0.0", dry=True)
83
+ out = capsys.readouterr().out.strip()
84
+ assert out.replace("--> ", "") == "pdm run fastapi dev --host=0.0.0.0 --port=8001"
85
+ dev(None, file="8001", host="0.0.0.0", dry=True)
86
+ out = capsys.readouterr().out.strip()
87
+ assert out.replace("--> ", "") == "pdm run fastapi dev --host=0.0.0.0 --port=8001"
88
+ dev(None, file="main.py", host="0.0.0.0", dry=True)
89
+ out = capsys.readouterr().out.strip()
90
+ assert out.replace("--> ", "") == "pdm run fastapi dev main.py --host=0.0.0.0"
91
+ dev(8001, file="main.py", host="0.0.0.0", dry=True)
92
+ out = capsys.readouterr().out.strip()
93
+ assert (
94
+ out.replace("--> ", "")
95
+ == "pdm run fastapi dev main.py --host=0.0.0.0 --port=8001"
96
+ )
97
+
98
+
99
+ def test_fast_dev(tmp_path):
100
+ file = tmp_path / "a.txt"
101
+ read_text = functools.partial(file.read_text, encoding="utf-8")
102
+ run_and_echo(f"fast dev 9000 --dry > {file}", verbose=False)
103
+ assert "fastapi dev --port=9000" in read_text()
104
+ run_and_echo(f"fast dev main.py --dry > {file}", verbose=False)
105
+ assert "fastapi dev main.py" in read_text()
106
+ run_and_echo(f"fast dev main.py --port=9000 --dry > {file}", verbose=False)
107
+ assert "fastapi dev main.py --port=9000" in read_text()
108
+ run_and_echo(
109
+ f"fast dev main.py --host=0.0.0.0 --port=9000 --dry > {file}", verbose=False
110
+ )
111
+ assert "fastapi dev main.py --host=0.0.0.0 --port=9000" in read_text()
112
+ run_and_echo(f"fast dev 9000 --host=0.0.0.0 --dry > {file}", verbose=False)
113
+ assert "fastapi dev --host=0.0.0.0 --port=9000" in read_text()
114
+
115
+
116
+ def test_run_by_module(tmp_path):
117
+ out = tmp_path / "b.txt"
118
+ fast = "python -m fast_dev_cli"
119
+ run_and_echo(f"{fast} dev 9000 --dry > {out}", verbose=False)
120
+ assert "fastapi dev --port=9000" in out.read_text()
121
+ run_and_echo(f"{fast} dev main.py --dry > {out}", verbose=False)
122
+ assert "fastapi dev main.py" in out.read_text()
123
+ run_and_echo(f"{fast} dev main.py --port=9000 --dry > {out}", verbose=False)
124
+ assert "fastapi dev main.py --port=9000" in out.read_text()
125
+ run_and_echo(
126
+ f"{fast} dev main.py --host=0.0.0.0 --port=9000 --dry > {out}", verbose=False
127
+ )
128
+ assert "fastapi dev main.py --host=0.0.0.0 --port=9000" in out.read_text()
129
+ run_and_echo(f"{fast} dev 9000 --host=0.0.0.0 --dry > {out}", verbose=False)
130
+ assert "fastapi dev --host=0.0.0.0 --port=9000" in out.read_text()
131
+
132
+
133
+ def test_main(mocker):
134
+ mocker.patch("fast_dev_cli.cli.cli")
135
+ main()
136
+ fast_dev_cli.cli.cli.assert_called_once() # type:ignore[attr-defined]
@@ -56,13 +56,17 @@ def test_sync_not_in_venv(mocker, capsys):
56
56
  "fast_dev_cli.cli.UpgradeDependencies.should_with_dev", return_value=True
57
57
  )
58
58
  pdm_export = "pdm export --without-hashes --with=dev -o req.txt"
59
- installing = "pdm run python -m pip install -r req.txt"
59
+ uv_export = "uv export --no-hashes --all-extras --all-groups --frozen -o req.txt"
60
+ using_pdm = Path("pdm.lock").exists()
61
+ export_cmd = pdm_export if using_pdm else uv_export
62
+ tool_run = "pdm run" if using_pdm else "uv run --no-sync"
63
+ installing = f"{tool_run} python -m pip install -r req.txt"
60
64
  if not is_pip_installed():
61
65
  installing = (
62
- "pdm run python -m ensurepip && pdm run python -m pip install -U pip && "
66
+ f"{tool_run} python -m ensurepip && {tool_run} python -m pip install -U pip && "
63
67
  + installing
64
68
  )
65
- expected = f"{pdm_export} && {installing}"
69
+ expected = f"{export_cmd} && {installing}"
66
70
  assert Sync("req.txt", "", True, dry=True).gen() == expected
67
71
 
68
72
 
@@ -79,10 +83,12 @@ def test_sync(mocker):
79
83
  "fast_dev_cli.cli.UpgradeDependencies.should_with_dev", return_value=True
80
84
  )
81
85
  pdm_export = "pdm export --without-hashes --with=dev -o req.txt"
86
+ uv_export = "uv export --no-hashes --all-extras --all-groups --frozen -o req.txt"
87
+ export_cmd = pdm_export if Path("pdm.lock").exists() else uv_export
82
88
  if is_pip_installed():
83
- expected = f"{pdm_export} && python -m pip install -r req.txt"
89
+ expected = f"{export_cmd} && python -m pip install -r req.txt"
84
90
  else:
85
- expected = f"{pdm_export} && python -m ensurepip && python -m pip install -U pip && python -m pip install -r req.txt"
91
+ expected = f"{export_cmd} && python -m ensurepip && python -m pip install -U pip && python -m pip install -r req.txt"
86
92
  assert Sync("req.txt", "", True, dry=True).gen() == expected
87
93
 
88
94
 
@@ -210,12 +216,12 @@ def test_sync_uv(mocker, tmp_path):
210
216
  toml.with_name("uv.lock").write_text(UV_LOCK_EXAMPLE)
211
217
  assert (
212
218
  Sync("req.txt", "", True, dry=True).gen()
213
- == "uv export --no-hashes --all-extras --all-groups --frozen -o req.txt && uv run python -m ensurepip && uv run python -m pip install -U pip && uv run python -m pip install -r req.txt"
219
+ == "uv export --no-hashes --all-extras --all-groups --frozen -o req.txt && uv run --no-sync python -m ensurepip && uv run --no-sync python -m pip install -U pip && uv run --no-sync python -m pip install -r req.txt"
214
220
  )
215
- run_and_echo("uv run python -m ensurepip")
221
+ run_and_echo("uv run --no-sync python -m ensurepip")
216
222
  assert (
217
223
  Sync("req.txt", "", True, dry=True).gen()
218
- == "uv export --no-hashes --all-extras --all-groups --frozen -o req.txt && uv run python -m pip install -r req.txt"
224
+ == "uv export --no-hashes --all-extras --all-groups --frozen -o req.txt && uv run --no-sync python -m pip install -r req.txt"
219
225
  )
220
226
 
221
227
 
@@ -8,7 +8,7 @@ from .utils import chdir
8
8
  def test_upload(capsys):
9
9
  upload(dry=True)
10
10
  out = capsys.readouterr().out.strip()
11
- assert out.replace("--> ", "") == "pdm publish"
11
+ assert out.replace("--> ", "") == "uv build && uv publish"
12
12
 
13
13
 
14
14
  def test_upload_poetry(tmp_path, capsys):
@@ -1 +0,0 @@
1
- __version__ = "0.21.1"
@@ -1,39 +0,0 @@
1
- import os
2
- from typing import Any
3
-
4
- from pdm.backend.hooks import Context
5
-
6
- BUILD_PACKAGE = os.getenv("BUILD_PACKAGE", "fast-dev-cli")
7
-
8
-
9
- def pdm_build_initialize(context: Context) -> None:
10
- metadata = context.config.metadata
11
- # Get custom config for the current package, from the env var
12
- config: dict[str, Any] = context.config.data["tool"]["waketzheng"][
13
- "_internal-slim-build"
14
- ]["packages"][BUILD_PACKAGE]
15
- project_config: dict[str, Any] = config["project"]
16
- # Get main optional dependencies, extras
17
- optional_dependencies: dict[str, list[str]] = metadata.get(
18
- "optional-dependencies", {}
19
- )
20
- # Get custom optional dependencies name to always include in this (non-slim) package
21
- include_optional_dependencies: list[str] = config.get(
22
- "include-optional-dependencies", []
23
- )
24
- # Override main [project] configs with custom configs for this package
25
- for key, value in project_config.items():
26
- metadata[key] = value
27
- # Get custom build config for the current package
28
- build_config: dict[str, Any] = (
29
- config.get("tool", {}).get("pdm", {}).get("build", {})
30
- )
31
- # Override PDM build config with custom build config for this package
32
- for key, value in build_config.items():
33
- context.config.build_config[key] = value
34
- # Get main dependencies
35
- dependencies: list[str] = metadata.get("dependencies", [])
36
- # Add optional dependencies to the default dependencies for this (non-slim) package
37
- for include_optional in include_optional_dependencies:
38
- optional_dependencies_group = optional_dependencies.get(include_optional, [])
39
- dependencies.extend(optional_dependencies_group)
@@ -1,30 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding:utf-8 -*-
3
- import os
4
- import sys
5
-
6
- TOOL = ("pdm", "poetry", "uv")[0]
7
- parent = os.path.abspath(os.path.dirname(__file__))
8
- work_dir = os.path.dirname(parent)
9
- if os.getcwd() != work_dir:
10
- os.chdir(work_dir)
11
-
12
-
13
- def run_and_echo(cmd, tool=TOOL, verbose=True):
14
- # type: (str, str, bool) -> int
15
- if tool:
16
- cmd = tool + " run " + cmd
17
- if verbose:
18
- print("--> " + cmd)
19
- return os.system(cmd)
20
-
21
-
22
- if run_and_echo("fast check --skip-mypy", verbose=False) != 0:
23
- print("\033[1m Please run './scripts/format.py' to auto-fix style issues \033[0m")
24
- sys.exit(1)
25
- package_name = os.path.basename(work_dir).replace("-", "_").replace(" ", "_")
26
- if run_and_echo("ty check {} {}".format(package_name, "tests")) != 0:
27
- sys.exit(1)
28
- if run_and_echo("bandit -r {}".format(package_name)) != 0:
29
- sys.exit(1)
30
- print("Done. ✨ 🍰 ✨")
@@ -1,156 +0,0 @@
1
- #!/usr/bin/env python
2
- """
3
- Install deps by `pdm install -G :all --frozen`
4
- if value of `pdm config use_uv` is True,
5
- otherwise by the following shells:
6
-
7
- pdm run python -m ensurepip
8
- pdm run python -m pip install --upgrade pip
9
- pdm run python -m pip install --group dev -e .
10
-
11
- Usage::
12
- pdm run python scripts/deps.py
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- import os
18
- import platform
19
- import shlex
20
- import subprocess
21
- import sys
22
- from pathlib import Path
23
-
24
- __version__ = "0.1.3"
25
- __updated_at__ = "2025.07.21"
26
- SHELL = """
27
- pdm run python -m ensurepip
28
- pdm run python -m pip install --upgrade pip
29
- pdm run python -m pip install --group dev -e .
30
- """
31
-
32
-
33
- def run_and_echo(
34
- cmd: str,
35
- env: dict[str, str] | None = None,
36
- dry: bool = False,
37
- verbose: bool = True,
38
- **kw,
39
- ) -> int:
40
- if verbose:
41
- print("-->", cmd)
42
- if dry:
43
- return 0
44
- if env is not None:
45
- env = dict(os.environ, **env)
46
- return subprocess.run(shlex.split(cmd), env=env, **kw).returncode
47
-
48
-
49
- def capture_output(cmd: str) -> str:
50
- r = subprocess.run(
51
- shlex.split(cmd), capture_output=True, text=True, encoding="utf-8"
52
- )
53
- return r.stdout.strip()
54
-
55
-
56
- def is_using_uv() -> bool:
57
- return capture_output("pdm config use_uv").lower() == "true"
58
-
59
-
60
- def pop_if_contains(args: list[str], flag: str) -> bool:
61
- try:
62
- index = args.index(flag)
63
- except ValueError:
64
- return False
65
- else:
66
- args.pop(index)
67
- return True
68
-
69
-
70
- def prefer_pdm(args: list[str]) -> bool:
71
- if pop_if_contains(args, "--pip"):
72
- return False
73
- return platform.system() != "Windows" or is_using_uv()
74
-
75
-
76
- def not_distribution() -> bool:
77
- toml = Path(__file__).parent / "pyproject.toml"
78
- if not toml.exists():
79
- toml = toml.parent.parent / toml.name
80
- if not toml.exists():
81
- return False
82
- if sys.version_info >= (3, 11):
83
- import tomllib
84
- else:
85
- try:
86
- import tomli as tomllib
87
- except ImportError:
88
- return False
89
- doc = tomllib.loads(toml.read_text("utf8"))
90
- try:
91
- return not doc["tool"]["pdm"]["distribution"]
92
- except KeyError:
93
- ...
94
- return False
95
-
96
-
97
- def run_shell(command: str, args: list[str], dry: bool) -> int | None:
98
- if args:
99
- command += " " + " ".join(i if i.startswith("-") else repr(i) for i in args)
100
- if run_and_echo(command, dry=dry) == 0:
101
- return None
102
- return 1
103
-
104
-
105
- def pip_available() -> bool:
106
- rc = run_and_echo(
107
- "pdm run python -m pip --version", capture_output=True, verbose=False
108
- )
109
- return rc == 0
110
-
111
-
112
- def main() -> int | None:
113
- args = sys.argv[1:]
114
- if len(args) == 1:
115
- a1 = args[0]
116
- if a1 == "-h" or "--help" in args:
117
- print(__doc__)
118
- return None
119
- elif a1 in ("-v", "--version"):
120
- print(__version__)
121
- return None
122
- dry = pop_if_contains(args, "--dry") or pop_if_contains(args, "--dry-run")
123
- if pop_if_contains(args, "--uv"):
124
- if os.path.exists("uv.lock"):
125
- return run_shell("uv sync --all-extras --all-groups", args, dry)
126
- command = (
127
- "uv install --all-extras -r pyproject.toml --group dev --no-verify-hashes"
128
- )
129
- return run_shell(command, args, dry)
130
- if prefer_pdm(args):
131
- command = "pdm install --frozen -G :all"
132
- return run_shell(command, args, dry)
133
- shell = SHELL.strip()
134
- if pop_if_contains(args, "--no-dev"):
135
- shell = shell.replace(" --group dev", "")
136
- if args:
137
- extras = " ".join(i if i.startswith("-") else repr(i) for i in args)
138
- if extras.startswith("-"):
139
- shell += " " + extras
140
- else:
141
- shell += extras # e.g.: '[xls,fastapi]'
142
- elif not_distribution():
143
- shell = shell.replace(" -e .", "")
144
- cmds = shell.splitlines()
145
- if pip_available(): # If pip exists, skip installing it by ensurepip
146
- cmds = cmds[1:]
147
- for cmd in cmds:
148
- if run_and_echo(cmd, dry=dry) != 0:
149
- return 1
150
- if not dry:
151
- print("Done.")
152
- return 0
153
-
154
-
155
- if __name__ == "__main__":
156
- sys.exit(main())
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding:utf-8 -*-
3
- import os
4
- import sys
5
-
6
- parent = os.path.abspath(os.path.dirname(__file__))
7
- work_dir = os.path.dirname(parent)
8
- if os.getcwd() != work_dir:
9
- os.chdir(work_dir)
10
-
11
- cmd = "pdm run fast lint --skip-mypy"
12
- if os.system(cmd) != 0:
13
- sys.exit(1)
@@ -1,48 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding:utf-8 -*-
3
- import os
4
- import shlex
5
- import subprocess
6
- import sys
7
- import time
8
- from pathlib import Path
9
-
10
- work_dir = Path(__file__).parent.resolve().parent
11
- if Path.cwd() != work_dir:
12
- os.chdir(str(work_dir))
13
-
14
- CMD = "pdm run coverage run -m pytest"
15
- COMBINE = "pdm run coverage combine .coverage*"
16
- REPORT = "pdm run coverage report -m"
17
-
18
-
19
- def remove_outdate_files(start_time: float) -> None:
20
- for file in work_dir.glob(".coverage*"):
21
- if file.stat().st_mtime < start_time:
22
- file.unlink()
23
- print(f"Removed outdate file: {file}")
24
-
25
-
26
- def run_command(cmd: str, shell=False) -> None:
27
- print("-->", cmd, flush=True)
28
- r = subprocess.run(cmd if shell else shlex.split(cmd), shell=shell)
29
- r.returncode and sys.exit(1)
30
-
31
-
32
- def combine_result_files(shell=COMBINE) -> None:
33
- to_be_combine = [i.name for i in work_dir.glob(".coverage.*")]
34
- if to_be_combine:
35
- if sys.platform == "win32":
36
- if work_dir.joinpath(".coverage").exists():
37
- shell = shell.replace("*", " ")
38
- else:
39
- shell = shell.replace(".coverage*", "")
40
- shell += " ".join(to_be_combine)
41
- run_command(shell, True)
42
-
43
-
44
- started_at = time.time()
45
- run_command(CMD)
46
- remove_outdate_files(started_at)
47
- combine_result_files()
48
- run_command(REPORT)
@@ -1,97 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import fast_dev_cli
4
- from fast_dev_cli.cli import dev, main, run_and_echo, runserver
5
-
6
-
7
- def test_runserver(capsys):
8
- runserver(dry=True)
9
- out = capsys.readouterr().out.strip()
10
- assert out.replace("--> ", "") == "fastapi dev"
11
- runserver(port=8000, dry=True)
12
- out = capsys.readouterr().out.strip()
13
- assert out.replace("--> ", "") == "fastapi dev"
14
- runserver(port=9000, dry=True)
15
- out = capsys.readouterr().out.strip()
16
- assert out.replace("--> ", "") == "fastapi dev --port=9000"
17
- runserver(host="0.0.0.0", dry=True)
18
- out = capsys.readouterr().out.strip()
19
- assert out.replace("--> ", "") == "fastapi dev --host=0.0.0.0"
20
- runserver(port=9000, host="0.0.0.0", dry=True)
21
- out = capsys.readouterr().out.strip()
22
- assert out.replace("--> ", "") == "fastapi dev --port=9000 --host=0.0.0.0"
23
- runserver("9000", host="0.0.0.0", dry=True)
24
- out = capsys.readouterr().out.strip()
25
- assert out.replace("--> ", "") == "fastapi dev --port=9000 --host=0.0.0.0"
26
- runserver("app.py", host="0.0.0.0", dry=True)
27
- out = capsys.readouterr().out.strip()
28
- assert out.replace("--> ", "") == "fastapi dev app.py --host=0.0.0.0"
29
-
30
-
31
- def test_dev(capsys):
32
- dev(None, None, dry=True)
33
- out = capsys.readouterr().out.strip()
34
- assert out.replace("--> ", "") == "fastapi dev"
35
- dev(port=8000, host="", dry=True)
36
- out = capsys.readouterr().out.strip()
37
- assert out.replace("--> ", "") == "fastapi dev"
38
- dev(port=9000, host=None, dry=True)
39
- out = capsys.readouterr().out.strip()
40
- assert out.replace("--> ", "") == "fastapi dev --port=9000"
41
- dev(8000, host="0.0.0.0", dry=True)
42
- out = capsys.readouterr().out.strip()
43
- assert out.replace("--> ", "") == "fastapi dev --host=0.0.0.0"
44
- dev(port=9000, host="0.0.0.0", dry=True)
45
- out = capsys.readouterr().out.strip()
46
- assert out.replace("--> ", "") == "fastapi dev --port=9000 --host=0.0.0.0"
47
- dev(8001, host="0.0.0.0", dry=True)
48
- out = capsys.readouterr().out.strip()
49
- assert out.replace("--> ", "") == "fastapi dev --port=8001 --host=0.0.0.0"
50
- dev(None, file="8001", host="0.0.0.0", dry=True)
51
- out = capsys.readouterr().out.strip()
52
- assert out.replace("--> ", "") == "fastapi dev --port=8001 --host=0.0.0.0"
53
- dev(None, file="main.py", host="0.0.0.0", dry=True)
54
- out = capsys.readouterr().out.strip()
55
- assert out.replace("--> ", "") == "fastapi dev main.py --host=0.0.0.0"
56
- dev(8001, file="main.py", host="0.0.0.0", dry=True)
57
- out = capsys.readouterr().out.strip()
58
- assert out.replace("--> ", "") == "fastapi dev main.py --port=8001 --host=0.0.0.0"
59
-
60
-
61
- def test_fast_dev(tmp_path):
62
- out = tmp_path / "a.txt"
63
- run_and_echo(f"fast dev 9000 --dry > {out}", verbose=False)
64
- assert "fastapi dev --port=9000" in out.read_text()
65
- run_and_echo(f"fast dev main.py --dry > {out}", verbose=False)
66
- assert "fastapi dev main.py" in out.read_text()
67
- run_and_echo(f"fast dev main.py --port=9000 --dry > {out}", verbose=False)
68
- assert "fastapi dev main.py --port=9000" in out.read_text()
69
- run_and_echo(
70
- f"fast dev main.py --port=9000 --host=0.0.0.0 --dry > {out}", verbose=False
71
- )
72
- assert "fastapi dev main.py --port=9000 --host=0.0.0.0" in out.read_text()
73
- run_and_echo(f"fast dev 9000 --host=0.0.0.0 --dry > {out}", verbose=False)
74
- assert "fastapi dev --port=9000 --host=0.0.0.0" in out.read_text()
75
-
76
-
77
- def test_run_by_module(tmp_path):
78
- out = tmp_path / "b.txt"
79
- fast = "python -m fast_dev_cli"
80
- run_and_echo(f"{fast} dev 9000 --dry > {out}", verbose=False)
81
- assert "fastapi dev --port=9000" in out.read_text()
82
- run_and_echo(f"{fast} dev main.py --dry > {out}", verbose=False)
83
- assert "fastapi dev main.py" in out.read_text()
84
- run_and_echo(f"{fast} dev main.py --port=9000 --dry > {out}", verbose=False)
85
- assert "fastapi dev main.py --port=9000" in out.read_text()
86
- run_and_echo(
87
- f"{fast} dev main.py --port=9000 --host=0.0.0.0 --dry > {out}", verbose=False
88
- )
89
- assert "fastapi dev main.py --port=9000 --host=0.0.0.0" in out.read_text()
90
- run_and_echo(f"{fast} dev 9000 --host=0.0.0.0 --dry > {out}", verbose=False)
91
- assert "fastapi dev --port=9000 --host=0.0.0.0" in out.read_text()
92
-
93
-
94
- def test_main(mocker):
95
- mocker.patch("fast_dev_cli.cli.cli")
96
- main()
97
- fast_dev_cli.cli.cli.assert_called_once() # type:ignore[attr-defined]
File without changes