bakefile 0.0.10__py3-none-any.whl → 0.0.12__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.
bakelib/space/base.py CHANGED
@@ -45,6 +45,28 @@ class BaseSpace(Bakebook):
45
45
  def test_all(self, ctx: Context) -> None:
46
46
  self._no_implementation(ctx)
47
47
 
48
+ def _clean(
49
+ self,
50
+ ctx: Context,
51
+ exclude_patterns: list[str] | None,
52
+ default_excludes: bool,
53
+ default_exclude_patterns: set[str],
54
+ ):
55
+ results = ctx.run("git clean -fdX -n", stream=False, dry_run=False, echo=True)
56
+
57
+ exclude_patterns: set[str] = set(exclude_patterns if exclude_patterns else [])
58
+
59
+ if default_excludes:
60
+ exclude_patterns |= default_exclude_patterns
61
+
62
+ console.err.print(f"Exclude pattens: {exclude_patterns}")
63
+
64
+ remove_git_clean_candidates(
65
+ git_clean_dry_run_output=results.stdout,
66
+ exclude_patterns=exclude_patterns,
67
+ dry_run=ctx.dry_run,
68
+ )
69
+
48
70
  @command(help="Clean gitignored files with optional exclusions")
49
71
  def clean(
50
72
  self,
@@ -62,19 +84,11 @@ class BaseSpace(Bakebook):
62
84
  typer.Option(help="Apply default exclude patterns (.env, .cache)"),
63
85
  ] = True,
64
86
  ) -> None:
