bakefile 0.0.7__tar.gz → 0.0.8__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.
Files changed (69) hide show
  1. {bakefile-0.0.7 → bakefile-0.0.8}/PKG-INFO +2 -1
  2. {bakefile-0.0.7 → bakefile-0.0.8}/pyproject.toml +6 -3
  3. bakefile-0.0.8/src/bake/manage/write_bakefile.py +20 -0
  4. bakefile-0.0.8/src/bakelib/__init__.py +23 -0
  5. bakefile-0.0.8/src/bakelib/environ/__init__.py +14 -0
  6. bakefile-0.0.8/src/bakelib/environ/bakebook.py +30 -0
  7. bakefile-0.0.8/src/bakelib/environ/base.py +112 -0
  8. bakefile-0.0.8/src/bakelib/environ/get_bakebook.py +49 -0
  9. bakefile-0.0.8/src/bakelib/environ/presets.py +70 -0
  10. bakefile-0.0.7/src/bake/manage/write_bakefile.py +0 -39
  11. bakefile-0.0.7/src/bakelib/__init__.py +0 -4
  12. {bakefile-0.0.7 → bakefile-0.0.8}/README.md +0 -0
  13. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/__init__.py +0 -0
  14. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/bakebook/__init__.py +0 -0
  15. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/bakebook/bakebook.py +0 -0
  16. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/bakebook/decorator.py +0 -0
  17. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/bakebook/get.py +0 -0
  18. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/__init__.py +0 -0
  19. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bake/__init__.py +0 -0
  20. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bake/__main__.py +0 -0
  21. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bake/main.py +0 -0
  22. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bake/reinvocation.py +0 -0
  23. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bakefile/__init__.py +0 -0
  24. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bakefile/__main__.py +0 -0
  25. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bakefile/add_inline.py +0 -0
  26. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bakefile/export.py +0 -0
  27. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bakefile/find_python.py +0 -0
  28. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bakefile/init.py +0 -0
  29. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bakefile/lint.py +0 -0
  30. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bakefile/main.py +0 -0
  31. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/bakefile/uv.py +0 -0
  32. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/common/__init__.py +0 -0
  33. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/common/app.py +0 -0
  34. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/common/callback.py +0 -0
  35. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/common/context.py +0 -0
  36. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/common/exception_handler.py +0 -0
  37. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/common/obj.py +0 -0
  38. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/common/params.py +0 -0
  39. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/utils/__init__.py +0 -0
  40. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/cli/utils/version.py +0 -0
  41. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/manage/__init__.py +0 -0
  42. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/manage/add_inline.py +0 -0
  43. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/manage/find_python.py +0 -0
  44. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/manage/lint.py +0 -0
  45. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/manage/run_uv.py +0 -0
  46. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/py.typed +0 -0
  47. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/samples/__init__.py +0 -0
  48. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/samples/simple.py +0 -0
  49. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/__init__.py +0 -0
  50. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/console.py +0 -0
  51. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/logger/__init__.py +0 -0
  52. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/logger/capsys.py +0 -0
  53. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/logger/setup.py +0 -0
  54. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/logger/utils.py +0 -0
  55. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/params.py +0 -0
  56. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/run/__init__.py +0 -0
  57. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/run/run.py +0 -0
  58. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/run/script.py +0 -0
  59. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/run/splitter.py +0 -0
  60. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/run/uv.py +0 -0
  61. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/ui/style.py +0 -0
  62. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/utils/__init__.py +0 -0
  63. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/utils/constants.py +0 -0
  64. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/utils/env.py +0 -0
  65. {bakefile-0.0.7 → bakefile-0.0.8}/src/bake/utils/exceptions.py +0 -0
  66. {bakefile-0.0.7 → bakefile-0.0.8}/src/bakelib/space/__init__.py +0 -0
  67. {bakefile-0.0.7 → bakefile-0.0.8}/src/bakelib/space/base.py +0 -0
  68. {bakefile-0.0.7 → bakefile-0.0.8}/src/bakelib/space/python.py +0 -0
  69. {bakefile-0.0.7 → bakefile-0.0.8}/src/bakelib/space/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bakefile
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: Add your description here
5
5
  Author: Wisaroot Lertthaweedech
