fast-dev-cli 0.22.0__tar.gz → 0.23.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 (30) hide show
  1. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/PKG-INFO +1 -1
  2. fast_dev_cli-0.23.0/fast_dev_cli/__init__.py +1 -0
  3. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/fast_dev_cli/cli.py +44 -30
  4. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/pyproject.toml +1 -3
  5. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_deps.py +25 -1
  6. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_functions.py +5 -4
  7. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_lint.py +17 -7
  8. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_runserver.py +2 -2
  9. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_tag.py +1 -1
  10. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_upgrade.py +4 -3
  11. fast_dev_cli-0.22.0/fast_dev_cli/__init__.py +0 -1
  12. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/LICENSE +0 -0
  13. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/README.md +0 -0
  14. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/fast_dev_cli/__main__.py +0 -0
  15. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/fast_dev_cli/py.typed +0 -0
  16. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/__init__.py +0 -0
  17. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/assets/uv-tx.lock +0 -0
  18. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/assets/uv-upload-time.lock +0 -0
  19. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/assets/uv.lock +0 -0
  20. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/conftest.py +0 -0
  21. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_bump.py +0 -0
  22. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_exec.py +0 -0
  23. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_fast_test.py +0 -0
  24. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_help.py +0 -0
  25. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_poetry_version_plugin.py +0 -0
  26. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_pypi.py +0 -0
  27. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_sync.py +0 -0
  28. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_upload.py +0 -0
  29. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.0}/tests/test_version.py +0 -0
  30. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.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.22.0
3
+ Version: 0.23.0
4
4
  Summary: Python project development tool.
5
5
  Author-Email: Waket Zheng <waketzheng@gmail.com>>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -0,0 +1 @@
1
+ __version__ = "0.23.0"
@@ -15,7 +15,6 @@ from pathlib import Path
15
15
  from typing import TYPE_CHECKING, Any, Literal, cast, get_args, overload
16
16
 
17
17
  import typer
18
- from click import UsageError
19
18
  from typer import Exit, Option, echo, secho
20
19
  from typer.models import ArgumentInfo, OptionInfo
21
20
 
@@ -98,6 +97,14 @@ def is_windows() -> bool:
98
97
  return platform.system() == "Windows"
99
98
 
100
99
 
100
+ @functools.cache
101
+ def prefer_uv_tool() -> bool:
102
+ if shutil.which("uv") is None:
103
+ return False
104
+ cmd = "uv tool list"
105
+ return Shell(cmd).capture_output() != "No tools installed"
106
+
107
+
101
108
  def yellow_warn(msg: str) -> None:
102
109
  if is_windows() and (encoding := sys.stdout.encoding) != "utf-8":
103
110
  msg = msg.encode(encoding, errors="ignore").decode(encoding)
@@ -281,7 +288,7 @@ def _get_frontend_version() -> tuple[Path, str] | None:
281
288
  except ImportError:
282
289
  from json import loads as json_loads # type:ignore[assignment]
283
290
  content = frontend_version_file.read_bytes()
284
- metadata: dict[str, str] = json_loads(content) # type:ignore[assignment]
291
+ metadata: dict[str, str] = json_loads(content) # type:ignore
285
292
  try:
286
293
  current_version = metadata["version"]
287
294
  except (KeyError, TypeError):
@@ -1078,7 +1085,7 @@ class GitTag(DryRun):
1078
1085
 
1079
1086
  def run(self) -> None:
1080
1087
  if self.mark_tag() and not self.dry:
1081
- echo("You may want to publish package:\n poetry publish --build")
1088
+ echo("You may want to publish package:\n pdm publish")
1082
1089
 
1083
1090
 
1084
1091
  @cli.command()
@@ -1173,17 +1180,21 @@ class LintCode(DryRun):
1173
1180
  ) -> str:
1174
1181
  if paths != "." and all(i.endswith(".html") for i in paths.split()):
1175
1182
  return f"prettier -w {paths}"
