From 31d0f49c46814020444587f8731447e611ef093b Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Sat, 12 Sep 2026 13:25:21 -0700 Subject: [PATCH] feat: add generic typing to Container.make() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit basedpyright reported reportUnknownMemberType on every app.make(...) call site because Container.make() was unannotated, degrading every resolved service to Unknown. Type make() with a TypeVar and overloads so make(SomeClass) is inferred as SomeClass while string keys (make('config')) return Any and keep type-checking. The return type is deliberately non-Optional: a missing key raises MissingContainerBindingNotFound instead of returning None, so callers need no narrowing. Application.make inherits the overloads. Typing-only change — no runtime edits to make/bind/resolve or hooks. Adds assert_type tests covering both key forms on Container and Application. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01BT53Svb7fNSFXmLfUNCkZL --- .../fastapi_startkit/container/container.py | 18 ++++++++-- fastapi_startkit/tests/core/test_container.py | 33 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) 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)