fast-dev-cli 0.7.3__tar.gz → 0.8.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.
@@ -1,24 +1,34 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fast-dev-cli
3
- Version: 0.7.3
4
- Summary:
5
- Author: Waket Zheng
6
- Author-email: waketzheng@gmail.com
7
- Requires-Python: >=3.10,<4.0
8
- Classifier: Programming Language :: Python :: 3
3
+ Version: 0.8.0
4
+ Summary: Python project development tool.
5
+ Author-Email: Waket Zheng <waketzheng@gmail.com>>
6
+ Classifier: Development Status :: 4 - Beta
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: Intended Audience :: System Administrators
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3 :: Only
9
12
  Classifier: Programming Language :: Python :: 3.10
10
13
  Classifier: Programming Language :: Python :: 3.11
11
14
  Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Typing :: Typed
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Project-URL: Homepage, https://github.com/waketzheng/fast-dev-cli
21
+ Requires-Python: <3.13,>=3.10
22
+ Requires-Dist: typer<0.13,>=0.12.3
23
+ Requires-Dist: typing-extensions>=4.8.0
24
+ Requires-Dist: coverage<8,>=7.5.1
25
+ Requires-Dist: ruff<0.5,>=0.4.4
26
+ Requires-Dist: mypy<2,>=1.10.0
27
+ Requires-Dist: bumpversion<0.7,>=0.6.0
28
+ Requires-Dist: pytest<9,>=8.2.0
29
+ Requires-Dist: ipython<9,>=8.24.0; extra == "all"
30
+ Requires-Dist: pytest-mock<4,>=3.14.0; extra == "all"
12
31
  Provides-Extra: all
13
- Requires-Dist: bumpversion (>=0.6.0,<0.7.0) ; extra == "all"
14
- Requires-Dist: click (>=7.1.1)
15
- Requires-Dist: coverage (>=6.5.0) ; extra == "all"
16
- Requires-Dist: mypy (>=1.10.0,<2.0.0) ; extra == "all"
17
- Requires-Dist: pytest (>=8.2.0,<9.0.0) ; extra == "all"
18
- Requires-Dist: ruff (>=0.4.2,<0.5.0) ; extra == "all"
19
- Requires-Dist: strenum (>=0.4.15) ; python_version < "3.11"
20
- Requires-Dist: type-extensions (>=0.1.2) ; python_version < "3.11"
21
- Requires-Dist: typer (>=0.12.3,<0.13.0) ; extra == "all"
22
32
  Description-Content-Type: text/markdown
23
33
 
24
34
  <p align="center">
@@ -60,11 +70,10 @@ Python 3.10+
60
70
 
61
71
  <div class="termy">
62
72
 
63
- ```console
64
- $ pip install "fast-dev-cli[all]"
65
- ---> 100%
66
- Successfully installed fast-dev-cli isort black ruff mypy typer bumpversion pytest coverage
73
+ ```bash
74
+ pip install "fast-dev-cli"
67
75
  ```
76
+ *Will install: fast-dev-cli typer ruff mypy bumpversion pytest coverage*
68
77
 
69
78
  </div>
70
79
 
@@ -95,7 +104,6 @@ fast sync
95
104
  fast upgrade
96
105
  ```
97
106
  - Start a fastapi server in development mode
98
- ```
107
+ ```bash
99
108
  fast dev
100
109
  ```
101
-
@@ -37,11 +37,10 @@ Python 3.10+
37
37
 
38
38
  <div class="termy">
39
39
 
40
- ```console
41
- $ pip install "fast-dev-cli[all]"
42
- ---> 100%
43
- Successfully installed fast-dev-cli isort black ruff mypy typer bumpversion pytest coverage
40
+ ```bash
41
+ pip install "fast-dev-cli"
44
42
  ```
43
+ *Will install: fast-dev-cli typer ruff mypy bumpversion pytest coverage*
45
44
 
46
45
  </div>
47
46
 
@@ -72,6 +71,6 @@ fast sync
72
71
  fast upgrade
73
72
  ```
74
73
  - Start a fastapi server in development mode
75
- ```
74
+ ```bash
76
75
  fast dev
77
76
  ```
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import importlib.metadata
3
+ import importlib.metadata as importlib_metadata
4
4
  import os
5
5
  import re
6
6
  import subprocess