1176
- cmd = ""
1177
- ruff_check = "ruff check --extend-select=I,B,SIM" + " --fix" * ruff_check_fix
1183
+ ruff_rules = ["I", "B"]
1184
+ if ruff_check_sim and not load_bool("FASTDEVCLI_NO_SIM"):
1185
+ ruff_rules.append("SIM")
1186
+ if ruff_check_up or load_bool("FASTDEVCLI_UP"):
1187
+ ruff_rules.append("UP")
1188
+ ruff_check = "ruff check --extend-select=" + ",".join(ruff_rules)
1189
+ if (
1190
+ ruff_check_fix
1191
+ and not check_only
1192
+ and (not load_bool("NO_FIX") and not load_bool("FASTDEVCLI_NO_FIX"))
1193
+ ):
1194
+ ruff_check += " --fix"
1178
1195
  tools = ["ruff format", ruff_check, "mypy"]
1179
1196
  if check_only:
1180
1197
  tools[0] += " --check"
1181
- if check_only or load_bool("NO_FIX"):
1182
- tools[1] = tools[1].replace(" --fix", "")
1183
- if ruff_check_up or load_bool("FASTDEVCLI_UP"):
1184
- tools[1] = tools[1].replace(",SIM", ",SIM,UP")
1185
- if not ruff_check_sim or load_bool("FASTDEVCLI_NO_SIM"):
1186
- tools[1] = tools[1].replace(",SIM", "")
1187
1198
  if skip_mypy or load_bool("SKIP_MYPY") or load_bool("FASTDEVCLI_NO_MYPY"):
1188
1199
  # Sometimes mypy is too slow
1189
1200
  tools = tools[:-1]
@@ -1195,23 +1206,11 @@ class LintCode(DryRun):
1195
1206
  tools[-1] += " --ignore-missing-imports"
1196
1207
  if mypy_strict or load_bool("FASTDEVCLI_STRICT"):
1197
1208
  tools[-1] += " --strict"
1198
- lint_them = " && ".join(
1199
- "{0}{" + str(i) + "} {1}" for i in range(2, len(tools) + 2)
1200
- )
1201
- if ruff_exists := cls.check_lint_tool_installed():
1202
- # `ruff <command>` get the same result with `pdm run ruff <command>`
1203
- # While `mypy .`(installed global and env not activated),
1204
- # does not the same as `pdm run mypy .`
1205
- lint_them = " && ".join(
1206
- ("" if tool.startswith("ruff") else "{0}")
1207
- + (
1208
- "{%d} {1}" % i # noqa: UP031
1209
- )
1210
- for i, tool in enumerate(tools, 2)
1211
- )
1209
+ ruff_exists = cls.check_lint_tool_installed()
1212
1210
  prefix = ""
1213
1211
  should_run_by_tool = with_prefix
1214
- if not should_run_by_tool and "mypy" in str(tools):
1212
+ requires_mypy = any(tool.startswith("mypy") for tool in tools)
1213
+ if requires_mypy and not should_run_by_tool:
1215
1214
  if is_venv() and Path(sys.argv[0]).parent != Path.home().joinpath(
1216
1215
  ".local/bin"
1217
1216
  ): # Virtual environment activated and fast-dev-cli is installed in it
@@ -1221,6 +1220,8 @@ class LintCode(DryRun):
1221
1220
  if shutil.which("pipx") is None:
1222
1221
  ensure_pipx = "pip install --user pipx\n pipx ensurepath\n "
1223
1222
  command = ensure_pipx + command
