fast-dev-cli 0.22.0__tar.gz → 0.23.1__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.1}/PKG-INFO +1 -1
  2. fast_dev_cli-0.23.1/fast_dev_cli/__init__.py +1 -0
  3. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.1}/fast_dev_cli/cli.py +110 -80
  4. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.1}/pyproject.toml +11 -4
  5. fast_dev_cli-0.22.0/fast_dev_cli/__init__.py +0 -1
  6. fast_dev_cli-0.22.0/tests/__init__.py +0 -0
  7. fast_dev_cli-0.22.0/tests/assets/uv-tx.lock +0 -1476
  8. fast_dev_cli-0.22.0/tests/assets/uv-upload-time.lock +0 -1658
  9. fast_dev_cli-0.22.0/tests/assets/uv.lock +0 -1476
  10. fast_dev_cli-0.22.0/tests/conftest.py +0 -59
  11. fast_dev_cli-0.22.0/tests/test_bump.py +0 -424
  12. fast_dev_cli-0.22.0/tests/test_deps.py +0 -78
  13. fast_dev_cli-0.22.0/tests/test_exec.py +0 -51
  14. fast_dev_cli-0.22.0/tests/test_fast_test.py +0 -115
  15. fast_dev_cli-0.22.0/tests/test_functions.py +0 -118
  16. fast_dev_cli-0.22.0/tests/test_help.py +0 -7
  17. fast_dev_cli-0.22.0/tests/test_lint.py +0 -464
  18. fast_dev_cli-0.22.0/tests/test_poetry_version_plugin.py +0 -102
  19. fast_dev_cli-0.22.0/tests/test_pypi.py +0 -118
  20. fast_dev_cli-0.22.0/tests/test_runserver.py +0 -136
  21. fast_dev_cli-0.22.0/tests/test_sync.py +0 -240
  22. fast_dev_cli-0.22.0/tests/test_tag.py +0 -73
  23. fast_dev_cli-0.22.0/tests/test_upgrade.py +0 -321
  24. fast_dev_cli-0.22.0/tests/test_upload.py +0 -43
  25. fast_dev_cli-0.22.0/tests/test_version.py +0 -78
  26. fast_dev_cli-0.22.0/tests/utils.py +0 -74
  27. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.1}/LICENSE +0 -0
  28. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.1}/README.md +0 -0
  29. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.1}/fast_dev_cli/__main__.py +0 -0
  30. {fast_dev_cli-0.22.0 → fast_dev_cli-0.23.1}/fast_dev_cli/py.typed +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.1
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.1"
@@ -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):
@@ -731,6 +738,12 @@ class Project:
731
738
  if t in backend:
732
739
  cls._tool = t
733
740
  return cls._tool
741
+ return cls._get_manage_tool(text, backend, skip_uv)
742
+
743
+ @classmethod
744
+ def _get_manage_tool(
745
+ cls: type[Self], text: str, backend: str, skip_uv: bool
746
+ ) -> ToolName | None:
734
747
  work_dir: Path | None = None
735
748
  uv_lock_exists: bool | None = None
736
749
  if skip_uv:
@@ -755,6 +768,12 @@ class Project:
755
768
  if uv_lock_exists:
756
769
  cls._tool = "uv"
757
770
  return cls._tool
771
+ return cls._parse_manage_tool(work_dir, text, backend)
772
+
773
+ @classmethod
774
+ def _parse_manage_tool(
775
+ cls: type[Self], work_dir: Path, text: str, backend: str
776
+ ) -> ToolName | None:
758
777
  pdm_lock_exists = Path(work_dir, "pdm.lock").exists()
759
778
  poetry_lock_exists = Path(work_dir, "poetry.lock").exists()
760
779
  match pdm_lock_exists + poetry_lock_exists:
@@ -1078,7 +1097,7 @@ class GitTag(DryRun):
1078
1097
 
1079
1098
  def run(self) -> None:
1080
1099
  if self.mark_tag() and not self.dry:
1081
- echo("You may want to publish package:\n poetry publish --build")
1100
+ echo("You may want to publish package:\n pdm publish")
1082
1101
 
1083
1102
 
1084
1103
  @cli.command()
@@ -1173,17 +1192,21 @@ class LintCode(DryRun):
1173
1192
  ) -> str:
1174
1193
  if paths != "." and all(i.endswith(".html") for i in paths.split()):
1175
1194
  return f"prettier -w {paths}"
