diff --git a/injector/__init__.py b/injector/__init__.py index 55f7ab2..596c2e5 100644 --- a/injector/__init__.py +++ b/injector/__init__.py @@ -53,7 +53,7 @@ log = logging.getLogger('injector') log.addHandler(logging.NullHandler()) -if log.level == logging.NOTSET: +if log.level == logging.NOTSET: # pragma: no cover log.setLevel(logging.WARN) T = TypeVar('T') @@ -782,9 +782,9 @@ def _punch_through_alias(type_: Any) -> type: def _get_origin(type_: type) -> Optional[type]: origin = getattr(type_, '__origin__', None) # Older typing behaves differently there and stores Dict and List as origin, we need to be flexible. - if origin is List: + if origin is List: # pragma: no cover return list - elif origin is Dict: + elif origin is Dict: # pragma: no cover return dict return origin diff --git a/injector_test.py b/injector_test.py index 917f34e..020e130 100644 --- a/injector_test.py +++ b/injector_test.py @@ -43,6 +43,7 @@ ScopeDecorator, SingletonScope, UnknownArgument, + UnknownProvider, UnsatisfiedRequirement, get_bindings, inject, @@ -1049,6 +1050,15 @@ def test_custom_scope(): injector.get(Handler) +def test_get_accepts_a_scope_decorator_and_applies_that_scope(): + class A: + pass + + injector = Injector() + assert injector.get(A) is not injector.get(A) + assert injector.get(A, scope=singleton) is injector.get(A, scope=singleton) + + def test_binder_install(): class ModuleA(Module): def configure(self, binder): @@ -1112,6 +1122,12 @@ def test_binder_provider_for_type_with_metaclass(): assert isinstance(binder.provider_for(A, None).get(injector), A) +def test_binder_provider_for_raises_unknown_provider_for_undeterminable_binding(): + binder = Injector().binder + with pytest.raises(UnknownProvider): + binder.provider_for('not-a-type', to='a string value') + + class ClassA: def __init__(self, parameter): pass @@ -1618,6 +1634,16 @@ def __init__(self, message: str) -> None: del X +def test_provider_with_unresolvable_forward_reference_return_type_raises_name_error(): + class CustomModule(Module): + @provider + def provide_x(self) -> 'ReferenceThatCannotBeResolved': + return object() + + with pytest.raises(NameError): + Injector(CustomModule) + + def test_more_useful_exception_is_raised_when_parameters_type_is_any(): @inject def fun(a: Any) -> None: @@ -1640,6 +1666,41 @@ def fun(a: Any) -> None: injector.call_with_injection(fun) +def test_create_object_wraps_new_typeerror_in_call_error(): + class ClassWhoseNewRequiresAnArgument: + def __new__(cls, required_argument): + return super().__new__(cls) + + with pytest.raises(CallError): + Injector().create_object(ClassWhoseNewRequiresAnArgument) + + +def test_unsatisfied_requirement_message_names_owning_module_for_a_function(): + class Unbound: + pass + + @inject + def function(dependency: Unbound) -> None: + pass + + injector = Injector(auto_bind=False) + with pytest.raises(UnsatisfiedRequirement) as exc_info: + injector.call_with_injection(function) + + assert str(exc_info.value) == '%s has an unsatisfied requirement on Unbound' % __name__ + + +def test_unsatisfied_requirement_message_describes_a_tuple_interface(): + class A: + pass + + injector = Injector(auto_bind=False) + with pytest.raises(UnsatisfiedRequirement) as exc_info: + injector.get((A,)) + + assert str(exc_info.value) == 'unsatisfied requirement on [A]' + + def test_optionals_are_ignored_for_now(): @inject def fun(s: str = None): @@ -2026,6 +2087,14 @@ def function(a: Inject[Inject[int]]) -> None: assert get_bindings(function) == {'a': int} +def test_get_bindings_excludes_union_with_a_noinject_member() -> None: + @inject + def function_with_noinject_nested_in_union(a: Union[NoInject[int], str]) -> None: + pass + + assert get_bindings(function_with_noinject_nested_in_union) == {} + + # Tests https://github.com/alecthomas/injector/issues/202 def test_get_bindings_for_pep_604(): @inject diff --git a/pytest.ini b/pytest.ini index cf7ece3..7cedea3 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] -addopts = -v --tb=native --doctest-glob=*.md --doctest-modules --cov-report term --cov-report html --cov-report xml --cov=injector --cov-branch --cov-fail-under=90 +addopts = -v --tb=native --doctest-glob=*.md --doctest-modules --cov-report term --cov-report html --cov-report xml --cov=injector --cov-branch --cov-fail-under=100 norecursedirs = __pycache__ *venv* .git build