Wiring with string module names (#515)

* Update main example

* Updating wiring module

* Update wiring test case name

* Implement string imports for wiring

* Update example

* Refactor implementation

* Update front example

* Fix a typo in README

* Update wiring docs

* Update single container example

* Update multiple containers example

* Update quotes in multiple containers example

* Update quotes in single container example

* Update decoupled-packages example

* Update single and multiple containers example

* Update quotes

* Update fastapi+redis example

* Update resource docs

* Update quotes in CLI tutorial

* Update CLI application (movie lister) tutorial

* Update monitoring daemon example

* Update python version in asyncio daemon example

* Update asyncio daemon tutorial

* Update quotes in wiring docs

* Update wiring docs
This commit is contained in:
Roman Mogylatov 2021-09-30 15:03:19 -04:00 committed by GitHub
parent 258c55dd22
commit 7d160cb4a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 4119 additions and 2942 deletions

View File

@ -80,7 +80,7 @@ Key features of the ``Dependency Injector``:
.. code-block:: python .. code-block:: python
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -104,11 +104,11 @@ Key features of the ``Dependency Injector``:
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.api_key.from_env('API_KEY') container.config.api_key.from_env("API_KEY")
container.config.timeout.from_env('TIMEOUT') container.config.timeout.from_env("TIMEOUT")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() # <-- dependency is injected automatically main() # <-- dependency is injected automatically
@ -195,7 +195,7 @@ What is the dependency injection?
- dependency injection is a principle that decreases coupling and increases cohesion - dependency injection is a principle that decreases coupling and increases cohesion
Why should I do the dependency injection? Why should I do the dependency injection?
- your code becomes more flexible, testable and clear 😎 - your code becomes more flexible, testable, and clear 😎
How do I start doing the dependency injection? How do I start doing the dependency injection?
- you start writing the code following the dependency injection principle - you start writing the code following the dependency injection principle
@ -204,7 +204,7 @@ How do I start doing the dependency injection?
What price do I pay and what do I get? What price do I pay and what do I get?
- you need to explicitly specify the dependencies - you need to explicitly specify the dependencies
- it will be extra work in the beginning - it will be an extra work in the beginning
- it will payoff as the project grows - it will payoff as the project grows
Have a question? Have a question?

View File

@ -86,7 +86,7 @@ Key features of the ``Dependency Injector``:
.. code-block:: python .. code-block:: python
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -110,11 +110,11 @@ Key features of the ``Dependency Injector``:
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.api_key.from_env('API_KEY') container.config.api_key.from_env("API_KEY")
container.config.timeout.from_env('TIMEOUT') container.config.timeout.from_env("TIMEOUT")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() # <-- dependency is injected automatically main() # <-- dependency is injected automatically

View File

@ -67,8 +67,8 @@ Before:
class ApiClient: class ApiClient:
def __init__(self): def __init__(self):
self.api_key = os.getenv('API_KEY') # <-- dependency self.api_key = os.getenv("API_KEY") # <-- dependency
self.timeout = os.getenv('TIMEOUT') # <-- dependency self.timeout = os.getenv("TIMEOUT") # <-- dependency
class Service: class Service:
@ -82,7 +82,7 @@ Before:
... ...
if __name__ == '__main__': if __name__ == "__main__":
main() main()
After: After:
@ -109,12 +109,12 @@ After:
... ...
if __name__ == '__main__': if __name__ == "__main__":
main( main(
service=Service( service=Service(
api_client=ApiClient( api_client=ApiClient(
api_key=os.getenv('API_KEY'), api_key=os.getenv("API_KEY"),
timeout=os.getenv('TIMEOUT'), timeout=os.getenv("TIMEOUT"),
), ),
), ),
) )
@ -136,8 +136,8 @@ Now you need to assemble and inject the objects like this:
main( main(
service=Service( service=Service(
api_client=ApiClient( api_client=ApiClient(
api_key=os.getenv('API_KEY'), api_key=os.getenv("API_KEY"),
timeout=os.getenv('TIMEOUT'), timeout=os.getenv("TIMEOUT"),
), ),
), ),
) )
@ -162,7 +162,7 @@ the dependency.
.. code-block:: python .. code-block:: python
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -186,11 +186,11 @@ the dependency.
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.api_key.from_env('API_KEY') container.config.api_key.from_env("API_KEY")
container.config.timeout.from_env('TIMEOUT') container.config.timeout.from_env("TIMEOUT")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() # <-- dependency is injected automatically main() # <-- dependency is injected automatically

View File

@ -98,7 +98,7 @@ you configure global resource:
configure_logging = providers.Resource( configure_logging = providers.Resource(
logging.config.fileConfig, logging.config.fileConfig,
fname='logging.ini', fname="logging.ini",
) )
Function initializer does not provide a way to specify custom resource shutdown. Function initializer does not provide a way to specify custom resource shutdown.
@ -210,8 +210,8 @@ first argument.
.. _resource-provider-wiring-closing: .. _resource-provider-wiring-closing:
Resources, wiring and per-function execution scope Resources, wiring, and per-function execution scope
-------------------------------------------------- ---------------------------------------------------
You can compound ``Resource`` provider with :ref:`wiring` to implement per-function You can compound ``Resource`` provider with :ref:`wiring` to implement per-function
execution scope. For doing this you need to use additional ``Closing`` marker from execution scope. For doing this you need to use additional ``Closing`` marker from
@ -220,7 +220,7 @@ execution scope. For doing this you need to use additional ``Closing`` marker fr
.. literalinclude:: ../../examples/wiring/flask_resource_closing.py .. literalinclude:: ../../examples/wiring/flask_resource_closing.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 24 :emphasize-lines: 22
Framework initializes and injects the resource into the function. With the ``Closing`` marker Framework initializes and injects the resource into the function. With the ``Closing`` marker
framework calls resource ``shutdown()`` method when function execution is over. framework calls resource ``shutdown()`` method when function execution is over.
@ -325,7 +325,7 @@ When you use resource provider with asynchronous initializer you need to call it
connection = await container.connection.shutdown() connection = await container.connection.shutdown()
if __name__ == '__main__': if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
Container ``init_resources()`` and ``shutdown_resources()`` methods should be used asynchronously if there is Container ``init_resources()`` and ``shutdown_resources()`` methods should be used asynchronously if there is
@ -349,7 +349,7 @@ at least one asynchronous resource provider:
await container.shutdown_resources() await container.shutdown_resources()
if __name__ == '__main__': if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
See also: See also:

View File

@ -59,8 +59,8 @@ The output should look something like:
.. code-block:: bash .. code-block:: bash
Docker version 19.03.12, build 48a66213fe Docker version 20.10.5, build 55c4c88
docker-compose version 1.26.2, build eefe0d31 docker-compose version 1.29.0, build 07737305
.. note:: .. note::
@ -135,7 +135,7 @@ Put next lines into the ``Dockerfile`` file:
.. code-block:: bash .. code-block:: bash
FROM python:3.8-buster FROM python:3.9-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
@ -267,9 +267,9 @@ Put next lines into the ``__main__.py`` file:
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.from_yaml('config.yml') container.config.from_yaml("config.yml")
container.init_resources() container.init_resources()
main() main()
@ -356,7 +356,7 @@ and next into the ``dispatcher.py``:
asyncio.run(self.start()) asyncio.run(self.start())
async def start(self) -> None: async def start(self) -> None:
self._logger.info('Starting up') self._logger.info("Starting up")
for monitor in self._monitors: for monitor in self._monitors:
self._monitor_tasks.append( self._monitor_tasks.append(
@ -376,11 +376,11 @@ and next into the ``dispatcher.py``:
self._stopping = True self._stopping = True
self._logger.info('Shutting down') self._logger.info("Shutting down")
for task, monitor in zip(self._monitor_tasks, self._monitors): for task, monitor in zip(self._monitor_tasks, self._monitors):
task.cancel() task.cancel()
self._monitor_tasks.clear() self._monitor_tasks.clear()
self._logger.info('Shutdown finished successfully') self._logger.info("Shutdown finished successfully")
@staticmethod @staticmethod
async def _run_monitor(monitor: Monitor) -> None: async def _run_monitor(monitor: Monitor) -> None:
@ -396,7 +396,7 @@ and next into the ``dispatcher.py``:
except asyncio.CancelledError: except asyncio.CancelledError:
break break
except Exception: except Exception:
monitor.logger.exception('Error executing monitor check') monitor.logger.exception("Error executing monitor check")
await asyncio.sleep(_until_next(last=time_start)) await asyncio.sleep(_until_next(last=time_start))
@ -442,13 +442,11 @@ and call the ``run()`` method. We will use :ref:`wiring` feature.
Edit ``__main__.py``: Edit ``__main__.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 3-7,11-13,20 :emphasize-lines: 3-5,9-11,18
"""Main module.""" """Main module."""
import sys from dependency_injector.wiring import Provide, inject
from dependency_injector.wiring import inject, Provide
from .dispatcher import Dispatcher from .dispatcher import Dispatcher
from .containers import Container from .containers import Container
@ -459,11 +457,11 @@ Edit ``__main__.py``:
dispatcher.run() dispatcher.run()
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.from_yaml('config.yml') container.config.from_yaml("config.yml")
container.init_resources() container.init_resources()
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() main()
@ -613,10 +611,10 @@ Edit ``monitors.py``:
options: Dict[str, Any], options: Dict[str, Any],
) -> None: ) -> None:
self._client = http_client self._client = http_client
self._method = options.pop('method') self._method = options.pop("method")
self._url = options.pop('url') self._url = options.pop("url")
self._timeout = options.pop('timeout') self._timeout = options.pop("timeout")
super().__init__(check_every=options.pop('check_every')) super().__init__(check_every=options.pop("check_every"))
async def check(self) -> None: async def check(self) -> None:
time_start = time.time() time_start = time.time()
@ -631,11 +629,11 @@ Edit ``monitors.py``:
time_took = time_end - time_start time_took = time_end - time_start
self.logger.info( self.logger.info(
'Check\n' "Check\n"
' %s %s\n' " %s %s\n"
' response code: %s\n' " response code: %s\n"
' content length: %s\n' " content length: %s\n"
' request took: %s seconds', " request took: %s seconds",
self._method, self._method,
self._url, self._url,
response.status, response.status,
@ -913,22 +911,22 @@ and put next into it:
def container(): def container():
container = Container() container = Container()
container.config.from_dict({ container.config.from_dict({
'log': { "log": {
'level': 'INFO', "level": "INFO",
'formant': '[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s', "formant": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s",
}, },
'monitors': { "monitors": {
'example': { "example": {
'method': 'GET', "method": "GET",
'url': 'http://fake-example.com', "url": "http://fake-example.com",
'timeout': 1, "timeout": 1,
'check_every': 1, "check_every": 1,
}, },
'httpbin': { "httpbin": {
'method': 'GET', "method": "GET",
'url': 'https://fake-httpbin.org/get', "url": "https://fake-httpbin.org/get",
'timeout': 1, "timeout": 1,
'check_every': 1, "check_every": 1,
}, },
}, },
}) })
@ -937,7 +935,7 @@ and put next into it:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_example_monitor(container, caplog): async def test_example_monitor(container, caplog):
caplog.set_level('INFO') caplog.set_level("INFO")
http_client_mock = mock.AsyncMock() http_client_mock = mock.AsyncMock()
http_client_mock.request.return_value = RequestStub( http_client_mock.request.return_value = RequestStub(
@ -949,14 +947,14 @@ and put next into it:
example_monitor = container.example_monitor() example_monitor = container.example_monitor()
await example_monitor.check() await example_monitor.check()
assert 'http://fake-example.com' in caplog.text assert "http://fake-example.com" in caplog.text
assert 'response code: 200' in caplog.text assert "response code: 200" in caplog.text
assert 'content length: 635' in caplog.text assert "content length: 635" in caplog.text
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_dispatcher(container, caplog, event_loop): async def test_dispatcher(container, caplog, event_loop):
caplog.set_level('INFO') caplog.set_level("INFO")
example_monitor_mock = mock.AsyncMock() example_monitor_mock = mock.AsyncMock()
httpbin_monitor_mock = mock.AsyncMock() httpbin_monitor_mock = mock.AsyncMock()

View File

@ -160,19 +160,19 @@ Second put next in the ``fixtures.py``:
SAMPLE_DATA = [ SAMPLE_DATA = [
('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'), ("The Hunger Games: Mockingjay - Part 2", 2015, "Francis Lawrence"),
('Rogue One: A Star Wars Story', 2016, 'Gareth Edwards'), ("Rogue One: A Star Wars Story", 2016, "Gareth Edwards"),
('The Jungle Book', 2016, 'Jon Favreau'), ("The Jungle Book", 2016, "Jon Favreau"),
] ]
FILE = pathlib.Path(__file__) FILE = pathlib.Path(__file__)
DIR = FILE.parent DIR = FILE.parent
CSV_FILE = DIR / 'movies.csv' CSV_FILE = DIR / "movies.csv"
SQLITE_FILE = DIR / 'movies.db' SQLITE_FILE = DIR / "movies.db"
def create_csv(movies_data, path): def create_csv(movies_data, path):
with open(path, 'w') as opened_file: with open(path, "w") as opened_file:
writer = csv.writer(opened_file) writer = csv.writer(opened_file)
for row in movies_data: for row in movies_data:
writer.writerow(row) writer.writerow(row)
@ -181,20 +181,20 @@ Second put next in the ``fixtures.py``:
def create_sqlite(movies_data, path): def create_sqlite(movies_data, path):
with sqlite3.connect(path) as db: with sqlite3.connect(path) as db:
db.execute( db.execute(
'CREATE TABLE IF NOT EXISTS movies ' "CREATE TABLE IF NOT EXISTS movies "
'(title text, year int, director text)' "(title text, year int, director text)"
) )
db.execute('DELETE FROM movies') db.execute("DELETE FROM movies")
db.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data) db.executemany("INSERT INTO movies VALUES (?,?,?)", movies_data)
def main(): def main():
create_csv(SAMPLE_DATA, CSV_FILE) create_csv(SAMPLE_DATA, CSV_FILE)
create_sqlite(SAMPLE_DATA, SQLITE_FILE) create_sqlite(SAMPLE_DATA, SQLITE_FILE)
print('OK') print("OK")
if __name__ == '__main__': if __name__ == "__main__":
main() main()
Now run in the terminal: Now run in the terminal:
@ -266,7 +266,7 @@ Edit ``__main__.py``:
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
main() main()
@ -321,7 +321,7 @@ and put next into it:
self.director = str(director) self.director = str(director)
def __repr__(self): def __repr__(self):
return '{0}(title={1}, year={2}, director={3})'.format( return "{0}(title={1}, year={2}, director={3})".format(
self.__class__.__name__, self.__class__.__name__,
repr(self.title), repr(self.title),
repr(self.year), repr(self.year),
@ -483,9 +483,9 @@ Edit ``__main__.py``:
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.from_yaml('config.yml') container.config.from_yaml("config.yml")
main() main()
@ -575,13 +575,11 @@ Let's inject the ``lister`` into the ``main()`` function.
Edit ``__main__.py``: Edit ``__main__.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 3-7,11-12,19 :emphasize-lines: 3-5,9-10,17
"""Main module.""" """Main module."""
import sys from dependency_injector.wiring import Provide, inject
from dependency_injector.wiring import inject, Provide
from .listers import MovieLister from .listers import MovieLister
from .containers import Container from .containers import Container
@ -592,10 +590,10 @@ Edit ``__main__.py``:
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.from_yaml('config.yml') container.config.from_yaml("config.yml")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() main()
@ -607,13 +605,11 @@ Francis Lawrence and movies released in 2016.
Edit ``__main__.py``: Edit ``__main__.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 13-19 :emphasize-lines: 11-17
"""Main module.""" """Main module."""
import sys from dependency_injector.wiring import Provide, inject
from dependency_injector.wiring import inject, Provide
from .listers import MovieLister from .listers import MovieLister
from .containers import Container from .containers import Container
@ -621,19 +617,19 @@ Edit ``__main__.py``:
@inject @inject
def main(lister: MovieLister = Provide[Container.lister]) -> None: def main(lister: MovieLister = Provide[Container.lister]) -> None:
print('Francis Lawrence movies:') print("Francis Lawrence movies:")
for movie in lister.movies_directed_by('Francis Lawrence'): for movie in lister.movies_directed_by("Francis Lawrence"):
print('\t-', movie) print("\t-", movie)
print('2016 movies:') print("2016 movies:")
for movie in lister.movies_released_in(2016): for movie in lister.movies_released_in(2016):
print('\t-', movie) print("\t-", movie)
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.from_yaml('config.yml') container.config.from_yaml("config.yml")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() main()
@ -718,7 +714,7 @@ Edit ``finders.py``:
def find_all(self) -> List[Movie]: def find_all(self) -> List[Movie]:
with self._database as db: with self._database as db:
rows = db.execute('SELECT title, year, director FROM movies') rows = db.execute("SELECT title, year, director FROM movies")
return [self._movie_factory(*row) for row in rows] return [self._movie_factory(*row) for row in rows]
Now we need to add the sqlite finder to the container and update lister's dependency to use it. Now we need to add the sqlite finder to the container and update lister's dependency to use it.
@ -863,13 +859,11 @@ Now we need to read the value of the ``config.finder.type`` option from the envi
Edit ``__main__.py``: Edit ``__main__.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 25 :emphasize-lines: 23
"""Main module.""" """Main module."""
import sys from dependency_injector.wiring import Provide, inject
from dependency_injector.wiring import inject, Provide
from .listers import MovieLister from .listers import MovieLister
from .containers import Container from .containers import Container
@ -877,19 +871,19 @@ Edit ``__main__.py``:
@inject @inject
def main(lister: MovieLister = Provide[Container.lister]) -> None: def main(lister: MovieLister = Provide[Container.lister]) -> None:
print('Francis Lawrence movies:') print("Francis Lawrence movies:")
for movie in lister.movies_directed_by('Francis Lawrence'): for movie in lister.movies_directed_by("Francis Lawrence"):
print('\t-', movie) print("\t-", movie)
print('2016 movies:') print("2016 movies:")
for movie in lister.movies_released_in(2016): for movie in lister.movies_released_in(2016):
print('\t-', movie) print("\t-", movie)
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.from_yaml('config.yml') container.config.from_yaml("config.yml")
container.config.finder.type.from_env('MOVIE_FINDER_TYPE') container.config.finder.type.from_env("MOVIE_FINDER_TYPE")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[sys.modules[__name__]])
main() main()
@ -963,14 +957,14 @@ and put next into it:
def container(): def container():
container = Container() container = Container()
container.config.from_dict({ container.config.from_dict({
'finder': { "finder": {
'type': 'csv', "type": "csv",
'csv': { "csv": {
'path': '/fake-movies.csv', "path": "/fake-movies.csv",
'delimiter': ',', "delimiter": ",",
}, },
'sqlite': { "sqlite": {
'path': '/fake-movies.db', "path": "/fake-movies.db",
}, },
}, },
}) })
@ -980,23 +974,23 @@ and put next into it:
def test_movies_directed_by(container): def test_movies_directed_by(container):
finder_mock = mock.Mock() finder_mock = mock.Mock()
finder_mock.find_all.return_value = [ finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'), container.movie("The 33", 2015, "Patricia Riggen"),
container.movie('The Jungle Book', 2016, 'Jon Favreau'), container.movie("The Jungle Book", 2016, "Jon Favreau"),
] ]
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_directed_by('Jon Favreau') movies = lister.movies_directed_by("Jon Favreau")
assert len(movies) == 1 assert len(movies) == 1
assert movies[0].title == 'The Jungle Book' assert movies[0].title == "The Jungle Book"
def test_movies_released_in(container): def test_movies_released_in(container):
finder_mock = mock.Mock() finder_mock = mock.Mock()
finder_mock.find_all.return_value = [ finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'), container.movie("The 33", 2015, "Patricia Riggen"),
container.movie('The Jungle Book', 2016, 'Jon Favreau'), container.movie("The Jungle Book", 2016, "Jon Favreau"),
] ]
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
@ -1004,7 +998,7 @@ and put next into it:
movies = lister.movies_released_in(2015) movies = lister.movies_released_in(2015)
assert len(movies) == 1 assert len(movies) == 1
assert movies[0].title == 'The 33' assert movies[0].title == "The 33"
Run in the terminal: Run in the terminal:

View File

@ -95,17 +95,17 @@ Also you can use ``Provide`` marker to inject a container.
.. literalinclude:: ../examples/wiring/example_container.py .. literalinclude:: ../examples/wiring/example_container.py
:language: python :language: python
:emphasize-lines: 16-19 :emphasize-lines: 14-17
:lines: 3- :lines: 3-
Strings identifiers String identifiers
------------------- ------------------
You can use wiring with string identifiers. String identifier should match provider name in the container: You can use wiring with string identifiers. String identifier should match provider name in the container:
.. literalinclude:: ../examples/wiring/example_string_id.py .. literalinclude:: ../examples/wiring/example_string_id.py
:language: python :language: python
:emphasize-lines: 17 :emphasize-lines: 15
:lines: 3- :lines: 3-
With string identifiers you don't need to use a container to specify an injection. With string identifiers you don't need to use a container to specify an injection.
@ -115,7 +115,7 @@ To specify an injection from a nested container use point ``.`` as a separator:
.. code-block:: python .. code-block:: python
@inject @inject
def foo(service: UserService = Provide['services.user']) -> None: def foo(service: UserService = Provide["services.user"]) -> None:
... ...
You can also use injection modifiers: You can also use injection modifiers:
@ -135,34 +135,34 @@ You can also use injection modifiers:
@inject @inject
def foo(value: int = Provide['config.option', as_int()]) -> None: def foo(value: int = Provide["config.option", as_int()]) -> None:
... ...
@inject @inject
def foo(value: float = Provide['config.option', as_float()]) -> None: def foo(value: float = Provide["config.option", as_float()]) -> None:
... ...
@inject @inject
def foo(value: Decimal = Provide['config.option', as_(Decimal)]) -> None: def foo(value: Decimal = Provide["config.option", as_(Decimal)]) -> None:
... ...
@inject @inject
def foo(value: str = Provide['config.option', required()]) -> None: def foo(value: str = Provide["config.option", required()]) -> None:
... ...
@inject @inject
def foo(value: int = Provide['config.option', required().as_int()]) -> None: def foo(value: int = Provide["config.option", required().as_int()]) -> None:
... ...
@inject @inject
def foo(value: int = Provide['config.option', invariant('config.switch')]) -> None: def foo(value: int = Provide["config.option", invariant("config.switch")]) -> None:
... ...
@inject @inject
def foo(value: int = Provide['service', provided().foo['bar'].call()]) -> None: def foo(value: int = Provide["service", provided().foo["bar"].call()]) -> None:
... ...
@ -171,7 +171,7 @@ To inject a container use special identifier ``<container>``:
.. code-block:: python .. code-block:: python
@inject @inject
def foo(container: Container = Provide['<container>']) -> None: def foo(container: Container = Provide["<container>"]) -> None:
... ...
@ -183,25 +183,63 @@ You can use wiring to make injections into modules and class attributes.
.. literalinclude:: ../examples/wiring/example_attribute.py .. literalinclude:: ../examples/wiring/example_attribute.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 16,21 :emphasize-lines: 14,19
You could also use string identifiers to avoid a dependency on a container: You could also use string identifiers to avoid a dependency on a container:
.. code-block:: python .. code-block:: python
:emphasize-lines: 1,6 :emphasize-lines: 1,6
service: Service = Provide['service'] service: Service = Provide["service"]
class Main: class Main:
service: Service = Provide['service'] service: Service = Provide["service"]
Wiring with modules and packages Wiring with modules and packages
-------------------------------- --------------------------------
To wire a container with a module you need to call ``container.wire(modules=[...])`` method. Argument To wire a container with the modules you need to call ``container.wire()`` method:
``modules`` is an iterable of the module objects.
.. code-block:: python
container.wire(
modules=[
"yourapp.module1",
"yourapp.module2",
],
)
Method ``container.wire()`` can resolve relative imports:
.. code-block:: python
# In module "yourapp.foo":
container.wire(
modules=[
".module1", # Resolved to: "yourapp.module1"
".module2", # Resolved to: "yourapp.module2"
],
)
You can also manually specify a base package for resolving relative imports with
the ``from_package`` argument:
.. code-block:: python
# In module "yourapp.main":
container.wire(
modules=[
".module1", # Resolved to: "anotherapp.module1"
".module2", # Resolved to: "anotherapp.module2"
],
from_package="anotherapp",
)
Argument ``modules`` can also take already imported modules:
.. code-block:: python .. code-block:: python
@ -211,15 +249,16 @@ To wire a container with a module you need to call ``container.wire(modules=[...
container = Container() container = Container()
container.wire(modules=[module1, module2]) container.wire(modules=[module1, module2])
You can wire container with a package. Container walks recursively over package modules. You can wire container with a package. Container walks recursively over the package modules:
.. code-block:: python .. code-block:: python
from yourapp import package1, package2 container.wire(
packages=[
"yourapp.package1",
container = Container() "yourapp.package2",
container.wire(packages=[package1, package2]) ],
)
Arguments ``modules`` and ``packages`` can be used together. Arguments ``modules`` and ``packages`` can be used together.
@ -233,7 +272,7 @@ When wiring is done functions and methods with the markers are patched to provid
container = Container() container = Container()
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
foo() # <--- Argument "bar" is injected foo() # <--- Argument "bar" is injected
@ -267,7 +306,7 @@ You can use that in testing to re-create and re-wire a container before each tes
def setUp(self): def setUp(self):
self.container = Container() self.container = Container()
self.container.wire(modules=[module1, module2]) self.container.wire(modules=["yourapp.module1", "yourapp.module2"])
self.addCleanup(self.container.unwire) self.addCleanup(self.container.unwire)
.. code-block:: python .. code-block:: python
@ -278,7 +317,7 @@ You can use that in testing to re-create and re-wire a container before each tes
@pytest.fixture @pytest.fixture
def container(): def container():
container = Container() container = Container()
container.wire(modules=[module1, module2]) container.wire(modules=["yourapp.module1", "yourapp.module2"])
yield container yield container
container.unwire() container.unwire()
@ -402,11 +441,11 @@ This is useful when you import modules dynamically.
from .containers import Container from .containers import Container
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
register_loader_containers(container) # <--- installs import hook register_loader_containers(container) # <--- installs import hook
module = importlib.import_module('package.module') module = importlib.import_module("package.module")
module.foo() module.foo()
You can register multiple containers in the import hook. For doing this call register function You can register multiple containers in the import hook. For doing this call register function

View File

@ -18,12 +18,12 @@ def main(service: Service): # <-- dependency is injected
... ...
if __name__ == '__main__': if __name__ == "__main__":
main( main(
service=Service( service=Service(
api_client=ApiClient( api_client=ApiClient(
api_key=os.getenv('API_KEY'), api_key=os.getenv("API_KEY"),
timeout=os.getenv('TIMEOUT'), timeout=os.getenv("TIMEOUT"),
), ),
), ),
) )

View File

@ -4,8 +4,8 @@ import os
class ApiClient: class ApiClient:
def __init__(self): def __init__(self):
self.api_key = os.getenv('API_KEY') # <-- dependency self.api_key = os.getenv("API_KEY") # <-- dependency
self.timeout = os.getenv('TIMEOUT') # <-- dependency self.timeout = os.getenv("TIMEOUT") # <-- dependency
class Service: class Service:
@ -19,5 +19,5 @@ def main() -> None:
... ...
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -1,8 +1,7 @@
import sys
from unittest import mock from unittest import mock
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
from after import ApiClient, Service from after import ApiClient, Service
@ -28,11 +27,11 @@ def main(service: Service = Provide[Container.service]):
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.api_key.from_env('API_KEY') container.config.api_key.from_env("API_KEY")
container.config.timeout.from_env('TIMEOUT') container.config.timeout.from_env("TIMEOUT")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() # <-- dependency is injected automatically main() # <-- dependency is injected automatically

View File

@ -3,7 +3,7 @@
from .containers import Application from .containers import Application
if __name__ == '__main__': if __name__ == "__main__":
application = Application() application = Application()
config = application.service.config() config = application.service.config()
config.build() config.build()

View File

@ -6,17 +6,17 @@ from .services import ConfigService
class Core(containers.DeclarativeContainer): class Core(containers.DeclarativeContainer):
config = providers.Configuration('config') config = providers.Configuration("config")
class Storage(containers.DeclarativeContainer): class Storage(containers.DeclarativeContainer):
queue = providers.Singleton(lambda: 'Some storage') queue = providers.Singleton(lambda: "Some storage")
class Adapter(containers.DeclarativeContainer): class Adapter(containers.DeclarativeContainer):
core = providers.DependenciesContainer(config=providers.Configuration()) core = providers.DependenciesContainer(config=providers.Configuration())
tinydb = providers.Singleton( tinydb = providers.Singleton(
lambda db_path: f'DB Path=[{db_path}]', lambda db_path: f"DB Path=[{db_path}]",
db_path=core.config.default.db_path, db_path=core.config.default.db_path,
) )
@ -25,7 +25,7 @@ class Repository(containers.DeclarativeContainer):
adapter = providers.DependenciesContainer() adapter = providers.DependenciesContainer()
storage = providers.DependenciesContainer() storage = providers.DependenciesContainer()
site = providers.Singleton( site = providers.Singleton(
lambda adapter, queue: f'Adapter=[{adapter}], queue=[{queue}]', lambda adapter, queue: f"Adapter=[{adapter}], queue=[{queue}]",
adapter=adapter.tinydb, adapter=adapter.tinydb,
queue=storage.queue, queue=storage.queue,
) )

View File

@ -6,4 +6,4 @@ class ConfigService:
self._config = config self._config = config
def build(self): def build(self):
self._config.from_dict({'default': {'db_path': '~/test'}}) self._config.from_dict({"default": {"db_path": "~/test"}})

View File

@ -2,7 +2,7 @@
import sys import sys
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
from .services import UserService, AuthService, PhotoService from .services import UserService, AuthService, PhotoService
from .containers import Application from .containers import Application
@ -22,10 +22,10 @@ def main(
photo_service.upload_photo(user, photo) photo_service.upload_photo(user, photo)
if __name__ == '__main__': if __name__ == "__main__":
application = Application() application = Application()
application.config.from_yaml('config.yml') application.config.from_yaml("config.yml")
application.core.init_resources() application.core.init_resources()
application.wire(modules=[sys.modules[__name__]]) application.wire(modules=[__name__])
main(*sys.argv[1:]) main(*sys.argv[1:])

View File

@ -30,7 +30,7 @@ class Gateways(containers.DeclarativeContainer):
s3_client = providers.Singleton( s3_client = providers.Singleton(
boto3.client, boto3.client,
service_name='s3', service_name="s3",
aws_access_key_id=config.aws.access_key_id, aws_access_key_id=config.aws.access_key_id,
aws_secret_access_key=config.aws.secret_access_key, aws_secret_access_key=config.aws.secret_access_key,
) )

View File

@ -11,7 +11,7 @@ class BaseService:
def __init__(self) -> None: def __init__(self) -> None:
self.logger = logging.getLogger( self.logger = logging.getLogger(
f'{__name__}.{self.__class__.__name__}', f"{__name__}.{self.__class__.__name__}",
) )
@ -22,8 +22,8 @@ class UserService(BaseService):
super().__init__() super().__init__()
def get_user(self, email: str) -> Dict[str, str]: def get_user(self, email: str) -> Dict[str, str]:
self.logger.debug('User %s has been found in database', email) self.logger.debug("User %s has been found in database", email)
return {'email': email, 'password_hash': '...'} return {"email": email, "password_hash": "..."}
class AuthService(BaseService): class AuthService(BaseService):
@ -36,8 +36,8 @@ class AuthService(BaseService):
def authenticate(self, user: Dict[str, str], password: str) -> None: def authenticate(self, user: Dict[str, str], password: str) -> None:
assert password is not None assert password is not None
self.logger.debug( self.logger.debug(
'User %s has been successfully authenticated', "User %s has been successfully authenticated",
user['email'], user["email"],
) )
@ -50,7 +50,7 @@ class PhotoService(BaseService):
def upload_photo(self, user: Dict[str, str], photo_path: str) -> None: def upload_photo(self, user: Dict[str, str], photo_path: str) -> None:
self.logger.debug( self.logger.debug(
'Photo %s has been successfully uploaded by user %s', "Photo %s has been successfully uploaded by user %s",
photo_path, photo_path,
user['email'], user["email"],
) )

View File

@ -2,7 +2,7 @@
import sys import sys
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
from .services import UserService, AuthService, PhotoService from .services import UserService, AuthService, PhotoService
from .containers import Container from .containers import Container
@ -22,10 +22,10 @@ def main(
photo_service.upload_photo(user, photo) photo_service.upload_photo(user, photo)
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.init_resources() container.init_resources()
container.config.from_ini('config.ini') container.config.from_ini("config.ini")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main(*sys.argv[1:]) main(*sys.argv[1:])

View File

@ -15,7 +15,7 @@ class Container(containers.DeclarativeContainer):
logging = providers.Resource( logging = providers.Resource(
logging.config.fileConfig, logging.config.fileConfig,
fname='logging.ini', fname="logging.ini",
) )
# Gateways # Gateways
@ -27,7 +27,7 @@ class Container(containers.DeclarativeContainer):
s3_client = providers.Singleton( s3_client = providers.Singleton(
boto3.client, boto3.client,
service_name='s3', service_name="s3",
aws_access_key_id=config.aws.access_key_id, aws_access_key_id=config.aws.access_key_id,
aws_secret_access_key=config.aws.secret_access_key, aws_secret_access_key=config.aws.secret_access_key,
) )

View File

@ -11,7 +11,7 @@ class BaseService:
def __init__(self) -> None: def __init__(self) -> None:
self.logger = logging.getLogger( self.logger = logging.getLogger(
f'{__name__}.{self.__class__.__name__}', f"{__name__}.{self.__class__.__name__}",
) )
@ -22,8 +22,8 @@ class UserService(BaseService):
super().__init__() super().__init__()
def get_user(self, email: str) -> Dict[str, str]: def get_user(self, email: str) -> Dict[str, str]:
self.logger.debug('User %s has been found in database', email) self.logger.debug("User %s has been found in database", email)
return {'email': email, 'password_hash': '...'} return {"email": email, "password_hash": "..."}
class AuthService(BaseService): class AuthService(BaseService):
@ -36,8 +36,8 @@ class AuthService(BaseService):
def authenticate(self, user: Dict[str, str], password: str) -> None: def authenticate(self, user: Dict[str, str], password: str) -> None:
assert password is not None assert password is not None
self.logger.debug( self.logger.debug(
'User %s has been successfully authenticated', "User %s has been successfully authenticated",
user['email'], user["email"],
) )
@ -50,7 +50,7 @@ class PhotoService(BaseService):
def upload_photo(self, user: Dict[str, str], photo_path: str) -> None: def upload_photo(self, user: Dict[str, str], photo_path: str) -> None:
self.logger.debug( self.logger.debug(
'Photo %s has been successfully uploaded by user %s', "Photo %s has been successfully uploaded by user %s",
photo_path, photo_path,
user['email'], user["email"],
) )

View File

@ -1,4 +1,4 @@
FROM python:3.8-buster FROM python:3.9-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1

View File

@ -1,7 +1,5 @@
"""Main module.""" """Main module."""
import sys
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import inject, Provide
from .dispatcher import Dispatcher from .dispatcher import Dispatcher
@ -13,10 +11,10 @@ def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None:
dispatcher.run() dispatcher.run()
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.from_yaml('config.yml') container.config.from_yaml("config.yml")
container.init_resources() container.init_resources()
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() main()

View File

@ -21,7 +21,7 @@ class Dispatcher:
asyncio.run(self.start()) asyncio.run(self.start())
async def start(self) -> None: async def start(self) -> None:
self._logger.info('Starting up') self._logger.info("Starting up")
for monitor in self._monitors: for monitor in self._monitors:
self._monitor_tasks.append( self._monitor_tasks.append(
@ -41,11 +41,11 @@ class Dispatcher:
self._stopping = True self._stopping = True
self._logger.info('Shutting down') self._logger.info("Shutting down")
for task, monitor in zip(self._monitor_tasks, self._monitors): for task, monitor in zip(self._monitor_tasks, self._monitors):
task.cancel() task.cancel()
self._monitor_tasks.clear() self._monitor_tasks.clear()
self._logger.info('Shutdown finished successfully') self._logger.info("Shutdown finished successfully")
@staticmethod @staticmethod
async def _run_monitor(monitor: Monitor) -> None: async def _run_monitor(monitor: Monitor) -> None:
@ -61,6 +61,6 @@ class Dispatcher:
except asyncio.CancelledError: except asyncio.CancelledError:
break break
except Exception: except Exception:
monitor.logger.exception('Error executing monitor check') monitor.logger.exception("Error executing monitor check")
await asyncio.sleep(_until_next(last=time_start)) await asyncio.sleep(_until_next(last=time_start))

View File

@ -25,10 +25,10 @@ class HttpMonitor(Monitor):
options: Dict[str, Any], options: Dict[str, Any],
) -> None: ) -> None:
self._client = http_client self._client = http_client
self._method = options.pop('method') self._method = options.pop("method")
self._url = options.pop('url') self._url = options.pop("url")
self._timeout = options.pop('timeout') self._timeout = options.pop("timeout")
super().__init__(check_every=options.pop('check_every')) super().__init__(check_every=options.pop("check_every"))
async def check(self) -> None: async def check(self) -> None:
time_start = time.time() time_start = time.time()
@ -43,11 +43,11 @@ class HttpMonitor(Monitor):
time_took = time_end - time_start time_took = time_end - time_start
self.logger.info( self.logger.info(
'Check\n' "Check\n"
' %s %s\n' " %s %s\n"
' response code: %s\n' " response code: %s\n"
' content length: %s\n' " content length: %s\n"
' request took: %s seconds', " request took: %s seconds",
self._method, self._method,
self._url, self._url,
response.status, response.status,

View File

@ -19,22 +19,22 @@ class RequestStub:
def container(): def container():
container = Container() container = Container()
container.config.from_dict({ container.config.from_dict({
'log': { "log": {
'level': 'INFO', "level": "INFO",
'formant': '[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s', "formant": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s",
}, },
'monitors': { "monitors": {
'example': { "example": {
'method': 'GET', "method": "GET",
'url': 'http://fake-example.com', "url": "http://fake-example.com",
'timeout': 1, "timeout": 1,
'check_every': 1, "check_every": 1,
}, },
'httpbin': { "httpbin": {
'method': 'GET', "method": "GET",
'url': 'https://fake-httpbin.org/get', "url": "https://fake-httpbin.org/get",
'timeout': 1, "timeout": 1,
'check_every': 1, "check_every": 1,
}, },
}, },
}) })
@ -43,7 +43,7 @@ def container():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_example_monitor(container, caplog): async def test_example_monitor(container, caplog):
caplog.set_level('INFO') caplog.set_level("INFO")
http_client_mock = mock.AsyncMock() http_client_mock = mock.AsyncMock()
http_client_mock.request.return_value = RequestStub( http_client_mock.request.return_value = RequestStub(
@ -55,14 +55,14 @@ async def test_example_monitor(container, caplog):
example_monitor = container.example_monitor() example_monitor = container.example_monitor()
await example_monitor.check() await example_monitor.check()
assert 'http://fake-example.com' in caplog.text assert "http://fake-example.com" in caplog.text
assert 'response code: 200' in caplog.text assert "response code: 200" in caplog.text
assert 'content length: 635' in caplog.text assert "content length: 635" in caplog.text
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_dispatcher(container, caplog, event_loop): async def test_dispatcher(container, caplog, event_loop):
caplog.set_level('INFO') caplog.set_level("INFO")
example_monitor_mock = mock.AsyncMock() example_monitor_mock = mock.AsyncMock()
httpbin_monitor_mock = mock.AsyncMock() httpbin_monitor_mock = mock.AsyncMock()

View File

@ -1,8 +1,6 @@
"""Main module.""" """Main module."""
import sys from dependency_injector.wiring import Provide, inject
from dependency_injector.wiring import inject, Provide
from .user.repositories import UserRepository from .user.repositories import UserRepository
from .photo.repositories import PhotoRepository from .photo.repositories import PhotoRepository
@ -24,20 +22,20 @@ def main(
) -> None: ) -> None:
user1 = user_repository.get(id=1) user1 = user_repository.get(id=1)
user1_photos = photo_repository.get_photos(user1.id) user1_photos = photo_repository.get_photos(user1.id)
print(f'Retrieve user id={user1.id}, photos count={len(user1_photos)}') print(f"Retrieve user id={user1.id}, photos count={len(user1_photos)}")
user2 = user_repository.get(id=2) user2 = user_repository.get(id=2)
user2_photos = photo_repository.get_photos(user2.id) user2_photos = photo_repository.get_photos(user2.id)
print(f'Retrieve user id={user2.id}, photos count={len(user2_photos)}') print(f"Retrieve user id={user2.id}, photos count={len(user2_photos)}")
assert aggregation_service.user_repository is user_repository assert aggregation_service.user_repository is user_repository
assert aggregation_service.photo_repository is photo_repository assert aggregation_service.photo_repository is photo_repository
print('Aggregate analytics from user and photo packages') print("Aggregate analytics from user and photo packages")
if __name__ == '__main__': if __name__ == "__main__":
application = ApplicationContainer() application = ApplicationContainer()
application.config.from_ini('config.ini') application.config.from_ini("config.ini")
application.wire(modules=[sys.modules[__name__]]) application.wire(modules=[__name__])
main() main()

View File

@ -18,7 +18,7 @@ class ApplicationContainer(containers.DeclarativeContainer):
s3 = providers.Singleton( s3 = providers.Singleton(
boto3.client, boto3.client,
service_name='s3', service_name="s3",
aws_access_key_id=config.aws.access_key_id, aws_access_key_id=config.aws.access_key_id,
aws_secret_access_key=config.aws.secret_access_key, aws_secret_access_key=config.aws.secret_access_key,
) )

View File

@ -1,9 +1,7 @@
"""Application module.""" """Application module."""
import sys
from fastapi import FastAPI, Depends
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import inject, Provide
from fastapi import FastAPI, Depends
from .containers import Container from .containers import Container
from .services import Service from .services import Service
@ -12,14 +10,14 @@ from .services import Service
app = FastAPI() app = FastAPI()
@app.api_route('/') @app.api_route("/")
@inject @inject
async def index(service: Service = Depends(Provide[Container.service])): async def index(service: Service = Depends(Provide[Container.service])):
value = await service.process() value = await service.process()
return {'result': value} return {"result": value}
container = Container() container = Container()
container.config.redis_host.from_env('REDIS_HOST', 'localhost') container.config.redis_host.from_env("REDIS_HOST", "localhost")
container.config.redis_password.from_env('REDIS_PASSWORD', 'password') container.config.redis_password.from_env("REDIS_PASSWORD", "password")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])

View File

@ -6,7 +6,7 @@ from aioredis import create_redis_pool, Redis
async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]: async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]:
pool = await create_redis_pool(f'redis://{host}', password=password) pool = await create_redis_pool(f"redis://{host}", password=password)
yield pool yield pool
pool.close() pool.close()
await pool.wait_closed() await pool.wait_closed()

View File

@ -8,5 +8,5 @@ class Service:
self._redis = redis self._redis = redis
async def process(self) -> str: async def process(self) -> str:
await self._redis.set('my-key', 'value') await self._redis.set("my-key", "value")
return await self._redis.get('my-key', encoding='utf-8') return await self._redis.get("my-key", encoding="utf-8")

View File

@ -11,7 +11,7 @@ from .services import Service
@pytest.fixture @pytest.fixture
def client(event_loop): def client(event_loop):
client = AsyncClient(app=app, base_url='http://test') client = AsyncClient(app=app, base_url="http://test")
yield client yield client
event_loop.run_until_complete(client.aclose()) event_loop.run_until_complete(client.aclose())
@ -19,10 +19,10 @@ def client(event_loop):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_index(client): async def test_index(client):
service_mock = mock.AsyncMock(spec=Service) service_mock = mock.AsyncMock(spec=Service)
service_mock.process.return_value = 'Foo' service_mock.process.return_value = "Foo"
with container.service.override(service_mock): with container.service.override(service_mock):
response = await client.get('/') response = await client.get("/")
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {'result': 'Foo'} assert response.json() == {"result": "Foo"}

View File

@ -1,7 +1,7 @@
dependency-injector dependency-injector
fastapi fastapi
uvicorn uvicorn
aioredis aioredis<2 # TODO: Update example to work with aioredis >= 2.0
# For testing: # For testing:
pytest pytest

View File

@ -6,19 +6,19 @@ import pathlib
SAMPLE_DATA = [ SAMPLE_DATA = [
('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'), ("The Hunger Games: Mockingjay - Part 2", 2015, "Francis Lawrence"),
('Rogue One: A Star Wars Story', 2016, 'Gareth Edwards'), ("Rogue One: A Star Wars Story", 2016, "Gareth Edwards"),
('The Jungle Book', 2016, 'Jon Favreau'), ("The Jungle Book", 2016, "Jon Favreau"),
] ]
FILE = pathlib.Path(__file__) FILE = pathlib.Path(__file__)
DIR = FILE.parent DIR = FILE.parent
CSV_FILE = DIR / 'movies.csv' CSV_FILE = DIR / "movies.csv"
SQLITE_FILE = DIR / 'movies.db' SQLITE_FILE = DIR / "movies.db"
def create_csv(movies_data, path): def create_csv(movies_data, path):
with open(path, 'w') as opened_file: with open(path, "w") as opened_file:
writer = csv.writer(opened_file) writer = csv.writer(opened_file)
for row in movies_data: for row in movies_data:
writer.writerow(row) writer.writerow(row)
@ -27,18 +27,18 @@ def create_csv(movies_data, path):
def create_sqlite(movies_data, path): def create_sqlite(movies_data, path):
with sqlite3.connect(path) as db: with sqlite3.connect(path) as db:
db.execute( db.execute(
'CREATE TABLE IF NOT EXISTS movies ' "CREATE TABLE IF NOT EXISTS movies "
'(title text, year int, director text)' "(title text, year int, director text)"
) )
db.execute('DELETE FROM movies') db.execute("DELETE FROM movies")
db.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data) db.executemany("INSERT INTO movies VALUES (?,?,?)", movies_data)
def main(): def main():
create_csv(SAMPLE_DATA, CSV_FILE) create_csv(SAMPLE_DATA, CSV_FILE)
create_sqlite(SAMPLE_DATA, SQLITE_FILE) create_sqlite(SAMPLE_DATA, SQLITE_FILE)
print('OK') print("OK")
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -1,8 +1,6 @@
"""Main module.""" """Main module."""
import sys from dependency_injector.wiring import Provide, inject
from dependency_injector.wiring import inject, Provide
from .listers import MovieLister from .listers import MovieLister
from .containers import Container from .containers import Container
@ -10,19 +8,19 @@ from .containers import Container
@inject @inject
def main(lister: MovieLister = Provide[Container.lister]) -> None: def main(lister: MovieLister = Provide[Container.lister]) -> None:
print('Francis Lawrence movies:') print("Francis Lawrence movies:")
for movie in lister.movies_directed_by('Francis Lawrence'): for movie in lister.movies_directed_by("Francis Lawrence"):
print('\t-', movie) print("\t-", movie)
print('2016 movies:') print("2016 movies:")
for movie in lister.movies_released_in(2016): for movie in lister.movies_released_in(2016):
print('\t-', movie) print("\t-", movie)
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.config.from_yaml('config.yml') container.config.from_yaml("config.yml")
container.config.finder.type.from_env('MOVIE_FINDER_TYPE') container.config.finder.type.from_env("MOVIE_FINDER_TYPE")
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() main()

View File

@ -9,7 +9,7 @@ class Movie:
self.director = str(director) self.director = str(director)
def __repr__(self): def __repr__(self):
return '{0}(title={1}, year={2}, director={3})'.format( return "{0}(title={1}, year={2}, director={3})".format(
self.__class__.__name__, self.__class__.__name__,
repr(self.title), repr(self.title),
repr(self.year), repr(self.year),

View File

@ -46,5 +46,5 @@ class SqliteMovieFinder(MovieFinder):
def find_all(self) -> List[Movie]: def find_all(self) -> List[Movie]:
with self._database as db: with self._database as db:
rows = db.execute('SELECT title, year, director FROM movies') rows = db.execute("SELECT title, year, director FROM movies")
return [self._movie_factory(*row) for row in rows] return [self._movie_factory(*row) for row in rows]

View File

@ -11,14 +11,14 @@ from .containers import Container
def container(): def container():
container = Container() container = Container()
container.config.from_dict({ container.config.from_dict({
'finder': { "finder": {
'type': 'csv', "type": "csv",
'csv': { "csv": {
'path': '/fake-movies.csv', "path": "/fake-movies.csv",
'delimiter': ',', "delimiter": ",",
}, },
'sqlite': { "sqlite": {
'path': '/fake-movies.db', "path": "/fake-movies.db",
}, },
}, },
}) })
@ -28,23 +28,23 @@ def container():
def test_movies_directed_by(container): def test_movies_directed_by(container):
finder_mock = mock.Mock() finder_mock = mock.Mock()
finder_mock.find_all.return_value = [ finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'), container.movie("The 33", 2015, "Patricia Riggen"),
container.movie('The Jungle Book', 2016, 'Jon Favreau'), container.movie("The Jungle Book", 2016, "Jon Favreau"),
] ]
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_directed_by('Jon Favreau') movies = lister.movies_directed_by("Jon Favreau")
assert len(movies) == 1 assert len(movies) == 1
assert movies[0].title == 'The Jungle Book' assert movies[0].title == "The Jungle Book"
def test_movies_released_in(container): def test_movies_released_in(container):
finder_mock = mock.Mock() finder_mock = mock.Mock()
finder_mock.find_all.return_value = [ finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'), container.movie("The 33", 2015, "Patricia Riggen"),
container.movie('The Jungle Book', 2016, 'Jon Favreau'), container.movie("The Jungle Book", 2016, "Jon Favreau"),
] ]
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
@ -52,4 +52,4 @@ def test_movies_released_in(container):
movies = lister.movies_released_in(2015) movies = lister.movies_released_in(2015)
assert len(movies) == 1 assert len(movies) == 1
assert movies[0].title == 'The 33' assert movies[0].title == "The 33"

View File

@ -29,12 +29,12 @@ class Container(containers.DeclarativeContainer):
) )
if __name__ == '__main__': if __name__ == "__main__":
container = Container(config={'max_workers': 4}) container = Container(config={"max_workers": 4})
container.init_resources() container.init_resources()
logging.info('Resources are initialized') logging.info("Resources are initialized")
thread_pool = container.thread_pool() thread_pool = container.thread_pool()
thread_pool.map(print, range(10)) thread_pool.map(print, range(10))

View File

@ -1,9 +1,7 @@
"""Wiring example.""" """Wiring example."""
import sys
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
class Service: class Service:
@ -20,8 +18,8 @@ def main(service: Service = Provide[Container.service]) -> None:
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() main()

View File

@ -1,7 +1,5 @@
"""Wiring attribute example.""" """Wiring attribute example."""
import sys
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide from dependency_injector.wiring import Provide
@ -23,9 +21,9 @@ class Main:
service: Service = Provide[Container.service] service: Service = Provide[Container.service]
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
assert isinstance(service, Service) assert isinstance(service, Service)
assert isinstance(Main.service, Service) assert isinstance(Main.service, Service)

View File

@ -1,9 +1,7 @@
"""Wiring container injection example.""" """Wiring container injection example."""
import sys
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
class Service: class Service:
@ -21,8 +19,8 @@ def main(container: Container = Provide[Container]):
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() main()

View File

@ -1,9 +1,7 @@
"""Wiring string id example.""" """Wiring string id example."""
import sys
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
class Service: class Service:
@ -16,12 +14,12 @@ class Container(containers.DeclarativeContainer):
@inject @inject
def main(service: Service = Provide['service']) -> None: def main(service: Service = Provide["service"]) -> None:
... ...
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
main() main()

View File

@ -1,9 +1,7 @@
"""Flask wiring example.""" """Flask wiring example."""
import sys
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import Provide, inject
from flask import Flask, json from flask import Flask, json
@ -18,13 +16,13 @@ class Container(containers.DeclarativeContainer):
@inject @inject
def index_view(service: Service = Provide[Container.service]) -> str: def index_view(service: Service = Provide[Container.service]) -> str:
return json.dumps({'service_id': id(service)}) return json.dumps({"service_id": id(service)})
if __name__ == '__main__': if __name__ == "__main__":
container = Container() container = Container()
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
app = Flask(__name__) app = Flask(__name__)
app.add_url_rule('/', 'index', index_view) app.add_url_rule("/", "index", index_view)
app.run() app.run()

View File

@ -1,9 +1,7 @@
"""`Resource` - Flask request scope example.""" """`Resource` - Flask request scope example."""
import sys
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide, Closing from dependency_injector.wiring import Closing, Provide, inject
from flask import Flask, current_app from flask import Flask, current_app
@ -12,9 +10,9 @@ class Service:
def init_service() -> Service: def init_service() -> Service:
print('Init service') print("Init service")
yield Service() yield Service()
print('Shutdown service') print("Shutdown service")
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -25,16 +23,16 @@ class Container(containers.DeclarativeContainer):
@inject @inject
def index_view(service: Service = Closing[Provide[Container.service]]): def index_view(service: Service = Closing[Provide[Container.service]]):
assert service is current_app.container.service() assert service is current_app.container.service()
return 'Hello World!' return "Hello World!"
container = Container() container = Container()
container.wire(modules=[sys.modules[__name__]]) container.wire(modules=[__name__])
app = Flask(__name__) app = Flask(__name__)
app.container = container app.container = container
app.add_url_rule('/', 'index', view_func=index_view) app.add_url_rule("/", "index", view_func=index_view)
if __name__ == '__main__': if __name__ == "__main__":
app.run() app.run()

File diff suppressed because it is too large Load Diff

View File

@ -43,7 +43,7 @@ class Container:
def override_providers(self, **overriding_providers: Union[Provider, Any]) -> None: ... def override_providers(self, **overriding_providers: Union[Provider, Any]) -> None: ...
def reset_last_overriding(self) -> None: ... def reset_last_overriding(self) -> None: ...
def reset_override(self) -> None: ... def reset_override(self) -> None: ...
def wire(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None) -> None: ... def wire(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None, from_package: Optional[str] = None) -> None: ...
def unwire(self) -> None: ... def unwire(self) -> None: ...
def init_resources(self) -> Optional[Awaitable]: ... def init_resources(self) -> Optional[Awaitable]: ...
def shutdown_resources(self) -> Optional[Awaitable]: ... def shutdown_resources(self) -> Optional[Awaitable]: ...

View File

@ -1,7 +1,10 @@
"""Containers module.""" """Containers module."""
import contextlib
import json import json
import sys import sys
import importlib
import inspect
try: try:
import asyncio import asyncio
@ -248,11 +251,22 @@ class DynamicContainer(Container):
for provider in six.itervalues(self.providers): for provider in six.itervalues(self.providers):
provider.reset_override() provider.reset_override()
def wire(self, modules=None, packages=None): def wire(self, modules=None, packages=None, from_package=None):
"""Wire container providers with provided packages and modules. """Wire container providers with provided packages and modules.
:rtype: None :rtype: None
""" """
modules = [*modules] if modules else []
packages = [*packages] if packages else []
if _any_relative_string_imports_in(modules) or _any_relative_string_imports_in(packages):
if from_package is None:
with contextlib.suppress(Exception):
from_package = _resolve_calling_package_name()
modules = _resolve_string_imports(modules, from_package)
packages = _resolve_string_imports(packages, from_package)
wire( wire(
container=self, container=self,
modules=modules, modules=modules,
@ -261,7 +275,6 @@ class DynamicContainer(Container):
if modules: if modules:
self.wired_to_modules.extend(modules) self.wired_to_modules.extend(modules)
if packages: if packages:
self.wired_to_packages.extend(packages) self.wired_to_packages.extend(packages)
@ -789,3 +802,27 @@ cpdef object _check_provider_type(object container, object provider):
if not isinstance(provider, container.provider_type): if not isinstance(provider, container.provider_type):
raise errors.Error('{0} can contain only {1} ' raise errors.Error('{0} can contain only {1} '
'instances'.format(container, container.provider_type)) 'instances'.format(container, container.provider_type))
cpdef bint _any_relative_string_imports_in(object modules):
for module in modules:
if not isinstance(module, str):
continue
if module.startswith("."):
return True
else:
return False
cpdef list _resolve_string_imports(object modules, object from_package):
return [
importlib.import_module(module, from_package) if isinstance(module, str) else module
for module in modules
]
cpdef object _resolve_calling_package_name():
stack = inspect.stack()
pre_last_frame = stack[0]
module = inspect.getmodule(pre_last_frame[0])
return module.__package__

View File

@ -325,8 +325,7 @@ def wire( # noqa: C901
packages: Optional[Iterable[ModuleType]] = None, packages: Optional[Iterable[ModuleType]] = None,
) -> None: ) -> None:
"""Wire container providers with provided packages and modules.""" """Wire container providers with provided packages and modules."""
if not modules: modules = [*modules] if modules else []
modules = []
if packages: if packages:
for package in packages: for package in packages:
@ -367,8 +366,7 @@ def unwire( # noqa: C901
packages: Optional[Iterable[ModuleType]] = None, packages: Optional[Iterable[ModuleType]] = None,
) -> None: ) -> None:
"""Wire provided packages and modules with previous wired providers.""" """Wire provided packages and modules with previous wired providers."""
if not modules: modules = [*modules] if modules else []
modules = []
if packages: if packages:
for package in packages: for package in packages:

View File

@ -0,0 +1,8 @@
"""Wiring sample package."""
def wire_with_relative_string_names(container):
container.wire(
modules=[".module"],
packages=[".package"],
)

View File

@ -36,6 +36,7 @@ from asyncutils import AsyncTestCase
from wiringsamples import module, package from wiringsamples import module, package
from wiringsamples.service import Service from wiringsamples.service import Service
from wiringsamples.container import Container, SubContainer from wiringsamples.container import Container, SubContainer
from wiringsamples.wire_relative_string_names import wire_with_relative_string_names
class WiringTest(unittest.TestCase): class WiringTest(unittest.TestCase):
@ -314,7 +315,53 @@ class WiringTest(unittest.TestCase):
self.assertIsInstance(service, Service) self.assertIsInstance(service, Service)
class ModuleAsPackagingTest(unittest.TestCase): class WiringWithStringModuleAndPackageNamesTest(unittest.TestCase):
container: Container
def setUp(self) -> None:
self.container = Container()
self.addCleanup(self.container.unwire)
def test_absolute_names(self):
self.container.wire(
modules=["wiringsamples.module"],
packages=["wiringsamples.package"],
)
service = module.test_function()
self.assertIsInstance(service, Service)
from wiringsamples.package.subpackage.submodule import test_function
service = test_function()
self.assertIsInstance(service, Service)
def test_relative_names_with_explicit_package(self):
self.container.wire(
modules=[".module"],
packages=[".package"],
from_package="wiringsamples",
)
service = module.test_function()
self.assertIsInstance(service, Service)
from wiringsamples.package.subpackage.submodule import test_function
service = test_function()
self.assertIsInstance(service, Service)
def test_relative_names_with_auto_package(self):
wire_with_relative_string_names(self.container)
service = module.test_function()
self.assertIsInstance(service, Service)
from wiringsamples.package.subpackage.submodule import test_function
service = test_function()
self.assertIsInstance(service, Service)
class ModuleAsPackageTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.container = Container(config={'a': {'b': {'c': 10}}}) self.container = Container(config={'a': {'b': {'c': 10}}})