1
|
|
|
""" |
2
|
|
|
Flask API (https://www.flaskapi.org/) |
3
|
|
|
""" |
4
|
|
|
|
5
|
1 |
|
from typing import Type, List, Optional, Any, Dict, Callable |
6
|
|
|
|
7
|
1 |
|
from flask import Flask, request |
8
|
|
|
|
9
|
1 |
|
from ..definitions import ConstructionWithoutContainer |
10
|
1 |
|
from ..interfaces import ExtendableContainer, WriteableContainer |
11
|
|
|
|
12
|
|
|
|
13
|
1 |
|
class _Request: |
14
|
1 |
|
pass |
15
|
|
|
|
16
|
|
|
|
17
|
|
|
# Exposing a type to be flask's request |
18
|
|
|
# for now it's an any type but later this could be better. |
19
|
1 |
|
Request: Any = _Request |
20
|
|
|
|
21
|
|
|
|
22
|
1 |
|
class FlaskIntegration: |
23
|
|
|
""" |
24
|
|
|
Wraps a flask app and a container so that dependencies are provided to |
25
|
|
|
flask routes |
26
|
|
|
""" |
27
|
|
|
|
28
|
1 |
|
flask_app: Flask |
29
|
1 |
|
_container: WriteableContainer |
30
|
1 |
|
_request_singletons: List[Type] |
31
|
1 |
|
_injection_map: Dict[Callable, Callable] |
32
|
|
|
|
33
|
1 |
|
def __init__( |
34
|
|
|
self, |
35
|
|
|
app: Flask, |
36
|
|
|
container: ExtendableContainer, |
37
|
|
|
request_singletons: Optional[List[Type]] = None, |
38
|
|
|
): |
39
|
|
|
""" |
40
|
|
|
|
41
|
|
|
:param app: The flask app to provide dependency injection for |
42
|
|
|
:param request_singletons: A list of types that should be singletons for a request |
43
|
|
|
:param container: an existing container to provide dependencies |
44
|
|
|
""" |
45
|
1 |
|
self.flask_app = app |
46
|
1 |
|
self._container = container.clone() |
47
|
1 |
|
self._container[Request] = ConstructionWithoutContainer(lambda: request) |
48
|
1 |
|
self._request_singletons = request_singletons or [] |
49
|
1 |
|
self._injection_map = {} |
50
|
|
|
|
51
|
1 |
|
def route(self, rule, **options): |
52
|
|
|
"""Equivalent to the flask @route decorator |
53
|
|
|
Injectable arguments should be set by making the default value |
54
|
|
|
lagom.injectable |
55
|
|
|
""" |
56
|
|
|
|
57
|
1 |
|
def _decorator(f): |
58
|
1 |
|
endpoint = options.pop("endpoint", None) |
59
|
1 |
|
if f not in self._injection_map: |
60
|
1 |
|
self._injection_map[f] = self._container.partial( |
61
|
|
|
f, shared=self._request_singletons |
62
|
|
|
) |
63
|
1 |
|
self.flask_app.add_url_rule( |
64
|
|
|
rule, endpoint, self._injection_map[f], **options |
65
|
|
|
) |
66
|
1 |
|
return f |
67
|
|
|
|
68
|
1 |
|
return _decorator |
69
|
|
|
|
70
|
1 |
View Code Duplication |
def magic_route(self, rule, **options): |
|
|
|
|
71
|
|
|
"""Equivalent to the flask @route decorator |
72
|
|
|
The injection container will try and bind all arguments |
73
|
|
|
""" |
74
|
|
|
|
75
|
|
|
def _decorator(f): |
76
|
|
|
endpoint = options.pop("endpoint", None) |
77
|
|
|
if f not in self._injection_map: |
78
|
|
|
self._injection_map[f] = self._container.magic_partial( |
79
|
|
|
f, shared=self._request_singletons |
80
|
|
|
) |
81
|
|
|
self.flask_app.add_url_rule( |
82
|
|
|
rule, endpoint, self._injection_map[f], **options |
83
|
|
|
) |
84
|
|
|
return f |
85
|
|
|
|
86
|
|
|
return _decorator |
87
|
|
|
|