1176
- cmd = ""
1177
- ruff_check = "ruff check --extend-select=I,B,SIM" + " --fix" * ruff_check_fix
1195
+ ruff_rules = ["I", "B"]
1196
+ if ruff_check_sim and not load_bool("FASTDEVCLI_NO_SIM"):
1197
+ ruff_rules.append("SIM")
1198
+ if ruff_check_up or load_bool("FASTDEVCLI_UP"):
1199
+ ruff_rules.append("UP")
1200
+ ruff_check = "ruff check --extend-select=" + ",".join(ruff_rules)
1201
+ if (
1202
+ ruff_check_fix
1203
+ and not check_only
1204
+ and (not load_bool("NO_FIX") and not load_bool("FASTDEVCLI_NO_FIX"))
1205
+ ):
1206
+ ruff_check += " --fix"
1178
1207
  tools = ["ruff format", ruff_check, "mypy"]
1179
1208
  if check_only:
1180
1209
  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
1210
  if skip_mypy or load_bool("SKIP_MYPY") or load_bool("FASTDEVCLI_NO_MYPY"):
1188
1211
  # Sometimes mypy is too slow
1189
1212
  tools = tools[:-1]
@@ -1195,23 +1218,11 @@ class LintCode(DryRun):
1195
1218
  tools[-1] += " --ignore-missing-imports"
1196
1219
  if mypy_strict or load_bool("FASTDEVCLI_STRICT"):
1197
1220
  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
- )
1221
+ ruff_exists = cls.check_lint_tool_installed()
1212
1222
  prefix = ""
1213
1223
  should_run_by_tool = with_prefix
1214
- if not should_run_by_tool and "mypy" in str(tools):
1224
+ requires_mypy = any(tool.startswith("mypy") for tool in tools)
1225
+ if requires_mypy and not should_run_by_tool:
1215
1226
  if is_venv() and Path(sys.argv[0]).parent != Path.home().joinpath(
1216
1227
  ".local/bin"
1217
1228
  ): # Virtual environment activated and fast-dev-cli is installed in it
@@ -1221,6 +1232,8 @@ class LintCode(DryRun):
1221
1232
  if shutil.which("pipx") is None:
1222
1233
  ensure_pipx = "pip install --user pipx\n pipx ensurepath\n "
1223
1234
  command = ensure_pipx + command
