fast-dev-cli 0.12.0__tar.gz → 0.13.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 (27) hide show
  1. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/PKG-INFO +9 -4
  2. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/README.md +6 -1
  3. fast_dev_cli-0.13.0/fast_dev_cli/__init__.py +1 -0
  4. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/fast_dev_cli/cli.py +48 -19
  5. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/pyproject.toml +3 -3
  6. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/scripts/check.py +3 -2
  7. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_lint.py +13 -6
  8. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_poetry_version_plugin.py +22 -12
  9. fast_dev_cli-0.12.0/fast_dev_cli/__init__.py +0 -1
  10. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/LICENSE +0 -0
  11. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/fast_dev_cli/__main__.py +0 -0
  12. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/fast_dev_cli/py.typed +0 -0
  13. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/pdm_build.py +0 -0
  14. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/scripts/format.py +0 -0
  15. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/scripts/test.py +0 -0
  16. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/__init__.py +0 -0
  17. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/conftest.py +0 -0
  18. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_bump.py +0 -0
  19. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_fast_test.py +0 -0
  20. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_functions.py +0 -0
  21. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_runserver.py +0 -0
  22. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_sync.py +0 -0
  23. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_tag.py +0 -0
  24. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_upgrade.py +0 -0
  25. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_upload.py +0 -0
  26. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.0}/tests/test_version.py +0 -0
  27. {fast_dev_cli-0.12.0 → fast_dev_cli-0.13.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.12.0
3
+ Version: 0.13.0
4
4
  Summary: Python project development tool.
5
5
  Author-Email: Waket Zheng <waketzheng@gmail.com>>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -25,8 +25,8 @@ Requires-Dist: typer<1,>=0.12.3
25
25
  Requires-Dist: emoji<3,>=2.12.1
26
26
  Requires-Dist: tomli<3,>=2.0.1; python_version < "3.11"
27
27
  Requires-Dist: coverage<8,>=7.5.1
28
- Requires-Dist: ruff<1,>=0.4.4
29
- Requires-Dist: mypy<2,>=1.10.0
28
+ Requires-Dist: ruff<1,>=0.6.0
29
+ Requires-Dist: mypy<2,>=1.15.0
30
30
  Requires-Dist: bumpversion2<2,>=1.4.3
31
31
  Requires-Dist: pytest<9,>=8.2.0
32
32
  Requires-Dist: packaging>=20.5
@@ -35,7 +35,7 @@ Requires-Dist: all; extra == "include-optional-dependencies"
35
35
  Description-Content-Type: text/markdown
36
36
 
37
37
  <p align="center">
38
- <img src="https://fastdevcli.waketzheng.top/img/logo-margin/logo-teal.png" alt="FastDevCli">
38
+ <img src="https://raw.githubusercontent.com/waketzheng/fast-dev-cli/main/img/logo-teal.png" alt="FastDevCli">
39
39
  </p>
40
40
  <p align="center">
41
41
  <em>Toolkit for python code lint/test/bump ...</em>
@@ -77,6 +77,11 @@ Description-Content-Type: text/markdown
77
77
 
78
78
  Python 3.9+
79
79
 
80
+ ## Features
81
+
82
+ - Support poetry/pdm/uv
83
+ - Support Windows/MacOS/Linux
84
+
80
85
  ## Installation
81
86
 
82
87
  <div class="termy">
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://fastdevcli.waketzheng.top/img/logo-margin/logo-teal.png" alt="FastDevCli">
2
+ <img src="https://raw.githubusercontent.com/waketzheng/fast-dev-cli/main/img/logo-teal.png" alt="FastDevCli">
3
3
  </p>
4
4
  <p align="center">
5
5
  <em>Toolkit for python code lint/test/bump ...</em>
@@ -41,6 +41,11 @@
41
41
 
42
42
  Python 3.9+
43
43
 
44
+ ## Features
45
+
46
+ - Support poetry/pdm/uv
47
+ - Support Windows/MacOS/Linux
48
+
44
49
  ## Installation
45
50
 
46
51
  <div class="termy">
@@ -0,0 +1 @@
1
+ __version__ = "0.13.0"
@@ -8,7 +8,7 @@ import subprocess # nosec:B404
8
8
  import sys
9
9
  from functools import cached_property
10
10
  from pathlib import Path
11
- from typing import Literal, Optional, get_args
11
+ from typing import Literal, Optional, get_args # Optional is required by typers
12
12
 
13
13
  import emoji
14
14
  import typer
@@ -96,7 +96,7 @@ def capture_cmd_output(command: list[str] | str, *, raises=False, **kw) -> str:
96
96
  r = _run_shell(command, capture_output=True, encoding="utf-8", **kw)
97
97
  if raises and r.returncode != 0:
98
98
  raise ShellCommandError(r.stderr)
99
- return r.stdout.strip()
99
+ return r.stdout.strip() or r.stderr
100
100
 
101
101
 
102
102
  def _parse_version(line: str, pattern: re.Pattern) -> str:
@@ -127,7 +127,7 @@ def read_version_from_file(
127
127
  secho("WARNING: __init__.py file does not exist!")
128
128
  return "0.0.0"
129
129
  pattern = re.compile(r"__version__\s*=")
130
- for line in init_file.read_text().splitlines():
130
+ for line in init_file.read_text("utf-8").splitlines():
131
131
  if pattern.match(line):
132
132
  return _parse_version(line, pattern)
133
133
  secho(f"WARNING: can not find '__version__' var in {init_file}!")
@@ -260,15 +260,25 @@ class BumpUp(DryRun):
260
260
  except KeyError:
261
261
  packages = []
262
262
  else:
263
- packages = [j for i in package_item if (j := i.get("include"))]
263
+ packages = [
264
+ (j, i.get("from", ""))
265
+ for i in package_item
266
+ if (j := i.get("include"))
267
+ ]
264
268
  # In case of managed by `poetry-plugin-version`
265
269
  cwd = Path.cwd()
266
270
  pattern = re.compile(r"__version__\s*=\s*['\"]")
267
- ds = [cwd / i for i in packages] + [cwd / poetry_module_name(cwd.name), cwd]
271
+ ds: list[Path] = []
272
+ for package_name, source_dir in packages:
273
+ ds.append(cwd / package_name)
274
+ ds.append(cwd / "src" / package_name)
275
+ if source_dir and source_dir != "src":
276
+ ds.append(cwd / source_dir / package_name)
277
+ module_name = poetry_module_name(cwd.name)
278
+ ds.extend([cwd / module_name, cwd / "src" / module_name, cwd])
268
279
  for d in ds:
269
- if (init_file := d / "__init__.py").exists() and pattern.search(
270
- init_file.read_text()
271
- ):
280
+ init_file = d / "__init__.py"
281
+ if init_file.exists() and pattern.search(init_file.read_text("utf8")):
272
282
  break
273
283
  else:
274
284
  raise ParseError("Version file not found! Where are you now?")
@@ -666,11 +676,13 @@ class LintCode(DryRun):
666
676
  dry=False,
667
677
  bandit=False,
668
678
  skip_mypy=False,
679
+ dmypy=False,
669
680
  ) -> None:
670
681
  self.args = args
671
682
  self.check_only = check_only
672
683
  self._bandit = bandit
673
684
  self._skip_mypy = skip_mypy
685
+ self._use_dmypy = dmypy
674
686
  super().__init__(_exit, dry)
675
687
 
676
688
  @staticmethod
@@ -678,11 +690,11 @@ class LintCode(DryRun):
678
690
  return check_call("ruff --version")
679
691
 
680
692
  @staticmethod
681
- def prefer_dmypy(paths: str, tools: list[str]) -> bool:
693
+ def prefer_dmypy(paths: str, tools: list[str], use_dmypy=False) -> bool:
682
694
  return (
683
695
  paths == "."
684
696
  and any(t.startswith("mypy") for t in tools)
685
- and not load_bool("NO_DMYPY")
697
+ and (use_dmypy or load_bool("FASTDEVCLI_DMYPY"))
686
698
  )
687
699
 
688
700
  @staticmethod
@@ -696,7 +708,12 @@ class LintCode(DryRun):
696
708
 
697
709
  @classmethod
698
710
  def to_cmd(
699
- cls: type[Self], paths=".", check_only=False, bandit=False, skip_mypy=False
711
+ cls: type[Self],
712
+ paths=".",
713
+ check_only=False,
714
+ bandit=False,
715
+ skip_mypy=False,
716
+ use_dmypy=False,
700
717
  ) -> str:
701
718
  cmd = ""
702
719
  tools = ["ruff format", "ruff check --extend-select=I,B,SIM --fix", "mypy"]
@@ -725,7 +742,7 @@ class LintCode(DryRun):
725
742
  should_run_by_tool = True
726
743
  if should_run_by_tool and (manage_tool := Project.get_manage_tool()):
727
744
  prefix = manage_tool + " run "
728
- if cls.prefer_dmypy(paths, tools):
745
+ if cls.prefer_dmypy(paths, tools, use_dmypy=use_dmypy):
729
746
  tools[-1] = "dmypy run"
730
747
  cmd += lint_them.format(prefix, paths, *tools)
731
748
  if bandit or load_bool("FASTDEVCLI_BANDIT"):
@@ -737,24 +754,32 @@ class LintCode(DryRun):
737
754
 
738
755
  def gen(self: Self) -> str:
739
756
  paths = " ".join(map(str, self.args)) if self.args else "."
740
- return self.to_cmd(paths, self.check_only, self._bandit, self._skip_mypy)
757
+ return self.to_cmd(
758
+ paths, self.check_only, self._bandit, self._skip_mypy, self._use_dmypy
759
+ )
741
760
 
742
761
 
743
762
  def parse_files(args: list[str] | tuple[str, ...]) -> list[str]:
744
763
  return [i for i in args if not i.startswith("-")]
745
764
 
746
765
 
747
- def lint(files=None, dry=False, skip_mypy=False) -> None:
766
+ def lint(files=None, dry=False, skip_mypy=False, dmypy=False) -> None:
748
767
  if files is None:
749
768
  files = parse_files(sys.argv[1:])
750
769
  if files and files[0] == "lint":
751
770
  files = files[1:]
752
- LintCode(files, dry=dry, skip_mypy=skip_mypy).run()
771
+ LintCode(files, dry=dry, skip_mypy=skip_mypy, dmypy=dmypy).run()
753
772
 
754
773
 
755
- def check(files=None, dry=False, bandit=False, skip_mypy=False) -> None:
774
+ def check(files=None, dry=False, bandit=False, skip_mypy=False, dmypy=False) -> None:
756
775
  LintCode(
757
- files, check_only=True, _exit=True, dry=dry, bandit=bandit, skip_mypy=skip_mypy
776
+ files,
777
+ check_only=True,
778
+ _exit=True,
779
+ dry=dry,
780
+ bandit=bandit,
781
+ skip_mypy=skip_mypy,
782
+ dmypy=dmypy,
758
783
  ).run()
759
784
 
760
785
 
@@ -763,6 +788,9 @@ def make_style(
763
788
  files: Optional[list[Path]] = typer.Argument(default=None), # noqa:B008
764
789
  check_only: bool = Option(False, "--check-only", "-c"),
765
790
  skip_mypy: bool = Option(False, "--skip-mypy"),
791
+ use_dmypy: bool = Option(
792
+ False, "--dmypy", help="Use `dmypy run` instead of `mypy`"
793
+ ),
766
794
  dry: bool = Option(False, "--dry", help="Only print, not really run shell command"),
767
795
  ) -> None:
768
796
  """Run: ruff check/format to reformat code and then mypy to check"""
@@ -771,10 +799,11 @@ def make_style(
771
799
  elif isinstance(files, str):
772
800
  files = [files]
773
801
  skip = _ensure_bool(skip_mypy)
802
+ dmypy = _ensure_bool(use_dmypy)
774
803
  if _ensure_bool(check_only):
775
- check(files, dry=dry, skip_mypy=skip)
804
+ check(files, dry=dry, skip_mypy=skip, dmypy=dmypy)
776
805
  else:
777
- lint(files, dry=dry, skip_mypy=skip)
806
+ lint(files, dry=dry, skip_mypy=skip, dmypy=dmypy)
778
807
 
779
808
 
780
809
  @cli.command(name="check")
@@ -36,13 +36,13 @@ dependencies = [
36
36
  "emoji >=2.12.1,<3",
37
37
  "tomli>=2.0.1,<3; python_version < '3.11'",
38
38
  "coverage >=7.5.1,<8",
39
- "ruff >=0.4.4,<1",
40
- "mypy >=1.10.0,<2",
39
+ "ruff >=0.6.0,<1",
40
+ "mypy >=1.15.0,<2",
41
41
  "bumpversion2 >=1.4.3,<2",
42
42
  "pytest >=8.2.0,<9",
43
43
  "packaging>=20.5",
44
44
  ]
45
- version = "0.12.0"
45
+ version = "0.13.0"
46
46
 
47
47
  [project.urls]
48
48
  Homepage = "https://github.com/waketzheng/fast-dev-cli"
@@ -19,11 +19,12 @@ def run_and_echo(cmd, tool=TOOL, verbose=True):
19
19
  return os.system(cmd)
20
20
 
21
21
 
22
- if run_and_echo("fast check", verbose=False) != 0:
22
+ if run_and_echo("fast check --skip-mypy", verbose=False) != 0:
23
23
  print("\033[1m Please run './scripts/format.py' to auto-fix style issues \033[0m")
24
24
  sys.exit(1)
25
-
26
25
  package_name = os.path.basename(work_dir).replace("-", "_").replace(" ", "_")
26
+ if run_and_echo("mypy {} {}".format(package_name, "tests")) != 0:
27
+ sys.exit(1)
27
28
  if run_and_echo("bandit -r {}".format(package_name)) != 0:
28
29
  sys.exit(1)
29
30
  print("Done. ✨ 🍰 ✨")
@@ -79,7 +79,10 @@ def test_check_bandit(tmp_path):
79
79
  with chdir(tmp_path):
80
80
  assert LintCode.get_package_name() == "."
81
81
  run_and_echo(f"poetry new {package_path.name}")
82
- shutil.rmtree(package_path / package_path.name)
82
+ src_dir = package_path / "src"
83
+ if not src_dir.exists(): # For poetry<2.1
84
+ src_dir = src_dir.parent / package_path.name
85
+ shutil.rmtree(src_dir)
83
86
  with chdir(package_path):
84
87
  command = capture_cmd_output("fast check --bandit --dry")
85
88
  assert "bandit -r ." in command
@@ -107,8 +110,7 @@ def test_fast_check_0(mock_no_dmypy_0):
107
110
 
108
111
  def _fast_check():
109
112
  command = capture_cmd_output("fast check --dry")
110
- expected = CHECK_CMD.replace("mypy", "dmypy run")
111
- for cmd in expected.split(SEP):
113
+ for cmd in CHECK_CMD.split(SEP):
112
114
  assert cmd in command
113
115
 
114
116
 
@@ -130,9 +132,14 @@ def test_lint_cmd(mock_no_dmypy):
130
132
  )
131
133
 
132
134
 
133
- def test_dmypy_run(mocker):
134
- mocker.patch("fast_dev_cli.cli.LintCode.prefer_dmypy", return_value=True)
135
- command = capture_cmd_output("fast lint --dry .")
135
+ def test_with_dmypy():
136
+ command = capture_cmd_output("fast lint --dmypy --dry .")
137
+ assert "dmypy run ." in command
138
+
139
+
140
+ def test_dmypy_run(monkeypatch):
141
+ monkeypatch.setenv("FASTDEVCLI_DMYPY", "1")
142
+ command = capture_cmd_output("python -m fast_dev_cli lint --dry .")
136
143
  assert "dmypy run ." in command
137
144
 
138
145
 
@@ -1,4 +1,3 @@
1
- import os
2
1
  import shutil
3
2
  import sys
4
3
  from collections.abc import Generator
@@ -41,6 +40,10 @@ def _prepare_package(
41
40
  b += f'\npackages = [{{include = "{package_name}"}}]'
42
41
  with chdir(package_path.parent):
43
42
  run_and_echo(f'poetry new "{package_path.name}"')
43
+ src_dir = package_path / "src"
44
+ if is_src_layout := src_dir.exists():
45
+ # poetry v2 default to use src/<package_name> layout
46
+ init_file = src_dir / package_name / init_file.name
44
47
  toml_file.unlink()
45
48
  py_version = "{}.{}".format(*sys.version_info)
46
49
  with chdir(package_path):
@@ -52,19 +55,23 @@ def _prepare_package(
52
55
  )
53
56
  toml_file.write_text(text + CONF)
54
57
  if package_path.name != package_name:
55
- shutil.move(package_path.name, package_name)
58
+ if is_src_layout:
59
+ shutil.move(src_dir / package_path.name, src_dir / package_name)
60
+ else:
61
+ shutil.move(package_path.name, package_name)
56
62
  init_file.write_text('__version__ = "0.0.1"\n')
57
63
  yield init_file
58
64
 
59
65
 
60
- def _build_bump_cmd(init_file: Path) -> str:
61
- relative_path = os.path.join(init_file.parent.name, init_file.name)
66
+ def _build_bump_cmd(init_file: Path, project_path: Path) -> str:
67
+ relative_path = init_file.relative_to(project_path).as_posix()
62
68
  return rf'bumpversion --parse "(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)" --current-version="0.0.1" patch {relative_path} --allow-dirty'
63
69
 
64
70
 
65
71
  def test_version_plugin(tmp_path: Path) -> None:
66
- with _prepare_package(tmp_path / "helloworld") as init_file:
67
- command = _build_bump_cmd(init_file)
72
+ project_path = tmp_path / "helloworld"
73
+ with _prepare_package(project_path) as init_file:
74
+ command = _build_bump_cmd(init_file, project_path)
68
75
  assert BumpUp(part="patch", commit=False, dry=True).gen() == command
69
76
  run_and_echo("poetry run fast bump patch")
70
77
  assert init_file.read_text() == '__version__ = "0.0.2"\n'
@@ -74,8 +81,9 @@ def test_version_plugin(tmp_path: Path) -> None:
74
81
 
75
82
 
76
83
  def test_version_plugin_2(tmp_path: Path) -> None:
77
- with _prepare_package(tmp_path / "helloworld", mark="0.0.0") as init_file:
78
- command = _build_bump_cmd(init_file)
84
+ project_path = tmp_path / "helloworld"
85
+ with _prepare_package(project_path, mark="0.0.0") as init_file:
86
+ command = _build_bump_cmd(init_file, project_path)
79
87
  assert BumpUp(part="patch", commit=False, dry=True).gen() == command
80
88
  run_and_echo("poetry run fast bump patch")
81
89
  assert init_file.read_text() == '__version__ = "0.0.2"\n'
@@ -85,16 +93,18 @@ def test_version_plugin_2(tmp_path: Path) -> None:
85
93
 
86
94
 
87
95
  def test_version_plugin_include_defined(tmp_path: Path) -> None:
88
- with _prepare_package(tmp_path / "hello world", True) as init_file:
89
- command = _build_bump_cmd(init_file)
96
+ project_path = tmp_path / "hello world"
97
+ with _prepare_package(project_path, True) as init_file:
98
+ command = _build_bump_cmd(init_file, project_path)
90
99
  assert BumpUp(part="patch", commit=False, dry=True).gen() == command
91
100
  run_and_echo("poetry run fast bump patch")
92
101
  assert init_file.read_text() == '__version__ = "0.0.2"\n'
93
102
 
94
103
 
95
104
  def test_version_plugin_include_defined_2(tmp_path: Path) -> None:
96
- with _prepare_package(tmp_path / "hello world", True, mark="0.0.0") as init_file:
97
- command = _build_bump_cmd(init_file)
105
+ project_path = tmp_path / "hello world"
106
+ with _prepare_package(project_path, True, mark="0.0.0") as init_file:
107
+ command = _build_bump_cmd(init_file, project_path)
98
108
  assert BumpUp(part="patch", commit=False, dry=True).gen() == command
99
109
  run_and_echo("poetry run fast bump patch")
100
110
  assert init_file.read_text() == '__version__ = "0.0.2"\n'
@@ -1 +0,0 @@
1
- __version__ = "0.12.0"
File without changes