fast-dev-cli 0.17.0__tar.gz → 0.17.2__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.17.0 → fast_dev_cli-0.17.2}/PKG-INFO +1 -1
- fast_dev_cli-0.17.2/fast_dev_cli/__init__.py +1 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/fast_dev_cli/cli.py +199 -67
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/pyproject.toml +5 -2
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/conftest.py +5 -3
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_bump.py +21 -8
- fast_dev_cli-0.17.2/tests/test_help.py +7 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_lint.py +9 -9
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_poetry_version_plugin.py +21 -36
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_tag.py +14 -6
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_upgrade.py +2 -2
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_version.py +20 -2
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/utils.py +13 -21
- fast_dev_cli-0.17.0/fast_dev_cli/__init__.py +0 -1
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/LICENSE +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/README.md +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/fast_dev_cli/__main__.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/fast_dev_cli/py.typed +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/pdm_build.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/scripts/check.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/scripts/deps.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/scripts/format.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/scripts/test.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/__init__.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_exec.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_fast_test.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_functions.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_runserver.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_sync.py +0 -0
- {fast_dev_cli-0.17.0 → fast_dev_cli-0.17.2}/tests/test_upload.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.17.2"
|
|
@@ -10,6 +10,7 @@ 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
|
|
@@ -23,17 +24,6 @@ import typer
|
|
|
23
24
|
from typer import Exit, Option, echo, secho
|
|
24
25
|
from typer.models import ArgumentInfo, OptionInfo
|
|
25
26
|
|
|
26
|
-
try:
|
|
27
|
-
from emoji import is_emoji
|
|
28
|
-
except ImportError:
|
|
29
|
-
|
|
30
|
-
def is_emoji(char: str) -> bool: # type:ignore[misc]
|
|
31
|
-
return (
|
|
32
|
-
not re.match(r"[\w\d\s]", char)
|
|
33
|
-
and not "\u4e00" <= char <= "\u9fff" # Chinese
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
27
|
try:
|
|
38
28
|
from . import __version__
|
|
39
29
|
except ImportError: # pragma: no cover
|
|
@@ -43,20 +33,24 @@ except ImportError: # pragma: no cover
|
|
|
43
33
|
|
|
44
34
|
if sys.version_info >= (3, 11): # pragma: no cover
|
|
45
35
|
from enum import StrEnum
|
|
46
|
-
from typing import Self
|
|
47
36
|
|
|
48
37
|
import tomllib
|
|
49
38
|
else: # pragma: no cover
|
|
50
39
|
from enum import Enum
|
|
51
40
|
|
|
52
41
|
import tomli as tomllib
|
|
53
|
-
from typing_extensions import Self
|
|
54
42
|
|
|
55
43
|
class StrEnum(str, Enum):
|
|
56
44
|
__str__ = str.__str__
|
|
57
45
|
|
|
58
46
|
|
|
59
|
-
|
|
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)
|
|
60
54
|
DryOption = Option(False, "--dry", help="Only print, not really run shell command")
|
|
61
55
|
TOML_FILE = "pyproject.toml"
|
|
62
56
|
ToolName = Literal["poetry", "pdm", "uv"]
|
|
@@ -83,6 +77,22 @@ def poetry_module_name(name: str) -> str:
|
|
|
83
77
|
return canonicalize_name(name).replace("-", "_").replace(" ", "_")
|
|
84
78
|
|
|
85
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
|
+
|
|
92
|
+
def yellow_warn(msg: str) -> None:
|
|
93
|
+
secho(msg, fg="yellow")
|
|
94
|
+
|
|
95
|
+
|
|
86
96
|
def load_bool(name: str, default: bool = False) -> bool:
|
|
87
97
|
if not (v := os.getenv(name)):
|
|
88
98
|
return default
|
|
@@ -116,7 +126,7 @@ def run_and_echo(
|
|
|
116
126
|
if dry:
|
|
117
127
|
return 0
|
|
118
128
|
command: list[str] | str = cmd
|
|
119
|
-
if "shell" not in kw and not (set(cmd) & {"|", ">"}):
|
|
129
|
+
if "shell" not in kw and not (set(cmd) & {"|", ">", "&"}):
|
|
120
130
|
command = shlex.split(cmd)
|
|
121
131
|
return _run_shell(command, **kw).returncode
|
|
122
132
|
|
|
@@ -155,9 +165,9 @@ def read_version_from_file(
|
|
|
155
165
|
toml_text = Project.load_toml_text()
|
|
156
166
|
context = tomllib.loads(toml_text)
|
|
157
167
|
with contextlib.suppress(KeyError):
|
|
158
|
-
return context["project"]["version"]
|
|
168
|
+
return cast(str, context["project"]["version"])
|
|
159
169
|
with contextlib.suppress(KeyError): # Poetry V1
|
|
160
|
-
return context["tool"]["poetry"]["version"]
|
|
170
|
+
return cast(str, context["tool"]["poetry"]["version"])
|
|
161
171
|
secho(f"WARNING: can not find 'version' item in {version_file}!")
|
|
162
172
|
return "0.0.0"
|
|
163
173
|
pattern = re.compile(r"__version__\s*=")
|
|
@@ -274,14 +284,14 @@ def exit_if_run_failed(
|
|
|
274
284
|
|
|
275
285
|
|
|
276
286
|
class DryRun:
|
|
277
|
-
def __init__(self
|
|
287
|
+
def __init__(self, _exit: bool = False, dry: bool = False) -> None:
|
|
278
288
|
self.dry = dry
|
|
279
289
|
self._exit = _exit
|
|
280
290
|
|
|
281
|
-
def gen(self
|
|
291
|
+
def gen(self) -> str:
|
|
282
292
|
raise NotImplementedError
|
|
283
293
|
|
|
284
|
-
def run(self
|
|
294
|
+
def run(self) -> None:
|
|
285
295
|
exit_if_run_failed(self.gen(), _exit=self._exit, dry=self.dry)
|
|
286
296
|
|
|
287
297
|
|
|
@@ -292,17 +302,21 @@ class BumpUp(DryRun):
|
|
|
292
302
|
major = "major"
|
|
293
303
|
|
|
294
304
|
def __init__(
|
|
295
|
-
self
|
|
305
|
+
self,
|
|
296
306
|
commit: bool,
|
|
297
307
|
part: str,
|
|
298
308
|
filename: str | None = None,
|
|
299
309
|
dry: bool = False,
|
|
310
|
+
no_sync: bool = False,
|
|
311
|
+
emoji: bool | None = None,
|
|
300
312
|
) -> None:
|
|
301
313
|
self.commit = commit
|
|
302
314
|
self.part = part
|
|
303
315
|
if filename is None:
|
|
304
316
|
filename = self.parse_filename()
|
|
305
317
|
self.filename = filename
|
|
318
|
+
self._no_sync = no_sync
|
|
319
|
+
self._emoji = emoji
|
|
306
320
|
super().__init__(dry=dry)
|
|
307
321
|
|
|
308
322
|
@staticmethod
|
|
@@ -352,7 +366,9 @@ class BumpUp(DryRun):
|
|
|
352
366
|
work_dir = Project.get_work_dir()
|
|
353
367
|
for tool in ("pdm", "hatch"):
|
|
354
368
|
with contextlib.suppress(KeyError):
|
|
355
|
-
version_path =
|
|
369
|
+
version_path = cast(
|
|
370
|
+
str, context["tool"][tool]["version"]["path"]
|
|
371
|
+
)
|
|
356
372
|
if (
|
|
357
373
|
Path(version_path).exists()
|
|
358
374
|
or work_dir.joinpath(version_path).exists()
|
|
@@ -425,10 +441,8 @@ class BumpUp(DryRun):
|
|
|
425
441
|
echo(f"Invalid part: {s!r}")
|
|
426
442
|
raise Exit(1) from e
|
|
427
443
|
|
|
428
|
-
def gen(self
|
|
444
|
+
def gen(self) -> str:
|
|
429
445
|
should_sync, _version = get_current_version(check_version=True)
|
|
430
|
-
if should_sync:
|
|
431
|
-
Project.sync_dependencies()
|
|
432
446
|
filename = self.filename
|
|
433
447
|
echo(f"Current version(@{filename}): {_version}")
|
|
434
448
|
if self.part:
|
|
@@ -444,15 +458,17 @@ class BumpUp(DryRun):
|
|
|
444
458
|
if part != "patch":
|
|
445
459
|
cmd += " --tag"
|
|
446
460
|
cmd += " --commit"
|
|
447
|
-
if self.should_add_emoji():
|
|
461
|
+
if self._emoji or (self._emoji is None and self.should_add_emoji()):
|
|
448
462
|
cmd += " --message-emoji=1"
|
|
449
463
|
if not load_bool("DONT_GIT_PUSH"):
|
|
450
464
|
cmd += " && git push && git push --tags && git log -1"
|
|
451
465
|
else:
|
|
452
466
|
cmd += " --allow-dirty"
|
|
467
|
+
if should_sync and not self._no_sync and (sync := Project.get_sync_command()):
|
|
468
|
+
cmd = f"{sync} && " + cmd
|
|
453
469
|
return cmd
|
|
454
470
|
|
|
455
|
-
def run(self
|
|
471
|
+
def run(self) -> None:
|
|
456
472
|
super().run()
|
|
457
473
|
if not self.commit and not self.dry:
|
|
458
474
|
new_version = get_current_version(True)
|
|
@@ -464,7 +480,19 @@ class BumpUp(DryRun):
|
|
|
464
480
|
@cli.command()
|
|
465
481
|
def version() -> None:
|
|
466
482
|
"""Show the version of this tool"""
|
|
467
|
-
echo(
|
|
483
|
+
echo("Fast Dev Cli Version: " + typer.style(__version__, fg=typer.colors.BLUE))
|
|
484
|
+
with contextlib.suppress(FileNotFoundError, KeyError):
|
|
485
|
+
toml_text = Project.load_toml_text()
|
|
486
|
+
doc = tomllib.loads(toml_text)
|
|
487
|
+
version_file = doc["tool"]["pdm"]["version"]["path"]
|
|
488
|
+
text = Project.get_work_dir().joinpath(version_file).read_text()
|
|
489
|
+
varname = "__version__"
|
|
490
|
+
for line in text.splitlines():
|
|
491
|
+
if line.strip().startswith(varname):
|
|
492
|
+
value = line.split("=", 1)[-1].strip().strip('"').strip("'")
|
|
493
|
+
styled = typer.style(value, bold=True)
|
|
494
|
+
echo(f"Version value in {version_file}: " + styled)
|
|
495
|
+
break
|
|
468
496
|
|
|
469
497
|
|
|
470
498
|
@cli.command(name="bump")
|
|
@@ -473,10 +501,24 @@ def bump_version(
|
|
|
473
501
|
commit: bool = Option(
|
|
474
502
|
False, "--commit", "-c", help="Whether run `git commit` after version changed"
|
|
475
503
|
),
|
|
504
|
+
emoji: Optional[bool] = Option(
|
|
505
|
+
None, "--emoji", help="Whether add emoji prefix to commit message"
|
|
506
|
+
),
|
|
507
|
+
no_sync: bool = Option(
|
|
508
|
+
False, "--no-sync", help="Do not run sync command to update version"
|
|
509
|
+
),
|
|
476
510
|
dry: bool = DryOption,
|
|
477
511
|
) -> None:
|
|
478
512
|
"""Bump up version string in pyproject.toml"""
|
|
479
|
-
|
|
513
|
+
if emoji is not None:
|
|
514
|
+
emoji = _ensure_bool(emoji)
|
|
515
|
+
return BumpUp(
|
|
516
|
+
_ensure_bool(commit),
|
|
517
|
+
getattr(part, "value", part),
|
|
518
|
+
no_sync=_ensure_bool(no_sync),
|
|
519
|
+
emoji=emoji,
|
|
520
|
+
dry=dry,
|
|
521
|
+
).run()
|
|
480
522
|
|
|
481
523
|
|
|
482
524
|
def bump() -> None:
|
|
@@ -488,7 +530,7 @@ def bump() -> None:
|
|
|
488
530
|
if not a.startswith("-"):
|
|
489
531
|
part = a
|
|
490
532
|
break
|
|
491
|
-
return BumpUp(commit, part, dry="--dry" in args).run()
|
|
533
|
+
return BumpUp(commit, part, no_sync="--no-sync" in args, dry="--dry" in args).run()
|
|
492
534
|
|
|
493
535
|
|
|
494
536
|
class EnvError(Exception):
|
|
@@ -594,14 +636,30 @@ class Project:
|
|
|
594
636
|
return True
|
|
595
637
|
|
|
596
638
|
@classmethod
|
|
597
|
-
def
|
|
639
|
+
def get_sync_command(cls, prod: bool = True, doc: dict | None = None) -> str:
|
|
598
640
|
if cls.is_pdm_project():
|
|
599
|
-
|
|
600
|
-
run_and_echo(cmd)
|
|
641
|
+
return "pdm sync" + " --prod" * prod
|
|
601
642
|
elif cls.manage_by_poetry(cache=True):
|
|
602
|
-
|
|
643
|
+
cmd = "poetry install"
|
|
644
|
+
if prod:
|
|
645
|
+
if doc is None:
|
|
646
|
+
doc = tomllib.loads(cls.load_toml_text())
|
|
647
|
+
if doc.get("project", {}).get("dependencies") or any(
|
|
648
|
+
i != "python"
|
|
649
|
+
for i in doc.get("tool", {})
|
|
650
|
+
.get("poetry", {})
|
|
651
|
+
.get("dependencies", [])
|
|
652
|
+
):
|
|
653
|
+
cmd += " --only=main"
|
|
654
|
+
return cmd
|
|
603
655
|
elif cls.get_manage_tool(cache=True) == "uv":
|
|
604
|
-
|
|
656
|
+
return "uv sync --inexact" + " --no-dev" * prod
|
|
657
|
+
return ""
|
|
658
|
+
|
|
659
|
+
@classmethod
|
|
660
|
+
def sync_dependencies(cls, prod: bool = True) -> None:
|
|
661
|
+
if cmd := cls.get_sync_command():
|
|
662
|
+
run_and_echo(cmd)
|
|
605
663
|
|
|
606
664
|
|
|
607
665
|
class ParseError(Exception):
|
|
@@ -612,7 +670,7 @@ class ParseError(Exception):
|
|
|
612
670
|
|
|
613
671
|
class UpgradeDependencies(Project, DryRun):
|
|
614
672
|
def __init__(
|
|
615
|
-
self
|
|
673
|
+
self, _exit: bool = False, dry: bool = False, tool: ToolName = "poetry"
|
|
616
674
|
) -> None:
|
|
617
675
|
super().__init__(_exit, dry)
|
|
618
676
|
self._tool = tool
|
|
@@ -777,11 +835,13 @@ class UpgradeDependencies(Project, DryRun):
|
|
|
777
835
|
_upgrade += f" && poetry add {' '.join(single)}"
|
|
778
836
|
return _upgrade
|
|
779
837
|
|
|
780
|
-
def gen(self
|
|
838
|
+
def gen(self) -> str:
|
|
781
839
|
if self._tool == "uv":
|
|
782
|
-
|
|
840
|
+
up = "uv lock --upgrade --verbose"
|
|
841
|
+
deps = "uv sync --inexact --frozen --all-groups --all-extras"
|
|
842
|
+
return f"{up} && {deps}"
|
|
783
843
|
elif self._tool == "pdm":
|
|
784
|
-
return "pdm update --verbose && pdm sync -G :all"
|
|
844
|
+
return "pdm update --verbose && pdm sync -G :all --frozen"
|
|
785
845
|
return self.gen_cmd() + " && poetry lock && poetry update"
|
|
786
846
|
|
|
787
847
|
|
|
@@ -801,36 +861,35 @@ def upgrade(
|
|
|
801
861
|
|
|
802
862
|
|
|
803
863
|
class GitTag(DryRun):
|
|
804
|
-
def __init__(self
|
|
864
|
+
def __init__(self, message: str, dry: bool, no_sync: bool = False) -> None:
|
|
805
865
|
self.message = message
|
|
866
|
+
self._no_sync = no_sync
|
|
806
867
|
super().__init__(dry=dry)
|
|
807
868
|
|
|
808
869
|
@staticmethod
|
|
809
870
|
def has_v_prefix() -> bool:
|
|
810
871
|
return "v" in capture_cmd_output("git tag")
|
|
811
872
|
|
|
812
|
-
def should_push(self
|
|
873
|
+
def should_push(self) -> bool:
|
|
813
874
|
return "git push" in self.git_status
|
|
814
875
|
|
|
815
|
-
def gen(self
|
|
876
|
+
def gen(self) -> str:
|
|
816
877
|
should_sync, _version = get_current_version(verbose=False, check_version=True)
|
|
817
|
-
if should_sync:
|
|
818
|
-
Project.sync_dependencies()
|
|
819
878
|
if self.has_v_prefix():
|
|
820
879
|
# Add `v` at prefix to compare with bumpversion tool
|
|
821
880
|
_version = "v" + _version
|
|
822
881
|
cmd = f"git tag -a {_version} -m {self.message!r} && git push --tags"
|
|
823
882
|
if self.should_push():
|
|
824
883
|
cmd += " && git push"
|
|
825
|
-
if Project.
|
|
826
|
-
cmd = "
|
|
884
|
+
if should_sync and not self._no_sync and (sync := Project.get_sync_command()):
|
|
885
|
+
cmd = f"{sync} && " + cmd
|
|
827
886
|
return cmd
|
|
828
887
|
|
|
829
888
|
@cached_property
|
|
830
|
-
def git_status(self
|
|
889
|
+
def git_status(self) -> str:
|
|
831
890
|
return capture_cmd_output("git status")
|
|
832
891
|
|
|
833
|
-
def mark_tag(self
|
|
892
|
+
def mark_tag(self) -> bool:
|
|
834
893
|
if not re.search(r"working (tree|directory) clean", self.git_status) and (
|
|
835
894
|
"无文件要提交,干净的工作区" not in self.git_status
|
|
836
895
|
):
|
|
@@ -839,7 +898,7 @@ class GitTag(DryRun):
|
|
|
839
898
|
return False
|
|
840
899
|
return bool(super().run())
|
|
841
900
|
|
|
842
|
-
def run(self
|
|
901
|
+
def run(self) -> None:
|
|
843
902
|
if self.mark_tag() and not self.dry:
|
|
844
903
|
echo("You may want to publish package:\n poetry publish --build")
|
|
845
904
|
|
|
@@ -847,15 +906,18 @@ class GitTag(DryRun):
|
|
|
847
906
|
@cli.command()
|
|
848
907
|
def tag(
|
|
849
908
|
message: str = Option("", "-m", "--message"),
|
|
909
|
+
no_sync: bool = Option(
|
|
910
|
+
False, "--no-sync", help="Do not run sync command to update version"
|
|
911
|
+
),
|
|
850
912
|
dry: bool = DryOption,
|
|
851
913
|
) -> None:
|
|
852
914
|
"""Run shell command: git tag -a <current-version-in-pyproject.toml> -m {message}"""
|
|
853
|
-
GitTag(message, dry=dry).run()
|
|
915
|
+
GitTag(message, dry=dry, no_sync=_ensure_bool(no_sync)).run()
|
|
854
916
|
|
|
855
917
|
|
|
856
918
|
class LintCode(DryRun):
|
|
857
919
|
def __init__(
|
|
858
|
-
self
|
|
920
|
+
self,
|
|
859
921
|
args: list[str] | str | None,
|
|
860
922
|
check_only: bool = False,
|
|
861
923
|
_exit: bool = False,
|
|
@@ -864,6 +926,7 @@ class LintCode(DryRun):
|
|
|
864
926
|
skip_mypy: bool = False,
|
|
865
927
|
dmypy: bool = False,
|
|
866
928
|
tool: str = ToolOption.default,
|
|
929
|
+
prefix: bool = False,
|
|
867
930
|
) -> None:
|
|
868
931
|
self.args = args
|
|
869
932
|
self.check_only = check_only
|
|
@@ -871,6 +934,7 @@ class LintCode(DryRun):
|
|
|
871
934
|
self._skip_mypy = skip_mypy
|
|
872
935
|
self._use_dmypy = dmypy
|
|
873
936
|
self._tool = tool
|
|
937
|
+
self._prefix = prefix
|
|
874
938
|
super().__init__(_exit, dry)
|
|
875
939
|
|
|
876
940
|
@staticmethod
|
|
@@ -904,6 +968,7 @@ class LintCode(DryRun):
|
|
|
904
968
|
skip_mypy: bool = False,
|
|
905
969
|
use_dmypy: bool = False,
|
|
906
970
|
tool: str = ToolOption.default,
|
|
971
|
+
with_prefix: bool = False,
|
|
907
972
|
) -> str:
|
|
908
973
|
if paths != "." and all(i.endswith(".html") for i in paths.split()):
|
|
909
974
|
return f"prettier -w {paths}"
|
|
@@ -921,22 +986,42 @@ class LintCode(DryRun):
|
|
|
921
986
|
lint_them = " && ".join(
|
|
922
987
|
"{0}{" + str(i) + "} {1}" for i in range(2, len(tools) + 2)
|
|
923
988
|
)
|
|
989
|
+
if ruff_exists := cls.check_lint_tool_installed():
|
|
990
|
+
# `ruff <command>` get the same result with `pdm run ruff <command>`
|
|
991
|
+
# While `mypy .`(installed global and env not activated),
|
|
992
|
+
# does not the same as `pdm run mypy .`
|
|
993
|
+
lint_them = " && ".join(
|
|
994
|
+
("" if tool.startswith("ruff") else "{0}")
|
|
995
|
+
+ (
|
|
996
|
+
"{%d} {1}" % i # noqa: UP031
|
|
997
|
+
)
|
|
998
|
+
for i, tool in enumerate(tools, 2)
|
|
999
|
+
)
|
|
924
1000
|
prefix = ""
|
|
925
|
-
should_run_by_tool =
|
|
926
|
-
if
|
|
927
|
-
if
|
|
1001
|
+
should_run_by_tool = with_prefix
|
|
1002
|
+
if not should_run_by_tool:
|
|
1003
|
+
if is_venv() and Path(sys.argv[0]).parent != Path.home().joinpath(
|
|
1004
|
+
".local/bin"
|
|
1005
|
+
):
|
|
1006
|
+
if not ruff_exists:
|
|
1007
|
+
should_run_by_tool = True
|
|
1008
|
+
if check_call('python -c "import fast_dev_cli"'):
|
|
1009
|
+
command = 'python -m pip install -U "fast-dev-cli"'
|
|
1010
|
+
yellow_warn(
|
|
1011
|
+
"You may need to run the following command"
|
|
1012
|
+
f" to install lint tools:\n\n {command}\n"
|
|
1013
|
+
)
|
|
1014
|
+
else:
|
|
928
1015
|
should_run_by_tool = True
|
|
929
|
-
if check_call('python -c "import fast_dev_cli"'):
|
|
930
|
-
command = 'python -m pip install -U "fast-dev-cli"'
|
|
931
|
-
tip = "You may need to run following command to install lint tools:"
|
|
932
|
-
secho(f"{tip}\n\n {command}\n", fg="yellow")
|
|
933
|
-
else:
|
|
934
|
-
should_run_by_tool = True
|
|
935
1016
|
if should_run_by_tool and tool:
|
|
936
1017
|
if tool == ToolOption.default:
|
|
937
1018
|
tool = Project.get_manage_tool() or ""
|
|
938
1019
|
if tool:
|
|
939
|
-
prefix =
|
|
1020
|
+
prefix = (
|
|
1021
|
+
bin_dir
|
|
1022
|
+
if tool == "uv" and Path(bin_dir := ".venv/bin/").exists()
|
|
1023
|
+
else (tool + " run ")
|
|
1024
|
+
)
|
|
940
1025
|
if cls.prefer_dmypy(paths, tools, use_dmypy=use_dmypy):
|
|
941
1026
|
tools[-1] = "dmypy run"
|
|
942
1027
|
cmd += lint_them.format(prefix, paths, *tools)
|
|
@@ -952,12 +1037,18 @@ class LintCode(DryRun):
|
|
|
952
1037
|
cmd += " && " + command
|
|
953
1038
|
return cmd
|
|
954
1039
|
|
|
955
|
-
def gen(self
|
|
1040
|
+
def gen(self) -> str:
|
|
956
1041
|
if isinstance(args := self.args, str):
|
|
957
1042
|
args = args.split()
|
|
958
1043
|
paths = " ".join(map(str, args)) if args else "."
|
|
959
1044
|
return self.to_cmd(
|
|
960
|
-
paths,
|
|
1045
|
+
paths,
|
|
1046
|
+
self.check_only,
|
|
1047
|
+
self._bandit,
|
|
1048
|
+
self._skip_mypy,
|
|
1049
|
+
self._use_dmypy,
|
|
1050
|
+
tool=self._tool,
|
|
1051
|
+
with_prefix=self._prefix,
|
|
961
1052
|
)
|
|
962
1053
|
|
|
963
1054
|
|
|
@@ -972,13 +1063,20 @@ def lint(
|
|
|
972
1063
|
skip_mypy: bool = False,
|
|
973
1064
|
dmypy: bool = False,
|
|
974
1065
|
tool: str = ToolOption.default,
|
|
1066
|
+
prefix: bool = False,
|
|
975
1067
|
) -> None:
|
|
976
1068
|
if files is None:
|
|
977
1069
|
files = parse_files(sys.argv[1:])
|
|
978
1070
|
if files and files[0] == "lint":
|
|
979
1071
|
files = files[1:]
|
|
980
1072
|
LintCode(
|
|
981
|
-
files,
|
|
1073
|
+
files,
|
|
1074
|
+
dry=dry,
|
|
1075
|
+
skip_mypy=skip_mypy,
|
|
1076
|
+
bandit=bandit,
|
|
1077
|
+
dmypy=dmypy,
|
|
1078
|
+
tool=tool,
|
|
1079
|
+
prefix=prefix,
|
|
982
1080
|
).run()
|
|
983
1081
|
|
|
984
1082
|
|
|
@@ -1007,6 +1105,11 @@ def make_style(
|
|
|
1007
1105
|
files: Optional[list[str]] = typer.Argument(default=None), # noqa:B008
|
|
1008
1106
|
check_only: bool = Option(False, "--check-only", "-c"),
|
|
1009
1107
|
bandit: bool = Option(False, "--bandit", help="Run `bandit -r <package_dir>`"),
|
|
1108
|
+
prefix: bool = Option(
|
|
1109
|
+
False,
|
|
1110
|
+
"--prefix",
|
|
1111
|
+
help="Run lint command with tool prefix, e.g.: pdm run ruff ...",
|
|
1112
|
+
),
|
|
1010
1113
|
skip_mypy: bool = Option(False, "--skip-mypy"),
|
|
1011
1114
|
use_dmypy: bool = Option(
|
|
1012
1115
|
False, "--dmypy", help="Use `dmypy run` instead of `mypy`"
|
|
@@ -1022,11 +1125,20 @@ def make_style(
|
|
|
1022
1125
|
skip = _ensure_bool(skip_mypy)
|
|
1023
1126
|
dmypy = _ensure_bool(use_dmypy)
|
|
1024
1127
|
bandit = _ensure_bool(bandit)
|
|
1128
|
+
prefix = _ensure_bool(prefix)
|
|
1025
1129
|
tool = _ensure_str(tool)
|
|
1026
1130
|
if _ensure_bool(check_only):
|
|
1027
1131
|
check(files, dry=dry, skip_mypy=skip, dmypy=dmypy, bandit=bandit, tool=tool)
|
|
1028
1132
|
else:
|
|
1029
|
-
lint(
|
|
1133
|
+
lint(
|
|
1134
|
+
files,
|
|
1135
|
+
dry=dry,
|
|
1136
|
+
skip_mypy=skip,
|
|
1137
|
+
dmypy=dmypy,
|
|
1138
|
+
bandit=bandit,
|
|
1139
|
+
tool=tool,
|
|
1140
|
+
prefix=prefix,
|
|
1141
|
+
)
|
|
1030
1142
|
|
|
1031
1143
|
|
|
1032
1144
|
@cli.command(name="check")
|
|
@@ -1041,7 +1153,7 @@ def only_check(
|
|
|
1041
1153
|
|
|
1042
1154
|
class Sync(DryRun):
|
|
1043
1155
|
def __init__(
|
|
1044
|
-
self
|
|
1156
|
+
self, filename: str, extras: str, save: bool, dry: bool = False
|
|
1045
1157
|
) -> None:
|
|
1046
1158
|
self.filename = filename
|
|
1047
1159
|
self.extras = extras
|
|
@@ -1192,7 +1304,7 @@ def runserver(
|
|
|
1192
1304
|
|
|
1193
1305
|
@cli.command(name="exec")
|
|
1194
1306
|
def run_by_subprocess(cmd: str, dry: bool = DryOption) -> None:
|
|
1195
|
-
"""Run cmd by subprocess, auto set shell=True when cmd contains '
|
|
1307
|
+
"""Run cmd by subprocess, auto set shell=True when cmd contains '|>'"""
|
|
1196
1308
|
try:
|
|
1197
1309
|
rc = run_and_echo(cmd, verbose=True, dry=_ensure_bool(dry))
|
|
1198
1310
|
except FileNotFoundError as e:
|
|
@@ -1205,6 +1317,26 @@ def run_by_subprocess(cmd: str, dry: bool = DryOption) -> None:
|
|
|
1205
1317
|
raise Exit(rc)
|
|
1206
1318
|
|
|
1207
1319
|
|
|
1320
|
+
def version_callback(value: bool) -> None:
|
|
1321
|
+
if value:
|
|
1322
|
+
echo("Fast Dev Cli Version: " + typer.style(__version__, bold=True))
|
|
1323
|
+
raise Exit()
|
|
1324
|
+
|
|
1325
|
+
|
|
1326
|
+
@cli.callback()
|
|
1327
|
+
def common(
|
|
1328
|
+
version: bool = Option(
|
|
1329
|
+
None,
|
|
1330
|
+
"--version",
|
|
1331
|
+
"-V",
|
|
1332
|
+
callback=version_callback,
|
|
1333
|
+
is_eager=True,
|
|
1334
|
+
help="Show the version of this tool",
|
|
1335
|
+
),
|
|
1336
|
+
) -> None:
|
|
1337
|
+
pass
|
|
1338
|
+
|
|
1339
|
+
|
|
1208
1340
|
def main() -> None:
|
|
1209
1341
|
cli()
|
|
1210
1342
|
|
|
@@ -42,7 +42,7 @@ dependencies = [
|
|
|
42
42
|
"bumpversion2 >=1.4.3,<2",
|
|
43
43
|
"pytest >=8.2.0,<9",
|
|
44
44
|
]
|
|
45
|
-
version = "0.17.
|
|
45
|
+
version = "0.17.2"
|
|
46
46
|
|
|
47
47
|
[project.urls]
|
|
48
48
|
Homepage = "https://github.com/waketzheng/fast-dev-cli"
|
|
@@ -59,6 +59,9 @@ fast = "fast_dev_cli:cli.main"
|
|
|
59
59
|
dev = [
|
|
60
60
|
"twine>=6.1.0",
|
|
61
61
|
]
|
|
62
|
+
test = [
|
|
63
|
+
"asynctor>=0.8.6",
|
|
64
|
+
]
|
|
62
65
|
|
|
63
66
|
[tool.pdm]
|
|
64
67
|
distribution = true
|
|
@@ -150,7 +153,7 @@ omit = [
|
|
|
150
153
|
context = "${CONTEXT}"
|
|
151
154
|
|
|
152
155
|
[tool.coverage.report]
|
|
153
|
-
|
|
156
|
+
exclude_also = [
|
|
154
157
|
"pragma: no cover",
|
|
155
158
|
"@overload",
|
|
156
159
|
"if __name__ == \"__main__\":",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
5
|
+
from asynctor.compat import chdir
|
|
4
6
|
|
|
5
|
-
from fast_dev_cli.cli import TOML_FILE
|
|
6
|
-
|
|
7
|
-
from .utils import chdir
|
|
7
|
+
from fast_dev_cli.cli import TOML_FILE, run_and_echo
|
|
8
8
|
|
|
9
9
|
# toml file content before migrate to pdm
|
|
10
10
|
TOML_CONTENT = """
|
|
@@ -59,4 +59,6 @@ def tmp_work_dir(tmp_path):
|
|
|
59
59
|
@pytest.fixture
|
|
60
60
|
def tmp_poetry_project(tmp_work_dir: Path):
|
|
61
61
|
Path(TOML_FILE).write_text(TOML_CONTENT)
|
|
62
|
+
if sys.version_info >= (3, 10):
|
|
63
|
+
run_and_echo("poetry config --local virtualenvs.in-project true")
|
|
62
64
|
yield tmp_work_dir
|
|
@@ -22,6 +22,7 @@ from fast_dev_cli.cli import (
|
|
|
22
22
|
bump_version,
|
|
23
23
|
capture_cmd_output,
|
|
24
24
|
get_current_version,
|
|
25
|
+
run_and_echo,
|
|
25
26
|
)
|
|
26
27
|
|
|
27
28
|
from .utils import chdir, mock_sys_argv, prepare_poetry_project
|
|
@@ -102,35 +103,38 @@ def test_bump(
|
|
|
102
103
|
|
|
103
104
|
def test_bump_with_poetry(mocker, tmp_poetry_project, tmp_path):
|
|
104
105
|
mocker.patch("builtins.input", return_value=" ")
|
|
105
|
-
version = get_current_version()
|
|
106
|
+
version = get_current_version(check_version=False)
|
|
106
107
|
patch_without_commit, patch_with_commit, minor_with_commit = _bump_commands(
|
|
107
108
|
version, add_sync=False
|
|
108
109
|
)
|
|
109
110
|
stream = StringIO()
|
|
110
111
|
with redirect_stdout(stream):
|
|
111
|
-
BumpUp(part="patch", commit=False).run()
|
|
112
|
+
BumpUp(part="patch", commit=False, no_sync=True).run()
|
|
112
113
|
assert f"Current version(@{TOML_FILE}):" in stream.getvalue()
|
|
113
114
|
stream = StringIO()
|
|
114
115
|
with redirect_stdout(stream):
|
|
115
|
-
BumpUp(part="minor", commit=False).run()
|
|
116
|
+
BumpUp(part="minor", commit=False, no_sync=True).run()
|
|
116
117
|
assert "You may want to pin tag by `fast tag`" in stream.getvalue()
|
|
117
118
|
stream = StringIO()
|
|
118
119
|
new_version = get_current_version()
|
|
119
120
|
with redirect_stdout(stream):
|
|
120
|
-
bump_version(BumpUp.PartChoices.patch, commit=False, dry=True)
|
|
121
|
+
bump_version(BumpUp.PartChoices.patch, commit=False, dry=True, no_sync=True)
|
|
121
122
|
assert patch_without_commit.replace(version, new_version) in stream.getvalue()
|
|
122
123
|
stream = StringIO()
|
|
123
|
-
with redirect_stdout(stream), mock_sys_argv(["patch", "--dry"]):
|
|
124
|
+
with redirect_stdout(stream), mock_sys_argv(["patch", "--dry", "--no-sync"]):
|
|
124
125
|
bump()
|
|
125
126
|
assert patch_without_commit.replace(version, new_version) in stream.getvalue()
|
|
126
127
|
stream = StringIO()
|
|
127
|
-
with
|
|
128
|
+
with (
|
|
129
|
+
redirect_stdout(stream),
|
|
130
|
+
mock_sys_argv(["patch", "--commit", "--dry", "--no-sync"]),
|
|
131
|
+
):
|
|
128
132
|
bump()
|
|
129
133
|
assert patch_with_commit.replace(version, new_version) in stream.getvalue()
|
|
130
134
|
stream = StringIO()
|
|
131
135
|
with (
|
|
132
136
|
redirect_stdout(stream),
|
|
133
|
-
mock_sys_argv(["-c", "minor", "--commit", "--dry"]),
|
|
137
|
+
mock_sys_argv(["-c", "minor", "--commit", "--dry", "--no-sync"]),
|
|
134
138
|
):
|
|
135
139
|
bump()
|
|
136
140
|
assert minor_with_commit.replace(version, new_version) in stream.getvalue()
|
|
@@ -182,7 +186,7 @@ def test_bump_with_emoji_in_poetry_project(mocker, tmp_path, monkeypatch):
|
|
|
182
186
|
assert BumpUp.should_add_emoji() is False
|
|
183
187
|
subprocess.run(["git", "commit", "-m", last_commit])
|
|
184
188
|
monkeypatch.setenv("DONT_GIT_PUSH", "1")
|
|
185
|
-
command = BumpUp(part="patch", commit=True).gen()
|
|
189
|
+
command = BumpUp(part="patch", commit=True, no_sync=True).gen()
|
|
186
190
|
expected = patch_with_commit.split("&&")[0].strip().replace('""', '"0.1.0"')
|
|
187
191
|
assert expected == command
|
|
188
192
|
subprocess.run(["poetry", "run", "pip", "install", "bumpversion2"])
|
|
@@ -267,6 +271,15 @@ def test_pdm_project(dynamic_pdm_project):
|
|
|
267
271
|
out = capture_cmd_output("fast bump patch")
|
|
268
272
|
assert str(init_file) in out
|
|
269
273
|
assert "0.2.1" in init_file.read_text()
|
|
274
|
+
run_and_echo("git init")
|
|
275
|
+
shutil.copy(Path(__file__).parent.parent / ".gitignore", ".")
|
|
276
|
+
run_and_echo("git add .")
|
|
277
|
+
run_and_echo("git config user.name xxx")
|
|
278
|
+
run_and_echo("git config user.email xxx@a.com")
|
|
279
|
+
run_and_echo('git commit -m "xxx"')
|
|
280
|
+
out = capture_cmd_output("fast bump patch --emoji --commit")
|
|
281
|
+
assert "--message-emoji=1" in out
|
|
282
|
+
assert "⬆️ Bump version: 0.2.1 → 0.2.2" in capture_cmd_output("git log")
|
|
270
283
|
|
|
271
284
|
|
|
272
285
|
def test_installed_version_is_0_0_0(dynamic_pdm_project):
|
|
@@ -160,14 +160,6 @@ def test_lint_html():
|
|
|
160
160
|
assert LintCode.to_cmd("index.html flv.html") == "prettier -w index.html flv.html"
|
|
161
161
|
|
|
162
162
|
|
|
163
|
-
def test_lint_by_global_fast():
|
|
164
|
-
run = "pdm run "
|
|
165
|
-
fast = Path.home() / ".local" / "bin" / "fast"
|
|
166
|
-
command = capture_cmd_output(f"{fast} lint --dry")
|
|
167
|
-
for cmd in command.split(SEP):
|
|
168
|
-
assert run in cmd
|
|
169
|
-
|
|
170
|
-
|
|
171
163
|
def test_with_dmypy():
|
|
172
164
|
cmd = "fast lint --dmypy --dry ."
|
|
173
165
|
assert "dmypy run ." in capture_cmd_output(cmd)
|
|
@@ -195,6 +187,14 @@ def test_lint_with_prefix(mocker):
|
|
|
195
187
|
assert "pdm run" in stream.getvalue()
|
|
196
188
|
|
|
197
189
|
|
|
190
|
+
def test_fast_lint_with_uv():
|
|
191
|
+
command = capture_cmd_output("fast lint --tool=uv --prefix --dry")
|
|
192
|
+
assert (
|
|
193
|
+
command
|
|
194
|
+
== "--> ruff format . && ruff check --extend-select=I,B,SIM --fix . && .venv/bin/mypy ."
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
198
|
def test_make_style(mock_skip_mypy_0, mocker, mock_no_dmypy):
|
|
199
199
|
mocker.patch("fast_dev_cli.cli.is_venv", return_value=True)
|
|
200
200
|
with capture_stdout() as stream:
|
|
@@ -247,7 +247,7 @@ def test_lint_without_ruff_installed(mocker, mock_no_dmypy):
|
|
|
247
247
|
output = stream.getvalue()
|
|
248
248
|
cmd = 'python -m pip install -U "fast-dev-cli"'
|
|
249
249
|
assert cmd in output
|
|
250
|
-
tip = "You may need to run following command to install lint tools"
|
|
250
|
+
tip = "You may need to run the following command to install lint tools"
|
|
251
251
|
assert tip in output
|
|
252
252
|
assert f"{tip}:\n\n {cmd}" in output
|
|
253
253
|
|
|
@@ -38,21 +38,20 @@ def _prepare_package(
|
|
|
38
38
|
a, b = 'version = "0.1.0"', f'version = "{mark}"'
|
|
39
39
|
if define_include:
|
|
40
40
|
b += f'\npackages = [{{include = "{package_name}"}}]'
|
|
41
|
+
py_version = "{}.{}".format(*sys.version_info)
|
|
41
42
|
with chdir(package_path.parent):
|
|
42
|
-
run_and_echo(
|
|
43
|
+
run_and_echo(
|
|
44
|
+
f'poetry new --python="^{py_version}" --no-interaction "{package_path.name}"'
|
|
45
|
+
)
|
|
43
46
|
src_dir = package_path / "src"
|
|
44
47
|
if is_src_layout := src_dir.exists():
|
|
45
48
|
# poetry v2 default to use src/<package_name> layout
|
|
46
49
|
init_file = src_dir / package_name / init_file.name
|
|
47
|
-
toml_file.unlink()
|
|
48
|
-
py_version = "{}.{}".format(*sys.version_info)
|
|
49
50
|
with chdir(package_path):
|
|
50
|
-
run_and_echo(
|
|
51
|
+
run_and_echo("poetry config --local virtualenvs.in-project true")
|
|
51
52
|
text = toml_file.read_text().replace(a, b)
|
|
52
53
|
if " " in package_path.name:
|
|
53
|
-
text = text.replace(
|
|
54
|
-
f'name = "{package_path.name}"', f'name = "{package_name}"'
|
|
55
|
-
)
|
|
54
|
+
text = text.replace(f'"{package_path.name}"', f'"{package_name}"')
|
|
56
55
|
toml_file.write_text(text + CONF)
|
|
57
56
|
if package_path.name != package_name:
|
|
58
57
|
if is_src_layout:
|
|
@@ -63,15 +62,21 @@ def _prepare_package(
|
|
|
63
62
|
yield init_file
|
|
64
63
|
|
|
65
64
|
|
|
66
|
-
def _build_bump_cmd(
|
|
65
|
+
def _build_bump_cmd(
|
|
66
|
+
init_file: Path, project_path: Path, should_sync: bool = False
|
|
67
|
+
) -> str:
|
|
67
68
|
relative_path = init_file.relative_to(project_path).as_posix()
|
|
68
|
-
|
|
69
|
+
cmd = rf'bumpversion --parse "(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)" --current-version="0.0.1" patch {relative_path} --allow-dirty'
|
|
70
|
+
if should_sync:
|
|
71
|
+
cmd = "poetry install && " + cmd
|
|
72
|
+
return cmd
|
|
69
73
|
|
|
70
74
|
|
|
71
|
-
|
|
75
|
+
@pytest.mark.parametrize("mark", ["0", "0.0.0"])
|
|
76
|
+
def test_version_plugin(mark, tmp_path: Path) -> None:
|
|
72
77
|
project_path = tmp_path / "helloworld"
|
|
73
|
-
with _prepare_package(project_path) as init_file:
|
|
74
|
-
command = _build_bump_cmd(init_file, project_path)
|
|
78
|
+
with _prepare_package(project_path, mark=mark) as init_file:
|
|
79
|
+
command = _build_bump_cmd(init_file, project_path, should_sync=True)
|
|
75
80
|
assert BumpUp(part="patch", commit=False, dry=True).gen() == command
|
|
76
81
|
run_and_echo("poetry run fast bump patch")
|
|
77
82
|
assert init_file.read_text() == '__version__ = "0.0.2"\n'
|
|
@@ -80,31 +85,11 @@ def test_version_plugin(tmp_path: Path) -> None:
|
|
|
80
85
|
BumpUp(part="patch", commit=False, dry=True).gen()
|
|
81
86
|
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
with _prepare_package(project_path, mark="0.0.0") as init_file:
|
|
86
|
-
command = _build_bump_cmd(init_file, project_path)
|
|
87
|
-
assert BumpUp(part="patch", commit=False, dry=True).gen() == command
|
|
88
|
-
run_and_echo("poetry run fast bump patch")
|
|
89
|
-
assert init_file.read_text() == '__version__ = "0.0.2"\n'
|
|
90
|
-
init_file.unlink()
|
|
91
|
-
with pytest.raises(ParseError, match=r"Version file not found!.*"):
|
|
92
|
-
BumpUp(part="patch", commit=False, dry=True).gen()
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def test_version_plugin_include_defined(tmp_path: Path) -> None:
|
|
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)
|
|
99
|
-
assert BumpUp(part="patch", commit=False, dry=True).gen() == command
|
|
100
|
-
run_and_echo("poetry run fast bump patch")
|
|
101
|
-
assert init_file.read_text() == '__version__ = "0.0.2"\n'
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def test_version_plugin_include_defined_2(tmp_path: Path) -> None:
|
|
88
|
+
@pytest.mark.parametrize("mark", ["0", "0.0.0"])
|
|
89
|
+
def test_version_plugin_include_defined(mark, tmp_path: Path) -> None:
|
|
105
90
|
project_path = tmp_path / "hello world"
|
|
106
|
-
with _prepare_package(project_path, True, mark=
|
|
107
|
-
command = _build_bump_cmd(init_file, project_path)
|
|
91
|
+
with _prepare_package(project_path, True, mark=mark) as init_file:
|
|
92
|
+
command = _build_bump_cmd(init_file, project_path, should_sync=True)
|
|
108
93
|
assert BumpUp(part="patch", commit=False, dry=True).gen() == command
|
|
109
94
|
run_and_echo("poetry run fast bump patch")
|
|
110
95
|
assert init_file.read_text() == '__version__ = "0.0.2"\n'
|
|
@@ -50,16 +50,24 @@ def _clear_tags():
|
|
|
50
50
|
def test_with_push(mocker):
|
|
51
51
|
git_tag = GitTag("", dry=True)
|
|
52
52
|
mocker.patch.object(git_tag, "git_status", return_value="git push")
|
|
53
|
-
version = get_current_version()
|
|
53
|
+
should_sync, version = get_current_version(check_version=True)
|
|
54
54
|
prefix = "v" if "v" in capture_cmd_output(["git", "tag"]) else ""
|
|
55
55
|
sync = "pdm sync --prod"
|
|
56
56
|
push = "git push --tags"
|
|
57
|
-
|
|
57
|
+
expected = f"git tag -a {prefix}{version} -m '' && {push}"
|
|
58
|
+
if should_sync:
|
|
59
|
+
expected = f"{sync} && " + expected
|
|
60
|
+
assert git_tag.gen() == expected
|
|
58
61
|
with _clear_tags():
|
|
59
62
|
git_tag_cmd = git_tag.gen()
|
|
60
|
-
|
|
63
|
+
expected = f"git tag -a {version} -m '' && {push}"
|
|
64
|
+
if should_sync:
|
|
65
|
+
expected = f"{sync} && " + expected
|
|
66
|
+
assert git_tag_cmd == expected
|
|
61
67
|
mocker.patch.object(git_tag, "has_v_prefix", return_value=True)
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
expected = f"git tag -a v{version} -m '' && {push}"
|
|
69
|
+
if should_sync:
|
|
70
|
+
expected = f"{sync} && " + expected
|
|
71
|
+
assert git_tag.gen() == expected
|
|
64
72
|
mocker.patch.object(git_tag, "should_push", return_value=True)
|
|
65
|
-
assert git_tag.gen() ==
|
|
73
|
+
assert git_tag.gen() == expected + " && git push"
|
|
@@ -292,7 +292,7 @@ fastapi = "^0.112.2"
|
|
|
292
292
|
|
|
293
293
|
def test_upgrade_uv_project():
|
|
294
294
|
cmd = "fast upgrade --tool=uv --dry"
|
|
295
|
-
expected = "uv lock --upgrade --verbose && uv sync --frozen --all-groups"
|
|
295
|
+
expected = "uv lock --upgrade --verbose && uv sync --inexact --frozen --all-groups --all-extras"
|
|
296
296
|
assert expected in capture_cmd_output(cmd)
|
|
297
297
|
assert expected in capture_cmd_output("pdm run " + cmd)
|
|
298
298
|
assert UpgradeDependencies(tool="uv").gen() == expected
|
|
@@ -300,7 +300,7 @@ def test_upgrade_uv_project():
|
|
|
300
300
|
|
|
301
301
|
def test_upgrade_pdm_project():
|
|
302
302
|
cmd = "fast upgrade --tool=pdm --dry"
|
|
303
|
-
expected = "pdm update --verbose && pdm sync -G :all"
|
|
303
|
+
expected = "pdm update --verbose && pdm sync -G :all --frozen"
|
|
304
304
|
assert expected in capture_cmd_output(cmd)
|
|
305
305
|
assert expected in capture_cmd_output("pdm run " + cmd)
|
|
306
306
|
assert UpgradeDependencies(tool="pdm").gen() == expected
|
|
@@ -2,19 +2,21 @@ import re
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
+
from asynctor import Shell
|
|
6
|
+
from asynctor.compat import chdir
|
|
5
7
|
|
|
6
8
|
from fast_dev_cli import __version__
|
|
7
9
|
from fast_dev_cli.cli import (
|
|
8
10
|
TOML_FILE,
|
|
11
|
+
Exit,
|
|
9
12
|
ShellCommandError,
|
|
10
13
|
_parse_version,
|
|
11
14
|
get_current_version,
|
|
12
15
|
read_version_from_file,
|
|
13
16
|
version,
|
|
17
|
+
version_callback,
|
|
14
18
|
)
|
|
15
19
|
|
|
16
|
-
from .utils import chdir
|
|
17
|
-
|
|
18
20
|
|
|
19
21
|
def test_version(capsys):
|
|
20
22
|
version()
|
|
@@ -58,3 +60,19 @@ def test_parse_version():
|
|
|
58
60
|
assert _parse_version(line, pattern) == "0.0.1"
|
|
59
61
|
line = '__version__ = "0.0.1" # 0.0.2'
|
|
60
62
|
assert _parse_version(line, pattern) == "0.0.1"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_display_self_version(capsys):
|
|
66
|
+
with pytest.raises(Exit):
|
|
67
|
+
version_callback(True)
|
|
68
|
+
assert __version__ in capsys.readouterr().out
|
|
69
|
+
out = Shell("fast --version").capture_output().strip()
|
|
70
|
+
assert out == f"Fast Dev Cli Version: {__version__}"
|
|
71
|
+
out_v = Shell("fast -V").capture_output().strip()
|
|
72
|
+
assert out_v == out
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_fast_version(capsys):
|
|
76
|
+
out = Shell("fast version").capture_output().strip()
|
|
77
|
+
assert f"Fast Dev Cli Version: {__version__}" in out
|
|
78
|
+
assert f"fast_dev_cli/__init__.py: {__version__}" in out
|
|
@@ -1,29 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import subprocess
|
|
1
|
+
import shutil
|
|
3
2
|
import sys
|
|
4
|
-
from contextlib import
|
|
3
|
+
from contextlib import contextmanager, redirect_stdout
|
|
5
4
|
from io import StringIO
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class chdir(AbstractContextManager): # Copied from source code of Python3.13
|
|
11
|
-
"""Non thread-safe context manager to change the current working directory."""
|
|
12
|
-
|
|
13
|
-
def __init__(self, path):
|
|
14
|
-
self.path = path
|
|
15
|
-
self._old_cwd = []
|
|
16
|
-
|
|
17
|
-
def __enter__(self):
|
|
18
|
-
self._old_cwd.append(os.getcwd())
|
|
19
|
-
os.chdir(self.path)
|
|
20
|
-
|
|
21
|
-
def __exit__(self, *excinfo):
|
|
22
|
-
os.chdir(self._old_cwd.pop())
|
|
23
|
-
|
|
7
|
+
from asynctor import Shell
|
|
8
|
+
from asynctor.compat import chdir
|
|
24
9
|
|
|
25
10
|
__all__ = (
|
|
26
|
-
"chdir",
|
|
27
11
|
"mock_sys_argv",
|
|
28
12
|
"capture_stdout",
|
|
29
13
|
"temp_file",
|
|
@@ -68,8 +52,16 @@ def temp_file(name: str, text=""):
|
|
|
68
52
|
|
|
69
53
|
@contextmanager
|
|
70
54
|
def prepare_poetry_project(tmp_path: Path):
|
|
55
|
+
py = "{}.{}".format(*sys.version_info)
|
|
56
|
+
poetry = "poetry"
|
|
57
|
+
if shutil.which(poetry) is None:
|
|
58
|
+
poetry = "uvx " + poetry
|
|
71
59
|
with chdir(tmp_path):
|
|
72
60
|
project = "foo"
|
|
73
|
-
|
|
61
|
+
Shell.run_by_subprocess(f"{poetry} new {project} --python=^{py}")
|
|
74
62
|
with chdir(tmp_path / project):
|
|
63
|
+
Shell.run_by_subprocess(
|
|
64
|
+
f"{poetry} config --local virtualenvs.in-project true"
|
|
65
|
+
)
|
|
66
|
+
Shell.run_by_subprocess(f"{poetry} env use {py}")
|
|
75
67
|
yield
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.17.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|