6
6
  Author-email: Wisaroot Lertthaweedech <l.wisaroot@gmail.com>
@@ -10,6 +10,7 @@ Requires-Dist: loguru>=0.7.3
10
10
  Requires-Dist: orjson>=3.11.5
11
11
  Requires-Dist: pydantic-settings>=2.0.0
12
12
  Requires-Dist: pydantic>=2.12.5
13
+ Requires-Dist: python-dotenv>=1.2.1
13
14
  Requires-Dist: pyyaml>=6.0.3
14
15
  Requires-Dist: rich>=14.2.0
15
16
  Requires-Dist: ruff>=0.14.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bakefile"
3
- version = "0.0.7" # use git tag
3
+ version = "0.0.8" # use git tag
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -14,6 +14,7 @@ dependencies = [
14
14
  "orjson>=3.11.5",
15
15
  "pydantic-settings>=2.0.0",
16
16
  "pydantic>=2.12.5",
17
+ "python-dotenv>=1.2.1",
17
18
  "pyyaml>=6.0.3",
18
19
  "rich>=14.2.0",
19
20
  "ruff>=0.14.10",
@@ -40,8 +41,7 @@ dev = [
40
41
  "pre-commit>=4.5.1",
41
42
  "pytest-cov>=7.0.0",
42
43
  "pytest>=9.0.2",
43
- "python-dotenv>=1.2.1",
44
- "python_package",
44
+ "python-package",
45
45
  "toml-sort>=0.24.3"
46
46
  ]
47
47
 
@@ -52,6 +52,9 @@ build-backend = "uv_build"
52
52
  [tool.deptry]
53
53
  known_first_party = ["bake", "bakelib", "python_package"]
54
54
 
55
+ [tool.deptry.package_module_name_map]
56
+ pydantic = ["pydantic", "pydantic_core"]
57
+
55
58
  [tool.ruff]
56
59
  line-length = 100
57
60
 
@@ -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)
@@ -0,0 +1,23 @@
1
+ from bakelib.environ import (
2
+ BaseEnv,
3
+ DevEnvBakebook,
4
+ EnvBakebook,
5
+ GcpLandingZoneEnv,
6
+ ProdEnvBakebook,
7
+ StagingEnvBakebook,
8
+ get_bakebook,
9
+ )
10
+ from bakelib.space.base import BaseSpace
11
+ from bakelib.space.python import PythonSpace
12
+
13
+ __all__ = [
14
+ "BaseEnv",
15
+ "BaseSpace",
16
+ "DevEnvBakebook",
17
+ "EnvBakebook",
18
+ "GcpLandingZoneEnv",
19
+ "ProdEnvBakebook",
20
+ "PythonSpace",
21
+ "StagingEnvBakebook",
22
+ "get_bakebook",
23
+ ]
@@ -0,0 +1,14 @@
1
+ from .bakebook import DevEnvBakebook, EnvBakebook, ProdEnvBakebook, StagingEnvBakebook
2
+ from .base import BaseEnv
3
+ from .get_bakebook import get_bakebook
4
+ from .presets import GcpLandingZoneEnv
5
+
6
+ __all__ = [
7
+ "BaseEnv",
8
+ "DevEnvBakebook",
9
+ "EnvBakebook",
10
+ "GcpLandingZoneEnv",
11
+ "ProdEnvBakebook",
12
+ "StagingEnvBakebook",
13
+ "get_bakebook",
14
+ ]
@@ -0,0 +1,30 @@
1
+ from typing import Annotated
2
+
3
+ from pydantic import Field, computed_field
4
+
5
+ from bake.bakebook.bakebook import Bakebook
6
+ from bakelib.environ.base import BaseEnv
7
+
8
+ BaseEnvFieldType = Annotated[BaseEnv, Field(exclude=True, repr=False)]
9
+
10
+
11
+ class EnvBakebook(Bakebook):
12
+ # Field name uses underscore suffix to avoid Pydantic reading from ENV env var
13
+ env_: BaseEnvFieldType = Field(exclude=True, repr=False)
14
+
15
+ @computed_field
16
+ @property
17
+ def env(self) -> BaseEnv:
18
+ return self.env_
19
+
20
+
21
+ class DevEnvBakebook(EnvBakebook):
22
+ env_: BaseEnvFieldType = BaseEnv("dev")
23
+
24
+
25
+ class StagingEnvBakebook(EnvBakebook):
26
+ env_: BaseEnvFieldType = BaseEnv("staging")
27
+
28
+
29
+ class ProdEnvBakebook(EnvBakebook):
30
+ env_: BaseEnvFieldType = BaseEnv("prod")
@@ -0,0 +1,112 @@
1
+ from typing import Any, ClassVar
2
+
3
+ from pydantic import GetCoreSchemaHandler
4
+ from pydantic_core import CoreSchema, core_schema
5
+
6
+
7
+ class BaseEnv(str):
8
+ """BaseEnvironment string with comparison and Pydantic support.
9
+
10
+ Inherits from str to provide natural string behavior while adding
11
+ comparison operators for priority-based ordering.
12
+
13
+ ENV_ORDER is a list where each element is either:
14
+ - A string (normal priority, order matters)
15
+ - A set of strings (equal priority group)
16
+
17
+ Example: ["dev", "staging", {"prod", "share"}]
18
+ - "dev" has highest priority (index 0)
19
+ - "staging" has medium priority (index 1)
20
+ - "prod" and "share" have equal lowest priority (both in set at index 2)
21
+ """
22
+
23
+ ENV_ORDER: ClassVar[list[str | set[str]]] = ["dev", "staging", "prod"]
24
+
25
+ def __init__(self, value: str):
26
+ if not self._is_valid(value):
27
+ raise ValueError(
28
+ f"Invalid {self.__class__.__name__}: '{value}'. "
29
+ f"Must be one of: {self._flattened_env_order()}"
30
+ )
31
+
32
+ @classmethod
33
+ def _is_valid(cls, value: str) -> bool:
34
+ try:
35
+ cls._get_priority_index(value)
36
+ return True
37
+ except ValueError:
38
+ return False
39
+
40
+ @classmethod
41
+ def _flattened_env_order(cls) -> list[str]:
42
+ result: list[str] = []
43
+ for item in cls.ENV_ORDER:
44
+ if isinstance(item, set):
45
+ result.extend(sorted(item))
46
+ else:
47
+ result.append(item)
48
+ return result
49
+
50
+ @classmethod
51
+ def _get_priority_index(cls, value: str) -> int:
52
+ for idx, item in enumerate(cls.ENV_ORDER):
53
+ is_in_set = isinstance(item, set) and value in item
54
+ is_equal = item == value
55
+ if is_in_set or is_equal:
56
+ return idx
57
+ raise ValueError(f"Value '{value}' not found in ENV_ORDER")
58
+
59
+ def __lt__(self, other: str) -> bool:
60
+ if type(other) is not type(self):
61
+ return NotImplemented
62
+ self_idx = self._get_priority_index(str(self))
63
+ other_idx = self._get_priority_index(str(other))
64
+ if self_idx != other_idx:
65
+ return self_idx < other_idx
66
+ # Same priority group, use alphabetical as tiebreaker
67
+ return str(self) < str(other)
68
+
69
+ def __le__(self, other: str) -> bool:
70
+ if type(other) is not type(self):
71
+ return NotImplemented
72
+ return self < other or self == other
73
+
74
+ def __gt__(self, other: str) -> bool:
75
+ if type(other) is not type(self):
76
+ return NotImplemented
77
+ return not (self < other) and self != other
78
+
79
+ def __ge__(self, other: str) -> bool:
80
+ if type(other) is not type(self):
81
+ return NotImplemented
82
+ return not (self < other)
83
+
84
+ def __eq__(self, other: object) -> bool:
85
+ if type(other) is not type(self):
86
+ return False
87
+ return str(self) == str(other)
88
+
89
+ def __ne__(self, other: object) -> bool:
90
+ return not self.__eq__(other)
91
+
92
+ def __hash__(self) -> int:
93
+ return hash(str(self))
94
+
95
+ def __repr__(self) -> str:
96
+ return f"{self.__class__.__name__}('{self!s}')"
97
+
98
+ @classmethod
99
+ def __get_pydantic_core_schema__(
100
+ cls, source_type: Any, handler: GetCoreSchemaHandler
101
+ ) -> CoreSchema:
102
+ """Pydantic v2 integration for custom type validation."""
103
+ return core_schema.no_info_after_validator_function(cls, handler(str))
104
+
105
+ @classmethod
106
+ def validate(cls, v: object) -> "BaseEnv":
107
+ """Validate and convert input to BaseEnv."""
108
+ if isinstance(v, cls):
109
+ return v
110
+ if isinstance(v, str):
111
+ return cls(v)
112
+ raise ValueError(f"Cannot convert {type(v).__name__} to {cls.__name__}")
@@ -0,0 +1,49 @@
1
+ import os
2
+ from typing import TypeVar
3
+
4
+ from dotenv import load_dotenv as _load_dotenv
5
+
6
+ from .bakebook import EnvBakebook
7
+
8
+ # TODO: When min Python >= 3.12, use PEP 695 type parameter syntax:
9
+ # def get_bakebook[E: EnvBakebook](bakebooks: list[E], ...) -> E:
10
+ E = TypeVar("E", bound=EnvBakebook)
11
+
12
+
13
+ def get_bakebook(
14
+ bakebooks: list[E],
15
+ *,
16
+ env_var_name: str = "ENV",
17
+ env_value: str | None = None,
18
+ load_dotenv: bool = True,
19
+ ) -> E:
20
+ if not bakebooks:
21
+ raise ValueError("bakebooks list cannot be empty")
22
+
23
+ # Convert to dict for O(1) lookup and duplicate detection
24
+ bakebooks_by_env: dict[str, E] = {}
25
+ for bb in bakebooks:
26
+ if not hasattr(bb, "env") or bb.env is None:
27
+ raise ValueError(f"All bakebooks must have an 'env' attribute. Found: {bb}")
28
+ env = str(bb.env)
29
+ if env in bakebooks_by_env:
30
+ raise ValueError(f"Duplicate env '{env}' found in bakebooks list")
31
+ bakebooks_by_env[env] = bb
32
+
33
+ # Get environment value
34
+ if env_value is None:
35
+ if load_dotenv:
36
+ _load_dotenv()
37
+ env_value = os.getenv(env_var_name)
38
+
39
+ # Env var not set - return lowest priority (min)
40
+ if env_value is None or env_value == "":
41
+ return bakebooks_by_env[str(min(bb.env for bb in bakebooks))]
42
+ # If env var is set, require exact match
43
+ elif env_value in bakebooks_by_env:
44
+ return bakebooks_by_env[env_value]
45
+
46
+ raise ValueError(
47
+ f"No bakebook found with env='{env_value}'. "
48
+ f"Available envs: {sorted(bakebooks_by_env.keys())}"
49
+ )
@@ -0,0 +1,70 @@
1
+ """Preset environment configurations.
2
+
3
+ This module contains pre-configured environment classes for common use cases.
4
+ Users can also create their own environments by inheriting from BaseEnv.
5
+ """
6
+
7
+ from typing import ClassVar
8
+
9
+ from bakelib.environ.base import BaseEnv
10
+
11
+
12
+ class GcpLandingZoneEnv(BaseEnv):
13
+ """GCP Landing Zone base environments.
14
+
15
+ Environment codes follow GCP Security Foundations Blueprint conventions:
16
+ https://docs.cloud.google.com/architecture/blueprints/security-foundations/summary
17
+
18
+ Environment Codes:
19
+ - d - Development
20
+ - n - Nonproduction
21
+ - p - Production
22
+ - s - Shared
23
+ - b - Bootstrap
24
+ - c - Common
25
+ - net - Network
26
+
27
+ Tiers (ordered by priority, lower index = higher priority):
28
+ - d (development) - lowest priority
29
+ - n (nonprod)
30
+ - p/s/b/c/net - highest priority, all equal (production + shared)
31
+
32
+ Example:
33
+ env = GcpLandingZoneEnv("d")
34
+ assert env < GcpLandingZoneEnv("n")
35
+ assert GcpLandingZoneEnv("n") < GcpLandingZoneEnv("p")
36
+ """
37
+
38
+ ENV_ORDER: ClassVar[list[str | set[str]]] = [
39
+ "d",
40
+ "n",
41
+ {"p", "s", "b", "c", "net"},
42
+ ]
43
+
44
+ def is_shared(self) -> bool:
45
+ return self.code in {"s", "b", "c", "net"}
46
+
47
+ @property
48
+ def name(self) -> str:
49
+ names = {
50
+ "d": "Development",
51
+ "n": "Nonproduction",
52
+ "p": "Production",
53
+ "s": "Shared",
54
+ "b": "Bootstrap",
55
+ "c": "Common",
56
+ "net": "Network",
57
+ }
58
+ return names[self.code]
59
+
60
+ @property
61
+ def code(self) -> str:
62
+ return str(self)
63
+
64
+ @property
65
+ def secondary_name(self) -> str:
66
+ return self.name if not self.is_shared() else "Shared"
67
+
68
+ @property
69
+ def secondary_code(self) -> str:
70
+ return self.code if not self.is_shared() else "s"
@@ -1,39 +0,0 @@
1
- import types
2
- from pathlib import Path
3
-
4
- from bake.samples import simple
5
- from bake.utils.constants import BAKEBOOK_NAME_IN_SAMPLES
6
-
7
- # Allowed sample modules
8
- # This dictionary acts as a whitelist for security - only these modules can be used
9
- ALLOWED_SAMPLE_MODULES: dict[str, types.ModuleType] = {
10
- simple.__name__: simple,
11
- }
12
-
13
-
14
- def write_bakefile(
15
- bakefile_path: Path, bakebook_name: str, sample_module: types.ModuleType
16
- ) -> None:
17
- if not hasattr(sample_module, BAKEBOOK_NAME_IN_SAMPLES):
18
- raise ValueError(
19
- f"Module `{sample_module.__name__}` must have `{BAKEBOOK_NAME_IN_SAMPLES}` attribute"
20
- )
21
-
22
- module_name = sample_module.__name__
23
- if module_name not in ALLOWED_SAMPLE_MODULES:
24
- raise ValueError(
25
- f"Module `{module_name}` is not in the allowed sample modules list. "
26
- f"Allowed modules: {list(ALLOWED_SAMPLE_MODULES.keys())}"
27
- )
28
-
29
- allowed_module = ALLOWED_SAMPLE_MODULES[module_name]
30
- if sample_module is not allowed_module:
31
- raise ValueError(f"Module `{module_name}` does not match the allowed module object")
32
-
33
- if allowed_module.__file__ is None:
34
- raise ValueError(f"Could not find file for module `{module_name}`")
35
-
36
- source_file_path = Path(allowed_module.__file__)
37
- original_bakefile_content = source_file_path.read_text()
38
- customized_content = original_bakefile_content.replace(BAKEBOOK_NAME_IN_SAMPLES, bakebook_name)
39
- bakefile_path.write_text(customized_content)
@@ -1,4 +0,0 @@
1
- from bakelib.space.base import BaseSpace
2
- from bakelib.space.python import PythonSpace
3
-
4
- __all__ = ["BaseSpace", "PythonSpace"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes