fast-dev-cli 0.19.1__tar.gz → 0.19.3__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 (34) hide show
  1. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/PKG-INFO +1 -1
  2. fast_dev_cli-0.19.3/fast_dev_cli/__init__.py +1 -0
  3. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/fast_dev_cli/cli.py +117 -88
  4. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/pyproject.toml +2 -2
  5. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_exec.py +10 -0
  6. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_sync.py +26 -8
  7. fast_dev_cli-0.19.1/fast_dev_cli/__init__.py +0 -1
  8. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/LICENSE +0 -0
  9. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/README.md +0 -0
  10. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/fast_dev_cli/__main__.py +0 -0
  11. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/fast_dev_cli/py.typed +0 -0
  12. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/pdm_build.py +0 -0
  13. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/scripts/check.py +0 -0
  14. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/scripts/deps.py +0 -0
  15. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/scripts/format.py +0 -0
  16. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/scripts/test.py +0 -0
  17. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/__init__.py +0 -0
  18. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/assets/uv-tx.lock +0 -0
  19. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/assets/uv.lock +0 -0
  20. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/conftest.py +0 -0
  21. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_bump.py +0 -0
  22. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_deps.py +0 -0
  23. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_fast_test.py +0 -0
  24. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_functions.py +0 -0
  25. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_help.py +0 -0
  26. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_lint.py +0 -0
  27. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_poetry_version_plugin.py +0 -0
  28. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_pypi.py +0 -0
  29. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_runserver.py +0 -0
  30. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_tag.py +0 -0
  31. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_upgrade.py +0 -0
  32. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_upload.py +0 -0
  33. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/test_version.py +0 -0
  34. {fast_dev_cli-0.19.1 → fast_dev_cli-0.19.3}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fast-dev-cli
3
- Version: 0.19.1
3
+ Version: 0.19.3
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.19.3"
@@ -12,16 +12,7 @@ import subprocess # nosec:B404
12
12
  import sys
13
13
  from functools import cached_property
14
14
  from pathlib import Path
15
- from typing import (
16
- TYPE_CHECKING,
17
- Any,
18
- Literal,
19
- # Optional is required by Option generated by typer
20
- Optional,
21
- cast,
22
- get_args,
23
- overload,
24
- )
15
+ from typing import TYPE_CHECKING, Any, Literal, cast, get_args, overload
25
16
 
26
17
  import typer
27
18
  from click import UsageError
@@ -148,14 +139,25 @@ class Shell:
148
139
  @property
149
140
  def command(self) -> list[str] | str:
150
141
  command: list[str] | str = self._cmd
151
- if (
152
- isinstance(command, str)
153
- and "shell" not in self._kw
154
- and not (set(self._cmd) & {"|", ">", "&"})
155
- ):
156
- command = shlex.split(command)
142
+ if isinstance(command, str):
143
+ cs = shlex.split(command)
144
+ if "shell" not in self._kw and not (set(self._cmd) & {"|", ">", "&"}):
145
+ command = self.extend_user(cs)
146
+ elif any(i.startswith("~") for i in cs):
147
+ command = re.sub(r" ~", " " + os.path.expanduser("~"), command)
148
+ else:
149
+ command = self.extend_user(command)
157
150
  return command
158
151
 
152
+ @staticmethod
153
+ def extend_user(cs: list[str]) -> list[str]:
154
+ if cs[0] == "echo":
155
+ return cs
156
+ for i, c in enumerate(cs):
157
+ if c.startswith("~"):
158
+ cs[i] = os.path.expanduser(c)
159
+ return cs
160
+
159
161
  def _run(self) -> subprocess.CompletedProcess[str]:
160
162
  return self.run_by_subprocess(self.command, **self._kw)
161
163
 
@@ -391,7 +393,36 @@ class BumpUp(DryRun):
391
393
  return is_emoji(first_char)
392
394
 
393
395
  @staticmethod