65
- results = ctx.run("git clean -fdX -n", stream=False, dry_run=False, echo=True)
66
-
67
- exclude_patterns: set[str] = set(exclude_patterns if exclude_patterns else [])
68
-
69
- if default_excludes:
70
- exclude_patterns |= {".env", ".cache"}
71
-
72
- console.err.print(f"Exclude pattens: {exclude_patterns}")
73
-
74
- remove_git_clean_candidates(
75
- git_clean_dry_run_output=results.stdout,
87
+ self._clean(
88
+ ctx=ctx,
76
89
  exclude_patterns=exclude_patterns,
77
- dry_run=ctx.dry_run,
90
+ default_excludes=default_excludes,
91
+ default_exclude_patterns={".env", ".cache"},
78
92
  )
79
93
 
80
94
  @command(help="Clean all gitignored files")
bakelib/space/lib.py ADDED
@@ -0,0 +1,161 @@
1
+ import subprocess
2
+ from abc import abstractmethod
3
+ from contextlib import contextmanager
4
+ from dataclasses import dataclass
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from pydantic import SecretStr
9
+ from tenacity import stop_after_attempt
10
+
11
+ from bake import Context, command, console
12
+ from bake.ui.logger import strip_ansi
13
+ from bakelib.refreshable_cache import ChainedCache, KeyringCache, NullCache
14
+
15
+ from .base import BaseSpace, ToolInfo
16
+ from .utils import CARGO_BIN, PlatformType, get_expected_paths, setup_rustup, setup_zerv
17
+
18
+
19
+ @dataclass
20
+ class PublishResult:
21
+ result: subprocess.CompletedProcess[str] | None
22
+ is_dry_run: bool
23
+ is_auth_failed: bool
24
+
25
+
26
+ class BaseLibSpace(BaseSpace):
27
+ bake_publish_token: SecretStr | None = None
28
+ _dummy_publish_token: str = "dummy-token-for-dry-run"
29
+
30
+ def setup_tools(self, ctx: Context, platform: PlatformType) -> None:
31
+ _ = platform
32
+ super().setup_tools(ctx, platform=platform)
33
+ setup_rustup(ctx)
34
+ setup_zerv(ctx)
35
+
36
+ @abstractmethod
37
+ def _get_publish_token_from_remote(self, registry: str) -> str | None: ...
38
+
39
+ @abstractmethod
40
+ def package_name(self, ctx: Context) -> str: ...
41
+
42
+ @abstractmethod
43
+ def _build_for_publish(self, ctx: Context): ...
44
+
45
+ @abstractmethod
46
+ def _publish_with_token(
47
+ self, ctx: Context, token: str | None, registry: str
48
+ ) -> PublishResult: ...
49
+
50
+ def _get_cached_publish_token(
51
+ self, ctx: Context, token: str | None, registry: str
52
+ ) -> ChainedCache[str | None]:
53
+ token_from_local = self._get_token_from_local(token)
54
+ key = f"publish-token-{registry}"
55
+ namespace = self.package_name(ctx)
56
+
57
+ def get_publish_token() -> str | None:
58
+ return token_from_local or self._get_publish_token_from_remote(registry)
59
+
60
+ stop = stop_after_attempt(1) if token_from_local else None
61
+
62
+ cached_publish_token = ChainedCache(
63
+ backends=[KeyringCache, NullCache],
64
+ namespace=namespace,
65
+ key=key,
66
+ fetch_fn=get_publish_token,
67
+ stop=stop,
68
+ )
69
+
70
+ if token_from_local is not None:
71
+ cached_publish_token.set(token_from_local)
72
+
73
+ return cached_publish_token
74
+
75
+ def _get_token_from_local(self, token: str | None) -> str | None:
76
+ if token:
77
+ return token
78
+
79
+ if self.bake_publish_token:
80
+ return self.bake_publish_token.get_secret_value()
81
+
82
+ return None
83
+
84
+ @contextmanager
85
+ @abstractmethod
86
+ def _version_bump_context(self, _ctx: Context, _version: str): ...
87
+
88
+ @abstractmethod
89
+ def _pre_publish_cleanup(self, _ctx: Context): ...
90
+
91
+ @property
92
+ def _version_schema(self) -> str | None:
93
+ return None
94
+
95
+ def _is_auth_failure(self, result: subprocess.CompletedProcess[str]) -> bool:
96
+ return result.returncode != 0
97
+
98
+ def _determine_version(self, ctx: Context, version: str | None) -> str:
99
+ return version if version else self.zerv_versioning(ctx, schema=self._version_schema)
100
+
101
+ @command(help="Build and publish the package")
102
+ def publish(
103
+ self,
104
+ ctx: Context,
105
+ registry: Annotated[str, typer.Option(help="Publish registry")] = "default",
106
+ token: Annotated[str | None, typer.Option(help="Publish token")] = None,
107
+ version: Annotated[str | None, typer.Option(help="Version to publish")] = None,
108
+ ):
109
+ cached_publish_token = self._get_cached_publish_token(
110
+ ctx=ctx, token=token, registry=registry
111
+ )
112
+ version = self._determine_version(ctx, version)
113
+
114
+ self._pre_publish_cleanup(ctx)
115
+
116
+ with self._version_bump_context(ctx, version):
117
+ self._build_for_publish(ctx)
118
+ publish_result = self._execute_publish(
119
+ ctx=ctx, cached_publish_token=cached_publish_token, registry=registry
120
+ )
121
+
122
+ self._handle_publish_result(ctx, publish_result=publish_result)
123
+
124
+ def _execute_publish(
125
+ self, ctx: Context, cached_publish_token: ChainedCache[str | None], registry: str
126
+ ) -> PublishResult:
127
+ @cached_publish_token.catch_refresh
128
+ def _publish() -> PublishResult:
129
+ token_value = cached_publish_token.get_value()
130
+ publish_result = self._publish_with_token(ctx=ctx, token=token_value, registry=registry)
131
+
132
+ if publish_result.result is not None and self._is_auth_failure(publish_result.result):
133
+ raise cached_publish_token.RefreshNeededError
134
+
135
+ return publish_result
136
+
137
+ try:
138
+ return _publish()
139
+ except cached_publish_token.RefreshNeededError:
140
+ return PublishResult(result=None, is_dry_run=False, is_auth_failed=True)
141
+
142
+ def _handle_publish_result(self, ctx: Context, publish_result: PublishResult) -> None:
143
+ if publish_result.is_auth_failed:
144
+ console.error("Authentication failed. Please check your publish token.")
145
+ raise typer.Exit(1)
146
+
147
+ if publish_result.is_dry_run and not ctx.dry_run:
148
+ console.warning(
149
+ "This was a dry-run. To actually publish, "
150
+ "set the BAKE_PUBLISH_TOKEN environment variable"
151
+ )
152
+
153
+ def zerv_versioning(self, ctx: Context, *, schema: str | None = None) -> str:
154
+ schema_flag = f"--schema {schema}" if schema else ""
155
+ result = ctx.run(f"zerv flow {schema_flag}", dry_run=False)
156
+ return strip_ansi(result.stdout.strip())
157
+
158
+ def _get_tools(self) -> dict[str, ToolInfo]:
159
+ tools = super()._get_tools()
160
+ tools["zerv"] = ToolInfo(expected_paths=get_expected_paths("zerv", {CARGO_BIN}))
161
+ return tools
bakelib/space/python.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from pathlib import Path
2
2
 
3
3
  from bake import Context, params
4
+ from bake.ui.logger import strip_ansi
4
5
 
5
6
  from .base import BaseSpace, ToolInfo
6
7
  from .utils import VENV_BIN, get_expected_paths
@@ -87,3 +88,14 @@ class PythonSpace(BaseSpace):
87
88
  super().update(ctx=ctx)
88
89
  ctx.run("uv lock --upgrade")
89
90
  ctx.run("uv sync --all-extras --all-groups")
91
+
92
+ def _uv_version(self, ctx: Context) -> tuple[str, str]:
93
+ result = ctx.run("uv version", stream=False, dry_run=False, echo=False)
94
+ package_name, original_version = strip_ansi(result.stdout.strip()).split()
95
+ return package_name, original_version
96
+
97
+ def package_name(self, ctx: Context) -> str:
98
+ return self._uv_version(ctx)[0]
99
+
100
+ def current_version(self, ctx: Context) -> str:
101
+ return self._uv_version(ctx)[1]
@@ -0,0 +1,77 @@
1
+ import shutil
2
+ import subprocess
3
+ from contextlib import contextmanager
4
+ from typing import Annotated, Literal, cast, get_args
5
+
6
+ import typer
7
+
8
+ from bake import Context
9
+
10
+ from .lib import BaseLibSpace, PublishResult
11
+ from .python import PythonSpace
12
+
13
+ PublishIndex = Literal["testpypi", "pypi"]
14
+
15
+
16
+ class PythonLibSpace(PythonSpace, BaseLibSpace):
17
+ @property
18
+ def _version_schema(self) -> str | None:
19
+ return "standard-base-prerelease-post-dev"
20
+
21
+ def _registry_to_index(self, registry: str) -> PublishIndex:
22
+ valid_indices = get_args(PublishIndex)
23
+ if registry not in valid_indices:
24
+ raise ValueError(f"Invalid registry: {registry!r}. Expected one of {valid_indices}.")
25
+ return cast(PublishIndex, registry)
26
+
27
+ def _get_publish_token_from_remote(self, registry: str) -> str | None:
28
+ index = self._registry_to_index(registry)
29
+ _ = index
30
+ return None
31
+
32
+ def _build_for_publish(self, ctx: Context):
33
+ ctx.run("uv build")
34
+
35
+ def _publish_with_token(self, ctx: Context, token: str | None, registry: str) -> PublishResult:
36
+ index = self._registry_to_index(registry)
37
+ index_flag = f"--index {index} " if index == "testpypi" else ""
38
+ dry_run_flag = "" if token is not None else "--dry-run "
39
+ is_dry_run = token is None
40
+
41
+ env: dict[str, str] = {
42
+ "UV_PUBLISH_TOKEN": token if token is not None else self._dummy_publish_token
43
+ }
44
+
45
+ result = ctx.run(
46
+ f"uv publish {dry_run_flag}{index_flag}",
47
+ stream=True,
48
+ env=env,
49
+ check=False,
50
+ )
51
+
52
+ return PublishResult(result=result, is_dry_run=is_dry_run, is_auth_failed=False)
53
+
54
+ def _is_auth_failure(self, result: subprocess.CompletedProcess[str]) -> bool:
55
+ auth_error_message = "403 Invalid or non-existent authentication information"
56
+ return result.returncode != 0 and auth_error_message in result.stderr
57
+
58
+ @contextmanager
59
+ def _version_bump_context(self, ctx: Context, version: str):
60
+ original_version = self.current_version(ctx)
61
+ ctx.run(f"uv version {version}")
62
+ try:
63
+ yield
64
+ finally:
65
+ ctx.run(f"uv version {original_version}")
66
+
67
+ def _pre_publish_cleanup(self, _ctx: Context):
68
+ shutil.rmtree("dist", ignore_errors=True)
69
+
70
+ def publish(
71
+ self,
72
+ ctx: Context,
73
+ index: Annotated[PublishIndex, typer.Option(help="Publish index")] = "testpypi",
74
+ token: Annotated[str | None, typer.Option(help="Publish token")] = None,
75
+ version: Annotated[str | None, typer.Option(help="Version to publish")] = None,
76
+ ):
77
+ return super().publish(ctx=ctx, registry=index, token=token, version=version)
bakelib/space/utils.py CHANGED
@@ -52,6 +52,15 @@ def setup_uv(ctx: Context) -> None:
52
52
  ctx.run("uv tool update-shell")
53
53
 
54
54
 
55
+ def setup_rustup(ctx: Context) -> None:
56
+ ctx.run("brew install rustup")
57
+ ctx.run("rustup update")
58
+
59
+
60
+ def setup_zerv(ctx: Context) -> None:
61
+ ctx.run("cargo install zerv")
62
+
63
+
55
64
  def setup_bun(ctx: Context) -> None:
56
65
  ctx.run("brew install oven-sh/bun/bun")
57
66
 
@@ -62,6 +71,7 @@ def setup_uv_tool(ctx: Context) -> None:
62
71
 
63
72
 
64
73
  HOMWBREW_BIN = Path("/opt/homebrew/bin")
74
+ CARGO_BIN = Path.home() / ".cargo" / "bin"
65
75
  LOCAL_BIN = Path.home() / ".local" / "bin"
66
76
  VENV_BIN = Path.cwd() / ".venv" / "bin"
67
77
 
@@ -1,13 +0,0 @@
1
- import typer
2
-
3
-
4
- def validate_file_name(file_name: str) -> str:
5
- if "/" in file_name or "\\" in file_name:
6
- raise typer.BadParameter(f"File name must not contain path separators: {file_name}")
7
- if not file_name.endswith(".py"):
8
- raise typer.BadParameter(f"File name must end with .py: {file_name}")
9
- return file_name
10
-
11
-
12
- def validate_file_name_callback(value: str) -> str:
13
- return validate_file_name(file_name=value)
bake/utils/env.py DELETED
@@ -1,10 +0,0 @@
1
- import os
2
-
3
- ENV_NO_COLOR = "NO_COLOR"
4
-
5
- _BAKE_REINVOKED = "_BAKE_REINVOKED"
6
-
7
-
8
- def should_use_colors() -> bool:
9
- value = os.environ.get(ENV_NO_COLOR)
10
- return value == "" or value is None