@@ -8,75 +8,40 @@ import sys
8
8
  from functools import cached_property
9
9
  from pathlib import Path
10
10
  from subprocess import CompletedProcess
11
- from typing import TYPE_CHECKING, Optional, Type
11
+ from typing import TYPE_CHECKING, Literal, Optional, Type
12
+
13
+ import typer
14
+ from typer import Exit, Option, echo, secho
12
15
 
13
16
  if sys.version_info >= (3, 11):
14
17
  from enum import StrEnum
15
18
  from typing import Self
16
19
  else: # pragma: no cover
17
- from strenum import StrEnum # type:ignore[no-redef,assignment]
20
+ from enum import Enum
21
+
18
22
  from typing_extensions import Self
19
23
 
24
+ class StrEnum(str, Enum):
25
+ __str__ = str.__str__
26
+
27
+
20
28
  if TYPE_CHECKING: # pragma: no cover
21
29
  from typer.models import OptionInfo
22
30
 
23
31
 
24
- __version__ = importlib.metadata.version(Path(__file__).parent.name)
32
+ __version__ = "0.8.0"
25
33
 
26
34
 
27
35
  def parse_files(args: list[str] | tuple[str, ...]) -> list[str]:
28
36
  return [i for i in args if not i.startswith("-")]
29
37
 
30
38
 
31
- try:
32
- import typer
33
- from typer import Exit, Option, echo, secho
34
-
35
- cli = typer.Typer()
36
- if len(sys.argv) >= 2 and sys.argv[1] == "lint":
37
- if not parse_files(sys.argv[2:]):
38
- sys.argv.append(".")
39
- except ModuleNotFoundError:
40
- import click
41
- from click import echo, secho
42
- from click.core import Group as _Group
43
- from click.exceptions import Exit
44
-
45
- def Option(default, *shortcuts, **kw): # type:ignore[no-redef]
46
- return default
47
-
48
- def _command(self, *args, **kwargs):
49
- from click.decorators import command
50
-
51
- def decorator(f):
52
- if kwargs.get("name") == "lint":
53
- import functools
54
-
55
- def auto_fill_args(func):
56
- @functools.wraps(func)
57
- def runner(*arguments: str, **kw):
58
- if "files" not in kw and not parse_files(arguments):
59
- arguments = (".",)
60
- return func(*arguments, **kw)
61
-
62
- return runner
63
-
64
- f = auto_fill_args(f)
65
- if sys.argv[2:]:
66
- f = click.argument("files", nargs=-1)(f)
67
- cmd = command(*args, **kwargs)(f)
68
- self.add_command(cmd)
69
- return cmd
70
-
71
- return decorator
72
-
73
- _Group.command = _command # type:ignore
74
-
75
- @click.group()
76
- def cli() -> None: ... # pragma: no cover
77
-
39
+ if len(sys.argv) >= 2 and sys.argv[1] == "lint":
40
+ if not parse_files(sys.argv[2:]):
41
+ sys.argv.append(".")
78
42
 
79
43
  TOML_FILE = "pyproject.toml"
44
+ cli = typer.Typer()
80
45
 
81
46
 
82
47
  def load_bool(name: str, default=False) -> bool:
@@ -118,7 +83,15 @@ def capture_cmd_output(command: list[str] | str, **kw) -> str:
118
83
  return r.stdout.strip().decode()
119
84
 
120
85
 
121
- def get_current_version(verbose=False) -> str:
86
+ def get_current_version(
87
+ verbose=False,
88
+ is_poetry: bool | None = None,
89
+ package_name=Path(__file__).parent.name,
90
+ ) -> str:
91
+ if is_poetry is None:
92
+ is_poetry = Project.manage_by_poetry()
93
+ if not is_poetry:
94
+ return importlib_metadata.version(package_name)
122
95
  cmd = ["poetry", "version", "-s"]
123
96
  if verbose:
124
97
  echo(f"--> {' '.join(cmd)}")
@@ -166,13 +139,25 @@ class BumpUp(DryRun):
166
139
  major = "major"
167
140
 
168
141
  def __init__(
169
- self: Self, commit: bool, part: str, filename=TOML_FILE, dry=False
142
+ self: Self, commit: bool, part: str, filename: str | None = None, dry=False
170
143
  ) -> None:
