bakefile 0.0.6__py3-none-any.whl → 0.0.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bakefile
3
- Version: 0.0.6
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
@@ -52,12 +52,17 @@ bake/utils/__init__.py,sha256=GUu_xlJy3RAHo6UcZXu2x4khxGqLHMA9Zos4hDiQIY8,326
52
52
  bake/utils/constants.py,sha256=mRq5IpgOTdlHOTWPq5dx0A-LwhiFkWgYHfr8cLWG7rY,471
53
53
  bake/utils/env.py,sha256=bzNdH_2bTJebQaw7D0uVJv-vzZ-uYl0pCAS8oQONVsA,190
54
54
  bake/utils/exceptions.py,sha256=pwsQnKH5ljMNxmqEREutXa7TohiBHATHg_D5kQUPT30,519
55
- bakelib/__init__.py,sha256=sZeRiNINWL8xI3b1MxkGyF3f2lKMjyhjKt7qyCCAufs,126
55
+ bakelib/__init__.py,sha256=7lWfLpL82AVkot3BXodJpyuEzEAiythFevMtzxzd_i4,455
56
+ bakelib/environ/__init__.py,sha256=XIFVtu8SQySjPetu9WR_Q8HgqxUzenMNm1K24pYSfNo,356
57
+ bakelib/environ/bakebook.py,sha256=gnOvi3t5Ww0_6N5wozXLVvLe8KAK-u-6_v0UYCl_iKY,749
58
+ bakelib/environ/base.py,sha256=azPUdc9C5zVU8iyXJrEm3uDfe39nG48DRAZiF9rTdHI,3753
59
+ bakelib/environ/get_bakebook.py,sha256=LihxB3VDcVq81KJy1HT-N-beyt2C7ReWm2kMPrd7VlA,1563
60
+ bakelib/environ/presets.py,sha256=IwHGeDeQe4e57k9_u_vBQ61bjMS5Z2Jj9zkXw6YJHAE,1943
56
61
  bakelib/space/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
62
  bakelib/space/base.py,sha256=bJlpPkP85xBu8X5fJoVaHrMzX27rQrYTEPSObwBANJ8,6008
58
63
  bakelib/space/python.py,sha256=UEr4Jo76T2cbAQdClVu7RvJYzflLE9i_xFohcNhjFjw,2597
59
64
  bakelib/space/utils.py,sha256=xx4X_txhDH_p97CKJ-KuvFpgfNBC0y_din1IBlUVusU,2983
60
- bakefile-0.0.6.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
61
- bakefile-0.0.6.dist-info/entry_points.txt,sha256=Ecvvh7BYHCPJ0UdntrDc3Od6AZdRPXN5Z7o_7ok_0Qw,107
62
- bakefile-0.0.6.dist-info/METADATA,sha256=6tec3_eRxbtTAsdHJHUC1Vo4yIondUR_U28oRq2Cp1c,2331
63
- bakefile-0.0.6.dist-info/RECORD,,
65
+ bakefile-0.0.8.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
66
+ bakefile-0.0.8.dist-info/entry_points.txt,sha256=Ecvvh7BYHCPJ0UdntrDc3Od6AZdRPXN5Z7o_7ok_0Qw,107
67
+ bakefile-0.0.8.dist-info/METADATA,sha256=3v3_P5s-qZZcRrMlgHwYEV8_lSP10mLPBe1unm7hWYg,2367
68
+ bakefile-0.0.8.dist-info/RECORD,,
bakelib/__init__.py CHANGED
@@ -1,4 +1,23 @@
1
+ from bakelib.environ import (
2
+ BaseEnv,
3
+ DevEnvBakebook,
4
+ EnvBakebook,
5
+ GcpLandingZoneEnv,
6
+ ProdEnvBakebook,
7
+ StagingEnvBakebook,
8
+ get_bakebook,
9
+ )
1
10
  from bakelib.space.base import BaseSpace
2
11
  from bakelib.space.python import PythonSpace
3
12
 
4
- __all__ = ["BaseSpace", "PythonSpace"]
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"