bakefile 0.0.4__py3-none-any.whl → 0.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. bake/__init__.py +9 -0
  2. bake/bakebook/bakebook.py +85 -0
  3. bake/bakebook/decorator.py +50 -0
  4. bake/bakebook/get.py +175 -0
  5. bake/cli/bake/__init__.py +3 -0
  6. bake/cli/bake/__main__.py +5 -0
  7. bake/cli/bake/main.py +74 -0
  8. bake/cli/bake/reinvocation.py +63 -0
  9. bake/cli/bakefile/__init__.py +3 -0
  10. bake/cli/bakefile/__main__.py +5 -0
  11. bake/cli/bakefile/add_inline.py +29 -0
  12. bake/cli/bakefile/find_python.py +18 -0
  13. bake/cli/bakefile/init.py +56 -0
  14. bake/cli/bakefile/lint.py +77 -0
  15. bake/cli/bakefile/main.py +41 -0
  16. bake/cli/bakefile/uv.py +146 -0
  17. bake/cli/common/app.py +54 -0
  18. bake/cli/common/callback.py +13 -0
  19. bake/cli/common/context.py +145 -0
  20. bake/cli/common/exception_handler.py +57 -0
  21. bake/cli/common/obj.py +214 -0
  22. bake/cli/common/params.py +72 -0
  23. bake/cli/utils/__init__.py +0 -0
  24. bake/cli/utils/version.py +18 -0
  25. bake/manage/__init__.py +0 -0
  26. bake/manage/add_inline.py +71 -0
  27. bake/manage/find_python.py +210 -0
  28. bake/manage/lint.py +101 -0
  29. bake/manage/run_uv.py +88 -0
  30. bake/manage/write_bakefile.py +20 -0
  31. bake/py.typed +0 -0
  32. bake/samples/__init__.py +0 -0
  33. bake/samples/simple.py +9 -0
  34. bake/ui/__init__.py +10 -0
  35. bake/ui/console.py +58 -0
  36. bake/ui/logger/__init__.py +33 -0
  37. bake/ui/logger/capsys.py +158 -0
  38. bake/ui/logger/setup.py +53 -0
  39. bake/ui/logger/utils.py +215 -0
  40. bake/ui/run/__init__.py +11 -0
  41. bake/ui/run/run.py +541 -0
  42. bake/ui/run/script.py +74 -0
  43. bake/ui/run/splitter.py +237 -0
  44. bake/ui/run/uv.py +83 -0
  45. bake/ui/style.py +2 -0
  46. bake/utils/__init__.py +11 -0
  47. bake/utils/constants.py +21 -0
  48. {bakefile → bake/utils}/env.py +3 -1
  49. bake/utils/exceptions.py +17 -0
  50. {bakefile-0.0.4.dist-info → bakefile-0.0.5.dist-info}/METADATA +14 -2
  51. bakefile-0.0.5.dist-info/RECORD +61 -0
  52. {bakefile-0.0.4.dist-info → bakefile-0.0.5.dist-info}/WHEEL +1 -1
  53. bakefile-0.0.5.dist-info/entry_points.txt +5 -0
  54. bakelib/__init__.py +4 -0
  55. bakelib/space/__init__.py +0 -0
  56. bakelib/space/base.py +73 -0
  57. bakelib/space/python.py +42 -0
  58. bakelib/space/utils.py +55 -0
  59. bakefile/__init__.py +0 -13
  60. bakefile/cli/bake/__init__.py +0 -3
  61. bakefile/cli/bake/main.py +0 -127
  62. bakefile/cli/bake/resolve_bakebook.py +0 -103
  63. bakefile/cli/bake/utils.py +0 -25
  64. bakefile/cli/bakefile.py +0 -19
  65. bakefile/cli/utils/version.py +0 -9
  66. bakefile/exceptions.py +0 -9
  67. bakefile-0.0.4.dist-info/RECORD +0 -16
  68. bakefile-0.0.4.dist-info/entry_points.txt +0 -4
  69. {bakefile/cli/utils → bake/bakebook}/__init__.py +0 -0
  70. {bakefile → bake}/cli/__init__.py +0 -0
  71. /bakefile/py.typed → /bake/cli/common/__init__.py +0 -0
