From 9492ba22982c157ed677ab41d273363b68776b75 Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sat, 12 Aug 2023 17:40:13 +0400 Subject: [PATCH] Updated dependency overrides. Signed-off-by: Pavel Kirilin --- README.md | 22 +++++++-- aiohttp_deps/initializer.py | 3 +- aiohttp_deps/view.py | 3 +- poetry.lock | 80 ++++++++++++++++----------------- tests/test_func_dependencies.py | 25 ++++++++++- tests/test_view_dependencies.py | 27 ++++++++++- 6 files changed, 112 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index f695ef2..03db3ec 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This project was initially created to show the abillities of [taskiq-dependencies](https://github.com/taskiq-python/taskiq-dependencies) project, which is used by [taskiq](https://github.com/taskiq-python/taskiq) to provide you with the best experience of sending distributed tasks. -This project adds [FastAPI](https://github.com/tiangolo/fastapi)-like dependency injection to your [AioHTTP](https://github.com/aio-libs/aiohttp) application. +This project adds [FastAPI](https://github.com/tiangolo/fastapi)-like dependency injection to your [AioHTTP](https://github.com/aio-libs/aiohttp) application and swagger documentation based on types. To start using dependency injection, just initialize the injector. @@ -362,8 +362,8 @@ async def my_handler(var: str = Depends(Path())): Sometimes for tests you don't want to calculate actual functions and you want to pass another functions instead. -To do so, you can add "dependency_overrides" key to the aplication. -It's a dict that is passed as additional context to dependency resolvers. +To do so, you can add "dependency_overrides" or "values_overrides" to the aplication's state. +These values should be dicts. Here's an example. @@ -385,5 +385,19 @@ where you create your application. And make sure that keys of that dict are actual function that are being replaced. ```python - my_app["dependency_overrides"] = {original_dep: 2} + my_app["values_overrides"] = {original_dep: 2} ``` + +But `values_overrides` only overrides values. If you want to +override functions, you have to use `dependency_overrides`. Here's an example: + +```python +def replacing_function() -> int: + return 2 + + +my_app["dependency_overrides"] = {original_dep: replacing_function} +``` + +The cool point about `dependency_overrides`, is that it recalculates graph and +you can use dependencies in function that replaces the original. diff --git a/aiohttp_deps/initializer.py b/aiohttp_deps/initializer.py index 3fbdb61..51e4cf9 100644 --- a/aiohttp_deps/initializer.py +++ b/aiohttp_deps/initializer.py @@ -44,8 +44,9 @@ async def __call__(self, request: web.Request) -> web.StreamResponse: { web.Request: request, web.Application: request.app, - **request.app.get("dependency_overrides", {}), + **request.app.get("values_overrides", {}), }, + replaced_deps=request.app.get("dependency_overrides"), ) as resolver: return await self.original_handler(**(await resolver.resolve_kwargs())) diff --git a/aiohttp_deps/view.py b/aiohttp_deps/view.py index 3d3bb8b..968e692 100644 --- a/aiohttp_deps/view.py +++ b/aiohttp_deps/view.py @@ -43,7 +43,8 @@ async def _iter(self) -> StreamResponse: { web.Request: self.request, web.Application: self.request.app, - **self.request.app.get("dependency_overrides", {}), + **self.request.app.get("values_overrides", {}), }, + replaced_deps=self.request.app.get("dependency_overrides"), ) as ctx: return await method(**(await ctx.resolve_kwargs())) # type: ignore diff --git a/poetry.lock b/poetry.lock index 27a65f6..07e5dab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -170,13 +170,13 @@ files = [ [[package]] name = "async-timeout" -version = "4.0.2" +version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, - {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] [[package]] @@ -566,19 +566,19 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p [[package]] name = "flake8" -version = "6.0.0" +version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, - {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.10.0,<2.11.0" -pyflakes = ">=3.0.0,<3.1.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" [[package]] name = "flake8-bandit" @@ -1152,13 +1152,13 @@ files = [ [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -1188,18 +1188,18 @@ flake8 = ">=5.0.0" [[package]] name = "platformdirs" -version = "3.9.1" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, - {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" @@ -1273,13 +1273,13 @@ files = [ [[package]] name = "pycodestyle" -version = "2.10.0" +version = "2.11.0" description = "Python style guide checker" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, - {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, + {file = "pycodestyle-2.11.0-py2.py3-none-any.whl", hash = "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8"}, + {file = "pycodestyle-2.11.0.tar.gz", hash = "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0"}, ] [[package]] @@ -1433,24 +1433,24 @@ toml = ["tomli (>=1.2.3)"] [[package]] name = "pyflakes" -version = "3.0.1" +version = "3.1.0" description = "passive checker of Python programs" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, - {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, ] [[package]] name = "pygments" -version = "2.15.1" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] @@ -1634,13 +1634,13 @@ docutils = ">=0.11,<1.0" [[package]] name = "rich" -version = "13.4.2" +version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, - {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, + {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, + {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, ] [package.dependencies] @@ -1716,13 +1716,13 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "taskiq-dependencies" -version = "1.3.0" +version = "1.3.1" description = "FastAPI like dependency injection implementation" optional = false python-versions = ">=3.8.1,<4.0.0" files = [ - {file = "taskiq_dependencies-1.3.0-py3-none-any.whl", hash = "sha256:e260bd8976b9190ac6d625a9c57bf9f43361afef3f9da585db4f8e2a141bda0f"}, - {file = "taskiq_dependencies-1.3.0.tar.gz", hash = "sha256:9cbad8700325db895ac9d3442c8d7d97e4e2c3335910b52d5000227847b2f9dd"}, + {file = "taskiq_dependencies-1.3.1-py3-none-any.whl", hash = "sha256:332ea97ea973cab974dc73a6c2dffd9287881244de9b1a105cd3901fc8f2a9aa"}, + {file = "taskiq_dependencies-1.3.1.tar.gz", hash = "sha256:7b85935b1881c9a63f7a89978aea86cb931daf85e89b87723a9faa0b7a4dc287"}, ] [package.dependencies] @@ -1730,13 +1730,13 @@ graphlib-backport = {version = ">=1.0.3,<2.0.0", markers = "python_version < \"3 [[package]] name = "tokenize-rt" -version = "5.1.0" +version = "5.2.0" description = "A wrapper around the stdlib `tokenize` which roundtrips." optional = false python-versions = ">=3.8" files = [ - {file = "tokenize_rt-5.1.0-py2.py3-none-any.whl", hash = "sha256:9b7bb843e77dd6ed0be5564bfaaba200083911e0497841cd3e9235a6a9794d74"}, - {file = "tokenize_rt-5.1.0.tar.gz", hash = "sha256:08f0c2daa94c4052e53c2fcaa8e32585e6ae9bdfc800974092d031401694e002"}, + {file = "tokenize_rt-5.2.0-py2.py3-none-any.whl", hash = "sha256:b79d41a65cfec71285433511b50271b05da3584a1da144a0752e9c621a285289"}, + {file = "tokenize_rt-5.2.0.tar.gz", hash = "sha256:9fe80f8a5c1edad2d3ede0f37481cc0cc1538a2f442c9c2f9e4feacd2792d054"}, ] [[package]] @@ -1774,13 +1774,13 @@ files = [ [[package]] name = "virtualenv" -version = "20.24.2" +version = "20.24.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.2-py3-none-any.whl", hash = "sha256:43a3052be36080548bdee0b42919c88072037d50d56c28bd3f853cbe92b953ff"}, - {file = "virtualenv-20.24.2.tar.gz", hash = "sha256:fd8a78f46f6b99a67b7ec5cf73f92357891a7b3a40fd97637c27f854aae3b9e0"}, + {file = "virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"}, + {file = "virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"}, ] [package.dependencies] diff --git a/tests/test_func_dependencies.py b/tests/test_func_dependencies.py index 9eff067..4de0394 100644 --- a/tests/test_func_dependencies.py +++ b/tests/test_func_dependencies.py @@ -37,6 +37,26 @@ async def handler(app: web.Application = Depends()): assert "Application" in (await resp.json())["request"] +@pytest.mark.anyio +async def test_values_override( + my_app: web.Application, + aiohttp_client: ClientGenerator, +): + def original_dep() -> int: + return 1 + + async def handler(num: int = Depends(original_dep)): + return web.json_response({"request": num}) + + my_app.router.add_get("/", handler) + my_app["values_overrides"] = {original_dep: 2} + + client = await aiohttp_client(my_app) + resp = await client.get("/") + assert resp.status == 200 + assert (await resp.json())["request"] == 2 + + @pytest.mark.anyio async def test_dependency_override( my_app: web.Application, @@ -45,11 +65,14 @@ async def test_dependency_override( def original_dep() -> int: return 1 + def custom_dep() -> int: + return 2 + async def handler(num: int = Depends(original_dep)): return web.json_response({"request": num}) my_app.router.add_get("/", handler) - my_app["dependency_overrides"] = {original_dep: 2} + my_app["dependency_overrides"] = {original_dep: custom_dep} client = await aiohttp_client(my_app) resp = await client.get("/") diff --git a/tests/test_view_dependencies.py b/tests/test_view_dependencies.py index e76e3cf..678cb15 100644 --- a/tests/test_view_dependencies.py +++ b/tests/test_view_dependencies.py @@ -55,6 +55,28 @@ async def get(self): assert resp.status == 405 +@pytest.mark.anyio +async def test_values_override( + my_app: web.Application, + aiohttp_client: ClientGenerator, +): + def original_dep() -> int: + return 1 + + class MyView(View): + async def get(self, num: int = Depends(original_dep)): + """Nothing.""" + return web.json_response({"request": num}) + + my_app.router.add_view("/", MyView) + my_app["values_overrides"] = {original_dep: 2} + + client = await aiohttp_client(my_app) + resp = await client.get("/") + assert resp.status == 200 + assert (await resp.json())["request"] == 2 + + @pytest.mark.anyio async def test_dependency_override( my_app: web.Application, @@ -63,13 +85,16 @@ async def test_dependency_override( def original_dep() -> int: return 1 + def replaced() -> int: + return 2 + class MyView(View): async def get(self, num: int = Depends(original_dep)): """Nothing.""" return web.json_response({"request": num}) my_app.router.add_view("/", MyView) - my_app["dependency_overrides"] = {original_dep: 2} + my_app["dependency_overrides"] = {original_dep: replaced} client = await aiohttp_client(my_app) resp = await client.get("/")