fast-dev-cli 0.16.2__tar.gz → 0.17.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.
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/PKG-INFO +1 -1
- fast_dev_cli-0.17.1/fast_dev_cli/__init__.py +1 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/fast_dev_cli/cli.py +255 -81
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/pyproject.toml +23 -24
- fast_dev_cli-0.17.1/scripts/deps.py +156 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/conftest.py +5 -3
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_bump.py +53 -56
- fast_dev_cli-0.17.1/tests/test_exec.py +41 -0
- fast_dev_cli-0.17.1/tests/test_help.py +7 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_lint.py +0 -8
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_poetry_version_plugin.py +21 -36
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_tag.py +14 -6
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_upgrade.py +2 -2
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_version.py +20 -2
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/utils.py +13 -21
- fast_dev_cli-0.16.2/fast_dev_cli/__init__.py +0 -1
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/LICENSE +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/README.md +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/fast_dev_cli/__main__.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/fast_dev_cli/py.typed +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/pdm_build.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/scripts/check.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/scripts/format.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/scripts/test.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/__init__.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_fast_test.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_functions.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_runserver.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_sync.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.1}/tests/test_upload.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.17.1"
|
|
@@ -10,29 +10,20 @@ import sys
|
|
|
10
10
|
from functools import cached_property
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import (
|
|
13
|
+
TYPE_CHECKING,
|
|
13
14
|
Any,
|
|
14
15
|
Literal,
|
|
15
16
|
# Optional is required by Option generated by typer
|
|
16
17
|
Optional,
|
|
17
18
|
cast,
|
|
18
19
|
get_args,
|
|
20
|
+
overload,
|
|
19
21
|
)
|
|
20
22
|
|
|
21
23
|
import typer
|
|
22
24
|
from typer import Exit, Option, echo, secho
|
|
23
25
|
from typer.models import ArgumentInfo, OptionInfo
|
|
24
26
|
|
|
25
|
-
try:
|
|
26
|
-
from emoji import is_emoji
|
|
27
|
-
except ImportError:
|
|
28
|
-
|
|
29
|
-
def is_emoji(char: str) -> bool: # type:ignore[misc]
|
|
30
|
-
return (
|
|
31
|
-
not re.match(r"[\w\d\s]", char)
|
|
32
|
-
and not "\u4e00" <= char <= "\u9fff" # Chinese
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
|
|
36
27
|
try:
|
|
37
28
|
from . import __version__
|
|
38
29
|
except ImportError: # pragma: no cover
|
|
@@ -42,20 +33,24 @@ except ImportError: # pragma: no cover
|
|
|
42
33
|
|
|
43
34
|
if sys.version_info >= (3, 11): # pragma: no cover
|
|
44
35
|
from enum import StrEnum
|
|
45
|
-
from typing import Self
|
|
46
36
|
|
|
47
37
|
import tomllib
|
|
48
38
|
else: # pragma: no cover
|
|
49
39
|
from enum import Enum
|
|
50
40
|
|
|
51
41
|
import tomli as tomllib
|
|
52
|
-
from typing_extensions import Self
|
|
53
42
|
|
|
54
43
|
class StrEnum(str, Enum):
|
|
55
44
|
__str__ = str.__str__
|
|
56
45
|
|
|
57
46
|
|
|
58
|
-
|
|
47
|
+
if TYPE_CHECKING:
|
|
48
|
+
if sys.version_info >= (3, 11):
|
|
49
|
+
from typing import Self
|
|
50
|
+
else:
|
|
51
|
+
from typing_extensions import Self
|
|
52
|
+
|
|
53
|
+
cli = typer.Typer(no_args_is_help=True)
|
|
59
54
|
DryOption = Option(False, "--dry", help="Only print, not really run shell command")
|
|
60
55
|
TOML_FILE = "pyproject.toml"
|
|
61
56
|
ToolName = Literal["poetry", "pdm", "uv"]
|
|
@@ -64,7 +59,10 @@ ToolOption = Option(
|
|
|
64
59
|
)
|
|
65
60
|
|
|
66
61
|
|
|
67
|
-
class
|
|
62
|
+
class FastDevCliError(Exception): ...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ShellCommandError(FastDevCliError): ...
|
|
68
66
|
|
|
69
67
|
|
|
70
68
|
def poetry_module_name(name: str) -> str:
|
|
@@ -79,6 +77,18 @@ def poetry_module_name(name: str) -> str:
|
|
|
79
77
|
return canonicalize_name(name).replace("-", "_").replace(" ", "_")
|
|
80
78
|
|
|
81
79
|
|
|
80
|
+
def is_emoji(char: str) -> bool:
|
|
81
|
+
try:
|
|
82
|
+
import emoji
|
|
83
|
+
except ImportError:
|
|
84
|
+
pass
|
|
85
|
+
else:
|
|
86
|
+
return emoji.is_emoji(char)
|
|
87
|
+
if re.match(r"[\w\d\s]", char):
|
|
88
|
+
return False
|
|
89
|
+
return not "\u4e00" <= char <= "\u9fff" # Chinese character
|
|
90
|
+
|
|
91
|
+
|
|
82
92
|
def load_bool(name: str, default: bool = False) -> bool:
|
|
83
93
|
if not (v := os.getenv(name)):
|
|
84
94
|
return default
|
|
@@ -111,7 +121,10 @@ def run_and_echo(
|
|
|
111
121
|
echo(f"--> {cmd}")
|
|
112
122
|
if dry:
|
|
113
123
|
return 0
|
|
114
|
-
|
|
124
|
+
command: list[str] | str = cmd
|
|
125
|
+
if "shell" not in kw and not (set(cmd) & {"|", ">", "&"}):
|
|
126
|
+
command = shlex.split(cmd)
|
|
127
|
+
return _run_shell(command, **kw).returncode
|
|
115
128
|
|
|
116
129
|
|
|
117
130
|
def check_call(cmd: str) -> bool:
|
|
@@ -148,9 +161,9 @@ def read_version_from_file(
|
|
|
148
161
|
toml_text = Project.load_toml_text()
|
|
149
162
|
context = tomllib.loads(toml_text)
|
|
150
163
|
with contextlib.suppress(KeyError):
|
|
151
|
-
return context["project"]["version"]
|
|
164
|
+
return cast(str, context["project"]["version"])
|
|
152
165
|
with contextlib.suppress(KeyError): # Poetry V1
|
|
153
|
-
return context["tool"]["poetry"]["version"]
|
|
166
|
+
return cast(str, context["tool"]["poetry"]["version"])
|
|
154
167
|
secho(f"WARNING: can not find 'version' item in {version_file}!")
|
|
155
168
|
return "0.0.0"
|
|
156
169
|
pattern = re.compile(r"__version__\s*=")
|
|
@@ -177,33 +190,61 @@ def read_version_from_file(
|
|
|
177
190
|
return "0.0.0"
|
|
178
191
|
|
|
179
192
|
|
|
193
|
+
@overload
|
|
180
194
|
def get_current_version(
|
|
181
195
|
verbose: bool = False,
|
|
182
196
|
is_poetry: bool | None = None,
|
|
183
197
|
package_name: str | None = None,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if not is_poetry:
|
|
188
|
-
work_dir = None
|
|
189
|
-
if package_name is None:
|
|
190
|
-
work_dir = Project.get_work_dir()
|
|
191
|
-
package_name = re.sub(r"[- ]", "_", work_dir.name)
|
|
192
|
-
try:
|
|
193
|
-
installed_version = importlib_metadata.version(package_name)
|
|
194
|
-
except importlib_metadata.PackageNotFoundError:
|
|
195
|
-
...
|
|
196
|
-
else:
|
|
197
|
-
if installed_version != "0.0.0":
|
|
198
|
-
return installed_version
|
|
199
|
-
return read_version_from_file(package_name, work_dir)
|
|
198
|
+
*,
|
|
199
|
+
check_version: Literal[False] = False,
|
|
200
|
+
) -> str: ...
|
|
200
201
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
202
|
+
|
|
203
|
+
@overload
|
|
204
|
+
def get_current_version(
|
|
205
|
+
verbose: bool = False,
|
|
206
|
+
is_poetry: bool | None = None,
|
|
207
|
+
package_name: str | None = None,
|
|
208
|
+
*,
|
|
209
|
+
check_version: Literal[True] = True,
|
|
210
|
+
) -> tuple[bool, str]: ...
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def get_current_version(
|
|
214
|
+
verbose: bool = False,
|
|
215
|
+
is_poetry: bool | None = None,
|
|
216
|
+
package_name: str | None = None,
|
|
217
|
+
*,
|
|
218
|
+
check_version: bool = False,
|
|
219
|
+
) -> str | tuple[bool, str]:
|
|
220
|
+
if is_poetry is True or Project.manage_by_poetry():
|
|
221
|
+
cmd = ["poetry", "version", "-s"]
|
|
222
|
+
if verbose:
|
|
223
|
+
echo(f"--> {' '.join(cmd)}")
|
|
224
|
+
if out := capture_cmd_output(cmd, raises=True):
|
|
225
|
+
out = out.splitlines()[-1].strip().split()[-1]
|
|
226
|
+
if check_version:
|
|
227
|
+
return True, out
|
|
228
|
+
return out
|
|
229
|
+
toml_text = work_dir = None
|
|
230
|
+
if package_name is None:
|
|
231
|
+
work_dir = Project.get_work_dir()
|
|
232
|
+
toml_text = Project.load_toml_text()
|
|
233
|
+
doc = tomllib.loads(toml_text)
|
|
234
|
+
project_name = doc.get("project", {}).get("name", work_dir.name)
|
|
235
|
+
package_name = re.sub(r"[- ]", "_", project_name)
|
|
236
|
+
local_version = read_version_from_file(package_name, work_dir, toml_text)
|
|
237
|
+
try:
|
|
238
|
+
installed_version = importlib_metadata.version(package_name)
|
|
239
|
+
except importlib_metadata.PackageNotFoundError:
|
|
240
|
+
installed_version = ""
|
|
241
|
+
current_version = local_version or installed_version
|
|
242
|
+
if not current_version:
|
|
243
|
+
raise FastDevCliError(f"Failed to get current version of {package_name!r}")
|
|
244
|
+
if check_version:
|
|
245
|
+
is_conflict = bool(local_version) and local_version != installed_version
|
|
246
|
+
return is_conflict, current_version
|
|
247
|
+
return current_version
|
|
207
248
|
|
|
208
249
|
|
|
209
250
|
def _ensure_bool(value: bool | OptionInfo) -> bool:
|
|
@@ -239,14 +280,14 @@ def exit_if_run_failed(
|
|
|
239
280
|
|
|
240
281
|
|
|
241
282
|
class DryRun:
|
|
242
|
-
def __init__(self
|
|
283
|
+
def __init__(self, _exit: bool = False, dry: bool = False) -> None:
|
|
243
284
|
self.dry = dry
|
|
244
285
|
self._exit = _exit
|
|
245
286
|
|
|
246
|
-
def gen(self
|
|
287
|
+
def gen(self) -> str:
|
|
247
288
|
raise NotImplementedError
|
|
248
289
|
|
|
249
|
-
def run(self
|
|
290
|
+
def run(self) -> None:
|
|
250
291
|
exit_if_run_failed(self.gen(), _exit=self._exit, dry=self.dry)
|
|
251
292
|
|
|
252
293
|
|
|
@@ -257,17 +298,21 @@ class BumpUp(DryRun):
|
|
|
257
298
|
major = "major"
|
|
258
299
|
|
|
259
300
|
def __init__(
|
|
260
|
-
self
|
|
301
|
+
self,
|
|
261
302
|
commit: bool,
|
|
262
303
|
part: str,
|
|
263
304
|
filename: str | None = None,
|
|
264
305
|
dry: bool = False,
|
|
306
|
+
no_sync: bool = False,
|
|
307
|
+
emoji: bool | None = None,
|
|
265
308
|
) -> None:
|
|
266
309
|
self.commit = commit
|
|
267
310
|
self.part = part
|
|
268
311
|
if filename is None:
|
|
269
312
|
filename = self.parse_filename()
|
|
270
313
|
self.filename = filename
|
|
314
|
+
self._no_sync = no_sync
|
|
315
|
+
self._emoji = emoji
|
|
271
316
|
super().__init__(dry=dry)
|
|
272
317
|
|
|
273
318
|
@staticmethod
|
|
@@ -317,7 +362,9 @@ class BumpUp(DryRun):
|
|
|
317
362
|
work_dir = Project.get_work_dir()
|
|
318
363
|
for tool in ("pdm", "hatch"):
|
|
319
364
|
with contextlib.suppress(KeyError):
|
|
320
|
-
version_path =
|
|
365
|
+
version_path = cast(
|
|
366
|
+
str, context["tool"][tool]["version"]["path"]
|
|
367
|
+
)
|
|
321
368
|
if (
|
|
322
369
|
Path(version_path).exists()
|
|
323
370
|
or work_dir.joinpath(version_path).exists()
|
|
@@ -390,8 +437,8 @@ class BumpUp(DryRun):
|
|
|
390
437
|
echo(f"Invalid part: {s!r}")
|
|
391
438
|
raise Exit(1) from e
|
|
392
439
|
|
|
393
|
-
def gen(self
|
|
394
|
-
_version = get_current_version()
|
|
440
|
+
def gen(self) -> str:
|
|
441
|
+
should_sync, _version = get_current_version(check_version=True)
|
|
395
442
|
filename = self.filename
|
|
396
443
|
echo(f"Current version(@{filename}): {_version}")
|
|
397
444
|
if self.part:
|
|
@@ -407,17 +454,17 @@ class BumpUp(DryRun):
|
|
|
407
454
|
if part != "patch":
|
|
408
455
|
cmd += " --tag"
|
|
409
456
|
cmd += " --commit"
|
|
410
|
-
if self.should_add_emoji():
|
|
457
|
+
if self._emoji or (self._emoji is None and self.should_add_emoji()):
|
|
411
458
|
cmd += " --message-emoji=1"
|
|
412
459
|
if not load_bool("DONT_GIT_PUSH"):
|
|
413
460
|
cmd += " && git push && git push --tags && git log -1"
|
|
414
461
|
else:
|
|
415
462
|
cmd += " --allow-dirty"
|
|
416
|
-
if Project.
|
|
417
|
-
cmd = "
|
|
463
|
+
if should_sync and not self._no_sync and (sync := Project.get_sync_command()):
|
|
464
|
+
cmd = f"{sync} && " + cmd
|
|
418
465
|
return cmd
|
|
419
466
|
|
|
420
|
-
def run(self
|
|
467
|
+
def run(self) -> None:
|
|
421
468
|
super().run()
|
|
422
469
|
if not self.commit and not self.dry:
|
|
423
470
|
new_version = get_current_version(True)
|
|
@@ -429,7 +476,19 @@ class BumpUp(DryRun):
|
|
|
429
476
|
@cli.command()
|
|
430
477
|
def version() -> None:
|
|
431
478
|
"""Show the version of this tool"""
|
|
432
|
-
echo(
|
|
479
|
+
echo("Fast Dev Cli Version: " + typer.style(__version__, fg=typer.colors.BLUE))
|
|
480
|
+
with contextlib.suppress(FileNotFoundError, KeyError):
|
|
481
|
+
toml_text = Project.load_toml_text()
|
|
482
|
+
doc = tomllib.loads(toml_text)
|
|
483
|
+
version_file = doc["tool"]["pdm"]["version"]["path"]
|
|
484
|
+
text = Project.get_work_dir().joinpath(version_file).read_text()
|
|
485
|
+
varname = "__version__"
|
|
486
|
+
for line in text.splitlines():
|
|
487
|
+
if line.strip().startswith(varname):
|
|
488
|
+
value = line.split("=", 1)[-1].strip().strip('"').strip("'")
|
|
489
|
+
styled = typer.style(value, bold=True)
|
|
490
|
+
echo(f"Version value in {version_file}: " + styled)
|
|
491
|
+
break
|
|
433
492
|
|
|
434
493
|
|
|
435
494
|
@cli.command(name="bump")
|
|
@@ -438,10 +497,24 @@ def bump_version(
|
|
|
438
497
|
commit: bool = Option(
|
|
439
498
|
False, "--commit", "-c", help="Whether run `git commit` after version changed"
|
|
440
499
|
),
|
|
500
|
+
emoji: Optional[bool] = Option(
|
|
501
|
+
None, "--emoji", help="Whether add emoji prefix to commit message"
|
|
502
|
+
),
|
|
503
|
+
no_sync: bool = Option(
|
|
504
|
+
False, "--no-sync", help="Do not run sync command to update version"
|
|
505
|
+
),
|
|
441
506
|
dry: bool = DryOption,
|
|
442
507
|
) -> None:
|
|
443
508
|
"""Bump up version string in pyproject.toml"""
|
|
444
|
-
|
|
509
|
+
if emoji is not None:
|
|
510
|
+
emoji = _ensure_bool(emoji)
|
|
511
|
+
return BumpUp(
|
|
512
|
+
_ensure_bool(commit),
|
|
513
|
+
getattr(part, "value", part),
|
|
514
|
+
no_sync=_ensure_bool(no_sync),
|
|
515
|
+
emoji=emoji,
|
|
516
|
+
dry=dry,
|
|
517
|
+
).run()
|
|
445
518
|
|
|
446
519
|
|
|
447
520
|
def bump() -> None:
|
|
@@ -453,7 +526,7 @@ def bump() -> None:
|
|
|
453
526
|
if not a.startswith("-"):
|
|
454
527
|
part = a
|
|
455
528
|
break
|
|
456
|
-
return BumpUp(commit, part, dry="--dry" in args).run()
|
|
529
|
+
return BumpUp(commit, part, no_sync="--no-sync" in args, dry="--dry" in args).run()
|
|
457
530
|
|
|
458
531
|
|
|
459
532
|
class EnvError(Exception):
|
|
@@ -462,6 +535,7 @@ class EnvError(Exception):
|
|
|
462
535
|
|
|
463
536
|
class Project:
|
|
464
537
|
path_depth = 5
|
|
538
|
+
_tool: ToolName | None = None
|
|
465
539
|
|
|
466
540
|
@staticmethod
|
|
467
541
|
def is_poetry_v2(text: str) -> bool:
|
|
@@ -492,7 +566,7 @@ class Project:
|
|
|
492
566
|
return d
|
|
493
567
|
if allow_cwd:
|
|
494
568
|
return cls.get_root_dir(cwd)
|
|
495
|
-
raise EnvError(f"{name} not found! Make sure this is a
|
|
569
|
+
raise EnvError(f"{name} not found! Make sure this is a python project.")
|
|
496
570
|
|
|
497
571
|
@classmethod
|
|
498
572
|
def load_toml_text(cls: type[Self], name: str = TOML_FILE) -> str:
|
|
@@ -500,22 +574,40 @@ class Project:
|
|
|
500
574
|
return toml_file.read_text("utf8")
|
|
501
575
|
|
|
502
576
|
@classmethod
|
|
503
|
-
def manage_by_poetry(cls: type[Self]) -> bool:
|
|
504
|
-
return cls.get_manage_tool() == "poetry"
|
|
577
|
+
def manage_by_poetry(cls: type[Self], cache: bool = False) -> bool:
|
|
578
|
+
return cls.get_manage_tool(cache=cache) == "poetry"
|
|
505
579
|
|
|
506
580
|
@classmethod
|
|
507
|
-
def get_manage_tool(cls: type[Self]) -> ToolName | None:
|
|
581
|
+
def get_manage_tool(cls: type[Self], cache: bool = False) -> ToolName | None:
|
|
582
|
+
if cache and cls._tool:
|
|
583
|
+
return cls._tool
|
|
508
584
|
try:
|
|
509
585
|
text = cls.load_toml_text()
|
|
510
586
|
except EnvError:
|
|
511
587
|
pass
|
|
512
588
|
else:
|
|
589
|
+
with contextlib.suppress(KeyError, tomllib.TOMLDecodeError):
|
|
590
|
+
doc = tomllib.loads(text)
|
|
591
|
+
backend = doc["build-system"]["build-backend"]
|
|
592
|
+
if "poetry" in backend:
|
|
593
|
+
cls._tool = "poetry"
|
|
594
|
+
return cls._tool
|
|
595
|
+
elif "pdm" in backend:
|
|
596
|
+
cls._tool = "pdm"
|
|
597
|
+
work_dir = cls.get_work_dir(allow_cwd=True)
|
|
598
|
+
if not Path(work_dir, "pdm.lock").exists() and (
|
|
599
|
+
"[tool.uv]" in text or Path(work_dir, "uv.lock").exists()
|
|
600
|
+
):
|
|
601
|
+
cls._tool = "uv"
|
|
602
|
+
return cls._tool
|
|
513
603
|
for name in get_args(ToolName):
|
|
514
604
|
if f"[tool.{name}]" in text:
|
|
515
|
-
|
|
605
|
+
cls._tool = cast(ToolName, name)
|
|
606
|
+
return cls._tool
|
|
516
607
|
# Poetry 2.0 default to not include the '[tool.poetry]' section
|
|
517
608
|
if cls.is_poetry_v2(text):
|
|
518
|
-
|
|
609
|
+
cls._tool = "poetry"
|
|
610
|
+
return cls._tool
|
|
519
611
|
return None
|
|
520
612
|
|
|
521
613
|
@staticmethod
|
|
@@ -531,14 +623,40 @@ class Project:
|
|
|
531
623
|
return root
|
|
532
624
|
|
|
533
625
|
@classmethod
|
|
534
|
-
def is_pdm_project(cls, strict: bool = True) -> bool:
|
|
535
|
-
if cls.get_manage_tool() != "pdm":
|
|
626
|
+
def is_pdm_project(cls, strict: bool = True, cache: bool = False) -> bool:
|
|
627
|
+
if cls.get_manage_tool(cache=cache) != "pdm":
|
|
536
628
|
return False
|
|
537
629
|
if strict:
|
|
538
630
|
lock_file = cls.get_work_dir() / "pdm.lock"
|
|
539
631
|
return lock_file.exists()
|
|
540
632
|
return True
|
|
541
633
|
|
|
634
|
+
@classmethod
|
|
635
|
+
def get_sync_command(cls, prod: bool = True, doc: dict | None = None) -> str:
|
|
636
|
+
if cls.is_pdm_project():
|
|
637
|
+
return "pdm sync" + " --prod" * prod
|
|
638
|
+
elif cls.manage_by_poetry(cache=True):
|
|
639
|
+
cmd = "poetry install"
|
|
640
|
+
if prod:
|
|
641
|
+
if doc is None:
|
|
642
|
+
doc = tomllib.loads(cls.load_toml_text())
|
|
643
|
+
if doc.get("project", {}).get("dependencies") or any(
|
|
644
|
+
i != "python"
|
|
645
|
+
for i in doc.get("tool", {})
|
|
646
|
+
.get("poetry", {})
|
|
647
|
+
.get("dependencies", [])
|
|
648
|
+
):
|
|
649
|
+
cmd += " --only=main"
|
|
650
|
+
return cmd
|
|
651
|
+
elif cls.get_manage_tool(cache=True) == "uv":
|
|
652
|
+
return "uv sync --inexact" + " --no-dev" * prod
|
|
653
|
+
return ""
|
|
654
|
+
|
|
655
|
+
@classmethod
|
|
656
|
+
def sync_dependencies(cls, prod: bool = True) -> None:
|
|
657
|
+
if cmd := cls.get_sync_command():
|
|
658
|
+
run_and_echo(cmd)
|
|
659
|
+
|
|
542
660
|
|
|
543
661
|
class ParseError(Exception):
|
|
544
662
|
"""Raise this if parse dependence line error"""
|
|
@@ -548,7 +666,7 @@ class ParseError(Exception):
|
|
|
548
666
|
|
|
549
667
|
class UpgradeDependencies(Project, DryRun):
|
|
550
668
|
def __init__(
|
|
551
|
-
self
|
|
669
|
+
self, _exit: bool = False, dry: bool = False, tool: ToolName = "poetry"
|
|
552
670
|
) -> None:
|
|
553
671
|
super().__init__(_exit, dry)
|
|
554
672
|
self._tool = tool
|
|
@@ -713,11 +831,13 @@ class UpgradeDependencies(Project, DryRun):
|
|
|
713
831
|
_upgrade += f" && poetry add {' '.join(single)}"
|
|
714
832
|
return _upgrade
|
|
715
833
|
|
|
716
|
-
def gen(self
|
|
834
|
+
def gen(self) -> str:
|
|
717
835
|
if self._tool == "uv":
|
|
718
|
-
|
|
836
|
+
up = "uv lock --upgrade --verbose"
|
|
837
|
+
deps = "uv sync --inexact --frozen --all-groups --all-extras"
|
|
838
|
+
return f"{up} && {deps}"
|
|
719
839
|
elif self._tool == "pdm":
|
|
720
|
-
return "pdm update --verbose && pdm sync -G :all"
|
|
840
|
+
return "pdm update --verbose && pdm sync -G :all --frozen"
|
|
721
841
|
return self.gen_cmd() + " && poetry lock && poetry update"
|
|
722
842
|
|
|
723
843
|
|
|
@@ -737,34 +857,35 @@ def upgrade(
|
|
|
737
857
|
|
|
738
858
|
|
|
739
859
|
class GitTag(DryRun):
|
|
740
|
-
def __init__(self
|
|
860
|
+
def __init__(self, message: str, dry: bool, no_sync: bool = False) -> None:
|
|
741
861
|
self.message = message
|
|
862
|
+
self._no_sync = no_sync
|
|
742
863
|
super().__init__(dry=dry)
|
|
743
864
|
|
|
744
865
|
@staticmethod
|
|
745
866
|
def has_v_prefix() -> bool:
|
|
746
867
|
return "v" in capture_cmd_output("git tag")
|
|
747
868
|
|
|
748
|
-
def should_push(self
|
|
869
|
+
def should_push(self) -> bool:
|
|
749
870
|
return "git push" in self.git_status
|
|
750
871
|
|
|
751
|
-
def gen(self
|
|
752
|
-
_version = get_current_version(verbose=False)
|
|
872
|
+
def gen(self) -> str:
|
|
873
|
+
should_sync, _version = get_current_version(verbose=False, check_version=True)
|
|
753
874
|
if self.has_v_prefix():
|
|
754
875
|
# Add `v` at prefix to compare with bumpversion tool
|
|
755
876
|
_version = "v" + _version
|
|
756
877
|
cmd = f"git tag -a {_version} -m {self.message!r} && git push --tags"
|
|
757
878
|
if self.should_push():
|
|
758
879
|
cmd += " && git push"
|
|
759
|
-
if Project.
|
|
760
|
-
cmd = "
|
|
880
|
+
if should_sync and not self._no_sync and (sync := Project.get_sync_command()):
|
|
881
|
+
cmd = f"{sync} && " + cmd
|
|
761
882
|
return cmd
|
|
762
883
|
|
|
763
884
|
@cached_property
|
|
764
|
-
def git_status(self
|
|
885
|
+
def git_status(self) -> str:
|
|
765
886
|
return capture_cmd_output("git status")
|
|
766
887
|
|
|
767
|
-
def mark_tag(self
|
|
888
|
+
def mark_tag(self) -> bool:
|
|
768
889
|
if not re.search(r"working (tree|directory) clean", self.git_status) and (
|
|
769
890
|
"无文件要提交,干净的工作区" not in self.git_status
|
|
770
891
|
):
|
|
@@ -773,7 +894,7 @@ class GitTag(DryRun):
|
|
|
773
894
|
return False
|
|
774
895
|
return bool(super().run())
|
|
775
896
|
|
|
776
|
-
def run(self
|
|
897
|
+
def run(self) -> None:
|
|
777
898
|
if self.mark_tag() and not self.dry:
|
|
778
899
|
echo("You may want to publish package:\n poetry publish --build")
|
|
779
900
|
|
|
@@ -781,15 +902,18 @@ class GitTag(DryRun):
|
|
|
781
902
|
@cli.command()
|
|
782
903
|
def tag(
|
|
783
904
|
message: str = Option("", "-m", "--message"),
|
|
905
|
+
no_sync: bool = Option(
|
|
906
|
+
False, "--no-sync", help="Do not run sync command to update version"
|
|
907
|
+
),
|
|
784
908
|
dry: bool = DryOption,
|
|
785
909
|
) -> None:
|
|
786
910
|
"""Run shell command: git tag -a <current-version-in-pyproject.toml> -m {message}"""
|
|
787
|
-
GitTag(message, dry=dry).run()
|
|
911
|
+
GitTag(message, dry=dry, no_sync=_ensure_bool(no_sync)).run()
|
|
788
912
|
|
|
789
913
|
|
|
790
914
|
class LintCode(DryRun):
|
|
791
915
|
def __init__(
|
|
792
|
-
self
|
|
916
|
+
self,
|
|
793
917
|
args: list[str] | str | None,
|
|
794
918
|
check_only: bool = False,
|
|
795
919
|
_exit: bool = False,
|
|
@@ -855,10 +979,21 @@ class LintCode(DryRun):
|
|
|
855
979
|
lint_them = " && ".join(
|
|
856
980
|
"{0}{" + str(i) + "} {1}" for i in range(2, len(tools) + 2)
|
|
857
981
|
)
|
|
982
|
+
if ruff_exists := cls.check_lint_tool_installed():
|
|
983
|
+
# `ruff <command>` get the same result with `pdm run ruff <command>`
|
|
984
|
+
# While `mypy .`(installed global and env not activated),
|
|
985
|
+
# does not the same as `pdm run mypy .`
|
|
986
|
+
lint_them = " && ".join(
|
|
987
|
+
("" if tool.startswith("ruff") else "{0}")
|
|
988
|
+
+ (
|
|
989
|
+
"{%d} {1}" % i # noqa: UP031
|
|
990
|
+
)
|
|
991
|
+
for i, tool in enumerate(tools, 2)
|
|
992
|
+
)
|
|
858
993
|
prefix = ""
|
|
859
994
|
should_run_by_tool = False
|
|
860
995
|
if is_venv() and Path(sys.argv[0]).parent != Path.home().joinpath(".local/bin"):
|
|
861
|
-
if not
|
|
996
|
+
if not ruff_exists:
|
|
862
997
|
should_run_by_tool = True
|
|
863
998
|
if check_call('python -c "import fast_dev_cli"'):
|
|
864
999
|
command = 'python -m pip install -U "fast-dev-cli"'
|
|
@@ -870,7 +1005,11 @@ class LintCode(DryRun):
|
|
|
870
1005
|
if tool == ToolOption.default:
|
|
871
1006
|
tool = Project.get_manage_tool() or ""
|
|
872
1007
|
if tool:
|
|
873
|
-
prefix =
|
|
1008
|
+
prefix = (
|
|
1009
|
+
str(bin_dir)
|
|
1010
|
+
if tool == "uv" and (bin_dir := Path(".venv/bin/")).exists()
|
|
1011
|
+
else (tool + " run ")
|
|
1012
|
+
)
|
|
874
1013
|
if cls.prefer_dmypy(paths, tools, use_dmypy=use_dmypy):
|
|
875
1014
|
tools[-1] = "dmypy run"
|
|
876
1015
|
cmd += lint_them.format(prefix, paths, *tools)
|
|
@@ -886,7 +1025,7 @@ class LintCode(DryRun):
|
|
|
886
1025
|
cmd += " && " + command
|
|
887
1026
|
return cmd
|
|
888
1027
|
|
|
889
|
-
def gen(self
|
|
1028
|
+
def gen(self) -> str:
|
|
890
1029
|
if isinstance(args := self.args, str):
|
|
891
1030
|
args = args.split()
|
|
892
1031
|
paths = " ".join(map(str, args)) if args else "."
|
|
@@ -975,7 +1114,7 @@ def only_check(
|
|
|
975
1114
|
|
|
976
1115
|
class Sync(DryRun):
|
|
977
1116
|
def __init__(
|
|
978
|
-
self
|
|
1117
|
+
self, filename: str, extras: str, save: bool, dry: bool = False
|
|
979
1118
|
) -> None:
|
|
980
1119
|
self.filename = filename
|
|
981
1120
|
self.extras = extras
|
|
@@ -1124,6 +1263,41 @@ def runserver(
|
|
|
1124
1263
|
dev(port, host, dry=dry)
|
|
1125
1264
|
|
|
1126
1265
|
|
|
1266
|
+
@cli.command(name="exec")
|
|
1267
|
+
def run_by_subprocess(cmd: str, dry: bool = DryOption) -> None:
|
|
1268
|
+
"""Run cmd by subprocess, auto set shell=True when cmd contains '|>'"""
|
|
1269
|
+
try:
|
|
1270
|
+
rc = run_and_echo(cmd, verbose=True, dry=_ensure_bool(dry))
|
|
1271
|
+
except FileNotFoundError as e:
|
|
1272
|
+
if e.filename == cmd.split()[0]:
|
|
1273
|
+
echo(f"Command not found: {e.filename}")
|
|
1274
|
+
raise Exit(1) from None
|
|
1275
|
+
raise e
|
|
1276
|
+
else:
|
|
1277
|
+
if rc:
|
|
1278
|
+
raise Exit(rc)
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
def version_callback(value: bool) -> None:
|
|
1282
|
+
if value:
|
|
1283
|
+
echo("Fast Dev Cli Version: " + typer.style(__version__, bold=True))
|
|
1284
|
+
raise Exit()
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
@cli.callback()
|
|
1288
|
+
def common(
|
|
1289
|
+
version: bool = Option(
|
|
1290
|
+
None,
|
|
1291
|
+
"--version",
|
|
1292
|
+
"-V",
|
|
1293
|
+
callback=version_callback,
|
|
1294
|
+
is_eager=True,
|
|
1295
|
+
help="Show the version of this tool",
|
|
1296
|
+
),
|
|
1297
|
+
) -> None:
|
|
1298
|
+
pass
|
|
1299
|
+
|
|
1300
|
+
|
|
1127
1301
|
def main() -> None:
|
|
1128
1302
|
cli()
|
|
1129
1303
|
|