Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
"python.analysis.typeCheckingMode": "standard",
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": true,
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff"
}
}
7 changes: 0 additions & 7 deletions src/uncoupled/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,9 @@ def get_concrete_instance[I](

def make_proxy_method(name: str):
def proxy_method(self, *args: Any, **kwargs: Any) -> Any:
if concrete := object.__getattribute__(self, "_concrete"):
return getattr(concrete, name)(*args, **kwargs)

interface = object.__getattribute__(self, "_interface")
resolver = object.__getattribute__(self, "_resolver")
concrete = Container._get_instance().get_concrete_instance(interface, resolver)

object.__setattr__(self, "_concrete", concrete)
return getattr(concrete, name)(*args, **kwargs)

return proxy_method
Expand All @@ -139,8 +134,6 @@ def __init__(self, interface: type[I], resolver: Resolver | None = None) -> None
self._interface = interface
self._resolver = resolver

self._concrete: I | None = None

__call__ = make_proxy_method("__call__")
__getattribute__ = make_proxy_method("__getattribute__")
__repr__ = make_proxy_method("__repr__")
Expand Down
9 changes: 7 additions & 2 deletions src/uncoupled/providers/scoped.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,13 @@ def get[T](self, interface: type[T], resolver: Resolver[T] | None = None) -> T:

def _get_scoped_instance[T](self, registered: Registered[T]) -> T:
scoped = self._registered_to_scoped[registered]
if scoped.current_instance is None or scoped.current_scope != self._get_scope():
scoped.current_scope = self._get_scope()
new_scope = self._get_scope()
if scoped.current_instance is None or scoped.current_scope != new_scope:
self._logger.debug(
f"Creating new instance of {registered.concrete.__name__} "
f"old scope was {scoped.current_scope}, new scope is {new_scope}"
)
scoped.current_scope = new_scope
scoped.current_instance = registered.concrete()
return scoped.current_instance

Expand Down
36 changes: 36 additions & 0 deletions tests/test_depends_scoped.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from collections.abc import Generator
from typing import Protocol
from uuid import uuid4
import pytest

from uncoupled.container import Container, Depends


class Interface(Protocol):
def unique_id(self) -> str: ...


class Impl(Interface):
def __init__(self) -> None:
self._unique_id = str(uuid4())

def unique_id(self) -> str:
return self._unique_id


@pytest.fixture(autouse=True)
def init_container_scoped() -> Generator:
c = Container.create(get_scope=lambda: uuid4())
c.add_scoped(Interface, Impl)
yield
Container._delete_instance()


def test_instance_recreated() -> None:
def get_instance(inst: Interface = Depends(Interface)) -> Interface:
return inst

i1 = get_instance()
i2 = get_instance()

assert i1.unique_id() != i2.unique_id()
Loading