1235
+ elif prefer_uv_tool():
1236
+ command = "uv tool install ruff"
1224
1237
  yellow_warn(
1225
1238
  "You may need to run the following command"
1226
1239
  f" to install ruff:\n\n {command}\n"
@@ -1228,7 +1241,7 @@ class LintCode(DryRun):
1228
1241
  elif cls.missing_mypy_exec():
1229
1242
  should_run_by_tool = True
1230
1243
  if check_call('python -c "import fast_dev_cli"'):
1231
- command = 'python -m pip install -U "fast-dev-cli"'
1244
+ command = "python -m pip install -U mypy"
1232
1245
  yellow_warn(
1233
1246
  "You may need to run the following command"
1234
1247
  f" to install lint tools:\n\n {command}\n"
@@ -1255,7 +1268,17 @@ class LintCode(DryRun):
1255
1268
  prefix = bin_dir
1256
1269
  if cls.prefer_dmypy(paths, tools, use_dmypy=use_dmypy):
1257
1270
  tools[-1] = "dmypy run"
1258
- cmd += lint_them.format(prefix, paths, *tools)
1271
+ cmd = " && ".join(
1272
+ (
1273
+ tool
1274
+ # `ruff <command>` get the same result with `pdm run ruff <command>`.
1275
+ # Other tools should run inside the selected environment.
1276
+ if ruff_exists and tool.startswith("ruff")
1277
+ else prefix + tool
1278
+ )
1279
+ + f" {paths}"
1280
+ for tool in tools
1281
+ )
1259
1282
  if bandit or load_bool("FASTDEVCLI_BANDIT"):
1260
1283
  command = prefix + "bandit"
1261
1284
  if Path("pyproject.toml").exists():
@@ -1565,6 +1588,59 @@ def should_use_just() -> bool:
1565
1588
  return False
1566
1589
 
1567
1590
 
1591
+ def _parse_serve_file(uvicorn, filename: str, cmd: str, args: list) -> str:
1592
+ if m := re.search(r"(.*):(\d+)$", filename):
1593
+ h, p = m.group(1), m.group(2)
1594
+ if h and "--host" not in str(args):
1595
+ if h == "0":
1596
+ args.append("--host=0.0.0.0")
1597
+ else:
1598
+ args.append(f"--host={h}")
1599
+ args.append(f"--port={p}")
1600
+ if uvicorn:
1601
+ p = Path("main.py")
1602
+ if p.exists():
1603
+ cmd += " main:app"
1604
+ elif Path("app", p.name).exists():
1605
+ cmd += " app.main:app"
1606
+ elif Path("app.py").exists():
1607
+ cmd += " app:app"
1608
+ return cmd
1609
+ if uvicorn and ((filepath := Path(filename)).is_file() or filepath.suffix == ".py"):
1610
+ filename = filepath.stem + ":app"
1611
+ parent_names = [j for i in filepath.parents if (j := i.name)]
1612
+ if parent_names:
1613
+ filename = ".".join([*parent_names[::-1], filename])
1614
+ cmd += " " + filename
1615
+ return cmd
1616
+
1617
+
1618
+ def _runserver(uvicorn, host, port, file) -> tuple[str, list[str]]:
1619
+ cmd = "uvicorn" if uvicorn else "fastapi dev"
1620
+ args = []
1621
+ if (host := getattr(host, "default", host)) and host not in (
1622
+ "localhost",
1623
+ "127.0.0.1",
1624
+ ):
1625
+ args.append(f"--host={host}")
1626
+ no_port_yet = True
1627
+ if file is not None:
1628
+ filename = str(file)
1629
+ try:
1630
+ port = int(filename)
1631
+ except ValueError:
1632
+ cmd = _parse_serve_file(uvicorn, filename, cmd, args)
1633
+ else:
1634
+ if port != 8000:
1635
+ args.append(f"--port={port}")
1636
+ no_port_yet = False
1637
+ if no_port_yet and (port := getattr(port, "default", port)) and str(port) != "8000":
1638
+ args.append(f"--port={port}")
1639
+ if shutil.which("pdm") is not None:
1640
+ cmd = "pdm run " + cmd
1641
+ return cmd, args
1642
+
1643
+
1568
1644
  def dev(
1569
1645
  port: int | None | OptionInfo,
1570
1646
  host: str | None | OptionInfo,
@@ -1579,56 +1655,7 @@ def dev(
1579
1655
  args = [i for i in sys.argv[2:] if i != "--dry"]
1580
1656
  cmd = "just dev"
1581
1657
  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
1658
+ cmd, args = _runserver(uvicorn, host, port, file)
1632
1659
  if args:
1633
1660
  cmd += " " + " ".join(args)
1634
1661
  exit_if_run_failed(cmd, dry=dry)
@@ -1717,7 +1744,7 @@ class MakeDeps(DryRun):
1717
1744
  if self.should_ensure_pip():
1718
1745
  cmd = f"python -m ensurepip && {upgrade} && {cmd}"
1719
1746
  elif self.should_upgrade_pip():
1720
- cmd = "{upgrade} && {cmd}"
1747
+ cmd = f"{upgrade} && {cmd}"
1721
1748
  return cmd
1722
1749
 
1723
1750
 
@@ -1743,7 +1770,10 @@ def make_deps(
1743
1770
  ) -> None:
1744
1771
  """Run: ruff check/format to reformat code and then mypy to check"""
1745
1772
  if use_uv + use_pdm + use_pip + use_poetry > 1:
1746
- raise UsageError("`--uv/--pdm/--pip/--poetry` can only choose one!")
1773
+ raise typer.BadParameter(
1774
+ "can only choose one",
1775
+ param_hint=("--uv", "--pdm", "--pip", "--poetry"),
1776
+ )
1747
1777
  if use_uv:
1748
1778
  tool = "uv"
1749
1779
  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.1"
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]
@@ -67,9 +65,18 @@ build-backend = "pdm.backend"
67
65
  distribution = true
68
66
 
69
67
  [tool.pdm.version]
70
- source = "file"
71
68
  path = "fast_dev_cli/__init__.py"
72
69
 
70
+ [tool.pdm.build]
71
+ excludes = [
72
+ "./**/.git",
73
+ "./**/.*_cache",
74
+ "examples",
75
+ "scripts",
76
+ "tests",
77
+ "fastdevcli-slim",
78
+ ]
79
+
73
80
  [tool.pdm.scripts]
74
81
  up = "just up {args}"
75
82
  tree = "pdm list --tree {args}"
@@ -1 +0,0 @@
1
- __version__ = "0.22.0"
File without changes