Skip to content

Commit

Permalink
fix: show resolver stack in missing dependency hint #32
Browse files Browse the repository at this point in the history
  • Loading branch information
proofit404 committed Dec 7, 2021
1 parent a86ec19 commit edf4436
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 44 deletions.
5 changes: 3 additions & 2 deletions src/_dependencies/injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ def __and__(cls, other):
return type(cls.__name__, (cls, other), {})

def __enter__(cls):
return _Scope(cls.__dependencies__)
return _Scope(cls.__name__, cls.__dependencies__)

def __exit__(self, exc_type, exc_value, traceback):
pass

def __getattr__(cls, attrname):
resolved = getattr(_Scope(cls.__dependencies__), attrname)
scope = _Scope(cls.__name__, cls.__dependencies__)
resolved = getattr(scope, attrname)
cls.__dependencies__.get(attrname).resolved()
return resolved

Expand Down
3 changes: 2 additions & 1 deletion src/_dependencies/objects/nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def __init__(self, injector):

def __call__(self, __self__):
parent = _Spec(lambda: __self__, {}, set(), set(), lambda: "'Injector'")
name = self.injector.__name__
graph = self.injector.__dependencies__
graph.specs["__parent__"] = parent
return _Scope(graph)
return _Scope(name, graph)
7 changes: 1 addition & 6 deletions src/_dependencies/resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ def is_optional(self, spec):
if self.state.have_default:
self.state.pop()
return True
if self.state.full():
message = "Can not resolve attribute {!r} while building {!r}".format(
self.state.current, self.state.stack.pop()[0]
)
else:
message = f"Can not resolve attribute {self.state.current!r}"
message = f"Can not resolve attribute {self.state.current!r}:\n\n{self.state!r}"
raise DependencyError(message)

def create(self, factory, args):
Expand Down
4 changes: 2 additions & 2 deletions src/_dependencies/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ class _IsScope:


class _Scope:
def __new__(cls, graph):
def __new__(cls, name, graph):
def getattr_method(self, attrname):
return _Resolver(graph, cache, attrname).resolve()

instance = type(cls.__name__, (_IsScope,), {"__getattr__": getattr_method})()
instance = type(name, (_IsScope,), {"__getattr__": getattr_method})()
cache = {"__self__": instance}
return instance
18 changes: 16 additions & 2 deletions src/_dependencies/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,19 @@ def kwargs(self, args):
def should(self, arg, have_default):
return arg not in self.tried or (arg not in self.cache and not have_default)

def full(self):
return len(self.stack) > 0
def __repr__(self):
indentation = _Indentation()
name = self.cache["__self__"].__class__.__name__
attributes = [attrname for attrname, have_default in self.stack]
attributes.append(self.current)
return "\n".join(f"{indentation()}{name}.{attrname}" for attrname in attributes)


class _Indentation:
def __init__(self):
self.index = 0

def __call__(self):
result = " " * self.index
self.index += 1
return result
56 changes: 32 additions & 24 deletions tests/test_injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,14 @@ class Container(Injector):
with pytest.raises(DependencyError) as exc_info:
Container.foo

assert str(exc_info.value) == "Can not resolve attribute 'y' while building 'foo'"
expected = """
Can not resolve attribute 'y':
Container.foo
Container.y
""".strip()

assert str(exc_info.value) == expected


def test_class_named_argument_default_value():
Expand Down Expand Up @@ -368,16 +375,6 @@ class Foo(Injector):
assert dir(Foo) == ["x", "y", "z"]


def test_show_injected_dependencies_with_dir():
"""`dir` should show injected dependencies and hide `__dependencies__` container."""

class Foo(Injector):
x = 1

assert "x" in dir(Foo)
assert "__dependencies__" not in dir(Foo)


def test_show_injected_dependencies_with_dir_once():
"""Do not repeat injected dependencies in the inheritance chain."""