1223
+ elif prefer_uv_tool():
1224
+ command = "uv tool install ruff"
1224
1225
  yellow_warn(
1225
1226
  "You may need to run the following command"
1226
1227
  f" to install ruff:\n\n {command}\n"
@@ -1228,7 +1229,7 @@ class LintCode(DryRun):
1228
1229
  elif cls.missing_mypy_exec():
1229
1230
  should_run_by_tool = True
1230
1231
  if check_call('python -c "import fast_dev_cli"'):
1231
- command = 'python -m pip install -U "fast-dev-cli"'
1232
+ command = "python -m pip install -U mypy"
1232
1233
  yellow_warn(
1233
1234
  "You may need to run the following command"
1234
1235
  f" to install lint tools:\n\n {command}\n"
@@ -1255,7 +1256,17 @@ class LintCode(DryRun):
1255
1256
  prefix = bin_dir
1256
1257
  if cls.prefer_dmypy(paths, tools, use_dmypy=use_dmypy):
1257
1258
  tools[-1] = "dmypy run"
1258
- cmd += lint_them.format(prefix, paths, *tools)
1259
+ cmd = " && ".join(
1260
+ (
1261
+ tool
1262
+ # `ruff <command>` get the same result with `pdm run ruff <command>`.
1263
+ # Other tools should run inside the selected environment.
1264
+ if ruff_exists and tool.startswith("ruff")
1265
+ else prefix + tool
1266
+ )
1267
+ + f" {paths}"
1268
+ for tool in tools
1269
+ )
1259
1270
  if bandit or load_bool("FASTDEVCLI_BANDIT"):
1260
1271
  command = prefix + "bandit"
1261
1272
  if Path("pyproject.toml").exists():
@@ -1717,7 +1728,7 @@ class MakeDeps(DryRun):
1717
1728
  if self.should_ensure_pip():
1718
1729
  cmd = f"python -m ensurepip && {upgrade} && {cmd}"
1719
1730
  elif self.should_upgrade_pip():
1720
- cmd = "{upgrade} && {cmd}"
1731
+ cmd = f"{upgrade} && {cmd}"
1721
1732
  return cmd
1722
1733
 
1723
1734
 
@@ -1743,7 +1754,10 @@ def make_deps(
1743
1754
  ) -> None:
1744
1755
  """Run: ruff check/format to reformat code and then mypy to check"""
1745
1756
  if use_uv + use_pdm + use_pip + use_poetry > 1:
1746
- raise UsageError("`--uv/--pdm/--pip/--poetry` can only choose one!")
1757
+ raise typer.BadParameter(
1758
+ "can only choose one",
1759
+ param_hint=("--uv", "--pdm", "--pip", "--poetry"),
1760
+ )
1747
1761
  if use_uv:
1748
1762
  tool = "uv"
1749
1763
  elif use_pdm:
@@ -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.22.0"
32
+ version = "0.23.0"
33
33
 
34
34
  [project.urls]
35
35
  Homepage = "https://github.com/waketzheng/fast-dev-cli"
@@ -52,9 +52,7 @@ fast = "fast_dev_cli.cli:main"
52
52
 
53
53
  [dependency-groups]
54
54
  dev = [
55
- "twine>=6.1.0",
56
55
  "asynctor>=0.10.0",
57
- "mypy>=1.19.1",
58
56
  ]
59
57
 
60
58
  [build-system]
@@ -1,7 +1,19 @@
1
- from fast_dev_cli.cli import MakeDeps, capture_cmd_output
1
+ import re
2
+
3
+ from fast_dev_cli.cli import MakeDeps, capture_cmd_output, run_and_echo
4
+
5
+ ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
6
+
7
+
8
+ def strip_ansi(text: str) -> str:
9
+ return ANSI_RE.sub("", text)
2
10
 
3
11
 
4
12
  def test_make_deps_class():
13
+ class PipDepsWithoutEnsure(MakeDeps):
14
+ def should_ensure_pip(self) -> bool:
15
+ return False
16
+
5
17
  assert (
6
18
  MakeDeps("uv", prod=False).gen()
7
19
  == "uv sync --inexact --active --all-extras --all-groups"
@@ -27,6 +39,10 @@ def test_make_deps_class():
27
39
  MakeDeps("pip", prod=True).gen()
28
40
  == "python -m ensurepip && python -m pip install --upgrade pip && python -m pip install -e ."
29
41
  )
42
+ assert (
43
+ PipDepsWithoutEnsure("pip", prod=False).gen()
44
+ == "python -m pip install --upgrade pip && python -m pip install -e . --group dev"
45
+ )
30
46
 
31
47
 
32
48
  def test_fast_deps():
@@ -50,6 +66,14 @@ def test_fast_deps():
50
66
  assert out == "--> uv sync --all-extras --all-groups"
51
67
 
52
68
 
69
+ def test_fast_deps_mutually_exclusive_options():
70
+ cmd = "fast deps --uv --pdm --dry"
71
+ assert run_and_echo(cmd, verbose=False) == 2
72
+ out = strip_ansi(capture_cmd_output(cmd))
73
+ assert "Invalid value for '--uv' / '--pdm' / '--pip' / '--poetry'" in out
74
+ assert "can only choose" in out
75
+
76
+
53
77
  def test_smart_fast_deps(tmp_work_dir, monkeypatch):
54
78
  toml_file = tmp_work_dir.joinpath("pyproject.toml")
55
79
  if toml_file.exists():
@@ -4,7 +4,8 @@ from io import StringIO
4
4
  from pathlib import Path
5
5
 
6
6
  import pytest
7
- import typer
7
+ from typer import Exit as TyperExit
8
+ from typer import Option
8
9
 
9
10
  from fast_dev_cli.cli import (
10
11
  DryRun,
@@ -85,7 +86,7 @@ def test_run_shell():
85
86
  )
86
87
  with pytest.raises(FileNotFoundError):
87
88
  exit_if_run_failed("in_valid_command", _exit=True, capture_output=True)
88
- with pytest.raises(typer.Exit):
89
+ with pytest.raises(TyperExit):
89
90
  exit_if_run_failed(
90
91
  "in_valid_command", _exit=False, capture_output=True, shell=True
91
92
  )
@@ -111,8 +112,8 @@ def test_get_version_in_poetry_project(tmp_path: Path):
111
112
  def test_ensure_bool():
112
113
  assert _ensure_bool(True) is True
113
114
  assert _ensure_bool(False) is False
114
- opt = typer.Option(False, "--check-only", "-c")
115
+ opt = Option(False, "--check-only", "-c")
115
116
  assert opt
116
117
  assert _ensure_bool(opt) is False
117
- opt = typer.Option(True, "--check-only", "-c")
118
+ opt = Option(True, "--check-only", "-c")
118
119
  assert _ensure_bool(opt) is True
@@ -12,6 +12,7 @@ from fast_dev_cli.cli import (
12
12
  LintCode,
13
13
  Project,
14
14
  capture_cmd_output,
15
+ is_windows,
15
16
  lint,
16
17
  make_style,
17
18
  only_check,
@@ -61,6 +62,11 @@ def mock_ty_0(monkeypatch):
61
62
  monkeypatch.setenv("FASTDEVCLI_TY", "0")
62
63
 
63
64
 
65
+ def get_bin_dir() -> str:
66
+ return ".venv" + ("\\Scripts\\" if is_windows() else "/bin/")
67
+
68
+
69
+ BIN_DIR = get_bin_dir()
64
70
  SEP = " && "
65
71
  _CMD = "ruff format{} . && ruff check --extend-select=I,B,SIM{} . && mypy ."
66
72
  LINT_CMD = _CMD.format("", " --fix")
@@ -72,7 +78,8 @@ def test_check(mock_no_dmypy, monkeypatch, mocker):
72
78
  for cmd in CHECK_CMD.split(SEP):
73
79
  assert cmd in command
74
80
  command2 = capture_cmd_output("fast check --bandit --dry")
75
- assert command2 == command + " && bandit -c pyproject.toml -r ."
81
+ bandit_check = "bandit -c pyproject.toml -r ."
82
+ assert command2 == command + " && " + BIN_DIR + bandit_check
76
83
  monkeypatch.setenv("FASTDEVCLI_BANDIT", "1")
77
84
  command3 = capture_cmd_output("fast check --dry")
78
85
  assert command3 == command2
@@ -160,7 +167,9 @@ def test_check_skip_mypy(mock_skip_mypy_0, mocker):
160
167
  command = capture_cmd_output(cmd)
161
168
  command2 = capture_cmd_output(cmd2)
162
169
  expected = "--> " + SEP.join(
163
- filter(lambda i: not i.startswith("mypy"), CHECK_CMD.split(SEP))
170
+ filter(
171
+ lambda i: not i.strip().split()[0].endswith("mypy"), CHECK_CMD.split(SEP)
172
+ )
164
173
  )
165
174
  assert command == command2 == expected
166
175
 
@@ -187,10 +196,11 @@ def test_lint_cmd(mock_no_dmypy, monkeypatch):
187
196
  assert cmd in command
188
197
  assert capture_cmd_output(f"{lint_cmd} --dry") == command
189
198
  out = capture_cmd_output(f"{run}fast lint --dry")
190
- assert out == "--> " + LINT_CMD
199
+ expected_lint_cmd = LINT_CMD.replace("&& mypy", "&& " + BIN_DIR + "mypy")
200
+ assert expected_lint_cmd in out
191
201
  assert capture_cmd_output(f"{lint_cmd} .") == capture_cmd_output(f"{lint_cmd}")
192
202
  out = capture_cmd_output(f"{run}fast lint")
193
- assert LINT_CMD in out
203
+ assert expected_lint_cmd in out
194
204
  assert "mypy --strict" in capture_cmd_output("fast lint --strict --dry")
195
205
  monkeypatch.setenv("FASTDEVCLI_STRICT", "1")
196
206
  assert "mypy --strict" in capture_cmd_output("fast lint --dry")
@@ -279,7 +289,7 @@ def test_make_style(mock_skip_mypy_0, mocker, mock_no_dmypy):
279
289
  make_style(["."], check_only=False, dry=True)
280
290
  assert expected in stream.getvalue()
281
291
  with capture_stdout() as stream:
282
- make_style(".", check_only=False, dry=True) # type:ignore[arg-type]
292
+ make_style(".", check_only=False, dry=True) # type:ignore
283
293
  assert expected in stream.getvalue()
284
294
  with capture_stdout() as stream:
285
295
  make_style(["."], check_only=True, dry=True)
@@ -332,7 +342,7 @@ def test_lint_without_ruff_installed(mocker, mock_no_dmypy):
332
342
  with capture_stdout() as stream:
333
343
  lint(".", dry=True)
334
344
  output = stream.getvalue()
335
- cmd = "pipx install ruff"
345
+ cmd = "uv tool install ruff"
336
346
  assert cmd in output
337
347
  tip = "You may need to run the following command to install ruff"
338
348
  assert tip in output
@@ -345,7 +355,7 @@ def test_lint_without_mypy_installed(mocker, mock_no_dmypy):
345
355
  with capture_stdout() as stream:
346
356
  lint(".", dry=True)
347
357
  output = stream.getvalue()
348
- cmd = 'python -m pip install -U "fast-dev-cli"'
358
+ cmd = "python -m pip install -U mypy"
349
359
  assert cmd in output
350
360
  tip = "You may need to run the following command to install lint tools"
351
361
  assert tip in output
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import functools
4
4
 
5
- import fast_dev_cli
5
+ import fast_dev_cli.cli
6
6
  from fast_dev_cli.cli import dev, main, run_and_echo, runserver
7
7
 
8
8
 
@@ -133,4 +133,4 @@ def test_run_by_module(tmp_path):
133
133
  def test_main(mocker):
134
134
  mocker.patch("fast_dev_cli.cli.cli")
135
135
  main()
136
- fast_dev_cli.cli.cli.assert_called_once() # type:ignore[attr-defined]
136
+ fast_dev_cli.cli.cli.assert_called_once() # type:ignore
@@ -32,7 +32,7 @@ def test_echo_when_not_dry(mocker, capsys):
32
32
  git_tag = GitTag("", dry=False)
33
33
  mocker.patch.object(git_tag, "mark_tag", return_value=True)
34
34
  git_tag.run()
35
- assert "poetry publish --build" in capsys.readouterr().out
35
+ assert "pdm publish" in capsys.readouterr().out
36
36
 
37
37
 
38
38
  @contextmanager
@@ -5,7 +5,8 @@ from io import StringIO
5
5
  from pathlib import Path
6
6
 
7
7
  import pytest
8
- import typer
8
+ from typer import Exit
9
+ from typer.colors import YELLOW
9
10
 
10
11
  import fast_dev_cli
11
12
  from fast_dev_cli.cli import (
@@ -314,8 +315,8 @@ def test_upgrade_unknown_tool(mocker):
314
315
  assert run_and_echo(cmd, verbose=False) == 1
315
316
  assert run_and_echo("pdm run " + cmd, verbose=False) == 1
316
317
  mocker.patch("fast_dev_cli.cli.secho")
317
- with pytest.raises(typer.Exit):
318
+ with pytest.raises(Exit):
318
319
  upgrade(tool="pipenv")
319
320
  fast_dev_cli.cli.secho.assert_called_once_with( # type:ignore
320
- "Unknown tool 'pipenv'", fg=typer.colors.YELLOW
321
+ "Unknown tool 'pipenv'", fg=YELLOW
321
322
  )
@@ -1 +0,0 @@
1
- __version__ = "0.22.0"
File without changes
File without changes