bakefile 0.0.10__py3-none-any.whl → 0.0.11__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.
- bake/cli/bake/main.py +51 -9
- bake/cli/bake/reinvocation.py +6 -3
- bake/cli/bakefile/main.py +38 -4
- bake/cli/common/app.py +35 -33
- bake/cli/common/context.py +4 -0
- bake/cli/common/obj.py +4 -3
- bake/cli/common/params.py +10 -2
- bake/manage/lint.py +10 -7
- bake/ui/console.py +73 -9
- bake/ui/run/run.py +45 -13
- bake/ui/run/uv.py +2 -10
- bake/utils/__init__.py +8 -2
- bake/utils/settings.py +25 -0
- {bakefile-0.0.10.dist-info → bakefile-0.0.11.dist-info}/METADATA +3 -1
- {bakefile-0.0.10.dist-info → bakefile-0.0.11.dist-info}/RECORD +26 -22
- {bakefile-0.0.10.dist-info → bakefile-0.0.11.dist-info}/WHEEL +1 -1
- bakelib/__init__.py +2 -0
- bakelib/refreshable_cache/__init__.py +17 -0
- bakelib/refreshable_cache/cache.py +250 -0
- bakelib/refreshable_cache/exceptions.py +2 -0
- bakelib/space/base.py +26 -12
- bakelib/space/lib.py +161 -0
- bakelib/space/python.py +17 -0
- bakelib/space/python_lib.py +77 -0
- bakelib/space/utils.py +10 -0
- bake/cli/common/callback.py +0 -13
- bake/utils/env.py +0 -10
- {bakefile-0.0.10.dist-info → bakefile-0.0.11.dist-info}/entry_points.txt +0 -0
bake/ui/run/run.py
CHANGED
|
@@ -68,7 +68,8 @@ def _run_with_temp_file(
|
|
|
68
68
|
stream: bool,
|
|
69
69
|
keep_temp_file: bool = False,
|
|
70
70
|
env: dict[str, str] | None = None,
|
|
71
|
-
_encoding: str =
|
|
71
|
+
_encoding: str | None = None,
|
|
72
|
+
echo_cmd: str | None = None,
|
|
72
73
|
**kwargs,
|
|
73
74
|
) -> subprocess.CompletedProcess[str] | subprocess.CompletedProcess[None]:
|
|
74
75
|
"""Run multi-line script using temp file with shebang support.
|
|
@@ -83,6 +84,9 @@ def _run_with_temp_file(
|
|
|
83
84
|
_encoding : str, optional
|
|
84
85
|
Encoding to use for subprocess output. Defaults to "utf-8" to ensure
|
|
85
86
|
cross-platform UTF-8 support for temp file scripts.
|
|
87
|
+
echo_cmd : str | None, optional
|
|
88
|
+
Override the command string displayed in logs and console output.
|
|
89
|
+
Default is None (show actual command).
|
|
86
90
|
|
|
87
91
|
Notes
|
|
88
92
|
-----
|
|
@@ -122,6 +126,7 @@ def _run_with_temp_file(
|
|
|
122
126
|
cwd=cwd,
|
|
123
127
|
stream=stream,
|
|
124
128
|
echo=False,
|
|
129
|
+
echo_cmd=echo_cmd,
|
|
125
130
|
env=env,
|
|
126
131
|
_encoding=_encoding,
|
|
127
132
|
**kwargs,
|
|
@@ -147,6 +152,7 @@ def run(
|
|
|
147
152
|
stream: bool = True,
|
|
148
153
|
shell: bool | None = None,
|
|
149
154
|
echo: bool = True,
|
|
155
|
+
echo_cmd: str | None = None,
|
|
150
156
|
dry_run: bool = False,
|
|
151
157
|
keep_temp_file: bool = False,
|
|
152
158
|
env: dict[str, str] | None = None,
|
|
@@ -165,6 +171,7 @@ def run(
|
|
|
165
171
|
stream: bool = True,
|
|
166
172
|
shell: bool | None = None,
|
|
167
173
|
echo: bool = True,
|
|
174
|
+
echo_cmd: str | None = None,
|
|
168
175
|
dry_run: bool = False,
|
|
169
176
|
keep_temp_file: bool = False,
|
|
170
177
|
env: dict[str, str] | None = None,
|
|
@@ -182,6 +189,7 @@ def run(
|
|
|
182
189
|
stream: bool = True,
|
|
183
190
|
shell: bool | None = None,
|
|
184
191
|
echo: bool = True,
|
|
192
|
+
echo_cmd: str | None = None,
|
|
185
193
|
dry_run: bool = False,
|
|
186
194
|
keep_temp_file: bool = False,
|
|
187
195
|
env: dict[str, str] | None = None,
|
|
@@ -215,6 +223,11 @@ def run(
|
|
|
215
223
|
echo : bool, optional
|
|
216
224
|
Display command before execution using console.cmd().
|
|
217
225
|
Default is True. Set to False for silent execution.
|
|
226
|
+
echo_cmd : str | None, optional
|
|
227
|
+
Override the command string displayed in logs and console output.
|
|
228
|
+
The actual command is still executed, but this string is shown instead.
|
|
229
|
+
Useful for hiding complex binary paths or secrets in commands.
|
|
230
|
+
Default is None (show actual command).
|
|
218
231
|
dry_run : bool, optional
|
|
219
232
|
Display command without executing (dry-run mode).
|
|
220
233
|
Default is False. Does NOT auto-echo; combine with echo=True
|
|
@@ -249,16 +262,18 @@ def run(
|
|
|
249
262
|
>>> run("echo hello", echo=True, dry_run=True) # Show but don't run
|
|
250
263
|
>>> run("ls *.py | wc -l") # Pipes and wildcards
|
|
251
264
|
>>> run(["echo", "hello"]) # List for direct execution
|
|
265
|
+
>>> run("/path/to/binary arg", echo_cmd="binary arg") # Override display
|
|
252
266
|
"""
|
|
253
267
|
_validate_params(stream=stream, capture_output=capture_output)
|
|
254
268
|
shell = _detect_shell(cmd=cmd, shell=shell)
|
|
255
269
|
cmd_str = _format_cmd_str(cmd=cmd)
|
|
270
|
+
cmd_str_for_display = echo_cmd if echo_cmd is not None else cmd_str
|
|
256
271
|
|
|
257
272
|
if echo:
|
|
258
|
-
console.cmd(
|
|
273
|
+
console.cmd(cmd_str_for_display)
|
|
259
274
|
|
|
260
275
|
if dry_run:
|
|
261
|
-
return _dry_run_result(cmd=cmd, capture_output=capture_output, cwd=cwd)
|
|
276
|
+
return _dry_run_result(cmd=cmd, capture_output=capture_output, cwd=cwd, echo_cmd=echo_cmd)
|
|
262
277
|
|
|
263
278
|
# Handle multi-line scripts that require temp file approach:
|
|
264
279
|
# - Windows: Any multi-line script with shell=True (cmd.exe limitation)
|
|
@@ -287,10 +302,12 @@ def run(
|
|
|
287
302
|
stream=stream,
|
|
288
303
|
keep_temp_file=keep_temp_file,
|
|
289
304
|
env=env,
|
|
305
|
+
_encoding=_encoding,
|
|
306
|
+
echo_cmd=echo_cmd,
|
|
290
307
|
**kwargs,
|
|
291
308
|
)
|
|
292
309
|
|
|
293
|
-
logger.debug(f"[run] {
|
|
310
|
+
logger.debug(f"[run] {cmd_str_for_display}", extra={"cwd": cwd})
|
|
294
311
|
start = time.perf_counter()
|
|
295
312
|
|
|
296
313
|
_run = _run_with_stream if stream else _run_without_stream
|
|
@@ -305,9 +322,9 @@ def run(
|
|
|
305
322
|
**kwargs,
|
|
306
323
|
)
|
|
307
324
|
|
|
308
|
-
_check_exit_code(
|
|
325
|
+
_check_exit_code(result=result, check=check, cmd_str_for_display=cmd_str_for_display)
|
|
309
326
|
|
|
310
|
-
_log_completion(
|
|
327
|
+
_log_completion(cmd_str_for_display=cmd_str_for_display, result=result, start=start)
|
|
311
328
|
return result
|
|
312
329
|
|
|
313
330
|
|
|
@@ -330,9 +347,11 @@ def _dry_run_result(
|
|
|
330
347
|
cmd: str | list[str] | tuple[str, ...],
|
|
331
348
|
capture_output: bool,
|
|
332
349
|
cwd: Path | str | None,
|
|
350
|
+
echo_cmd: str | None = None,
|
|
333
351
|
) -> subprocess.CompletedProcess[str]:
|
|
334
352
|
cmd_str = _format_cmd_str(cmd)
|
|
335
|
-
|
|
353
|
+
cmd_str_for_display = echo_cmd if echo_cmd is not None else cmd_str
|
|
354
|
+
logger.debug(f"[dry-run] {cmd_str_for_display}", extra={"cwd": cwd})
|
|
336
355
|
return subprocess.CompletedProcess(
|
|
337
356
|
args=cmd,
|
|
338
357
|
returncode=0,
|
|
@@ -341,10 +360,21 @@ def _dry_run_result(
|
|
|
341
360
|
)
|
|
342
361
|
|
|
343
362
|
|
|
344
|
-
def _check_exit_code(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
363
|
+
def _check_exit_code(
|
|
364
|
+
result: subprocess.CompletedProcess[str] | subprocess.CompletedProcess[None],
|
|
365
|
+
check: bool,
|
|
366
|
+
cmd_str_for_display: str,
|
|
367
|
+
) -> None:
|
|
368
|
+
if check and result.returncode != 0:
|
|
369
|
+
logger.debug(
|
|
370
|
+
f"[error] {cmd_str_for_display}",
|
|
371
|
+
extra={
|
|
372
|
+
"returncode": result.returncode,
|
|
373
|
+
"stdout": result.stdout,
|
|
374
|
+
"stderr": result.stderr,
|
|
375
|
+
},
|
|
376
|
+
)
|
|
377
|
+
raise typer.Exit(result.returncode)
|
|
348
378
|
|
|
349
379
|
|
|
350
380
|
def _process_stream_output(
|
|
@@ -533,10 +563,12 @@ def _run_without_stream(
|
|
|
533
563
|
return result
|
|
534
564
|
|
|
535
565
|
|
|
536
|
-
def _log_completion(
|
|
566
|
+
def _log_completion(
|
|
567
|
+
cmd_str_for_display: str, result: subprocess.CompletedProcess, start: float
|
|
568
|
+
) -> None:
|
|
537
569
|
elapsed_seconds = time.perf_counter() - start
|
|
538
570
|
logger.debug(
|
|
539
|
-
f"[done] {
|
|
571
|
+
f"[done] {cmd_str_for_display}",
|
|
540
572
|
extra={
|
|
541
573
|
"returncode": result.returncode,
|
|
542
574
|
"stdout": result.stdout,
|
bake/ui/run/uv.py
CHANGED
|
@@ -4,7 +4,6 @@ from typing import Literal, overload
|
|
|
4
4
|
|
|
5
5
|
from uv import find_uv_bin
|
|
6
6
|
|
|
7
|
-
from bake.ui import console
|
|
8
7
|
from bake.ui.run.run import run
|
|
9
8
|
|
|
10
9
|
|
|
@@ -59,14 +58,6 @@ def run_uv(
|
|
|
59
58
|
) -> subprocess.CompletedProcess[str] | subprocess.CompletedProcess[None]:
|
|
60
59
|
uv_bin = find_uv_bin()
|
|
61
60
|
|
|
62
|
-
# Build display string: "uv" + command parts (no full binary path)
|
|
63
|
-
display_cmd = "uv " + " ".join(cmd)
|
|
64
|
-
|
|
65
|
-
# Echo command to console if requested
|
|
66
|
-
if echo:
|
|
67
|
-
console.cmd(display_cmd)
|
|
68
|
-
|
|
69
|
-
# Call run with full uv binary path, echo=False (already displayed), pass through options
|
|
70
61
|
return run(
|
|
71
62
|
[uv_bin, *cmd],
|
|
72
63
|
capture_output=capture_output,
|
|
@@ -74,7 +65,8 @@ def run_uv(
|
|
|
74
65
|
cwd=cwd,
|
|
75
66
|
stream=stream,
|
|
76
67
|
shell=False,
|
|
77
|
-
echo=
|
|
68
|
+
echo=echo,
|
|
69
|
+
echo_cmd="uv " + " ".join(cmd) if echo else None,
|
|
78
70
|
dry_run=dry_run,
|
|
79
71
|
keep_temp_file=keep_temp_file,
|
|
80
72
|
env=env,
|
bake/utils/__init__.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
from bake.utils.constants import DEFAULT_BAKEBOOK_NAME, DEFAULT_FILE_NAME
|
|
2
|
-
from bake.utils.env import should_use_colors
|
|
3
2
|
from bake.utils.exceptions import BakebookError, BaseBakefileError
|
|
3
|
+
from bake.utils.settings import (
|
|
4
|
+
ENV__BAKE_REINVOKED,
|
|
5
|
+
ENV_NO_COLOR,
|
|
6
|
+
bake_settings,
|
|
7
|
+
)
|
|
4
8
|
|
|
5
9
|
__all__ = [
|
|
6
10
|
"DEFAULT_BAKEBOOK_NAME",
|
|
7
11
|
"DEFAULT_FILE_NAME",
|
|
12
|
+
"ENV_NO_COLOR",
|
|
13
|
+
"ENV__BAKE_REINVOKED",
|
|
8
14
|
"BakebookError",
|
|
9
15
|
"BaseBakefileError",
|
|
10
|
-
"
|
|
16
|
+
"bake_settings",
|
|
11
17
|
]
|
bake/utils/settings.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
3
|
+
|
|
4
|
+
ENV_NO_COLOR = "NO_COLOR"
|
|
5
|
+
ENV__BAKE_REINVOKED = "_BAKE_REINVOKED"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BakeSettings(BaseSettings):
|
|
9
|
+
model_config = SettingsConfigDict(
|
|
10
|
+
env_file=".env",
|
|
11
|
+
env_file_encoding="utf-8",
|
|
12
|
+
extra="ignore",
|
|
13
|
+
populate_by_name=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
ci: bool = False
|
|
17
|
+
github_actions: bool = False
|
|
18
|
+
no_color: bool = False
|
|
19
|
+
bake_reinvoked: bool = Field(default=False, alias="_BAKE_REINVOKED")
|
|
20
|
+
|
|
21
|
+
def should_use_colors(self) -> bool:
|
|
22
|
+
return self.no_color
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
bake_settings = BakeSettings()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: bakefile
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Author: Wisaroot Lertthaweedech
|
|
6
6
|
Author-email: Wisaroot Lertthaweedech <l.wisaroot@gmail.com>
|
|
@@ -18,7 +18,9 @@ Requires-Dist: tomli>=2.0.0 ; python_full_version < '3.11'
|
|
|
18
18
|
Requires-Dist: ty>=0.0.8
|
|
19
19
|
Requires-Dist: typer>=0.21.0
|
|
20
20
|
Requires-Dist: uv>=0.9.20
|
|
21
|
+
Requires-Dist: keyring>=25.7.0 ; extra == 'lib'
|
|
21
22
|
Requires-Dist: pathspec>=1.0.3 ; extra == 'lib'
|
|
23
|
+
Requires-Dist: tenacity>=9.1.2 ; extra == 'lib'
|
|
22
24
|
Requires-Python: >=3.10
|
|
23
25
|
Provides-Extra: lib
|
|
24
26
|
Description-Content-Type: text/markdown
|
|
@@ -6,8 +6,8 @@ bake/bakebook/get.py,sha256=fx5WV66OIBrywPN0thePXAJk2gt7wvTVAxYQqZ_PMao,5807
|
|
|
6
6
|
bake/cli/__init__.py,sha256=da1PTClDMl-IBkrSvq6JC1lnS-K_BASzCvxVhNxN5Ls,13
|
|
7
7
|
bake/cli/bake/__init__.py,sha256=CJokSP1t1KXaIqjkjFQ8_gbaSZ9RRB4YdemIoSTqRcI,56
|
|
8
8
|
bake/cli/bake/__main__.py,sha256=5Ui5_OD1-oG1ou6boak63EPQDmRsW9sGOVVDgXJaNec,133
|
|
9
|
-
bake/cli/bake/main.py,sha256=
|
|
10
|
-
bake/cli/bake/reinvocation.py,sha256=
|
|
9
|
+
bake/cli/bake/main.py,sha256=286zCeRpxq2sGm7_vbX55YVm13dHrrCBtVNEXeZcNzg,3630
|
|
10
|
+
bake/cli/bake/reinvocation.py,sha256=Df_rRylarejI0nj2dV7s9YAhgN0B5iJnclzU0EcfN5Q,2100
|
|
11
11
|
bake/cli/bakefile/__init__.py,sha256=_zD3rXQHLr6EWHADdPLAmnc2A5C3dhmBuvP5uJ-_A58,60
|
|
12
12
|
bake/cli/bakefile/__main__.py,sha256=FVntzkZdzdygSWjMzyneXCXsM-MDTPmC3GUk4JZiYFU,137
|
|
13
13
|
bake/cli/bakefile/add_inline.py,sha256=V98T50SLMPqnWVtyEO_6hL17r4n3ZtkSC8NSEqdyHzc,919
|
|
@@ -15,54 +15,58 @@ bake/cli/bakefile/export.py,sha256=m9X0u6FgbjUzneQuh39H1CaFUT444jOPTFBNjnjs_Dg,6
|
|
|
15
15
|
bake/cli/bakefile/find_python.py,sha256=J2HDs_nfNODqCHBZCNM64ESB4kVZK-C04i-KNmVUoSs,539
|
|
16
16
|
bake/cli/bakefile/init.py,sha256=0QuvADFOZZUBN2BUJfK90aEY1oUzoSNVRiljlUSjLu0,1825
|
|
17
17
|
bake/cli/bakefile/lint.py,sha256=DJkIJNBOef6JvgwQ3iL9jTrLqgUyn66Mhv6cuAgqXk0,2509
|
|
18
|
-
bake/cli/bakefile/main.py,sha256=
|
|
18
|
+
bake/cli/bakefile/main.py,sha256=pxDIefHx-_PrbGbE133eODHwC7UQC7NkjMUm_7EGFKg,2435
|
|
19
19
|
bake/cli/bakefile/uv.py,sha256=PMFG3BdofzGWkor4fMEi3GE4G7hGtclCgPm2xlaPDso,4013
|
|
20
20
|
bake/cli/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
bake/cli/common/app.py,sha256=
|
|
22
|
-
bake/cli/common/
|
|
23
|
-
bake/cli/common/context.py,sha256=RtFHUDCZLcD88Ys17u_zXoHUq-12jkoXc9f_D4jh_7M,3871
|
|
21
|
+
bake/cli/common/app.py,sha256=6tWCyMKHwSynYI3qzc1AfSfLS4p2P25AilOsThqLN7k,1296
|
|
22
|
+
bake/cli/common/context.py,sha256=miyA87yaZLeuTsBnjIQdo4f5QKsI3CtOByTrzAWtweQ,4013
|
|
24
23
|
bake/cli/common/exception_handler.py,sha256=2vLbqMeZlLxKqNWUkTs3cA-8l6IjK0dU3SyZlRb96YI,1759
|
|
25
|
-
bake/cli/common/obj.py,sha256=
|
|
26
|
-
bake/cli/common/params.py,sha256=
|
|
24
|
+
bake/cli/common/obj.py,sha256=IcURtAHXRrwFkXZsVTTruEMeLddwowtBvpa5lBknpEk,7181
|
|
25
|
+
bake/cli/common/params.py,sha256=229B4PPTv84InlDSA0sjlU3ToCjyzvhjNpRIp2iFano,2278
|
|
27
26
|
bake/cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
27
|
bake/cli/utils/version.py,sha256=aiweLD0vDezBlJAcCC99oMms71WGD9CWSJuZ4i3VLHA,390
|
|
29
28
|
bake/manage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
29
|
bake/manage/add_inline.py,sha256=yefHmF33ghCB8NZ-v61ybeVsaeE8iDFvfRGeTAKg4I8,2245
|
|
31
30
|
bake/manage/find_python.py,sha256=67PAFPDA3fdSsDGjYfPcXOVUcgC63wg5mipjVIt1VUQ,7730
|
|
32
|
-
bake/manage/lint.py,sha256=
|
|
31
|
+
bake/manage/lint.py,sha256=4opV1EanzPUz5tmdzvNclM-qK4TmL85IixbzP5z-zRY,2472
|
|
33
32
|
bake/manage/run_uv.py,sha256=QzlKeVpr20dXNDcwUgyJqnXT4MofRqK-6XkWpzBbUhE,3234
|
|
34
33
|
bake/manage/write_bakefile.py,sha256=efGViLk7sh-QX9Mox7yQw_A1Tp7EOuc_vmSTbFmXUm0,736
|
|
35
34
|
bake/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
35
|
bake/samples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
36
|
bake/samples/simple.py,sha256=hP2TW-D7BQBGJseqRPpilxkoQ8ScTuZZePICupyvFKA,155
|
|
38
37
|
bake/ui/__init__.py,sha256=6OhZVKjfC9aumbhraxkGtx7KLpV1ouepeHRA2dUVoSo,209
|
|
39
|
-
bake/ui/console.py,sha256=
|
|
38
|
+
bake/ui/console.py,sha256=y5pZ5i_Iqh4bzaHGJQFDMnqRILsIClr8004EkLydLr4,3289
|
|
40
39
|
bake/ui/logger/__init__.py,sha256=bup2cssTHhergh47s6uYbGtY2dJNxlKKH6otBc4ECFM,728
|
|
41
40
|
bake/ui/logger/capsys.py,sha256=KZL6k7Werp_8styfJKfIvQyv0-gJq54vY3hSJFIacEM,5267
|
|
42
41
|
bake/ui/logger/setup.py,sha256=OrX9UiY0iBGfWWfhMJCdfqCRJsL5yC3rIdIEOn7rveo,1377
|
|
43
42
|
bake/ui/logger/utils.py,sha256=dcppxoS_pX92AFcHIerJGI2_JBHBNghRQmQqlZmmj2Q,7218
|
|
44
43
|
bake/ui/params.py,sha256=yNDChJQkbeZSxQzXTSBrAPCbwsJ5zOK4s4sFHQPSnHs,140
|
|
45
44
|
bake/ui/run/__init__.py,sha256=A671l5YVTRAtS47ewvaMCNwPRim_Wkof1am0WibxA2I,205
|
|
46
|
-
bake/ui/run/run.py,sha256=
|
|
45
|
+
bake/ui/run/run.py,sha256=t06v1E2TD8PGymnUsIHfZxwiBGeqFZ7Mt9W3IwowsMc,18842
|
|
47
46
|
bake/ui/run/script.py,sha256=fk7KiDklYDYpFGkH3wu-hZGI4OnvgcB8z5jtNt41Hg0,2263
|
|
48
47
|
bake/ui/run/splitter.py,sha256=L6uCU3bzpoMgj891Q1BZnOtiWF07QFDcCOx9RyUqHKk,9198
|
|
49
|
-
bake/ui/run/uv.py,sha256=
|
|
48
|
+
bake/ui/run/uv.py,sha256=LC1Eo3rbmRug8_MsmXxZYVGWcyr79WGzFfXQrEwtWH0,1810
|
|
50
49
|
bake/ui/style.py,sha256=v9dferzV317Acb0GHpVK_niCj_s2HtL-yiToBZtXky4,70
|
|
51
|
-
bake/utils/__init__.py,sha256=
|
|
50
|
+
bake/utils/__init__.py,sha256=1XKY-YbhTIwkWM-goTwek82mcQhwKqMW_bNTirUSrfI,422
|
|
52
51
|
bake/utils/constants.py,sha256=mRq5IpgOTdlHOTWPq5dx0A-LwhiFkWgYHfr8cLWG7rY,471
|
|
53
|
-
bake/utils/env.py,sha256=bzNdH_2bTJebQaw7D0uVJv-vzZ-uYl0pCAS8oQONVsA,190
|
|
54
52
|
bake/utils/exceptions.py,sha256=pwsQnKH5ljMNxmqEREutXa7TohiBHATHg_D5kQUPT30,519
|
|
55
|
-
|
|
53
|
+
bake/utils/settings.py,sha256=SRFU9WVHRMB1cE0TjReKEgL1nbIFM9NBLJWcGiODdOk,612
|
|
54
|
+
bakelib/__init__.py,sha256=yj72sp5MbSebIxIGHh183gbNE5oIdv-OG-8IYnoJ6AE,529
|
|
56
55
|
bakelib/environ/__init__.py,sha256=XIFVtu8SQySjPetu9WR_Q8HgqxUzenMNm1K24pYSfNo,356
|
|
57
56
|
bakelib/environ/bakebook.py,sha256=gnOvi3t5Ww0_6N5wozXLVvLe8KAK-u-6_v0UYCl_iKY,749
|
|
58
57
|
bakelib/environ/base.py,sha256=azPUdc9C5zVU8iyXJrEm3uDfe39nG48DRAZiF9rTdHI,3753
|
|
59
58
|
bakelib/environ/get_bakebook.py,sha256=LihxB3VDcVq81KJy1HT-N-beyt2C7ReWm2kMPrd7VlA,1563
|
|
60
59
|
bakelib/environ/presets.py,sha256=IwHGeDeQe4e57k9_u_vBQ61bjMS5Z2Jj9zkXw6YJHAE,1943
|
|
60
|
+
bakelib/refreshable_cache/__init__.py,sha256=hNylzA9X9GpAiuG3WYBvgGRY-z8Gcu_BxsyHOIwtTOQ,347
|
|
61
|
+
bakelib/refreshable_cache/cache.py,sha256=P8gX63FP1vgdwNOwKLf-U0ly1P1LvNsB7KqDS9XdG5Q,7629
|
|
62
|
+
bakelib/refreshable_cache/exceptions.py,sha256=d15dd0RbapXneWZpDwfvDt7k49cB6jZBMxULi5DLU9Q,97
|
|
61
63
|
bakelib/space/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
|
-
bakelib/space/base.py,sha256=
|
|
63
|
-
bakelib/space/
|
|
64
|
-
bakelib/space/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
bakefile-0.0.
|
|
68
|
-
bakefile-0.0.
|
|
64
|
+
bakelib/space/base.py,sha256=7vwVw19hkPimvamvTVySkc7Ndk8san3EF_5pg-8jf7w,6260
|
|
65
|
+
bakelib/space/lib.py,sha256=MZnS_vP9zxalRE_eFb-fkW5NU5-6l8w8Rsz63PMCVS8,5617
|
|
66
|
+
bakelib/space/python.py,sha256=o7t46DnBOcHf_bcJ8HOhiisjMf-UQsWCcykgLYxffu4,3400
|
|
67
|
+
bakelib/space/python_lib.py,sha256=kkfdoMOWMX_qP3uGafQbLXy5NFbvv4ZKwmOXrgzH6Cs,2729
|
|
68
|
+
bakelib/space/utils.py,sha256=Mp82CgpNMeG76slXaDs9GXa1r_ugoiJICvpsOaH_2tg,3206
|
|
69
|
+
bakefile-0.0.11.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
70
|
+
bakefile-0.0.11.dist-info/entry_points.txt,sha256=Ecvvh7BYHCPJ0UdntrDc3Od6AZdRPXN5Z7o_7ok_0Qw,107
|
|
71
|
+
bakefile-0.0.11.dist-info/METADATA,sha256=sDQXFSpH61wMHVp8mBsWgNzXLJWMR1upvBP3wsEMu9E,2464
|
|
72
|
+
bakefile-0.0.11.dist-info/RECORD,,
|
bakelib/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ from bakelib.environ import (
|
|
|
9
9
|
)
|
|
10
10
|
from bakelib.space.base import BaseSpace
|
|
11
11
|
from bakelib.space.python import PythonSpace
|
|
12
|
+
from bakelib.space.python_lib import PythonLibSpace
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"BaseEnv",
|
|
@@ -17,6 +18,7 @@ __all__ = [
|
|
|
17
18
|
"EnvBakebook",
|
|
18
19
|
"GcpLandingZoneEnv",
|
|
19
20
|
"ProdEnvBakebook",
|
|
21
|
+
"PythonLibSpace",
|
|
20
22
|
"PythonSpace",
|
|
21
23
|
"StagingEnvBakebook",
|
|
22
24
|
"get_bakebook",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from bakelib.refreshable_cache.cache import (
|
|
2
|
+
ChainedCache,
|
|
3
|
+
KeyringCache,
|
|
4
|
+
MemoryCache,
|
|
5
|
+
NullCache,
|
|
6
|
+
RefreshableCache,
|
|
7
|
+
)
|
|
8
|
+
from bakelib.refreshable_cache.exceptions import RefreshNeededError
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ChainedCache",
|
|
12
|
+
"KeyringCache",
|
|
13
|
+
"MemoryCache",
|
|
14
|
+
"NullCache",
|
|
15
|
+
"RefreshNeededError",
|
|
16
|
+
"RefreshableCache",
|
|
17
|
+
]
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import functools
|
|
3
|
+
import inspect
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, ParamSpec, TypeVar
|
|
9
|
+
|
|
10
|
+
import keyring as kr
|
|
11
|
+
from keyring.errors import PasswordDeleteError
|
|
12
|
+
from pydantic import BaseModel, TypeAdapter
|
|
13
|
+
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_none
|
|
14
|
+
|
|
15
|
+
from bakelib.refreshable_cache.exceptions import RefreshNeededError
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from tenacity.stop import StopBaseT
|
|
19
|
+
from tenacity.wait import WaitBaseT
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
P = ParamSpec("P")
|
|
25
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
26
|
+
T = TypeVar("T")
|
|
27
|
+
CachedT = TypeVar("CachedT", covariant=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CacheEntry(BaseModel, Generic[CachedT]):
|
|
31
|
+
"""Cache entry containing the cached value and timestamp."""
|
|
32
|
+
|
|
33
|
+
value: CachedT
|
|
34
|
+
timestamp: float
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
DEFAULT_NAMESPACE = "bakelib.refreshable_cache"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class RefreshableCache(ABC, Generic[CachedT]):
|
|
41
|
+
"""Cache that can be refreshed when values expire or become invalid."""
|
|
42
|
+
|
|
43
|
+
RefreshNeededError: type[RefreshNeededError] = RefreshNeededError
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
key: str,
|
|
48
|
+
fetch_fn: Callable[[], CachedT],
|
|
49
|
+
ttl: float | None = None,
|
|
50
|
+
namespace: str | None = None,
|
|
51
|
+
stop: "StopBaseT | None" = None,
|
|
52
|
+
wait: "WaitBaseT | None" = None,
|
|
53
|
+
cached_type: Any = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
self._key = key
|
|
56
|
+
self._fetch_fn = fetch_fn
|
|
57
|
+
self._ttl = ttl
|
|
58
|
+
self._namespace = namespace if namespace is not None else DEFAULT_NAMESPACE
|
|
59
|
+
self._stop = stop if stop is not None else stop_after_attempt(2)
|
|
60
|
+
self._wait = wait if wait is not None else wait_none()
|
|
61
|
+
|
|
62
|
+
# Determine cached type: explicit cached_type or infer from fetch_fn
|
|
63
|
+
if cached_type is not None:
|
|
64
|
+
return_type = cached_type
|
|
65
|
+
else:
|
|
66
|
+
return_type = inspect.signature(fetch_fn).return_annotation
|
|
67
|
+
if return_type is inspect.Parameter.empty:
|
|
68
|
+
msg = "fetch_fn must have a return type annotation or cached_type must be provided"
|
|
69
|
+
raise TypeError(msg)
|
|
70
|
+
|
|
71
|
+
self._adapter = TypeAdapter(CacheEntry[return_type])
|
|
72
|
+
|
|
73
|
+
def _get_full_key(self) -> str:
|
|
74
|
+
return f"{self._namespace}:{self._key}"
|
|
75
|
+
|
|
76
|
+
def _serialize_entry(self, value: CachedT) -> bytes:
|
|
77
|
+
entry = CacheEntry(value=value, timestamp=time.time())
|
|
78
|
+
return self._adapter.dump_json(entry)
|
|
79
|
+
|
|
80
|
+
def _deserialize_entry(self, data: bytes) -> CacheEntry[CachedT]:
|
|
81
|
+
return self._adapter.validate_json(data)
|
|
82
|
+
|
|
83
|
+
def _is_expired(self, timestamp: float) -> bool:
|
|
84
|
+
if self._ttl is None:
|
|
85
|
+
return False
|
|
86
|
+
return time.time() - timestamp > self._ttl
|
|
87
|
+
|
|
88
|
+
def get_value(self) -> CachedT:
|
|
89
|
+
cached = self._get_entry()
|
|
90
|
+
if cached is None:
|
|
91
|
+
logger.debug(f"Cache miss for key '{self._key}', fetching value")
|
|
92
|
+
return self._refresh()
|
|
93
|
+
if self._is_expired(cached.timestamp):
|
|
94
|
+
logger.debug(f"Cache expired for key '{self._key}', fetching fresh value")
|
|
95
|
+
return self._refresh()
|
|
96
|
+
logger.debug(f"Cache hit for key '{self._key}'")
|
|
97
|
+
return cached.value
|
|
98
|
+
|
|
99
|
+
def _refresh(self) -> CachedT:
|
|
100
|
+
logger.debug(f"Refreshing value for key '{self._key}'")
|
|
101
|
+
value = self._fetch_fn()
|
|
102
|
+
self.set(value)
|
|
103
|
+
return value
|
|
104
|
+
|
|
105
|
+
def catch_refresh(self, func: Callable[P, T]) -> Callable[P, T]:
|
|
106
|
+
@functools.wraps(func)
|
|
107
|
+
@retry(
|
|
108
|
+
stop=self._stop,
|
|
109
|
+
wait=self._wait,
|
|
110
|
+
retry=retry_if_exception_type(self.RefreshNeededError),
|
|
111
|
+
reraise=True,
|
|
112
|
+
)
|
|
113
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
114
|
+
try:
|
|
115
|
+
return func(*args, **kwargs)
|
|
116
|
+
except self.RefreshNeededError:
|
|
117
|
+
self.delete()
|
|
118
|
+
raise
|
|
119
|
+
|
|
120
|
+
return wrapper
|
|
121
|
+
|
|
122
|
+
@abstractmethod
|
|
123
|
+
def _get_entry(self) -> CacheEntry[CachedT] | None: ...
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def set(self, value: CachedT) -> None: ...
|
|
127
|
+
|
|
128
|
+
@abstractmethod
|
|
129
|
+
def delete(self) -> None: ...
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class KeyringCache(RefreshableCache[CachedT]):
|
|
133
|
+
"""Cache using system keyring for persistent storage."""
|
|
134
|
+
|
|
135
|
+
def _get_entry(self) -> CacheEntry[CachedT] | None:
|
|
136
|
+
data = kr.get_password(self._namespace, self._key)
|
|
137
|
+
if data is None:
|
|
138
|
+
return None
|
|
139
|
+
return self._deserialize_entry(data.encode())
|
|
140
|
+
|
|
141
|
+
def set(self, value: CachedT) -> None:
|
|
142
|
+
data = self._serialize_entry(value).decode()
|
|
143
|
+
kr.set_password(self._namespace, self._key, data)
|
|
144
|
+
|
|
145
|
+
def delete(self) -> None:
|
|
146
|
+
with contextlib.suppress(PasswordDeleteError):
|
|
147
|
+
kr.delete_password(self._namespace, self._key)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class MemoryCache(RefreshableCache[CachedT]):
|
|
151
|
+
"""In-memory cache for ephemeral storage."""
|
|
152
|
+
|
|
153
|
+
_storage: ClassVar[dict[str, CacheEntry[CachedT]]] = {}
|
|
154
|
+
|
|
155
|
+
def _get_entry(self) -> CacheEntry[CachedT] | None:
|
|
156
|
+
entry = self._storage.get(self._get_full_key())
|
|
157
|
+
if entry is None:
|
|
158
|
+
return None
|
|
159
|
+
return entry
|
|
160
|
+
|
|
161
|
+
def set(self, value: CachedT) -> None:
|
|
162
|
+
self._storage[self._get_full_key()] = CacheEntry(value=value, timestamp=time.time())
|
|
163
|
+
|
|
164
|
+
def delete(self) -> None:
|
|
165
|
+
self._storage.pop(self._get_full_key(), None)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class NullCache(RefreshableCache[CachedT]):
|
|
169
|
+
"""Cache that doesn't cache anything (Null Object pattern).
|
|
170
|
+
|
|
171
|
+
Useful as a final fallback when you want to explicitly disable caching.
|
|
172
|
+
Reads always return None (triggering fetch), writes/deletes do nothing.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def _get_entry(self) -> CacheEntry[CachedT] | None:
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
def set(self, value: CachedT) -> None:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
def delete(self) -> None:
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class ChainedCache(RefreshableCache[CachedT]):
|
|
186
|
+
"""Tries multiple backends in order.
|
|
187
|
+
|
|
188
|
+
Reads from the first backend that has data.
|
|
189
|
+
Writes to all backends (stops on first success).
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
_backends: list[RefreshableCache[CachedT]]
|
|
193
|
+
|
|
194
|
+
def __init__(
|
|
195
|
+
self,
|
|
196
|
+
backends: list[type[RefreshableCache[CachedT]]],
|
|
197
|
+
key: str,
|
|
198
|
+
fetch_fn: Callable[[], CachedT],
|
|
199
|
+
ttl: float | None = None,
|
|
200
|
+
namespace: str | None = None,
|
|
201
|
+
stop: "StopBaseT | None" = None,
|
|
202
|
+
wait: "WaitBaseT | None" = None,
|
|
203
|
+
cached_type: Any = None,
|
|
204
|
+
) -> None:
|
|
205
|
+
super().__init__(
|
|
206
|
+
key=key,
|
|
207
|
+
fetch_fn=fetch_fn,
|
|
208
|
+
ttl=ttl,
|
|
209
|
+
namespace=namespace,
|
|
210
|
+
stop=stop,
|
|
211
|
+
wait=wait,
|
|
212
|
+
cached_type=cached_type,
|
|
213
|
+
)
|
|
214
|
+
self._backends = [
|
|
215
|
+
backend(
|
|
216
|
+
key=key,
|
|
217
|
+
fetch_fn=fetch_fn,
|
|
218
|
+
ttl=ttl,
|
|
219
|
+
namespace=namespace,
|
|
220
|
+
stop=stop,
|
|
221
|
+
wait=wait,
|
|
222
|
+
cached_type=cached_type,
|
|
223
|
+
)
|
|
224
|
+
for backend in backends
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
def _get_entry(self) -> CacheEntry[CachedT] | None:
|
|
228
|
+
for backend in self._backends:
|
|
229
|
+
try:
|
|
230
|
+
entry = backend._get_entry()
|
|
231
|
+
if entry is not None:
|
|
232
|
+
return entry
|
|
233
|
+
except Exception:
|
|
234
|
+
continue
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
def set(self, value: CachedT) -> None:
|
|
238
|
+
for backend in self._backends:
|
|
239
|
+
try:
|
|
240
|
+
backend.set(value)
|
|
241
|
+
return
|
|
242
|
+
except Exception:
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
def delete(self) -> None:
|
|
246
|
+
for backend in self._backends:
|
|
247
|
+
try:
|
|
248
|
+
backend.delete()
|
|
249
|
+
except Exception:
|
|
250
|
+
continue
|