bakefile 0.0.3__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 (67) 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. bake/utils/env.py +10 -0
  49. bake/utils/exceptions.py +17 -0
  50. {bakefile-0.0.3.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.3.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/cli/bake/__init__.py +0 -3
  60. bakefile/cli/bake/main.py +0 -17
  61. bakefile/cli/bake/resolve_bakebook.py +0 -75
  62. bakefile/cli/bakefile.py +0 -8
  63. bakefile-0.0.3.dist-info/RECORD +0 -11
  64. bakefile-0.0.3.dist-info/entry_points.txt +0 -4
  65. {bakefile → bake/bakebook}/__init__.py +0 -0
  66. {bakefile → bake}/cli/__init__.py +0 -0
  67. /bakefile/py.typed → /bake/cli/common/__init__.py +0 -0
@@ -0,0 +1,72 @@
1
+ from pathlib import Path
2
+ from typing import Annotated
3
+
4
+ import typer
5
+
6
+ from bake.cli.common.callback import validate_file_name_callback
7
+ from bake.cli.utils.version import version_callback
8
+
9
+
10
+ def verbosity_callback(_ctx: typer.Context, _param: typer.CallbackParam, value: int) -> int:
11
+ """Validate verbosity level (max 2)."""
12
+ if value > 2:
13
+ raise typer.BadParameter("Maximum verbosity is -vv")
14
+ return value
15
+
16
+
17
+ # ==========================================================
18
+ # Bakefile CLI Parameters
19
+ # ==========================================================
20
+ chdir_option = Annotated[
21
+ Path,
22
+ typer.Option(
23
+ "-C",
24
+ "--chdir",
25
+ help="Change directory before running",
26
+ ),
27
+ ]
28
+ file_name_option = Annotated[
29
+ str,
30
+ typer.Option(
31
+ "--file-name",
32
+ "-f",
33
+ help="Path to bakefile.py",
34
+ callback=validate_file_name_callback,
35
+ ),
36
+ ]
37
+ bakebook_name_option = Annotated[
38
+ str, typer.Option("--book-name", "-b", help="Name of bakebook object to retrieve")
39
+ ]
40
+ version_option = Annotated[
41
+ bool,
42
+ typer.Option(
43
+ "--version",
44
+ help="Show version",
45
+ callback=version_callback,
46
+ is_eager=True,
47
+ ),
48
+ ]
49
+ is_chain_commands_option = Annotated[bool, typer.Option("--chain", "-c", help="Chain commands")]
50
+ remaining_args_argument = Annotated[list[str] | None, typer.Argument()]
51
+
52
+ verbosity_option = Annotated[
53
+ int,
54
+ typer.Option(
55
+ "-v",
56
+ "--verbose",
57
+ help="Increase verbosity (-v for info, -vv for debug)",
58
+ count=True,
59
+ callback=verbosity_callback,
60
+ ),
61
+ ]
62
+ dry_run_option = Annotated[
63
+ bool,
64
+ typer.Option("-n", "--dry-run", help="Dry run (show what would be done without executing)"),
65
+ ]
66
+
67
+ # ==========================================================
68
+ # Bakefile Local CLI Frequently Used Params
69
+ # ==========================================================
70
+ force_option = Annotated[
71
+ bool | None, typer.Option("--force/--no-force", "-f", help="Force execution")
72
+ ]
File without changes
@@ -0,0 +1,18 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ import typer
4
+
5
+ from bake.ui import console
6
+
7
+
8
+ def _get_version() -> str:
9
+ try:
10
+ return version("bakefile")
11
+ except PackageNotFoundError:
12
+ return "0.0.0"
13
+
14
+
15
+ def version_callback(value: bool) -> None:
16
+ if value:
17
+ console.out.print(_get_version(), style=None, highlight=False)
18
+ raise typer.Exit()
File without changes
@@ -0,0 +1,71 @@
1
+ import logging
2
+ import re
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from bake.ui import console, run_uv
8
+ from bake.utils.exceptions import BakebookError
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ if sys.version_info >= (3, 11):
13
+ import tomllib
14
+ else:
15
+ import tomli as tomllib # type: ignore[import-not-found]
16
+
17
+
18
+ def add_inline_metadata(bakefile_path: Path) -> None:
19
+ if not bakefile_path.exists():
20
+ logger.error(f"Bakefile not found at {bakefile_path}")
21
+ raise BakebookError(
22
+ f"Bakefile not found at {bakefile_path}. "
23
+ f"Run 'bakefile init --inline' to create a new bakefile with PEP 723 metadata."
24
+ )
25
+
26
+ result = run_uv(
27
+ ["init", "--script", str(bakefile_path.name)],
28
+ check=False,
29
+ cwd=bakefile_path.parent,
30
+ echo=False,
31
+ )
32
+
33
+ is_already_pep723 = result.returncode == 2 and "is already a PEP 723 script" in result.stderr
34
+
35
+ is_valid_output = result.returncode == 0 or is_already_pep723
36
+
37
+ if not is_valid_output:
38
+ command: str = " ".join(result.args)
39
+ logger.error(f"Failed to initialize PEP 723 metadata for {bakefile_path}")
40
+ raise BakebookError(
41
+ f"Failed to initialize PEP 723 metadata.\n\n"
42
+ f"Command: `{command}`\n\n"
43
+ f"Error: {result.stderr.strip()}"
44
+ )
45
+
46
+ if is_already_pep723:
47
+ console.warning(f"{bakefile_path.name} already has PEP 723 metadata")
48
+
49
+ run_uv(
50
+ ["add", "bakefile", "--script", str(bakefile_path.name)],
51
+ cwd=bakefile_path.parent,
52
+ echo=False,
53
+ )
54
+
55
+
56
+ def read_inline(bakefile_path: Path) -> dict[str, Any] | None:
57
+ inline_regex = r"(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$"
58
+ script = bakefile_path.read_text()
59
+ name = "script"
60
+ matches = list(filter(lambda m: m.group("type") == name, re.finditer(inline_regex, script)))
61
+
62
+ if len(matches) > 1:
63
+ raise ValueError(f"Multiple {name} blocks found")
64
+ elif len(matches) == 1:
65
+ content = "".join(
66
+ line[2:] if line.startswith("# ") else line[1:]
67
+ for line in matches[0].group("content").splitlines(keepends=True)
68
+ )
69
+ return tomllib.loads(content)
70
+ else:
71
+ return None
@@ -0,0 +1,210 @@
1
+ import logging
2
+ import re
3
+ from pathlib import Path
4
+
5
+ from bake.manage.add_inline import read_inline
6
+ from bake.ui import run_uv
7
+ from bake.utils import BakebookError
8
+ from bake.utils.exceptions import PythonNotFoundError
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def is_standalone_bakefile(bakefile_path: Path) -> bool:
14
+ inline_metadata = read_inline(bakefile_path)
15
+ if inline_metadata is None:
16
+ return False
17
+
18
+ dependencies = inline_metadata.get("dependencies", [])
19
+ has_bakefile = any(dep.startswith("bakefile") for dep in dependencies)
20
+
21
+ if not has_bakefile:
22
+ logger.error(
23
+ f"Invalid inline metadata in {bakefile_path}: "
24
+ f"PEP 723 metadata exists but 'bakefile' dependency is missing"
25
+ )
26
+ raise BakebookError(
27
+ f"Invalid inline metadata in {bakefile_path}: "
28
+ f"PEP 723 metadata exists but 'bakefile' dependency is missing. "
29
+ f"Run 'bakefile add-inline' to fix."
30
+ )
31
+
32
+ return True
33
+
34
+
35
+ def _find_bakefile_lock(bakefile_path: Path) -> Path | None:
36
+ """Find bakefile-level lock (<bakefile.py.lock>)."""
37
+ lock_path = bakefile_path.with_suffix(bakefile_path.suffix + ".lock")
38
+ if lock_path.exists():
39
+ logger.debug(f"Found bakefile lock at {lock_path}")
40
+ return lock_path
41
+ logger.debug("No bakefile lock found")
42
+ return None
43
+
44
+
45
+ def _find_project_lock(bakefile_path: Path) -> Path | None:
46
+ """Find project-level uv.lock by searching up directory tree."""
47
+ current_dir = bakefile_path.parent
48
+ for _ in range(10): # Limit search depth
49
+ uv_lock = current_dir / "uv.lock"
50
+ if uv_lock.exists():
51
+ logger.debug(f"Found project lock at {uv_lock}")
52
+ return uv_lock
53
+ parent = current_dir.parent
54
+ if parent == current_dir: # Reached root
55
+ break
56
+ current_dir = parent
57
+ logger.debug("No project lock found")
58
+ return None
59
+
60
+
61
+ def _find_bakefile_python(bakefile_path: Path) -> Path | None:
62
+ # References:
63
+ # https://github.com/astral-sh/uv/blob/543f1f3f5924d1d2734fd718381e6f0d0f6f70b5/crates/uv/src/commands/project/mod.rs#L843
64
+
65
+ kind = "script"
66
+ found_bakefile_level_venv_message = (
67
+ f"The {kind} environment's Python version satisfies the request"
68
+ )
69
+ result = run_uv(
70
+ ["python", "find", "--script", str(bakefile_path.name), "-v"],
71
+ check=False,
72
+ cwd=bakefile_path.parent,
73
+ echo=False,
74
+ )
75
+
76
+ is_bakefile_python_found = (
77
+ result.returncode == 0 and found_bakefile_level_venv_message in result.stderr.strip()
78
+ )
79
+
80
+ if is_bakefile_python_found:
81
+ python_path = Path(result.stdout.strip())
82
+ logger.debug(f"Found bakefile Python at {python_path.as_posix()}")
83
+ return python_path
84
+
85
+ logger.debug("No bakefile Python found")
86
+ return None
87
+
88
+
89
+ def _find_project_python(bakefile_path: Path) -> Path | None:
90
+ """Find Python from project-level venv using uv python find -v."""
91
+ # References:
92
+ # https://github.com/astral-sh/uv/blob/543f1f3f5924d1d2734fd718381e6f0d0f6f70b5/crates/uv-python/src/discovery.rs#L795
93
+ # https://github.com/astral-sh/uv/blob/543f1f3f5924d1d2734fd718381e6f0d0f6f70b5/crates/uv-python/src/discovery.rs#L3169-L3184
94
+ result = run_uv(
95
+ ["python", "find", "-v"],
96
+ check=False,
97
+ cwd=bakefile_path.parent,
98
+ echo=False,
99
+ )
100
+
101
+ # Check if stderr contains "Found `...` at `...` (...)"
102
+ # where source is "active virtual environment" or "virtual environment"
103
+ stderr = result.stderr.strip()
104
+ pattern = r"Found `[^`]+` at `[^`]+` \(([^)]+)\)"
105
+ match = re.search(pattern, stderr)
106
+
107
+ if result.returncode == 0 and match:
108
+ source = match.group(1)
109
+ if source in {"active virtual environment", "virtual environment"}:
110
+ python_path = Path(result.stdout.strip())
111
+ logger.debug(f"Found project Python at {python_path} (source: {source})")
112
+ return python_path
113
+
114
+ logger.debug("No project Python found")
115
+ return None
116
+
117
+
118
+ def _create_bakefile_venv(bakefile_path: Path) -> Path | None:
119
+ """Create bakefile-level venv and return Python path."""
120
+ lock_path = _find_bakefile_lock(bakefile_path)
121
+
122
+ if lock_path:
123
+ # Use frozen sync if lock exists
124
+ logger.debug("Syncing bakefile with frozen lock")
125
+ run_uv(
126
+ ["sync", "--script", str(bakefile_path.name), "--frozen"],
127
+ check=True,
128
+ cwd=bakefile_path.parent,
129
+ echo=False,
130
+ )
131
+ else:
132
+ # Create new lock and sync
133
+ logger.debug("Creating bakefile lock and syncing")
134
+ run_uv(
135
+ ["sync", "--script", str(bakefile_path.name)],
136
+ check=True,
137
+ cwd=bakefile_path.parent,
138
+ echo=False,
139
+ )
140
+ run_uv(
141
+ ["lock", "--script", str(bakefile_path.name)],
142
+ check=True,
143
+ cwd=bakefile_path.parent,
144
+ echo=False,
145
+ )
146
+
147
+ return _find_bakefile_python(bakefile_path)
148
+
149
+
150
+ def _create_project_venv(bakefile_path: Path) -> Path | None:
151
+ """Create project-level venv and return Python path."""
152
+ work_dir = bakefile_path.parent
153
+
154
+ # Check if pyproject.toml exists
155
+ pyproject = work_dir / "pyproject.toml"
156
+ if not pyproject.exists():
157
+ logger.debug("No pyproject.toml found, cannot create project venv")
158
+ return None
159
+
160
+ lock_path = _find_project_lock(bakefile_path)
161
+
162
+ if lock_path:
163
+ # Use frozen sync if lock exists
164
+ logger.debug("Syncing project with frozen lock")
165
+ run_uv(["sync", "--frozen"], check=True, cwd=work_dir, echo=False)
166
+ else:
167
+ # Create new lock and sync
168
+ logger.debug("Creating project lock and syncing")
169
+ run_uv(["lock"], check=True, cwd=work_dir, echo=False)
170
+ run_uv(["sync"], check=True, cwd=work_dir, echo=False)
171
+
172
+ return _find_project_python(bakefile_path)
173
+
174
+
175
+ def find_python_path(bakefile_path: Path | None) -> Path:
176
+ if bakefile_path is None or not bakefile_path.exists():
177
+ raise PythonNotFoundError(f"Bakefile not found at {bakefile_path}")
178
+
179
+ is_standalone = is_standalone_bakefile(bakefile_path)
180
+
181
+ if is_standalone:
182
+ logger.debug("Bakefile has inline metadata -> bakefile-level Python")
183
+
184
+ # Step 1: Try to find existing bakefile-level Python
185
+ python_path = _find_bakefile_python(bakefile_path)
186
+ if python_path:
187
+ return python_path
188
+
189
+ # Step 2: Create bakefile-level venv
190
+ python_path = _create_bakefile_venv(bakefile_path)
191
+ if python_path:
192
+ return python_path
193
+
194
+ else:
195
+ logger.debug("No inline metadata -> project-level Python")
196
+
197
+ # Step 1: Try to find existing project-level Python
198
+ python_path = _find_project_python(bakefile_path)
199
+ if python_path:
200
+ return python_path
201
+
202
+ # Step 2: Create project-level venv
203
+ python_path = _create_project_venv(bakefile_path)
204
+ if python_path:
205
+ return python_path
206
+
207
+ raise PythonNotFoundError(
208
+ f"Could not find Python for {bakefile_path}. "
209
+ f"Run 'bakefile add-inline' to add PEP 723 metadata for bakefile-level Python."
210
+ )
bake/manage/lint.py ADDED
@@ -0,0 +1,101 @@
1
+ import logging
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+ from ruff.__main__ import find_ruff_bin
6
+ from ty.__main__ import find_ty_bin
7
+
8
+ from bake.ui import console
9
+ from bake.ui.run import run
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def run_ruff(
15
+ bakefile_path: Path,
16
+ subcommand: str,
17
+ args: list[str],
18
+ *,
19
+ only_bakefile: bool = False,
20
+ check: bool = True,
21
+ dry_run: bool = False,
22
+ ) -> subprocess.CompletedProcess[str]:
23
+ ruff_bin = find_ruff_bin()
24
+ target = bakefile_path.name if only_bakefile else "."
25
+ cmd = [subcommand, *args, target]
26
+ display_cmd = "ruff " + " ".join(cmd)
27
+ console.cmd(display_cmd)
28
+ return run(
29
+ [str(ruff_bin), *cmd],
30
+ cwd=bakefile_path.parent,
31
+ capture_output=True,
32
+ stream=True,
33
+ check=check,
34
+ echo=False,
35
+ dry_run=dry_run,
36
+ )
37
+
38
+
39
+ def run_ruff_format(
40
+ bakefile_path: Path,
41
+ *,
42
+ only_bakefile: bool = False,
43
+ check: bool = True,
44
+ dry_run: bool = False,
45
+ ) -> subprocess.CompletedProcess[str]:
46
+ return run_ruff(
47
+ bakefile_path=bakefile_path,
48
+ subcommand="format",
49
+ args=["--exit-non-zero-on-format"],
50
+ only_bakefile=only_bakefile,
51
+ check=check,
52
+ dry_run=dry_run,
53
+ )
54
+
55
+
56
+ def run_ruff_check(
57
+ bakefile_path: Path,
58
+ *,
59
+ only_bakefile: bool = False,
60
+ check: bool = True,
61
+ dry_run: bool = False,
62
+ ) -> subprocess.CompletedProcess[str]:
63
+ return run_ruff(
64
+ bakefile_path=bakefile_path,
65
+ subcommand="check",
66
+ args=[
67
+ "--fix",
68
+ "--exit-non-zero-on-fix",
69
+ "--extend-select",
70
+ "ARG,B,C4,E,F,I,N,PGH,PIE,PYI,RUF,SIM,UP",
71
+ ],
72
+ only_bakefile=only_bakefile,
73
+ check=check,
74
+ dry_run=dry_run,
75
+ )
76
+
77
+
78
+ def run_ty_check(
79
+ bakefile_path: Path,
80
+ python_path: Path,
81
+ *,
82
+ only_bakefile: bool = False,
83
+ check: bool = True,
84
+ dry_run: bool = False,
85
+ ) -> subprocess.CompletedProcess[str]:
86
+ ty_bin = find_ty_bin()
87
+ cmd = ["check", "--error-on-warning", "--python", str(python_path)]
88
+ if only_bakefile:
89
+ cmd.append(bakefile_path.name)
90
+
91
+ display_cmd = "ty " + " ".join(cmd)
92
+ console.cmd(display_cmd)
93
+ return run(
94
+ [str(ty_bin), *cmd],
95
+ cwd=bakefile_path.parent,
96
+ capture_output=True,
97
+ stream=True,
98
+ check=check,
99
+ echo=False,
100
+ dry_run=dry_run,
101
+ )
bake/manage/run_uv.py ADDED
@@ -0,0 +1,88 @@
1
+ import logging
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+ from bake.manage.find_python import find_python_path, is_standalone_bakefile
6
+ from bake.ui import console, style
7
+ from bake.ui.run import run, run_uv
8
+ from bake.utils import BakebookError
9
+ from bake.utils.exceptions import PythonNotFoundError
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def _run_uv(
15
+ bakefile_path: Path | None, command_name: str, cmd: list[str], dry_run: bool = False
16
+ ) -> subprocess.CompletedProcess[str] | subprocess.CompletedProcess[None]:
17
+ if bakefile_path is None or not bakefile_path.exists():
18
+ raise PythonNotFoundError(f"Bakefile not found at {bakefile_path}")
19
+
20
+ if not is_standalone_bakefile(bakefile_path):
21
+ error_msg = (
22
+ f"`{command_name}` command requires PEP 723 inline metadata in the bakefile. "
23
+ f"Run {style.code('bakefile add-inline')} to add metadata, "
24
+ f"or use {style.code(f'uv {command_name}')} directly for project-level dependencies."
25
+ )
26
+ raise BakebookError(error_msg)
27
+
28
+ logger.debug(f"Running `uv {command_name}` for {bakefile_path}")
29
+ result = run_uv(
30
+ (command_name, "--script", bakefile_path.name, *cmd),
31
+ capture_output=True,
32
+ stream=True,
33
+ check=True,
34
+ echo=True,
35
+ cwd=bakefile_path.parent,
36
+ dry_run=dry_run,
37
+ )
38
+ return result
39
+
40
+
41
+ def run_uv_add(
42
+ bakefile_path: Path | None, cmd: list[str], dry_run: bool
43
+ ) -> subprocess.CompletedProcess[str] | subprocess.CompletedProcess[None]:
44
+ return _run_uv(bakefile_path=bakefile_path, command_name="add", cmd=cmd, dry_run=dry_run)
45
+
46
+
47
+ def run_uv_lock(
48
+ bakefile_path: Path | None, cmd: list[str], dry_run: bool
49
+ ) -> subprocess.CompletedProcess[str] | subprocess.CompletedProcess[None]:
50
+ return _run_uv(bakefile_path=bakefile_path, command_name="lock", cmd=cmd, dry_run=dry_run)
51
+
52
+
53
+ def run_uv_sync(
54
+ bakefile_path: Path | None, cmd: list[str], dry_run: bool
55
+ ) -> subprocess.CompletedProcess[str] | subprocess.CompletedProcess[None]:
56
+ return _run_uv(bakefile_path=bakefile_path, command_name="sync", cmd=cmd, dry_run=dry_run)
57
+
58
+
59
+ def run_uv_pip(
60
+ bakefile_path: Path | None, cmd: list[str], dry_run: bool
61
+ ) -> subprocess.CompletedProcess[str] | subprocess.CompletedProcess[None]:
62
+ if bakefile_path is None or not bakefile_path.exists():
63
+ raise PythonNotFoundError(f"Bakefile not found at {bakefile_path}")
64
+
65
+ is_standalone = is_standalone_bakefile(bakefile_path)
66
+ if not is_standalone:
67
+ console.warning(
68
+ "No PEP 723 inline metadata found. Using project-level Python.\n"
69
+ f"For project-level dependencies, consider using {style.code('uv pip')} directly.\n"
70
+ )
71
+
72
+ python_path = find_python_path(bakefile_path)
73
+
74
+ version_result = run(
75
+ [str(python_path), "--version"], capture_output=True, stream=False, echo=False
76
+ )
77
+ version = version_result.stdout.strip() or version_result.stderr.strip()
78
+ console.err.print(f"Using {version}\n")
79
+
80
+ logger.debug(f"Running uv pip with cmd: {cmd}")
81
+ return run_uv(
82
+ ("pip", *cmd, "--python", str(python_path)),
83
+ capture_output=True,
84
+ stream=True,
85
+ check=True,
86
+ echo=True,
87
+ dry_run=dry_run,
88
+ )
@@ -0,0 +1,20 @@
1
+ import types
2
+ from pathlib import Path
3
+
4
+ from bake.utils.constants import BAKEBOOK_NAME_IN_SAMPLES
5
+
6
+
7
+ def write_bakefile(
8
+ bakefile_path: Path, bakebook_name: str, sample_module: types.ModuleType
9
+ ) -> None:
10
+ if not hasattr(sample_module, BAKEBOOK_NAME_IN_SAMPLES):
11
+ raise ValueError(
12
+ f"Module `{sample_module.__name__}` must have `{BAKEBOOK_NAME_IN_SAMPLES}` attribute"
13
+ )
14
+
15
+ if sample_module.__file__ is None:
16
+ raise ValueError(f"Could not find `{sample_module.__name__}`")
17
+
18
+ original_bakefile_content = Path(sample_module.__file__).read_text()
19
+ customized_content = original_bakefile_content.replace(BAKEBOOK_NAME_IN_SAMPLES, bakebook_name)
20
+ bakefile_path.write_text(customized_content)
bake/py.typed ADDED
File without changes
File without changes
bake/samples/simple.py ADDED
@@ -0,0 +1,9 @@
1
+ from bake.bakebook.bakebook import Bakebook
2
+ from bake.ui import console
3
+
4
+ __bakebook__ = Bakebook()
5
+
6
+
7
+ @__bakebook__.command()
8
+ def hello(name: str = "world"):
9
+ console.echo(f"Hello {name}!")
bake/ui/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ from bake.ui import console
2
+ from bake.ui.logger.setup import setup_logging
3
+ from bake.ui.run import run, run_uv
4
+
5
+ __all__ = [
6
+ "console",
7
+ "run",
8
+ "run_uv",
9
+ "setup_logging",
10
+ ]
bake/ui/console.py ADDED
@@ -0,0 +1,58 @@
1
+ import textwrap
2
+ from typing import Any
3
+
4
+ from beautysh import BashFormatter
5
+ from rich.console import Console
6
+
7
+ out = Console(stderr=False)
8
+ err = Console(stderr=True)
9
+
10
+ BOLD_GREEN = "bold green"
11
+
12
+
13
+ def _print(
14
+ console_obj: Console, emoji: str | None, label: str, style: str, message: str, **kwargs
15
+ ) -> None:
16
+ formatted_label = f"[{label}]" if console_obj.no_color or out.color_system is None else label
17
+
18
+ emoji = emoji + " " if emoji else ""
19
+ console_obj.print(f"[{style}]{emoji}{formatted_label}[/{style}] {message}", **kwargs)
20
+
21
+
22
+ def success(message: str, **kwargs) -> None:
23
+ _print(out, ":white_check_mark:", "SUCCESS", BOLD_GREEN, message, **kwargs)
24
+
25
+
26
+ def echo(message: Any, **kwargs) -> None:
27
+ out.print(message, **kwargs)
28
+
29
+
30
+ def cmd(cmd_str: str, **kwargs) -> None:
31
+ err.print(f"[bold green]❯[/bold green] [default]{cmd_str}[/default]", **kwargs) # noqa: RUF001
32
+
33
+
34
+ def script_block(title: str, script: str, **kwargs) -> None:
35
+ formatter = BashFormatter()
36
+ formatted, error = formatter.beautify_string(script)
37
+
38
+ if error:
39
+ formatted = textwrap.dedent(script)
40
+
41
+ terminal_width: int = err.size.width
42
+ width = min(70, terminal_width)
43
+ bold_line = "━" * width
44
+ thin_line = "─" * width
45
+
46
+ err.print(bold_line, style=BOLD_GREEN)
47
+ err.print(title, style="bold")
48
+ err.print(thin_line, style=BOLD_GREEN)
49
+ err.print(formatted, highlight=False, **kwargs)
50
+ err.print(bold_line, style=BOLD_GREEN)
51
+
52
+
53
+ def warning(message: str, **kwargs) -> None:
54
+ _print(err, ":warning-emoji: ", "WARNING", "bold yellow", message, **kwargs)
55
+
56
+
57
+ def error(message: str, **kwargs) -> None:
58
+ _print(err, ":x:", "ERROR", "bold red", message, **kwargs)