Skip to content

Commit 73b8a4a

Browse files
authored
Introduce wiring inspect filter (#412)
* Introduce wiring inspect filter * Upgrade exclusion filter * Refactor wiring
1 parent 6763ad2 commit 73b8a4a

File tree

4 files changed

+117
-11
lines changed

4 files changed

+117
-11
lines changed

docs/main/changelog.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ follows `Semantic versioning`_
99

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

src/dependency_injector/wiring.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,21 @@ class GenericMeta(type):
3737

3838

3939
try:
40-
from fastapi.params import Depends as FastAPIDepends
41-
fastapi_installed = True
40+
import fastapi.params
4241
except ImportError:
43-
fastapi_installed = False
42+
fastapi = None
43+
44+
45+
try:
46+
import starlette.requests
47+
except ImportError:
48+
starlette = None
49+
50+
51+
try:
52+
import werkzeug.local
53+
except ImportError:
54+
werkzeug = None
4455

4556

4657
from . import providers
@@ -111,20 +122,21 @@ def resolve_provider(
111122
) -> Optional[providers.Provider]:
112123
if isinstance(provider, providers.Delegate):
113124
return self._resolve_delegate(provider)
114-
if isinstance(provider, (
125+
elif isinstance(provider, (
115126
providers.ProvidedInstance,
116127
providers.AttributeGetter,
117128
providers.ItemGetter,
118129
providers.MethodCaller,
119130
)):
120131
return self._resolve_provided_instance(provider)
121-
if isinstance(provider, providers.ConfigurationOption):
132+
elif isinstance(provider, providers.ConfigurationOption):
122133
return self._resolve_config_option(provider)
123-
if isinstance(provider, providers.TypedConfigurationOption):
134+
elif isinstance(provider, providers.TypedConfigurationOption):
124135
return self._resolve_config_option(provider.option, as_=provider.provides)
125-
if isinstance(provider, str):
136+
elif isinstance(provider, str):
126137
return self._resolve_string_id(provider, modifier)
127-
return self._resolve_provider(provider)
138+
else:
139+
return self._resolve_provider(provider)
128140

129141
def _resolve_string_id(
130142
self,
@@ -247,6 +259,28 @@ def _create_providers_map(
247259
return providers_map
248260

249261

262+
class InspectFilter:
263+
264+
def is_excluded(self, instance: object) -> bool:
265+
if self._is_werkzeug_local_proxy(instance):
266+
return True
267+
elif self._is_starlette_request_cls(instance):
268+
return True
269+
else:
270+
return False
271+
272+
def _is_werkzeug_local_proxy(self, instance: object) -> bool:
273+
return werkzeug and isinstance(instance, werkzeug.local.LocalProxy)
274+
275+
def _is_starlette_request_cls(self, instance: object) -> bool:
276+
return starlette \
277+
and isinstance(instance, type) \
278+
and issubclass(instance, starlette.requests.Request)
279+
280+
281+
inspect_filter = InspectFilter()
282+
283+
250284
def wire( # noqa: C901
251285
container: Container,
252286
*,
@@ -268,6 +302,8 @@ def wire( # noqa: C901
268302

269303
for module in modules:
270304
for name, member in inspect.getmembers(module):
305+
if inspect_filter.is_excluded(member):
306+
continue
271307
if inspect.isfunction(member):
272308
_patch_fn(module, name, member, providers_map)
273309
elif inspect.isclass(member):
@@ -530,7 +566,7 @@ def _is_fastapi_default_arg_injection(injection, kwargs):
530566

531567

532568
def _is_fastapi_depends(param: Any) -> bool:
533-
return fastapi_installed and isinstance(param, FastAPIDepends)
569+
return fastapi and isinstance(param, fastapi.params.Depends)
534570

535571

536572
def _is_patched(fn):

tests/unit/samples/wiringflask/web.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import sys
2+
3+
from flask import Flask, jsonify, request, current_app, session, g
4+
from flask import _request_ctx_stack, _app_ctx_stack
5+
from dependency_injector import containers, providers
6+
from dependency_injector.wiring import inject, Provide
7+
8+
# This is here for testing wiring bypasses these objects without crashing
9+
request, current_app, session, g # noqa
10+
_request_ctx_stack, _app_ctx_stack # noqa
11+
12+
13+
class Service:
14+
def process(self) -> str:
15+
return 'Ok'
16+
17+
18+
class Container(containers.DeclarativeContainer):
19+
20+
service = providers.Factory(Service)
21+
22+
23+
app = Flask(__name__)
24+
25+
26+
@app.route('/')
27+
@inject
28+
def index(service: Service = Provide[Container.service]):
29+
result = service.process()
30+
return jsonify({'result': result})
31+
32+
33+
container = Container()
34+
container.wire(modules=[sys.modules[__name__]])
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import unittest
2+
3+
# Runtime import to avoid syntax errors in samples on Python < 3.5 and reach top-dir
4+
import os
5+
_TOP_DIR = os.path.abspath(
6+
os.path.sep.join((
7+
os.path.dirname(__file__),
8+
'../',
9+
)),
10+
)
11+
_SAMPLES_DIR = os.path.abspath(
12+
os.path.sep.join((
13+
os.path.dirname(__file__),
14+
'../samples/',
15+
)),
16+
)
17+
import sys
18+
sys.path.append(_TOP_DIR)
19+
sys.path.append(_SAMPLES_DIR)
20+
21+
from wiringflask import web
22+
23+
24+
class WiringFlaskTest(unittest.TestCase):
25+
26+
def test(self):
27+
client = web.app.test_client()
28+
29+
with web.app.app_context():
30+
response = client.get('/')
31+
32+
self.assertEqual(response.status_code, 200)
33+
self.assertEqual(response.data, b'{"result":"Ok"}\n')

0 commit comments

Comments
 (0)