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.
- {bakefile-0.0.6.dist-info → bakefile-0.0.8.dist-info}/METADATA +2 -1
- {bakefile-0.0.6.dist-info → bakefile-0.0.8.dist-info}/RECORD +10 -5
- bakelib/__init__.py +20 -1
- bakelib/environ/__init__.py +14 -0
- bakelib/environ/bakebook.py +30 -0
- bakelib/environ/base.py +112 -0
- bakelib/environ/get_bakebook.py +49 -0
- bakelib/environ/presets.py +70 -0
- {bakefile-0.0.6.dist-info → bakefile-0.0.8.dist-info}/WHEEL +0 -0
- {bakefile-0.0.6.dist-info → bakefile-0.0.8.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: bakefile
|
|
3
|
-
Version: 0.0.
|
|
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=
|
|
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.
|
|
61
|
-
bakefile-0.0.
|
|
62
|
-
bakefile-0.0.
|
|
63
|
-
bakefile-0.0.
|
|
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__ = [
|
|
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")
|
bakelib/environ/base.py
ADDED
|
@@ -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"
|
|
File without changes
|
|
File without changes
|