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
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

# -- General configuration
extensions = [
"sphinx_copybutton",
"sphinx.ext.duration",
"sphinx.ext.doctest",
"sphinx.ext.autodoc",
Expand Down
6 changes: 6 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
providers/collections
providers/selector
providers/object

.. toctree::
:maxdepth: 1
:caption: Testing

testing/provider-overriding

.. toctree::
:maxdepth: 1
Expand Down
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
sphinx==7.*
sphinx-rtd-theme==2.*
myst-parser
sphinx-copybutton
107 changes: 107 additions & 0 deletions docs/testing/provider-overriding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Provider overriding

DI container provides, in addition to direct dependency injection, another very important functionality:
**dependencies or providers overriding**.

Any provider registered with the container can be overridden.
This can help you replace objects with simple stubs, or with other objects.
**Override affects all providers that use the overridden provider (_see example_)**.

## Example

```python
from pydantic_settings import BaseSettings
from sqlalchemy import create_engine, Engine, text
from testcontainers.postgres import PostgresContainer
from that_depends import BaseContainer, providers, Provide, inject


class SomeSQLADao:
def __init__(self, *, sqla_engine: Engine):
self.engine = sqla_engine
self._connection = None

def __enter__(self):
self._connection = self.engine.connect()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self._connection.close()

def exec_query(self, query: str):
return self._connection.execute(text(query))


class Settings(BaseSettings):
db_url: str = 'some_production_db_url'


class DIContainer(BaseContainer):
settings = providers.Singleton(Settings)
sqla_engine = providers.Singleton(create_engine, settings.db_url)
some_sqla_dao = providers.Factory(SomeSQLADao, sqla_engine=sqla_engine)


@inject
def exec_query_example(some_sqla_dao=Provide[DIContainer.some_sqla_dao]):
with some_sqla_dao:
result = some_sqla_dao.exec_query('SELECT 234')

return next(result)


def main():
pg_container = PostgresContainer(image='postgres:alpine3.19')
pg_container.start()
db_url = pg_container.get_connection_url()

"""
We override only settings, but this override will also affect the 'sqla_engine'
and 'some_sqla_dao' providers because the 'settings' provider is used by them!
"""
local_testing_settings = Settings(db_url=db_url)
DIContainer.settings.override(local_testing_settings)

try:
result = exec_query_example()
assert result == (234,)
finally:
DIContainer.settings.reset_override()
pg_container.stop()


if __name__ == '__main__':
main()

```

The example above shows how overriding a nested provider ('_settings_')
affects another provider ('_engine_' and '_some_sqla_dao_').

## Override multiple providers

The example above looked at overriding only one settings provider,
but the container also provides the ability to override
multiple providers at once with method ```override_providers```.

The code above could remain the same except that
the single provider override could be replaced with the following code:

```python
def main():
pg_container = PostgresContainer(image='postgres:alpine3.19')
pg_container.start()
db_url = pg_container.get_connection_url()

local_testing_settings = Settings(db_url=db_url)
providers_for_overriding = {
'settings': local_testing_settings,
# more values...
}
with DIContainer.override_providers(providers_for_overriding):
try:
result = exec_query_example()
assert result == (234,)
finally:
pg_container.stop()
```