Skip to content

Commit

Permalink
feat: more flexbible Dependant implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
adriangb committed Oct 5, 2021
1 parent 1a01625 commit ec20923
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 49 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ install-poetry: .install-poetry
.init: .install-poetry
@echo "---- 📦 Building package ----"
rm -rf .venv
poetry install --default
poetry install --default --with lint
touch .init

.docs: .init
Expand Down
43 changes: 14 additions & 29 deletions di/dependant.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import inspect
from typing import Any, Dict, Optional, cast, overload
from typing import Any, Dict, Optional, overload

from di._docstrings import join_docstring_from
from di._inspect import get_parameters, infer_call_from_annotation
Expand All @@ -24,19 +24,6 @@
)


_expected_attributes = ("call", "scope", "share", "get_dependencies")


def _is_dependant_protocol_instance(o: object) -> bool:
# run cheap attribute checks before running isinstance
# isinstance is expensive since it runs reflection on methods
# to check argument types, etc.
for attr in _expected_attributes:
if not hasattr(o, attr):
return False
return isinstance(o, DependantProtocol)


class Dependant(DependantProtocol[DependencyType], object):
@overload
def __init__(
Expand Down Expand Up @@ -123,15 +110,14 @@ def gather_parameters(self) -> Dict[str, inspect.Parameter]:
assert self.call is not None, "Cannot gather parameters without a bound call"
return get_parameters(self.call)

@join_docstring_from(DependantProtocol[Any].infer_call_from_annotation)
def infer_call_from_annotation(
self, param: inspect.Parameter
) -> DependencyProvider:
if param.annotation is param.empty:
raise WiringError(
"Cannot wire a parameter with no default and no type annotation"
)
return infer_call_from_annotation(param)
def register_parameter(self, param: inspect.Parameter) -> None:
if self.call is None:
call = infer_call_from_annotation(param)
if call is inspect.Parameter.empty:
raise WiringError(
"Cannot wire a parameter with no default and no type annotation"
)
self.call = call

def gather_dependencies(
self,
Expand All @@ -150,25 +136,24 @@ def gather_dependencies(
raise WiringError(
"Dependencies may not use variable positional or keyword arguments"
)
if _is_dependant_protocol_instance(param.default):
sub_dependant = cast(DependantProtocol[Any], param.default)
if sub_dependant.call is None:
sub_dependant.call = sub_dependant.infer_call_from_annotation(param)
if isinstance(param.default, Dependant):
sub_dependant: DependantProtocol[Any] = param.default
elif param.default is param.empty:
sub_dependant = self.create_sub_dependant(
call=self.infer_call_from_annotation(param),
call=None,
scope=self.scope,
share=self.share,
)
else:
continue # pragma: no cover
sub_dependant.register_parameter(param)
res[param_name] = DependencyParameter(
dependency=sub_dependant, parameter=param
)
return res

def create_sub_dependant(
self, call: DependencyProvider, scope: Scope, share: bool
self, call: Optional[DependencyProvider], scope: Scope, share: bool
) -> DependantProtocol[Any]:
"""Create a Dependant instance from a sub-dependency of this Dependency.
Expand Down
21 changes: 8 additions & 13 deletions di/types/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@
else:
from typing import Protocol, runtime_checkable

from di.types.providers import (
DependencyProvider,
DependencyProviderType,
DependencyType,
)
from di.types.providers import DependencyProviderType, DependencyType
from di.types.scopes import Scope


Expand Down Expand Up @@ -58,15 +54,14 @@ def get_dependencies(
"""
raise NotImplementedError

def infer_call_from_annotation(
self, param: inspect.Parameter
) -> DependencyProvider:
"""Called when the dependency was not explicitly passed a callable.
def register_parameter(self, param: inspect.Parameter) -> None:
"""Called by the parent so that us / this / the child can register
the parameter it is attached to.
It is important to note that param in this context refers to the parameter in this
Dependant's parent.
For example, in the case of `def func(thing: Something = Dependency())` this method
will be called with a Parameter corresponding to Something.
It is *required* that this method register a noe None `call` method,
if one is not already present.
This can also be used for recording type annotations or parameter names.
"""
raise NotImplementedError

Expand Down
8 changes: 3 additions & 5 deletions docs/src/headers_example.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import inspect
from typing import Any, Callable, Mapping, Optional
from typing import Any, Mapping, Optional

from di import Container, Dependant, Depends

Expand All @@ -14,9 +14,7 @@ def __init__(self, alias: Optional[str]) -> None:
self.alias = alias
super().__init__(call=None, scope=None, share=False)

def infer_call_from_annotation(
self, param: inspect.Parameter
) -> Callable[[Request], Any]:
def register_parameter(self, param: inspect.Parameter) -> None:
if self.alias is not None:
name = self.alias
else:
Expand All @@ -25,7 +23,7 @@ def infer_call_from_annotation(
def get_header(request: Request = Depends()) -> str:
return param.annotation(request.headers[name])

return get_header
self.call = get_header


def Header(alias: Optional[str] = None) -> Any:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "di"
version = "0.6.1"
version = "0.7.0"
description = "Autowiring dependency injection"
authors = ["Adrian Garcia Badaracco <[email protected]>"]
readme = "README.md"
Expand Down

0 comments on commit ec20923

Please sign in to comment.