python-dependency-injector
python-dependency-injector copied to clipboard
Confusing behavior of Singletons with pytest scopes
I looked at a related issue but found that the solution there was not working well for me. What I understand is that singletons need to be reset.
Here is my code which has a root container with a services and controllers sub-container.
The expected behavior is like this:
containerfixture infunctionscope -> no problemcontainerfixture inmodulescope withreset_singletonsfixture in auto-use -> no problems
The strange behaviour is as follows:
containerfixture inmodulescope withreset_servicesfixture in auto-use -> 5 failing tests
5 failed
- tests/test_override.py:76 TestReset.test_can_reset
- tests/test_override.py:79 TestReset.test_can_change_result
- tests/test_override.py:99 TestOverride.test_can_override
- tests/test_override.py:102 TestOverride.test_can_change_result
- tests/test_override.py:106 TestOverride.test_can_override_within
Can you help me understand what's going on here and if it is a bug? Resetting only parts of the singletons is my preferred way of doing it as I do not want to reset all singletons in my (bigger) app but only these.
Click to toggle
import pytest
from dependency_injector import containers, providers
class MyService:
def __init__(self):
self._result = "real"
def run(self):
return self._result
class MyController:
def __init__(self, service):
self._service = service
def run(self):
return self._service.run()
class Services(containers.DeclarativeContainer):
my_service = providers.Singleton(MyService)
class Controllers(containers.DeclarativeContainer):
services: Services = providers.DependenciesContainer()
my_controller = providers.ContextLocalSingleton(
MyController, service=services.my_service
)
class MainContainer(containers.DeclarativeContainer):
services = providers.Container(Services)
controllers = providers.Container(Controllers, services=services)
@pytest.fixture(autouse=True)
def reset_singletons(container):
with container.reset_singletons():
yield
@pytest.fixture(autouse=True)
def reset_services(container):
with container.services.reset_singletons():
yield
@pytest.fixture
def container():
return MainContainer()
@pytest.fixture
def service():
...
@pytest.fixture
def controller(container, service):
return container.controllers.my_controller()
def test_can_run(controller):
assert controller.run() == "real"
class TestReset:
@pytest.fixture
def service(self, container):
with container.services.my_service.reset() as s:
instance = s()
instance._result = "reset"
yield instance
def test_can_reset(self, controller):
assert controller.run() == "reset"
def test_can_change_result(self, service, controller):
service._result = "changed"
assert controller.run() == "changed"
def test_can_reset_within(self, container):
with container.services.my_service.reset() as s:
s._result = "reset"
assert container.controllers.my_controller().run() == "reset"
class TestOverride:
@pytest.fixture
def service(self, container):
override = MyService()
override._result = "override"
container.services.my_service.reset()
with container.services.my_service.override(override):
yield override
def test_can_override(self, controller):
assert controller.run() == "override"
def test_can_change_result(self, service, controller):
service._result = "override-changed"
assert controller.run() == "override-changed"
def test_can_override_within(self, container):
service = MyService()
service._result = "override"
with container.services.my_service.override(service):
controller = container.controllers.my_controller()
assert controller.run() == "override"