bake/__init__.py ADDED
@@ -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
bake/bakebook/get.py ADDED
@@ -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,3 @@
1
+ from bake.cli.bake.main import main
2
+
3
+ __all__ = ["main"]
@@ -0,0 +1,5 @@
1
+ # This enables: python -m bakefile.cli.bake
2
+ # Standard Python pattern for running packages as modules
3
+ from .main import main
4
+
5
+ main()
bake/cli/bake/main.py ADDED
@@ -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,3 @@
1
+ from bake.cli.bakefile.main import main
2
+
3
+ __all__ = ["main"]
@@ -0,0 +1,5 @@
1
+ # This enables: python -m bakefile.cli.bakefile
2
+ # Standard Python pattern for running packages as modules
3
+ from .main import main
4
+
5
+ main()
@@ -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
@@ -0,0 +1,56 @@
1
+ from typing import Annotated
2
+
3
+ import typer
4
+
5
+ from bake.bakebook.get import (
6
+ resolve_bakefile_path,
7
+ )
8
+ from bake.cli.common.context import Context
9
+ from bake.cli.common.params import force_option
10
+ from bake.manage.add_inline import add_inline_metadata
11
+ from bake.manage.write_bakefile import write_bakefile
12
+ from bake.samples import simple
13
+ from bake.ui import console
14
+ from bake.utils.exceptions import BakebookError
15
+
16
+
17
+ def init(
18
+ ctx: Context,
19
+ force: force_option = False,
20
+ inline: Annotated[
21
+ bool, typer.Option("--inline", "-i", help="Create bakefile with PEP 723 inline metadata")
22
+ ] = False,
23
+ ) -> None:
24
+ """Create a new bakefile.py in the current directory."""
25
+
26
+ if ctx.obj.bakebook is not None and not force:
27
+ console.error("Bakebook already loaded. Use --force to override.")
28
+ raise typer.Exit(code=1)
29
+
30
+ ctx.obj.bakefile_path = resolve_bakefile_path(chdir=ctx.obj.chdir, file_name=ctx.obj.file_name)
31
+
32
+ if ctx.obj.bakefile_path.exists() and not force:
33
+ console.error(f"File already exists at {ctx.obj.bakefile_path}. Use --force to overwrite.")
34
+ raise typer.Exit(code=1)
35
+
36
+ write_bakefile(
37
+ bakefile_path=ctx.obj.bakefile_path,
38
+ bakebook_name=ctx.obj.bakebook_name,
39
+ sample_module=simple,
40
+ )
41
+ ctx.obj.get_bakebook(allow_missing=False)
42
+ assert ctx.obj.bakebook is not None
43
+
44
+ if inline:
45
+ try:
46
+ add_inline_metadata(ctx.obj.bakefile_path)
47
+ except BakebookError as e:
48
+ console.error(f"Failed to add PEP 723 metadata: {e}")
49
+ raise typer.Exit(code=1) from None
50
+
51
+ console.success(
52
+ f"Successfully created bakefile with PEP 723 metadata at {ctx.obj.bakefile_path}"
53
+ )
54
+ return
55
+
56
+ console.success(f"Successfully created bakefile at {ctx.obj.bakefile_path}")
@@ -0,0 +1,77 @@
1
+ from typing import Annotated
2
+
3
+ import typer
4
+
5
+ from bake.cli.common.context import Context
6
+ from bake.manage.find_python import find_python_path
7
+ from bake.manage.lint import run_ruff_check, run_ruff_format, run_ty_check
8
+ from bake.ui import console
9
+
10
+
11
+ def lint(
12
+ ctx: Context,
13
+ only_bakefile: Annotated[
14
+ bool,
15
+ typer.Option("--only-bakefile", "-b", help="Only lint the bakefile, not entire project"),
16
+ ] = False,
17
+ ruff_format: Annotated[
18
+ bool,
19
+ typer.Option("--ruff-format/--no-ruff-format", show_default=False),
20
+ ] = True,
21
+ ruff_check: Annotated[
22
+ bool,
23
+ typer.Option("--ruff-check/--no-ruff-check", show_default=False),
24
+ ] = True,
25
+ ty_check: Annotated[
26
+ bool,
27
+ typer.Option("--ty/--no-ty", show_default=False),
28
+ ] = True,
29
+ ) -> None:
30
+ """
31
+ Quick and strict lint your bakefile.py.
32
+
33
+ Simple way to ensure your bakefile follows standard formatting
34
+ and type safety best practices. By default, also lints all Python
35
+ files in your project. For advanced linter configuration, use
36
+ ruff and ty directly.
37
+
38
+ By default, runs: ruff format, ruff check, ty check
39
+
40
+ Examples:
41
+ bakefile lint # Lint bakefile.py and all Python files
42
+ bakefile lint -b # Lint only bakefile.py
43
+ bakefile lint --no-ty # Skip type checking
44
+ """
45
+ bakefile_path = ctx.obj.bakefile_path
46
+ if bakefile_path is None or not bakefile_path.exists():
47
+ console.error("Bakefile not found. Run 'bakefile init' first.")
48
+ raise typer.Exit(code=1)
49
+
50
+ if not any([ruff_format, ruff_check, ty_check]):
51
+ console.warning("All linters disabled. Nothing to do.")
52
+ raise typer.Exit(code=0)
53
+
54
+ try:
55
+ if ruff_format:
56
+ run_ruff_format(
57
+ bakefile_path, only_bakefile=only_bakefile, check=True, dry_run=ctx.obj.dry_run
58
+ )
59
+
60
+ if ruff_check:
61
+ run_ruff_check(
62
+ bakefile_path, only_bakefile=only_bakefile, check=True, dry_run=ctx.obj.dry_run
63
+ )
64
+
65
+ if ty_check:
66
+ python_path = find_python_path(bakefile_path)
67
+ run_ty_check(
68
+ bakefile_path,
69
+ python_path,
70
+ only_bakefile=only_bakefile,
71
+ check=True,
72
+ dry_run=ctx.obj.dry_run,
73
+ )
74
+ except typer.Exit as e:
75
+ if e.exit_code != 0:
76
+ console.error("Linting failed.")
77
+ raise typer.Exit(code=e.exit_code) from e