Skip to content

Commit

Permalink
feat: move retrievers into separate module; look at parent dependency…
Browse files Browse the repository at this point in the history
… instance for values.
  • Loading branch information
joshorr committed Jan 3, 2023
1 parent 446e52b commit b296850
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 151 deletions.
84 changes: 42 additions & 42 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ classifiers = [
[tool.poetry.dependencies]
python = "^3.8"
xsentinels = "^1.2.0"
xinject = "^1.1.0"
xinject = "^1.2.0"
xloop = "^1.0.1"
xbool = "^1.0.0"
ciso8601 = "^2.3.0"
Expand Down
5 changes: 3 additions & 2 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import pytest as pytest

from xsettings.fields import SettingsField, generate_setting_fields
from xsettings.settings import SettingsRetriever, Settings
from xsettings.retreivers import SettingsRetriever
from xsettings import Settings


@pytest.mark.parametrize(
Expand Down Expand Up @@ -93,7 +94,7 @@ def test_property_as_retreiver():
def my_default_retreiver(*, field: SettingsField, settings: 'Settings'):
return f"field.name={field.name}"

class TestSettings(Settings, retrievers=[my_default_retreiver]):
class TestSettings(Settings, default_retrievers=[my_default_retreiver]):
my_str_field: str

@property
Expand Down
49 changes: 39 additions & 10 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

from xsettings.env_settings import EnvSettings
from xsettings.fields import SettingsConversionError
from xsettings.settings import Settings, SettingsField, SettingsValueError, SettingsRetriever
from xsettings.settings import PropertyRetriever
from xsettings.settings import Settings, SettingsField, SettingsValueError
from xsettings.retreivers import SettingsRetriever, PropertyRetriever


def test_set_default_value_after_settings_subclass_created():
Expand Down Expand Up @@ -48,7 +48,7 @@ def __call__(self, *, field: SettingsField, settings: 'Settings') -> Any:
class MyForwardSettings(Settings):
my_forwarded_field: str = "my_forwarded_field-value"

class MySettings(Settings, retrievers=MyRetriever()):
class MySettings(Settings, default_retrievers=MyRetriever()):
my_field = "my_field-value"

@property
Expand Down Expand Up @@ -514,16 +514,16 @@ class MyChildSettings(MyParentSettings):
def test_inherit_multiple_retrievers():

def r1(*, field: SettingsField, settings: Settings):
if field.name == 'a':
return 'a-val'
return None
if field.name == 'a':
return 'a-val'
return None

def r2(*, field: SettingsField, settings: Settings):
if field.name == 'b_alt_name':
return True
return None
if field.name == 'b_alt_name':
return True
return None

class MyParentSettings(Settings, retrievers=[r1, r2]):
class MyParentSettings(Settings, default_retrievers=[r1, r2]):
# Make them fields in our Settings subclass, default value to another settings class.
a: str
b: bool = SettingsField(name="b_alt_name")
Expand Down Expand Up @@ -553,3 +553,32 @@ class MyChildSettings(MyParentSettings):
# Error should be unchanged:
with pytest.raises(SettingsValueError):
error_getting_non_optional_value = my_parent_settings.c


def test_grab_setting_values_from_parent_dependency_instances():
def r1(*, field: SettingsField, settings: Settings):
return 2 if field.name == 'c' else 'str-val'

class MySettings(Settings, default_retrievers=[r1]):
# Make them fields in our Settings subclass, default value to another settings class.
a: str
b: str
c: int

my_settings = MySettings.proxy()
my_settings.a = "override-a"

assert my_settings.a == 'override-a'
assert my_settings.b == 'str-val'
assert my_settings.c == 2

with MySettings(b='override-via-child-instance-b'):
# This value should come from the parent-instance to the one inside the above `with`
# (ie: the MySettings instance that is 'current' outside this `with` statement)
assert my_settings.a == 'override-a'
assert my_settings.b == 'override-via-child-instance-b'
assert my_settings.c == 2

assert my_settings.a == 'override-a'
assert my_settings.b == 'str-val'
assert my_settings.c == 2
5 changes: 3 additions & 2 deletions xsettings/env_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
from typing import Any

from xsettings.fields import SettingsField
from xsettings.settings import SettingsRetriever, Settings
from xsettings.settings import Settings
from xsettings.retreivers import SettingsRetriever


class EnvSettingsRetriever(SettingsRetriever):
def __call__(self, *, field: SettingsField, settings: 'Settings') -> Any:
return os.environ.get(field.name)


class EnvSettings(Settings, retrievers=EnvSettingsRetriever()):
class EnvSettings(Settings, default_retrievers=EnvSettingsRetriever()):
pass
21 changes: 11 additions & 10 deletions xsettings/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from xsentinels import unwrap_union, Default

if TYPE_CHECKING:
from .settings import Settings, SettingsRetrieverCallable, PropertyRetriever
from .settings import Settings
from .retreivers import PropertyRetriever, SettingsRetriever

T = TypeVar("T")

Expand Down Expand Up @@ -101,11 +102,11 @@ class SettingsField:
converter will always be called.
"""

retriever: 'SettingsRetrieverCallable' = None
retriever: 'SettingsRetriever' = None
"""
Retriever callable to use to retrieve settings from some source.
Can be any callable that follows the `SettingsRetrieverCallable` calling interface.
Can be any callable that follows the `SettingsRetriever` calling interface.
Although, subclassing `SettingsRetriever` makes things generally easier as it handles
some of the expected default behavior for you (example: `xyn_config.config.ConfigRetriever`).
Expand Down Expand Up @@ -222,7 +223,7 @@ def getter(self):
# Whatever we return from this internal method is what is left on
# the class afterwards. In this case, the original SettingsField (self).
def wrap_property_getter_func(func):
from xsettings.settings import PropertyRetriever
from xsettings.retreivers import PropertyRetriever
wrapped_property = property(fget=func)
self.retriever = PropertyRetriever(wrapped_property)
return self
Expand Down Expand Up @@ -499,12 +500,12 @@ def _add_field_default_from_attrs(class_attrs: Dict[str, Any], merge_field):
"We may support property setters in the future."
)

# For normal properties, we always wrap them in a PropertyRetreiver,
# and use that for the fields retreiver; we consider a normal property
# the 'retreiver' for that value. If user did not directly set the value
# For normal properties, we always wrap them in a PropertyRetriever,
# and use that for the field's retriever; we consider a normal property
# the 'retriever' for that value. If user did not directly set the value
# the retriever, ie: this PropertyRetriever will be called and it will in turn
# call the wrapped property to 'retreive' the value.
from xsettings.settings import PropertyRetriever
# call the wrapped property to 'retriever' the value.
from xsettings.retreivers import PropertyRetriever
field_values.retriever = PropertyRetriever(v)
else:
field_values.default_value = v
Expand Down Expand Up @@ -540,5 +541,5 @@ def _assert_retriever_valid(field):
return
assert callable(field.retriever), (
f"Invalid retriever for field {field}, needs to be callable, see "
f"SettingsRetrieverCallable."
f"SettingsRetriever."
)
Loading

0 comments on commit b296850

Please sign in to comment.