Compare commits
645 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2c0aede4aa | ||
|
84a14f2ca7 | ||
|
0c58064a36 | ||
|
eb74b1e9d0 | ||
|
be7d25518d | ||
|
04b5907f21 | ||
|
e6cc12762f | ||
|
bf2ddbce32 | ||
|
9d6994391f | ||
|
dd84a1b5d6 | ||
|
31a98f7731 | ||
|
b261251b34 | ||
|
4bfe64563e | ||
|
b411807572 | ||
|
f2da51e0d4 | ||
|
2293251986 | ||
|
1b4b3d349f | ||
|
d8e49f7dd5 | ||
|
c1f14a876a | ||
|
c97a0cc515 | ||
|
0ada62acbf | ||
|
67827a36d1 | ||
|
ceed6a8222 | ||
|
6766ef3eba | ||
|
a8914e54e0 | ||
|
cdd9ce5048 | ||
|
51c7db771d | ||
|
a322584308 | ||
|
4b3476cb48 | ||
|
8460465b5e | ||
|
16f444b230 | ||
|
1ae96e3eeb | ||
|
7fcf1ac7ad | ||
|
1271d0fa79 | ||
|
b9df88eea7 | ||
|
99489afa3f | ||
|
193249f7ec | ||
|
01349c43e1 | ||
|
41ed07a210 | ||
|
8be79126ad | ||
|
dfee54932b | ||
|
a61749c68d | ||
|
14be69371b | ||
|
cfeb018ca7 | ||
|
183b2ae7ff | ||
|
561ff46658 | ||
|
49cc8ed827 | ||
|
383e95faed | ||
|
e7e64f6ae0 | ||
|
f50cc95405 | ||
|
8814db3fb3 | ||
|
8bf9ed04c8 | ||
|
dbf86e4eb4 | ||
|
9a08bfcede | ||
|
4ae79ac21f | ||
|
35bfafdfe2 | ||
|
6685c5a141 | ||
|
57123cebaa | ||
|
6e4794bab1 | ||
|
f3b3b1baa4 | ||
|
9b66d4bf16 | ||
|
7d4ebecd19 | ||
|
09efbffab1 | ||
|
8b625d81ad | ||
|
23acf01c15 | ||
|
0d6fdb5b78 | ||
|
2330122de6 | ||
|
29ae3e1337 | ||
|
50643e0dfb | ||
|
3893e1df81 | ||
|
0fd35baee6 | ||
|
3df95847d5 | ||
|
6d9d34c0f6 | ||
|
de50666a13 | ||
|
ccbd5bbb80 | ||
|
00326e9a22 | ||
|
46646b1acf | ||
|
9f38db6ef3 | ||
|
9f4e2839d2 | ||
|
41e18dfa90 | ||
|
f9db578c59 | ||
|
d82d9fb822 | ||
|
3ba4704bc1 | ||
|
aa56b70dc8 | ||
|
7f586246b4 | ||
|
87741edb53 | ||
|
be7abb3ec7 | ||
|
15400dea7d | ||
|
704e36a642 | ||
|
83d71acb70 | ||
|
c61fc16b8d | ||
|
cab75cb9c7 | ||
|
494c457643 | ||
|
abf2a2577c | ||
|
3777a947ea | ||
|
c92129dcb0 | ||
|
37486900cd | ||
|
9071583981 | ||
|
595daebd3a | ||
|
13a7ef609b | ||
|
7a88a8ee8d | ||
|
938091b6ea | ||
|
4bda5105c2 | ||
|
46034cbeb1 | ||
|
39ac098ca2 | ||
|
f54604fc14 | ||
|
2c998b8448 | ||
|
5697f1d5d8 | ||
|
086d82f13d | ||
|
3375436eb3 | ||
|
fec2b08210 | ||
|
8a44027f3d | ||
|
f56453f59f | ||
|
1b9e079524 | ||
|
b1a3a69428 | ||
|
a8b54423dc | ||
|
3e56fef461 | ||
|
5d1e5ee485 | ||
|
f7c6cb2647 | ||
|
a5166bf591 | ||
|
98d5867743 | ||
|
68da747ce0 | ||
|
cc2304e46e | ||
|
4bfdf89142 | ||
|
659d242503 | ||
|
6b13b6dbaf | ||
|
d3320f5f06 | ||
|
31c1f7c2d6 | ||
|
d0c8f328b3 | ||
|
3b76a0d091 | ||
|
a79ea1790c | ||
|
781d3b9c4c | ||
|
6f491a6cae | ||
|
88a2b96102 | ||
|
f0d9eda566 | ||
|
55f81bd754 | ||
|
a9cd0de886 | ||
|
aaff333f01 | ||
|
3858cef657 | ||
|
8cf86826eb | ||
|
6f859e4aa2 | ||
|
0668295543 | ||
|
142b91921a | ||
|
753e863d02 | ||
|
14b5ddae4f | ||
|
bf356ec565 | ||
|
9bc11a7828 | ||
|
a0bb7c4ede | ||
|
450407bf7a | ||
|
4666a15092 | ||
|
daca85d555 | ||
|
20bf3c0a01 | ||
|
4188f721d6 | ||
|
cfed30cf07 | ||
|
13cae77d57 | ||
|
8b0745d43e | ||
|
93c8cbc83b | ||
|
77b5cdebd3 | ||
|
f0c55cda22 | ||
|
f00fa16bd0 | ||
|
c2877777af | ||
|
8fe00bcff0 | ||
|
c26b260c73 | ||
|
ad0d430229 | ||
|
0235d68265 | ||
|
86df7f91f6 | ||
|
38ca1cdeed | ||
|
8dc3dd2f09 | ||
|
a38ca647c3 | ||
|
d4933baec1 | ||
|
742e73af1a | ||
|
cfadd8c3fa | ||
|
cc17052acc | ||
|
7238482402 | ||
|
541131e338 | ||
|
72d0e2610d | ||
|
0b3bcf334e | ||
|
99d858e2fb | ||
|
fe01ad41d9 | ||
|
6030950596 | ||
|
34902db86e | ||
|
b16b190ff7 | ||
|
b97862cb9f | ||
|
94aca21fb8 | ||
|
4cc4ca9188 | ||
|
284dee6e58 | ||
|
73a43e6191 | ||
|
08ea99759d | ||
|
f82a6b5445 | ||
|
1e198a3ebd | ||
|
4f977c7cf0 | ||
|
8ade2b7839 | ||
|
0b1e214135 | ||
|
98f036e14c | ||
|
023d766267 | ||
|
196d86f4b3 | ||
|
6b4c7e50b5 | ||
|
3c52756d3f | ||
|
274d1fe53b | ||
|
8bea62eeee | ||
|
b64c9b7a05 | ||
|
31bed0651f | ||
|
e670377bb3 | ||
|
a9173496b4 | ||
|
02b9793189 | ||
|
4a52595a9d | ||
|
c92a941fe5 | ||
|
7e794c41dd | ||
|
93dad6bbd0 | ||
|
320d837bea | ||
|
d827f93816 | ||
|
b3732281a1 | ||
|
7d160cb4a5 | ||
|
258c55dd22 | ||
|
0b5987bf84 | ||
|
cf039a0c2b | ||
|
980914c2f7 | ||
|
5c7bdf4fc6 | ||
|
4733aad44e | ||
|
d8aa70c70b | ||
|
8377f2a82d | ||
|
cc4235257c | ||
|
ff5b81fecb | ||
|
cef6d35cfd | ||
|
902913ccff | ||
|
14d8ed909b | ||
|
6af818102b | ||
|
e0825041b0 | ||
|
b4df3dd2c9 | ||
|
cf2861c4b4 | ||
|
49e2cc75c2 | ||
|
eda67e42d0 | ||
|
ea9aa2370e | ||
|
36bfd2ed58 | ||
|
83c2af0e7e | ||
|
1163ac59d4 | ||
|
4286013ca0 | ||
|
48df949cd5 | ||
|
9637d97d48 | ||
|
c4639e555e | ||
|
7b19fa0964 | ||
|
cde7dee4b3 | ||
|
5acde87a6e | ||
|
7bdcc33eda | ||
|
b4ddf61939 | ||
|
f376628dfa | ||
|
384117db9c | ||
|
06f9855140 | ||
|
547b7fd844 | ||
|
04117938d2 | ||
|
c23a48c28e | ||
|
98a4b06a12 | ||
|
c19969a6ed | ||
|
aa251a44ba | ||
|
f44924f285 | ||
|
a126df4764 | ||
|
3d1bb5d7b3 | ||
|
54de3a9d2c | ||
|
6ffb98af67 | ||
|
ef049daae5 | ||
|
9abf34cb88 | ||
|
31beb54979 | ||
|
dbbf5fdcf1 | ||
|
bbd623c719 | ||
|
585c717650 | ||
|
e06dd782cd | ||
|
c2a1351612 | ||
|
64574dff4d | ||
|
39fb88a0e9 | ||
|
eacb190907 | ||
|
fcba4cc989 | ||
|
b4a23670c1 | ||
|
ce157eeab9 | ||
|
0f3e170711 | ||
|
fab4e3e5be | ||
|
42deda7eb5 | ||
|
a34bd456e8 | ||
|
9cb8e60280 | ||
|
155f598699 | ||
|
57b4913b9b | ||
|
664a6ca5d9 | ||
|
9ed13a4023 | ||
|
7b70f46601 | ||
|
d04596be73 | ||
|
37dd617260 | ||
|
1aef599606 | ||
|
fca9fd498c | ||
|
88455d10ff | ||
|
41e698f633 | ||
|
f961ff536a | ||
|
8cad8c6b65 | ||
|
9ea8709ed9 | ||
|
6c1b7cc677 | ||
|
ee89476db0 | ||
|
e42d7dc05e | ||
|
bbbed8972a | ||
|
b3bcf60ced | ||
|
ed0b93bdbe | ||
|
d4ebb1b786 | ||
|
6b57ce9f15 | ||
|
dbad7949b0 | ||
|
22629544a4 | ||
|
c14ff96773 | ||
|
2cab6c687a | ||
|
f1a3ad0b82 | ||
|
3f026887bf | ||
|
1304e596d6 | ||
|
2bf3601695 | ||
|
e0b0a1e968 | ||
|
e6a0973be3 | ||
|
346451819e | ||
|
26571e805a | ||
|
25c966f7af | ||
|
cc05b42200 | ||
|
da13341453 | ||
|
c787ac2f63 | ||
|
48392beff2 | ||
|
73b8a4aac4 | ||
|
6763ad2934 | ||
|
4ac798014a | ||
|
9788a1888f | ||
|
3cf14c139f | ||
|
95b0356edc | ||
|
43eb15ed65 | ||
|
5f6777db19 | ||
|
d3720bd6dd | ||
|
0149338bb6 | ||
|
a4a84bea54 | ||
|
d9d811a4d4 | ||
|
0026f48cb6 | ||
|
e5017347c7 | ||
|
a85d89e6f2 | ||
|
6a73b9d3fd | ||
|
a71154e05f | ||
|
2dc78a6875 | ||
|
990fd3a554 | ||
|
c0d1e48f7b | ||
|
1d588cf9f6 | ||
|
8806405f0f | ||
|
13aa5fa53d | ||
|
64a7a18f79 | ||
|
c7ba58c0af | ||
|
18051522d7 | ||
|
763d42d532 | ||
|
f9a2ffaad6 | ||
|
6c45eb4eee | ||
|
70bebf9075 | ||
|
24cfd13acb | ||
|
27d0e07718 | ||
|
6e59b4ab6f | ||
|
6402c5b6f1 | ||
|
de1181bdf7 | ||
|
b2ea773c71 | ||
|
12d53c799d | ||
|
8cc2c1188b | ||
|
6c06548019 | ||
|
c28a4dc047 | ||
|
64d37efa37 | ||
|
93fa37728b | ||
|
ed0a413b67 | ||
|
368f50f0ba | ||
|
d9e946ff56 | ||
|
02dea7bce5 | ||
|
22bc447d14 | ||
|
8b770772a1 | ||
|
0d8f2ff44e | ||
|
8eea9c4e45 | ||
|
2c5bb45bf1 | ||
|
2127e3cef9 | ||
|
7fdd25e46f | ||
|
839a319831 | ||
|
e65212e231 | ||
|
2621e505cd | ||
|
50d05d43c6 | ||
|
21c0c82144 | ||
|
1d884b5101 | ||
|
3ba65da1ad | ||
|
cb7c13f1ba | ||
|
351bdd282e | ||
|
9677701626 | ||
|
b376836150 | ||
|
99e404650f | ||
|
b68d4d8d08 | ||
|
dd6b0caffd | ||
|
980f9fc2bc | ||
|
e29040d2ee | ||
|
7ef3c63ca9 | ||
|
5f34c7ce3f | ||
|
ff3ae95482 | ||
|
1c433ed0ad | ||
|
81da4e0451 | ||
|
5b18d609f1 | ||
|
47aa8c11fe | ||
|
a9fd206aae | ||
|
719b61cf22 | ||
|
5e5531765d | ||
|
6b24cb84a5 | ||
|
674a6b0f9e | ||
|
ce6d3df72c | ||
|
398d502981 | ||
|
19a2f551ae | ||
|
2fe0e00cef | ||
|
78f623c05b | ||
|
e80c56f9be | ||
|
b25356d2fa | ||
|
c964253204 | ||
|
c4892af31e | ||
|
c9ab7d540d | ||
|
288284aa9c | ||
|
b3bd8e888b | ||
|
35f280ac8a | ||
|
2c1eb9f95f | ||
|
d45d98e300 | ||
|
a1f779a9f3 | ||
|
892330f43c | ||
|
cba5aefd65 | ||
|
6cc1a0c61f | ||
|
f48fd159f0 | ||
|
15fa6c301e | ||
|
1fabbf314b | ||
|
fbe51b95e8 | ||
|
e9a16d1f17 | ||
|
3ca6dd9af1 | ||
|
0c1a08174f | ||
|
cd949c6a0b | ||
|
4942f9c160 | ||
|
b7afbe2cdc | ||
|
fba00894ea | ||
|
39cb963351 | ||
|
f188811d87 | ||
|
a0ba7fd16c | ||
|
478ca18ae3 | ||
|
1f17bc6e08 | ||
|
ebeb258e96 | ||
|
eb587933f4 | ||
|
9225f9dcd6 | ||
|
aca67663b6 | ||
|
6224131a76 | ||
|
9136fdcbb5 | ||
|
725e3fa322 | ||
|
ba0fb38ad0 | ||
|
874b13fdea | ||
|
92938b018d | ||
|
78479c65e6 | ||
|
f5b2862354 | ||
|
9efc8ed488 | ||
|
9bdce2d376 | ||
|
63dade0615 | ||
|
7b33733bdf | ||
|
717f7a0497 | ||
|
dfedead4f0 | ||
|
4cc39fc6eb | ||
|
2d49308c16 | ||
|
500855895b | ||
|
582c232790 | ||
|
d533ac110a | ||
|
c16f974517 | ||
|
4b0272ab30 | ||
|
349c252b50 | ||
|
c865e7c90d | ||
|
e338309c10 | ||
|
9f7dbe89f6 | ||
|
0cc63cd075 | ||
|
a635e46b59 | ||
|
21cc6ffc1b | ||
|
2cfc61aa37 | ||
|
50c5c81fd7 | ||
|
0ab0239d47 | ||
|
4991c5d4b0 | ||
|
2ced67d52b | ||
|
7ada2dc938 | ||
|
d74e8248a1 | ||
|
3b69ed91c6 | ||
|
21fb81dffb | ||
|
d9ff0a01fd | ||
|
b873137614 | ||
|
07d6261e3f | ||
|
907a4f1887 | ||
|
3e207a4f21 | ||
|
a996c142ac | ||
|
1c87a9973d | ||
|
1cbd3a0215 | ||
|
21053b3fb1 | ||
|
1628cfaf28 | ||
|
de6c3cda78 | ||
|
d9f914dfeb | ||
|
b425ac955c | ||
|
dd57c68ce5 | ||
|
46422a6845 | ||
|
6b8239ebb4 | ||
|
cd4807b2f9 | ||
|
cebeb79b93 | ||
|
86c4a22b5f | ||
|
8dd8446d39 | ||
|
6e77a95909 | ||
|
ce1bf704f2 | ||
|
9cd201a493 | ||
|
dfb7bb5a93 | ||
|
48a2bcc039 | ||
|
feed916f46 | ||
|
9f6d2bb522 | ||
|
aad84476d0 | ||
|
89aeca76c0 | ||
|
47278030ce | ||
|
468451d4c0 | ||
|
10becb316f | ||
|
1c00243c09 | ||
|
d51a7565e4 | ||
|
ca1404694a | ||
|
873b0907ec | ||
|
730baf87d8 | ||
|
c1e53e5edd | ||
|
0b7cf3254e | ||
|
b49f158583 | ||
|
ec49d56751 | ||
|
c4dd923f37 | ||
|
de092023b8 | ||
|
afa39b148e | ||
|
5c4c84b34e | ||
|
4ff1a6eb25 | ||
|
9f314fd7e9 | ||
|
57faa5f93f | ||
|
cd2c5697c3 | ||
|
fbaf35244c | ||
|
034e4814da | ||
|
c8178cecda | ||
|
8092421727 | ||
|
ee965f9782 | ||
|
d581a28b6e | ||
|
49921dcf9d | ||
|
950d5a5e6e | ||
|
d37ae8e7db | ||
|
262c035bc1 | ||
|
a2b09c57dc | ||
|
092be74145 | ||
|
ca671abea6 | ||
|
ae3024588c | ||
|
b0b8820ac1 | ||
|
9bcf875ef0 | ||
|
bece33fc21 | ||
|
59f25241cc | ||
|
af742b980a | ||
|
cdce5247c8 | ||
|
97c33442e0 | ||
|
26a89d664b | ||
|
ccf6ccad54 | ||
|
985be71a04 | ||
|
bbaed9c061 | ||
|
fb0d99c1c9 | ||
|
4120afd1c3 | ||
|
3ff5c8ce82 | ||
|
02acaba034 | ||
|
6b93c2e412 | ||
|
3c71ba4b2a | ||
|
4579e35d33 | ||
|
f3ec74a31f | ||
|
18ef566aae | ||
|
e847c357a6 | ||
|
25549d7cfe | ||
|
fb2d927cae | ||
|
c1cf1bfa1c | ||
|
b0d3f08aa6 | ||
|
e4e77d0cdb | ||
|
63977a73b2 | ||
|
c5f799a1ec | ||
|
4c46d34b20 | ||
|
f65f8b1af9 | ||
|
84b0029494 | ||
|
d65b31865b | ||
|
707446a70f | ||
|
b18385a867 | ||
|
21f0658ef3 | ||
|
30bc505b79 | ||
|
c3a04cc340 | ||
|
eadea64e0f | ||
|
e615b9c48c | ||
|
e3ccb0e764 | ||
|
018102b4e2 | ||
|
d3ec1ef532 | ||
|
fea8c33cd4 | ||
|
f7499354ec | ||
|
cdc5e84e7f | ||
|
bf82f594b4 | ||
|
b7e7d8a171 | ||
|
e4ed0bab6f | ||
|
c0e5eac016 | ||
|
5c09be46d2 | ||
|
871bd617d4 | ||
|
776cb4eebf | ||
|
2565a1eab0 | ||
|
467e7ec9aa | ||
|
c7d8645044 | ||
|
9d5ccfea85 | ||
|
532e1e3033 | ||
|
b0501ca92a | ||
|
3e4abb78e7 | ||
|
3cd590fc70 | ||
|
a66256a629 | ||
|
279910410e | ||
|
84888224a9 | ||
|
8381caec1e | ||
|
dfd0575e16 | ||
|
27cebeb2c7 | ||
|
df3300588f | ||
|
0938ccd46d | ||
|
dd6de523cf | ||
|
969849e4da | ||
|
3b4fae8dca | ||
|
25d626e92f | ||
|
ec0f9636bf | ||
|
c6e7551e87 | ||
|
cabeb6da55 | ||
|
b01ee7709c | ||
|
90af46da98 | ||
|
a3c7296879 | ||
|
47c79b2772 | ||
|
b54bcb7b31 | ||
|
5c1486e1a3 | ||
|
97731db180 | ||
|
081fccea93 | ||
|
c81194682b | ||
|
3a9e64af8f | ||
|
c90f6c1b97 | ||
|
4ddac663d9 | ||
|
fa81cadf29 | ||
|
ca54db6d05 | ||
|
ff4d24706e | ||
|
452a13c9f7 | ||
|
ca9a2a5692 | ||
|
81c67f8b27 | ||
|
0ffa1e8392 | ||
|
4e5083693d | ||
|
44e0a03148 | ||
|
f1867b6bf4 | ||
|
11ac677d42 | ||
|
619fa1f25c | ||
|
56c9023b2d | ||
|
88b3482ced | ||
|
09d1c4ce9f | ||
|
819023e7aa | ||
|
39f3f3a623 | ||
|
a6838dcb74 | ||
|
f97c486811 | ||
|
846deaa4dc | ||
|
07d4f7e74f |
|
@ -1,7 +0,0 @@
|
|||
[run]
|
||||
source = src/dependency_injector
|
||||
omit = tests/unit
|
||||
plugins = Cython.Coverage
|
||||
|
||||
[html]
|
||||
directory=reports/unittests/
|
29
.cursor/rules/coding-guide.mdc
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
description: Code in Python and Cython
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
- Follow PEP 8 rules
|
||||
- When you write imports, split system, 3rd-party, and local imports with a new line
|
||||
- Have two empty lines between the import block and the rest of the code
|
||||
- Have an empty line (\n) at the end of every file
|
||||
- If a file is supposed to be run, always add ``if __name__ == 'main'``
|
||||
- Always follow a consistent pattern of using double or single quotes
|
||||
- When there is a class without a docblock, leave one blank line before its members, e.g.:
|
||||
```python
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Factory(Service)
|
||||
```
|
||||
|
||||
- Avoid shortcuts in names unless absolutely necessary, exceptions:
|
||||
```
|
||||
arg
|
||||
args
|
||||
kwarg
|
||||
kwargs
|
||||
obj
|
||||
cls
|
||||
```
|
||||
|
||||
- Avoid inline comments unless absolutely necessary
|
7
.cursor/rules/makefile-commands.mdc
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
description: Build and run tests
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
- Use Makefile commands to build, test, lint and other similar operations when they are available.
|
||||
- Activate virtualenv before running any commands by ``. venv/bin/actvate``
|
8
.cursor/rules/run-examples.mdc
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
description: Run examples
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
- When you run an example from the ``examples/`` folder, switch to the example folder and run it from there.
|
||||
- If there are instructions on running the examples or its tests in readme, follow them
|
||||
- Activate virtualenv before running any commands by ``. venv/bin/actvate``
|
12
.deepsource.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
version = 1
|
||||
|
||||
test_patterns = ["tests/**/test_*.py"]
|
||||
|
||||
exclude_patterns = ["docs/**"]
|
||||
|
||||
[[analyzers]]
|
||||
name = "python"
|
||||
enabled = true
|
||||
|
||||
[analyzers.meta]
|
||||
runtime_version = "3.x.x"
|
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{py,pyi,pxd,pyx}]
|
||||
ij_visual_guides = 80,88
|
1
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
github: rmk135
|
131
.github/workflows/publishing.yml
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
name: Publishing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
|
||||
tests:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: 3.13
|
||||
|
||||
linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: ${{ matrix.toxenv }}
|
||||
|
||||
build-sdist:
|
||||
name: Build source tarball
|
||||
needs: [tests, linters]
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
- run: |
|
||||
python -m pip install --upgrade build
|
||||
python -m build --sdist
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cibw-sdist
|
||||
path: ./dist/*
|
||||
|
||||
build-wheels:
|
||||
name: Build wheels
|
||||
needs: [tests, linters]
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2022, macos-14]
|
||||
env:
|
||||
CIBW_ENABLE: pypy
|
||||
CIBW_ENVIRONMENT: >-
|
||||
PIP_CONFIG_SETTINGS="build_ext=-j4"
|
||||
DEPENDENCY_INJECTOR_LIMITED_API="1"
|
||||
CFLAGS="-g0"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v3.0.0
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
test-publish:
|
||||
name: Upload release to TestPyPI
|
||||
needs: [build-sdist, build-wheels]
|
||||
runs-on: ubuntu-latest
|
||||
environment: test-pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: cibw-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
|
||||
publish:
|
||||
name: Upload release to PyPI
|
||||
needs: [build-sdist, build-wheels, test-publish]
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: cibw-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
||||
publish-docs:
|
||||
name: Publish docs
|
||||
needs: [publish]
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
- run: pip install awscli
|
||||
- run: pip install -r requirements-doc.txt
|
||||
- run: pip install -e .
|
||||
- run: (cd docs && make clean html)
|
||||
- run: |
|
||||
aws s3 sync docs/_build/html s3://python-dependency-injector-docs --delete
|
||||
aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} --path "/*" > /dev/null
|
||||
echo "Cache invalidation triggered"
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
|
67
.github/workflows/tests-and-linters.yml
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
name: Tests and linters
|
||||
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
|
||||
test-on-different-versions:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
DEPENDENCY_INJECTOR_LIMITED_API: 1
|
||||
TOXENV: ${{ matrix.python-version }}
|
||||
|
||||
test-different-pydantic-versions:
|
||||
name: Run tests with different pydantic versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- run: pip install tox
|
||||
- run: tox -e pydantic-v1,pydantic-v2
|
||||
|
||||
test-coverage:
|
||||
name: Run tests with coverage
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DEPENDENCY_INJECTOR_DEBUG_MODE: 1
|
||||
PIP_VERBOSE: 1
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.12
|
||||
- run: pip install tox
|
||||
- run: tox -vv
|
||||
env:
|
||||
TOXENV: coveralls
|
||||
|
||||
linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: ${{ matrix.toxenv }}
|
21
.gitignore
vendored
|
@ -15,6 +15,7 @@ lib64/
|
|||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheelhouse/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
@ -36,6 +37,7 @@ reports/
|
|||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
@ -54,7 +56,7 @@ target/
|
|||
.idea/
|
||||
|
||||
# Virtualenv
|
||||
venv/
|
||||
venv*/
|
||||
|
||||
# SQLite
|
||||
*.db
|
||||
|
@ -62,10 +64,13 @@ venv/
|
|||
# Vim Rope
|
||||
.ropeproject/
|
||||
|
||||
# C extensions
|
||||
src/dependency_injector/*.h
|
||||
src/dependency_injector/*.so
|
||||
src/dependency_injector/containers/*.h
|
||||
src/dependency_injector/containers/*.so
|
||||
src/dependency_injector/providers/*.h
|
||||
src/dependency_injector/providers/*.so
|
||||
# Cython artifacts
|
||||
src/**/*.c
|
||||
src/**/*.h
|
||||
src/**/*.so
|
||||
src/**/*.html
|
||||
|
||||
# Workspace for samples
|
||||
.workspace/
|
||||
|
||||
.vscode/
|
||||
|
|
49
.pylintrc
|
@ -1,49 +0,0 @@
|
|||
[MASTER]
|
||||
|
||||
# Add <file or directory> to the black list. It should be a base name, not a
|
||||
# path. You may set this option multiple times.
|
||||
ignore=utils,tests
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Disable the message(s) with the given id(s).
|
||||
# disable-msg=
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=5
|
||||
|
||||
[TYPECHECK]
|
||||
ignore-mixin-members=yes
|
||||
# ignored-classes=
|
||||
zope=no
|
||||
# generated-members=providedBy,implementedBy,rawDataReceived
|
||||
|
||||
[DESIGN]
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=10
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=20
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=10
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branchs=10
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=60
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=10
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=30
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=0
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=30
|
101
.travis.yml
|
@ -1,101 +0,0 @@
|
|||
os: linux
|
||||
dist: xenial
|
||||
language: python
|
||||
jobs:
|
||||
include:
|
||||
- python: 3.8
|
||||
env: TOXENV=coveralls DEPENDENCY_INJECTOR_DEBUG_MODE=1
|
||||
install:
|
||||
- pip install tox
|
||||
- pip install cython
|
||||
- make cythonize
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=pylint
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=flake8
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=pydocstyle
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=mypy
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 2.7
|
||||
env: TOXENV=py27
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.4
|
||||
env: TOXENV=py34
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.5
|
||||
env: TOXENV=py35
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=py36
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.7
|
||||
env: TOXENV=py37
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.8
|
||||
env: TOXENV=py38
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: pypy
|
||||
env: TOXENV=pypy
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: pypy3
|
||||
env: TOXENV=pypy3
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.8
|
||||
if: tag IS present
|
||||
env: TWINE_USERNAME=__token__
|
||||
install: pip install pip --upgrade
|
||||
script: python setup.py sdist
|
||||
after_success:
|
||||
- python3 -m pip install twine
|
||||
- python3 -m twine upload dist/*
|
||||
- services: docker
|
||||
if: tag IS present
|
||||
env: TWINE_USERNAME=__token__
|
||||
install: python3 -m pip install cibuildwheel==1.5.1
|
||||
script: python3 -m cibuildwheel --output-dir wheelhouse
|
||||
after_success:
|
||||
- python3 -m pip install twine
|
||||
- python3 -m twine upload wheelhouse/*.whl
|
||||
- os: osx
|
||||
if: tag IS present
|
||||
language: shell
|
||||
env: TWINE_USERNAME=__token__
|
||||
install: python3 -m pip install cibuildwheel==1.5.1
|
||||
script: python3 -m cibuildwheel --output-dir wheelhouse
|
||||
after_success:
|
||||
- python3 -m pip install twine
|
||||
- python3 -m twine upload wheelhouse/*.whl
|
||||
- os: windows
|
||||
if: tag IS present
|
||||
language: shell
|
||||
env: TWINE_USERNAME=__token__
|
||||
before_install:
|
||||
- choco install python --version 3.8.0
|
||||
- export PATH="/c/Python38:/c/Python38/Scripts:$PATH"
|
||||
install: python -m pip install cibuildwheel==1.5.1
|
||||
script: python -m cibuildwheel --output-dir wheelhouse
|
||||
after_success:
|
||||
- python -m pip install twine
|
||||
- python -m twine upload wheelhouse/*.whl
|
||||
notifications:
|
||||
slack:
|
||||
rooms:
|
||||
secure: CdWDgKnfYW7vvvoH3nS3yg3TcNZiYLRUyEp6ukQ4rQiiuR4+ltuvyGyFJWgP8r7VVJ9yHkB0jebCKWLUMsAEt1my33B6eMDEVefovpkdh2eJjGswmm80brt0EJULpgwPOtB1U47Mwca8L5jDW4KSv9RypUFRgn8eHDoWw6LKf5g=
|
|
@ -13,3 +13,12 @@ Dependency Injector Contributors
|
|||
+ Bruno P. Kinoshita (kinow)
|
||||
+ RobinsonMa (RobinsonMa)
|
||||
+ Rüdiger Busche (JarnoRFB)
|
||||
+ Dmitry Rassoshenko (rda-dev)
|
||||
+ Fotis Koutoupas (kootoopas)
|
||||
+ Shubhendra Singh Chauhan (withshubh)
|
||||
+ sonthonaxrk (sonthonaxrk)
|
||||
+ Ngo Thanh Loi (Leonn) (loingo95)
|
||||
+ Thiago Hiromi (thiromi)
|
||||
+ Felipe Rubio (krouw)
|
||||
+ Anton Petrov (anton-petrov)
|
||||
+ ZipFile (ZipFile)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2017, ETS Labs
|
||||
Copyright (c) 2024, Roman Mogylatov
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
recursive-include src/dependency_injector *.py* *.c
|
||||
recursive-include src/dependency_injector *.py* *.c py.typed
|
||||
recursive-include tests *.py
|
||||
include README.rst
|
||||
include CONTRIBUTORS.rst
|
||||
include LICENSE.rst
|
||||
include requirements.txt
|
||||
include setup.py
|
||||
include tox.ini
|
||||
include py.typed
|
||||
|
|
41
Makefile
|
@ -1,14 +1,6 @@
|
|||
VERSION := $(shell python setup.py --version)
|
||||
|
||||
CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx')
|
||||
|
||||
CYTHON_DIRECTIVES = -Xlanguage_level=2
|
||||
|
||||
ifdef DEPENDENCY_INJECTOR_DEBUG_MODE
|
||||
CYTHON_DIRECTIVES += -Xprofile=True
|
||||
CYTHON_DIRECTIVES += -Xlinetrace=True
|
||||
endif
|
||||
|
||||
export COVERAGE_RCFILE := pyproject.toml
|
||||
|
||||
clean:
|
||||
# Clean sources
|
||||
|
@ -25,39 +17,28 @@ clean:
|
|||
find examples -name '*.py[co]' -delete
|
||||
find examples -name '__pycache__' -delete
|
||||
|
||||
cythonize:
|
||||
# Compile Cython to C
|
||||
cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC)
|
||||
build: clean
|
||||
# Compile C extensions
|
||||
python setup.py build_ext --inplace
|
||||
# Move all Cython html reports
|
||||
mkdir -p reports/cython/
|
||||
find src -name '*.html' -exec mv {} reports/cython/ \;
|
||||
|
||||
build: clean cythonize
|
||||
# Compile C extensions
|
||||
python setup.py build_ext --inplace
|
||||
|
||||
docs-live:
|
||||
sphinx-autobuild docs docs/_build/html
|
||||
|
||||
install: uninstall clean cythonize
|
||||
install: uninstall clean build
|
||||
pip install -ve .
|
||||
|
||||
uninstall:
|
||||
- pip uninstall -y -q dependency-injector 2> /dev/null
|
||||
|
||||
test-py2: build
|
||||
test:
|
||||
# Unit tests with coverage report
|
||||
coverage erase
|
||||
coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*_py2_py3.py
|
||||
coverage report --rcfile=./.coveragerc
|
||||
coverage html --rcfile=./.coveragerc
|
||||
|
||||
test-py3: build
|
||||
# Unit tests with coverage report
|
||||
coverage erase
|
||||
coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*py3*.py
|
||||
coverage report --rcfile=./.coveragerc
|
||||
coverage html --rcfile=./.coveragerc
|
||||
coverage run -m pytest
|
||||
coverage report
|
||||
coverage html
|
||||
|
||||
check:
|
||||
flake8 src/dependency_injector/
|
||||
|
@ -68,9 +49,9 @@ check:
|
|||
|
||||
mypy tests/typing
|
||||
|
||||
test-publish: cythonize
|
||||
test-publish: build
|
||||
# Create distributions
|
||||
python setup.py sdist
|
||||
python -m build --sdist
|
||||
# Upload distributions to PyPI
|
||||
twine upload --repository testpypi dist/dependency-injector-$(VERSION)*
|
||||
|
||||
|
|
157
README.rst
|
@ -35,14 +35,10 @@
|
|||
:target: https://pypi.org/project/dependency-injector/
|
||||
:alt: Wheel
|
||||
|
||||
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
|
||||
:target: https://travis-ci.org/ets-labs/python-dependency-injector
|
||||
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
|
||||
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||
:alt: Build Status
|
||||
|
||||
.. image:: http://readthedocs.org/projects/python-dependency-injector/badge/?version=latest
|
||||
:target: http://python-dependency-injector.ets-labs.org/
|
||||
:alt: Docs Status
|
||||
|
||||
|
||||
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
||||
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
|
||||
:alt: Coverage Status
|
||||
|
@ -52,29 +48,39 @@ What is ``Dependency Injector``?
|
|||
|
||||
``Dependency Injector`` is a dependency injection framework for Python.
|
||||
|
||||
It helps implementing the dependency injection principle.
|
||||
It helps implement the dependency injection principle.
|
||||
|
||||
Key features of the ``Dependency Injector``:
|
||||
|
||||
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||
``List``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your
|
||||
objects. See `Providers <http://python-dependency-injector.ets-labs.org/providers/index.html>`_.
|
||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
|
||||
that help assemble your objects.
|
||||
See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_.
|
||||
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
|
||||
and configuring dev / stage environment to replace API clients with stubs etc. See
|
||||
`Provider overriding <http://python-dependency-injector.ets-labs.org/providers/overriding.html>`_.
|
||||
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables
|
||||
and dictionaries.
|
||||
See `Configuration provider <http://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
|
||||
and configuring dev/stage environment to replace API clients with stubs etc. See
|
||||
`Provider overriding <https://python-dependency-injector.ets-labs.org/providers/overriding.html>`_.
|
||||
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
|
||||
environment variables, and dictionaries.
|
||||
See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
|
||||
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
|
||||
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
|
||||
See `Resource provider <https://python-dependency-injector.ets-labs.org/providers/resource.html>`_.
|
||||
- **Containers**. Provides declarative and dynamic containers.
|
||||
See `Containers <http://python-dependency-injector.ets-labs.org/containers/index.html>`_.
|
||||
- **Performance**. Fast. Written in ``Cython``.
|
||||
See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_.
|
||||
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
|
||||
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc.
|
||||
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
|
||||
- **Asynchronous**. Supports asynchronous injections.
|
||||
See `Asynchronous injections <https://python-dependency-injector.ets-labs.org/providers/async.html>`_.
|
||||
- **Typing**. Provides typing stubs, ``mypy``-friendly.
|
||||
See `Typing and mypy <http://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
|
||||
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
|
||||
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
|
||||
- **Performance**. Fast. Written in ``Cython``.
|
||||
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -84,7 +90,7 @@ Key features of the ``Dependency Injector``:
|
|||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
timeout=config.timeout,
|
||||
)
|
||||
|
||||
service = providers.Factory(
|
||||
|
@ -93,25 +99,40 @@ Key features of the ``Dependency Injector``:
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
container.config.api_key.from_env("API_KEY", required=True)
|
||||
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
service = container.service()
|
||||
main() # <-- dependency is injected automatically
|
||||
|
||||
With the ``Dependency Injector`` you keep **application structure in one place**.
|
||||
This place is called **the container**. You use the container to manage all the components of the
|
||||
application. All the component dependencies are defined explicitly. This provides the control on
|
||||
the application structure. It is **easy to understand and change** it.
|
||||
with container.api_client.override(mock.Mock()):
|
||||
main() # <-- overridden dependency is injected automatically
|
||||
|
||||
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-map.svg
|
||||
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically.
|
||||
|
||||
When you do testing, you call the ``container.api_client.override()`` method to replace the real API
|
||||
client with a mock. When you call ``main()``, the mock is injected.
|
||||
|
||||
You can override any provider with another provider.
|
||||
|
||||
It also helps you in a re-configuring project for different environments: replace an API client
|
||||
with a stub on the dev or stage.
|
||||
|
||||
With the ``Dependency Injector``, object assembling is consolidated in a container. Dependency injections are defined explicitly.
|
||||
This makes it easier to understand and change how an application works.
|
||||
|
||||
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
|
||||
:target: https://github.com/ets-labs/python-dependency-injector
|
||||
|
||||
*The container is like a map of your application. You always know what depends on what.*
|
||||
|
||||
Visit the docs to know more about the
|
||||
`Dependency injection and inversion of control in Python <http://python-dependency-injector.ets-labs.org/introduction/di_in_python.html>`_.
|
||||
`Dependency injection and inversion of control in Python <https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html>`_.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
@ -123,83 +144,67 @@ The package is available on the `PyPi`_::
|
|||
Documentation
|
||||
-------------
|
||||
|
||||
The documentation is available on the `Read The Docs <http://python-dependency-injector.ets-labs.org/>`_
|
||||
The documentation is available `here <https://python-dependency-injector.ets-labs.org/>`_.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Choose one of the following:
|
||||
|
||||
- `Application example (single container) <http://python-dependency-injector.ets-labs.org/examples/application-single-container.html>`_
|
||||
- `Application example (multiple containers) <http://python-dependency-injector.ets-labs.org/examples/application-multiple-containers.html>`_
|
||||
- `Decoupled packages example (multiple containers) <http://python-dependency-injector.ets-labs.org/examples/decoupled-packages.html>`_
|
||||
- `Application example (single container) <https://python-dependency-injector.ets-labs.org/examples/application-single-container.html>`_
|
||||
- `Application example (multiple containers) <https://python-dependency-injector.ets-labs.org/examples/application-multiple-containers.html>`_
|
||||
- `Decoupled packages example (multiple containers) <https://python-dependency-injector.ets-labs.org/examples/decoupled-packages.html>`_
|
||||
- `Boto3 example <https://python-dependency-injector.ets-labs.org/examples/boto3.html>`_
|
||||
- `Django example <https://python-dependency-injector.ets-labs.org/examples/django.html>`_
|
||||
- `Flask example <https://python-dependency-injector.ets-labs.org/examples/flask.html>`_
|
||||
- `Aiohttp example <https://python-dependency-injector.ets-labs.org/examples/aiohttp.html>`_
|
||||
- `Sanic example <https://python-dependency-injector.ets-labs.org/examples/sanic.html>`_
|
||||
- `FastAPI example <https://python-dependency-injector.ets-labs.org/examples/fastapi.html>`_
|
||||
- `FastAPI + Redis example <https://python-dependency-injector.ets-labs.org/examples/fastapi-redis.html>`_
|
||||
- `FastAPI + SQLAlchemy example <https://python-dependency-injector.ets-labs.org/examples/fastapi-sqlalchemy.html>`_
|
||||
|
||||
Tutorials
|
||||
---------
|
||||
|
||||
Choose one of the following:
|
||||
|
||||
- `Flask web application tutorial <http://python-dependency-injector.ets-labs.org/tutorials/flask.html>`_
|
||||
- `Aiohttp REST API tutorial <http://python-dependency-injector.ets-labs.org/tutorials/aiohttp.html>`_
|
||||
- `Asyncio monitoring daemon tutorial <http://python-dependency-injector.ets-labs.org/tutorials/asyncio-daemon.html>`_
|
||||
- `CLI application tutorial <http://python-dependency-injector.ets-labs.org/tutorials/cli.html>`_
|
||||
- `Flask web application tutorial <https://python-dependency-injector.ets-labs.org/tutorials/flask.html>`_
|
||||
- `Aiohttp REST API tutorial <https://python-dependency-injector.ets-labs.org/tutorials/aiohttp.html>`_
|
||||
- `Asyncio monitoring daemon tutorial <https://python-dependency-injector.ets-labs.org/tutorials/asyncio-daemon.html>`_
|
||||
- `CLI application tutorial <https://python-dependency-injector.ets-labs.org/tutorials/cli.html>`_
|
||||
|
||||
Concept
|
||||
-------
|
||||
|
||||
``Dependency Injector`` stands on two principles:
|
||||
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
|
||||
|
||||
- Explicit is better than implicit (PEP20).
|
||||
- Do no magic to your code.
|
||||
.. code-block:: bash
|
||||
|
||||
How is it different from the other frameworks?
|
||||
Explicit is better than implicit
|
||||
|
||||
- **No autowiring.** The framework does NOT do any autowiring / autoresolving of the dependencies. You need to specify everything explicitly. Because *"Explicit is better than implicit" (PEP20)*.
|
||||
- **Does not pollute your code.** Your application does NOT know and does NOT depend on the framework. No ``@inject`` decorators, annotations, patching or any other magic tricks.
|
||||
You need to specify how to assemble and where to inject the dependencies explicitly.
|
||||
|
||||
``Dependency Injector`` makes a simple contract with you:
|
||||
|
||||
- You tell the framework how to assemble your objects
|
||||
- The framework does it for you
|
||||
|
||||
The power of the ``Dependency Injector`` is in its simplicity and straightforwardness. It is a simple tool for the powerful concept.
|
||||
The power of the framework is in its simplicity.
|
||||
``Dependency Injector`` is a simple tool for the powerful concept.
|
||||
|
||||
Frequently asked questions
|
||||
--------------------------
|
||||
|
||||
What is the dependency injection?
|
||||
What is dependency injection?
|
||||
- dependency injection is a principle that decreases coupling and increases cohesion
|
||||
|
||||
Why should I do the dependency injection?
|
||||
- your code becomes more flexible, testable and clear
|
||||
- you have no problems when you need to understand how it works or change it 😎
|
||||
- your code becomes more flexible, testable, and clear 😎
|
||||
|
||||
How do I start doing the dependency injection?
|
||||
How do I start applying the dependency injection?
|
||||
- you start writing the code following the dependency injection principle
|
||||
- you register all of your application components and their dependencies in the container
|
||||
- when you need a component, you get it from the container
|
||||
|
||||
Why do I need a framework for this?
|
||||
- you need the framework for this to not create it by your own
|
||||
- this framework gives you the container and the providers
|
||||
- the container is like a dictionary with the batteries 🔋
|
||||
- the providers manage the lifetime of your components, you will need factories, singletons, smart config object etc
|
||||
- when you need a component, you specify where to inject it or get it from the container
|
||||
|
||||
What price do I pay and what do I get?
|
||||
- you need to explicitly specify the dependencies in the container
|
||||
- you need to explicitly specify the dependencies
|
||||
- it will be extra work in the beginning
|
||||
- it will payoff when project grows or in two weeks 😊 (when you forget what project was about)
|
||||
|
||||
What features does the framework have?
|
||||
- building objects graph
|
||||
- smart configuration object
|
||||
- providers: factory, singleton, thread locals registers, etc
|
||||
- positional and keyword context injections
|
||||
- overriding of the objects in any part of the graph
|
||||
|
||||
What features the framework does NOT have?
|
||||
- autowiring / autoresolving of the dependencies
|
||||
- the annotations and ``@inject``-like decorators
|
||||
- it will payoff as project grows
|
||||
|
||||
Have a question?
|
||||
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
|
||||
|
|
9
docs/_static/custom.css
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
.no-border {
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
}
|
||||
.no-border td {
|
||||
border: 0px !important;
|
||||
padding: 0px 10px 0px 0px !important;
|
||||
}
|
11
docs/_static/disqus.js
vendored
|
@ -1,11 +0,0 @@
|
|||
var disqus_shortname;
|
||||
var disqus_identifier;
|
||||
|
||||
$(function() {
|
||||
var disqus_thread = $("#disqus_thread");
|
||||
disqus_shortname = disqus_thread.data('disqus-shortname');
|
||||
disqus_identifier = disqus_thread.data('disqus-identifier');
|
||||
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
|
||||
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
|
||||
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
|
||||
});
|
2
docs/_static/logo.svg
vendored
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.8 KiB |
3
docs/_static/sphinx_rtd_theme-hotfix.css
vendored
|
@ -1,3 +0,0 @@
|
|||
.rst-content .highlight>pre, .rst-content .linenodiv>pre {
|
||||
line-height: normal;
|
||||
}
|
1
docs/_static/sponsor.html
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<iframe src="https://github.com/sponsors/rmk135/button" title="Sponsor Dependency Injector" height="32" width="114" style="border: 0; border-radius: 6px;"></iframe>
|
|
@ -1,9 +0,0 @@
|
|||
dependency_injector.ext.aiohttp
|
||||
===============================
|
||||
|
||||
.. automodule:: dependency_injector.ext.aiohttp
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
.. disqus::
|
9
docs/api/asgi-lifespan.rst
Normal file
|
@ -0,0 +1,9 @@
|
|||
dependency_injector.ext.starlette
|
||||
=================================
|
||||
|
||||
.. automodule:: dependency_injector.ext.starlette
|
||||
:members:
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. disqus::
|
|
@ -1,9 +0,0 @@
|
|||
dependency_injector.ext.flask
|
||||
=============================
|
||||
|
||||
.. automodule:: dependency_injector.ext.flask
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
.. disqus::
|
|
@ -2,11 +2,11 @@ API Documentation
|
|||
=================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 2
|
||||
|
||||
top_level
|
||||
providers
|
||||
containers
|
||||
errors
|
||||
aiohttpext
|
||||
flaskext
|
||||
top-level
|
||||
providers
|
||||
containers
|
||||
wiring
|
||||
errors
|
||||
asgi-lifespan
|
||||
|
|
7
docs/api/wiring.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
dependency_injector.wiring
|
||||
=============================
|
||||
|
||||
.. automodule:: dependency_injector.wiring
|
||||
:members:
|
||||
|
||||
.. disqus::
|
129
docs/conf.py
|
@ -20,49 +20,49 @@ import alabaster
|
|||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
#needs_sphinx = "1.0"
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# extensions coming with Sphinx (named "sphinx.ext.*") or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'alabaster',
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinxcontrib.disqus',
|
||||
"alabaster",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx_disqus.disqus",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
# source_suffix = [".rst", ".md"]
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
#source_encoding = "utf-8-sig"
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u'Dependency Injector'
|
||||
copyright = u'2020, ETS Labs'
|
||||
author = u'ETS Labs'
|
||||
project = "Dependency Injector"
|
||||
copyright = "2024, Roman Mogylatov"
|
||||
author = "Roman Mogylatov"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# The version info for the project you"re documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
# Getting version:
|
||||
with open('../src/dependency_injector/__init__.py') as init_file:
|
||||
version = re.search('__version__ = \'(.*?)\'', init_file.read()).group(1)
|
||||
with open("../src/dependency_injector/__init__.py") as init_file:
|
||||
version = re.search("__version__ = \"(.*?)\"", init_file.read()).group(1)
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
@ -72,23 +72,23 @@ release = version
|
|||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
language = "en"
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
#today = ""
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
#today_fmt = "%B %d, %Y"
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# If true, "()" will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
|
@ -100,7 +100,7 @@ exclude_patterns = ['_build']
|
|||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
@ -116,8 +116,8 @@ todo_include_todos = False
|
|||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
# html_theme = 'sphinx_rtd_theme'
|
||||
html_theme = 'alabaster'
|
||||
# html_theme = "sphinx_rtd_theme"
|
||||
html_theme = "alabaster"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
|
@ -141,21 +141,24 @@ html_theme_path = [alabaster.get_path()]
|
|||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
html_favicon = "favicon.ico"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
html_css_files = [
|
||||
"custom.css",
|
||||
]
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# If not "", a "Last updated on:" timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
#html_last_updated_fmt = "%b %d, %Y"
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
|
@ -189,50 +192,50 @@ html_static_path = ['_static']
|
|||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
#html_use_opensearch = ""
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
#html_search_language = 'en'
|
||||
# "da", "de", "en", "es", "fi", "fr", "hu", "it", "ja"
|
||||
# "nl", "no", "pt", "ro", "ru", "sv", "tr"
|
||||
#html_search_language = "en"
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
#html_search_options = {'type': 'default'}
|
||||
# Now only "ja" uses this config value
|
||||
#html_search_options = {"type": "default"}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#html_search_scorer = 'scorer.js'
|
||||
#html_search_scorer = "scorer.js"
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'dependency_injectordoc'
|
||||
htmlhelp_basename = "dependency_injectordoc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ("letterpaper" or "a4paper").
|
||||
#"papersize": "letterpaper",
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ("10pt", "11pt" or "12pt").
|
||||
#"pointsize": "10pt",
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
#"preamble": "",
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
#"figure_align": "htbp",
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'dependency_injector.tex', u'Dependency Injector Documentation',
|
||||
u'ETS Labs', 'manual'),
|
||||
(master_doc, "dependency_injector.tex", u"Dependency Injector Documentation",
|
||||
u"Roman Mogylatov", "manual"),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
@ -261,7 +264,7 @@ latex_documents = [
|
|||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'Dependency Injector', u'Dependency Injector Documentation',
|
||||
(master_doc, "Dependency Injector", u"Dependency Injector Documentation",
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
@ -275,9 +278,9 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'Dependency Injector', u'Dependency Injector Documentation',
|
||||
author, 'Dependency Injector', 'Dependency injection microframework for Python',
|
||||
'Miscellaneous'),
|
||||
(master_doc, "Dependency Injector", u"Dependency Injector Documentation",
|
||||
author, "Dependency Injector", "Dependency injection microframework for Python",
|
||||
"Miscellaneous"),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
@ -286,23 +289,25 @@ texinfo_documents = [
|
|||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
# How to display URL addresses: "footnote", "no", or "inline".
|
||||
#texinfo_show_urls = "footnote"
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
# If true, do not generate a @detailmenu in the "Top" node"s menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
autodoc_member_order = 'bysource'
|
||||
autodoc_member_order = "bysource"
|
||||
|
||||
disqus_shortname = 'python-dependency-injector'
|
||||
disqus_shortname = "python-dependency-injector"
|
||||
|
||||
html_theme_options = {
|
||||
'github_user': 'ets-labs',
|
||||
'github_repo': 'python-dependency-injector',
|
||||
'github_type': 'star',
|
||||
'github_button': True,
|
||||
'github_banner': True,
|
||||
'logo': 'logo.svg',
|
||||
'description': 'Dependency injection framework for Python',
|
||||
'code_font_size': '10pt',
|
||||
"github_user": "ets-labs",
|
||||
"github_repo": "python-dependency-injector",
|
||||
"github_type": "star",
|
||||
"github_button": True,
|
||||
"github_banner": True,
|
||||
"logo": "logo.svg",
|
||||
"description": "Dependency injection framework for Python by Roman Mogylatov",
|
||||
"code_font_size": "10pt",
|
||||
"analytics_id": "UA-67012059-1",
|
||||
"donate_url": "https://github.com/sponsors/rmk135",
|
||||
}
|
||||
|
|
18
docs/containers/check_dependencies.rst
Normal file
|
@ -0,0 +1,18 @@
|
|||
.. _check-container-dependencies:
|
||||
|
||||
Check container dependencies
|
||||
----------------------------
|
||||
|
||||
To check container dependencies use method ``.check_dependencies()``.
|
||||
|
||||
.. literalinclude:: ../../examples/containers/check_dependencies.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 12
|
||||
|
||||
Method ``.check_dependencies()`` raises an error if container has any undefined dependencies.
|
||||
If all dependencies are provided or have defaults, no error is raised.
|
||||
|
||||
See also: :ref:`dependency-provider`.
|
||||
|
||||
.. disqus::
|
23
docs/containers/copying.rst
Normal file
|
@ -0,0 +1,23 @@
|
|||
Container copying
|
||||
-----------------
|
||||
|
||||
You can create declarative container copies using ``@containers.copy()`` decorator.
|
||||
|
||||
.. literalinclude:: ../../examples/containers/declarative_copy_decorator1.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 18-22
|
||||
|
||||
Decorator ``@containers.copy()`` copies providers from source container to destination container.
|
||||
Destination container provider will replace source provider, if names match.
|
||||
|
||||
Decorator ``@containers.copy()`` helps you when you create derived declarative containers
|
||||
from the base one. Base container often keeps default dependencies while derived containers define
|
||||
overriding providers. Without ``@containers.copy()`` decorator, overridden providers are available
|
||||
in the derived container, but base class dependencies continue to be bound to the base class providers.
|
||||
|
||||
.. literalinclude:: ../../examples/containers/declarative_copy_decorator2.py
|
||||
:language: python
|
||||
:lines: 11-
|
||||
|
||||
.. disqus::
|
|
@ -34,10 +34,39 @@ Injections in the declarative container are done the usual way:
|
|||
:language: python
|
||||
:lines: 3-
|
||||
|
||||
You can override the container providers when you create the container instance:
|
||||
You can override container providers while creating a container instance:
|
||||
|
||||
.. literalinclude:: ../../examples/containers/declarative_override_providers.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 13
|
||||
|
||||
Alternatively, you can call ``container.override_providers()`` method when the container instance
|
||||
already exists:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
container = Container()
|
||||
|
||||
container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar))
|
||||
|
||||
assert isinstance(container.foo(), mock.Mock)
|
||||
assert isinstance(container.bar(), mock.Mock)
|
||||
|
||||
You can also use ``container.override_providers()`` with a context manager to reset
|
||||
provided overriding after the context is closed:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
container = Container()
|
||||
|
||||
with container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar)):
|
||||
assert isinstance(container.foo(), mock.Mock)
|
||||
assert isinstance(container.bar(), mock.Mock)
|
||||
|
||||
assert isinstance(container.foo(), Foo)
|
||||
assert isinstance(container.bar(), Bar)
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -23,3 +23,7 @@ Containers module API docs - :py:mod:`dependency_injector.containers`.
|
|||
dynamic
|
||||
specialization
|
||||
overriding
|
||||
copying
|
||||
reset_singletons
|
||||
check_dependencies
|
||||
traversal
|
||||
|
|
|
@ -21,4 +21,20 @@ The container also has:
|
|||
|
||||
:py:class:`DynamicContainer` has the same functionality.
|
||||
|
||||
Another possible way to override container providers on declarative level is
|
||||
``@containers.override()`` decorator:
|
||||
|
||||
.. literalinclude:: ../../examples/containers/declarative_override_decorator.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 12-16
|
||||
|
||||
Decorator ``@containers.override()`` takes a container for overriding as an argument.
|
||||
This container providers will be overridden by the providers with the same names from
|
||||
the decorated container.
|
||||
|
||||
It helps to change the behaviour of application by importing extension modules but not a code change.
|
||||
Imported module can override providers in main container. While the code uses main container as
|
||||
before, the overridden providers provide components defined in the extension module.
|
||||
|
||||
.. disqus::
|
||||
|
|
31
docs/containers/reset_singletons.rst
Normal file
|
@ -0,0 +1,31 @@
|
|||
.. _reset-container-singletons:
|
||||
|
||||
Reset container singletons
|
||||
--------------------------
|
||||
|
||||
To reset all container singletons use method ``.reset_singletons()``.
|
||||
|
||||
.. literalinclude:: ../../examples/containers/reset_singletons.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 16
|
||||
|
||||
Method ``.reset_singletons()`` also resets singletons in sub-containers: ``providers.Container`` and
|
||||
``providers.DependenciesContainer.``
|
||||
|
||||
.. literalinclude:: ../../examples/containers/reset_singletons_subcontainers.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 21
|
||||
|
||||
You can use ``.reset_singletons()`` method with a context manager. Singletons will be reset on
|
||||
both entering and exiting a context.
|
||||
|
||||
.. literalinclude:: ../../examples/containers/reset_singletons_with.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 14-15
|
||||
|
||||
See also: :ref:`singleton-provider`.
|
||||
|
||||
.. disqus::
|
33
docs/containers/traversal.rst
Normal file
|
@ -0,0 +1,33 @@
|
|||
Container providers traversal
|
||||
-----------------------------
|
||||
|
||||
To traverse container providers use method ``.traverse()``.
|
||||
|
||||
.. literalinclude:: ../../examples/containers/traverse.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 38
|
||||
|
||||
Method ``.traverse()`` returns a generator. Traversal generator visits all container providers.
|
||||
This includes nested providers even if they are not present on the root level of the container.
|
||||
|
||||
Traversal generator guarantees that each container provider will be visited only once.
|
||||
It can traverse cyclic provider graphs.
|
||||
|
||||
Traversal generator does not guarantee traversal order.
|
||||
|
||||
You can use ``types=[...]`` argument to filter providers. Traversal generator will only return
|
||||
providers matching specified types.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
container = Container()
|
||||
|
||||
for provider in container.traverse(types=[providers.Resource]):
|
||||
print(provider)
|
||||
|
||||
# <dependency_injector.providers.Resource(<function init_database at 0x10bd2cb80>) at 0x10d346b40>
|
||||
# <dependency_injector.providers.Resource(<function init_cache at 0x10be373a0>) at 0x10d346bc0>
|
||||
|
||||
.. disqus::
|
|
@ -19,7 +19,7 @@ additional arguments.
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
instance = concrete_factory()
|
||||
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
|
||||
|
||||
|
@ -43,21 +43,21 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
|
|||
providers.Factory(dict, arg1=1),
|
||||
arg2=2,
|
||||
)
|
||||
print(chained_dict_factory()) # prints: {'arg1': 1, 'arg2': 2}
|
||||
print(chained_dict_factory()) # prints: {"arg1": 1, "arg2": 2}
|
||||
|
||||
# 2. Keyword arguments of upper level factory have priority
|
||||
chained_dict_factory = providers.Factory(
|
||||
providers.Factory(dict, arg1=1),
|
||||
arg1=2,
|
||||
)
|
||||
print(chained_dict_factory()) # prints: {'arg1': 2}
|
||||
print(chained_dict_factory()) # prints: {"arg1": 2}
|
||||
|
||||
# 3. Keyword arguments provided from context have the most priority
|
||||
chained_dict_factory = providers.Factory(
|
||||
providers.Factory(dict, arg1=1),
|
||||
arg1=2,
|
||||
)
|
||||
print(chained_dict_factory(arg1=3)) # prints: {'arg1': 3}
|
||||
print(chained_dict_factory(arg1=3)) # prints: {"arg1": 3}
|
||||
|
||||
|
||||
Credits
|
||||
|
|
|
@ -20,7 +20,7 @@ additional arguments.
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
instance = concrete_factory()
|
||||
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
|
||||
|
||||
|
@ -46,7 +46,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
|
|||
arg1=1,
|
||||
)
|
||||
dict_factory = factory_of_dict_factories(arg2=2)
|
||||
print(dict_factory()) # prints: {'arg1': 1, 'arg2': 2}
|
||||
print(dict_factory()) # prints: {"arg1": 1, "arg2": 2}
|
||||
|
||||
# 2. Keyword arguments of upper level factory have priority
|
||||
factory_of_dict_factories = providers.Factory(
|
||||
|
@ -55,7 +55,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
|
|||
arg1=1,
|
||||
)
|
||||
dict_factory = factory_of_dict_factories(arg1=2)
|
||||
print(dict_factory()) # prints: {'arg1': 2}
|
||||
print(dict_factory()) # prints: {"arg1": 2}
|
||||
|
||||
# 3. Keyword arguments provided from context have the most priority
|
||||
factory_of_dict_factories = providers.Factory(
|
||||
|
@ -64,7 +64,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
|
|||
arg1=1,
|
||||
)
|
||||
dict_factory = factory_of_dict_factories(arg1=2)
|
||||
print(dict_factory(arg1=3)) # prints: {'arg1': 3}
|
||||
print(dict_factory(arg1=3)) # prints: {"arg1": 3}
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
|
83
docs/examples/aiohttp.rst
Normal file
|
@ -0,0 +1,83 @@
|
|||
.. _aiohttp-example:
|
||||
|
||||
Aiohttp example
|
||||
===============
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,Aiohttp,Example
|
||||
:description: This example demonstrates a usage of the Aiohttp and Dependency Injector.
|
||||
|
||||
|
||||
This example shows how to use ``Dependency Injector`` with `Aiohttp <https://docs.aiohttp.org/>`_.
|
||||
|
||||
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
||||
|
||||
:ref:`aiohttp-tutorial` demonstrates how to build this application step-by-step.
|
||||
|
||||
Application structure
|
||||
---------------------
|
||||
|
||||
Application has next structure:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./
|
||||
├── giphynavigator/
|
||||
│ ├── __init__.py
|
||||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── giphy.py
|
||||
│ ├── handlers.py
|
||||
│ ├── services.py
|
||||
│ └── tests.py
|
||||
├── config.yml
|
||||
└── requirements.txt
|
||||
|
||||
Container
|
||||
---------
|
||||
|
||||
Declarative container is defined in ``giphynavigator/containers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/containers.py
|
||||
:language: python
|
||||
|
||||
Handlers
|
||||
--------
|
||||
|
||||
Handler has dependencies on search service and some config options. The dependencies are injected
|
||||
using :ref:`wiring` feature.
|
||||
|
||||
Listing of ``giphynavigator/handlers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/handlers.py
|
||||
:language: python
|
||||
|
||||
Application factory
|
||||
-------------------
|
||||
Application factory creates container, wires it with the ``handlers`` module, creates
|
||||
``Aiohttp`` app and setup routes.
|
||||
|
||||
Listing of ``giphynavigator/application.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/application.py
|
||||
:language: python
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/tests.py
|
||||
:language: python
|
||||
:emphasize-lines: 32,59,73
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
|
@ -84,4 +84,6 @@ Run the application
|
|||
|
||||
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
|
@ -90,4 +90,6 @@ Run the application
|
|||
|
||||
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-single-container>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
22
docs/examples/boto3.rst
Normal file
|
@ -0,0 +1,22 @@
|
|||
.. _boto3-example:
|
||||
|
||||
Boto3 example
|
||||
=============
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,Boto3,AWS,Amazon Web Services,S3,SQS,Rout53,EC2,Lambda,Example
|
||||
:description: This example demonstrates a usage of Boto3 AWS client and Dependency Injector.
|
||||
|
||||
|
||||
This example shows how to use ``Dependency Injector`` with `Boto3 <https://github.com/boto/boto3>`_.
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/boto3-session>`_.
|
||||
|
||||
Listing of ``boto3_session_example.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
|
||||
:language: python
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
|
@ -129,4 +129,6 @@ Run the application
|
|||
|
||||
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
99
docs/examples/django.rst
Normal file
|
@ -0,0 +1,99 @@
|
|||
.. _django-example:
|
||||
|
||||
Django example
|
||||
==============
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,Django,Example
|
||||
:description: This example demonstrates a usage of the Django and Dependency Injector.
|
||||
|
||||
|
||||
This example shows how to use ``Dependency Injector`` with `Django <https://www.djangoproject.com/>`_.
|
||||
|
||||
The example application helps to search for repositories on the Github.
|
||||
|
||||
.. image:: images/django.png
|
||||
:width: 100%
|
||||
:align: center
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
|
||||
|
||||
Application structure
|
||||
---------------------
|
||||
|
||||
Application has standard Django project structure. It consists of ``githubnavigator`` project package and
|
||||
``web`` application package:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./
|
||||
├── githubnavigator/
|
||||
│ ├── __init__.py
|
||||
│ ├── asgi.py
|
||||
│ ├── containers.py
|
||||
│ ├── services.py
|
||||
│ ├── settings.py
|
||||
│ ├── urls.py
|
||||
│ └── wsgi.py
|
||||
├── web/
|
||||
│ ├── templates/
|
||||
│ │ ├── base.html
|
||||
│ │ └── index.html
|
||||
│ ├── __init__.py
|
||||
│ ├── apps.py
|
||||
│ ├── tests.py
|
||||
│ ├── urls.py
|
||||
│ └── views.py
|
||||
├── manage.py
|
||||
└── requirements.txt
|
||||
|
||||
Container
|
||||
---------
|
||||
|
||||
Declarative container is defined in ``githubnavigator/containers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/django/githubnavigator/containers.py
|
||||
:language: python
|
||||
|
||||
Container instance is created in ``githubnavigator/__init__.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/django/githubnavigator/__init__.py
|
||||
:language: python
|
||||
|
||||
Views
|
||||
-----
|
||||
|
||||
View has dependencies on search service and some config options. The dependencies are injected
|
||||
using :ref:`wiring` feature.
|
||||
|
||||
Listing of ``web/views.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/django/web/views.py
|
||||
:language: python
|
||||
|
||||
App config
|
||||
----------
|
||||
|
||||
Container is wired to the ``views`` module in the app config ``web/apps.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/django/web/apps.py
|
||||
:language: python
|
||||
:emphasize-lines: 12
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``web/tests.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/django/web/tests.py
|
||||
:language: python
|
||||
:emphasize-lines: 39,60
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
100
docs/examples/fastapi-redis.rst
Normal file
|
@ -0,0 +1,100 @@
|
|||
.. _fastapi-redis-example:
|
||||
|
||||
FastAPI + Redis example
|
||||
=======================
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,FastAPI,Redis,Example
|
||||
:description: This example demonstrates a usage of the FastAPI, Redis, and Dependency Injector.
|
||||
|
||||
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_ and
|
||||
`Redis <https://redis.io/>`_.
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-redis>`_.
|
||||
|
||||
See also:
|
||||
|
||||
- Provider :ref:`async-injections`
|
||||
- Resource provider :ref:`resource-async-initializers`
|
||||
- Wiring :ref:`async-injections-wiring`
|
||||
|
||||
Application structure
|
||||
---------------------
|
||||
|
||||
Application has next structure:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./
|
||||
├── fastapiredis/
|
||||
│ ├── __init__.py
|
||||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── redis.py
|
||||
│ ├── services.py
|
||||
│ └── tests.py
|
||||
├── docker-compose.yml
|
||||
├── Dockerfile
|
||||
└── requirements.txt
|
||||
|
||||
Redis
|
||||
-----
|
||||
|
||||
Module ``redis`` defines Redis connection pool initialization and shutdown. See ``fastapiredis/redis.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/redis.py
|
||||
:language: python
|
||||
|
||||
Service
|
||||
-------
|
||||
|
||||
Module ``services`` contains example service. Service has a dependency on Redis connection pool.
|
||||
It uses it for getting and setting a key asynchronously. Real life service will do something more meaningful.
|
||||
See ``fastapiredis/services.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/services.py
|
||||
:language: python
|
||||
|
||||
Container
|
||||
---------
|
||||
|
||||
Declarative container wires example service with Redis connection pool. See ``fastapiredis/containers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/containers.py
|
||||
:language: python
|
||||
|
||||
Application
|
||||
-----------
|
||||
|
||||
Module ``application`` creates ``FastAPI`` app, setup endpoint, and init container.
|
||||
|
||||
Endpoint ``index`` has a dependency on example service. The dependency is injected using :ref:`wiring` feature.
|
||||
|
||||
Listing of ``fastapiredis/application.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/application.py
|
||||
:language: python
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Tests use :ref:`provider-overriding` feature to replace example service with a mock. See ``fastapiredis/tests.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/tests.py
|
||||
:language: python
|
||||
:emphasize-lines: 24
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-redis>`_.
|
||||
|
||||
See also:
|
||||
|
||||
- Provider :ref:`async-injections`
|
||||
- Resource provider :ref:`resource-async-initializers`
|
||||
- Wiring :ref:`async-injections-wiring`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
121
docs/examples/fastapi-sqlalchemy.rst
Normal file
|
@ -0,0 +1,121 @@
|
|||
.. _fastapi-sqlalchemy-example:
|
||||
|
||||
FastAPI + SQLAlchemy example
|
||||
============================
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,FastAPI,SQLAlchemy,Example
|
||||
:description: This example demonstrates a usage of the FastAPI, SQLAlchemy, and Dependency Injector.
|
||||
|
||||
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_ and
|
||||
`SQLAlchemy <https://www.sqlalchemy.org/>`_.
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_.
|
||||
|
||||
Thanks to `@ShvetsovYura <https://github.com/ShvetsovYura>`_ for providing initial example:
|
||||
`FastAPI_DI_SqlAlchemy <https://github.com/ShvetsovYura/FastAPI_DI_SqlAlchemy>`_.
|
||||
|
||||
Application structure
|
||||
---------------------
|
||||
|
||||
Application has next structure:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./
|
||||
├── webapp/
|
||||
│ ├── __init__.py
|
||||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── database.py
|
||||
│ ├── endpoints.py
|
||||
│ ├── models.py
|
||||
│ ├── repositories.py
|
||||
│ ├── services.py
|
||||
│ └── tests.py
|
||||
├── config.yml
|
||||
├── docker-compose.yml
|
||||
├── Dockerfile
|
||||
└── requirements.txt
|
||||
|
||||
Application factory
|
||||
-------------------
|
||||
|
||||
Application factory creates container, wires it with the ``endpoints`` module, creates
|
||||
``FastAPI`` app, and setup routes.
|
||||
|
||||
Application factory also creates database if it does not exist.
|
||||
|
||||
Listing of ``webapp/application.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/application.py
|
||||
:language: python
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
Module ``endpoints`` contains example endpoints. Endpoints have a dependency on user service.
|
||||
User service is injected using :ref:`wiring` feature. See ``webapp/endpoints.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/endpoints.py
|
||||
:language: python
|
||||
|
||||
Container
|
||||
---------
|
||||
|
||||
Declarative container wires example user service, user repository, and utility database class.
|
||||
See ``webapp/containers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/containers.py
|
||||
:language: python
|
||||
|
||||
Services
|
||||
--------
|
||||
|
||||
Module ``services`` contains example user service. See ``webapp/services.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/services.py
|
||||
:language: python
|
||||
|
||||
Repositories
|
||||
------------
|
||||
|
||||
Module ``repositories`` contains example user repository. See ``webapp/repositories.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/repositories.py
|
||||
:language: python
|
||||
|
||||
Models
|
||||
------
|
||||
|
||||
Module ``models`` contains example SQLAlchemy user model. See ``webapp/models.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/models.py
|
||||
:language: python
|
||||
|
||||
Database
|
||||
--------
|
||||
|
||||
Module ``database`` defines declarative base and utility class with engine and session factory.
|
||||
See ``webapp/database.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/database.py
|
||||
:language: python
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Tests use :ref:`provider-overriding` feature to replace repository with a mock. See ``webapp/tests.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/tests.py
|
||||
:language: python
|
||||
:emphasize-lines: 25, 45, 58, 74, 86, 97
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
81
docs/examples/fastapi.rst
Normal file
|
@ -0,0 +1,81 @@
|
|||
.. _fastapi-example:
|
||||
|
||||
FastAPI example
|
||||
===============
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,FastAPI,Example
|
||||
:description: This example demonstrates a usage of the FastAPI and Dependency Injector.
|
||||
|
||||
|
||||
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_.
|
||||
|
||||
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
|
||||
|
||||
Application structure
|
||||
---------------------
|
||||
|
||||
Application has next structure:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./
|
||||
├── giphynavigator/
|
||||
│ ├── __init__.py
|
||||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── endpoints.py
|
||||
│ ├── giphy.py
|
||||
│ ├── services.py
|
||||
│ └── tests.py
|
||||
├── config.yml
|
||||
└── requirements.txt
|
||||
|
||||
Container
|
||||
---------
|
||||
|
||||
Declarative container is defined in ``giphynavigator/containers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/containers.py
|
||||
:language: python
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
Endpoint has a dependency on search service. There are also some config options that are used as default values.
|
||||
The dependencies are injected using :ref:`wiring` feature.
|
||||
|
||||
Listing of ``giphynavigator/endpoints.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/endpoints.py
|
||||
:language: python
|
||||
|
||||
Application factory
|
||||
-------------------
|
||||
Application factory creates container, wires it with the ``endpoints`` module, creates
|
||||
``FastAPI`` app, and setup routes.
|
||||
|
||||
Listing of ``giphynavigator/application.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/application.py
|
||||
:language: python
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/tests.py
|
||||
:language: python
|
||||
:emphasize-lines: 29,57,72
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
48
docs/examples/fastdepends.rst
Normal file
|
@ -0,0 +1,48 @@
|
|||
.. _fastdepends-example:
|
||||
|
||||
FastDepends example
|
||||
===================
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,FastDepends,Example
|
||||
:description: This example demonstrates a usage of the FastDepends and Dependency Injector.
|
||||
|
||||
|
||||
This example demonstrates how to use ``Dependency Injector`` with `FastDepends <https://github.com/Lancetnik/FastDepends>`_, a lightweight dependency injection framework inspired by FastAPI's dependency system, but without the web framework components.
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
The integration between FastDepends and Dependency Injector is straightforward. Simply use Dependency Injector's ``Provide`` marker within FastDepends' ``Depends`` function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from fast_depends import Depends
|
||||
|
||||
|
||||
class CoefficientService:
|
||||
@staticmethod
|
||||
def get_coefficient() -> float:
|
||||
return 1.2
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
service = providers.Factory(CoefficientService)
|
||||
|
||||
|
||||
@inject
|
||||
def apply_coefficient(
|
||||
a: int,
|
||||
coefficient_provider: CoefficientService = Depends(Provide[Container.service]),
|
||||
) -> float:
|
||||
return a * coefficient_provider.get_coefficient()
|
||||
|
||||
|
||||
container = Container()
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
|
||||
apply_coefficient(100) == 120.0
|
91
docs/examples/flask-blueprints.rst
Normal file
|
@ -0,0 +1,91 @@
|
|||
.. _flask-blueprints-example:
|
||||
|
||||
Flask blueprints example
|
||||
========================
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,Flask,Blueprints,Example
|
||||
:description: This example demonstrates a usage of the Flask Blueprints and Dependency Injector.
|
||||
|
||||
|
||||
This example shows how to use ``Dependency Injector`` with `Flask <https://flask.palletsprojects.com/en/1.1.x/>`_
|
||||
blueprints.
|
||||
|
||||
The example application helps to search for repositories on the Github.
|
||||
|
||||
.. image:: images/flask.png
|
||||
:width: 100%
|
||||
:align: center
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
|
||||
|
||||
Application structure
|
||||
---------------------
|
||||
|
||||
Application has next structure:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./
|
||||
├── githubnavigator/
|
||||
│ ├── blueprints
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── example.py
|
||||
│ ├── templates
|
||||
│ │ ├── base.html
|
||||
│ │ └── index.py
|
||||
│ ├── __init__.py
|
||||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── services.py
|
||||
│ └── tests.py
|
||||
├── config.yml
|
||||
└── requirements.txt
|
||||
|
||||
Container
|
||||
---------
|
||||
|
||||
Declarative container is defined in ``githubnavigator/containers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/containers.py
|
||||
:language: python
|
||||
|
||||
Blueprints
|
||||
----------
|
||||
|
||||
Blueprint's view has dependencies on search service and some config options. The dependencies are injected
|
||||
using :ref:`wiring` feature.
|
||||
|
||||
Listing of ``githubnavigator/blueprints/example.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/blueprints/example.py
|
||||
:language: python
|
||||
|
||||
Application factory
|
||||
-------------------
|
||||
|
||||
Application factory creates container, wires it with the blueprints, creates
|
||||
``Flask`` app, and setup routes.
|
||||
|
||||
Listing of ``githubnavigator/application.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/application.py
|
||||
:language: python
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``githubnavigator/tests.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/tests.py
|
||||
:language: python
|
||||
:emphasize-lines: 44,67
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
89
docs/examples/flask.rst
Normal file
|
@ -0,0 +1,89 @@
|
|||
.. _flask-example:
|
||||
|
||||
Flask example
|
||||
=============
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,Flask,Example
|
||||
:description: This example demonstrates a usage of the Flask and Dependency Injector.
|
||||
|
||||
|
||||
This example shows how to use ``Dependency Injector`` with `Flask <https://flask.palletsprojects.com/en/1.1.x/>`_.
|
||||
|
||||
The example application helps to search for repositories on the Github.
|
||||
|
||||
.. image:: images/flask.png
|
||||
:width: 100%
|
||||
:align: center
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
|
||||
|
||||
:ref:`flask-tutorial` demonstrates how to build this application step-by-step.
|
||||
|
||||
Application structure
|
||||
---------------------
|
||||
|
||||
Application has next structure:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./
|
||||
├── githubnavigator/
|
||||
│ ├── templates
|
||||
│ │ ├── base.html
|
||||
│ │ └── index.py
|
||||
│ ├── __init__.py
|
||||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── services.py
|
||||
│ ├── tests.py
|
||||
│ └── views.py
|
||||
├── config.yml
|
||||
└── requirements.txt
|
||||
|
||||
Container
|
||||
---------
|
||||
|
||||
Declarative container is defined in ``githubnavigator/containers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/containers.py
|
||||
:language: python
|
||||
|
||||
Views
|
||||
-----
|
||||
|
||||
View has dependencies on search service and some config options. The dependencies are injected
|
||||
using :ref:`wiring` feature.
|
||||
|
||||
Listing of ``githubnavigator/views.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/views.py
|
||||
:language: python
|
||||
|
||||
Application factory
|
||||
-------------------
|
||||
Application factory creates container, wires it with the ``views`` module, creates
|
||||
``Flask`` app and setup routes.
|
||||
|
||||
Listing of ``githubnavigator/application.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/application.py
|
||||
:language: python
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``githubnavigator/tests.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/tests.py
|
||||
:language: python
|
||||
:emphasize-lines: 44,67
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
BIN
docs/examples/images/django.png
Normal file
After Width: | Height: | Size: 382 KiB |
Before Width: | Height: | Size: 647 KiB After Width: | Height: | Size: 647 KiB |
|
@ -13,5 +13,15 @@ Explore the examples to see the ``Dependency Injector`` in action.
|
|||
application-single-container
|
||||
application-multiple-containers
|
||||
decoupled-packages
|
||||
boto3
|
||||
django
|
||||
flask
|
||||
flask-blueprints
|
||||
aiohttp
|
||||
sanic
|
||||
fastapi
|
||||
fastapi-redis
|
||||
fastapi-sqlalchemy
|
||||
fastdepends
|
||||
|
||||
.. disqus::
|
||||
|
|
82
docs/examples/sanic.rst
Normal file
|
@ -0,0 +1,82 @@
|
|||
.. _sanic-example:
|
||||
|
||||
Sanic example
|
||||
==============
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,Sanic,Example
|
||||
:description: This example demonstrates a usage of the Sanic and Dependency Injector.
|
||||
|
||||
|
||||
This example shows how to use ``Dependency Injector`` with `Sanic <https://sanic.readthedocs.io/en/latest/>`_.
|
||||
|
||||
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
|
||||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
|
||||
|
||||
Application structure
|
||||
---------------------
|
||||
|
||||
Application has next structure:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./
|
||||
├── giphynavigator/
|
||||
│ ├── __init__.py
|
||||
│ ├── __main__.py
|
||||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── giphy.py
|
||||
│ ├── handlers.py
|
||||
│ ├── services.py
|
||||
│ └── tests.py
|
||||
├── config.yml
|
||||
└── requirements.txt
|
||||
|
||||
Container
|
||||
---------
|
||||
|
||||
Declarative container is defined in ``giphynavigator/containers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/containers.py
|
||||
:language: python
|
||||
|
||||
Handlers
|
||||
--------
|
||||
|
||||
Handler has dependencies on search service and some config options. The dependencies are injected
|
||||
using :ref:`wiring` feature.
|
||||
|
||||
Listing of ``giphynavigator/handlers.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/handlers.py
|
||||
:language: python
|
||||
|
||||
Application factory
|
||||
-------------------
|
||||
Application factory creates container, wires it with the ``handlers`` module, creates
|
||||
``Sanic`` app and setup routes.
|
||||
|
||||
Listing of ``giphynavigator/application.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/application.py
|
||||
:language: python
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
|
||||
|
||||
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/tests.py
|
||||
:language: python
|
||||
:emphasize-lines: 34,61,75
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
BIN
docs/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB |
|
@ -9,11 +9,11 @@ Dependency Injector --- Dependency injection framework for Python
|
|||
:description: Dependency Injector is a dependency injection framework
|
||||
for Python. It helps to maintain you application structure.
|
||||
It was designed to be unified, developer-friendly
|
||||
tool that helps to implement dependency injection design
|
||||
pattern in formal, pretty, Pythonic way. Dependency Injector
|
||||
provides implementations of such popular design patterns
|
||||
like IoC container, Factory and Singleton. Dependency
|
||||
Injector providers are implemented as C extension types
|
||||
tool that helps to implement dependency injection design
|
||||
pattern in formal, pretty, Pythonic way. Dependency Injector
|
||||
provides implementations of such popular design patterns
|
||||
like IoC container, Factory and Singleton. Dependency
|
||||
Injector providers are implemented as C extension types
|
||||
using Cython.
|
||||
|
||||
.. _index:
|
||||
|
@ -34,15 +34,15 @@ Dependency Injector --- Dependency injection framework for Python
|
|||
:target: https://pypi.org/project/dependency-injector/
|
||||
:alt: Supported Python implementations
|
||||
|
||||
.. image:: https://pepy.tech/badge/dependency-injector
|
||||
.. image:: https://static.pepy.tech/badge/dependency-injector
|
||||
:target: https://pepy.tech/project/dependency-injector
|
||||
:alt: Downloads
|
||||
|
||||
.. image:: https://pepy.tech/badge/dependency-injector/month
|
||||
.. image:: https://static.pepy.tech/badge/dependency-injector/month
|
||||
:target: https://pepy.tech/project/dependency-injector
|
||||
:alt: Downloads
|
||||
|
||||
.. image:: https://pepy.tech/badge/dependency-injector/week
|
||||
.. image:: https://static.pepy.tech/badge/dependency-injector/week
|
||||
:target: https://pepy.tech/project/dependency-injector
|
||||
:alt: Downloads
|
||||
|
||||
|
@ -50,14 +50,10 @@ Dependency Injector --- Dependency injection framework for Python
|
|||
:target: https://pypi.org/project/dependency-injector/
|
||||
:alt: Wheel
|
||||
|
||||
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
|
||||
:target: https://travis-ci.org/ets-labs/python-dependency-injector
|
||||
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
|
||||
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||
:alt: Build Status
|
||||
|
||||
.. image:: http://readthedocs.org/projects/python-dependency-injector/badge/?version=latest
|
||||
:target: http://python-dependency-injector.ets-labs.org/
|
||||
:alt: Docs Status
|
||||
|
||||
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
||||
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
|
||||
:alt: Coverage Status
|
||||
|
@ -69,21 +65,28 @@ It helps implementing the dependency injection principle.
|
|||
Key features of the ``Dependency Injector``:
|
||||
|
||||
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||
``List``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your
|
||||
objects. See :ref:`providers`.
|
||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
|
||||
that help assemble your objects. See :ref:`providers`.
|
||||
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
|
||||
and configuring dev / stage environment to replace API clients with stubs etc. See
|
||||
and configuring dev/stage environment to replace API clients with stubs etc. See
|
||||
:ref:`provider-overriding`.
|
||||
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables
|
||||
and dictionaries. See :ref:`configuration-provider`.
|
||||
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
|
||||
environment variables, and dictionaries. See :ref:`configuration-provider`.
|
||||
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
|
||||
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
|
||||
See :ref:`resource-provider`.
|
||||
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
|
||||
- **Performance**. Fast. Written in ``Cython``.
|
||||
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
|
||||
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
|
||||
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
|
||||
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
||||
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
|
||||
- **Performance**. Fast. Written in ``Cython``.
|
||||
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -93,7 +96,7 @@ Key features of the ``Dependency Injector``:
|
|||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
timeout=config.timeout,
|
||||
)
|
||||
|
||||
service = providers.Factory(
|
||||
|
@ -102,23 +105,29 @@ Key features of the ``Dependency Injector``:
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
container.config.api_key.from_env("API_KEY", required=True)
|
||||
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
service = container.service()
|
||||
main() # <-- dependency is injected automatically
|
||||
|
||||
With the ``Dependency Injector`` you keep **application structure in one place**.
|
||||
This place is called **the container**. You use the container to manage all the components of the
|
||||
application. All the component dependencies are defined explicitly. This provides the control on
|
||||
the application structure. It is **easy to understand and change** it.
|
||||
with container.api_client.override(mock.Mock()):
|
||||
main() # <-- overridden dependency is injected automatically
|
||||
|
||||
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-map.svg
|
||||
With the ``Dependency Injector``, object assembling is consolidated in the container.
|
||||
Dependency injections are defined explicitly.
|
||||
This makes it easier to understand and change how the application works.
|
||||
|
||||
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
|
||||
:target: https://github.com/ets-labs/python-dependency-injector
|
||||
|
||||
*The container is like a map of your application. You always know what depends on what.*
|
||||
|
||||
Explore the documentation to know more about the ``Dependency Injector``.
|
||||
|
||||
.. _contents:
|
||||
|
@ -126,15 +135,16 @@ Explore the documentation to know more about the ``Dependency Injector``.
|
|||
Contents
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
introduction/index
|
||||
examples/index
|
||||
tutorials/index
|
||||
providers/index
|
||||
containers/index
|
||||
examples-other/index
|
||||
api/index
|
||||
main/feedback
|
||||
main/changelog
|
||||
introduction/index
|
||||
examples/index
|
||||
tutorials/index
|
||||
providers/index
|
||||
containers/index
|
||||
wiring
|
||||
examples-other/index
|
||||
api/index
|
||||
main/feedback
|
||||
main/changelog
|
||||
|
|
|
@ -9,27 +9,27 @@ Dependency injection and inversion of control in Python
|
|||
Dependency Injector, its container, Factory, Singleton and Configuration
|
||||
providers. The example show how to use Dependency Injector providers overriding
|
||||
feature for testing or configuring project in different environments and explains
|
||||
why it's better then monkey-patching.
|
||||
why it's better than monkey-patching.
|
||||
|
||||
Originally dependency injection pattern got popular in the languages with a static typing,
|
||||
like Java. Dependency injection is a principle that helps to achieve an inversion of control.
|
||||
Dependency injection framework can significantly improve a flexibility of the language
|
||||
with a static typing. Implementation of a dependency injection framework for a language
|
||||
with a static typing is not something that one can do quickly. It will be a quite complex thing
|
||||
Originally dependency injection pattern got popular in languages with static typing like Java.
|
||||
Dependency injection is a principle that helps to achieve an inversion of control. A
|
||||
dependency injection framework can significantly improve the flexibility of a language
|
||||
with static typing. Implementation of a dependency injection framework for a language
|
||||
with static typing is not something that one can do quickly. It will be a quite complex thing
|
||||
to be done well. And will take time.
|
||||
|
||||
Python is an interpreted language with a dynamic typing. There is an opinion that dependency
|
||||
Python is an interpreted language with dynamic typing. There is an opinion that dependency
|
||||
injection doesn't work for it as well as it does for Java. A lot of the flexibility is already
|
||||
built in. Also there is an opinion that a dependency injection framework is something that
|
||||
built-in. Also, there is an opinion that a dependency injection framework is something that
|
||||
Python developer rarely needs. Python developers say that dependency injection can be implemented
|
||||
easily using language fundamentals.
|
||||
|
||||
This page describes the advantages of the dependency injection usage in Python. It
|
||||
contains Python examples that show how to implement dependency injection. It demonstrates a usage
|
||||
of the dependency injection framework ``Dependency Injector``, its container, ``Factory``,
|
||||
``Singleton`` and ``Configuration`` providers. The example shows how to use ``Dependency Injector``
|
||||
providers overriding feature for testing or configuring project in different environments and
|
||||
explains why it's better then monkey-patching.
|
||||
This page describes the advantages of applying dependency injection in Python. It
|
||||
contains Python examples that show how to implement dependency injection. It demonstrates the usage
|
||||
of the ``Dependency Injector`` framework, its container, ``Factory``, ``Singleton``,
|
||||
and ``Configuration`` providers. The example shows how to use providers' overriding feature
|
||||
of ``Dependency Injector`` for testing or re-configuring a project in different environments and
|
||||
explains why it's better than monkey-patching.
|
||||
|
||||
What is dependency injection?
|
||||
-----------------------------
|
||||
|
@ -44,14 +44,14 @@ What is coupling and cohesion?
|
|||
|
||||
Coupling and cohesion are about how tough the components are tied.
|
||||
|
||||
- **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way
|
||||
- **High coupling**. If the coupling is high it's like using superglue or welding. No easy way
|
||||
to disassemble.
|
||||
- **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and
|
||||
assemble back or assemble a different way. It is an opposite to high coupling.
|
||||
- **High cohesion**. High cohesion is like using screws. Quite easy to disassemble and
|
||||
re-assemble in a different way. It is an opposite to high coupling.
|
||||
|
||||
When the cohesion is high the coupling is low.
|
||||
Cohesion often correlates with coupling. Higher cohesion usually leads to lower coupling and vice versa.
|
||||
|
||||
Low coupling brings a flexibility. Your code becomes easier to change and test.
|
||||
Low coupling brings flexibility. Your code becomes easier to change and test.
|
||||
|
||||
How to implement the dependency injection?
|
||||
|
||||
|
@ -66,21 +66,25 @@ Before:
|
|||
|
||||
class ApiClient:
|
||||
|
||||
def __init__(self):
|
||||
self.api_key = os.getenv('API_KEY') # <-- the dependency
|
||||
self.timeout = os.getenv('TIMEOUT') # <-- the dependency
|
||||
def __init__(self) -> None:
|
||||
self.api_key = os.getenv("API_KEY") # <-- dependency
|
||||
self.timeout = int(os.getenv("TIMEOUT")) # <-- dependency
|
||||
|
||||
|
||||
class Service:
|
||||
|
||||
def __init__(self):
|
||||
self.api_client = ApiClient() # <-- the dependency
|
||||
def __init__(self) -> None:
|
||||
self.api_client = ApiClient() # <-- dependency
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
service = Service()
|
||||
def main() -> None:
|
||||
service = Service() # <-- dependency
|
||||
...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
After:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -90,19 +94,30 @@ After:
|
|||
|
||||
class ApiClient:
|
||||
|
||||
def __init__(self, api_key: str, timeout: int):
|
||||
self.api_key = api_key # <-- the dependency is injected
|
||||
self.timeout = timeout # <-- the dependency is injected
|
||||
def __init__(self, api_key: str, timeout: int) -> None:
|
||||
self.api_key = api_key # <-- dependency is injected
|
||||
self.timeout = timeout # <-- dependency is injected
|
||||
|
||||
|
||||
class Service:
|
||||
|
||||
def __init__(self, api_client: ApiClient):
|
||||
self.api_client = api_client # <-- the dependency is injected
|
||||
def __init__(self, api_client: ApiClient) -> None:
|
||||
self.api_client = api_client # <-- dependency is injected
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
|
||||
def main(service: Service) -> None: # <-- dependency is injected
|
||||
...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(
|
||||
service=Service(
|
||||
api_client=ApiClient(
|
||||
api_key=os.getenv("API_KEY"),
|
||||
timeout=int(os.getenv("TIMEOUT")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
``ApiClient`` is decoupled from knowing where the options come from. You can read a key and a
|
||||
timeout from a configuration file or even get them from a database.
|
||||
|
@ -110,11 +125,22 @@ timeout from a configuration file or even get them from a database.
|
|||
``Service`` is decoupled from the ``ApiClient``. It does not create it anymore. You can provide a
|
||||
stub or other compatible object.
|
||||
|
||||
Function ``main()`` is decoupled from ``Service``. It receives it as an argument.
|
||||
|
||||
Flexibility comes with a price.
|
||||
|
||||
Now you need to assemble the objects like this::
|
||||
Now you need to assemble and inject the objects like this:
|
||||
|
||||
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
|
||||
.. code-block:: python
|
||||
|
||||
main(
|
||||
service=Service(
|
||||
api_client=ApiClient(
|
||||
api_key=os.getenv("API_KEY"),
|
||||
timeout=int(os.getenv("TIMEOUT")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
The assembly code might get duplicated and it'll become harder to change the application structure.
|
||||
|
||||
|
@ -123,18 +149,20 @@ Here comes the ``Dependency Injector``.
|
|||
What does the Dependency Injector do?
|
||||
-------------------------------------
|
||||
|
||||
With the dependency injection pattern objects loose the responsibility of assembling the
|
||||
dependencies. The ``Dependency Injector`` absorbs that responsibility.
|
||||
With the dependency injection pattern, objects lose the responsibility of assembling
|
||||
the dependencies. The ``Dependency Injector`` absorbs that responsibility.
|
||||
|
||||
``Dependency Injector`` helps to assemble the objects.
|
||||
``Dependency Injector`` helps to assemble and inject the dependencies.
|
||||
|
||||
It provides a container and providers that help you with the objects assembly. When you
|
||||
need an object you get it from the container. The rest of the assembly work is done by the
|
||||
framework:
|
||||
It provides a container and providers that help you with the objects assembly.
|
||||
When you need an object you place a ``Provide`` marker as a default value of a
|
||||
function argument. When you call this function, framework assembles and injects
|
||||
the dependency.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -144,7 +172,7 @@ framework:
|
|||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
timeout=config.timeout,
|
||||
)
|
||||
|
||||
service = providers.Factory(
|
||||
|
@ -153,96 +181,95 @@ framework:
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
container.config.api_key.from_env("API_KEY", required=True)
|
||||
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
service = container.service()
|
||||
main() # <-- dependency is injected automatically
|
||||
|
||||
Retrieving of the ``Service`` instance now is done like this::
|
||||
with container.api_client.override(mock.Mock()):
|
||||
main() # <-- overridden dependency is injected automatically
|
||||
|
||||
service = container.service()
|
||||
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically.
|
||||
|
||||
Objects assembling is consolidated in the container. When you need to make a change you do it in
|
||||
one place.
|
||||
|
||||
When doing a testing you call the ``container.api_client.override()`` to replace the real API
|
||||
client with a mock:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from unittest import mock
|
||||
|
||||
|
||||
with container.api_client.override(mock.Mock()):
|
||||
service = container.service()
|
||||
When you do testing, you call the ``container.api_client.override()`` method to replace the real API
|
||||
client with a mock. When you call ``main()``, the mock is injected.
|
||||
|
||||
You can override any provider with another provider.
|
||||
|
||||
It also helps you in configuring project for the different environments: replace an API client
|
||||
It also helps you in a re-configuring project for different environments: replace an API client
|
||||
with a stub on the dev or stage.
|
||||
|
||||
Objects assembling is consolidated in a container. Dependency injections are defined explicitly.
|
||||
This makes it easier to understand and change how an application works.
|
||||
|
||||
Testing, Monkey-patching and dependency injection
|
||||
-------------------------------------------------
|
||||
|
||||
The testability benefit is opposed to a monkey-patching.
|
||||
The testability benefit is opposed to monkey-patching.
|
||||
|
||||
In Python you can monkey-patch
|
||||
anything, anytime. The problem with a monkey-patching is that it's too fragile. The reason is that
|
||||
when you monkey-patch you do something that wasn't intended to be done. You monkey-patch the
|
||||
implementation details. When implementation changes the monkey-patching is broken.
|
||||
In Python, you can monkey-patch anything, anytime. The problem with monkey-patching is
|
||||
that it's too fragile. The cause of it is that when you monkey-patch you do something that
|
||||
wasn't intended to be done. You monkey-patch the implementation details. When implementation
|
||||
changes the monkey-patching is broken.
|
||||
|
||||
With a dependency injection you patch the interface, not an implementation. This is a way more
|
||||
With dependency injection, you patch the interface, not an implementation. This is a way more
|
||||
stable approach.
|
||||
|
||||
Also monkey-patching is a way too dirty to be used outside of the testing code for
|
||||
reconfiguring the project for the different environments.
|
||||
Also, monkey-patching is way too dirty to be used outside of the testing code for
|
||||
re-configuring the project for the different environments.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
Dependency injection brings you 3 advantages:
|
||||
Dependency injection provides you with three advantages:
|
||||
|
||||
- **Flexibility**. The components are loosely coupled. You can easily extend or change a
|
||||
functionality of the system by combining the components different way. You even can do it on
|
||||
- **Flexibility**. The components are loosely coupled. You can easily extend or change the
|
||||
functionality of a system by combining the components in a different way. You even can do it on
|
||||
the fly.
|
||||
- **Testability**. Testing is easy because you can easily inject mocks instead of real objects
|
||||
- **Testability**. Testing is easier because you can easily inject mocks instead of real objects
|
||||
that use API or database, etc.
|
||||
- **Clearness and maintainability**. Dependency injection helps you reveal the dependencies.
|
||||
Implicit becomes explicit. And "Explicit is better than implicit" (PEP 20 - The Zen of Python).
|
||||
You have all the components and dependencies defined explicitly in the container. This
|
||||
provides an overview and control on the application structure. It is easy to understand and
|
||||
You have all the components and dependencies defined explicitly in a container. This
|
||||
provides an overview and control of the application structure. It is easier to understand and
|
||||
change it.
|
||||
|
||||
Is it worth to use a dependency injection in Python?
|
||||
Is it worth applying dependency injection in Python?
|
||||
|
||||
It depends on what you build. The advantages above are not too important if you use Python as a
|
||||
scripting language. The picture is different when you use Python to create an application. The
|
||||
larger the application the more significant is the benefit.
|
||||
larger the application the more significant the benefits.
|
||||
|
||||
Is it worth to use a framework for the dependency injection?
|
||||
Is it worth using a framework for applying dependency injection?
|
||||
|
||||
The complexity of the dependency injection pattern implementation in Python is
|
||||
lower than in the other languages but it's still in place. It doesn't mean you have to use a
|
||||
lower than in other languages but it's still in place. It doesn't mean you have to use a
|
||||
framework but using a framework is beneficial because the framework is:
|
||||
|
||||
- Already implemented
|
||||
- Tested on all platforms and versions of Python
|
||||
- Documented
|
||||
- Supported
|
||||
- Known to the other engineers
|
||||
- Other engineers are familiar with it
|
||||
|
||||
Few advices at last:
|
||||
An advice at last:
|
||||
|
||||
- **Give it a try**. Dependency injection is counter-intuitive. Our nature is that
|
||||
when we need something the first thought that comes to our mind is to go and get it. Dependency
|
||||
injection is just like "Wait, I need to state a need instead of getting something right now".
|
||||
injection is just like "Wait, I need to state a need instead of getting something right away".
|
||||
It's like a little investment that will pay-off later. The advice is to just give it a try for
|
||||
two weeks. This time will be enough for getting your own impression. If you don't like it you
|
||||
won't lose too much.
|
||||
- **Common sense first**. Use a common sense when apply dependency injection. It is a good
|
||||
principle, but not a silver bullet. If you do it too much you will reveal too much of the
|
||||
- **Common sense first**. Use common sense when applying dependency injection. It is a good
|
||||
principle, but not a silver bullet. If you do it too much you will reveal too many of the
|
||||
implementation details. Experience comes with practice and time.
|
||||
|
||||
What's next?
|
||||
|
@ -254,6 +281,15 @@ Choose one of the following as a next step:
|
|||
- :ref:`application-single-container`
|
||||
- :ref:`application-multiple-containers`
|
||||
- :ref:`decoupled-packages`
|
||||
- :ref:`boto3-example`
|
||||
- :ref:`django-example`
|
||||
- :ref:`flask-example`
|
||||
- :ref:`flask-blueprints-example`
|
||||
- :ref:`aiohttp-example`
|
||||
- :ref:`sanic-example`
|
||||
- :ref:`fastapi-example`
|
||||
- :ref:`fastapi-redis-example`
|
||||
- :ref:`fastapi-sqlalchemy-example`
|
||||
- Pass the tutorials:
|
||||
- :ref:`flask-tutorial`
|
||||
- :ref:`aiohttp-tutorial`
|
||||
|
@ -261,17 +297,19 @@ Choose one of the following as a next step:
|
|||
- :ref:`cli-tutorial`
|
||||
- Know more about the ``Dependency Injector`` :ref:`key-features`
|
||||
- Know more about the :ref:`providers`
|
||||
- Know more about the :ref:`wiring`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
Useful links
|
||||
------------
|
||||
|
||||
There are some useful links related to dependency injection design pattern
|
||||
that could be used for further reading:
|
||||
A few useful links related to a dependency injection design pattern for further reading:
|
||||
|
||||
+ https://en.wikipedia.org/wiki/Dependency_injection
|
||||
+ https://martinfowler.com/articles/injection.html
|
||||
+ https://github.com/ets-labs/python-dependency-injector
|
||||
+ https://pypi.org/project/dependency-injector/
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -7,8 +7,8 @@ Introduction
|
|||
overview of the dependency injection, inversion of
|
||||
control and Dependency Injector framework.
|
||||
|
||||
Current section of the documentation provides an overview of the
|
||||
dependency injection, inversion of control and the ``Dependency Injector`` framework.
|
||||
The current section of the documentation provides an overview of the
|
||||
dependency injection, inversion of control, and the ``Dependency Injector`` framework.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
|
|
@ -1,41 +1,42 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
*Dependency Injector* framework is distributed by PyPi_.
|
||||
|
||||
Latest stable version (and all previous versions) of *Dependency Injector*
|
||||
framework can be installed from PyPi_:
|
||||
``Dependency Injector`` is available on `PyPI <https://pypi.org/project/dependency-injector/>`_.
|
||||
To install the latest version you can use ``pip``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install dependency-injector
|
||||
|
||||
.. note::
|
||||
Some components of *Dependency Injector* are implemented as C extension types.
|
||||
*Dependency Injector* is distributed as an archive with a source code, so
|
||||
C compiler and Python header files are required for the installation.
|
||||
Some modules of the ``Dependency Injector`` are implemented as C extensions.
|
||||
``Dependency Injector`` is distributed as a pre-compiled wheels. Wheels are
|
||||
available for all supported Python versions on Linux, Windows, and MacOS.
|
||||
Linux distribution uses `manylinux <https://github.com/pypa/manylinux>`_.
|
||||
|
||||
Sources can be cloned from GitHub_:
|
||||
If there is no appropriate wheel for your environment (Python version and OS)
|
||||
installer will compile the package from sources on your machine. You'll need
|
||||
a C compiler and Python header files.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/ets-labs/python-dependency-injector.git
|
||||
|
||||
Also all *Dependency Injector* releases can be downloaded from
|
||||
`GitHub releases page`_.
|
||||
|
||||
Verification of currently installed version could be done using
|
||||
:py:obj:`dependency_injector.VERSION` constant:
|
||||
To verify the installed version:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
>>> import dependency_injector
|
||||
>>> dependency_injector.__version__
|
||||
'3.43.0'
|
||||
'4.39.0'
|
||||
|
||||
.. _PyPi: https://pypi.org/project/dependency-injector/
|
||||
.. _GitHub: https://github.com/ets-labs/python-dependency-injector
|
||||
.. _GitHub releases page: https://github.com/ets-labs/python-dependency-injector/releases
|
||||
.. note::
|
||||
When adding ``Dependency Injector`` to ``pyproject.toml`` or ``requirements.txt``
|
||||
don't forget to pin the version to the current major:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
dependency-injector>=4.0,<5.0
|
||||
|
||||
*The next major version can be incompatible.*
|
||||
|
||||
All releases are available on the `PyPI release history page <https://pypi.org/project/dependency-injector/#history>`_.
|
||||
Each release has an appropriate tag. The tags are available on the
|
||||
`GitHub releases page <https://github.com/ets-labs/python-dependency-injector/releases>`_.
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -11,28 +11,33 @@ Key features
|
|||
Key features of the ``Dependency Injector``:
|
||||
|
||||
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||
``List``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your
|
||||
objects. See :ref:`providers`.
|
||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
|
||||
that help assemble your objects. See :ref:`providers`.
|
||||
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
|
||||
and configuring dev / stage environment to replace API clients with stubs etc. See
|
||||
and configuring dev/stage environment to replace API clients with stubs etc. See
|
||||
:ref:`provider-overriding`.
|
||||
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables
|
||||
and dictionaries. See :ref:`configuration-provider`.
|
||||
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
|
||||
environment variables, and dictionaries. See :ref:`configuration-provider`.
|
||||
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
|
||||
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
|
||||
See :ref:`resource-provider`.
|
||||
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
|
||||
- **Performance**. Fast. Written in ``Cython``.
|
||||
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
|
||||
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
|
||||
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
|
||||
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
||||
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
|
||||
- **Performance**. Fast. Written in ``Cython``.
|
||||
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
|
||||
|
||||
The framework stands on two principles:
|
||||
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
|
||||
|
||||
- **Explicit is better than implicit (PEP20)**.
|
||||
- **Do not do any magic to your code**.
|
||||
.. code-block:: text
|
||||
|
||||
How is that different from the other frameworks?
|
||||
Explicit is better than implicit
|
||||
|
||||
- **No autowiring.** The framework does NOT do any autowiring / autoresolving of the dependencies. You need to specify everything explicitly. Because *"Explicit is better than implicit" (PEP20)*.
|
||||
- **Does not pollute your code.** Your application does NOT know and does NOT depend on the framework. No ``@inject`` decorators, annotations, patching or any other magic tricks.
|
||||
You need to specify how to assemble and where to inject the dependencies explicitly.
|
||||
|
||||
The power of the framework is in a simplicity. ``Dependency Injector`` is a simple tool for the powerful concept.
|
||||
The power of the framework is in its simplicity.
|
||||
``Dependency Injector`` is a simple tool for the powerful concept.
|
||||
|
||||
.. disqus::
|
||||
|
|
72
docs/providers/aggregate.rst
Normal file
|
@ -0,0 +1,72 @@
|
|||
.. _aggregate-provider:
|
||||
|
||||
Aggregate provider
|
||||
==================
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
|
||||
Aggregate,Polymorphism,Environment Variable,Flexibility
|
||||
:description: Aggregate provider aggregates other providers.
|
||||
This page demonstrates how to implement the polymorphism and increase the
|
||||
flexibility of your application using the Aggregate provider.
|
||||
|
||||
:py:class:`Aggregate` provider aggregates a group of other providers.
|
||||
|
||||
.. currentmodule:: dependency_injector.providers
|
||||
|
||||
.. literalinclude:: ../../examples/providers/aggregate.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 24-27
|
||||
|
||||
Each provider in the ``Aggregate`` is associated with a key. You can call aggregated providers by providing
|
||||
their key as a first argument. All positional and keyword arguments following the key will be forwarded to
|
||||
the called provider:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
yaml_reader = container.config_readers("yaml", "./config.yml", foo=...)
|
||||
|
||||
You can also retrieve an aggregated provider by providing its key as an attribute name:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
yaml_reader = container.config_readers.yaml("./config.yml", foo=...)
|
||||
|
||||
To retrieve a dictionary of aggregated providers, use ``.providers`` attribute:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config_readers.providers == {
|
||||
"yaml": <YAML provider>,
|
||||
"json": <JSON provider>,
|
||||
}
|
||||
|
||||
.. note::
|
||||
You can not override the ``Aggregate`` provider.
|
||||
|
||||
.. note::
|
||||
When you inject the ``Aggregate`` provider, it is passed "as is".
|
||||
|
||||
To use non-string keys or string keys with ``.`` and ``-``, provide a dictionary as a positional argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
aggregate = providers.Aggregate({
|
||||
SomeClass: providers.Factory(...),
|
||||
"key.with.periods": providers.Factory(...),
|
||||
"key-with-dashes": providers.Factory(...),
|
||||
})
|
||||
|
||||
.. seealso::
|
||||
:ref:`selector-provider` to make injections based on a configuration value, environment variable, or a result of a callable.
|
||||
|
||||
``Aggregate`` provider is different from the :ref:`selector-provider`. ``Aggregate`` provider doesn't select which provider
|
||||
to inject and doesn't have a selector. It is a group of providers and is always injected "as is". The rest of the interface
|
||||
of both providers is similar.
|
||||
|
||||
.. note::
|
||||
``Aggregate`` provider is a successor of :ref:`factory-aggregate-provider` provider. ``Aggregate`` provider doesn't have
|
||||
a restriction on the provider type, while ``FactoryAggregate`` aggregates only ``Factory`` providers.
|
||||
|
||||
.. disqus::
|
110
docs/providers/async.rst
Normal file
|
@ -0,0 +1,110 @@
|
|||
.. _async-injections:
|
||||
|
||||
Asynchronous injections
|
||||
=======================
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Providers,Async,Injections,Asynchronous,Await,
|
||||
Asyncio
|
||||
:description: Dependency Injector providers support asynchronous injections. This page
|
||||
demonstrates how make asynchronous dependency injections in Python.
|
||||
|
||||
Providers support asynchronous injections.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/async.py
|
||||
:language: python
|
||||
:emphasize-lines: 26-29
|
||||
:lines: 3-
|
||||
|
||||
If provider has any awaitable injections it switches into async mode. In async mode provider always returns awaitable.
|
||||
This causes a cascade effect:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
provider1() <── Async mode enabled <──┐
|
||||
│ │
|
||||
├──> provider2() │
|
||||
│ │
|
||||
├──> provider3() <── Async mode enabled <──┤
|
||||
│ │ │
|
||||
│ └──> provider4() <── Async provider ───────┘
|
||||
│
|
||||
└──> provider5()
|
||||
│
|
||||
└──> provider6()
|
||||
|
||||
In async mode provider prepares injections asynchronously.
|
||||
|
||||
If provider has multiple awaitable dependencies, it will run them concurrently. Provider will wait until all
|
||||
dependencies are ready and inject them afterwards.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
provider1()
|
||||
│
|
||||
├──> provider2() <── Async mode enabled
|
||||
│
|
||||
├──> provider3() <── Async mode enabled
|
||||
│
|
||||
└──> provider4() <── Async mode enabled
|
||||
|
||||
Here is what provider will do for the previous example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
injections = await asyncio.gather(
|
||||
provider2(),
|
||||
provider3(),
|
||||
provider4(),
|
||||
)
|
||||
await provider1(*injections)
|
||||
|
||||
Overriding behaviour
|
||||
--------------------
|
||||
|
||||
In async mode provider always returns awaitable. It applies to the overriding too. If provider in async mode is
|
||||
overridden by a provider that doesn't return awaitable result, the result will be wrapped into awaitable.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/async_overriding.py
|
||||
:language: python
|
||||
:emphasize-lines: 19-24
|
||||
:lines: 3-
|
||||
|
||||
Async mode mechanics and API
|
||||
----------------------------
|
||||
|
||||
By default provider's async mode is undefined.
|
||||
|
||||
When provider async mode is undefined, provider will automatically select the mode during the next call.
|
||||
If the result is awaitable, provider will enable async mode, if not - disable it.
|
||||
|
||||
If provider async mode is enabled, provider always returns awaitable. If the result is not awaitable,
|
||||
provider wraps it into awaitable explicitly. You can safely ``await`` provider in async mode.
|
||||
|
||||
If provider async mode is disabled, provider behaves the regular way. It doesn't do async injections
|
||||
preparation or non-awaitables to awaitables conversion.
|
||||
|
||||
Once provider async mode is enabled or disabled, provider will stay in this state. No automatic switching
|
||||
will be done.
|
||||
|
||||
.. image:: images/async_mode.png
|
||||
|
||||
You can also use following methods to change provider's async mode manually:
|
||||
|
||||
- ``Provider.enable_async_mode()``
|
||||
- ``Provider.disable_async_mode()``
|
||||
- ``Provider.reset_async_mode()``
|
||||
|
||||
To check the state of provider's async mode use:
|
||||
|
||||
- ``Provider.is_async_mode_enabled()``
|
||||
- ``Provider.is_async_mode_disabled()``
|
||||
- ``Provider.is_async_mode_undefined()``
|
||||
|
||||
See also:
|
||||
|
||||
- Wiring :ref:`async-injections-wiring`
|
||||
- Resource provider :ref:`resource-async-initializers`
|
||||
- :ref:`fastapi-redis-example`
|
||||
|
||||
.. disqus::
|
|
@ -5,10 +5,14 @@ Configuration provider
|
|||
|
||||
.. meta::
|
||||
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
|
||||
Option,Ini,Json,Yaml,Dict,Environment Variable,Load,Read,Get
|
||||
Option,Ini,Json,Yaml,Pydantic,Dict,Environment Variable Interpolation,
|
||||
Environment Variable Substitution,Environment Variable in Config,
|
||||
Environment Variable in YAML file,Environment Variable in INI file,Default,Load,Read
|
||||
:description: Configuration provides configuration options to the other providers. This page
|
||||
demonstrates how to use Configuration provider to inject the dependencies, load
|
||||
a configuration from an ini or yaml file, dictionary or an environment variable.
|
||||
a configuration from an ini or yaml file, a dictionary, an environment variable,
|
||||
or a pydantic settings object. This page also describes how to substitute (interpolate)
|
||||
environment variables in YAML and INI configuration files.
|
||||
|
||||
.. currentmodule:: dependency_injector.providers
|
||||
|
||||
|
@ -21,6 +25,10 @@ Configuration provider
|
|||
|
||||
It implements the principle "use first, define later".
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:backlinks: none
|
||||
|
||||
Loading from an INI file
|
||||
------------------------
|
||||
|
||||
|
@ -37,9 +45,31 @@ where ``examples/providers/configuration/config.ini`` is:
|
|||
.. literalinclude:: ../../examples/providers/configuration/config.ini
|
||||
:language: ini
|
||||
|
||||
:py:meth:`Configuration.from_ini` method supports environment variables interpolation. Use
|
||||
``${ENV_NAME}`` format in the configuration file to substitute value of the environment
|
||||
variable ``ENV_NAME``.
|
||||
Alternatively, you can provide a path to the INI file over the configuration provider argument. In that case,
|
||||
the container will call ``config.from_ini()`` automatically:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(ini_files=["./config.ini"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # Config is loaded from ./config.ini
|
||||
|
||||
|
||||
:py:meth:`Configuration.from_ini` method supports environment variables interpolation.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[section]
|
||||
option1 = ${ENV_VAR}
|
||||
option2 = ${ENV_VAR}/path
|
||||
option3 = ${ENV_VAR:default}
|
||||
|
||||
See also: :ref:`configuration-envs-interpolation`.
|
||||
|
||||
Loading from a YAML file
|
||||
------------------------
|
||||
|
@ -57,9 +87,40 @@ where ``examples/providers/configuration/config.yml`` is:
|
|||
.. literalinclude:: ../../examples/providers/configuration/config.yml
|
||||
:language: ini
|
||||
|
||||
:py:meth:`Configuration.from_yaml` method supports environment variables interpolation. Use
|
||||
``${ENV_NAME}`` format in the configuration file to substitute value of the environment
|
||||
variable ``ENV_NAME``.
|
||||
Alternatively, you can provide a path to the YAML file over the configuration provider argument. In that case,
|
||||
the container will call ``config.from_yaml()`` automatically:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(yaml_files=["./config.yml"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # Config is loaded from ./config.yml
|
||||
|
||||
:py:meth:`Configuration.from_yaml` method supports environment variables interpolation.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
section:
|
||||
option1: ${ENV_VAR}
|
||||
option2: ${ENV_VAR}/path
|
||||
option3: ${ENV_VAR:default}
|
||||
|
||||
See also: :ref:`configuration-envs-interpolation`.
|
||||
|
||||
:py:meth:`Configuration.from_yaml` method uses custom version of ``yaml.SafeLoader``.
|
||||
To use another loader use ``loader`` argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
container.config.from_yaml("config.yml", loader=yaml.UnsafeLoader)
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -75,6 +136,102 @@ variable ``ENV_NAME``.
|
|||
|
||||
*Don't forget to mirror the changes in the requirements file.*
|
||||
|
||||
Loading from a JSON file
|
||||
------------------------
|
||||
|
||||
``Configuration`` provider can load configuration from a ``json`` file using the
|
||||
:py:meth:`Configuration.from_json` method:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_json.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 12
|
||||
|
||||
where ``examples/providers/configuration/config.json`` is:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/config.json
|
||||
:language: json
|
||||
|
||||
Alternatively, you can provide a path to a json file over the configuration provider argument. In that case,
|
||||
the container will call ``config.from_json()`` automatically:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(json_files=["./config.json"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # Config is loaded from ./config.json
|
||||
|
||||
:py:meth:`Configuration.from_json` method supports environment variables interpolation.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"section": {
|
||||
"option1": "${ENV_VAR}",
|
||||
"option2": "${ENV_VAR}/path",
|
||||
"option3": "${ENV_VAR:default}"
|
||||
}
|
||||
}
|
||||
|
||||
See also: :ref:`configuration-envs-interpolation`.
|
||||
|
||||
Loading from a Pydantic settings
|
||||
--------------------------------
|
||||
|
||||
``Configuration`` provider can load configuration from a ``pydantic_settings.BaseSettings`` object using the
|
||||
:py:meth:`Configuration.from_pydantic` method:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 32
|
||||
|
||||
To get the data from pydantic settings ``Configuration`` provider calls its ``model_dump()`` method.
|
||||
If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_pydantic(Settings(), exclude={"optional"})
|
||||
|
||||
Alternatively, you can provide a ``pydantic_settings.BaseSettings`` object over the configuration provider argument. In that case,
|
||||
the container will call ``config.from_pydantic()`` automatically:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(pydantic_settings=[Settings()])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # Config is loaded from Settings()
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
``Dependency Injector`` doesn't install ``pydantic-settings`` by default.
|
||||
|
||||
You can install the ``Dependency Injector`` with an extra dependency::
|
||||
|
||||
pip install dependency-injector[pydantic2]
|
||||
|
||||
or install ``pydantic-settings`` directly::
|
||||
|
||||
pip install pydantic-settings
|
||||
|
||||
*Don't forget to mirror the changes in the requirements file.*
|
||||
|
||||
.. note::
|
||||
|
||||
For backward-compatibility, Pydantic v1 is still supported.
|
||||
Passing ``pydantic.BaseSettings`` instances will work just as fine as ``pydantic_settings.BaseSettings``.
|
||||
|
||||
Loading from a dictionary
|
||||
-------------------------
|
||||
|
||||
|
@ -97,6 +254,35 @@ Loading from an environment variable
|
|||
:lines: 3-
|
||||
:emphasize-lines: 18-20
|
||||
|
||||
You can use ``as_`` argument for the type casting of an environment variable value:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 2,6,10
|
||||
|
||||
# API_KEY=secret
|
||||
container.config.api_key.from_env("API_KEY", as_=str, required=True)
|
||||
assert container.config.api_key() == "secret"
|
||||
|
||||
# SAMPLING_RATIO=0.5
|
||||
container.config.sampling.from_env("SAMPLING_RATIO", as_=float, required=True)
|
||||
assert container.config.sampling() == 0.5
|
||||
|
||||
# TIMEOUT undefined, default is used
|
||||
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
|
||||
assert container.config.timeout() == 5
|
||||
|
||||
|
||||
Loading a value
|
||||
---------------
|
||||
|
||||
``Configuration`` provider can load configuration value using the
|
||||
:py:meth:`Configuration.from_value` method:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_value.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 14-15
|
||||
|
||||
Loading from the multiple sources
|
||||
---------------------------------
|
||||
|
||||
|
@ -113,6 +299,123 @@ where ``examples/providers/configuration/config.local.yml`` is:
|
|||
.. literalinclude:: ../../examples/providers/configuration/config.local.yml
|
||||
:language: ini
|
||||
|
||||
.. _configuration-envs-interpolation:
|
||||
|
||||
Using environment variables in configuration files
|
||||
--------------------------------------------------
|
||||
|
||||
``Configuration`` provider supports environment variables interpolation in configuration files.
|
||||
Use ``${ENV_NAME}`` in the configuration file to substitute value from environment
|
||||
variable ``ENV_NAME``.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
section:
|
||||
option: ${ENV_NAME}
|
||||
|
||||
You can also specify a default value using ``${ENV_NAME:default}`` format. If environment
|
||||
variable ``ENV_NAME`` is undefined, configuration provider will substitute value ``default``.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[section]
|
||||
option = ${ENV_NAME:default}
|
||||
|
||||
If you'd like to specify a default value for environment variable inside of the application you can use
|
||||
``os.environ.setdefault()``.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_env_interpolation_os_default.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 12
|
||||
|
||||
If environment variable is undefined and doesn't have a default, ``Configuration`` provider
|
||||
will replace it with an empty value. This is a default behavior. To raise an error on
|
||||
undefined environment variable that doesn't have a default value, pass argument
|
||||
``envs_required=True`` to a configuration reading method:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_yaml("config.yml", envs_required=True)
|
||||
|
||||
See also: :ref:`configuration-strict-mode`.
|
||||
|
||||
.. note::
|
||||
``Configuration`` provider makes environment variables interpolation before parsing. This preserves
|
||||
original parser behavior. For instance, undefined environment variable in YAML configuration file
|
||||
will be replaced with an empty value and then YAML parser will load the file.
|
||||
|
||||
Original configuration file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
section:
|
||||
option: ${ENV_NAME}
|
||||
|
||||
Configuration file after interpolation where ``ENV_NAME`` is undefined:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
section:
|
||||
option:
|
||||
|
||||
Configuration provider after parsing interpolated YAML file contains ``None`` in
|
||||
option ``section.option``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
assert container.config.section.option() is None
|
||||
|
||||
If you want to disable environment variables interpolation, pass ``envs_required=None``:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: templates.yml
|
||||
|
||||
template_string: 'Hello, ${name}!'
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> container.config.from_yaml("templates.yml", envs_required=None)
|
||||
>>> container.config.template_string()
|
||||
'Hello, ${name}!'
|
||||
|
||||
Mandatory and optional sources
|
||||
------------------------------
|
||||
|
||||
By default, methods ``.from_yaml()`` and ``.from_ini()`` ignore errors if configuration file does not exist.
|
||||
You can use this to specify optional configuration files.
|
||||
|
||||
If configuration file is mandatory, use ``required`` argument. Configuration provider will raise an error
|
||||
if required file does not exist.
|
||||
|
||||
You can also use ``required`` argument when loading configuration from dictionaries and environment variables.
|
||||
|
||||
Mandatory YAML file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_yaml("config.yaml", required=True)
|
||||
|
||||
Mandatory INI file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_ini("config.ini", required=True)
|
||||
|
||||
Mandatory dictionary:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_dict(config_dict, required=True)
|
||||
|
||||
Mandatory environment variable:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.api_key.from_env("API_KEY", required=True)
|
||||
|
||||
See also: :ref:`configuration-strict-mode`.
|
||||
|
||||
Specifying the value type
|
||||
-------------------------
|
||||
|
||||
|
@ -143,6 +446,119 @@ With the ``.as_(callback, *args, **kwargs)`` you can specify a function that wil
|
|||
before the injection. The value from the config will be passed as a first argument. The returned
|
||||
value will be injected. Parameters ``*args`` and ``**kwargs`` are handled as any other injections.
|
||||
|
||||
.. _configuration-strict-mode:
|
||||
|
||||
Strict mode and required options
|
||||
--------------------------------
|
||||
|
||||
You can use configuration provider in strict mode. In strict mode configuration provider raises an error
|
||||
on access to any undefined option.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_strict.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 12
|
||||
|
||||
Methods ``.from_*()`` in strict mode raise an exception if configuration file does not exist or
|
||||
configuration data is undefined:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 10,15,20,25,30
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(strict=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
try:
|
||||
container.config.from_yaml("does-not_exist.yml") # raise exception
|
||||
except FileNotFoundError:
|
||||
...
|
||||
|
||||
try:
|
||||
container.config.from_ini("does-not_exist.ini") # raise exception
|
||||
except FileNotFoundError:
|
||||
...
|
||||
|
||||
try:
|
||||
container.config.from_pydantic(EmptySettings()) # raise exception
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
try:
|
||||
container.config.from_env("UNDEFINED_ENV_VAR") # raise exception
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
try:
|
||||
container.config.from_dict({}) # raise exception
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
Environment variables interpolation in strict mode raises an exception when encounters
|
||||
an undefined environment variable without a default value.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
section:
|
||||
option: ${UNDEFINED}
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
try:
|
||||
container.config.from_yaml("undefined_env.yml") # raise exception
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
You can override ``.from_*()`` methods behaviour in strict mode using ``required`` argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(strict=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
container.config.from_yaml("config.yml")
|
||||
container.config.from_yaml("config.local.yml", required=False)
|
||||
|
||||
You can also use ``.required()`` option modifier when making an injection. It does not require to switch
|
||||
configuration provider to strict mode.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_required.py
|
||||
:language: python
|
||||
:lines: 11-20
|
||||
:emphasize-lines: 8-9
|
||||
|
||||
.. note::
|
||||
|
||||
Modifier ``.required()`` should be specified before type modifier ``.as_*()``.
|
||||
|
||||
Aliases
|
||||
-------
|
||||
|
||||
You can use ``Configuration`` provider with a context manager to create aliases.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_alias.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 14,22
|
||||
|
||||
.. note::
|
||||
|
||||
Library ``environs`` is a 3rd party library. You need to install it
|
||||
separately::
|
||||
|
||||
pip install environs
|
||||
|
||||
Documentation is available on GitHub: https://github.com/sloria/environs
|
||||
|
||||
Injecting invariants
|
||||
--------------------
|
||||
|
||||
|
|
|
@ -16,12 +16,16 @@ To create a custom provider you need to follow these rules:
|
|||
1. New provider class should inherit :py:class:`Provider`.
|
||||
2. You need to implement the ``Provider._provide()`` method.
|
||||
3. You need to implement the ``Provider.__deepcopy__()`` method. It should return an
|
||||
equivalent copy of a provider. All providers must be copied with a ``deepcopy()`` function
|
||||
from the ``providers`` module. After the a new provider object is created use
|
||||
``Provider._copy_overriding()`` method to copy all overriding providers. See the example
|
||||
below.
|
||||
4. If the new provider has a ``__init__()`` method, it should call the parent
|
||||
equivalent copy of a provider. All providers must be copied with the ``deepcopy()`` function
|
||||
from the ``providers`` module. It's essential to pass ``memo`` into ``deepcopy`` in order to keep
|
||||
the preconfigured ``args`` and ``kwargs`` of stored providers. After the a new provider object
|
||||
is created, use ``Provider._copy_overriding()`` method to copy all overriding providers. See the
|
||||
example below.
|
||||
4. If new provider has a ``__init__()`` method, it should call the parent
|
||||
``Provider.__init__()``.
|
||||
5. If new provider stores any other providers, these providers should be listed in
|
||||
``.related`` property. Property ``.related`` also should yield providers from parent
|
||||
``.related`` property.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/custom_factory.py
|
||||
:language: python
|
||||
|
@ -30,7 +34,7 @@ To create a custom provider you need to follow these rules:
|
|||
.. note::
|
||||
1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or
|
||||
inheriting a ``Provider`` and use ``Factory`` internally - the last is better.
|
||||
2. When create a new provider follow the ``Factory``-like injections style. Consistency matters.
|
||||
2. When creating a new provider follow the ``Factory``-like injections style. Consistency matters.
|
||||
3. Use the ``__slots__`` attribute to make sure nothing could be attached to your provider. You
|
||||
will also save some memory.
|
||||
|
||||
|
|
|
@ -1,21 +1,38 @@
|
|||
.. _dependency-provider:
|
||||
|
||||
Dependency provider
|
||||
===================
|
||||
|
||||
.. currentmodule:: dependency_injector.providers
|
||||
|
||||
:py:class:`Dependency` provider is a placeholder for the dependency of the specified type.
|
||||
:py:class:`Dependency` provider is a placeholder for a dependency of a certain type.
|
||||
|
||||
The first argument of the ``Dependency`` provider specifies a type of the dependency. It is
|
||||
called ``instance_of``. ``Dependency`` provider controls the type of the returned object to be an
|
||||
instance of the ``instance_of`` type.
|
||||
|
||||
The ``Dependency`` provider must be overridden before usage. It can be overridden by any type of
|
||||
the provider. The only rule is that overriding provider must return an instance of ``instance_of``
|
||||
dependency type.
|
||||
To specify a type of the dependency use ``instance_of`` argument: ``Dependency(instance_of=SomeClass)``.
|
||||
Dependency provider will control that returned object is an instance of ``instance_of`` type.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/dependency.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 26
|
||||
:emphasize-lines: 26,35-36
|
||||
|
||||
To provide a dependency you need to override the ``Dependency`` provider. You can call
|
||||
provider ``.override()`` method or provide an overriding provider when creating a container.
|
||||
See :ref:`provider-overriding`. If you don't provide a dependency, ``Dependency`` provider
|
||||
will raise an error:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/dependency_undefined_error.py
|
||||
:language: python
|
||||
:lines: 18-
|
||||
|
||||
You also can provide a default for the dependency. To provide a default use ``default`` argument:
|
||||
``Dependency(..., default=...)``. Default can be a value or a provider. If default is not a provider,
|
||||
dependency provider will wrap it into the ``Object`` provider.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/dependency_default.py
|
||||
:language: python
|
||||
:lines: 16-23
|
||||
:emphasize-lines: 3
|
||||
|
||||
See also: :ref:`check-container-dependencies`.
|
||||
|
||||
.. disqus::
|
||||
|
|
37
docs/providers/dict.rst
Normal file
|
@ -0,0 +1,37 @@
|
|||
Dict provider
|
||||
=============
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Dict,Injection
|
||||
:description: Dict provider helps to inject a dictionary of the dependencies. This page demonstrates
|
||||
how to use Dict provider.
|
||||
|
||||
.. currentmodule:: dependency_injector.providers
|
||||
|
||||
:py:class:`Dict` provider provides a dictionary of values.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/dict.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 21-24
|
||||
|
||||
``Dict`` provider handles keyword arguments the same way as a :ref:`factory-provider`.
|
||||
|
||||
To use non-string keys or keys with ``.`` and ``-`` provide a dictionary as a positional argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
providers.Dict({
|
||||
SomeClass: providers.Factory(...),
|
||||
"key.with.periods": providers.Factory(...),
|
||||
"key-with-dashes": providers.Factory(...),
|
||||
})
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/dict_non_string_keys.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 40-43
|
||||
|
||||
.. disqus::
|
|
@ -40,6 +40,14 @@ injected following these rules:
|
|||
:language: python
|
||||
:lines: 3-
|
||||
|
||||
``Factory`` provider can inject attributes. Use ``.add_attributes()`` method to specify
|
||||
attribute injections.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/factory_attribute_injections.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 18-18
|
||||
|
||||
Passing arguments to the underlying providers
|
||||
---------------------------------------------
|
||||
|
||||
|
@ -102,6 +110,45 @@ attribute of the provider that you're going to inject.
|
|||
|
||||
.. note:: Any provider has a ``.provider`` attribute.
|
||||
|
||||
.. _factory-string-imports:
|
||||
|
||||
String imports
|
||||
--------------
|
||||
|
||||
``Factory`` provider can handle string imports:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Factory("myapp.mypackage.mymodule.Service")
|
||||
|
||||
You can also make a relative import:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# in myapp/container.py
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Factory(".mypackage.mymodule.Service")
|
||||
|
||||
or import a member of the current module just specifying its name:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Service:
|
||||
...
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Factory("Service")
|
||||
|
||||
.. note::
|
||||
``Singleton``, ``Callable``, ``Resource``, and ``Coroutine`` providers handle string imports
|
||||
the same way as a ``Factory`` provider.
|
||||
|
||||
.. _factory-specialize-provided-type:
|
||||
|
||||
Specializing the provided type
|
||||
|
@ -137,16 +184,20 @@ provider with two peculiarities:
|
|||
:lines: 3-
|
||||
:emphasize-lines: 34
|
||||
|
||||
.. _factory-aggregate-provider:
|
||||
|
||||
Factory aggregate
|
||||
-----------------
|
||||
|
||||
:py:class:`FactoryAggregate` provider aggregates multiple factories. When you call the
|
||||
``FactoryAggregate`` it delegates the call to one of the factories.
|
||||
:py:class:`FactoryAggregate` provider aggregates multiple factories.
|
||||
|
||||
The aggregated factories are associated with the string names. When you call the
|
||||
``FactoryAggregate`` you have to provide one of the these names as a first argument.
|
||||
``FactoryAggregate`` looks for the factory with a matching name and delegates it the work. The
|
||||
rest of the arguments are passed to the delegated ``Factory``.
|
||||
.. seealso::
|
||||
:ref:`aggregate-provider` – it's a successor of ``FactoryAggregate`` provider that can aggregate
|
||||
any type of provider, not only ``Factory``.
|
||||
|
||||
The aggregated factories are associated with the string keys. When you call the
|
||||
``FactoryAggregate`` you have to provide one of the these keys as a first argument.
|
||||
``FactoryAggregate`` looks for the factory with a matching key and calls it with the rest of the arguments.
|
||||
|
||||
.. image:: images/factory_aggregate.png
|
||||
:width: 100%
|
||||
|
@ -157,12 +208,12 @@ rest of the arguments are passed to the delegated ``Factory``.
|
|||
:lines: 3-
|
||||
:emphasize-lines: 33-37,47
|
||||
|
||||
You can get a dictionary of the aggregated factories using the ``.factories`` attribute of the
|
||||
``FactoryAggregate``. To get a game factories dictionary from the previous example you can use
|
||||
``game_factory.factories`` attribute.
|
||||
You can get a dictionary of the aggregated providers using ``.providers`` attribute.
|
||||
To get a game provider dictionary from the previous example you can use
|
||||
``game_factory.providers`` attribute.
|
||||
|
||||
You can also access an aggregated factory as an attribute. To create the ``Chess`` object from the
|
||||
previous example you can do ``chess = game_factory.chess('John', 'Jane')``.
|
||||
previous example you can do ``chess = game_factory.chess("John", "Jane")``.
|
||||
|
||||
.. note::
|
||||
You can not override the ``FactoryAggregate`` provider.
|
||||
|
@ -170,4 +221,22 @@ previous example you can do ``chess = game_factory.chess('John', 'Jane')``.
|
|||
.. note::
|
||||
When you inject the ``FactoryAggregate`` provider it is passed "as is".
|
||||
|
||||
To use non-string keys or string keys with ``.`` and ``-``, you can provide a dictionary as a positional argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
providers.FactoryAggregate({
|
||||
SomeClass: providers.Factory(...),
|
||||
"key.with.periods": providers.Factory(...),
|
||||
"key-with-dashes": providers.Factory(...),
|
||||
})
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/factory_aggregate_non_string_keys.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 30-33,39-40
|
||||
|
||||
|
||||
.. disqus::
|
||||
|
|
BIN
docs/providers/images/async_mode.png
Normal file
After Width: | Height: | Size: 12 KiB |
|
@ -43,10 +43,15 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
|
|||
coroutine
|
||||
object
|
||||
list
|
||||
dict
|
||||
configuration
|
||||
resource
|
||||
aggregate
|
||||
selector
|
||||
dependency
|
||||
overriding
|
||||
provided_instance
|
||||
inject_self
|
||||
custom
|
||||
async
|
||||
typing_mypy
|
||||
|
|
20
docs/providers/inject_self.rst
Normal file
|
@ -0,0 +1,20 @@
|
|||
Injecting container "self"
|
||||
==========================
|
||||
|
||||
You can inject container "self" into container providers.
|
||||
|
||||
.. literalinclude:: ../../examples/containers/inject_self.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 20, 26
|
||||
|
||||
To inject container "self" you need to define ``Self`` provider. Container can have only one ``Self`` provider.
|
||||
|
||||
Usually you will use name ``__self__``.
|
||||
You can also use different name. When you use different name container will also reference
|
||||
defined ``Self`` provider in ``.__self__`` attribute.
|
||||
|
||||
Provider ``Self`` is not listed in container ``.providers`` attributes.
|
||||
|
||||
.. disqus::
|
||||
|
|
@ -35,24 +35,4 @@ You can do nested constructions:
|
|||
:emphasize-lines: 26-32
|
||||
:lines: 3-
|
||||
|
||||
The ``.provided`` attribute is available for the next providers:
|
||||
|
||||
- :py:class:`Factory` and its subclasses
|
||||
- :py:class:`Singleton` and its subclasses
|
||||
- :py:class:`Callable` and its subclasses
|
||||
- :py:class:`Object`
|
||||
- :py:class:`List`
|
||||
- :py:class:`Selector`
|
||||
- :py:class:`Dependency`
|
||||
|
||||
When you create a new provider subclass and want to implement the ``.provided`` attribute, you
|
||||
should use the :py:class:`ProvidedInstance` provider. Add the ``.provided`` property
|
||||
implementation to a new subclass:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@property
|
||||
def provided(self):
|
||||
return ProvidedInstance(self)
|
||||
|
||||
.. disqus::
|
||||
|
|
520
docs/providers/resource.rst
Normal file
|
@ -0,0 +1,520 @@
|
|||
.. _resource-provider:
|
||||
|
||||
Resource provider
|
||||
=================
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Resource,Injection,
|
||||
Logging,Event Loop,Thread Pool
|
||||
:description: Resource provider provides a component with initialization and shutdown. It works
|
||||
well for configuring logging, event loop, thread or process pool, etc.
|
||||
This page demonstrates how to use resource provider.
|
||||
|
||||
.. currentmodule:: dependency_injector.providers
|
||||
|
||||
:py:class:`Resource` provider provides a component with initialization and shutdown.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/resource.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
|
||||
Resource providers help to initialize and configure logging, event loop, thread or process pool, etc.
|
||||
|
||||
Resource provider is similar to ``Singleton``. Resource initialization happens only once.
|
||||
You can make injections and use provided instance the same way like you do with any other provider.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 12
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
thread_pool = providers.Resource(
|
||||
init_thread_pool,
|
||||
max_workers=config.max_workers,
|
||||
)
|
||||
|
||||
dispatcher = providers.Factory(
|
||||
TaskDispatcher,
|
||||
executor=thread_pool,
|
||||
)
|
||||
|
||||
Container has an interface to initialize and shutdown all resources at once:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container = Container()
|
||||
container.init_resources()
|
||||
container.shutdown_resources()
|
||||
|
||||
You can also initialize and shutdown resources one-by-one using ``init()`` and
|
||||
``shutdown()`` methods of the provider:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container = Container()
|
||||
container.thread_pool.init()
|
||||
container.thread_pool.shutdown()
|
||||
|
||||
When you call ``.shutdown()`` method on a resource provider, it will remove the reference to the initialized resource,
|
||||
if any, and switch to uninitialized state. Some of resource initializer types support specifying custom
|
||||
resource shutdown.
|
||||
|
||||
Resource provider supports 4 types of initializers:
|
||||
|
||||
- Function
|
||||
- Context Manager
|
||||
- Generator (legacy)
|
||||
- Subclass of ``resources.Resource`` (legacy)
|
||||
|
||||
Function initializer
|
||||
--------------------
|
||||
|
||||
Function is the most common way to specify resource initialization:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def init_resource(argument1=..., argument2=...):
|
||||
return SomeResource()
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
resource = providers.Resource(
|
||||
init_resource,
|
||||
argument1=...,
|
||||
argument2=...,
|
||||
)
|
||||
|
||||
Function initializer may not return a value. This often happens when
|
||||
you configure global resource:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import logging.config
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
configure_logging = providers.Resource(
|
||||
logging.config.fileConfig,
|
||||
fname="logging.ini",
|
||||
)
|
||||
|
||||
Function initializer does not provide a way to specify custom resource shutdown.
|
||||
|
||||
Context Manager initializer
|
||||
---------------------------
|
||||
|
||||
This is an extension to the Function initializer. Resource provider automatically detects if the initializer returns a
|
||||
context manager and uses it to manage the resource lifecycle.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
class DatabaseConnection:
|
||||
def __init__(self, host, port, user, password):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
|
||||
def __enter__(self):
|
||||
print(f"Connecting to {self.host}:{self.port} as {self.user}")
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
print("Closing connection")
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
db = providers.Resource(
|
||||
DatabaseConnection,
|
||||
host=config.db.host,
|
||||
port=config.db.port,
|
||||
user=config.db.user,
|
||||
password=config.db.password,
|
||||
)
|
||||
|
||||
Generator initializer (legacy)
|
||||
------------------------------
|
||||
|
||||
Resource provider can use 2-step generators:
|
||||
|
||||
- First step of generator is an initialization phase
|
||||
- The second is step is a shutdown phase
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def init_resource(argument1=..., argument2=...):
|
||||
resource = SomeResource() # initialization
|
||||
|
||||
yield resource
|
||||
|
||||
# shutdown
|
||||
...
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
resource = providers.Resource(
|
||||
init_resource,
|
||||
argument1=...,
|
||||
argument2=...,
|
||||
)
|
||||
|
||||
Generator initialization phase ends on the first ``yield`` statement. You can return a
|
||||
resource object using ``yield resource`` like in the example above. Returning of the
|
||||
object is not mandatory. You can leave ``yield`` statement empty:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def init_resource(argument1=..., argument2=...):
|
||||
# initialization
|
||||
...
|
||||
|
||||
yield
|
||||
|
||||
# shutdown
|
||||
...
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
resource = providers.Resource(
|
||||
init_resource,
|
||||
argument1=...,
|
||||
argument2=...,
|
||||
)
|
||||
|
||||
.. note::
|
||||
|
||||
Generator initializers are automatically wrapped with ``contextmanager`` or ``asynccontextmanager`` decorator when
|
||||
provided to a ``Resource`` provider.
|
||||
|
||||
Subclass initializer (legacy)
|
||||
-----------------------------
|
||||
|
||||
You can create resource initializer by implementing a subclass of the ``resources.Resource``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import resources
|
||||
|
||||
|
||||
class MyResource(resources.Resource):
|
||||
|
||||
def init(self, argument1=..., argument2=...) -> SomeResource:
|
||||
return SomeResource()
|
||||
|
||||
def shutdown(self, resource: SomeResource) -> None:
|
||||
# shutdown
|
||||
...
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
resource = providers.Resource(
|
||||
MyResource,
|
||||
argument1=...,
|
||||
argument2=...,
|
||||
)
|
||||
|
||||
Subclass must implement two methods: ``init()`` and ``shutdown()``.
|
||||
|
||||
Method ``init()`` receives arguments specified in resource provider.
|
||||
It performs initialization and returns resource object. Returning of the object
|
||||
is not mandatory.
|
||||
|
||||
Method ``shutdown()`` receives resource object returned from ``init()``. If ``init()``
|
||||
didn't return an object ``shutdown()`` method will be called anyway with ``None`` as a
|
||||
first argument.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import resources
|
||||
|
||||
|
||||
class MyResource(resources.Resource):
|
||||
|
||||
def init(self, argument1=..., argument2=...) -> None:
|
||||
# initialization
|
||||
...
|
||||
|
||||
def shutdown(self, _: None) -> None:
|
||||
# shutdown
|
||||
...
|
||||
|
||||
|
||||
.. _resource-provider-wiring-closing:
|
||||
|
||||
Scoping Resources using specialized subclasses
|
||||
----------------------------------------------
|
||||
|
||||
You can use specialized subclasses of ``Resource`` provider to initialize and shutdown resources by type.
|
||||
Allowing for example to only initialize a subgroup of resources.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ScopedResource(resources.Resource):
|
||||
pass
|
||||
|
||||
def init_service(name) -> Service:
|
||||
print(f"Init {name}")
|
||||
yield Service()
|
||||
print(f"Shutdown {name}")
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
scoped = ScopedResource(
|
||||
init_service,
|
||||
"scoped",
|
||||
)
|
||||
|
||||
generic = providers.Resource(
|
||||
init_service,
|
||||
"generic",
|
||||
)
|
||||
|
||||
|
||||
To initialize resources by type you can use ``init_resources(resource_type)`` and ``shutdown_resources(resource_type)``
|
||||
methods adding the resource type as an argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def main():
|
||||
container = Container()
|
||||
container.init_resources(ScopedResource)
|
||||
# Generates:
|
||||
# >>> Init scoped
|
||||
|
||||
container.shutdown_resources(ScopedResource)
|
||||
# Generates:
|
||||
# >>> Shutdown scoped
|
||||
|
||||
|
||||
And to initialize all resources you can use ``init_resources()`` and ``shutdown_resources()`` without arguments:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def main():
|
||||
container = Container()
|
||||
container.init_resources()
|
||||
# Generates:
|
||||
# >>> Init scoped
|
||||
# >>> Init generic
|
||||
|
||||
container.shutdown_resources()
|
||||
# Generates:
|
||||
# >>> Shutdown scoped
|
||||
# >>> Shutdown generic
|
||||
|
||||
|
||||
It works using the ``traverse()`` method to find all resources of the specified type, selecting all resources
|
||||
which are instances of the specified type.
|
||||
|
||||
|
||||
Resources, wiring, and per-function execution scope
|
||||
---------------------------------------------------
|
||||
|
||||
You can compound ``Resource`` provider with :ref:`wiring` to implement per-function
|
||||
execution scope. For doing this you need to use additional ``Closing`` marker from
|
||||
``wiring`` module.
|
||||
|
||||
.. literalinclude:: ../../examples/wiring/flask_resource_closing.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 22
|
||||
|
||||
Framework initializes and injects the resource into the function. With the ``Closing`` marker
|
||||
framework calls resource ``shutdown()`` method when function execution is over.
|
||||
|
||||
The example above produces next output:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Init service
|
||||
Shutdown service
|
||||
127.0.0.1 - - [29/Oct/2020 22:39:40] "GET / HTTP/1.1" 200 -
|
||||
Init service
|
||||
Shutdown service
|
||||
127.0.0.1 - - [29/Oct/2020 22:39:41] "GET / HTTP/1.1" 200 -
|
||||
Init service
|
||||
Shutdown service
|
||||
127.0.0.1 - - [29/Oct/2020 22:39:41] "GET / HTTP/1.1" 200 -
|
||||
|
||||
.. _resource-async-initializers:
|
||||
|
||||
Asynchronous initializers
|
||||
-------------------------
|
||||
|
||||
When you write an asynchronous application, you might need to initialize resources asynchronously. Resource
|
||||
provider supports asynchronous initialization and shutdown.
|
||||
|
||||
Asynchronous function initializer:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def init_async_resource(argument1=..., argument2=...):
|
||||
return await connect()
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
resource = providers.Resource(
|
||||
init_resource,
|
||||
argument1=...,
|
||||
argument2=...,
|
||||
)
|
||||
|
||||
Asynchronous Context Manager initializer:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@asynccontextmanager
|
||||
async def init_async_resource(argument1=..., argument2=...):
|
||||
connection = await connect()
|
||||
yield connection
|
||||
await connection.close()
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
resource = providers.Resource(
|
||||
init_async_resource,
|
||||
argument1=...,
|
||||
argument2=...,
|
||||
)
|
||||
|
||||
Asynchronous subclass initializer:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import resources
|
||||
|
||||
|
||||
class AsyncConnection(resources.AsyncResource):
|
||||
|
||||
async def init(self, argument1=..., argument2=...):
|
||||
yield await connect()
|
||||
|
||||
async def shutdown(self, connection):
|
||||
await connection.close()
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
resource = providers.Resource(
|
||||
AsyncConnection,
|
||||
argument1=...,
|
||||
argument2=...,
|
||||
)
|
||||
|
||||
When you use resource provider with asynchronous initializer you need to call its ``__call__()``,
|
||||
``init()``, and ``shutdown()`` methods asynchronously:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
connection = providers.Resource(init_async_connection)
|
||||
|
||||
|
||||
async def main():
|
||||
container = Container()
|
||||
connection = await container.connection()
|
||||
connection = await container.connection.init()
|
||||
connection = await container.connection.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
Container ``init_resources()`` and ``shutdown_resources()`` methods should be used asynchronously if there is
|
||||
at least one asynchronous resource provider:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
connection1 = providers.Resource(init_async_connection)
|
||||
|
||||
connection2 = providers.Resource(init_sync_connection)
|
||||
|
||||
|
||||
async def main():
|
||||
container = Container()
|
||||
await container.init_resources()
|
||||
await container.shutdown_resources()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
See also:
|
||||
|
||||
- Provider :ref:`async-injections`
|
||||
- Wiring :ref:`async-injections-wiring`
|
||||
- :ref:`fastapi-redis-example`
|
||||
|
||||
ASGI Lifespan Protocol Support
|
||||
------------------------------
|
||||
|
||||
The :mod:`dependency_injector.ext.starlette` module provides a :class:`~dependency_injector.ext.starlette.Lifespan`
|
||||
class that integrates resource providers with ASGI applications using the `Lifespan Protocol`_. This allows resources to
|
||||
be automatically initialized at application startup and properly shut down when the application stops.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
from dependency_injector.ext.starlette import Lifespan
|
||||
from fastapi import FastAPI, Request, Depends, APIRouter
|
||||
|
||||
class Connection: ...
|
||||
|
||||
@asynccontextmanager
|
||||
async def init_database():
|
||||
print("opening database connection")
|
||||
yield Connection()
|
||||
print("closing database connection")
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/")
|
||||
@inject
|
||||
async def index(request: Request, db: Connection = Depends(Provide["db"])):
|
||||
# use the database connection here
|
||||
return "OK!"
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
__self__ = providers.Self()
|
||||
db = providers.Resource(init_database)
|
||||
lifespan = providers.Singleton(Lifespan, __self__)
|
||||
app = providers.Singleton(FastAPI, lifespan=lifespan)
|
||||
_include_router = providers.Resource(
|
||||
app.provided.include_router.call(),
|
||||
router,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
container = Container()
|
||||
app = container.app()
|
||||
uvicorn.run(app, host="localhost", port=8000)
|
||||
|
||||
.. _Lifespan Protocol: https://asgi.readthedocs.io/en/latest/specs/lifespan.html
|
||||
|
||||
.. disqus::
|
|
@ -30,4 +30,7 @@ When a ``Selector`` provider is called, it gets a ``selector`` value and delegat
|
|||
the provider with a matching name. The ``selector`` callable works as a switch: when the returned
|
||||
value is changed the ``Selector`` provider will delegate the work to another provider.
|
||||
|
||||
.. seealso::
|
||||
:ref:`aggregate-provider` to inject a group of providers.
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.. _singleton-provider:
|
||||
|
||||
Singleton provider
|
||||
==================
|
||||
|
||||
|
@ -18,13 +20,12 @@ returns it on the rest of the calls.
|
|||
:language: python
|
||||
:lines: 3-
|
||||
|
||||
``Singleton`` provider handles an injection of the dependencies the same way like a
|
||||
:ref:`factory-provider`.
|
||||
``Singleton`` provider handles dependencies injection the same way like a :ref:`factory-provider`.
|
||||
|
||||
.. note::
|
||||
|
||||
``Singleton`` provider does dependencies injection only when creates the object. When the object
|
||||
is created and memorized ``Singleton`` provider just returns it without applying the injections.
|
||||
``Singleton`` provider makes dependencies injection only when it creates an object. When an object
|
||||
is created and memorized ``Singleton`` provider just returns it without applying injections.
|
||||
|
||||
Specialization of the provided type and abstract singletons work the same like like for the
|
||||
factories:
|
||||
|
@ -54,6 +55,38 @@ provider.
|
|||
Resetting of the memorized object clears the reference to it. Further object's lifecycle is
|
||||
managed by the garbage collector.
|
||||
|
||||
You can use ``.reset()`` method with a context manager. Memorized instance will be reset on
|
||||
both entering and exiting a context.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/singleton_resetting_with.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 18-19
|
||||
|
||||
Context manager ``.reset()`` returns resetting singleton provider. You can use it for aliasing.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with container.user_service.reset() as user_service:
|
||||
...
|
||||
|
||||
Method ``.reset()`` resets only current provider. To reset all dependent singleton providers
|
||||
call ``.full_reset()`` method.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/singleton_full_resetting.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 25
|
||||
|
||||
Method ``.full_reset()`` supports context manager interface like ``.reset()`` does.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with container.user_service.full_reset() as user_service:
|
||||
...
|
||||
|
||||
See also: :ref:`reset-container-singletons`.
|
||||
|
||||
Using singleton with multiple threads
|
||||
-------------------------------------
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ IDE.
|
|||
provider = providers.Factory(Cat)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
animal = provider() # mypy knows that animal is of type "Cat"
|
||||
|
||||
|
||||
|
@ -54,5 +54,7 @@ function or method.
|
|||
provider: providers.Provider[Animal] = providers.Factory(Cat)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
animal = provider() # mypy knows that animal is of type "Animal"
|
||||
|
||||
.. disqus::
|
||||
|
|
7
docs/sponsor.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
.. list-table::
|
||||
:class: no-border
|
||||
:align: left
|
||||
|
||||
* - Sponsor the project on GitHub:
|
||||
- .. raw:: html
|
||||
:file: _static/sponsor.html
|
|
@ -21,7 +21,7 @@ Start from the scratch or jump to the section:
|
|||
:backlinks: none
|
||||
|
||||
You can find complete project on the
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/giphynav-aiohttp>`_.
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
||||
|
||||
What are we going to build?
|
||||
---------------------------
|
||||
|
@ -88,18 +88,18 @@ Prepare the environment
|
|||
|
||||
Let's create the environment for the project.
|
||||
|
||||
First we need to create a project folder and the virtual environment:
|
||||
First we need to create a project folder:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
mkdir giphynav-aiohttp-tutorial
|
||||
cd giphynav-aiohttp-tutorial
|
||||
python3 -m venv venv
|
||||
|
||||
Now let's activate the virtual environment:
|
||||
Now let's create and activate virtual environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
|
||||
Environment is ready and now we're going to create the layout of the project.
|
||||
|
@ -116,7 +116,7 @@ Initial project layout::
|
|||
│ ├── __init__.py
|
||||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ └── views.py
|
||||
│ └── handlers.py
|
||||
├── venv/
|
||||
└── requirements.txt
|
||||
|
||||
|
@ -127,8 +127,6 @@ Now it's time to install the project requirements. We will use next packages:
|
|||
|
||||
- ``dependency-injector`` - the dependency injection framework
|
||||
- ``aiohttp`` - the web framework
|
||||
- ``aiohttp-devtools`` - the helper library that will provide a development server with live
|
||||
reloading
|
||||
- ``pyyaml`` - the YAML files parsing library, used for the reading of the configuration files
|
||||
- ``pytest-aiohttp`` - the helper library for the testing of the ``aiohttp`` application
|
||||
- ``pytest-cov`` - the helper library for measuring the test coverage
|
||||
|
@ -139,7 +137,6 @@ Put next lines into the ``requirements.txt`` file:
|
|||
|
||||
dependency-injector
|
||||
aiohttp
|
||||
aiohttp-devtools
|
||||
pyyaml
|
||||
pytest-aiohttp
|
||||
pytest-cov
|
||||
|
@ -164,60 +161,51 @@ The requirements are setup. Now we will build a minimal application.
|
|||
Minimal application
|
||||
-------------------
|
||||
|
||||
In this section we will build a minimal application. It will have an endpoint that we can call.
|
||||
The endpoint will answer in the right format and will have no data.
|
||||
In this section we will build a minimal application. It will have an endpoint that
|
||||
will answer our requests in json format. There will be no payload for now.
|
||||
|
||||
Edit ``views.py``:
|
||||
Edit ``handlers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Views module."""
|
||||
"""Handlers module."""
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
async def index(request: web.Request) -> web.Response:
|
||||
query = request.query.get('query', 'Dependency Injector')
|
||||
limit = int(request.query.get('limit', 10))
|
||||
query = request.query.get("query", "Dependency Injector")
|
||||
limit = int(request.query.get("limit", 10))
|
||||
|
||||
gifs = []
|
||||
|
||||
return web.json_response(
|
||||
{
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
'gifs': gifs,
|
||||
"query": query,
|
||||
"limit": limit,
|
||||
"gifs": gifs,
|
||||
},
|
||||
)
|
||||
|
||||
Now let's create the main part of our application - the container. Container will keep all of the
|
||||
application components and their dependencies. First two providers we need to add are
|
||||
the ``aiohttp`` application provider and the view provider.
|
||||
Now let's create a container. Container will keep all of the application components and their dependencies.
|
||||
|
||||
Put next into the ``containers.py``:
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers
|
||||
from dependency_injector.ext import aiohttp
|
||||
from aiohttp import web
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
...
|
||||
|
||||
app = aiohttp.Application(web.Application)
|
||||
Container is empty for now. We will add the providers in the following sections.
|
||||
|
||||
index_view = aiohttp.View(views.index)
|
||||
|
||||
At the last we need to create the ``aiohttp`` application factory. It is traditionally called
|
||||
``create_app()``. It will create the container. Then it will use the container to create
|
||||
the ``aiohttp`` application. Last step is to configure the routing - we will assign
|
||||
``index_view`` from the container to handle the requests to the root ``/`` of our REST API server.
|
||||
Finally we need to create ``aiohttp`` application factory. It will create and configure container
|
||||
and ``web.Application``. It is traditionally called ``create_app()``.
|
||||
We will assign ``index`` handler to handle user requests to the root ``/`` of our web application.
|
||||
|
||||
Put next into the ``application.py``:
|
||||
|
||||
|
@ -227,27 +215,24 @@ Put next into the ``application.py``:
|
|||
|
||||
from aiohttp import web
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from .containers import Container
|
||||
from . import handlers
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create and return aiohttp application."""
|
||||
container = ApplicationContainer()
|
||||
def create_app() -> web.Application:
|
||||
container = Container()
|
||||
|
||||
app: web.Application = container.app()
|
||||
app = web.Application()
|
||||
app.container = container
|
||||
|
||||
app.add_routes([
|
||||
web.get('/', container.index_view.as_view()),
|
||||
web.get("/", handlers.index),
|
||||
])
|
||||
|
||||
return app
|
||||
|
||||
.. note::
|
||||
|
||||
Container is the first object in the application.
|
||||
|
||||
The container is used to create all other objects.
|
||||
if __name__ == "__main__":
|
||||
app = create_app()
|
||||
web.run_app(app)
|
||||
|
||||
Now we're ready to run our application
|
||||
|
||||
|
@ -255,30 +240,30 @@ Do next in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
adev runserver giphynavigator/application.py --livereload
|
||||
python -m giphynavigator.application
|
||||
|
||||
The output should be something like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[18:52:59] Starting aux server at http://localhost:8001 ◆
|
||||
[18:52:59] Starting dev server at http://localhost:8000 ●
|
||||
======== Running on http://0.0.0.0:8080 ========
|
||||
(Press CTRL+C to quit)
|
||||
|
||||
Let's use ``httpie`` to check that it works:
|
||||
Let's check that it works. Open another terminal session and use ``httpie``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
http http://127.0.0.1:8000/
|
||||
http http://0.0.0.0:8080/
|
||||
|
||||
You should see:
|
||||
|
||||
.. code-block:: json
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 844
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Date: Wed, 29 Jul 2020 21:01:50 GMT
|
||||
Server: Python/3.8 aiohttp/3.6.2
|
||||
Server: Python/3.10 aiohttp/3.6.2
|
||||
|
||||
{
|
||||
"gifs": [],
|
||||
|
@ -306,7 +291,7 @@ Create ``giphy.py`` module in the ``giphynavigator`` package:
|
|||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── giphy.py
|
||||
│ └── views.py
|
||||
│ └── handlers.py
|
||||
├── venv/
|
||||
└── requirements.txt
|
||||
|
||||
|
@ -321,7 +306,7 @@ and put next into it:
|
|||
|
||||
class GiphyClient:
|
||||
|
||||
API_URL = 'http://api.giphy.com/v1'
|
||||
API_URL = "https://api.giphy.com/v1"
|
||||
|
||||
def __init__(self, api_key, timeout):
|
||||
self._api_key = api_key
|
||||
|
@ -329,11 +314,11 @@ and put next into it:
|
|||
|
||||
async def search(self, query, limit):
|
||||
"""Make search API call and return result."""
|
||||
url = f'{self.API_URL}/gifs/search'
|
||||
url = f"{self.API_URL}/gifs/search"
|
||||
params = {
|
||||
'q': query,
|
||||
'api_key': self._api_key,
|
||||
'limit': limit,
|
||||
"q": query,
|
||||
"api_key": self._api_key,
|
||||
"limit": limit,
|
||||
}
|
||||
async with ClientSession(timeout=self._timeout) as session:
|
||||
async with session.get(url, params=params) as response:
|
||||
|
@ -345,29 +330,26 @@ Now we need to add ``GiphyClient`` into the container. The ``GiphyClient`` has t
|
|||
that have to be injected: the API key and the request timeout. We will need to use two more
|
||||
providers from the ``dependency_injector.providers`` module:
|
||||
|
||||
- ``Factory`` provider that will create the ``GiphyClient`` client.
|
||||
- ``Configuration`` provider that will provide the API key and the request timeout.
|
||||
- ``Factory`` provider. It will create a ``GiphyClient`` client.
|
||||
- ``Configuration`` provider. It will provide an API key and a request timeout for the ``GiphyClient``
|
||||
client. We will specify the location of the configuration file. The configuration provider will parse
|
||||
the configuration file when we create a container instance.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3,7,15,17-21
|
||||
:emphasize-lines: 3-5,10-16
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import aiohttp
|
||||
from aiohttp import web
|
||||
|
||||
from . import giphy, views
|
||||
from . import giphy
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
app = aiohttp.Application(web.Application)
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
|
@ -375,20 +357,8 @@ Edit ``containers.py``:
|
|||
timeout=config.giphy.request_timeout,
|
||||
)
|
||||
|
||||
index_view = aiohttp.View(views.index)
|
||||
|
||||
.. note::
|
||||
|
||||
We have used the configuration value before it was defined. That's the principle how the
|
||||
``Configuration`` provider works.
|
||||
|
||||
Use first, define later.
|
||||
|
||||
Now let's add the configuration file.
|
||||
|
||||
We will use YAML.
|
||||
|
||||
Create an empty file ``config.yml`` in the root root of the project:
|
||||
Now let's add the configuration file. We will use YAML. Create an empty file ``config.yml`` in
|
||||
the root root of the project:
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 9
|
||||
|
@ -399,7 +369,7 @@ Create an empty file ``config.yml`` in the root root of the project:
|
|||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── giphy.py
|
||||
│ └── views.py
|
||||
│ └── handlers.py
|
||||
├── venv/
|
||||
├── config.yml
|
||||
└── requirements.txt
|
||||
|
@ -411,40 +381,39 @@ and put next into it:
|
|||
giphy:
|
||||
request_timeout: 10
|
||||
|
||||
We will use an environment variable ``GIPHY_API_KEY`` to provide the API key.
|
||||
|
||||
Now we need to edit ``create_app()`` to make two things when application starts:
|
||||
|
||||
- Load the configuration file the ``config.yml``.
|
||||
- Load the API key from the ``GIPHY_API_KEY`` environment variable.
|
||||
We will use the ``GIPHY_API_KEY`` environment variable to provide the API key. Let’s edit
|
||||
``create_app()`` to fetch the key value from it.
|
||||
|
||||
Edit ``application.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 11-12
|
||||
:emphasize-lines: 11
|
||||
|
||||
"""Application module."""
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from .containers import Container
|
||||
from . import handlers
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create and return aiohttp application."""
|
||||
container = ApplicationContainer()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.giphy.api_key.from_env('GIPHY_API_KEY')
|
||||
def create_app() -> web.Application:
|
||||
container = Container()
|
||||
container.config.giphy.api_key.from_env("GIPHY_API_KEY")
|
||||
|
||||
app: web.Application = container.app()
|
||||
app = web.Application()
|
||||
app.container = container
|
||||
|
||||
app.add_routes([
|
||||
web.get('/', container.index_view.as_view()),
|
||||
web.get("/", handlers.index),
|
||||
])
|
||||
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = create_app()
|
||||
web.run_app(app)
|
||||
|
||||
Now we need to create an API key and set it to the environment variable.
|
||||
|
||||
As for now, don’t worry, just take this one:
|
||||
|
@ -473,7 +442,7 @@ Now it's time to add the ``SearchService``. It will:
|
|||
Create ``services.py`` module in the ``giphynavigator`` package:
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 7
|
||||
:emphasize-lines: 8
|
||||
|
||||
./
|
||||
├── giphynavigator/
|
||||
|
@ -481,9 +450,10 @@ Create ``services.py`` module in the ``giphynavigator`` package:
|
|||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── giphy.py
|
||||
│ ├── services.py
|
||||
│ └── views.py
|
||||
│ ├── handlers.py
|
||||
│ └── services.py
|
||||
├── venv/
|
||||
├── config.yml
|
||||
└── requirements.txt
|
||||
|
||||
and put next into it:
|
||||
|
@ -507,31 +477,26 @@ and put next into it:
|
|||
|
||||
result = await self._giphy_client.search(query, limit)
|
||||
|
||||
return [{'url': gif['url']} for gif in result['data']]
|
||||
return [{"url": gif["url"]} for gif in result["data"]]
|
||||
|
||||
The ``SearchService`` has a dependency on the ``GiphyClient``. This dependency will be injected.
|
||||
Let's add ``SearchService`` to the container.
|
||||
The ``SearchService`` has a dependency on the ``GiphyClient``. This dependency will be
|
||||
injected when we add ``SearchService`` to the container.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 7,23-26
|
||||
:emphasize-lines: 5,18-21
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import aiohttp
|
||||
from aiohttp import web
|
||||
|
||||
from . import giphy, services, views
|
||||
from . import giphy, services
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
app = aiohttp.Application(web.Application)
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
|
@ -544,67 +509,67 @@ Edit ``containers.py``:
|
|||
giphy_client=giphy_client,
|
||||
)
|
||||
|
||||
index_view = aiohttp.View(views.index)
|
||||
|
||||
|
||||
The search service is ready. In the next section we're going to make it work.
|
||||
The search service is ready. In next section we're going to put it to work.
|
||||
|
||||
Make the search work
|
||||
--------------------
|
||||
|
||||
Now we are ready to make the search work. Let's use the ``SearchService`` in the ``index`` view.
|
||||
Now we are ready to put the search into work. Let's inject ``SearchService`` into
|
||||
the ``index`` handler. We will use :ref:`wiring` feature.
|
||||
|
||||
Edit ``views.py``:
|
||||
Edit ``handlers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5,8-11,15
|
||||
:emphasize-lines: 4-7,10-14,18
|
||||
|
||||
"""Views module."""
|
||||
"""Handlers module."""
|
||||
|
||||
from aiohttp import web
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
async def index(
|
||||
request: web.Request,
|
||||
search_service: SearchService,
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
) -> web.Response:
|
||||
query = request.query.get('query', 'Dependency Injector')
|
||||
limit = int(request.query.get('limit', 10))
|
||||
query = request.query.get("query", "Dependency Injector")
|
||||
limit = int(request.query.get("limit", 10))
|
||||
|
||||
gifs = await search_service.search(query, limit)
|
||||
|
||||
return web.json_response(
|
||||
{
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
'gifs': gifs,
|
||||
"query": query,
|
||||
"limit": limit,
|
||||
"gifs": gifs,
|
||||
},
|
||||
)
|
||||
|
||||
Now let's inject the ``SearchService`` dependency into the ``index`` view.
|
||||
To make the injection work we need to wire the container with the ``handlers`` module.
|
||||
Let's configure the container to automatically make wiring with the ``handlers`` module when we
|
||||
create a container instance.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 28-31
|
||||
:emphasize-lines: 10
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import aiohttp
|
||||
from aiohttp import web
|
||||
|
||||
from . import giphy, services, views
|
||||
from . import giphy, services
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
app = aiohttp.Application(web.Application)
|
||||
wiring_config = containers.WiringConfiguration(modules=[".handlers"])
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
|
@ -617,52 +582,47 @@ Edit ``containers.py``:
|
|||
giphy_client=giphy_client,
|
||||
)
|
||||
|
||||
index_view = aiohttp.View(
|
||||
views.index,
|
||||
search_service=search_service,
|
||||
)
|
||||
|
||||
Make sure the app is running or use:
|
||||
Make sure the app is running:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
adev runserver giphynavigator/application.py --livereload
|
||||
python -m giphynavigator.application
|
||||
|
||||
and make a request to the API in the terminal:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
http http://localhost:8000/ query=="wow,it works" limit==5
|
||||
http http://0.0.0.0:8080/ query=="wow,it works" limit==5
|
||||
|
||||
You should see:
|
||||
|
||||
.. code-block:: json
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 850
|
||||
Content-Length: 492
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Date: Wed, 29 Jul 2020 22:22:55 GMT
|
||||
Server: Python/3.8 aiohttp/3.6.2
|
||||
Date: Fri, 09 Oct 2020 01:35:48 GMT
|
||||
Server: Python/3.10 aiohttp/3.6.2
|
||||
|
||||
{
|
||||
"gifs": [
|
||||
{
|
||||
"url": "https://giphy.com/gifs/dollyparton-3xIVVMnZfG3KQ9v4Ye"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/tennistv-unbelievable-disbelief-cant-believe-UWWJnhHHbpGvZOapEh"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/discoverychannel-nugget-gold-rush-rick-ness-KGGPIlnC4hr4u2s3pY"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/primevideoin-ll1hyBS2IrUPLE0E71"
|
||||
"url": "https://giphy.com/gifs/soulpancake-wow-work-xUe4HVXTPi0wQ2OAJC"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/jackman-works-jackmanworks-l4pTgQoCrmXq8Txlu"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/cat-massage-at-work-l46CzMaOlJXAFuO3u"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/everwhatproductions-fun-christmas-3oxHQCI8tKXoeW4IBq"
|
||||
},
|
||||
"url": "https://giphy.com/gifs/readingrainbow-teamwork-levar-burton-reading-rainbow-3o7qE1EaTWLQGDSabK"
|
||||
}
|
||||
],
|
||||
"limit": 10,
|
||||
"limit": 5,
|
||||
"query": "wow,it works"
|
||||
}
|
||||
|
||||
|
@ -673,86 +633,48 @@ The search works!
|
|||
Make some refactoring
|
||||
---------------------
|
||||
|
||||
Our ``index`` view has two hardcoded config values:
|
||||
Our ``index`` handler has two hardcoded config values:
|
||||
|
||||
- Default search query
|
||||
- Default results limit
|
||||
|
||||
Let's make some refactoring. We will move these values to the config.
|
||||
|
||||
Edit ``views.py``:
|
||||
Edit ``handlers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 11-12,14-15
|
||||
:emphasize-lines: 14-15,17-18
|
||||
|
||||
"""Views module."""
|
||||
"""Handlers module."""
|
||||
|
||||
from aiohttp import web
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
async def index(
|
||||
request: web.Request,
|
||||
search_service: SearchService,
|
||||
default_query: str,
|
||||
default_limit: int,
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
default_query: str = Provide[Container.config.default.query],
|
||||
default_limit: int = Provide[Container.config.default.limit.as_int()],
|
||||
) -> web.Response:
|
||||
query = request.query.get('query', default_query)
|
||||
limit = int(request.query.get('limit', default_limit))
|
||||
query = request.query.get("query", default_query)
|
||||
limit = int(request.query.get("limit", default_limit))
|
||||
|
||||
gifs = await search_service.search(query, limit)
|
||||
|
||||
return web.json_response(
|
||||
{
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
'gifs': gifs,
|
||||
"query": query,
|
||||
"limit": limit,
|
||||
"gifs": gifs,
|
||||
},
|
||||
)
|
||||
|
||||
Now we need to inject these values. Let's update the container.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 31-32
|
||||
|
||||
"""Application containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import aiohttp
|
||||
from aiohttp import web
|
||||
|
||||
from . import giphy, services, views
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
|
||||
app = aiohttp.Application(web.Application)
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
api_key=config.giphy.api_key,
|
||||
timeout=config.giphy.request_timeout,
|
||||
)
|
||||
|
||||
search_service = providers.Factory(
|
||||
services.SearchService,
|
||||
giphy_client=giphy_client,
|
||||
)
|
||||
|
||||
index_view = aiohttp.View(
|
||||
views.index,
|
||||
search_service=search_service,
|
||||
default_query=config.search.default_query,
|
||||
default_limit=config.search.default_limit,
|
||||
)
|
||||
|
||||
Finally let's update the config.
|
||||
Let's update the config.
|
||||
|
||||
Edit ``config.yml``:
|
||||
|
||||
|
@ -761,26 +683,21 @@ Edit ``config.yml``:
|
|||
|
||||
giphy:
|
||||
request_timeout: 10
|
||||
search:
|
||||
default_query: "Dependency Injector"
|
||||
default_limit: 10
|
||||
default:
|
||||
query: "Dependency Injector"
|
||||
limit: 10
|
||||
|
||||
The refactoring is done. We've made it cleaner - hardcoded values are now moved to the config.
|
||||
|
||||
In the next section we will add some tests.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
It would be nice to add some tests. Let's do it.
|
||||
|
||||
We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
|
||||
`coverage <https://coverage.readthedocs.io/>`_.
|
||||
In this section we will add some tests.
|
||||
|
||||
Create ``tests.py`` module in the ``giphynavigator`` package:
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 8
|
||||
:emphasize-lines: 9
|
||||
|
||||
./
|
||||
├── giphynavigator/
|
||||
|
@ -788,16 +705,17 @@ Create ``tests.py`` module in the ``giphynavigator`` package:
|
|||
│ ├── application.py
|
||||
│ ├── containers.py
|
||||
│ ├── giphy.py
|
||||
│ ├── handlers.py
|
||||
│ ├── services.py
|
||||
│ ├── tests.py
|
||||
│ └── views.py
|
||||
│ └── tests.py
|
||||
├── venv/
|
||||
├── config.yml
|
||||
└── requirements.txt
|
||||
|
||||
and put next into it:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 30,57,71
|
||||
:emphasize-lines: 32,59,73
|
||||
|
||||
"""Tests module."""
|
||||
|
||||
|
@ -811,7 +729,9 @@ and put next into it:
|
|||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return create_app()
|
||||
app = create_app()
|
||||
yield app
|
||||
app.container.unwire()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -822,29 +742,29 @@ and put next into it:
|
|||
async def test_index(client, app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [
|
||||
{'url': 'https://giphy.com/gif1.gif'},
|
||||
{'url': 'https://giphy.com/gif2.gif'},
|
||||
"data": [
|
||||
{"url": "https://giphy.com/gif1.gif"},
|
||||
{"url": "https://giphy.com/gif2.gif"},
|
||||
],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
response = await client.get(
|
||||
'/',
|
||||
"/",
|
||||
params={
|
||||
'query': 'test',
|
||||
'limit': 10,
|
||||
"query": "test",
|
||||
"limit": 10,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data == {
|
||||
'query': 'test',
|
||||
'limit': 10,
|
||||
'gifs': [
|
||||
{'url': 'https://giphy.com/gif1.gif'},
|
||||
{'url': 'https://giphy.com/gif2.gif'},
|
||||
"query": "test",
|
||||
"limit": 10,
|
||||
"gifs": [
|
||||
{"url": "https://giphy.com/gif1.gif"},
|
||||
{"url": "https://giphy.com/gif2.gif"},
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -852,30 +772,30 @@ and put next into it:
|
|||
async def test_index_no_data(client, app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [],
|
||||
"data": [],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
response = await client.get('/')
|
||||
response = await client.get("/")
|
||||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data['gifs'] == []
|
||||
assert data["gifs"] == []
|
||||
|
||||
|
||||
async def test_index_default_params(client, app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [],
|
||||
"data": [],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
response = await client.get('/')
|
||||
response = await client.get("/")
|
||||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data['query'] == app.container.config.search.default_query()
|
||||
assert data['limit'] == app.container.config.search.default_limit()
|
||||
assert data["query"] == app.container.config.default.query()
|
||||
assert data["limit"] == app.container.config.default.limit()
|
||||
|
||||
Now let's run it and check the coverage:
|
||||
|
||||
|
@ -885,27 +805,26 @@ Now let's run it and check the coverage:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block::
|
||||
|
||||
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
|
||||
plugins: cov-2.10.0, aiohttp-0.3.0, asyncio-0.14.0
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.0
|
||||
collected 3 items
|
||||
|
||||
giphynavigator/tests.py ... [100%]
|
||||
|
||||
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
|
||||
---------- coverage: platform darwin, python 3.10.0-final-0 ----------
|
||||
Name Stmts Miss Cover
|
||||
---------------------------------------------------
|
||||
giphynavigator/__init__.py 0 0 100%
|
||||
giphynavigator/__main__.py 5 5 0%
|
||||
giphynavigator/application.py 10 0 100%
|
||||
giphynavigator/containers.py 10 0 100%
|
||||
giphynavigator/application.py 13 2 85%
|
||||
giphynavigator/containers.py 7 0 100%
|
||||
giphynavigator/giphy.py 14 9 36%
|
||||
giphynavigator/handlers.py 10 0 100%
|
||||
giphynavigator/services.py 9 1 89%
|
||||
giphynavigator/tests.py 35 0 100%
|
||||
giphynavigator/views.py 7 0 100%
|
||||
giphynavigator/tests.py 37 0 100%
|
||||
---------------------------------------------------
|
||||
TOTAL 90 15 83%
|
||||
TOTAL 90 12 87%
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -920,45 +839,19 @@ In this tutorial we've built an ``aiohttp`` REST API application following the d
|
|||
injection principle.
|
||||
We've used the ``Dependency Injector`` as a dependency injection framework.
|
||||
|
||||
The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
|
||||
when you need to understand or change your application structure. It's easy with the container,
|
||||
cause you have everything defined explicitly in one place:
|
||||
:ref:`containers` and :ref:`providers` helped to specify how to assemble search service and
|
||||
giphy client.
|
||||
|
||||
.. code-block:: python
|
||||
:ref:`configuration-provider` helped to deal with reading YAML file and environment variable.
|
||||
|
||||
"""Application containers module."""
|
||||
We used :ref:`wiring` feature to inject the dependencies into the ``index()`` handler.
|
||||
:ref:`provider-overriding` feature helped in testing.
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import aiohttp
|
||||
from aiohttp import web
|
||||
We kept all the dependencies injected explicitly. This will help when you need to add or
|
||||
change something in future.
|
||||
|
||||
from . import giphy, services, views
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
|
||||
app = aiohttp.Application(web.Application)
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
api_key=config.giphy.api_key,
|
||||
timeout=config.giphy.request_timeout,
|
||||
)
|
||||
|
||||
search_service = providers.Factory(
|
||||
services.SearchService,
|
||||
giphy_client=giphy_client,
|
||||
)
|
||||
|
||||
index_view = aiohttp.View(
|
||||
views.index,
|
||||
search_service=search_service,
|
||||
default_query=config.search.default_query,
|
||||
default_limit=config.search.default_limit,
|
||||
)
|
||||
You can find complete project on the
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
||||
|
||||
What's next?
|
||||
|
||||
|
@ -966,4 +859,6 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -18,7 +18,7 @@ In this tutorial we will use:
|
|||
|
||||
- Python 3
|
||||
- Docker
|
||||
- Docker-compose
|
||||
- Docker Compose
|
||||
|
||||
Start from the scratch or jump to the section:
|
||||
|
||||
|
@ -27,7 +27,7 @@ Start from the scratch or jump to the section:
|
|||
:backlinks: none
|
||||
|
||||
You can find complete project on the
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/monitoring-daemon-asyncio>`_.
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/asyncio-daemon>`_.
|
||||
|
||||
What are we going to build?
|
||||
---------------------------
|
||||
|
@ -42,33 +42,32 @@ response it will log:
|
|||
- The amount of bytes in the response
|
||||
- The time took to complete the response
|
||||
|
||||
.. image:: asyncio_images/diagram.png
|
||||
.. image:: asyncio-images/diagram.png
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
We will use `Docker <https://www.docker.com/>`_ and
|
||||
`docker-compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
|
||||
We will use `docker compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker --version
|
||||
docker-compose --version
|
||||
docker compose version
|
||||
|
||||
The output should look something like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Docker version 19.03.12, build 48a66213fe
|
||||
docker-compose version 1.26.2, build eefe0d31
|
||||
Docker version 27.3.1, build ce12230
|
||||
Docker Compose version v2.29.7
|
||||
|
||||
.. note::
|
||||
|
||||
If you don't have ``Docker`` or ``docker-compose`` you need to install them before proceeding.
|
||||
If you don't have ``Docker`` or ``docker compose`` you need to install them before proceeding.
|
||||
Follow these installation guides:
|
||||
|
||||
- `Install Docker <https://docs.docker.com/get-docker/>`_
|
||||
- `Install docker-compose <https://docs.docker.com/compose/install/>`_
|
||||
- `Install docker compose <https://docs.docker.com/compose/install/>`_
|
||||
|
||||
The prerequisites are satisfied. Let's get started with the project layout.
|
||||
|
||||
|
@ -79,8 +78,8 @@ Create the project root folder and set it as a working directory:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
mkdir monitoring-daemon-tutorial
|
||||
cd monitoring-daemon-tutorial
|
||||
mkdir asyncio-daemon-tutorial
|
||||
cd asyncio-daemon-tutorial
|
||||
|
||||
Now we need to create the initial project structure. Create the files and folders following next
|
||||
layout. All files should be empty for now. We will fill them later.
|
||||
|
@ -129,13 +128,13 @@ Put next lines into the ``requirements.txt`` file:
|
|||
pytest-cov
|
||||
|
||||
Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and
|
||||
specify how to run it. We will use ``python:3.8-buster`` as a base image.
|
||||
specify how to run it. We will use ``python:3.13-bookworm`` as a base image.
|
||||
|
||||
Put next lines into the ``Dockerfile`` file:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM python:3.8-buster
|
||||
FROM python:3.13-bookworm
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
@ -155,8 +154,6 @@ Put next lines into the ``docker-compose.yml`` file:
|
|||
|
||||
.. code-block:: yaml
|
||||
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
|
||||
monitor:
|
||||
|
@ -171,7 +168,7 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose build
|
||||
docker compose build
|
||||
|
||||
The build process may take a couple of minutes. You should see something like this in the end:
|
||||
|
||||
|
@ -184,16 +181,16 @@ After the build is done run the container:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
docker compose up
|
||||
|
||||
The output should look like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Creating network "monitoring-daemon-tutorial_default" with the default driver
|
||||
Creating monitoring-daemon-tutorial_monitor_1 ... done
|
||||
Attaching to monitoring-daemon-tutorial_monitor_1
|
||||
monitoring-daemon-tutorial_monitor_1 exited with code 0
|
||||
Creating network "asyncio-daemon-tutorial_default" with the default driver
|
||||
Creating asyncio-daemon-tutorial_monitor_1 ... done
|
||||
Attaching to asyncio-daemon-tutorial_monitor_1
|
||||
asyncio-daemon-tutorial_monitor_1 exited with code 0
|
||||
|
||||
The environment is ready. The application does not do any work and just exits with a code ``0``.
|
||||
|
||||
|
@ -204,17 +201,17 @@ Logging and configuration
|
|||
|
||||
In this section we will configure the logging and configuration file parsing.
|
||||
|
||||
Let's start with the the main part of our application - the container. Container will keep all of
|
||||
Let's start with the the main part of our application – the container. Container will keep all of
|
||||
the application components and their dependencies.
|
||||
|
||||
First two components that we're going to add are the config object and the provider for
|
||||
configuring the logging.
|
||||
First two components that we're going to add are the configuration provider and the resource provider
|
||||
for configuring the logging.
|
||||
|
||||
Put next lines into the ``containers.py`` file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
@ -222,28 +219,18 @@ Put next lines into the ``containers.py`` file:
|
|||
from dependency_injector import containers, providers
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
configure_logging = providers.Callable(
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
stream=sys.stdout,
|
||||
level=config.log.level,
|
||||
format=config.log.format,
|
||||
)
|
||||
|
||||
.. note::
|
||||
|
||||
We have used the configuration value before it was defined. That's the principle how the
|
||||
``Configuration`` provider works.
|
||||
|
||||
Use first, define later.
|
||||
|
||||
The configuration file will keep the logging settings.
|
||||
|
||||
Put next lines into the ``config.yml`` file:
|
||||
The configuration file will keep the logging settings. Put next lines into the ``config.yml`` file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
@ -251,37 +238,35 @@ Put next lines into the ``config.yml`` file:
|
|||
level: "INFO"
|
||||
format: "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s"
|
||||
|
||||
Now let's create the function that will run our daemon. It's traditionally called
|
||||
``main()``. The ``main()`` function will create the container. Then it will use the container
|
||||
to parse the ``config.yml`` file and call the logging configuration provider.
|
||||
Now let's create the function that will run our daemon. It's traditionally called ``main()``.
|
||||
The ``main()`` function will start the dispatcher, but we will keep it empty for now.
|
||||
We will create the container instance before calling ``main()`` in ``if __name__ == "__main__"``.
|
||||
Container instance will parse ``config.yml`` and then we will call the logging configuration provider.
|
||||
|
||||
Put next lines into the ``__main__.py`` file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Main module."""
|
||||
"""Main module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from .containers import Container
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the application."""
|
||||
container = ApplicationContainer()
|
||||
|
||||
container.config.from_yaml('config.yml')
|
||||
container.configure_logging()
|
||||
def main() -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.init_resources()
|
||||
|
||||
main()
|
||||
|
||||
.. note::
|
||||
|
||||
Container is the first object in the application.
|
||||
|
||||
The container is used to create all other objects.
|
||||
|
||||
Logging and configuration parsing part is done. In the next section we will create the monitoring
|
||||
Logging and configuration parsing part is done. In next section we will create the monitoring
|
||||
checks dispatcher.
|
||||
|
||||
Dispatcher
|
||||
|
@ -293,7 +278,7 @@ The dispatcher will control a list of the monitoring tasks. It will execute each
|
|||
to the configured schedule. The ``Monitor`` class is the base class for all the monitors. You can
|
||||
create different monitors by subclassing it and implementing the ``check()`` method.
|
||||
|
||||
.. image:: asyncio_images/class_1.png
|
||||
.. image:: asyncio-images/classes-01.png
|
||||
|
||||
Let's create dispatcher and the monitor base classes.
|
||||
|
||||
|
@ -336,7 +321,7 @@ and next into the ``dispatcher.py``:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
""""Dispatcher module."""
|
||||
"""Dispatcher module."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
@ -359,7 +344,7 @@ and next into the ``dispatcher.py``:
|
|||
asyncio.run(self.start())
|
||||
|
||||
async def start(self) -> None:
|
||||
self._logger.info('Starting up')
|
||||
self._logger.info("Starting up")
|
||||
|
||||
for monitor in self._monitors:
|
||||
self._monitor_tasks.append(
|
||||
|
@ -379,10 +364,11 @@ and next into the ``dispatcher.py``:
|
|||
|
||||
self._stopping = True
|
||||
|
||||
self._logger.info('Shutting down')
|
||||
self._logger.info("Shutting down")
|
||||
for task, monitor in zip(self._monitor_tasks, self._monitors):
|
||||
task.cancel()
|
||||
self._logger.info('Shutdown finished successfully')
|
||||
self._monitor_tasks.clear()
|
||||
self._logger.info("Shutdown finished successfully")
|
||||
|
||||
@staticmethod
|
||||
async def _run_monitor(monitor: Monitor) -> None:
|
||||
|
@ -398,7 +384,7 @@ and next into the ``dispatcher.py``:
|
|||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception:
|
||||
monitor.logger.exception('Error executing monitor check')
|
||||
monitor.logger.exception("Error executing monitor check")
|
||||
|
||||
await asyncio.sleep(_until_next(last=time_start))
|
||||
|
||||
|
@ -407,9 +393,9 @@ Now we need to add the dispatcher to the container.
|
|||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 8,23-28
|
||||
:emphasize-lines: 8,22-27
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
@ -419,12 +405,11 @@ Edit ``containers.py``:
|
|||
from . import dispatcher
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
configure_logging = providers.Callable(
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
stream=sys.stdout,
|
||||
level=config.log.level,
|
||||
|
@ -438,35 +423,33 @@ Edit ``containers.py``:
|
|||
),
|
||||
)
|
||||
|
||||
.. note::
|
||||
At the last we will inject dispatcher into the ``main()`` function
|
||||
and call the ``run()`` method. We will use :ref:`wiring` feature.
|
||||
|
||||
Every component should be added to the container.
|
||||
|
||||
At the last we will add the dispatcher in the ``main()`` function. We will retrieve the
|
||||
dispatcher instance from the container and call the ``run()`` method.
|
||||
|
||||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 13-14
|
||||
:emphasize-lines: 3-5,9-11,17
|
||||
|
||||
"""Main module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .dispatcher import Dispatcher
|
||||
from .containers import Container
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the application."""
|
||||
container = ApplicationContainer()
|
||||
|
||||
container.config.from_yaml('config.yml')
|
||||
container.configure_logging()
|
||||
|
||||
dispatcher = container.dispatcher()
|
||||
@inject
|
||||
def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None:
|
||||
dispatcher.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.init_resources()
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
main()
|
||||
|
||||
Finally let's start the daemon to check that all works.
|
||||
|
@ -475,28 +458,28 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
docker compose up
|
||||
|
||||
The output should look like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Starting monitoring-daemon-tutorial_monitor_1 ... done
|
||||
Attaching to monitoring-daemon-tutorial_monitor_1
|
||||
Starting asyncio-daemon-tutorial_monitor_1 ... done
|
||||
Attaching to asyncio-daemon-tutorial_monitor_1
|
||||
monitor_1 | [2020-08-08 16:12:35,772] [INFO] [Dispatcher]: Starting up
|
||||
monitor_1 | [2020-08-08 16:12:35,774] [INFO] [Dispatcher]: Shutting down
|
||||
monitor_1 | [2020-08-08 16:12:35,774] [INFO] [Dispatcher]: Shutdown finished successfully
|
||||
monitoring-daemon-tutorial_monitor_1 exited with code 0
|
||||
asyncio-daemon-tutorial_monitor_1 exited with code 0
|
||||
|
||||
Everything works properly. Dispatcher starts up and exits because there are no monitoring tasks.
|
||||
|
||||
By the end of this section we have the application skeleton ready. In the next section will will
|
||||
By the end of this section we have the application skeleton ready. In next section will will
|
||||
add first monitoring task.
|
||||
|
||||
Example.com monitor
|
||||
-------------------
|
||||
|
||||
In this section we will add the monitoring task that will check the availability of the
|
||||
In this section we will add a monitoring task that will check the availability of the
|
||||
`http://example.com <http://example.com>`_.
|
||||
|
||||
We will start from the extending of our class model with a new type of the monitoring check, the
|
||||
|
@ -506,9 +489,9 @@ The ``HttpMonitor`` is a subclass of the ``Monitor``. We will implement the ``ch
|
|||
will send the HTTP request to the specified URL. The http request sending will be delegated to
|
||||
the ``HttpClient``.
|
||||
|
||||
.. image:: asyncio_images/class_2.png
|
||||
.. image:: asyncio-images/classes-02.png
|
||||
|
||||
First, we need to create the ``HttpClient``.
|
||||
First we need to create the ``HttpClient``.
|
||||
|
||||
Create ``http.py`` in the ``monitoringdaemon`` package:
|
||||
|
||||
|
@ -549,9 +532,9 @@ Now we need to add the ``HttpClient`` to the container.
|
|||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 8, 23
|
||||
:emphasize-lines: 8,22
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
@ -561,12 +544,11 @@ Edit ``containers.py``:
|
|||
from . import http, dispatcher
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
configure_logging = providers.Callable(
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
stream=sys.stdout,
|
||||
level=config.log.level,
|
||||
|
@ -587,7 +569,7 @@ Now we're ready to add the ``HttpMonitor``. We will add it to the ``monitors`` m
|
|||
Edit ``monitors.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 4-5,7,20-56
|
||||
:emphasize-lines: 4-7,20-56
|
||||
|
||||
"""Monitors module."""
|
||||
|
||||
|
@ -616,10 +598,10 @@ Edit ``monitors.py``:
|
|||
options: Dict[str, Any],
|
||||
) -> None:
|
||||
self._client = http_client
|
||||
self._method = options.pop('method')
|
||||
self._url = options.pop('url')
|
||||
self._timeout = options.pop('timeout')
|
||||
super().__init__(check_every=options.pop('check_every'))
|
||||
self._method = options.pop("method")
|
||||
self._url = options.pop("url")
|
||||
self._timeout = options.pop("timeout")
|
||||
super().__init__(check_every=options.pop("check_every"))
|
||||
|
||||
async def check(self) -> None:
|
||||
time_start = time.time()
|
||||
|
@ -634,11 +616,11 @@ Edit ``monitors.py``:
|
|||
time_took = time_end - time_start
|
||||
|
||||
self.logger.info(
|
||||
'Check\n'
|
||||
' %s %s\n'
|
||||
' response code: %s\n'
|
||||
' content length: %s\n'
|
||||
' request took: %s seconds\n',
|
||||
"Check\n"
|
||||
" %s %s\n"
|
||||
" response code: %s\n"
|
||||
" content length: %s\n"
|
||||
" request took: %s seconds",
|
||||
self._method,
|
||||
self._url,
|
||||
response.status,
|
||||
|
@ -655,9 +637,9 @@ We make two changes in the container:
|
|||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 8,25-29,34
|
||||
:emphasize-lines: 8,24-28,33
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
@ -667,12 +649,11 @@ Edit ``containers.py``:
|
|||
from . import http, monitors, dispatcher
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
configure_logging = providers.Callable(
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
stream=sys.stdout,
|
||||
level=config.log.level,
|
||||
|
@ -721,21 +702,20 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
docker compose up
|
||||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Starting monitoring-daemon-tutorial_monitor_1 ... done
|
||||
Attaching to monitoring-daemon-tutorial_monitor_1
|
||||
Starting asyncio-daemon-tutorial_monitor_1 ... done
|
||||
Attaching to asyncio-daemon-tutorial_monitor_1
|
||||
monitor_1 | [2020-08-08 17:06:41,965] [INFO] [Dispatcher]: Starting up
|
||||
monitor_1 | [2020-08-08 17:06:42,033] [INFO] [HttpMonitor]: Check
|
||||
monitor_1 | GET http://example.com
|
||||
monitor_1 | response code: 200
|
||||
monitor_1 | content length: 648
|
||||
monitor_1 | request took: 0.067 seconds
|
||||
monitor_1 |
|
||||
monitor_1 | [2020-08-08 17:06:47,040] [INFO] [HttpMonitor]: Check
|
||||
monitor_1 | GET http://example.com
|
||||
monitor_1 | response code: 200
|
||||
|
@ -744,21 +724,21 @@ You should see:
|
|||
|
||||
Our daemon can monitor `http://example.com <http://example.com>`_ availability.
|
||||
|
||||
Let's add the monitor for the `http://httpbin.org <http://httpbin.org>`_.
|
||||
Let's add a monitor for the `https://httpbin.org <https://httpbin.org>`_.
|
||||
|
||||
Httpbin.org monitor
|
||||
-------------------
|
||||
|
||||
Adding of the monitor for the `httpbin.org`_ will be much easier because we have all the
|
||||
components ready. We just need to create a new provider in the container and update the
|
||||
configuration.
|
||||
Adding of a monitor for the `https://httpbin.org <https://httpbin.org>`_ will be much
|
||||
easier because we have all the components ready. We just need to create a new provider
|
||||
in the container and update the configuration.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 31-35,41
|
||||
:emphasize-lines: 30-34,40
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
@ -768,12 +748,11 @@ Edit ``containers.py``:
|
|||
from . import http, monitors, dispatcher
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
configure_logging = providers.Callable(
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
stream=sys.stdout,
|
||||
level=config.log.level,
|
||||
|
@ -831,33 +810,30 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
docker compose up
|
||||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Starting monitoring-daemon-tutorial_monitor_1 ... done
|
||||
Attaching to monitoring-daemon-tutorial_monitor_1
|
||||
Starting asyncio-daemon-tutorial_monitor_1 ... done
|
||||
Attaching to asyncio-daemon-tutorial_monitor_1
|
||||
monitor_1 | [2020-08-08 18:09:08,540] [INFO] [Dispatcher]: Starting up
|
||||
monitor_1 | [2020-08-08 18:09:08,618] [INFO] [HttpMonitor]: Check
|
||||
monitor_1 | GET http://example.com
|
||||
monitor_1 | response code: 200
|
||||
monitor_1 | content length: 648
|
||||
monitor_1 | request took: 0.077 seconds
|
||||
monitor_1 |
|
||||
monitor_1 | [2020-08-08 18:09:08,722] [INFO] [HttpMonitor]: Check
|
||||
monitor_1 | GET https://httpbin.org/get
|
||||
monitor_1 | response code: 200
|
||||
monitor_1 | content length: 310
|
||||
monitor_1 | request took: 0.18 seconds
|
||||
monitor_1 |
|
||||
monitor_1 | [2020-08-08 18:09:13,619] [INFO] [HttpMonitor]: Check
|
||||
monitor_1 | GET http://example.com
|
||||
monitor_1 | response code: 200
|
||||
monitor_1 | content length: 648
|
||||
monitor_1 | request took: 0.066 seconds
|
||||
monitor_1 |
|
||||
monitor_1 | [2020-08-08 18:09:13,681] [INFO] [HttpMonitor]: Check
|
||||
monitor_1 | GET https://httpbin.org/get
|
||||
monitor_1 | response code: 200
|
||||
|
@ -867,12 +843,12 @@ You should see:
|
|||
The functional part is done. Daemon monitors `http://example.com <http://example.com>`_ and
|
||||
`https://httpbin.org <https://httpbin.org>`_.
|
||||
|
||||
In the next section we will add some tests.
|
||||
In next section we will add some tests.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
It would be nice to add some tests. Let's do it.
|
||||
In this section we will add some tests.
|
||||
|
||||
We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
|
||||
`coverage <https://coverage.readthedocs.io/>`_.
|
||||
|
@ -899,7 +875,7 @@ Create ``tests.py`` in the ``monitoringdaemon`` package:
|
|||
and put next into it:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 54,70-71
|
||||
:emphasize-lines: 54,70-73
|
||||
|
||||
"""Tests module."""
|
||||
|
||||
|
@ -909,7 +885,7 @@ and put next into it:
|
|||
|
||||
import pytest
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
@ -920,33 +896,33 @@ and put next into it:
|
|||
|
||||
@pytest.fixture
|
||||
def container():
|
||||
container = ApplicationContainer()
|
||||
container.config.from_dict({
|
||||
'log': {
|
||||
'level': 'INFO',
|
||||
'formant': '[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s',
|
||||
},
|
||||
'monitors': {
|
||||
'example': {
|
||||
'method': 'GET',
|
||||
'url': 'http://fake-example.com',
|
||||
'timeout': 1,
|
||||
'check_every': 1,
|
||||
return Container(
|
||||
config={
|
||||
"log": {
|
||||
"level": "INFO",
|
||||
"formant": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s",
|
||||
},
|
||||
'httpbin': {
|
||||
'method': 'GET',
|
||||
'url': 'https://fake-httpbin.org/get',
|
||||
'timeout': 1,
|
||||
'check_every': 1,
|
||||
"monitors": {
|
||||
"example": {
|
||||
"method": "GET",
|
||||
"url": "http://fake-example.com",
|
||||
"timeout": 1,
|
||||
"check_every": 1,
|
||||
},
|
||||
"httpbin": {
|
||||
"method": "GET",
|
||||
"url": "https://fake-httpbin.org/get",
|
||||
"timeout": 1,
|
||||
"check_every": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return container
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_example_monitor(container, caplog):
|
||||
caplog.set_level('INFO')
|
||||
caplog.set_level("INFO")
|
||||
|
||||
http_client_mock = mock.AsyncMock()
|
||||
http_client_mock.request.return_value = RequestStub(
|
||||
|
@ -958,21 +934,22 @@ and put next into it:
|
|||
example_monitor = container.example_monitor()
|
||||
await example_monitor.check()
|
||||
|
||||
assert 'http://fake-example.com' in caplog.text
|
||||
assert 'response code: 200' in caplog.text
|
||||
assert 'content length: 635' in caplog.text
|
||||
assert "http://fake-example.com" in caplog.text
|
||||
assert "response code: 200" in caplog.text
|
||||
assert "content length: 635" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatcher(container, caplog, event_loop):
|
||||
caplog.set_level('INFO')
|
||||
caplog.set_level("INFO")
|
||||
|
||||
example_monitor_mock = mock.AsyncMock()
|
||||
httpbin_monitor_mock = mock.AsyncMock()
|
||||
|
||||
with container.example_monitor.override(example_monitor_mock), \
|
||||
container.httpbin_monitor.override(httpbin_monitor_mock):
|
||||
|
||||
with container.override_providers(
|
||||
example_monitor=example_monitor_mock,
|
||||
httpbin_monitor=httpbin_monitor_mock,
|
||||
):
|
||||
dispatcher = container.dispatcher()
|
||||
event_loop.create_task(dispatcher.start())
|
||||
await asyncio.sleep(0.1)
|
||||
|
@ -985,31 +962,32 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
platform linux -- Python 3.8.3, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
|
||||
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||
rootdir: /code
|
||||
plugins: asyncio-0.14.0, cov-2.10.0
|
||||
plugins: cov-6.0.0, asyncio-0.24.0
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
collected 2 items
|
||||
|
||||
monitoringdaemon/tests.py .. [100%]
|
||||
|
||||
----------- coverage: platform linux, python 3.8.3-final-0 -----------
|
||||
---------- coverage: platform linux, python 3.10.0-final-0 -----------
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------------------
|
||||
monitoringdaemon/__init__.py 0 0 100%
|
||||
monitoringdaemon/__main__.py 9 9 0%
|
||||
monitoringdaemon/__main__.py 11 11 0%
|
||||
monitoringdaemon/containers.py 11 0 100%
|
||||
monitoringdaemon/dispatcher.py 43 5 88%
|
||||
monitoringdaemon/dispatcher.py 45 5 89%
|
||||
monitoringdaemon/http.py 6 3 50%
|
||||
monitoringdaemon/monitors.py 23 1 96%
|
||||
monitoringdaemon/tests.py 37 0 100%
|
||||
monitoringdaemon/tests.py 35 0 100%
|
||||
----------------------------------------------------
|
||||
TOTAL 129 18 86%
|
||||
TOTAL 131 20 85%
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -1028,55 +1006,19 @@ In this tutorial we've built an ``asyncio`` monitoring daemon following the dep
|
|||
injection principle.
|
||||
We've used the ``Dependency Injector`` as a dependency injection framework.
|
||||
|
||||
The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
|
||||
when you need to understand or change your application structure. It's easy with the container,
|
||||
cause you have everything defined explicitly in one place:
|
||||
With a help of :ref:`containers` and :ref:`providers` we have defined how to assemble application components.
|
||||
|
||||
.. code-block:: python
|
||||
``List`` provider helped to inject a list of monitors into dispatcher.
|
||||
:ref:`configuration-provider` helped to deal with reading YAML file.
|
||||
|
||||
"""Application containers module."""
|
||||
We used :ref:`wiring` feature to inject dispatcher into the ``main()`` function.
|
||||
:ref:`provider-overriding` feature helped in testing.
|
||||
|
||||
import logging
|
||||
import sys
|
||||
We kept all the dependencies injected explicitly. This will help when you need to add or
|
||||
change something in future.
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
from . import http, monitors, dispatcher
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
configure_logging = providers.Callable(
|
||||
logging.basicConfig,
|
||||
stream=sys.stdout,
|
||||
level=config.log.level,
|
||||
format=config.log.format,
|
||||
)
|
||||
|
||||
http_client = providers.Factory(http.HttpClient)
|
||||
|
||||
example_monitor = providers.Factory(
|
||||
monitors.HttpMonitor,
|
||||
http_client=http_client,
|
||||
options=config.monitors.example,
|
||||
)
|
||||
|
||||
httpbin_monitor = providers.Factory(
|
||||
monitors.HttpMonitor,
|
||||
http_client=http_client,
|
||||
options=config.monitors.httpbin,
|
||||
)
|
||||
|
||||
dispatcher = providers.Factory(
|
||||
dispatcher.Dispatcher,
|
||||
monitors=providers.List(
|
||||
example_monitor,
|
||||
httpbin_monitor,
|
||||
),
|
||||
)
|
||||
You can find complete project on the
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/asyncio-daemon>`_.
|
||||
|
||||
What's next?
|
||||
|
||||
|
@ -1084,4 +1026,6 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
@ -46,11 +46,11 @@ How does Movie Lister work?
|
|||
Movie Lister is a naive example from Martin Fowler's article about the dependency injection and
|
||||
inversion of control:
|
||||
|
||||
http://www.martinfowler.com/articles/injection.html
|
||||
https://www.martinfowler.com/articles/injection.html
|
||||
|
||||
Here is a class diagram of the Movie Lister application:
|
||||
|
||||
.. image:: cli-images/classes_01.png
|
||||
.. image:: cli-images/classes-01.png
|
||||
|
||||
The responsibilities are split next way:
|
||||
|
||||
|
@ -63,18 +63,18 @@ Prepare the environment
|
|||
|
||||
Let's create the environment for the project.
|
||||
|
||||
First we need to create a project folder and the virtual environment:
|
||||
First we need to create a project folder:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
mkdir movie-lister-tutorial
|
||||
cd movie-lister-tutorial
|
||||
python3 -m venv venv
|
||||
|
||||
Now let's activate the virtual environment:
|
||||
Now let's create and activate virtual environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
|
||||
Project layout
|
||||
|
@ -84,7 +84,7 @@ Create next structure in the project root directory. All files are empty. That's
|
|||
|
||||
Initial project layout:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
|
||||
./
|
||||
├── movies/
|
||||
|
@ -109,7 +109,7 @@ Now it's time to install the project requirements. We will use next packages:
|
|||
|
||||
Put next lines into the ``requirements.txt`` file:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
|
||||
dependency-injector
|
||||
pyyaml
|
||||
|
@ -134,7 +134,7 @@ We will create a script that creates database files.
|
|||
First add the folder ``data/`` in the root of the project and then add the file
|
||||
``fixtures.py`` inside of it:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 2-3
|
||||
|
||||
./
|
||||
|
@ -160,19 +160,19 @@ Second put next in the ``fixtures.py``:
|
|||
|
||||
|
||||
SAMPLE_DATA = [
|
||||
('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'),
|
||||
('Rogue One: A Star Wars Story', 2016, 'Gareth Edwards'),
|
||||
('The Jungle Book', 2016, 'Jon Favreau'),
|
||||
("The Hunger Games: Mockingjay - Part 2", 2015, "Francis Lawrence"),
|
||||
("Rogue One: A Star Wars Story", 2016, "Gareth Edwards"),
|
||||
("The Jungle Book", 2016, "Jon Favreau"),
|
||||
]
|
||||
|
||||
FILE = pathlib.Path(__file__)
|
||||
DIR = FILE.parent
|
||||
CSV_FILE = DIR / 'movies.csv'
|
||||
SQLITE_FILE = DIR / 'movies.db'
|
||||
CSV_FILE = DIR / "movies.csv"
|
||||
SQLITE_FILE = DIR / "movies.db"
|
||||
|
||||
|
||||
def create_csv(movies_data, path):
|
||||
with open(path, 'w') as opened_file:
|
||||
with open(path, "w") as opened_file:
|
||||
writer = csv.writer(opened_file)
|
||||
for row in movies_data:
|
||||
writer.writerow(row)
|
||||
|
@ -181,20 +181,20 @@ Second put next in the ``fixtures.py``:
|
|||
def create_sqlite(movies_data, path):
|
||||
with sqlite3.connect(path) as db:
|
||||
db.execute(
|
||||
'CREATE TABLE IF NOT EXISTS movies '
|
||||
'(title text, year int, director text)'
|
||||
"CREATE TABLE IF NOT EXISTS movies "
|
||||
"(title text, year int, director text)"
|
||||
)
|
||||
db.execute('DELETE FROM movies')
|
||||
db.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data)
|
||||
db.execute("DELETE FROM movies")
|
||||
db.executemany("INSERT INTO movies VALUES (?,?,?)", movies_data)
|
||||
|
||||
|
||||
def main():
|
||||
create_csv(SAMPLE_DATA, CSV_FILE)
|
||||
create_sqlite(SAMPLE_DATA, SQLITE_FILE)
|
||||
print('OK')
|
||||
print("OK")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Now run in the terminal:
|
||||
|
@ -205,13 +205,13 @@ Now run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
|
||||
OK
|
||||
|
||||
Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 4-5
|
||||
|
||||
./
|
||||
|
@ -245,13 +245,13 @@ Edit ``containers.py``:
|
|||
from dependency_injector import containers
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
class Container(containers.DeclarativeContainer):
|
||||
...
|
||||
|
||||
Container is empty for now. We will add the providers in the following sections.
|
||||
|
||||
Let's also create the ``main()`` function. Its responsibility is to run our application. For now
|
||||
it will just create the container.
|
||||
it will just do nothing.
|
||||
|
||||
Edit ``__main__.py``:
|
||||
|
||||
|
@ -259,22 +259,18 @@ Edit ``__main__.py``:
|
|||
|
||||
"""Main module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from .containers import Container
|
||||
|
||||
|
||||
def main():
|
||||
container = ApplicationContainer()
|
||||
def main() -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
main()
|
||||
|
||||
.. note::
|
||||
|
||||
Container is the first object in the application.
|
||||
|
||||
The container is used to create all other objects.
|
||||
|
||||
Csv finder
|
||||
----------
|
||||
|
||||
|
@ -289,11 +285,11 @@ We will add:
|
|||
|
||||
After each step we will add the provider to the container.
|
||||
|
||||
.. image:: cli-images/classes_02.png
|
||||
.. image:: cli-images/classes-02.png
|
||||
|
||||
Create the ``entities.py`` in the ``movies`` package:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 10
|
||||
|
||||
./
|
||||
|
@ -325,7 +321,7 @@ and put next into it:
|
|||
self.director = str(director)
|
||||
|
||||
def __repr__(self):
|
||||
return '{0}(title={1}, year={2}, director={3})'.format(
|
||||
return "{0}(title={1}, year={2}, director={3})".format(
|
||||
self.__class__.__name__,
|
||||
repr(self.title),
|
||||
repr(self.year),
|
||||
|
@ -338,7 +334,7 @@ Now we need to add the ``Movie`` factory to the container. We need to add import
|
|||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3,5,9
|
||||
:emphasize-lines: 3,5,10
|
||||
|
||||
"""Containers module."""
|
||||
|
||||
|
@ -346,7 +342,8 @@ Edit ``containers.py``:
|
|||
|
||||
from . import entities
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
|
@ -359,7 +356,7 @@ Let's move on to the finders.
|
|||
|
||||
Create the ``finders.py`` in the ``movies`` package:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 11
|
||||
|
||||
./
|
||||
|
@ -420,7 +417,7 @@ Now let's add the csv finder into the container.
|
|||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5,9,13-18
|
||||
:emphasize-lines: 5,10,14-19
|
||||
|
||||
"""Containers module."""
|
||||
|
||||
|
@ -428,9 +425,10 @@ Edit ``containers.py``:
|
|||
|
||||
from . import finders, entities
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
|
@ -447,15 +445,9 @@ This is also called the delegation of the provider. If we just pass the movie fa
|
|||
as the dependency, it will be called when csv finder is created and the ``Movie`` instance will
|
||||
be injected. With the ``.provider`` attribute the provider itself will be injected.
|
||||
|
||||
The csv finder also has a few dependencies on the configuration options. We added configuration
|
||||
provider to provide these dependencies.
|
||||
|
||||
.. note::
|
||||
|
||||
We have used the configuration value before it was defined. That's the principle how the
|
||||
Configuration provider works.
|
||||
|
||||
Use first, define later.
|
||||
The csv finder also has a few dependencies on the configuration options. We added a configuration
|
||||
provider to provide these dependencies and specified the location of the configuration file.
|
||||
The configuration provider will parse the configuration file when we create a container instance.
|
||||
|
||||
Not let's define the configuration values.
|
||||
|
||||
|
@ -469,32 +461,11 @@ Edit ``config.yml``:
|
|||
path: "data/movies.csv"
|
||||
delimiter: ","
|
||||
|
||||
The configuration file is ready. Now let's update the ``main()`` function to specify its location.
|
||||
|
||||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 9
|
||||
|
||||
"""Main module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
|
||||
|
||||
def main():
|
||||
container = ApplicationContainer()
|
||||
|
||||
container.config.from_yaml('config.yml')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
Move on to the lister.
|
||||
The configuration file is ready. Move on to the lister.
|
||||
|
||||
Create the ``listers.py`` in the ``movies`` package:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 12
|
||||
|
||||
./
|
||||
|
@ -542,7 +513,7 @@ and put next into it:
|
|||
and edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5,20-23
|
||||
:emphasize-lines: 5,21-24
|
||||
|
||||
"""Containers module."""
|
||||
|
||||
|
@ -550,9 +521,10 @@ and edit ``containers.py``:
|
|||
|
||||
from . import finders, listers, entities
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
|
@ -570,36 +542,65 @@ and edit ``containers.py``:
|
|||
|
||||
All the components are created and added to the container.
|
||||
|
||||
Finally let's update the ``main()`` function.
|
||||
Let's inject the ``lister`` into the ``main()`` function.
|
||||
|
||||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 11-20
|
||||
:emphasize-lines: 3-5,9-10,16
|
||||
|
||||
"""Main module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
||||
|
||||
def main():
|
||||
container = ApplicationContainer()
|
||||
|
||||
container.config.from_yaml('config.yml')
|
||||
|
||||
lister = container.lister()
|
||||
|
||||
print(
|
||||
'Francis Lawrence movies:',
|
||||
lister.movies_directed_by('Francis Lawrence'),
|
||||
)
|
||||
print(
|
||||
'2016 movies:',
|
||||
lister.movies_released_in(2016),
|
||||
)
|
||||
@inject
|
||||
def main(lister: MovieLister = Provide[Container.lister]) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
main()
|
||||
|
||||
Now when we call ``main()`` the container will assemble and inject the movie lister.
|
||||
|
||||
Let's add some payload to ``main()`` function. It will list movies directed by
|
||||
Francis Lawrence and movies released in 2016.
|
||||
|
||||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 11-17
|
||||
|
||||
"""Main module."""
|
||||
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def main(lister: MovieLister = Provide[Container.lister]) -> None:
|
||||
print("Francis Lawrence movies:")
|
||||
for movie in lister.movies_directed_by("Francis Lawrence"):
|
||||
print("\t-", movie)
|
||||
|
||||
print("2016 movies:")
|
||||
for movie in lister.movies_released_in(2016):
|
||||
print("\t-", movie)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
main()
|
||||
|
||||
All set. Now we run the application.
|
||||
|
@ -612,12 +613,15 @@ Run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
|
||||
Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
|
||||
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]
|
||||
Francis Lawrence movies:
|
||||
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
||||
2016 movies:
|
||||
- Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards')
|
||||
- Movie(title='The Jungle Book', year=2016, director='Jon Favreau')
|
||||
|
||||
Our application can work with the movies database in the csv format. We also need to support
|
||||
Our application can work with the movies database in the csv format. We also want to support
|
||||
the sqlite format. We will deal with it in the next section.
|
||||
|
||||
Sqlite finder
|
||||
|
@ -680,7 +684,7 @@ Edit ``finders.py``:
|
|||
|
||||
def find_all(self) -> List[Movie]:
|
||||
with self._database as db:
|
||||
rows = db.execute('SELECT title, year, director FROM movies')
|
||||
rows = db.execute("SELECT title, year, director FROM movies")
|
||||
return [self._movie_factory(*row) for row in rows]
|
||||
|
||||
Now we need to add the sqlite finder to the container and update lister's dependency to use it.
|
||||
|
@ -688,7 +692,7 @@ Now we need to add the sqlite finder to the container and update lister's depend
|
|||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 20-24,28
|
||||
:emphasize-lines: 21-25,29
|
||||
|
||||
"""Containers module."""
|
||||
|
||||
|
@ -696,9 +700,10 @@ Edit ``containers.py``:
|
|||
|
||||
from . import finders, listers, entities
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
|
@ -747,10 +752,13 @@ Run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
|
||||
Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
|
||||
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]
|
||||
Francis Lawrence movies:
|
||||
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
||||
2016 movies:
|
||||
- Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards')
|
||||
- Movie(title='The Jungle Book', year=2016, director='Jon Favreau')
|
||||
|
||||
Our application now supports both formats: csv files and sqlite databases. Every time when we
|
||||
need to work with the different format we need to make a code change in the container. We will
|
||||
|
@ -782,9 +790,9 @@ Edit ``containers.py``:
|
|||
from . import finders, listers, entities
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
|
@ -812,7 +820,7 @@ Edit ``containers.py``:
|
|||
movie_finder=finder,
|
||||
)
|
||||
|
||||
The switch is the ``config.finder.type`` option. When its value is ``csv``, the provider under
|
||||
The switch is the ``config.finder.type`` option. When its value is ``csv``, the provider with the
|
||||
``csv`` key is used. The same is for ``sqlite``.
|
||||
|
||||
Now we need to read the value of the ``config.finder.type`` option from the environment variable
|
||||
|
@ -821,32 +829,32 @@ Now we need to read the value of the ``config.finder.type`` option from the envi
|
|||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 10
|
||||
:emphasize-lines: 22
|
||||
|
||||
"""Main module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
||||
|
||||
def main():
|
||||
container = ApplicationContainer()
|
||||
@inject
|
||||
def main(lister: MovieLister = Provide[Container.lister]) -> None:
|
||||
print("Francis Lawrence movies:")
|
||||
for movie in lister.movies_directed_by("Francis Lawrence"):
|
||||
print("\t-", movie)
|
||||
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.finder.type.from_env('MOVIE_FINDER_TYPE')
|
||||
|
||||
lister = container.lister()
|
||||
|
||||
print(
|
||||
'Francis Lawrence movies:',
|
||||
lister.movies_directed_by('Francis Lawrence'),
|
||||
)
|
||||
print(
|
||||
'2016 movies:',
|
||||
lister.movies_released_in(2016),
|
||||
)
|
||||
print("2016 movies:")
|
||||
for movie in lister.movies_released_in(2016):
|
||||
print("\t-", movie)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.finder.type.from_env("MOVIE_FINDER_TYPE")
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
|
||||
main()
|
||||
|
||||
Done.
|
||||
|
@ -858,12 +866,15 @@ Run in the terminal line by line:
|
|||
MOVIE_FINDER_TYPE=csv python -m movies
|
||||
MOVIE_FINDER_TYPE=sqlite python -m movies
|
||||
|
||||
The output should be something like this for each command:
|
||||
The output should be similar for each command:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
|
||||
Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
|
||||
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]
|
||||
Francis Lawrence movies:
|
||||
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
||||
2016 movies:
|
||||
- Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards')
|
||||
- Movie(title='The Jungle Book', year=2016, director='Jon Favreau')
|
||||
|
||||
In the next section we will add some tests.
|
||||
|
||||
|
@ -877,7 +888,7 @@ We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
|
|||
|
||||
Create ``tests.py`` in the ``movies`` package:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 13
|
||||
|
||||
./
|
||||
|
@ -900,7 +911,7 @@ Create ``tests.py`` in the ``movies`` package:
|
|||
and put next into it:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 35,50
|
||||
:emphasize-lines: 41,50
|
||||
|
||||
"""Tests module."""
|
||||
|
||||
|
@ -908,55 +919,55 @@ and put next into it:
|
|||
|
||||
import pytest
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def container():
|
||||
container = ApplicationContainer()
|
||||
container.config.from_dict({
|
||||
'finder': {
|
||||
'type': 'csv',
|
||||
'csv': {
|
||||
'path': '/fake-movies.csv',
|
||||
'delimiter': ',',
|
||||
},
|
||||
'sqlite': {
|
||||
'path': '/fake-movies.db',
|
||||
container = Container(
|
||||
config={
|
||||
"finder": {
|
||||
"type": "csv",
|
||||
"csv": {
|
||||
"path": "/fake-movies.csv",
|
||||
"delimiter": ",",
|
||||
},
|
||||
"sqlite": {
|
||||
"path": "/fake-movies.db",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
return container
|
||||
|
||||
|
||||
def test_movies_directed_by(container):
|
||||
@pytest.fixture
|
||||
def finder_mock(container):
|
||||
finder_mock = mock.Mock()
|
||||
finder_mock.find_all.return_value = [
|
||||
container.movie('The 33', 2015, 'Patricia Riggen'),
|
||||
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
|
||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
||||
]
|
||||
|
||||
return finder_mock
|
||||
|
||||
|
||||
def test_movies_directed_by(container, finder_mock):
|
||||
with container.finder.override(finder_mock):
|
||||
lister = container.lister()
|
||||
movies = lister.movies_directed_by('Jon Favreau')
|
||||
movies = lister.movies_directed_by("Jon Favreau")
|
||||
|
||||
assert len(movies) == 1
|
||||
assert movies[0].title == 'The Jungle Book'
|
||||
assert movies[0].title == "The Jungle Book"
|
||||
|
||||
|
||||
def test_movies_released_in(container):
|
||||
finder_mock = mock.Mock()
|
||||
finder_mock.find_all.return_value = [
|
||||
container.movie('The 33', 2015, 'Patricia Riggen'),
|
||||
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
|
||||
]
|
||||
|
||||
def test_movies_released_in(container, finder_mock):
|
||||
with container.finder.override(finder_mock):
|
||||
lister = container.lister()
|
||||
movies = lister.movies_released_in(2015)
|
||||
|
||||
assert len(movies) == 1
|
||||
assert movies[0].title == 'The 33'
|
||||
assert movies[0].title == "The 33"
|
||||
|
||||
Run in the terminal:
|
||||
|
||||
|
@ -966,26 +977,26 @@ Run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
|
||||
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
|
||||
plugins: cov-2.10.0
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: cov-3.0.0
|
||||
collected 2 items
|
||||
|
||||
movies/tests.py .. [100%]
|
||||
|
||||
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
|
||||
---------- coverage: platform darwin, python 3.10 -----------
|
||||
Name Stmts Miss Cover
|
||||
------------------------------------------
|
||||
movies/__init__.py 0 0 100%
|
||||
movies/__main__.py 10 10 0%
|
||||
movies/__main__.py 16 16 0%
|
||||
movies/containers.py 9 0 100%
|
||||
movies/entities.py 7 1 86%
|
||||
movies/finders.py 26 13 50%
|
||||
movies/listers.py 8 0 100%
|
||||
movies/tests.py 24 0 100%
|
||||
------------------------------------------
|
||||
TOTAL 84 24 71%
|
||||
TOTAL 90 30 67%
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -1002,48 +1013,19 @@ Conclusion
|
|||
In this tutorial we've built a CLI application following the dependency injection principle.
|
||||
We've used the ``Dependency Injector`` as a dependency injection framework.
|
||||
|
||||
The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
|
||||
when you need to understand or change your application structure. It's easy with the container,
|
||||
cause you have everything defined explicitly in one place:
|
||||
With a help of :ref:`containers` and :ref:`providers` we have defined how to assemble application components.
|
||||
|
||||
.. code-block:: python
|
||||
``Selector`` provider served as a switch for selecting the database format based on a configuration.
|
||||
:ref:`configuration-provider` helped to deal with reading a YAML file and environment variables.
|
||||
|
||||
"""Containers module."""
|
||||
We used :ref:`wiring` feature to inject the dependencies into the ``main()`` function.
|
||||
:ref:`provider-overriding` feature helped in testing.
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
We kept all the dependencies injected explicitly. This will help when you need to add or
|
||||
change something in future.
|
||||
|
||||
from . import finders, listers, entities
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
csv_finder = providers.Singleton(
|
||||
finders.CsvMovieFinder,
|
||||
movie_factory=movie.provider,
|
||||
path=config.finder.csv.path,
|
||||
delimiter=config.finder.csv.delimiter,
|
||||
)
|
||||
|
||||
sqlite_finder = providers.Singleton(
|
||||
finders.SqliteMovieFinder,
|
||||
movie_factory=movie.provider,
|
||||
path=config.finder.sqlite.path,
|
||||
)
|
||||
|
||||
finder = providers.Selector(
|
||||
config.finder.type,
|
||||
csv=csv_finder,
|
||||
sqlite=sqlite_finder,
|
||||
)
|
||||
|
||||
lister = providers.Factory(
|
||||
listers.MovieLister,
|
||||
movie_finder=finder,
|
||||
)
|
||||
You can find complete project on the
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/movie-lister>`_.
|
||||
|
||||
What's next?
|
||||
|
||||
|
@ -1051,4 +1033,6 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 647 KiB After Width: | Height: | Size: 647 KiB |
|
@ -21,7 +21,7 @@ Start from the scratch or jump to the section:
|
|||
:backlinks: none
|
||||
|
||||
You can find complete project on the
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/ghnav-flask>`_.
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
|
||||
|
||||
What are we going to build?
|
||||
---------------------------
|
||||
|
@ -43,25 +43,25 @@ How does Github Navigator work?
|
|||
- User can click on the repository, the repository owner or the last commit to open its web page
|
||||
on the Github.
|
||||
|
||||
.. image:: flask_images/screen_02.png
|
||||
.. image:: flask-images/screen-02.png
|
||||
|
||||
Prepare the environment
|
||||
-----------------------
|
||||
|
||||
Let's create the environment for the project.
|
||||
|
||||
First we need to create a project folder and the virtual environment:
|
||||
First we need to create a project folder:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
mkdir ghnav-flask-tutorial
|
||||
cd ghnav-flask-tutorial
|
||||
python3 -m venv venv
|
||||
|
||||
Now let's activate the virtual environment:
|
||||
Now let's create and activate virtual environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
|
||||
Project layout
|
||||
|
@ -110,13 +110,13 @@ You should see something like:
|
|||
.. code-block:: bash
|
||||
|
||||
(venv) $ python -c "import dependency_injector; print(dependency_injector.__version__)"
|
||||
3.22.0
|
||||
4.37.0
|
||||
(venv) $ python -c "import flask; print(flask.__version__)"
|
||||
1.1.2
|
||||
2.0.2
|
||||
|
||||
*Versions can be different. That's fine.*
|
||||
|
||||
Hello world!
|
||||
Hello World!
|
||||
------------
|
||||
|
||||
Let's create minimal application.
|
||||
|
@ -129,38 +129,29 @@ Put next into the ``views.py``:
|
|||
|
||||
|
||||
def index():
|
||||
return 'Hello, World!'
|
||||
return "Hello, World!"
|
||||
|
||||
Ok, we have the view.
|
||||
|
||||
Now let's create the main part of our application - the container. Container will keep all of the
|
||||
application components and their dependencies. First two providers we need to add are
|
||||
the ``Flask`` application provider and the view provider.
|
||||
Now let's create a container. Container will keep all of the application components and their dependencies.
|
||||
|
||||
Put next into the ``containers.py``:
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers
|
||||
from dependency_injector.ext import flask
|
||||
from flask import Flask
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
...
|
||||
|
||||
app = flask.Application(Flask, __name__)
|
||||
Container is empty for now. We will add the providers in the following sections.
|
||||
|
||||
index_view = flask.View(views.index)
|
||||
|
||||
Finally we need to create the Flask application factory. It is traditionally called
|
||||
``create_app()``. It will create the container. Then it will use the container to create
|
||||
the Flask application. Last step is to configure the routing - we will assign ``index_view`` from
|
||||
the container to handle user requests to the root ``/`` of our web application.
|
||||
Finally we need to create Flask application factory. It will create and configure container
|
||||
and Flask application. It is traditionally called ``create_app()``.
|
||||
We will assign ``index`` view to handle user requests to the root ``/`` of our web application.
|
||||
|
||||
Put next into the ``application.py``:
|
||||
|
||||
|
@ -168,26 +159,21 @@ Put next into the ``application.py``:
|
|||
|
||||
"""Application module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from flask import Flask
|
||||
|
||||
from .containers import Container
|
||||
from . import views
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create and return Flask application."""
|
||||
container = ApplicationContainer()
|
||||
def create_app() -> Flask:
|
||||
container = Container()
|
||||
|
||||
app = container.app()
|
||||
app = Flask(__name__)
|
||||
app.container = container
|
||||
|
||||
app.add_url_rule('/', view_func=container.index_view.as_view())
|
||||
app.add_url_rule("/", "index", views.index)
|
||||
|
||||
return app
|
||||
|
||||
.. note::
|
||||
|
||||
Container is the first object in the application.
|
||||
|
||||
The container is used to create all other objects.
|
||||
|
||||
Ok. Now we're ready to say "Hello, World!".
|
||||
|
||||
Do next in the terminal:
|
||||
|
@ -237,58 +223,34 @@ and run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install --upgrade -r requirements.txt
|
||||
|
||||
Now we need to add ``bootstrap-flask`` extension to the container.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 6,16
|
||||
|
||||
"""Application containers module."""
|
||||
|
||||
from dependency_injector import containers
|
||||
from dependency_injector.ext import flask
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
|
||||
app = flask.Application(Flask, __name__)
|
||||
|
||||
bootstrap = flask.Extension(Bootstrap)
|
||||
|
||||
index_view = flask.View(views.index)
|
||||
pip install -r requirements.txt
|
||||
|
||||
Let's initialize ``bootstrap-flask`` extension. We will need to modify ``create_app()``.
|
||||
|
||||
Edit ``application.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 13-14
|
||||
:emphasize-lines: 4,17-18
|
||||
|
||||
"""Application module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
|
||||
from .containers import Container
|
||||
from . import views
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create and return Flask application."""
|
||||
container = ApplicationContainer()
|
||||
def create_app() -> Flask:
|
||||
container = Container()
|
||||
|
||||
app = container.app()
|
||||
app = Flask(__name__)
|
||||
app.container = container
|
||||
app.add_url_rule("/", "index", views.index)
|
||||
|
||||
bootstrap = container.bootstrap()
|
||||
bootstrap = Bootstrap()
|
||||
bootstrap.init_app(app)
|
||||
|
||||
app.add_url_rule('/', view_func=container.index_view.as_view())
|
||||
|
||||
return app
|
||||
|
||||
Now we need to add the templates. For doing this we will need to add the folder ``templates/`` to
|
||||
|
@ -318,7 +280,7 @@ Now let's fill in the layout.
|
|||
|
||||
Put next into the ``base.html``:
|
||||
|
||||
.. code-block:: html
|
||||
.. code-block:: jinja
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
@ -351,7 +313,7 @@ And put something to the index page.
|
|||
|
||||
Put next into the ``index.html``:
|
||||
|
||||
.. code-block:: html
|
||||
.. code-block:: jinja
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
|
@ -436,13 +398,13 @@ Edit ``views.py``:
|
|||
|
||||
|
||||
def index():
|
||||
query = request.args.get('query', 'Dependency Injector')
|
||||
limit = request.args.get('limit', 10, int)
|
||||
query = request.args.get("query", "Dependency Injector")
|
||||
limit = request.args.get("limit", 10, int)
|
||||
|
||||
repositories = []
|
||||
|
||||
return render_template(
|
||||
'index.html',
|
||||
"index.html",
|
||||
query=query,
|
||||
limit=limit,
|
||||
repositories=repositories,
|
||||
|
@ -454,7 +416,7 @@ Make sure the app is running or use ``flask run`` and open ``http://127.0.0.1:50
|
|||
|
||||
You should see:
|
||||
|
||||
.. image:: flask_images/screen_01.png
|
||||
.. image:: flask-images/screen-01.png
|
||||
|
||||
Connect to the GitHub
|
||||
---------------------
|
||||
|
@ -477,41 +439,30 @@ and run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install --upgrade -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
Now we need to add Github API client the container. We will need to add two more providers from
|
||||
the ``dependency_injector.providers`` module:
|
||||
|
||||
- ``Factory`` provider that will create ``Github`` client.
|
||||
- ``Configuration`` provider that will be used for providing the API token and the request timeout
|
||||
for the ``Github`` client.
|
||||
|
||||
Let's do it.
|
||||
- ``Factory`` provider. It will create a ``Github`` client.
|
||||
- ``Configuration`` provider. It will provide an API token and a request timeout for the ``Github`` client.
|
||||
We will specify the location of the configuration file. The configuration provider will parse
|
||||
the configuration file when we create a container instance.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3,7,19,21-25
|
||||
:emphasize-lines: 3-4,9,11-15
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import flask
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
from github import Github
|
||||
|
||||
from . import views
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
|
||||
app = flask.Application(Flask, __name__)
|
||||
|
||||
bootstrap = flask.Extension(Bootstrap)
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
github_client = providers.Factory(
|
||||
Github,
|
||||
|
@ -519,20 +470,14 @@ Edit ``containers.py``:
|
|||
timeout=config.github.request_timeout,
|
||||
)
|
||||
|
||||
index_view = flask.View(views.index)
|
||||
|
||||
.. note::
|
||||
|
||||
We have used the configuration value before it was defined. That's the principle how
|
||||
``Configuration`` provider works.
|
||||
Don't forget to remove the Ellipsis ``...`` from the container. We don't need it anymore
|
||||
since we container is not empty.
|
||||
|
||||
Use first, define later.
|
||||
|
||||
Now let's add the configuration file.
|
||||
|
||||
We will use YAML.
|
||||
|
||||
Create an empty file ``config.yml`` in the root root of the project:
|
||||
Now let's add the configuration file. We will use YAML. Create an empty file ``config.yml``
|
||||
in the root of the project:
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 11
|
||||
|
@ -575,39 +520,36 @@ and install it:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install --upgrade -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
We will use environment variable ``GITHUB_TOKEN`` to provide the API token.
|
||||
|
||||
Now we need to edit ``create_app()`` to make two things when application starts:
|
||||
|
||||
- Load the configuration file the ``config.yml``.
|
||||
- Load the API token from the ``GITHUB_TOKEN`` environment variable.
|
||||
We will use the ``GITHUB_TOKEN`` environment variable to provide the API token. Let's edit
|
||||
``create_app()`` to fetch the token value from it.
|
||||
|
||||
Edit ``application.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 9-10
|
||||
:emphasize-lines: 12
|
||||
|
||||
"""Application module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
|
||||
from .containers import Container
|
||||
from . import views
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create and return Flask application."""
|
||||
container = ApplicationContainer()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.github.auth_token.from_env('GITHUB_TOKEN')
|
||||
def create_app() -> Flask:
|
||||
container = Container()
|
||||
container.config.github.auth_token.from_env("GITHUB_TOKEN")
|
||||
|
||||
app = container.app()
|
||||
app = Flask(__name__)
|
||||
app.container = container
|
||||
app.add_url_rule("/", "index", views.index)
|
||||
|
||||
bootstrap = container.bootstrap()
|
||||
bootstrap = Bootstrap()
|
||||
bootstrap.init_app(app)
|
||||
|
||||
app.add_url_rule('/', view_func=container.index_view.as_view())
|
||||
|
||||
return app
|
||||
|
||||
Now we need create an API token.
|
||||
|
@ -636,7 +578,7 @@ Github API client setup is done.
|
|||
Search service
|
||||
--------------
|
||||
|
||||
Now it's time to add the ``SearchService``. It will:
|
||||
Now it's time to add ``SearchService``. It will:
|
||||
|
||||
- Perform the search.
|
||||
- Fetch commit extra data for each result.
|
||||
|
@ -684,7 +626,7 @@ and put next into it:
|
|||
"""Search for repositories and return formatted data."""
|
||||
repositories = self._github_client.search_repositories(
|
||||
query=query,
|
||||
**{'in': 'name'},
|
||||
**{"in": "name"},
|
||||
)
|
||||
return [
|
||||
self._format_repo(repository)
|
||||
|
@ -694,22 +636,22 @@ and put next into it:
|
|||
def _format_repo(self, repository: Repository):
|
||||
commits = repository.get_commits()
|
||||
return {
|
||||
'url': repository.html_url,
|
||||
'name': repository.name,
|
||||
'owner': {
|
||||
'login': repository.owner.login,
|
||||
'url': repository.owner.html_url,
|
||||
'avatar_url': repository.owner.avatar_url,
|
||||
"url": repository.html_url,
|
||||
"name": repository.name,
|
||||
"owner": {
|
||||
"login": repository.owner.login,
|
||||
"url": repository.owner.html_url,
|
||||
"avatar_url": repository.owner.avatar_url,
|
||||
},
|
||||
'latest_commit': self._format_commit(commits[0]) if commits else {},
|
||||
"latest_commit": self._format_commit(commits[0]) if commits else {},
|
||||
}
|
||||
|
||||
def _format_commit(self, commit: Commit):
|
||||
return {
|
||||
'sha': commit.sha,
|
||||
'url': commit.html_url,
|
||||
'message': commit.commit.message,
|
||||
'author_name': commit.commit.author.name,
|
||||
"sha": commit.sha,
|
||||
"url": commit.html_url,
|
||||
"message": commit.commit.message,
|
||||
"author_name": commit.commit.author.name,
|
||||
}
|
||||
|
||||
Now let's add ``SearchService`` to the container.
|
||||
|
@ -717,27 +659,19 @@ Now let's add ``SearchService`` to the container.
|
|||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 9,27-30
|
||||
:emphasize-lines: 6,19-22
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import flask
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
from github import Github
|
||||
|
||||
from . import services, views
|
||||
from . import services
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
app = flask.Application(Flask, __name__)
|
||||
|
||||
bootstrap = flask.Extension(Bootstrap)
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
github_client = providers.Factory(
|
||||
Github,
|
||||
|
@ -750,64 +684,63 @@ Edit ``containers.py``:
|
|||
github_client=github_client,
|
||||
)
|
||||
|
||||
index_view = flask.View(views.index)
|
||||
Inject search service into view
|
||||
-------------------------------
|
||||
|
||||
Make the search work
|
||||
--------------------
|
||||
Now we are ready to make the search work.
|
||||
|
||||
Now we are ready to make the search work. Let's use the ``SearchService`` in the ``index`` view.
|
||||
Let's inject ``SearchService`` into the ``index`` view. We will use :ref:`Wiring` feature.
|
||||
|
||||
Edit ``views.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5,8,12
|
||||
:emphasize-lines: 4,6-7,10-11,15
|
||||
|
||||
"""Views module."""
|
||||
|
||||
from flask import request, render_template
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
def index(search_service: SearchService):
|
||||
query = request.args.get('query', 'Dependency Injector')
|
||||
limit = request.args.get('limit', 10, int)
|
||||
@inject
|
||||
def index(search_service: SearchService = Provide[Container.search_service]):
|
||||
query = request.args.get("query", "Dependency Injector")
|
||||
limit = request.args.get("limit", 10, int)
|
||||
|
||||
repositories = search_service.search_repositories(query, limit)
|
||||
|
||||
return render_template(
|
||||
'index.html',
|
||||
"index.html",
|
||||
query=query,
|
||||
limit=limit,
|
||||
repositories=repositories,
|
||||
)
|
||||
|
||||
Now let's inject the ``SearchService`` dependency into the ``index`` view.
|
||||
To make the injection work we need to wire the container with the ``views`` module.
|
||||
Let's configure the container to automatically make wiring with the ``views`` module when we
|
||||
create a container instance.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 32-35
|
||||
:emphasize-lines: 11
|
||||
|
||||
"""Application containers module."""
|
||||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import flask
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
from github import Github
|
||||
|
||||
from . import services, views
|
||||
from . import services
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
app = flask.Application(Flask, __name__)
|
||||
wiring_config = containers.WiringConfiguration(modules=[".views"])
|
||||
|
||||
bootstrap = flask.Extension(Bootstrap)
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
github_client = providers.Factory(
|
||||
Github,
|
||||
|
@ -820,16 +753,11 @@ Edit ``containers.py``:
|
|||
github_client=github_client,
|
||||
)
|
||||
|
||||
index_view = flask.View(
|
||||
views.index,
|
||||
search_service=search_service,
|
||||
)
|
||||
|
||||
Make sure the app is running or use ``flask run`` and open ``http://127.0.0.1:5000/``.
|
||||
|
||||
You should see:
|
||||
|
||||
.. image:: flask_images/screen_02.png
|
||||
.. image:: flask-images/screen-02.png
|
||||
|
||||
Make some refactoring
|
||||
---------------------
|
||||
|
@ -844,79 +772,35 @@ Let's make some refactoring. We will move these values to the config.
|
|||
Edit ``views.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 8-14
|
||||
:emphasize-lines: 11-17
|
||||
|
||||
"""Views module."""
|
||||
|
||||
from flask import request, render_template
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def index(
|
||||
search_service: SearchService,
|
||||
default_query: str,
|
||||
default_limit: int,
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
default_query: str = Provide[Container.config.default.query],
|
||||
default_limit: int = Provide[Container.config.default.limit.as_int()],
|
||||
):
|
||||
query = request.args.get('query', default_query)
|
||||
limit = request.args.get('limit', default_limit, int)
|
||||
query = request.args.get("query", default_query)
|
||||
limit = request.args.get("limit", default_limit, int)
|
||||
|
||||
repositories = search_service.search_repositories(query, limit)
|
||||
|
||||
return render_template(
|
||||
'index.html',
|
||||
"index.html",
|
||||
query=query,
|
||||
limit=limit,
|
||||
repositories=repositories,
|
||||
)
|
||||
|
||||
Now we need to inject these values. Let's update the container.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 35-36
|
||||
|
||||
"""Application containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import flask
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
from github import Github
|
||||
|
||||
from . import services, views
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
|
||||
app = flask.Application(Flask, __name__)
|
||||
|
||||
bootstrap = flask.Extension(Bootstrap)
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
github_client = providers.Factory(
|
||||
Github,
|
||||
login_or_token=config.github.auth_token,
|
||||
timeout=config.github.request_timeout,
|
||||
)
|
||||
|
||||
search_service = providers.Factory(
|
||||
services.SearchService,
|
||||
github_client=github_client,
|
||||
)
|
||||
|
||||
index_view = flask.View(
|
||||
views.index,
|
||||
search_service=search_service,
|
||||
default_query=config.search.default_query,
|
||||
default_limit=config.search.default_limit,
|
||||
)
|
||||
|
||||
Finally let's update the config.
|
||||
|
||||
Edit ``config.yml``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
@ -924,20 +808,18 @@ Edit ``config.yml``:
|
|||
|
||||
github:
|
||||
request_timeout: 10
|
||||
search:
|
||||
default_query: "Dependency Injector"
|
||||
default_limit: 10
|
||||
default:
|
||||
query: "Dependency Injector"
|
||||
limit: 10
|
||||
|
||||
That's it.
|
||||
|
||||
The refactoring is done. We've made it cleaner.
|
||||
That's it. The refactoring is done. We've made it cleaner.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
It would be nice to add some tests. Let's do this.
|
||||
In this section we will add some tests.
|
||||
|
||||
We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
|
||||
We will use `pytest <https://docs.pytest.org/en/stable/>`_ with its Flask extension and
|
||||
`coverage <https://coverage.readthedocs.io/>`_.
|
||||
|
||||
Edit ``requirements.txt``:
|
||||
|
@ -953,7 +835,7 @@ Edit ``requirements.txt``:
|
|||
pytest-flask
|
||||
pytest-cov
|
||||
|
||||
And let's install it:
|
||||
And install added packages:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -982,7 +864,7 @@ Create empty file ``tests.py`` in the ``githubnavigator`` package:
|
|||
and put next into it:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 42,65
|
||||
:emphasize-lines: 44,67
|
||||
|
||||
"""Tests module."""
|
||||
|
||||
|
@ -997,51 +879,53 @@ and put next into it:
|
|||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return create_app()
|
||||
app = create_app()
|
||||
yield app
|
||||
app.container.unwire()
|
||||
|
||||
|
||||
def test_index(client, app):
|
||||
github_client_mock = mock.Mock(spec=Github)
|
||||
github_client_mock.search_repositories.return_value = [
|
||||
mock.Mock(
|
||||
html_url='repo1-url',
|
||||
name='repo1-name',
|
||||
html_url="repo1-url",
|
||||
name="repo1-name",
|
||||
owner=mock.Mock(
|
||||
login='owner1-login',
|
||||
html_url='owner1-url',
|
||||
avatar_url='owner1-avatar-url',
|
||||
login="owner1-login",
|
||||
html_url="owner1-url",
|
||||
avatar_url="owner1-avatar-url",
|
||||
),
|
||||
get_commits=mock.Mock(return_value=[mock.Mock()]),
|
||||
),
|
||||
mock.Mock(
|
||||
html_url='repo2-url',
|
||||
name='repo2-name',
|
||||
html_url="repo2-url",
|
||||
name="repo2-name",
|
||||
owner=mock.Mock(
|
||||
login='owner2-login',
|
||||
html_url='owner2-url',
|
||||
avatar_url='owner2-avatar-url',
|
||||
login="owner2-login",
|
||||
html_url="owner2-url",
|
||||
avatar_url="owner2-avatar-url",
|
||||
),
|
||||
get_commits=mock.Mock(return_value=[mock.Mock()]),
|
||||
),
|
||||
]
|
||||
|
||||
with app.container.github_client.override(github_client_mock):
|
||||
response = client.get(url_for('index'))
|
||||
response = client.get(url_for("index"))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b'Results found: 2' in response.data
|
||||
assert b"Results found: 2" in response.data
|
||||
|
||||
assert b'repo1-url' in response.data
|
||||
assert b'repo1-name' in response.data
|
||||
assert b'owner1-login' in response.data
|
||||
assert b'owner1-url' in response.data
|
||||
assert b'owner1-avatar-url' in response.data
|
||||
assert b"repo1-url" in response.data
|
||||
assert b"repo1-name" in response.data
|
||||
assert b"owner1-login" in response.data
|
||||
assert b"owner1-url" in response.data
|
||||
assert b"owner1-avatar-url" in response.data
|
||||
|
||||
assert b'repo2-url' in response.data
|
||||
assert b'repo2-name' in response.data
|
||||
assert b'owner2-login' in response.data
|
||||
assert b'owner2-url' in response.data
|
||||
assert b'owner2-avatar-url' in response.data
|
||||
assert b"repo2-url" in response.data
|
||||
assert b"repo2-name" in response.data
|
||||
assert b"owner2-login" in response.data
|
||||
assert b"owner2-url" in response.data
|
||||
assert b"owner2-avatar-url" in response.data
|
||||
|
||||
|
||||
def test_index_no_results(client, app):
|
||||
|
@ -1049,10 +933,10 @@ and put next into it:
|
|||
github_client_mock.search_repositories.return_value = []
|
||||
|
||||
with app.container.github_client.override(github_client_mock):
|
||||
response = client.get(url_for('index'))
|
||||
response = client.get(url_for("index"))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b'Results found: 0' in response.data
|
||||
assert b"Results found: 0" in response.data
|
||||
|
||||
Now let's run it and check the coverage:
|
||||
|
||||
|
@ -1064,23 +948,23 @@ You should see:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
|
||||
plugins: flask-1.0.0, cov-2.10.0
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: cov-3.0.0, flask-1.2.0
|
||||
collected 2 items
|
||||
|
||||
githubnavigator/tests.py .. [100%]
|
||||
|
||||
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
|
||||
---------- coverage: platform darwin, python 3.10.0-final-0 ----------
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------------------
|
||||
githubnavigator/__init__.py 0 0 100%
|
||||
githubnavigator/application.py 11 0 100%
|
||||
githubnavigator/containers.py 13 0 100%
|
||||
githubnavigator/application.py 13 0 100%
|
||||
githubnavigator/containers.py 8 0 100%
|
||||
githubnavigator/services.py 14 0 100%
|
||||
githubnavigator/tests.py 32 0 100%
|
||||
githubnavigator/views.py 7 0 100%
|
||||
githubnavigator/tests.py 34 0 100%
|
||||
githubnavigator/views.py 10 0 100%
|
||||
----------------------------------------------------
|
||||
TOTAL 77 0 100%
|
||||
TOTAL 79 0 100%
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -1091,53 +975,22 @@ You should see:
|
|||
Conclusion
|
||||
----------
|
||||
|
||||
We are done.
|
||||
|
||||
In this tutorial we've built a ``Flask`` application following the dependency injection principle.
|
||||
We've used the ``Dependency Injector`` as a dependency injection framework.
|
||||
|
||||
The main part of this application is the container. It keeps all the application components and
|
||||
their dependencies defined explicitly in one place:
|
||||
:ref:`containers` and :ref:`providers` helped to specify how to assemble search service and
|
||||
integrate it with a 3rd-party library.
|
||||
|
||||
.. code-block:: python
|
||||
:ref:`configuration-provider` helped to deal with reading YAML file and environment variable.
|
||||
|
||||
"""Application containers module."""
|
||||
We used :ref:`wiring` feature to inject the dependencies into the ``index()`` view.
|
||||
:ref:`provider-overriding` feature helped in testing.
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import flask
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
from github import Github
|
||||
We kept all the dependencies injected explicitly. This will help when you need to add or
|
||||
change something in future.
|
||||
|
||||
from . import services, views
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
|
||||
app = flask.Application(Flask, __name__)
|
||||
|
||||
bootstrap = flask.Extension(Bootstrap)
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
github_client = providers.Factory(
|
||||
Github,
|
||||
login_or_token=config.github.auth_token,
|
||||
timeout=config.github.request_timeout,
|
||||
)
|
||||
|
||||
search_service = providers.Factory(
|
||||
services.SearchService,
|
||||
github_client=github_client,
|
||||
)
|
||||
|
||||
index_view = flask.View(
|
||||
views.index,
|
||||
search_service=search_service,
|
||||
default_query=config.search.default_query,
|
||||
default_limit=config.search.default_limit,
|
||||
)
|
||||
You can find complete project on the
|
||||
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
|
||||
|
||||
What's next?
|
||||
|
||||
|
@ -1145,5 +998,6 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
698
docs/wiring.rst
Normal file
|
@ -0,0 +1,698 @@
|
|||
.. _wiring:
|
||||
|
||||
Wiring
|
||||
======
|
||||
|
||||
Wiring feature provides a way to inject container providers into the functions and methods.
|
||||
|
||||
To use wiring you need:
|
||||
|
||||
- **Place @inject decorator**. Decorator ``@inject`` injects the dependencies.
|
||||
- **Place markers**. Wiring marker specifies what dependency to inject,
|
||||
e.g. ``Provide[Container.bar]``. This helps container to find the injections.
|
||||
- **Wire the container with the markers in the code**. Call ``container.wire()``
|
||||
specifying modules and packages you would like to wire it with.
|
||||
- **Use functions and classes as you normally do**. Framework will provide specified injections.
|
||||
|
||||
.. literalinclude:: ../examples/wiring/example.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:backlinks: none
|
||||
|
||||
Decorator @inject
|
||||
-----------------
|
||||
|
||||
Decorator ``@inject`` injects the dependencies. Use it to decorate all functions and methods
|
||||
with the injections.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.bar]):
|
||||
...
|
||||
|
||||
Decorator ``@inject`` must be specified as a very first decorator of a function to ensure that
|
||||
the wiring works appropriately. This will also contribute to the performance of the wiring process.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
@decorator_etc
|
||||
@decorator_2
|
||||
@decorator_1
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.bar]):
|
||||
...
|
||||
|
||||
Specifying the ``@inject`` as a first decorator is also crucial for FastAPI, other frameworks
|
||||
using decorators similarly, for closures, and for any types of custom decorators with the injections.
|
||||
|
||||
FastAPI example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.api_route("/")
|
||||
@inject
|
||||
async def index(service: Annotated[Service, Depends(Provide[Container.service])]):
|
||||
value = await service.process()
|
||||
return {"result": value}
|
||||
|
||||
Decorators example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def decorator1(func):
|
||||
@functools.wraps(func)
|
||||
@inject
|
||||
def wrapper(value1: int = Provide[Container.config.value1]):
|
||||
result = func()
|
||||
return result + value1
|
||||
return wrapper
|
||||
|
||||
|
||||
def decorator2(func):
|
||||
@functools.wraps(func)
|
||||
@inject
|
||||
def wrapper(value2: int = Provide[Container.config.value2]):
|
||||
result = func()
|
||||
return result + value2
|
||||
return wrapper
|
||||
|
||||
@decorator1
|
||||
@decorator2
|
||||
def sample():
|
||||
...
|
||||
|
||||
.. seealso::
|
||||
`Issue #404 <https://github.com/ets-labs/python-dependency-injector/issues/404#issuecomment-785216978>`_
|
||||
explains ``@inject`` decorator in a few more details.
|
||||
|
||||
Markers
|
||||
-------
|
||||
|
||||
Wiring feature uses markers to make injections. Injection marker is specified as a default value of
|
||||
a function or method argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.bar]):
|
||||
...
|
||||
|
||||
Specifying an annotation is optional.
|
||||
|
||||
To inject the provider itself use ``Provide[foo.provider]``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.providers import Factory
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]):
|
||||
bar = bar_provider(argument="baz")
|
||||
...
|
||||
|
||||
You can also use ``Provider[foo]`` for injecting the provider itself:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.providers import Factory
|
||||
from dependency_injector.wiring import inject, Provider
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar_provider: Factory[Bar] = Provider[Container.bar]):
|
||||
bar = bar_provider(argument="baz")
|
||||
...
|
||||
|
||||
You can use configuration, provided instance and sub-container providers as you normally do.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(token: str = Provide[Container.config.api_token]):
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(timeout: int = Provide[Container.config.timeout.as_(int)]):
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(baz: Baz = Provide[Container.bar.provided.baz]):
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.subcontainer.bar]):
|
||||
...
|
||||
|
||||
|
||||
You can compound wiring and ``Resource`` provider to implement per-function execution scope.
|
||||
See :ref:`Resources, wiring and per-function execution scope <resource-provider-wiring-closing>` for details.
|
||||
|
||||
Also you can use ``Provide`` marker to inject a container.
|
||||
|
||||
.. literalinclude:: ../examples/wiring/example_container.py
|
||||
:language: python
|
||||
:emphasize-lines: 14-17
|
||||
:lines: 3-
|
||||
|
||||
String identifiers
|
||||
------------------
|
||||
|
||||
You can use wiring with string identifiers. String identifier should match provider name in the container:
|
||||
|
||||
.. literalinclude:: ../examples/wiring/example_string_id.py
|
||||
:language: python
|
||||
:emphasize-lines: 15
|
||||
:lines: 3-
|
||||
|
||||
With string identifiers you don't need to use a container to specify an injection.
|
||||
|
||||
To specify an injection from a nested container use point ``.`` as a separator:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(service: UserService = Provide["services.user"]) -> None:
|
||||
...
|
||||
|
||||
You can also use injection modifiers:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.wiring import (
|
||||
inject,
|
||||
Provide,
|
||||
as_int,
|
||||
as_float,
|
||||
as_,
|
||||
required,
|
||||
invariant,
|
||||
provided,
|
||||
)
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide["config.option", as_int()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: float = Provide["config.option", as_float()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: Decimal = Provide["config.option", as_(Decimal)]) -> None:
|
||||
...
|
||||
|
||||
@inject
|
||||
def foo(value: str = Provide["config.option", required()]) -> None:
|
||||
...
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide["config.option", required().as_int()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide["config.option", invariant("config.switch")]) -> None:
|
||||
...
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide["service", provided().foo["bar"].call()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
To inject a container use special identifier ``<container>``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(container: Container = Provide["<container>"]) -> None:
|
||||
...
|
||||
|
||||
|
||||
Making injections into modules and class attributes
|
||||
---------------------------------------------------
|
||||
|
||||
You can use wiring to make injections into modules and class attributes. Both the classic marker
|
||||
syntax and the ``Annotated`` form are supported.
|
||||
|
||||
Classic marker syntax:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
service: Service = Provide[Container.service]
|
||||
|
||||
class Main:
|
||||
service: Service = Provide[Container.service]
|
||||
|
||||
Full example of the classic marker syntax:
|
||||
|
||||
.. literalinclude:: ../examples/wiring/example_attribute.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 14,19
|
||||
|
||||
Annotated form (Python 3.9+):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
service: Annotated[Service, Provide[Container.service]]
|
||||
|
||||
class Main:
|
||||
service: Annotated[Service, Provide[Container.service]]
|
||||
|
||||
Full example of the annotated form:
|
||||
|
||||
.. literalinclude:: ../examples/wiring/example_attribute_annotated.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 16,21
|
||||
|
||||
You could also use string identifiers to avoid a dependency on a container:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1,6
|
||||
|
||||
service: Service = Provide["service"]
|
||||
|
||||
|
||||
class Main:
|
||||
|
||||
service: Service = Provide["service"]
|
||||
|
||||
Wiring with modules and packages
|
||||
--------------------------------
|
||||
|
||||
To wire a container with the modules you need to call ``container.wire()`` method:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.wire(
|
||||
modules=[
|
||||
"yourapp.module1",
|
||||
"yourapp.module2",
|
||||
],
|
||||
)
|
||||
|
||||
Method ``container.wire()`` can resolve relative imports:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# In module "yourapp.main":
|
||||
|
||||
container.wire(
|
||||
modules=[
|
||||
".module1", # Resolved to: "yourapp.module1"
|
||||
".module2", # Resolved to: "yourapp.module2"
|
||||
],
|
||||
)
|
||||
|
||||
You can also manually specify a base package for resolving relative imports with
|
||||
the ``from_package`` argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# In module "yourapp.main":
|
||||
|
||||
container.wire(
|
||||
modules=[
|
||||
".module1", # Resolved to: "anotherapp.module1"
|
||||
".module2", # Resolved to: "anotherapp.module2"
|
||||
],
|
||||
from_package="anotherapp",
|
||||
)
|
||||
|
||||
Argument ``modules`` can also take already imported modules:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from yourapp import module1, module2
|
||||
|
||||
|
||||
container = Container()
|
||||
container.wire(modules=[module1, module2])
|
||||
|
||||
You can wire container with a package. Container walks recursively over the package modules:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.wire(
|
||||
packages=[
|
||||
"yourapp.package1",
|
||||
"yourapp.package2",
|
||||
],
|
||||
)
|
||||
|
||||
Arguments ``modules`` and ``packages`` can be used together.
|
||||
|
||||
When wiring is done functions and methods with the markers are patched to provide injections when called.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.bar]):
|
||||
...
|
||||
|
||||
|
||||
container = Container()
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
foo() # <--- Argument "bar" is injected
|
||||
|
||||
Injections are done as keyword arguments.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
foo() # Equivalent to:
|
||||
foo(bar=container.bar())
|
||||
|
||||
Context keyword arguments have a priority over injections.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
foo(bar=Bar()) # Bar() is injected
|
||||
|
||||
To unpatch previously patched functions and methods call ``container.unwire()`` method.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.unwire()
|
||||
|
||||
You can use that in testing to re-create and re-wire a container before each test.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class SomeTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.container = Container()
|
||||
self.container.wire(modules=["yourapp.module1", "yourapp.module2"])
|
||||
self.addCleanup(self.container.unwire)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def container():
|
||||
container = Container()
|
||||
container.wire(modules=["yourapp.module1", "yourapp.module2"])
|
||||
yield container
|
||||
container.unwire()
|
||||
|
||||
.. note::
|
||||
Wiring can take time if you have a large codebase. Consider to persist a container instance and
|
||||
avoid re-wiring between tests.
|
||||
|
||||
.. note::
|
||||
Python has a limitation on patching individually imported functions. To protect from errors
|
||||
prefer importing modules to importing individual functions or make sure imports happen
|
||||
after the wiring:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Potential error:
|
||||
|
||||
from .module import fn
|
||||
|
||||
fn()
|
||||
|
||||
Instead use next:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Always works:
|
||||
|
||||
from . import module
|
||||
|
||||
module.fn()
|
||||
|
||||
Wiring configuration
|
||||
--------------------
|
||||
|
||||
You can specify wiring configuration in the container. When wiring configuration is defined,
|
||||
container will call method ``.wire()`` automatically when you create an instance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
wiring_config = containers.WiringConfiguration(
|
||||
modules=[
|
||||
"yourapp.module1",
|
||||
"yourapp.module2",
|
||||
],
|
||||
packages=[
|
||||
"yourapp.package1",
|
||||
"yourapp.package2",
|
||||
],
|
||||
)
|
||||
|
||||
...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # container.wire() is called automatically
|
||||
...
|
||||
|
||||
You can also use relative imports. Container will resolve them corresponding
|
||||
to the module of the container class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# In module "yourapp.container":
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
wiring_config = containers.WiringConfiguration(
|
||||
modules=[
|
||||
".module1", # Resolved to: "yourapp.module1"
|
||||
".module2", # Resolved to: "yourapp.module2"
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# In module "yourapp.foo.bar.main":
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # wire to "yourapp.module1" and "yourapp.module2"
|
||||
...
|
||||
|
||||
To use wiring configuration and call method ``.wire()`` manually, set flag ``auto_wire=False``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
wiring_config = containers.WiringConfiguration(
|
||||
modules=["yourapp.module1"],
|
||||
auto_wire=False,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # container.wire() is NOT called automatically
|
||||
container.wire() # wire to "yourapp.module1"
|
||||
...
|
||||
|
||||
.. _async-injections-wiring:
|
||||
|
||||
Asynchronous injections
|
||||
-----------------------
|
||||
|
||||
Wiring feature supports asynchronous injections:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
db = providers.Resource(init_async_db_client)
|
||||
|
||||
cache = providers.Resource(init_async_cache_client)
|
||||
|
||||
|
||||
@inject
|
||||
async def main(
|
||||
db: Database = Provide[Container.db],
|
||||
cache: Cache = Provide[Container.cache],
|
||||
):
|
||||
...
|
||||
|
||||
When you call asynchronous function wiring prepares injections asynchronously.
|
||||
Here is what it does for previous example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
db, cache = await asyncio.gather(
|
||||
container.db(),
|
||||
container.cache(),
|
||||
)
|
||||
|
||||
await main(db=db, cache=cache)
|
||||
|
||||
You can also use ``Closing`` marker with the asynchronous ``Resource`` providers:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
async def main(
|
||||
db: Database = Closing[Provide[Container.db]],
|
||||
cache: Cache = Closing[Provide[Container.cache]],
|
||||
):
|
||||
...
|
||||
|
||||
Wiring does closing asynchronously:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
db, cache = await asyncio.gather(
|
||||
container.db(),
|
||||
container.cache(),
|
||||
)
|
||||
|
||||
await main(db=db, cache=cache)
|
||||
|
||||
await asyncio.gather(
|
||||
container.db.shutdown(),
|
||||
container.cache.shutdown(),
|
||||
)
|
||||
|
||||
See :ref:`Resources, wiring and per-function execution scope <resource-provider-wiring-closing>` for
|
||||
details on ``Closing`` marker.
|
||||
|
||||
.. note::
|
||||
|
||||
Wiring does not not convert asynchronous injections to synchronous.
|
||||
|
||||
It handles asynchronous injections only for ``async def`` functions. Asynchronous injections into
|
||||
synchronous ``def`` function still work, but you need to take care of awaitables by your own.
|
||||
|
||||
See also:
|
||||
|
||||
- Provider :ref:`async-injections`
|
||||
- Resource provider :ref:`resource-async-initializers`
|
||||
- :ref:`fastapi-redis-example`
|
||||
|
||||
Wiring of dynamically imported modules
|
||||
--------------------------------------
|
||||
|
||||
You can install an import hook that automatically wires containers to the imported modules.
|
||||
This is useful when you import modules dynamically.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import importlib
|
||||
|
||||
from dependency_injector.wiring import register_loader_containers
|
||||
|
||||
from .containers import Container
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
register_loader_containers(container) # <--- installs import hook
|
||||
|
||||
module = importlib.import_module("package.module")
|
||||
module.foo()
|
||||
|
||||
You can register multiple containers in the import hook. For doing this call register function
|
||||
with multiple containers ``register_loader_containers(container1, container2, ...)``
|
||||
or with a single container ``register_loader_containers(container)`` multiple times.
|
||||
|
||||
To unregister a container use ``unregister_loader_containers(container)``.
|
||||
Wiring module will uninstall the import hook when unregister last container.
|
||||
|
||||
Few notes on performance
|
||||
------------------------
|
||||
|
||||
``.wire()`` utilize caching to speed up the wiring process. At the end it clears the cache to avoid memory leaks.
|
||||
But this may not always be desirable, when you want to keep the cache for the next wiring
|
||||
(e.g. due to usage of multiple containers or during unit tests).
|
||||
|
||||
To keep the cache after wiring, you can set flag ``keep_cache=True`` (works with ``WiringConfiguration`` too):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container1.wire(
|
||||
modules=["yourapp.module1", "yourapp.module2"],
|
||||
keep_cache=True,
|
||||
)
|
||||
container2.wire(
|
||||
modules=["yourapp.module2", "yourapp.module3"],
|
||||
keep_cache=True,
|
||||
)
|
||||
...
|
||||
|
||||
and then clear it manually when you need it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.wiring import clear_cache
|
||||
|
||||
clear_cache()
|
||||
|
||||
|
||||
Integration with other frameworks
|
||||
---------------------------------
|
||||
|
||||
Wiring feature helps to integrate with other frameworks like Django, Flask, etc.
|
||||
|
||||
With wiring you do not need to change the traditional application structure of your framework.
|
||||
|
||||
1. Create a container and put framework-independent components as providers.
|
||||
2. Place wiring markers in the functions and methods where you want the providers
|
||||
to be injected (Flask or Django views, Aiohttp or Sanic handlers, etc).
|
||||
3. Wire the container with the application modules.
|
||||
4. Run the application.
|
||||
|
||||
.. literalinclude:: ../examples/wiring/flask_example.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
|
||||
Take a look at other application examples:
|
||||
|
||||
- :ref:`application-single-container`
|
||||
- :ref:`application-multiple-containers`
|
||||
- :ref:`decoupled-packages`
|
||||
- :ref:`boto3-example`
|
||||
- :ref:`django-example`
|
||||
- :ref:`flask-example`
|
||||
- :ref:`flask-blueprints-example`
|
||||
- :ref:`aiohttp-example`
|
||||
- :ref:`sanic-example`
|
||||
- :ref:`fastapi-example`
|
||||
- :ref:`fastapi-redis-example`
|
||||
- :ref:`fastapi-sqlalchemy-example`
|
||||
- :ref:`fastdepends-example`
|
||||
|
||||
.. disqus::
|
15
examples/containers/check_dependencies.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
"""Container dependencies check example."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service1 = providers.Dependency()
|
||||
service2 = providers.Dependency()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.check_dependencies() # <-- raises error:
|
||||
# Container has undefined dependencies: "Container.service1", "Container.service2"
|
|
@ -10,7 +10,7 @@ class Container(containers.DeclarativeContainer):
|
|||
factory2 = providers.Factory(object)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
object1 = container.factory1()
|
||||
|
@ -18,6 +18,6 @@ if __name__ == '__main__':
|
|||
|
||||
print(container.providers)
|
||||
# {
|
||||
# 'factory1': <dependency_injector.providers.Factory(...),
|
||||
# 'factory2': <dependency_injector.providers.Factory(...),
|
||||
# "factory1": <dependency_injector.providers.Factory(...),
|
||||
# "factory2": <dependency_injector.providers.Factory(...),
|
||||
# }
|
||||
|
|
31
examples/containers/declarative_copy_decorator1.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
"""Declarative container provider copying with ``@copy()`` decorator."""
|
||||
|
||||
import sqlite3
|
||||
from unittest import mock
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
|
||||
class Service:
|
||||
def __init__(self, db):
|
||||
self.db = db
|
||||
|
||||
|
||||
class SourceContainer(containers.DeclarativeContainer):
|
||||
|
||||
database = providers.Singleton(sqlite3.connect, ":memory:")
|
||||
service = providers.Factory(Service, db=database)
|
||||
|
||||
|
||||
# Copy ``SourceContainer`` providers into ``DestinationContainer``:
|
||||
@containers.copy(SourceContainer)
|
||||
class DestinationContainer(SourceContainer):
|
||||
|
||||
database = providers.Singleton(mock.Mock)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = DestinationContainer()
|
||||
|
||||
service = container.service()
|
||||
assert isinstance(service.db, mock.Mock)
|
33
examples/containers/declarative_copy_decorator2.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
"""Declarative container provider copying with ``@copy()`` decorator."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
|
||||
class Service:
|
||||
def __init__(self, dependency: str):
|
||||
self.dependency = dependency
|
||||
|
||||
|
||||
class Base(containers.DeclarativeContainer):
|
||||
dependency = providers.Dependency(instance_of=str, default="Default value")
|
||||
service = providers.Factory(Service, dependency=dependency)
|
||||
|
||||
|
||||
@containers.copy(Base)
|
||||
class Derived1(Base):
|
||||
dependency = providers.Dependency(instance_of=str, default="Derived 1")
|
||||
|
||||
|
||||
# @containers.copy(Base) # <-- No @copy decorator
|
||||
class Derived2(Base):
|
||||
dependency = providers.Dependency(instance_of=str, default="Derived 2")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container1 = Derived1()
|
||||
service1 = container1.service()
|
||||
print(service1.dependency) # Derived 1
|
||||
|
||||
container2 = Derived2()
|
||||
service2 = container2.service()
|
||||
print(service2.dependency) # Default value
|
|
@ -14,21 +14,21 @@ class ContainerB(ContainerA):
|
|||
|
||||
|
||||
assert ContainerA.providers == {
|
||||
'provider1': ContainerA.provider1,
|
||||
"provider1": ContainerA.provider1,
|
||||
}
|
||||
assert ContainerB.providers == {
|
||||
'provider1': ContainerA.provider1,
|
||||
'provider2': ContainerB.provider2,
|
||||
"provider1": ContainerA.provider1,
|
||||
"provider2": ContainerB.provider2,
|
||||
}
|
||||
|
||||
assert ContainerA.cls_providers == {
|
||||
'provider1': ContainerA.provider1,
|
||||
"provider1": ContainerA.provider1,
|
||||
}
|
||||
assert ContainerB.cls_providers == {
|
||||
'provider2': ContainerB.provider2,
|
||||
"provider2": ContainerB.provider2,
|
||||
}
|
||||
|
||||
assert ContainerA.inherited_providers == {}
|
||||
assert ContainerB.inherited_providers == {
|
||||
'provider1': ContainerA.provider1,
|
||||
"provider1": ContainerA.provider1,
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class AuthService:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
database = providers.Singleton(sqlite3.connect, ':memory:')
|
||||
database = providers.Singleton(sqlite3.connect, ":memory:")
|
||||
|
||||
user_service = providers.Factory(
|
||||
UserService,
|
||||
|
@ -32,7 +32,7 @@ class Container(containers.DeclarativeContainer):
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
user_service = container.user_service()
|
||||
|
|
25
examples/containers/declarative_override_decorator.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
"""Declarative container provider overriding with ``@override()`` decorator."""
|
||||
|
||||
import sqlite3
|
||||
from unittest import mock
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
database = providers.Singleton(sqlite3.connect, ":memory:")
|
||||
|
||||
|
||||
# Overriding ``Container`` with ``OverridingContainer``:
|
||||
@containers.override(Container)
|
||||
class OverridingContainer(containers.DeclarativeContainer):
|
||||
|
||||
database = providers.Singleton(mock.Mock)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
database = container.database()
|
||||
assert isinstance(database, mock.Mock)
|
|
@ -8,10 +8,10 @@ from dependency_injector import containers, providers
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
database = providers.Singleton(sqlite3.connect, ':memory:')
|
||||
database = providers.Singleton(sqlite3.connect, ":memory:")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container(database=mock.Mock(sqlite3.Connection))
|
||||
|
||||
database = container.database()
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from dependency_injector import containers, providers
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = containers.DynamicContainer()
|
||||
container.factory1 = providers.Factory(object)
|
||||
container.factory2 = providers.Factory(object)
|
||||
|
@ -13,6 +13,6 @@ if __name__ == '__main__':
|
|||
|
||||
print(container.providers)
|
||||
# {
|
||||
# 'factory1': <dependency_injector.providers.Factory(...),
|
||||
# 'factory2': <dependency_injector.providers.Factory(...),
|
||||
# "factory1": <dependency_injector.providers.Factory(...),
|
||||
# "factory2": <dependency_injector.providers.Factory(...),
|
||||
# }
|
||||
|
|
|
@ -13,20 +13,20 @@ class AuthService:
|
|||
|
||||
def populate_container(container, providers_config):
|
||||
for provider_name, provider_info in providers_config.items():
|
||||
provided_cls = globals().get(provider_info['class'])
|
||||
provider_cls = getattr(providers, provider_info['provider_class'])
|
||||
provided_cls = globals().get(provider_info["class"])
|
||||
provider_cls = getattr(providers, provider_info["provider_class"])
|
||||
setattr(container, provider_name, provider_cls(provided_cls))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
services_config = {
|
||||
'user': {
|
||||
'class': 'UserService',
|
||||
'provider_class': 'Factory',
|
||||
"user": {
|
||||
"class": "UserService",
|
||||
"provider_class": "Factory",
|
||||
},
|
||||
'auth': {
|
||||
'class': 'AuthService',
|
||||
'provider_class': 'Factory',
|
||||
"auth": {
|
||||
"class": "AuthService",
|
||||
"provider_class": "Factory",
|
||||
},
|
||||
}
|
||||
services = containers.DynamicContainer()
|
||||
|
|