Skip to content

Commit c7b21c9

Browse files
nightblureivan
andauthored
Make attrgetter extract value from any nesting level of objects (#57)
* Fix attrgetter logic to get the attribute value at any nesting level of objects --------- Co-authored-by: ivan <ivan.belyaev@ailet.com>
1 parent 11a5204 commit c7b21c9

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

tests/test_attr_getter.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import random
2+
from dataclasses import dataclass, field
3+
4+
import pytest
5+
6+
from that_depends import providers
7+
from that_depends.providers.attr_getter import _get_value_from_object_by_dotted_path
8+
9+
10+
@dataclass
11+
class Nested2:
12+
some_const = 144
13+
14+
15+
@dataclass
16+
class Nested1:
17+
nested2_attr: Nested2 = field(default_factory=Nested2)
18+
19+
20+
@dataclass
21+
class Settings:
22+
some_str_value: str = "some_string_value"
23+
some_int_value: int = 3453621
24+
nested1_attr: Nested1 = field(default_factory=Nested1)
25+
26+
27+
@dataclass
28+
class NestingTestDTO: ...
29+
30+
31+
@pytest.fixture()
32+
def some_settings_provider() -> providers.Singleton[Settings]:
33+
return providers.Singleton(Settings)
34+
35+
36+
def test_attr_getter_with_zero_attribute_depth(some_settings_provider: providers.Singleton[Settings]) -> None:
37+
attr_getter = some_settings_provider.some_str_value
38+
assert attr_getter.sync_resolve() == Settings().some_str_value
39+
40+
41+
def test_attr_getter_with_more_than_zero_attribute_depth(some_settings_provider: providers.Singleton[Settings]) -> None:
42+
attr_getter = some_settings_provider.nested1_attr.nested2_attr.some_const
43+
assert attr_getter.sync_resolve() == Nested2().some_const
44+
45+
46+
@pytest.mark.parametrize(
47+
("field_count", "test_field_name", "test_value"),
48+
[(1, "test_field", "sdf6fF^SF(FF*4ffsf"), (5, "nested_field", -252625), (50, "50_lvl_field", 909234235)],
49+
)
50+
def test_nesting_levels(field_count: int, test_field_name: str, test_value: str | int) -> None:
51+
obj = NestingTestDTO()
52+
fields = [f"field_{i}" for i in range(1, field_count + 1)]
53+
random.shuffle(fields)
54+
55+
attr_path = ".".join(fields) + f".{test_field_name}"
56+
obj_copy = obj
57+
58+
while fields:
59+
field_name = fields.pop(0)
60+
setattr(obj_copy, field_name, NestingTestDTO())
61+
obj_copy = obj_copy.__getattribute__(field_name)
62+
63+
setattr(obj_copy, test_field_name, test_value)
64+
65+
attr_value = _get_value_from_object_by_dotted_path(obj, attr_path)
66+
assert attr_value == test_value
Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import typing
2+
from operator import attrgetter
23

34
from that_depends.providers.base import AbstractProvider
45

@@ -7,15 +8,28 @@
78
P = typing.ParamSpec("P")
89

910

11+
def _get_value_from_object_by_dotted_path(obj: typing.Any, path: str) -> typing.Any: # noqa: ANN401
12+
attribute_getter = attrgetter(path)
13+
return attribute_getter(obj)
14+
15+
1016
class AttrGetter(AbstractProvider[T]):
11-
__slots__ = "_provider", "_attr_name"
17+
__slots__ = "_provider", "_attrs"
1218

1319
def __init__(self, provider: AbstractProvider[T], attr_name: str) -> None:
1420
self._provider = provider
15-
self._attr_name = attr_name
21+
self._attrs = [attr_name]
22+
23+
def __getattr__(self, attr: str) -> "AttrGetter[T]":
24+
self._attrs.append(attr)
25+
return self
1626

1727
async def async_resolve(self) -> typing.Any: # noqa: ANN401
18-
return getattr(await self._provider.async_resolve(), self._attr_name)
28+
resolved_provider_object = await self._provider.async_resolve()
29+
attribute_path = ".".join(self._attrs)
30+
return _get_value_from_object_by_dotted_path(resolved_provider_object, attribute_path)
1931

2032
def sync_resolve(self) -> typing.Any: # noqa: ANN401
21-
return getattr(self._provider.sync_resolve(), self._attr_name)
33+
resolved_provider_object = self._provider.sync_resolve()
34+
attribute_path = ".".join(self._attrs)
35+
return _get_value_from_object_by_dotted_path(resolved_provider_object, attribute_path)

0 commit comments

Comments
 (0)