Skip to content

Commit 75e346c

Browse files
committed
Introduce wiring inspect filter
1 parent 6763ad2 commit 75e346c

File tree

4 files changed

+103
-4
lines changed

4 files changed

+103
-4
lines changed

docs/main/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ 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.

src/dependency_injector/wiring.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,15 @@ 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 werkzeug.local
47+
except ImportError:
48+
werkzeug = None
4449

4550

4651
from . import providers
@@ -247,6 +252,26 @@ def _create_providers_map(
247252
return providers_map
248253

249254

255+
class InspectFilter:
256+
257+
def is_excluded(self, instance: object) -> bool:
258+
if self._is_werkzeug_local_proxy(instance):
259+
return True
260+
elif self._is_fastapi_request(instance):
261+
return True
262+
else:
263+
return False
264+
265+
def _is_werkzeug_local_proxy(self, instance: object) -> bool:
266+
return werkzeug and isinstance(instance, werkzeug.local.LocalProxy)
267+
268+
def _is_fastapi_request(self, instance: object) -> bool:
269+
return fastapi and isinstance(instance, fastapi.Request)
270+
271+
272+
inspect_filter = InspectFilter()
273+
274+
250275
def wire( # noqa: C901
251276
container: Container,
252277
*,
@@ -268,6 +293,8 @@ def wire( # noqa: C901
268293

269294
for module in modules:
270295
for name, member in inspect.getmembers(module):
296+
if inspect_filter.is_excluded(member):
297+
continue
271298
if inspect.isfunction(member):
272299
_patch_fn(module, name, member, providers_map)
273300
elif inspect.isclass(member):
@@ -530,7 +557,7 @@ def _is_fastapi_default_arg_injection(injection, kwargs):
530557

531558

532559
def _is_fastapi_depends(param: Any) -> bool:
533-
return fastapi_installed and isinstance(param, FastAPIDepends)
560+
return fastapi and isinstance(param, fastapi.params.Depends)
534561

535562

536563
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)