Introduce wiring inspect filter (#412)

* Introduce wiring inspect filter

* Upgrade exclusion filter

* Refactor wiring
This commit is contained in:
Roman Mogylatov 2021-02-27 09:45:49 -05:00 committed by GitHub
parent 6763ad2934
commit 73b8a4aac4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 117 additions and 11 deletions

View File

@ -9,13 +9,16 @@ follows `Semantic versioning`_
Development version Development version
------------------- -------------------
- Introduce wiring inspect filter to filter out ``flask.request`` and other local proxy objects
from the inspection.
See issue: `#408 <https://github.com/ets-labs/python-dependency-injector/issues/408>`_.
Many thanks to `@bvanfleet <https://github.com/bvanfleet>`_ for reporting the issue and
help in finding the root cause.
- Add ``boto3`` example. - Add ``boto3`` example.
- Add tests for ``.as_float()`` modifier usage with wiring. - Add tests for ``.as_float()`` modifier usage with wiring.
- Make refactoring of wiring module and tests. - Make refactoring of wiring module and tests.
See PR # `#406 <https://github.com/ets-labs/python-dependency-injector/issues/406>`_. See PR # `#406 <https://github.com/ets-labs/python-dependency-injector/issues/406>`_.
Thanks to `@withshubh <https://github.com/withshubh>`_ for the contribution: Thanks to `@withshubh <https://github.com/withshubh>`_ for the contribution:
- Refactor unnecessary ``else`` / ``elif`` in ``wiring`` module when ``if`` block has a
return statement.
- Remove unused imports in tests. - Remove unused imports in tests.
- Use literal syntax to create data structure in tests. - Use literal syntax to create data structure in tests.
- Add integration with a static analysis tool `DeepSource <https://deepsource.io/>`_. - Add integration with a static analysis tool `DeepSource <https://deepsource.io/>`_.

View File

@ -37,10 +37,21 @@ else:
try: try:
from fastapi.params import Depends as FastAPIDepends import fastapi.params
fastapi_installed = True
except ImportError: except ImportError:
fastapi_installed = False fastapi = None
try:
import starlette.requests
except ImportError:
starlette = None
try:
import werkzeug.local
except ImportError:
werkzeug = None
from . import providers from . import providers
@ -111,19 +122,20 @@ class ProvidersMap:
) -> Optional[providers.Provider]: ) -> Optional[providers.Provider]:
if isinstance(provider, providers.Delegate): if isinstance(provider, providers.Delegate):
return self._resolve_delegate(provider) return self._resolve_delegate(provider)
if isinstance(provider, ( elif isinstance(provider, (
providers.ProvidedInstance, providers.ProvidedInstance,
providers.AttributeGetter, providers.AttributeGetter,
providers.ItemGetter, providers.ItemGetter,
providers.MethodCaller, providers.MethodCaller,
)): )):
return self._resolve_provided_instance(provider) return self._resolve_provided_instance(provider)
if isinstance(provider, providers.ConfigurationOption): elif isinstance(provider, providers.ConfigurationOption):
return self._resolve_config_option(provider) return self._resolve_config_option(provider)
if isinstance(provider, providers.TypedConfigurationOption): elif isinstance(provider, providers.TypedConfigurationOption):
return self._resolve_config_option(provider.option, as_=provider.provides) return self._resolve_config_option(provider.option, as_=provider.provides)
if isinstance(provider, str): elif isinstance(provider, str):
return self._resolve_string_id(provider, modifier) return self._resolve_string_id(provider, modifier)
else:
return self._resolve_provider(provider) return self._resolve_provider(provider)
def _resolve_string_id( def _resolve_string_id(
@ -247,6 +259,28 @@ class ProvidersMap:
return providers_map return providers_map
class InspectFilter:
def is_excluded(self, instance: object) -> bool:
if self._is_werkzeug_local_proxy(instance):
return True
elif self._is_starlette_request_cls(instance):
return True
else:
return False
def _is_werkzeug_local_proxy(self, instance: object) -> bool:
return werkzeug and isinstance(instance, werkzeug.local.LocalProxy)
def _is_starlette_request_cls(self, instance: object) -> bool:
return starlette \
and isinstance(instance, type) \
and issubclass(instance, starlette.requests.Request)
inspect_filter = InspectFilter()
def wire( # noqa: C901 def wire( # noqa: C901
container: Container, container: Container,
*, *,
@ -268,6 +302,8 @@ def wire( # noqa: C901
for module in modules: for module in modules:
for name, member in inspect.getmembers(module): for name, member in inspect.getmembers(module):
if inspect_filter.is_excluded(member):
continue
if inspect.isfunction(member): if inspect.isfunction(member):
_patch_fn(module, name, member, providers_map) _patch_fn(module, name, member, providers_map)
elif inspect.isclass(member): elif inspect.isclass(member):
@ -530,7 +566,7 @@ def _is_fastapi_default_arg_injection(injection, kwargs):
def _is_fastapi_depends(param: Any) -> bool: def _is_fastapi_depends(param: Any) -> bool:
return fastapi_installed and isinstance(param, FastAPIDepends) return fastapi and isinstance(param, fastapi.params.Depends)
def _is_patched(fn): def _is_patched(fn):

View File

@ -0,0 +1,34 @@
import sys
from flask import Flask, jsonify, request, current_app, session, g
from flask import _request_ctx_stack, _app_ctx_stack
from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
# This is here for testing wiring bypasses these objects without crashing
request, current_app, session, g # noqa
_request_ctx_stack, _app_ctx_stack # noqa
class Service:
def process(self) -> str:
return 'Ok'
class Container(containers.DeclarativeContainer):
service = providers.Factory(Service)
app = Flask(__name__)
@app.route('/')
@inject
def index(service: Service = Provide[Container.service]):
result = service.process()
return jsonify({'result': result})
container = Container()
container.wire(modules=[sys.modules[__name__]])

View File

@ -0,0 +1,33 @@
import unittest
# Runtime import to avoid syntax errors in samples on Python < 3.5 and reach top-dir
import os
_TOP_DIR = os.path.abspath(
os.path.sep.join((
os.path.dirname(__file__),
'../',
)),
)
_SAMPLES_DIR = os.path.abspath(
os.path.sep.join((
os.path.dirname(__file__),
'../samples/',
)),
)
import sys
sys.path.append(_TOP_DIR)
sys.path.append(_SAMPLES_DIR)
from wiringflask import web
class WiringFlaskTest(unittest.TestCase):
def test(self):
client = web.app.test_client()
with web.app.app_context():
response = client.get('/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, b'{"result":"Ok"}\n')