bakefile 0.0.4__tar.gz → 0.0.5__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.
- {bakefile-0.0.4 → bakefile-0.0.5}/PKG-INFO +14 -2
- {bakefile-0.0.4 → bakefile-0.0.5}/pyproject.toml +36 -8
- bakefile-0.0.5/src/bake/__init__.py +9 -0
- bakefile-0.0.5/src/bake/bakebook/bakebook.py +85 -0
- bakefile-0.0.5/src/bake/bakebook/decorator.py +50 -0
- bakefile-0.0.5/src/bake/bakebook/get.py +175 -0
- bakefile-0.0.5/src/bake/cli/bake/__init__.py +3 -0
- bakefile-0.0.5/src/bake/cli/bake/__main__.py +5 -0
- bakefile-0.0.5/src/bake/cli/bake/main.py +74 -0
- bakefile-0.0.5/src/bake/cli/bake/reinvocation.py +63 -0
- bakefile-0.0.5/src/bake/cli/bakefile/__init__.py +3 -0
- bakefile-0.0.5/src/bake/cli/bakefile/__main__.py +5 -0
- bakefile-0.0.5/src/bake/cli/bakefile/add_inline.py +29 -0
- bakefile-0.0.5/src/bake/cli/bakefile/find_python.py +18 -0
- bakefile-0.0.5/src/bake/cli/bakefile/init.py +56 -0
- bakefile-0.0.5/src/bake/cli/bakefile/lint.py +77 -0
- bakefile-0.0.5/src/bake/cli/bakefile/main.py +41 -0
- bakefile-0.0.5/src/bake/cli/bakefile/uv.py +146 -0
- bakefile-0.0.5/src/bake/cli/common/app.py +54 -0
- bakefile-0.0.5/src/bake/cli/common/callback.py +13 -0
- bakefile-0.0.5/src/bake/cli/common/context.py +145 -0
- bakefile-0.0.5/src/bake/cli/common/exception_handler.py +57 -0
- bakefile-0.0.5/src/bake/cli/common/obj.py +214 -0
- bakefile-0.0.5/src/bake/cli/common/params.py +72 -0
- bakefile-0.0.5/src/bake/cli/utils/__init__.py +0 -0
- bakefile-0.0.5/src/bake/cli/utils/version.py +18 -0
- bakefile-0.0.5/src/bake/manage/__init__.py +0 -0
- bakefile-0.0.5/src/bake/manage/add_inline.py +71 -0
- bakefile-0.0.5/src/bake/manage/find_python.py +210 -0
- bakefile-0.0.5/src/bake/manage/lint.py +101 -0
- bakefile-0.0.5/src/bake/manage/run_uv.py +88 -0
- bakefile-0.0.5/src/bake/manage/write_bakefile.py +20 -0
- bakefile-0.0.5/src/bake/py.typed +0 -0
- bakefile-0.0.5/src/bake/samples/__init__.py +0 -0
- bakefile-0.0.5/src/bake/samples/simple.py +9 -0
- bakefile-0.0.5/src/bake/ui/__init__.py +10 -0
- bakefile-0.0.5/src/bake/ui/console.py +58 -0
- bakefile-0.0.5/src/bake/ui/logger/__init__.py +33 -0
- bakefile-0.0.5/src/bake/ui/logger/capsys.py +158 -0
- bakefile-0.0.5/src/bake/ui/logger/setup.py +53 -0
- bakefile-0.0.5/src/bake/ui/logger/utils.py +215 -0
- bakefile-0.0.5/src/bake/ui/run/__init__.py +11 -0
- bakefile-0.0.5/src/bake/ui/run/run.py +541 -0
- bakefile-0.0.5/src/bake/ui/run/script.py +74 -0
- bakefile-0.0.5/src/bake/ui/run/splitter.py +237 -0
- bakefile-0.0.5/src/bake/ui/run/uv.py +83 -0
- bakefile-0.0.5/src/bake/ui/style.py +2 -0
- bakefile-0.0.5/src/bake/utils/__init__.py +11 -0
- bakefile-0.0.5/src/bake/utils/constants.py +21 -0
- {bakefile-0.0.4/src/bakefile → bakefile-0.0.5/src/bake/utils}/env.py +3 -1
- bakefile-0.0.5/src/bake/utils/exceptions.py +17 -0
- bakefile-0.0.5/src/bakelib/__init__.py +4 -0
- bakefile-0.0.5/src/bakelib/space/__init__.py +0 -0
- bakefile-0.0.5/src/bakelib/space/base.py +73 -0
- bakefile-0.0.5/src/bakelib/space/python.py +42 -0
- bakefile-0.0.5/src/bakelib/space/utils.py +55 -0
- bakefile-0.0.4/src/bakefile/__init__.py +0 -13
- bakefile-0.0.4/src/bakefile/cli/bake/__init__.py +0 -3
- bakefile-0.0.4/src/bakefile/cli/bake/main.py +0 -127
- bakefile-0.0.4/src/bakefile/cli/bake/resolve_bakebook.py +0 -103
- bakefile-0.0.4/src/bakefile/cli/bake/utils.py +0 -25
- bakefile-0.0.4/src/bakefile/cli/bakefile.py +0 -19
- bakefile-0.0.4/src/bakefile/cli/utils/version.py +0 -9
- bakefile-0.0.4/src/bakefile/exceptions.py +0 -9
- {bakefile-0.0.4 → bakefile-0.0.5}/README.md +0 -0
- {bakefile-0.0.4/src/bakefile/cli/utils → bakefile-0.0.5/src/bake/bakebook}/__init__.py +0 -0
- {bakefile-0.0.4/src/bakefile → bakefile-0.0.5/src/bake}/cli/__init__.py +0 -0
- /bakefile-0.0.4/src/bakefile/py.typed → /bakefile-0.0.5/src/bake/cli/common/__init__.py +0 -0
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: bakefile
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Author: Wisaroot Lertthaweedech
|
|
6
6
|
Author-email: Wisaroot Lertthaweedech <l.wisaroot@gmail.com>
|
|
7
|
+
Requires-Dist: beautysh>=6.4.2
|
|
8
|
+
Requires-Dist: click>=8.3.1
|
|
9
|
+
Requires-Dist: loguru>=0.7.3
|
|
10
|
+
Requires-Dist: orjson>=3.11.5
|
|
11
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
7
12
|
Requires-Dist: pydantic>=2.12.5
|
|
8
|
-
Requires-Dist:
|
|
13
|
+
Requires-Dist: rich>=14.2.0
|
|
14
|
+
Requires-Dist: ruff>=0.14.10
|
|
15
|
+
Requires-Dist: tomli>=2.0.0 ; python_full_version < '3.11'
|
|
16
|
+
Requires-Dist: ty>=0.0.8
|
|
17
|
+
Requires-Dist: typer>=0.21.0
|
|
18
|
+
Requires-Dist: uv>=0.9.20
|
|
19
|
+
Requires-Dist: pathspec>=1.0.3 ; extra == 'lib'
|
|
9
20
|
Requires-Python: >=3.10
|
|
21
|
+
Provides-Extra: lib
|
|
10
22
|
Description-Content-Type: text/markdown
|
|
11
23
|
|
|
12
24
|
[](https://github.com/wislertt/bakefile/actions/workflows/cd.yml)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bakefile"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.5" # use git tag
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -8,28 +8,52 @@ authors = [
|
|
|
8
8
|
]
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
11
|
+
"beautysh>=6.4.2",
|
|
12
|
+
"click>=8.3.1",
|
|
13
|
+
"loguru>=0.7.3",
|
|
14
|
+
"orjson>=3.11.5",
|
|
15
|
+
# "pathspec>=1.0.3",
|
|
16
|
+
"pydantic-settings>=2.0.0",
|
|
11
17
|
"pydantic>=2.12.5",
|
|
12
|
-
"
|
|
18
|
+
"rich>=14.2.0",
|
|
19
|
+
"ruff>=0.14.10",
|
|
20
|
+
"tomli>=2.0.0; python_version < '3.11'",
|
|
21
|
+
"ty>=0.0.8",
|
|
22
|
+
"typer>=0.21.0",
|
|
23
|
+
"uv>=0.9.20"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
lib = [
|
|
28
|
+
"pathspec>=1.0.3"
|
|
13
29
|
]
|
|
14
30
|
|
|
15
31
|
[project.scripts]
|
|
16
|
-
bake = "
|
|
17
|
-
bakefile = "
|
|
32
|
+
bake = "bake.cli.bake:main"
|
|
33
|
+
bakefile = "bake.cli.bakefile:main"
|
|
34
|
+
bf = "bake.cli.bakefile:main"
|
|
18
35
|
|
|
19
36
|
[dependency-groups]
|
|
20
37
|
dev = [
|
|
38
|
+
"deptry>=0.24.0",
|
|
21
39
|
"pre-commit>=4.5.1",
|
|
22
40
|
"pytest-cov>=7.0.0",
|
|
23
41
|
"pytest>=9.0.2",
|
|
24
|
-
"
|
|
25
|
-
"toml-sort>=0.24.3",
|
|
26
|
-
"ty>=0.0.5"
|
|
42
|
+
"toml-sort>=0.24.3"
|
|
27
43
|
]
|
|
28
44
|
|
|
29
45
|
[build-system]
|
|
30
46
|
requires = ["uv_build>=0.9.18,<0.10.0"]
|
|
31
47
|
build-backend = "uv_build"
|
|
32
48
|
|
|
49
|
+
[tool.deptry]
|
|
50
|
+
known_first_party = ["bake", "bakelib"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
markers = [
|
|
54
|
+
"integration: marks tests as integration tests (slower, full flow tests)"
|
|
55
|
+
]
|
|
56
|
+
|
|
33
57
|
[tool.ruff]
|
|
34
58
|
line-length = 100
|
|
35
59
|
|
|
@@ -37,11 +61,11 @@ line-length = 100
|
|
|
37
61
|
select = [
|
|
38
62
|
"ARG",
|
|
39
63
|
"B", # bugbear
|
|
40
|
-
"B9",
|
|
41
64
|
"C4", # flake8-comprehensions
|
|
42
65
|
"E", # pycodestyle (error)
|
|
43
66
|
"F", # pyflakes
|
|
44
67
|
"I", # isort
|
|
68
|
+
"N",
|
|
45
69
|
"PGH", # pygrep-hooks
|
|
46
70
|
"PIE", # flake8-pie
|
|
47
71
|
"PYI", # flake8-pyi
|
|
@@ -49,3 +73,7 @@ select = [
|
|
|
49
73
|
"SIM", # flake8-simplify
|
|
50
74
|
"UP" # pyupgrade
|
|
51
75
|
]
|
|
76
|
+
|
|
77
|
+
[tool.uv.build-backend]
|
|
78
|
+
module-name = ["bake", "bakelib"]
|
|
79
|
+
module-root = "src"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from bake.bakebook.bakebook import Bakebook
|
|
2
|
+
from bake.bakebook.decorator import command
|
|
3
|
+
from bake.cli.common.context import BakeCommand, Context
|
|
4
|
+
from bake.cli.utils.version import _get_version
|
|
5
|
+
from bake.ui import console
|
|
6
|
+
|
|
7
|
+
__version__ = _get_version()
|
|
8
|
+
|
|
9
|
+
__all__ = ["BakeCommand", "Bakebook", "Context", "__version__", "command", "console"]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import types
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from pydantic import PrivateAttr
|
|
7
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
8
|
+
from typer.core import TyperCommand
|
|
9
|
+
from typer.models import CommandFunctionType, Default
|
|
10
|
+
|
|
11
|
+
from bake.cli.common.context import BakeCommand
|
|
12
|
+
from bake.utils.constants import BAKE_COMMAND_KWARGS
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Bakebook(BaseSettings):
|
|
16
|
+
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
|
17
|
+
_app: typer.Typer = PrivateAttr(default_factory=typer.Typer)
|
|
18
|
+
|
|
19
|
+
def __init__(self, **kwargs):
|
|
20
|
+
super().__init__(**kwargs)
|
|
21
|
+
self._register_marked_methods()
|
|
22
|
+
|
|
23
|
+
def _get_command_kwargs(self, method: types.MethodType) -> dict[str, Any] | None:
|
|
24
|
+
func = method.__func__
|
|
25
|
+
|
|
26
|
+
if hasattr(func, BAKE_COMMAND_KWARGS):
|
|
27
|
+
return object.__getattribute__(func, BAKE_COMMAND_KWARGS)
|
|
28
|
+
|
|
29
|
+
method_name = method.__name__
|
|
30
|
+
for base in self.__class__.__mro__[1:]:
|
|
31
|
+
if not hasattr(base, method_name):
|
|
32
|
+
continue
|
|
33
|
+
parent_func = getattr(base, method_name)
|
|
34
|
+
if hasattr(parent_func, BAKE_COMMAND_KWARGS):
|
|
35
|
+
return object.__getattribute__(parent_func, BAKE_COMMAND_KWARGS)
|
|
36
|
+
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def _register_marked_methods(self) -> None:
|
|
40
|
+
base_names = set(dir(BaseSettings()))
|
|
41
|
+
method_names = [
|
|
42
|
+
name for name in dir(self) if not name.startswith("_") and name not in base_names
|
|
43
|
+
]
|
|
44
|
+
for name in method_names:
|
|
45
|
+
bound_method = getattr(self, name)
|
|
46
|
+
if not isinstance(bound_method, types.MethodType):
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
cmd_kwargs = self._get_command_kwargs(bound_method)
|
|
50
|
+
if cmd_kwargs:
|
|
51
|
+
self._app.command(**cmd_kwargs)(bound_method)
|
|
52
|
+
|
|
53
|
+
def command(
|
|
54
|
+
self,
|
|
55
|
+
name: str | None = None,
|
|
56
|
+
*,
|
|
57
|
+
cls: type[TyperCommand] | None = None,
|
|
58
|
+
context_settings: dict[Any, Any] | None = None,
|
|
59
|
+
help: str | None = None,
|
|
60
|
+
epilog: str | None = None,
|
|
61
|
+
short_help: str | None = None,
|
|
62
|
+
options_metavar: str | None = None,
|
|
63
|
+
add_help_option: bool = True,
|
|
64
|
+
no_args_is_help: bool = False,
|
|
65
|
+
hidden: bool = False,
|
|
66
|
+
deprecated: bool = False,
|
|
67
|
+
rich_help_panel: str | None = Default(None),
|
|
68
|
+
) -> Callable[[CommandFunctionType], CommandFunctionType]:
|
|
69
|
+
if cls is None:
|
|
70
|
+
cls = BakeCommand
|
|
71
|
+
|
|
72
|
+
return self._app.command(
|
|
73
|
+
name=name,
|
|
74
|
+
cls=cls,
|
|
75
|
+
context_settings=context_settings,
|
|
76
|
+
help=help,
|
|
77
|
+
epilog=epilog,
|
|
78
|
+
short_help=short_help,
|
|
79
|
+
options_metavar=options_metavar,
|
|
80
|
+
add_help_option=add_help_option,
|
|
81
|
+
no_args_is_help=no_args_is_help,
|
|
82
|
+
hidden=hidden,
|
|
83
|
+
deprecated=deprecated,
|
|
84
|
+
rich_help_panel=rich_help_panel,
|
|
85
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from typer.core import TyperCommand
|
|
5
|
+
from typer.models import CommandFunctionType, Default
|
|
6
|
+
|
|
7
|
+
from bake.cli.common.context import BakeCommand
|
|
8
|
+
from bake.utils.constants import BAKE_COMMAND_KWARGS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def command(
|
|
12
|
+
name: str | None = None,
|
|
13
|
+
*,
|
|
14
|
+
cls: type[TyperCommand] | None = None,
|
|
15
|
+
context_settings: dict[Any, Any] | None = None,
|
|
16
|
+
help: str | None = None,
|
|
17
|
+
epilog: str | None = None,
|
|
18
|
+
short_help: str | None = None,
|
|
19
|
+
options_metavar: str | None = None,
|
|
20
|
+
add_help_option: bool = True,
|
|
21
|
+
no_args_is_help: bool = False,
|
|
22
|
+
hidden: bool = False,
|
|
23
|
+
deprecated: bool = False,
|
|
24
|
+
rich_help_panel: str | None = Default(None),
|
|
25
|
+
) -> Callable[[CommandFunctionType], CommandFunctionType]:
|
|
26
|
+
if cls is None:
|
|
27
|
+
cls = BakeCommand
|
|
28
|
+
|
|
29
|
+
def decorator(func: CommandFunctionType) -> CommandFunctionType:
|
|
30
|
+
object.__setattr__(
|
|
31
|
+
func,
|
|
32
|
+
BAKE_COMMAND_KWARGS,
|
|
33
|
+
{
|
|
34
|
+
"name": name,
|
|
35
|
+
"cls": cls,
|
|
36
|
+
"context_settings": context_settings,
|
|
37
|
+
"help": help,
|
|
38
|
+
"epilog": epilog,
|
|
39
|
+
"short_help": short_help,
|
|
40
|
+
"options_metavar": options_metavar,
|
|
41
|
+
"add_help_option": add_help_option,
|
|
42
|
+
"no_args_is_help": no_args_is_help,
|
|
43
|
+
"hidden": hidden,
|
|
44
|
+
"deprecated": deprecated,
|
|
45
|
+
"rich_help_panel": rich_help_panel,
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
return func
|
|
49
|
+
|
|
50
|
+
return decorator
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import types
|
|
5
|
+
from collections.abc import Generator
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from importlib.abc import Loader
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
10
|
+
|
|
11
|
+
from bake.manage.find_python import is_standalone_bakefile
|
|
12
|
+
from bake.manage.run_uv import run_uv_sync
|
|
13
|
+
from bake.ui import console, style
|
|
14
|
+
from bake.ui.run import run_uv
|
|
15
|
+
from bake.utils.exceptions import BakebookError, BakefileNotFoundError
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from bake.bakebook.bakebook import Bakebook
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
T = TypeVar("T")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@contextmanager
|
|
26
|
+
def prepend_sys_path(path: str) -> Generator[None, None, None]:
|
|
27
|
+
path_existed = path in sys.path
|
|
28
|
+
if not path_existed:
|
|
29
|
+
sys.path.insert(0, path)
|
|
30
|
+
try:
|
|
31
|
+
yield
|
|
32
|
+
finally:
|
|
33
|
+
if not path_existed:
|
|
34
|
+
sys.path.remove(path)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_target_dir_path(chdir: Path, create_if_not_exist: bool) -> Path:
|
|
38
|
+
if not chdir.exists():
|
|
39
|
+
if create_if_not_exist:
|
|
40
|
+
chdir.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
else:
|
|
42
|
+
logger.debug(f"Directory not found: {chdir}")
|
|
43
|
+
raise BakefileNotFoundError(f"Directory not found: {chdir}")
|
|
44
|
+
if not chdir.is_dir():
|
|
45
|
+
logger.debug(f"Not a directory: {chdir}")
|
|
46
|
+
raise BakefileNotFoundError(f"Not a directory: {chdir}")
|
|
47
|
+
return chdir.resolve()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def resolve_bakefile_path(chdir: Path, file_name: str) -> Path:
|
|
51
|
+
target_dir_path = get_target_dir_path(chdir=chdir, create_if_not_exist=False)
|
|
52
|
+
bakefile_path = target_dir_path / file_name
|
|
53
|
+
logger.debug(f"Resolve bakefile path: {bakefile_path}", extra={"bakefile_path": bakefile_path})
|
|
54
|
+
return bakefile_path
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def retry_load_module_with_uv_sync(
|
|
58
|
+
target_dir_path: Path,
|
|
59
|
+
error: ImportError,
|
|
60
|
+
parent_dir: str,
|
|
61
|
+
loader: Loader,
|
|
62
|
+
module: types.ModuleType,
|
|
63
|
+
dry_run: bool = False,
|
|
64
|
+
):
|
|
65
|
+
logger.debug(f"Missing dependency: {error.name}. Running sync...")
|
|
66
|
+
|
|
67
|
+
sync_args = ["--all-groups", "--frozen", "--all-extras"]
|
|
68
|
+
|
|
69
|
+
if is_standalone_bakefile(target_dir_path):
|
|
70
|
+
# Standalone bakefile: use --script flag
|
|
71
|
+
run_uv_sync(
|
|
72
|
+
bakefile_path=target_dir_path,
|
|
73
|
+
cmd=sync_args,
|
|
74
|
+
dry_run=dry_run,
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
# Project-level: use uv sync directly
|
|
78
|
+
run_uv(
|
|
79
|
+
("sync", *sync_args),
|
|
80
|
+
cwd=parent_dir,
|
|
81
|
+
capture_output=True,
|
|
82
|
+
stream=True,
|
|
83
|
+
check=True,
|
|
84
|
+
echo=True,
|
|
85
|
+
dry_run=dry_run,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
loader.exec_module(module)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
error_message = (
|
|
92
|
+
f"Failed get bakebook from: {target_dir_path} even after sync.\n"
|
|
93
|
+
f"{e.__class__.__name__}: {e}"
|
|
94
|
+
)
|
|
95
|
+
logger.debug(error_message)
|
|
96
|
+
console.error(
|
|
97
|
+
"Failed to load bakebook after dependency sync. "
|
|
98
|
+
f"Try running {style.code('uv cache clean')} to resolve potential caching issues."
|
|
99
|
+
)
|
|
100
|
+
raise BakebookError(error_message) from e
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def load_module(target_dir_path: Path) -> types.ModuleType:
|
|
104
|
+
if not target_dir_path.exists():
|
|
105
|
+
logger.debug(f"Directory not found: {target_dir_path}")
|
|
106
|
+
raise BakefileNotFoundError(f"Directory not found: {target_dir_path}")
|
|
107
|
+
|
|
108
|
+
parent_dir = str(target_dir_path.parent)
|
|
109
|
+
module_name = "bakefile"
|
|
110
|
+
|
|
111
|
+
with prepend_sys_path(parent_dir):
|
|
112
|
+
spec = importlib.util.spec_from_file_location(module_name, target_dir_path)
|
|
113
|
+
if spec is None or spec.loader is None:
|
|
114
|
+
logger.debug(f"Failed to load bakebook module from: {target_dir_path}")
|
|
115
|
+
raise BakebookError(f"Failed to load: {target_dir_path}")
|
|
116
|
+
|
|
117
|
+
module: types.ModuleType = importlib.util.module_from_spec(spec)
|
|
118
|
+
sys.modules[module_name] = module
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
spec.loader.exec_module(module)
|
|
122
|
+
except ImportError as e:
|
|
123
|
+
retry_load_module_with_uv_sync(
|
|
124
|
+
target_dir_path=target_dir_path,
|
|
125
|
+
error=e,
|
|
126
|
+
parent_dir=parent_dir,
|
|
127
|
+
loader=spec.loader,
|
|
128
|
+
module=module,
|
|
129
|
+
)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
error_message = (
|
|
132
|
+
f"Failed get bakebook from: {target_dir_path}.\n{e.__class__.__name__}: {e}"
|
|
133
|
+
)
|
|
134
|
+
logger.debug(error_message)
|
|
135
|
+
raise BakebookError(error_message) from e
|
|
136
|
+
return module
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def validate_bakebook(bakebook: Any, bakebook_name: str) -> "Bakebook":
|
|
140
|
+
from bake.bakebook.bakebook import Bakebook
|
|
141
|
+
|
|
142
|
+
if not isinstance(bakebook, Bakebook):
|
|
143
|
+
logger.debug(
|
|
144
|
+
f"Invalid bakebook type for '{bakebook_name}': "
|
|
145
|
+
f"expected {Bakebook.__name__}, got {type(bakebook).__name__}"
|
|
146
|
+
)
|
|
147
|
+
raise BakebookError(
|
|
148
|
+
f"Bakebook '{bakebook_name}' must be a {Bakebook.__name__}, "
|
|
149
|
+
f"got {type(bakebook).__name__}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return bakebook
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_bakebook_from_module(
|
|
156
|
+
module: types.ModuleType, bakebook_name: str, target_dir_path: Path
|
|
157
|
+
) -> "Bakebook":
|
|
158
|
+
if not hasattr(module, bakebook_name):
|
|
159
|
+
logger.debug(f"Bakebook '{bakebook_name}' not found in {target_dir_path}")
|
|
160
|
+
raise BakebookError(f"No '{bakebook_name}' found in {target_dir_path}")
|
|
161
|
+
bakebook = getattr(module, bakebook_name)
|
|
162
|
+
bakebook = validate_bakebook(bakebook=bakebook, bakebook_name=bakebook_name)
|
|
163
|
+
return bakebook
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_bakebook_from_target_dir_path(
|
|
167
|
+
target_dir_path: Path,
|
|
168
|
+
bakebook_name: str,
|
|
169
|
+
) -> "Bakebook":
|
|
170
|
+
module = load_module(target_dir_path=target_dir_path)
|
|
171
|
+
bakebook = get_bakebook_from_module(
|
|
172
|
+
module=module, bakebook_name=bakebook_name, target_dir_path=target_dir_path
|
|
173
|
+
)
|
|
174
|
+
logger.debug(f"Successfully retrieved bakebook '{bakebook_name}' from {target_dir_path}")
|
|
175
|
+
return bakebook
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from bake.cli.bake.reinvocation import _reinvoke_with_detected_python
|
|
7
|
+
from bake.cli.common.app import (
|
|
8
|
+
add_completion,
|
|
9
|
+
bake_app_callback_with_obj,
|
|
10
|
+
rich_markup_mode,
|
|
11
|
+
)
|
|
12
|
+
from bake.cli.common.obj import get_bakefile_object, is_bakebook_optional
|
|
13
|
+
from bake.ui import console
|
|
14
|
+
from bake.utils import env
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@contextmanager
|
|
18
|
+
def set_argv(argv: list[str]):
|
|
19
|
+
original = sys.argv.copy()
|
|
20
|
+
sys.argv = argv
|
|
21
|
+
try:
|
|
22
|
+
yield
|
|
23
|
+
finally:
|
|
24
|
+
sys.argv = original
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _run_chain_commands(remaining_args: list[str], prog_name: str, bake_app: typer.Typer) -> int:
|
|
28
|
+
exit_code = 0
|
|
29
|
+
for cmd in remaining_args:
|
|
30
|
+
try:
|
|
31
|
+
with set_argv([prog_name, cmd]):
|
|
32
|
+
console.cmd(" ".join(sys.argv))
|
|
33
|
+
bake_app(prog_name=prog_name)
|
|
34
|
+
except SystemExit as e:
|
|
35
|
+
if e.code is not None and e.code != 0:
|
|
36
|
+
exit_code = e.code if isinstance(e.code, int) else 1
|
|
37
|
+
break
|
|
38
|
+
return exit_code
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
bakefile_obj = get_bakefile_object(rich_markup_mode=rich_markup_mode)
|
|
43
|
+
bakefile_obj.setup_logging()
|
|
44
|
+
bakefile_obj.resolve_bakefile_path()
|
|
45
|
+
|
|
46
|
+
# Check re-invocation with resolved bakefile path
|
|
47
|
+
# If re-invocation happens, process is replaced and we don't return
|
|
48
|
+
_reinvoke_with_detected_python(bakefile_obj.bakefile_path)
|
|
49
|
+
# If returned above, we're in the correct Python
|
|
50
|
+
|
|
51
|
+
bakefile_obj.get_bakebook(
|
|
52
|
+
allow_missing=is_bakebook_optional(remaining_args=bakefile_obj.remaining_args)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
bakefile_obj.warn_if_no_bakebook(color_echo=env.should_use_colors())
|
|
56
|
+
|
|
57
|
+
bake_app = typer.Typer(
|
|
58
|
+
add_completion=add_completion,
|
|
59
|
+
rich_markup_mode=rich_markup_mode,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
bake_app.callback(invoke_without_command=True)(bake_app_callback_with_obj(obj=bakefile_obj))
|
|
63
|
+
|
|
64
|
+
prog_name = "bake"
|
|
65
|
+
|
|
66
|
+
if bakefile_obj.bakebook is not None:
|
|
67
|
+
bake_app.add_typer(bakefile_obj.bakebook._app)
|
|
68
|
+
|
|
69
|
+
if bakefile_obj.is_chain_commands and bakefile_obj.remaining_args:
|
|
70
|
+
exit_code = _run_chain_commands(
|
|
71
|
+
remaining_args=bakefile_obj.remaining_args, prog_name=prog_name, bake_app=bake_app
|
|
72
|
+
)
|
|
73
|
+
raise SystemExit(exit_code)
|
|
74
|
+
bake_app(prog_name=prog_name)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from bake.utils.env import _BAKE_REINVOKED
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _reinvoke_with_detected_python(bakefile_path: Path | None) -> None:
|
|
13
|
+
"""Re-invoke bake CLI with detected Python if needed.
|
|
14
|
+
|
|
15
|
+
Checks if the current Python is the correct one for the bakefile.
|
|
16
|
+
If not, re-invokes the bake CLI with the detected Python using os.execve().
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
bakefile_path: Path to the bakefile, or None if not found
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
None. Either calls os.execve() (replaces process) or returns normally.
|
|
23
|
+
"""
|
|
24
|
+
# 1. Check marker to prevent infinite loops
|
|
25
|
+
if os.environ.get(_BAKE_REINVOKED):
|
|
26
|
+
logger.debug(
|
|
27
|
+
"Re-invocation marker set, skipping Python check",
|
|
28
|
+
extra={"sys.executable": sys.executable},
|
|
29
|
+
)
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
# 2. Try to find correct Python
|
|
33
|
+
try:
|
|
34
|
+
from bake.manage.find_python import find_python_path
|
|
35
|
+
|
|
36
|
+
python_path = find_python_path(bakefile_path)
|
|
37
|
+
except Exception:
|
|
38
|
+
logger.debug("Failed to find Python for bakefile, continuing with current Python")
|
|
39
|
+
return # Continue with current Python
|
|
40
|
+
|
|
41
|
+
# 3. Compare with current Python (don't resolve symlinks - we want the venv Python)
|
|
42
|
+
current_python = Path(sys.executable)
|
|
43
|
+
target_python = python_path
|
|
44
|
+
|
|
45
|
+
if current_python == target_python:
|
|
46
|
+
logger.debug(f"Already using correct Python: {current_python}")
|
|
47
|
+
return # Already correct
|
|
48
|
+
|
|
49
|
+
# 4. Re-invoke with detected Python
|
|
50
|
+
logger.debug(
|
|
51
|
+
f"Re-invoking bake with detected Python: {target_python} (current: {current_python})",
|
|
52
|
+
extra={"target_python": str(target_python)},
|
|
53
|
+
)
|
|
54
|
+
env = os.environ.copy()
|
|
55
|
+
env[_BAKE_REINVOKED] = "1"
|
|
56
|
+
|
|
57
|
+
sys.stdout.flush()
|
|
58
|
+
sys.stderr.flush()
|
|
59
|
+
result = subprocess.run(
|
|
60
|
+
[str(target_python), "-m", "bake.cli.bake", *sys.argv[1:]],
|
|
61
|
+
env=env,
|
|
62
|
+
)
|
|
63
|
+
raise SystemExit(result.returncode)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from bake.cli.common.context import Context
|
|
4
|
+
from bake.manage.add_inline import add_inline_metadata
|
|
5
|
+
from bake.ui import console, style
|
|
6
|
+
from bake.utils.exceptions import BakebookError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def add_inline(
|
|
10
|
+
ctx: Context,
|
|
11
|
+
) -> None:
|
|
12
|
+
"""Add PEP 723 inline metadata to bakefile.py.
|
|
13
|
+
|
|
14
|
+
For non-Python projects without pyproject.toml for dependency management.
|
|
15
|
+
"""
|
|
16
|
+
if ctx.obj.bakefile_path is None or not ctx.obj.bakefile_path.exists():
|
|
17
|
+
console.error(
|
|
18
|
+
f"Bakefile not found at {ctx.obj.bakefile_path}. "
|
|
19
|
+
f"Run {style.code('bakefile init --inline')} to create one."
|
|
20
|
+
)
|
|
21
|
+
raise typer.Exit(code=1)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
add_inline_metadata(ctx.obj.bakefile_path)
|
|
25
|
+
except BakebookError as e:
|
|
26
|
+
console.error(str(e))
|
|
27
|
+
raise typer.Exit(code=1) from None
|
|
28
|
+
|
|
29
|
+
console.success(f"Successfully added PEP 723 inline metadata to {ctx.obj.bakefile_path}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from bake.cli.common.context import Context
|
|
4
|
+
from bake.manage.find_python import find_python_path
|
|
5
|
+
from bake.ui import console
|
|
6
|
+
from bake.utils.exceptions import PythonNotFoundError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_python(
|
|
10
|
+
ctx: Context,
|
|
11
|
+
) -> None:
|
|
12
|
+
"""Find the Python interpreter path for the bakefile.py project."""
|
|
13
|
+
try:
|
|
14
|
+
python_path = find_python_path(ctx.obj.bakefile_path)
|
|
15
|
+
console.echo(python_path.as_posix())
|
|
16
|
+
except PythonNotFoundError as e:
|
|
17
|
+
console.error(str(e))
|
|
18
|
+
raise typer.Exit(code=1) from None
|