171
144
  self.commit = commit
172
145
  self.part = part
146
+ if filename is None:
147
+ filename = self.parse_filename()
173
148
  self.filename = filename
174
149
  super().__init__(dry=dry)
175
150
 
151
+ @staticmethod
152
+ def parse_filename() -> str:
153
+ if not Project.manage_by_poetry():
154
+ # version = { source = "file", path = "fast_dev_cli/cli.py" }
155
+ for line in Project.load_toml_text().splitlines():
156
+ if not line.startswith("version = "):
157
+ continue
158
+ return line.split('path = "', 1)[-1].split('"')[0]
159
+ return TOML_FILE
160
+
176
161
  def get_part(self, s: str) -> str:
177
162
  choices: dict[str, str] = {}
178
163
  for i, p in enumerate(self.PartChoices, 1):
@@ -247,7 +232,7 @@ def bump() -> None:
247
232
 
248
233
 
249
234
  class EnvError(Exception):
250
- """Raise this when the project is expected to be managed by poetry, but toml file not found."""
235
+ """Raise when expected to be managed by poetry, but toml file not found."""
251
236
 
252
237
 
253
238
  class Project:
@@ -277,6 +262,23 @@ class Project:
277
262
  toml_file = cls.get_work_dir().resolve() / TOML_FILE # to be optimize
278
263
  return toml_file.read_text("utf8")
279
264
 
265
+ @classmethod
266
+ def manage_by_poetry(cls: Type[Self]) -> bool:
267
+ return "[tool.poetry]" in cls.load_toml_text()
268
+
269
+ @classmethod
270
+ def get_manage_tool(cls: Type[Self]) -> Literal["poetry", "pdm", ""]:
271
+ try:
272
+ text = cls.load_toml_text()
273
+ except EnvError:
274
+ pass
275
+ else:
276
+ if "[tool.poetry]" in text:
277
+ return "poetry"
278
+ elif "[tool.pdm]" in text:
279
+ return "pdm"
280
+ return ""
281
+
280
282
  @staticmethod
281
283
  def python_exec_dir() -> Path:
282
284
  return Path(sys.executable).parent
@@ -397,6 +399,10 @@ class UpgradeDependencies(Project, DryRun):
397
399
  if toml_text is None:
398
400
  toml_text = cls.load_toml_text()
399
401
  main_title = "[tool.poetry.dependencies]"
402
+ if main_title not in toml_text:
403
+ raise EnvError(
404
+ f"{main_title} not found! Make sure this is a poetry project."
405
+ )
400
406
  text = toml_text.split(main_title)[-1]
401
407
  dev_flag = "--group dev"
402
408
  new_flag, old_flag = cls.DevFlag.new, cls.DevFlag.old
@@ -514,24 +520,28 @@ class LintCode(DryRun):
514
520
  @classmethod
515
521
  def to_cmd(cls: Type[Self], paths=".", check_only=False) -> str:
516
522
  cmd = ""
517
- tools = ["ruff check --extend-select=I --fix", "ruff format", "mypy"]
523
+ tools = ["ruff format", "ruff check --extend-select=I --fix", "mypy"]
518
524
  if check_only:
519
- tools[1] += " --check"
525
+ tools[0] += " --check"
520
526
  if check_only or load_bool("NO_FIX"):
521
- tools[0] = tools[0].replace(" --fix", "")
527
+ tools[1] = tools[1].replace(" --fix", "")
522
528
  if load_bool("SKIP_MYPY"):
523
529
  # Sometimes mypy is too slow
524
530
  tools = tools[:-1]
525
531
  lint_them = " && ".join("{0}{%d} {1}" % i for i in range(2, len(tools) + 2))
526
- prefix = "poetry run "
532
+ prefix = ""
533
+ should_run_by_tool = False
527
534
  if is_venv():
528
- if cls.check_lint_tool_installed():
529
- prefix = ""
530
- else:
535
+ if not cls.check_lint_tool_installed():
536
+ should_run_by_tool = True
531
537
  if check_call("python -c 'import fast_dev_cli'"):
532
- command = 'python -m pip install -U "fast_dev_cli[all]"'
533
- tip = "You may need to run the following command to install lint tools"
538
+ command = 'python -m pip install -U "fast_dev_cli"'
539
+ tip = "You may need to run following command to install lint tools"
534
540
  secho(f"{tip}:\n\n {command}\n", fg="yellow")
