-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
182 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import os | ||
import pwd | ||
from typing import Any, Callable, Dict, Optional | ||
|
||
|
||
class ConfigProperty: | ||
def __init__( | ||
self, | ||
type_: type, | ||
default: Any = None, | ||
env_var: Optional[str] = None, | ||
default_lambda: Optional[Callable[[], Any]] = None, | ||
): | ||
self.type = type_ | ||
self.default = default | ||
self.env_var = env_var | ||
self.default_lambda = default_lambda | ||
|
||
|
||
class Config: | ||
_shared_instance = None | ||
|
||
def __init__(self, properties: Dict[str, ConfigProperty] | None = None): | ||
self._values: Dict[str, Any] = {} | ||
self._properties: Dict[str, ConfigProperty] = properties or { | ||
"user_id": ConfigProperty( | ||
str, | ||
env_var="KILN_USER_ID", | ||
default_lambda=lambda: pwd.getpwuid(os.getuid()).pw_name, | ||
), | ||
} | ||
|
||
@classmethod | ||
def shared(cls): | ||
if cls._shared_instance is None: | ||
cls._shared_instance = cls() | ||
return cls._shared_instance | ||
|
||
def __getattr__(self, name: str) -> Any: | ||
if name not in self._properties: | ||
raise AttributeError(f"Config has no attribute '{name}'") | ||
|
||
if name not in self._values: | ||
prop = self._properties[name] | ||
value = None | ||
if prop.env_var and prop.env_var in os.environ: | ||
value = os.environ[prop.env_var] | ||
elif prop.default is not None: | ||
value = prop.default | ||
elif prop.default_lambda is not None: | ||
value = prop.default_lambda() | ||
self._values[name] = prop.type(value) if value is not None else None | ||
|
||
return self._values[name] | ||
|
||
def __setattr__(self, name: str, value: Any) -> None: | ||
if name in ("_values", "_properties"): | ||
super().__setattr__(name, value) | ||
elif name in self._properties: | ||
self._values[name] = self._properties[name].type(value) | ||
else: | ||
raise AttributeError(f"Config has no attribute '{name}'") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import os | ||
|
||
import pytest | ||
|
||
from libs.core.kiln_ai.utils.config import Config, ConfigProperty | ||
|
||
|
||
def TestConfig(): | ||
return Config( | ||
properties={ | ||
"example_property": ConfigProperty( | ||
str, default="default_value", env_var="EXAMPLE_PROPERTY" | ||
), | ||
} | ||
) | ||
|
||
|
||
@pytest.fixture | ||
def reset_config(): | ||
Config._shared_instance = None | ||
yield | ||
Config._shared_instance = None | ||
|
||
|
||
def test_shared_instance(reset_config): | ||
config1 = Config.shared() | ||
config2 = Config.shared() | ||
assert config1 is config2 | ||
|
||
|
||
def test_separate_instances(reset_config): | ||
config1 = TestConfig() | ||
config2 = TestConfig() | ||
assert config1 is not config2 | ||
|
||
|
||
def test_property_default_value(reset_config): | ||
config = TestConfig() | ||
assert config.example_property == "default_value" | ||
|
||
|
||
def test_property_env_var(reset_config): | ||
os.environ["EXAMPLE_PROPERTY"] = "env_value" | ||
config = TestConfig() | ||
assert config.example_property == "env_value" | ||
del os.environ["EXAMPLE_PROPERTY"] | ||
|
||
|
||
def test_property_setter(reset_config): | ||
config = TestConfig() | ||
config.example_property = "new_value" | ||
assert config.example_property == "new_value" | ||
|
||
|
||
def test_nonexistent_property(reset_config): | ||
config = TestConfig() | ||
with pytest.raises(AttributeError): | ||
config.nonexistent_property | ||
|
||
|
||
def test_property_type_conversion(reset_config): | ||
Config._shared_instance = None | ||
|
||
config = Config(properties={"int_property": ConfigProperty(int, default="42")}) | ||
assert isinstance(config.int_property, int) | ||
assert config.int_property == 42 | ||
|
||
|
||
def test_property_priority(reset_config): | ||
os.environ["EXAMPLE_PROPERTY"] = "env_value" | ||
config = TestConfig() | ||
|
||
# Environment variable takes precedence over default | ||
assert config.example_property == "env_value" | ||
|
||
# Setter takes precedence over environment variable | ||
config.example_property = "new_value" | ||
assert config.example_property == "new_value" | ||
|
||
del os.environ["EXAMPLE_PROPERTY"] | ||
|
||
|
||
def test_lazy_loading(reset_config): | ||
config = TestConfig() | ||
assert "example_property" not in config._values | ||
_ = config.example_property | ||
assert "example_property" in config._values | ||
|
||
|
||
def test_default_lambda(reset_config): | ||
Config._shared_instance = None | ||
|
||
def default_lambda(): | ||
return "lambda_value" | ||
|
||
config = Config( | ||
properties={ | ||
"lambda_property": ConfigProperty(str, default_lambda=default_lambda) | ||
} | ||
) | ||
|
||
assert config.lambda_property == "lambda_value" | ||
|
||
# Test that the lambda is only called once | ||
assert "lambda_property" in config._values | ||
config._properties["lambda_property"].default_lambda = lambda: "new_lambda_value" | ||
assert config.lambda_property == "lambda_value" | ||
|
||
|
||
def test_user_id_default(reset_config): | ||
config = Config() | ||
# assert config.user_id == "scosman" | ||
assert len(config.user_id) > 0 |