diff --git a/fastapi_startkit/src/fastapi_startkit/container/container.py b/fastapi_startkit/src/fastapi_startkit/container/container.py index 0ec26652..e075da8c 100644 --- a/fastapi_startkit/src/fastapi_startkit/container/container.py +++ b/fastapi_startkit/src/fastapi_startkit/container/container.py @@ -1,7 +1,7 @@ """Core of the IOC Container.""" import inspect -from typing import Any +from typing import Any, TypeVar, overload from ..exceptions import ( ContainerError, @@ -9,6 +9,8 @@ StrictContainerException, ) +T = TypeVar("T") + class Container: """Core of the Service Container. @@ -103,11 +105,21 @@ def singleton(self, name, class_obj): obj = self.resolve(class_obj) self.bind(name, obj) - def make(self, name, *arguments): + @overload + def make(self, name: type[T], *arguments: Any) -> T: ... + + @overload + def make(self, name: str, *arguments: Any) -> Any: ... + + def make(self, name: str | type[T], *arguments: Any) -> Any: """Retrieve a class from the container by key. + Class keys resolve to an instance of that class (make(SomeClass) -> SomeClass); + string keys resolve to whatever was bound (Any). A missing key raises rather + than returning None, so the return type is deliberately non-Optional. + Arguments: - name {string} -- Key in the container that you want to get. + name {string | type} -- Key in the container that you want to get. Raises: MissingContainerBindingNotFound -- Raised if the key is not in the container. diff --git a/fastapi_startkit/tests/core/test_container.py b/fastapi_startkit/tests/core/test_container.py index aae18b5a..04f6fd65 100644 --- a/fastapi_startkit/tests/core/test_container.py +++ b/fastapi_startkit/tests/core/test_container.py @@ -1,5 +1,7 @@ """Tests for the IoC service container.""" +from typing import Any, assert_type + import pytest from fastapi_startkit.container.container import Container @@ -515,3 +517,34 @@ def fn(unknown_param): with pytest.raises(ContainerError): container.resolve(fn) + + +# --------------------------------------------------------------------------- +# make() — static typing assertions (checked by basedpyright/pyright, +# executed at runtime as ordinary asserts) +# --------------------------------------------------------------------------- + + +class TestMakeTyping: + def test_make_with_class_key_is_typed_as_that_class(self, container): + container.bind("service_a", ServiceA) + + instance = container.make(ServiceA) + assert_type(instance, ServiceA) + assert isinstance(instance, ServiceA) + + def test_make_with_string_key_is_typed_as_any(self, container): + container.bind("service_a", ServiceA) + + instance = container.make("service_a") + assert_type(instance, Any) + assert isinstance(instance, ServiceA) + + def test_application_make_inherits_typing(self, tmp_path): + from fastapi_startkit.application import Application + + app = Application(base_path=tmp_path, env="testing") + app.bind("service_a", ServiceA) + + assert_type(app.make(ServiceA), ServiceA) + assert_type(app.make("service_a"), Any)