541
+ else:
542
+ should_run_by_tool = True
543
+ if should_run_by_tool:
544
+ prefix = Project.get_manage_tool() + " run "
535
545
  cmd += lint_them.format(prefix, paths, *tools)
536
546
  return cmd
537
547
 
@@ -628,7 +638,8 @@ def test(dry: bool, ignore_script=False) -> None:
628
638
  cmd = 'coverage run -m pytest -s && coverage report --omit="tests/*" -m'
629
639
  if not is_venv() or not check_call("coverage --version"):
630
640
  sep = " && "
631
- cmd = sep.join("poetry run " + i for i in cmd.split(sep))
641
+ tool = Project.get_manage_tool()
642
+ cmd = sep.join(f"{tool} run " + i for i in cmd.split(sep))
632
643
  exit_if_run_failed(cmd, dry=dry)
633
644
 
634
645
 
@@ -0,0 +1,39 @@
1
+ import os
2
+ from typing import Any, Dict, List
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)
@@ -0,0 +1,142 @@
1
+ [build-system]
2
+ requires = [
3
+ "pdm-backend",
4
+ ]
5
+ build-backend = "pdm.backend"
6
+
7
+ [project]
8
+ name = "fast-dev-cli"
9
+ dynamic = []
10
+ description = "Python project development tool."
11
+ authors = [
12
+ { name = "Waket Zheng", email = "waketzheng@gmail.com>" },
13
+ ]
14
+ readme = "README.md"
15
+ requires-python = ">=3.10,<3.13"
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: Science/Research",
20
+ "Intended Audience :: System Administrators",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ "Topic :: Software Development :: Libraries",
28
+ "Topic :: Software Development",
29
+ "Typing :: Typed",
30
+ "License :: OSI Approved :: MIT License",
31
+ ]
32
+ dependencies = [
33
+ "typer>=0.12.3,<0.13",
34
+ "typing-extensions>=4.8.0",
35
+ "coverage >=7.5.1,<8",
36
+ "ruff >=0.4.4,<0.5",
37
+ "mypy >=1.10.0,<2",
38
+ "bumpversion >=0.6.0,<0.7",
39
+ "pytest >=8.2.0,<9",
40
+ ]
41
+ version = "0.8.0"
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/waketzheng/fast-dev-cli"
45
+
46
+ [project.optional-dependencies]
47
+ all = [
48
+ "ipython >=8.24.0,<9",
49
+ "pytest-mock >=3.14.0,<4",
50
+ ]
51
+
52
+ [project.scripts]
53
+ fast = "fast_dev_cli:cli.cli"
54
+
55
+ [tool.pdm]
56
+ distribution = true
57
+
58
+ [tool.pdm.version]
59
+ source = "file"
60
+ path = "fast_dev_cli/cli.py"
61
+
62
+ [tool.pdm.build]
63
+ source-includes = [
64
+ "scripts/",
65
+ ]
66
+
67
+ [tool.pdm.dev-dependencies]
68
+ dev = [
69
+ "-e file:///${PROJECT_ROOT}/#egg=fast-dev-cli[standard,all]",
70
+ ]
71
+
72
+ [tool.waketzheng._internal-slim-build.packages.fastdevcli-slim.project]
73
+ name = "fastdevcli-slim"
74
+
75
+ [tool.waketzheng._internal-slim-build.packages.fast-dev-cli]
76
+ include-optional-dependencies = [
77
+ "standard",
78
+ ]
79
+
80
+ [tool.waketzheng._internal-slim-build.packages.fast-dev-cli.project.optional-dependencies]
81
+ all = [
82
+ "ipython >=8.24.0,<9",
83
+ "pytest-mock >=3.14.0,<4",
84
+ ]
85
+
86
+ [tool.waketzheng._internal-slim-build.packages.fast-dev-cli.project.scripts]
87
+ fast = "fast_dev_cli:cli.cli"
88
+
89
+ [tool.pytest.ini_options]
90
+ testpaths = [
91
+ "tests",
92
+ ]
93
+
94
+ [tool.coverage.run]
95
+ parallel = true
96
+ source = [
97
+ "fast_dev_cli",
98
+ ]
99
+ omit = [
100
+ "__pypackages__/*",
101
+ ".venv/*",
102
+ "venv*.bak/*",
103
+ ]
104
+ context = "${CONTEXT}"
105
+
106
+ [tool.coverage.report]
107
+ exclude_lines = [
108
+ "pragma: no cover",
109
+ "@overload",
110
+ "if __name__ == \"__main__\":",
111
+ "if TYPE_CHECKING:",
112
+ ]
113
+ omit = [
114
+ "tests/*",
115
+ ]
116
+
117
+ [tool.mypy]
118
+ pretty = true
119
+ ignore_missing_imports = true
120
+ check_untyped_defs = true
121
+ exclude = [
122
+ "^fabfile\\.py$",
123
+ "two\\.pyi$",
124
+ "^\\.venv",
125
+ "\\.bak",
126
+ "__pypackages__",
127
+ ]
128
+
129
+ [tool.ruff.lint]
130
+ extend-select = [
131
+ "E",
132
+ "W",
133
+ "F",
134
+ "I",
135
+ "B",
136
+ "C4",
137
+ ]
138
+
139
+ [tool.ruff.lint.per-file-ignores]
140
+ "test_*.py" = [
141
+ "E501",
142
+ ]
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+ set -x
5
+
6
+ [ -f ../pyproject.toml ] && cd ..
7
+
8
+ pdm run fast check || \
9
+ echo -e "\033[1m Please run './scripts/format.sh' to auto-fix style issues \033[0m"
@@ -0,0 +1,6 @@
1
+ #!/bin/sh -e
2
+ set -x
3
+
4
+ [ -f ../pyproject.toml ] && cd ..
5
+
6
+ pdm run fast lint
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+ pdm run fast test --ignore-script
File without changes
@@ -1,6 +1,16 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from fast_dev_cli.cli import TOML_FILE
6
+
7
+ from .utils import chdir
8
+
9
+ # toml file content before migrate to pdm
10
+ TOML_CONTENT = """
1
11
  [tool.poetry]
2
12
  name = "fast-dev-cli"
3
- version = "0.7.3"
13
+ version = "0.8.0"
4
14
  description = ""
5
15
  authors = ["Waket Zheng <waketzheng@gmail.com>"]
6
16
  readme = "README.md"
@@ -12,7 +22,7 @@ click = ">=7.1.1" # Many package depends on click, so only limit min version
12
22
  strenum = {version = ">=0.4.15", python = "<3.11"}
13
23
  type-extensions = {version = ">=0.1.2", python = "<3.11"}
14
24
  coverage = {version = ">=6.5.0", optional = true}
15
- ruff = {version = "^0.4.2", optional = true}
25
+ ruff = {version = "^0.4.4", optional = true}
16
26
  mypy = {version = "^1.10.0", optional = true}
17
27
  bumpversion = {version = "^0.6.0", optional = true}
18
28
  pytest = {version = "^8.2.0", optional = true}
@@ -25,7 +35,7 @@ all = ["ruff", "typer", "mypy", "bumpversion", "pytest", "coverage"]
25
35
  coveralls = {version = ">=4.0.0", python = ">=3.10,<3.13"}
26
36
  coverage = ">=6.5.0" # use >= to compare with coveralls
27
37
  typer = "^0.12.3"
28
- ruff = "^0.4.2"
38
+ ruff = "^0.4.4"
29
39
  mypy = "^1.10.0"
30
40
  pytest = "^8.2.0"
31
41
  ipython = "^8.24.0"
@@ -37,14 +47,11 @@ strenum = "^0.4.15"
37
47
  [build-system]
38
48
  requires = ["poetry-core"]
39
49
  build-backend = "poetry.core.masonry.api"
50
+ """
40
51
 
41
- [tool.poetry.scripts]
42
- fast = "fast_dev_cli:cli.cli"
43
-
44
- [tool.mypy]
45
- pretty = true
46
- ignore_missing_imports = true
47
- check_untyped_defs = true
48
52
 
49
- [tool.ruff.lint.per-file-ignores]
50
- "test_*.py" = ["E501"]
53
+ @pytest.fixture
54
+ def tmp_poetry_project(tmp_path: Path):
55
+ with chdir(tmp_path):
56
+ tmp_path.joinpath(TOML_FILE).write_text(TOML_CONTENT)
57
+ yield