Expand Down Expand Up @@ -901,56 +898,67 @@ def _d851e0414bdf(Container1, Container2, Container3):
return (Container1 & Container2 & Container3).foo.x


attribute_error = CodeCollector()
attribute_error = CodeCollector("container_name", "code")


@attribute_error.parametrize
def test_attribute_error(code):
def test_attribute_error(container_name, code):
"""Raise `DependencyError` if we can't find dependency."""
with pytest.raises(DependencyError) as exc_info:
code()

assert str(exc_info.value) == "Can not resolve attribute 'test'"
expected = f"""
Can not resolve attribute 'test':
{container_name}.test
""".strip()

assert str(exc_info.value) == expected

@attribute_error

@attribute_error("Foo")
def _c58b054bfcd0():
class Foo(Injector):
x = 1

Foo.test


@attribute_error
@attribute_error("Injector")
def _f9c50c81e8c9():
Foo = Injector(x=1)

Foo.test


@attribute_error
@attribute_error("Foo")
def _e2f16596a652():
class Foo(Injector):
x = 1

Foo(y=2).test


incomplete_dependencies = CodeCollector()
incomplete_dependencies = CodeCollector("container_name", "code")


@incomplete_dependencies.parametrize
def test_incomplete_dependencies_error(code):
def test_incomplete_dependencies_error(container_name, code):
"""Raise `DependencyError` if we can't find dependency."""
with pytest.raises(DependencyError) as exc_info:
code()

assert (
str(exc_info.value) == "Can not resolve attribute 'test' while building 'bar'"
)
expected = f"""
Can not resolve attribute 'test':
{container_name}.bar
{container_name}.test
""".strip()

assert str(exc_info.value) == expected


@incomplete_dependencies
@incomplete_dependencies("Foo")
def _c4e7ecf75167():
class Bar:
def __init__(self, test, two=2):
Expand All @@ -962,7 +970,7 @@ class Foo(Injector):
Foo.bar


@incomplete_dependencies
@incomplete_dependencies("Injector")
def _dmsMgYqbsHgB():
class Bar:
def __init__(self, test):
Expand Down
20 changes: 13 additions & 7 deletions tests/test_objects_this.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,11 @@ def _rN3suiVzhqMM(Root):
Injector(root=Root, foo=this.Nested.foo, Nested=Injector(foo=(this << 2).bar)).root


attribute_error = CodeCollector()
attribute_error = CodeCollector("container_name", "code")


@attribute_error.parametrize
def test_attribute_error_on_parent_access(code):
def test_attribute_error_on_parent_access(container_name, code):
"""Verify `this` object expression against existed dependencies.
We should raise `AttributeError` if we have correct number of parents but specify
Expand All @@ -305,10 +305,16 @@ def __init__(self, foo):
with pytest.raises(DependencyError) as exc_info:
code(Root)

assert str(exc_info.value) == "Can not resolve attribute 'bar'"
expected = f"""
Can not resolve attribute 'bar':
{container_name}.bar
""".strip()

@attribute_error
assert str(exc_info.value) == expected


@attribute_error("Container")
def _t1jn9RI9v42t(Root):
class Container(Injector):
root = Root
Expand All @@ -317,12 +323,12 @@ class Container(Injector):
Container.root


@attribute_error
@attribute_error("Injector")
def _vnmkIELBH3MN(Root):
Injector(root=Root, foo=this.bar).root


@attribute_error
@attribute_error("Container")
def _yOEj1qQfsXHy(Root):
class Container(Injector):
root = Root
Expand All @@ -334,7 +340,7 @@ class Nested(Injector):
Container.root


@attribute_error
@attribute_error("Injector")
def _pG9M52ZRQr2S(Root):
Injector(root=Root, foo=this.Nested.foo, Nested=Injector(foo=(this << 1).bar)).root

Expand Down

0 comments on commit edf4436

Please sign in to comment.