396
+ def parse_dynamic_version(
397
+ toml_text: str,
398
+ context: dict,
399
+ work_dir: Path | None = None,
400
+ ) -> str | None:
401
+ if work_dir is None:
402
+ work_dir = Project.get_work_dir()
403
+ for tool in ("pdm", "hatch"):
404
+ with contextlib.suppress(KeyError):
405
+ version_path = cast(str, context["tool"][tool]["version"]["path"])
406
+ if (
407
+ Path(version_path).exists()
408
+ or work_dir.joinpath(version_path).exists()
409
+ ):
410
+ return version_path
411
+ # version = { source = "file", path = "fast_dev_cli/__init__.py" }
412
+ v_key = "version = "
413
+ p_key = 'path = "'
414
+ for line in toml_text.splitlines():
415
+ if not line.startswith(v_key):
416
+ continue
417
+ if p_key in (value := line.split(v_key, 1)[-1].split("#")[0]):
418
+ filename = value.split(p_key, 1)[-1].split('"')[0]
419
+ if work_dir.joinpath(filename).exists():
420
+ return filename
421
+ return None
422
+
423
+ @classmethod
394
424
  def parse_filename(
425
+ cls,
395
426
  toml_text: str | None = None,
396
427
  work_dir: Path | None = None,
397
428
  package_name: str | None = None,
@@ -414,74 +445,55 @@ class BumpUp(DryRun):
414
445
  try:
415
446
  version_value = context["tool"]["poetry"]["version"]
416
447
  except KeyError:
417
- if not Project.manage_by_poetry():
418
- if work_dir is None:
419
- work_dir = Project.get_work_dir()
420
- for tool in ("pdm", "hatch"):
421
- with contextlib.suppress(KeyError):
422
- version_path = cast(
423
- str, context["tool"][tool]["version"]["path"]
424
- )
425
- if (
426
- Path(version_path).exists()
427
- or work_dir.joinpath(version_path).exists()
428
- ):
429
- return version_path
430
- # version = { source = "file", path = "fast_dev_cli/__init__.py" }
431
- v_key = "version = "
432
- p_key = 'path = "'
433
- for line in toml_text.splitlines():
434
- if not line.startswith(v_key):
435
- continue
436
- if p_key in (value := line.split(v_key, 1)[-1].split("#")[0]):
437
- filename = value.split(p_key, 1)[-1].split('"')[0]
438
- if work_dir.joinpath(filename).exists():
439
- return filename
448
+ if not Project.manage_by_poetry() and (
449
+ filename := cls.parse_dynamic_version(toml_text, context, work_dir)
450
+ ):
451
+ return filename
440
452
  else:
441
453
  by_version_plugin = version_value in ("0", "0.0.0", "init")
442
454
  if by_version_plugin:
455
+ return cls.parse_plugin_version(context, package_name)
456
+
457
+ return TOML_FILE
458
+
459
+ @staticmethod
460
+ def parse_plugin_version(context: dict, package_name: str | None) -> str:
461
+ try:
462
+ package_item = context["tool"]["poetry"]["packages"]
463
+ except KeyError:
443
464
  try:
444
- package_item = context["tool"]["poetry"]["packages"]
465
+ project_name = context["project"]["name"]
445
466
  except KeyError:
446
- try:
447
- project_name = context["project"]["name"]
448
- except KeyError:
449
- packages = []
450
- else:
451
- packages = [(poetry_module_name(project_name), "")]
467
+ packages = []
452
468
  else:
453
- packages = [
454
- (j, i.get("from", ""))
455
- for i in package_item
456
- if (j := i.get("include"))
457
- ]
458
- # In case of managed by `poetry-plugin-version`
459
- cwd = Path.cwd()
460
- pattern = re.compile(r"__version__\s*=\s*['\"]")
461
- ds: list[Path] = []
462
- if package_name is not None:
463
- packages.insert(0, (package_name, ""))
464
- for package_name, source_dir in packages:
465
- ds.append(cwd / package_name)
466
- ds.append(cwd / "src" / package_name)
467
- if source_dir and source_dir != "src":
468
- ds.append(cwd / source_dir / package_name)
469
- module_name = poetry_module_name(cwd.name)
470
- ds.extend([cwd / module_name, cwd / "src" / module_name, cwd])
471
- for d in ds:
472
- init_file = d / "__init__.py"
473
- if (
474
- init_file.exists() and pattern.search(init_file.read_text("utf8"))
475
- ) or (
476
- (init_file := init_file.with_name("__version__.py")).exists()
477
- and pattern.search(init_file.read_text("utf8"))
478
- ):
479
- break
480
- else:
481
- raise ParseError("Version file not found! Where are you now?")
482
- return os.path.relpath(init_file, cwd)
483
-
484
- return TOML_FILE
469
+ packages = [(poetry_module_name(project_name), "")]
470
+ else:
471
+ packages = [
472
+ (j, i.get("from", "")) for i in package_item if (j := i.get("include"))
473
+ ]
474
+ # In case of managed by `poetry-plugin-version`
475
+ cwd = Path.cwd()
476
+ pattern = re.compile(r"__version__\s*=\s*['\"]")
477
+ ds: list[Path] = []
478
+ if package_name is not None:
479
+ packages.insert(0, (package_name, ""))
480
+ for package_name, source_dir in packages:
481
+ ds.append(cwd / package_name)
482
+ ds.append(cwd / "src" / package_name)
483
+ if source_dir and source_dir != "src":
484
+ ds.append(cwd / source_dir / package_name)
485
+ module_name = poetry_module_name(cwd.name)
486
+ ds.extend([cwd / module_name, cwd / "src" / module_name, cwd])
487
+ for d in ds:
488
+ init_file = d / "__init__.py"
489
+ if (init_file.exists() and pattern.search(init_file.read_text("utf8"))) or (
490
+ (init_file := init_file.with_name("__version__.py")).exists()
491
+ and pattern.search(init_file.read_text("utf8"))
492
+ ):
493
+ break
494
+ else:
495
+ raise ParseError("Version file not found! Where are you now?")
496
+ return os.path.relpath(init_file, cwd)
485
497
 
486
498
  def get_part(self, s: str) -> str:
487
499
  choices: dict[str, str] = {}
@@ -517,7 +529,11 @@ class BumpUp(DryRun):
517
529
  cmd += " && git push && git push --tags && git log -1"
518
530
  else:
519
531
  cmd += " --allow-dirty"
520
- if should_sync and not self._no_sync and (sync := Project.get_sync_command()):
532
+ if (
533
+ should_sync
534
+ and not self._no_sync
535
+ and (sync := Project.get_sync_command(only_me=True))
536
+ ):
521
537
  cmd = f"{sync} && " + cmd
522
538
  return cmd
523
539
 
@@ -561,7 +577,7 @@ def bump_version(
561
577
  commit: bool = Option(
562
578
  False, "--commit", "-c", help="Whether run `git commit` after version changed"
563
579
  ),
564
- emoji: Optional[bool] = Option(
580
+ emoji: bool | None = Option(
565
581
  None, "--emoji", help="Whether add emoji prefix to commit message"
566
582
  ),
567
583
  no_sync: bool = Option(
@@ -744,9 +760,12 @@ class Project:
744
760
  return True
745
761
 
746
762
  @classmethod
747
- def get_sync_command(cls, prod: bool = True, doc: dict | None = None) -> str:
763
+ def get_sync_command(
764
+ cls, prod: bool = True, doc: dict | None = None, only_me: bool = False
765
+ ) -> str:
766
+ pdm_i = "pdm install --frozen" + " --prod" * prod
748
767
  if cls.is_pdm_project():
749
- return "pdm install --frozen" + " --prod" * prod
768
+ return pdm_i
750
769
  elif cls.manage_by_poetry(cache=True):
751
770
  cmd = "poetry install"
752
771
  if prod:
@@ -761,7 +780,17 @@ class Project:
761
780
  cmd += " --only=main"
762
781
  return cmd
763
782
  elif cls.get_manage_tool(cache=True) == "uv":
764
- return "uv sync --inexact" + " --no-dev" * prod
783
+ install_me = "uv pip install -e ."
784
+ if doc is None:
785
+ doc = tomllib.loads(cls.load_toml_text())
786
+ is_distribution = (
787
+ doc.get("tool", {}).get("pdm", {}).get("distribution") is not False
788
+ )
789
+ if only_me:
790
+ return install_me if is_distribution else pdm_i
791
+ cmd = "uv sync --inexact" + " --no-dev" * prod
792
+ if is_distribution:
793
+ cmd += f" && {install_me}"
765
794
  return ""
766
795
 
767
796
  @classmethod
@@ -1251,7 +1280,7 @@ def check(
1251
1280
 
1252
1281
  @cli.command(name="lint")
1253
1282
  def make_style(
1254
- files: Optional[list[str]] = typer.Argument(default=None), # noqa:B008
1283
+ files: list[str] | None = typer.Argument(default=None), # noqa:B008
1255
1284
  check_only: bool = Option(False, "--check-only", "-c"),
1256
1285
  bandit: bool = Option(False, "--bandit", help="Run `bandit -r <package_dir>`"),
1257
1286
  prefix: bool = Option(
@@ -1434,9 +1463,9 @@ def dev(
1434
1463
 
1435
1464
  @cli.command(name="dev")
1436
1465
  def runserver(
1437
- file_or_port: Optional[str] = typer.Argument(default=None),
1438
- port: Optional[int] = Option(None, "-p", "--port"),
1439
- host: Optional[str] = Option(None, "-h", "--host"),
1466
+ file_or_port: str | None = typer.Argument(default=None),
1467
+ port: int | None = Option(None, "-p", "--port"),
1468
+ host: str | None = Option(None, "-h", "--host"),
1440
1469
  dry: bool = DryOption,
1441
1470
  ) -> None:
1442
1471
  """Start a fastapi server(only for fastapi>=0.111.0)"""
@@ -1624,7 +1653,7 @@ class UvPypi(DryRun):
1624
1653
 
1625
1654
  @cli.command()
1626
1655
  def pypi(
1627
- file: Optional[str] = typer.Argument(default=None),
1656
+ file: str | None = typer.Argument(default=None),
1628
1657
  dry: bool = DryOption,
1629
1658
  verbose: bool = False,
1630
1659
  quiet: bool = False,
@@ -41,7 +41,7 @@ dependencies = [
41
41
  "bumpversion2 >=1.4.3",
42
42
  "pytest >=8.2.0",
43
43
  ]
44
- version = "0.19.1"
44
+ version = "0.19.3"
45
45
 
46
46
  [project.urls]
47
47
  Homepage = "https://github.com/waketzheng/fast-dev-cli"
@@ -57,7 +57,7 @@ fast = "fast_dev_cli:cli.main"
57
57
  [dependency-groups]
58
58
  dev = [
59
59
  "twine>=6.1.0",
60
- "asynctor>=0.8.6",
60
+ "asynctor>=0.10.0",
61
61
  ]
62
62
 
63
63
  [tool.pdm]
@@ -1,3 +1,5 @@
1
+ import os
2
+
1
3
  import pytest
2
4
 
3
5
  from fast_dev_cli.cli import Exit, capture_cmd_output, run_by_subprocess
@@ -15,6 +17,8 @@ def test_exec_dry():
15
17
  )
16
18
  assert "success" in out
17
19
  assert "failed" not in out
20
+ out = capture_cmd_output('fast exec "python ~/0.py" --dry')
21
+ assert "--> python ~/0.py" in out
18
22
 
19
23
 
20
24
  def test_exec():
@@ -29,6 +33,12 @@ def test_exec():
29
33
  )
30
34
  assert "failed" in out
31
35
  assert "success" not in out
36
+ out = capture_cmd_output('fast exec "ls -a ~/"')
37
+ assert "--> ls -a ~" in out
38
+ assert ".bashrc" in out
39
+ home = os.path.expanduser("~")
40
+ expected = capture_cmd_output(f"ls {home}")
41
+ assert not (set(expected.splitlines()) - set(out.splitlines()))
32
42
 
33
43
 
34
44
  def test_run_by_subprocess(capsys):
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import functools
3
4
  from pathlib import Path
4
5
 
5
6
  import pytest
@@ -30,6 +31,16 @@ build-backend = "poetry.core.masonry.api"
30
31
  """
31
32
 
32
33
 
34
+ @functools.cache
35
+ def is_pip_installed() -> bool:
36
+ try:
37
+ import pip # NOQA
38
+ except ImportError:
39
+ return False
40
+ else:
41
+ return True
42
+
43
+
33
44
  def test_sync_not_in_venv(mocker, capsys):
34
45
  mocker.patch("fast_dev_cli.cli.is_venv", return_value=False)
35
46
  test_dir = Path(__file__).parent
@@ -44,10 +55,15 @@ def test_sync_not_in_venv(mocker, capsys):
44
55
  mocker.patch(
45
56
  "fast_dev_cli.cli.UpgradeDependencies.should_with_dev", return_value=True
46
57
  )
47
- assert (
48
- Sync("req.txt", "", True, dry=True).gen()
49
- == "pdm export --without-hashes --with=dev -o req.txt && pdm run python -m pip install -r req.txt"
50
- )
58
+ pdm_export = "pdm export --without-hashes --with=dev -o req.txt"
59
+ installing = "pdm run python -m pip install -r req.txt"
60
+ if not is_pip_installed():
61
+ installing = (
62
+ "pdm run python -m ensurepip && pdm run python -m pip install -U pip && "
63
+ + installing
64
+ )
65
+ expected = f"{pdm_export} && {installing}"
66
+ assert Sync("req.txt", "", True, dry=True).gen() == expected
51
67
 
52
68
 
53
69
  def test_sync(mocker):
@@ -62,10 +78,12 @@ def test_sync(mocker):
62
78
  mocker.patch(
63
79
  "fast_dev_cli.cli.UpgradeDependencies.should_with_dev", return_value=True
64
80
  )
65
- assert (
66
- Sync("req.txt", "", True, dry=True).gen()
67
- == "pdm export --without-hashes --with=dev -o req.txt && python -m pip install -r req.txt"
68
- )
81
+ pdm_export = "pdm export --without-hashes --with=dev -o req.txt"
82
+ if is_pip_installed():
83
+ expected = f"{pdm_export} && python -m pip install -r req.txt"
84
+ else:
85
+ expected = f"{pdm_export} && python -m ensurepip && python -m pip install -U pip && python -m pip install -r req.txt"
86
+ assert Sync("req.txt", "", True, dry=True).gen() == expected
69
87
 
70
88
 
71
89
  UV_TOML_EXAMPLE = """
@@ -1 +0,0 @@
1
- __version__ = "0.19.1"
File without changes
File without changes