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
from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer):
@ -104,11 +104,11 @@ Key features of the ``Dependency Injector``:
...
if __name__ == '__main__':
if __name__ == "__main__":
container = Container()
container.config.api_key.from_env('API_KEY')
container.config.timeout.from_env('TIMEOUT')
container.wire(modules=[sys.modules[__name__]])
container.config.api_key.from_env("API_KEY")
container.config.timeout.from_env("TIMEOUT")
container.wire(modules=[__name__])
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
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?
- 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?
- 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
Have a question?

View File

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

View File

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

View File

@ -98,7 +98,7 @@ you configure global resource:
configure_logging = providers.Resource(
logging.config.fileConfig,
fname='logging.ini',
fname="logging.ini",
)
Function initializer does not provide a way to specify custom resource shutdown.
@ -210,8 +210,8 @@ first argument.
.. _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
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
:language: python
:lines: 3-
:emphasize-lines: 24
:emphasize-lines: 22
Framework initializes and injects the resource into the function. With the ``Closing`` marker
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()
if __name__ == '__main__':
if __name__ == "__main__":
asyncio.run(main())
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()
if __name__ == '__main__':
if __name__ == "__main__":
asyncio.run(main())
See also:

View File

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

View File

@ -160,19 +160,19 @@ Second put next in the ``fixtures.py``:
SAMPLE_DATA = [
('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'),
('Rogue One: A Star Wars Story', 2016, 'Gareth Edwards'),
('The Jungle Book', 2016, 'Jon Favreau'),
("The Hunger Games: Mockingjay - Part 2", 2015, "Francis Lawrence"),
("Rogue One: A Star Wars Story", 2016, "Gareth Edwards"),
("The Jungle Book", 2016, "Jon Favreau"),
]
FILE = pathlib.Path(__file__)
DIR = FILE.parent
CSV_FILE = DIR / 'movies.csv'
SQLITE_FILE = DIR / 'movies.db'
CSV_FILE = DIR / "movies.csv"
SQLITE_FILE = DIR / "movies.db"
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)
for row in movies_data:
writer.writerow(row)
@ -181,20 +181,20 @@ Second put next in the ``fixtures.py``:
def create_sqlite(movies_data, path):
with sqlite3.connect(path) as db:
db.execute(
'CREATE TABLE IF NOT EXISTS movies '
'(title text, year int, director text)'
"CREATE TABLE IF NOT EXISTS movies "
"(title text, year int, director text)"
)
db.execute('DELETE FROM movies')
db.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data)
db.execute("DELETE FROM movies")
db.executemany("INSERT INTO movies VALUES (?,?,?)", movies_data)
def main():
create_csv(SAMPLE_DATA, CSV_FILE)
create_sqlite(SAMPLE_DATA, SQLITE_FILE)
print('OK')
print("OK")
if __name__ == '__main__':
if __name__ == "__main__":
main()
Now run in the terminal:
@ -266,7 +266,7 @@ Edit ``__main__.py``:
...
if __name__ == '__main__':
if __name__ == "__main__":
container = Container()
main()
@ -321,7 +321,7 @@ and put next into it:
self.director = str(director)
def __repr__(self):
return '{0}(title={1}, year={2}, director={3})'.format(
return "{0}(title={1}, year={2}, director={3})".format(
self.__class__.__name__,
repr(self.title),
repr(self.year),
@ -483,9 +483,9 @@ Edit ``__main__.py``:
...
if __name__ == '__main__':
if __name__ == "__main__":
container = Container()
container.config.from_yaml('config.yml')
container.config.from_yaml("config.yml")
main()
@ -575,13 +575,11 @@ Let's inject the ``lister`` into the ``main()`` function.
Edit ``__main__.py``:
.. code-block:: python
:emphasize-lines: 3-7,11-12,19
:emphasize-lines: 3-5,9-10,17
"""Main module."""
import sys
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
from .listers import MovieLister
from .containers import Container
@ -592,10 +590,10 @@ Edit ``__main__.py``:
...
if __name__ == '__main__':
if __name__ == "__main__":
container = Container()
container.config.from_yaml('config.yml')
container.wire(modules=[sys.modules[__name__]])
container.config.from_yaml("config.yml")
container.wire(modules=[__name__])
main()
@ -607,13 +605,11 @@ Francis Lawrence and movies released in 2016.
Edit ``__main__.py``:
.. code-block:: python
:emphasize-lines: 13-19
:emphasize-lines: 11-17
"""Main module."""
import sys
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
from .listers import MovieLister
from .containers import Container
@ -621,19 +617,19 @@ Edit ``__main__.py``:
@inject
def main(lister: MovieLister = Provide[Container.lister]) -> None:
print('Francis Lawrence movies:')
for movie in lister.movies_directed_by('Francis Lawrence'):
print('\t-', movie)
print("Francis Lawrence movies:")
for movie in lister.movies_directed_by("Francis Lawrence"):
print("\t-", movie)
print('2016 movies:')
print("2016 movies:")
for movie in lister.movies_released_in(2016):
print('\t-', movie)
print("\t-", movie)
if __name__ == '__main__':
if __name__ == "__main__":
container = Container()
container.config.from_yaml('config.yml')
container.wire(modules=[sys.modules[__name__]])
container.config.from_yaml("config.yml")
container.wire(modules=[__name__])
main()
@ -718,7 +714,7 @@ Edit ``finders.py``:
def find_all(self) -> List[Movie]:
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]
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``:
.. code-block:: python
:emphasize-lines: 25
:emphasize-lines: 23
"""Main module."""
import sys
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
from .listers import MovieLister
from .containers import Container
@ -877,19 +871,19 @@ Edit ``__main__.py``:
@inject
def main(lister: MovieLister = Provide[Container.lister]) -> None:
print('Francis Lawrence movies:')
for movie in lister.movies_directed_by('Francis Lawrence'):
print('\t-', movie)
print("Francis Lawrence movies:")
for movie in lister.movies_directed_by("Francis Lawrence"):
print("\t-", movie)
print('2016 movies:')
print("2016 movies:")
for movie in lister.movies_released_in(2016):
print('\t-', movie)
print("\t-", movie)
if __name__ == '__main__':
if __name__ == "__main__":
container = Container()
container.config.from_yaml('config.yml')
container.config.finder.type.from_env('MOVIE_FINDER_TYPE')
container.config.from_yaml("config.yml")
container.config.finder.type.from_env("MOVIE_FINDER_TYPE")
container.wire(modules=[sys.modules[__name__]])
main()
@ -963,14 +957,14 @@ and put next into it:
def container():
container = Container()
container.config.from_dict({
'finder': {
'type': 'csv',
'csv': {
'path': '/fake-movies.csv',
'delimiter': ',',
"finder": {
"type": "csv",
"csv": {
"path": "/fake-movies.csv",
"delimiter": ",",
},
'sqlite': {
'path': '/fake-movies.db',
"sqlite": {
"path": "/fake-movies.db",
},
},
})
@ -980,23 +974,23 @@ and put next into it:
def test_movies_directed_by(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'),
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"),
]
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_directed_by('Jon Favreau')
movies = lister.movies_directed_by("Jon Favreau")
assert len(movies) == 1
assert movies[0].title == 'The Jungle Book'
assert movies[0].title == "The Jungle Book"
def test_movies_released_in(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'),
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"),
]
with container.finder.override(finder_mock):
@ -1004,7 +998,7 @@ and put next into it:
movies = lister.movies_released_in(2015)
assert len(movies) == 1
assert movies[0].title == 'The 33'
assert movies[0].title == "The 33"
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
:language: python
:emphasize-lines: 16-19
:emphasize-lines: 14-17
:lines: 3-
Strings identifiers
-------------------
String identifiers
------------------
You can use wiring with string identifiers. String identifier should match provider name in the container:
.. literalinclude:: ../examples/wiring/example_string_id.py
:language: python
:emphasize-lines: 17
:emphasize-lines: 15
:lines: 3-
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
@inject
def foo(service: UserService = Provide['services.user']) -> None:
def foo(service: UserService = Provide["services.user"]) -> None:
...
You can also use injection modifiers:
@ -135,34 +135,34 @@ You can also use injection modifiers:
@inject
def foo(value: int = Provide['config.option', as_int()]) -> None:
def foo(value: int = Provide["config.option", as_int()]) -> None:
...
@inject
def foo(value: float = Provide['config.option', as_float()]) -> None:
def foo(value: float = Provide["config.option", as_float()]) -> None:
...
@inject
def foo(value: Decimal = Provide['config.option', as_(Decimal)]) -> None:
def foo(value: Decimal = Provide["config.option", as_(Decimal)]) -> None:
...
@inject
def foo(value: str = Provide['config.option', required()]) -> None:
def foo(value: str = Provide["config.option", required()]) -> None:
...
@inject
def foo(value: int = Provide['config.option', required().as_int()]) -> None:
def foo(value: int = Provide["config.option", required().as_int()]) -> None:
...
@inject
def foo(value: int = Provide['config.option', invariant('config.switch')]) -> None:
def foo(value: int = Provide["config.option", invariant("config.switch")]) -> None:
...
@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
@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
:language: python
:lines: 3-
:emphasize-lines: 16,21
:emphasize-lines: 14,19
You could also use string identifiers to avoid a dependency on a container:
.. code-block:: python
:emphasize-lines: 1,6
service: Service = Provide['service']
service: Service = Provide["service"]
class Main:
service: Service = Provide['service']
service: Service = Provide["service"]
Wiring with modules and packages
--------------------------------
To wire a container with a module you need to call ``container.wire(modules=[...])`` method. Argument
``modules`` is an iterable of the module objects.
To wire a container with the modules you need to call ``container.wire()`` method:
.. 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
@ -211,15 +249,16 @@ To wire a container with a module you need to call ``container.wire(modules=[...
container = Container()
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
from yourapp import package1, package2
container = Container()
container.wire(packages=[package1, package2])
container.wire(
packages=[
"yourapp.package1",
"yourapp.package2",
],
)
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.wire(modules=[sys.modules[__name__]])
container.wire(modules=[__name__])
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):
self.container = Container()
self.container.wire(modules=[module1, module2])
self.container.wire(modules=["yourapp.module1", "yourapp.module2"])
self.addCleanup(self.container.unwire)
.. 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
def container():
container = Container()
container.wire(modules=[module1, module2])
container.wire(modules=["yourapp.module1", "yourapp.module2"])
yield container
container.unwire()
@ -402,11 +441,11 @@ This is useful when you import modules dynamically.
from .containers import Container
if __name__ == '__main__':
if __name__ == "__main__":
container = Container()
register_loader_containers(container) # <--- installs import hook
module = importlib.import_module('package.module')
module = importlib.import_module("package.module")
module.foo()
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(
service=Service(
api_client=ApiClient(
api_key=os.getenv('API_KEY'),
timeout=os.getenv('TIMEOUT'),
api_key=os.getenv("API_KEY"),
timeout=os.getenv("TIMEOUT"),
),
),
)

View File

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

View File

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

View File

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

View File

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

View File

@ -6,4 +6,4 @@ class ConfigService:
self._config = config
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
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
from .services import UserService, AuthService, PhotoService
from .containers import Application
@ -22,10 +22,10 @@ def main(
photo_service.upload_photo(user, photo)
if __name__ == '__main__':
if __name__ == "__main__":
application = Application()
application.config.from_yaml('config.yml')
application.config.from_yaml("config.yml")
application.core.init_resources()
application.wire(modules=[sys.modules[__name__]])
application.wire(modules=[__name__])
main(*sys.argv[1:])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,6 @@
"""Main module."""
import sys
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
from .user.repositories import UserRepository
from .photo.repositories import PhotoRepository
@ -24,20 +22,20 @@ def main(
) -> None:
user1 = user_repository.get(id=1)
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_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.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.config.from_ini('config.ini')
application.wire(modules=[sys.modules[__name__]])
application.config.from_ini("config.ini")
application.wire(modules=[__name__])
main()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -46,5 +46,5 @@ class SqliteMovieFinder(MovieFinder):
def find_all(self) -> List[Movie]:
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]

View File

@ -11,14 +11,14 @@ from .containers import Container
def container():
container = Container()
container.config.from_dict({
'finder': {
'type': 'csv',
'csv': {
'path': '/fake-movies.csv',
'delimiter': ',',
"finder": {
"type": "csv",
"csv": {
"path": "/fake-movies.csv",
"delimiter": ",",
},
'sqlite': {
'path': '/fake-movies.db',
"sqlite": {
"path": "/fake-movies.db",
},
},
})
@ -28,23 +28,23 @@ def container():
def test_movies_directed_by(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'),
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"),
]
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_directed_by('Jon Favreau')
movies = lister.movies_directed_by("Jon Favreau")
assert len(movies) == 1
assert movies[0].title == 'The Jungle Book'
assert movies[0].title == "The Jungle Book"
def test_movies_released_in(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'),
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"),
]
with container.finder.override(finder_mock):
@ -52,4 +52,4 @@ def test_movies_released_in(container):
movies = lister.movies_released_in(2015)
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__':
container = Container(config={'max_workers': 4})
if __name__ == "__main__":
container = Container(config={"max_workers": 4})
container.init_resources()
logging.info('Resources are initialized')
logging.info("Resources are initialized")
thread_pool = container.thread_pool()
thread_pool.map(print, range(10))

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,7 @@
"""Flask wiring example."""
import sys
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
@ -18,13 +16,13 @@ class Container(containers.DeclarativeContainer):
@inject
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.wire(modules=[sys.modules[__name__]])
container.wire(modules=[__name__])
app = Flask(__name__)
app.add_url_rule('/', 'index', index_view)
app.add_url_rule("/", "index", index_view)
app.run()

View File

@ -1,9 +1,7 @@
"""`Resource` - Flask request scope example."""
import sys
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
@ -12,9 +10,9 @@ class Service:
def init_service() -> Service:
print('Init service')
print("Init service")
yield Service()
print('Shutdown service')
print("Shutdown service")
class Container(containers.DeclarativeContainer):
@ -25,16 +23,16 @@ class Container(containers.DeclarativeContainer):
@inject
def index_view(service: Service = Closing[Provide[Container.service]]):
assert service is current_app.container.service()
return 'Hello World!'
return "Hello World!"
container = Container()
container.wire(modules=[sys.modules[__name__]])
container.wire(modules=[__name__])
app = Flask(__name__)
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()

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 reset_last_overriding(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 init_resources(self) -> Optional[Awaitable]: ...
def shutdown_resources(self) -> Optional[Awaitable]: ...

View File

@ -1,7 +1,10 @@
"""Containers module."""
import contextlib
import json
import sys
import importlib
import inspect
try:
import asyncio
@ -248,11 +251,22 @@ class DynamicContainer(Container):
for provider in six.itervalues(self.providers):
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.
: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(
container=self,
modules=modules,
@ -261,7 +275,6 @@ class DynamicContainer(Container):
if modules:
self.wired_to_modules.extend(modules)
if 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):
raise errors.Error('{0} can contain only {1} '
'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,
) -> None:
"""Wire container providers with provided packages and modules."""
if not modules:
modules = []
modules = [*modules] if modules else []
if packages:
for package in packages:
@ -367,8 +366,7 @@ def unwire( # noqa: C901
packages: Optional[Iterable[ModuleType]] = None,
) -> None:
"""Wire provided packages and modules with previous wired providers."""
if not modules:
modules = []
modules = [*modules] if modules else []
if 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.service import Service
from wiringsamples.container import Container, SubContainer
from wiringsamples.wire_relative_string_names import wire_with_relative_string_names
class WiringTest(unittest.TestCase):
@ -314,7 +315,53 @@ class WiringTest(unittest.TestCase):
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):
self.container = Container(config={'a': {'b': {'c': 10}}})