Compare commits
2098 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 | ||
|
53b7ad0275 | ||
|
1f136e427d | ||
|
9ee4959f76 | ||
|
39368591b9 | ||
|
d8439a28b1 | ||
|
f56b5398ef | ||
|
6febd03646 | ||
|
0cddc4cf25 | ||
|
07f050d2ad | ||
|
203427aa97 | ||
|
ffc477d1ee | ||
|
505ccf5a1d | ||
|
5740dd6df5 | ||
|
ac8212a95b | ||
|
a305660a08 | ||
|
21764dbeec | ||
|
172c175e89 | ||
|
8dcf6d99ce | ||
|
5cb7917a68 | ||
|
b4772af2c1 | ||
|
7e11d56ad6 | ||
|
81ab8f807a | ||
|
2b4d211207 | ||
|
f75d90b21b | ||
|
31f15c0a13 | ||
|
ba574660d8 | ||
|
2d28bc0d02 | ||
|
039e51d4ba | ||
|
139e67dd95 | ||
|
e1844a3040 | ||
|
8284d1f169 | ||
|
d93c4a419b | ||
|
36ece67586 | ||
|
f84d3e6f4e | ||
|
af51937004 | ||
|
098ecb4eec | ||
|
5306b27c48 | ||
|
e257fd9cb0 | ||
|
6d2a0382a7 | ||
|
29f209d382 | ||
|
06bc0f1bac | ||
|
5bb9d221a5 | ||
|
bf978601ba | ||
|
d16e8817db | ||
|
72b2d0570f | ||
|
4ec3cce1d0 | ||
|
d6dbef0a75 | ||
|
0a10f32233 | ||
|
f0004c805f | ||
|
74df2ed00d | ||
|
14c2ecae8f | ||
|
2cf5efa031 | ||
|
5f7d978012 | ||
|
b5b6c1f680 | ||
|
913ce01475 | ||
|
441cc66427 | ||
|
ad260fe709 | ||
|
d6e4e8fb08 | ||
|
4f111aae9b | ||
|
d61281a0b9 | ||
|
e48746d65f | ||
|
2a23f3d2f4 | ||
|
6528411271 | ||
|
b074d2aeb7 | ||
|
a497cb2527 | ||
|
1eb9020a4e | ||
|
a7afa66e40 | ||
|
33b4416c2c | ||
|
8b13a809e6 | ||
|
ca986698e9 | ||
|
13286783d0 | ||
|
b2b8d2f4cf | ||
|
f32363d259 | ||
|
f5758d842f | ||
|
063301a0b3 | ||
|
27ea3e3156 | ||
|
6012d97a70 | ||
|
e29bf348ea | ||
|
580a9a66b5 | ||
|
c2426d2a09 | ||
|
dd2ded7321 | ||
|
aeace8cba5 | ||
|
e4ca126188 | ||
|
3eb7b9bc69 | ||
|
f8648adaf7 | ||
|
dcc59ab0f4 | ||
|
806bd31735 | ||
|
89fec6c905 | ||
|
0985635ea6 | ||
|
c4b33749d2 | ||
|
995b2165df | ||
|
0bb30f91ef | ||
|
520945483f | ||
|
0d9aaaac0b | ||
|
1ad852d193 | ||
|
f83d8ce143 | ||
|
3c27cd6ab2 | ||
|
2e940adb50 | ||
|
47f4279ccd | ||
|
15acccf48f | ||
|
0c868fc0db | ||
|
cc7ade89d7 | ||
|
9d85b63c3b | ||
|
2b30e172d1 | ||
|
3f94a900a4 | ||
|
6c7c9eaa47 | ||
|
0f952b5915 | ||
|
fcbab97db2 | ||
|
f5b97ca92e | ||
|
69ebc19b5f | ||
|
cc8536b5e4 | ||
|
c05f2b2e42 | ||
|
cf862fe8b5 | ||
|
50a9dda192 | ||
|
e15a6dbb1d | ||
|
597f6794a9 | ||
|
01a18bc2ff | ||
|
d2828519b4 | ||
|
75c65f334e | ||
|
cfdcbaa77a | ||
|
e0fa746d7f | ||
|
e479e2cb94 | ||
|
925801c73f | ||
|
684745a6b4 | ||
|
88866d596b | ||
|
ac6412bd41 | ||
|
32a4add687 | ||
|
f67dab1f03 | ||
|
3ddf131180 | ||
|
2f42de9fa1 | ||
|
5358dd85f1 | ||
|
ca18fea26c | ||
|
3c69b4683f | ||
|
bb1403b5e9 | ||
|
1c189c47e2 | ||
|
d8102a825f | ||
|
ed2a795581 | ||
|
c9762ce6fa | ||
|
2bef0229d9 | ||
|
d3f3b58389 | ||
|
3a61457be7 | ||
|
2ff36b44ab | ||
|
0cb45bbc1c | ||
|
5bf5a7651b | ||
|
9b30752da2 | ||
|
4a8133204c | ||
|
2fc3606671 | ||
|
dc9c1dde3f | ||
|
1f302ec74c | ||
|
a202335519 | ||
|
ea1e79885c | ||
|
459ff5fcf5 | ||
|
6fccb6eec1 | ||
|
45e6b943f6 | ||
|
299a7bca89 | ||
|
978ebda9eb | ||
|
d2f4585a8b | ||
|
97577d84e4 | ||
|
adcea61657 | ||
|
577c7854da | ||
|
4ca385d73b | ||
|
469cb37302 | ||
|
c190e049b5 | ||
|
ed2fc618f1 | ||
|
e0d81c2d28 | ||
|
bed547cc91 | ||
|
8f5ea52303 | ||
|
adf77c2399 | ||
|
4a1caf7300 | ||
|
00d91ae829 | ||
|
1f115111b2 | ||
|
cfbed20a05 | ||
|
93adbe5207 | ||
|
1674cecc8d | ||
|
3ee629eb5d | ||
|
78b7be218b | ||
|
b3c0a79725 | ||
|
6eff213a68 | ||
|
8a2a88ccc9 | ||
|
8b2486c5e5 | ||
|
f05c6bef29 | ||
|
5ada7f24dd | ||
|
0c77e73d51 | ||
|
73cfc873ce | ||
|
72147b664e | ||
|
359dce2978 | ||
|
70d6e8c68f | ||
|
24addb65eb | ||
|
a2ac13c551 | ||
|
a782170579 | ||
|
0b906be150 | ||
|
b6bf490239 | ||
|
603ecf0838 | ||
|
8dc1e9d4a4 | ||
|
3c2b6bb558 | ||
|
4f753c8fd3 | ||
|
21cdd0aa3c | ||
|
d6176dc754 | ||
|
fa469618dd | ||
|
f4c713fc22 | ||
|
a8135c0628 | ||
|
f130d41dbc | ||
|
ec3be85cf7 | ||
|
5e84f3145e | ||
|
0da9f2c6b5 | ||
|
cc90f66dc8 | ||
|
4a82aa18c3 | ||
|
75a9f6faf0 | ||
|
18bac2b1d6 | ||
|
f84daf6467 | ||
|
dfe61826b0 | ||
|
0126fdb522 | ||
|
f8b78443f9 | ||
|
1ca6f303c9 | ||
|
8681500236 | ||
|
17621d9fd7 | ||
|
1a5e7baf7b | ||
|
a0a3da8f72 | ||
|
2513d1f600 | ||
|
783be3ad36 | ||
|
284b258879 | ||
|
7491fc05a2 | ||
|
1cd25e701f | ||
|
5718140a82 | ||
|
525ddbc24c | ||
|
f176b02045 | ||
|
62e1995a7d | ||
|
507a832bb8 | ||
|
3217feb73b | ||
|
e5275cf3f2 | ||
|
65db4f2b1d | ||
|
ad2d8155d4 | ||
|
c8b0fa22af | ||
|
e22edcdadd | ||
|
0dad8727cf | ||
|
1a23b8b6f5 | ||
|
50d8363650 | ||
|
52c98bbe49 | ||
|
f7871dfc49 | ||
|
597f5d772f | ||
|
a76744805a | ||
|
2c43a1c426 | ||
|
feab6b6814 | ||
|
b727be3f9a | ||
|
f95dadfc23 | ||
|
a985952d0c | ||
|
87bb5ff97d | ||
|
3a0c746430 | ||
|
1e13727a94 | ||
|
2fd216bb5a | ||
|
827f9b57bb | ||
|
69e2230b80 | ||
|
c8b781e744 | ||
|
e6f096270e | ||
|
86021f2948 | ||
|
7c3d961ae7 | ||
|
761fc84658 | ||
|
d90a490987 | ||
|
eadac4b1cc | ||
|
c136663071 | ||
|
a0d4af15e9 | ||
|
877275e7fc | ||
|
29243ccdda | ||
|
cc1043eb82 | ||
|
efd6e3e7e5 | ||
|
dd7c951ea6 | ||
|
8f2c397413 | ||
|
af7297ce71 | ||
|
b6f6f63764 | ||
|
2beafb9a51 | ||
|
d8ecd28b22 | ||
|
910dec38a4 | ||
|
ebd98bebe9 | ||
|
fac768496e | ||
|
b3eb05f705 | ||
|
6c81568888 | ||
|
a0349305db | ||
|
68a0f4b5f0 | ||
|
5e6a4ce1ec | ||
|
b00edad1b9 | ||
|
92e91a3fdf | ||
|
78dd3ec744 | ||
|
f8ddf1b19b | ||
|
0dae54df18 | ||
|
7ce170a1d9 | ||
|
363b87fd74 | ||
|
b09868233c | ||
|
febae1b64c | ||
|
b9f68d18a9 | ||
|
a58e300707 | ||
|
4d9d12a2ed | ||
|
c20202a23d | ||
|
cf44e5815d | ||
|
06ff4bbbe4 | ||
|
5c1174fad6 | ||
|
953f93631b | ||
|
ed8053fce1 | ||
|
b876d001df | ||
|
23642e49f4 | ||
|
2c7b082301 | ||
|
d58a10a2c5 | ||
|
08de710b2f | ||
|
8026e106a5 | ||
|
fb264a8379 | ||
|
7e61412b8a | ||
|
2684b79103 | ||
|
e48af3212e | ||
|
75ffa8dd93 | ||
|
31642f4eca | ||
|
642fd7397e | ||
|
2b51a6e5f2 | ||
|
3069910606 | ||
|
0568e402a1 | ||
|
e5f437ff8d | ||
|
0ac1a10336 | ||
|
e1b9303797 | ||
|
ecae4c2296 | ||
|
bcbdc7d6ad | ||
|
35cb351e72 | ||
|
3ced0ed37a | ||
|
215b45b442 | ||
|
cae4d8cdf2 | ||
|
d04fe41eb1 | ||
|
b2a3404007 | ||
|
efdae17b57 | ||
|
a39f873693 | ||
|
87011a7280 | ||
|
72d5741ece | ||
|
6c9bff116a | ||
|
69602dc3de | ||
|
673796b4b7 | ||
|
9ef616a148 | ||
|
5bb49b9af5 | ||
|
5cf5bdda24 | ||
|
5d94fa96b3 | ||
|
7c47c67808 | ||
|
2792d02455 | ||
|
f4f773be5a | ||
|
2d0a91efbf | ||
|
dc85b2e981 | ||
|
15a3031da6 | ||
|
1f2c626b03 | ||
|
ce3ea6c9c1 | ||
|
3e73555869 | ||
|
b1e166dcd5 | ||
|
62b0f914b9 | ||
|
84e2fd717f | ||
|
ea0f9d8306 | ||
|
9a785de4b5 | ||
|
ac0e5eb26a | ||
|
829eed2222 | ||
|
b3a6aeae41 | ||
|
bd855ace58 | ||
|
b2b69b34bb | ||
|
1cd3bd18e8 | ||
|
883fc951a3 | ||
|
1c0f2e7bf1 | ||
|
bd43d62f98 | ||
|
0757f492a5 | ||
|
7daf23eb2b | ||
|
d90b794568 | ||
|
4cdc5bce56 | ||
|
e13d3b4271 | ||
|
7aa07449b2 | ||
|
2a051ffd70 | ||
|
50af2508e8 | ||
|
8cc00dee83 | ||
|
875dcbf8f2 | ||
|
49d74cb66a | ||
|
18393eb9ed | ||
|
461c546d31 | ||
|
4bfaf92099 | ||
|
0f35208650 | ||
|
cc35e1fdb2 | ||
|
f77e91cdac | ||
|
48ac3824cb | ||
|
699eb74a66 | ||
|
d47f47ea14 | ||
|
b243afa748 | ||
|
83053ad49e | ||
|
1eabebd0b8 | ||
|
f1c1a4ed0c | ||
|
a8dd335d71 | ||
|
96b44004b1 | ||
|
32a8052715 | ||
|
4d3573dd6f | ||
|
926d152d3b | ||
|
431dee80d8 | ||
|
56cd23f581 | ||
|
cdc3a7b51d | ||
|
78c5ad8531 | ||
|
f62b977093 | ||
|
481bb25f3e | ||
|
50fc01eeee | ||
|
4e299b09e4 | ||
|
947a484f21 | ||
|
665b8d024d | ||
|
c6fd9f3cb8 | ||
|
bdbb9b40de | ||
|
e81b459656 | ||
|
403e406d4a | ||
|
de7b38626b | ||
|
13fd1be6c2 | ||
|
6bdb0448aa | ||
|
44fcc43b6d | ||
|
fe4f32303a | ||
|
0e50b1d379 | ||
|
d5bd3bdfce | ||
|
96476ba9ca | ||
|
94fd0bfe33 | ||
|
39b62e37d5 | ||
|
b94b424f3a | ||
|
a300945f1e | ||
|
4b863a659e | ||
|
e877b33fd1 | ||
|
86a8efa294 | ||
|
68c734a2d1 | ||
|
4a37433c43 | ||
|
c50322db02 | ||
|
af28cb60c2 | ||
|
7e4528e35d | ||
|
bb155bb74b | ||
|
3f15640e55 | ||
|
942be1dab4 | ||
|
f24b160c13 | ||
|
9a634f86ab | ||
|
b76738798d | ||
|
f1997ee822 | ||
|
d5ac1474d4 | ||
|
4a24549923 | ||
|
bb390f51f0 | ||
|
1c6160e827 | ||
|
1115e783e0 | ||
|
828a04de88 | ||
|
c5d8032e95 | ||
|
832518582a | ||
|
a67e4ebd44 | ||
|
dec490291b | ||
|
81718c5b6a | ||
|
6a64b6e297 | ||
|
bd38c23c74 | ||
|
e4275c0f85 | ||
|
7b0b3e8381 | ||
|
8cc8c57589 | ||
|
d9082f185a | ||
|
7bfa38307d | ||
|
5fb7f93f23 | ||
|
74a058eb93 | ||
|
83a792bfe0 | ||
|
7bd0997f85 | ||
|
236fe316e3 | ||
|
73ce30e2ba | ||
|
ea111c452b | ||
|
12cfa78850 | ||
|
5cbaa702e2 | ||
|
8431ebacc0 | ||
|
e4580b7849 | ||
|
a22373a68d | ||
|
774c6ff38b | ||
|
4e100b36ef | ||
|
5a61d262b9 | ||
|
28a63be016 | ||
|
bbc909f2fa | ||
|
81ac0f903b | ||
|
b1ef79f6b3 | ||
|
4cbe918bcc | ||
|
b65d3bac98 | ||
|
a548924969 | ||
|
94a5bc21d2 | ||
|
73e5112938 | ||
|
be65996e57 | ||
|
9305b9db7d | ||
|
3364d6f1b9 | ||
|
a155f25ca6 | ||
|
c4c00129bc | ||
|
bca7c78e22 | ||
|
9bdf010bc5 | ||
|
d969125d7a | ||
|
44016907bc | ||
|
b8e88517f7 | ||
|
c1053cbd73 | ||
|
eb52ccb3c0 | ||
|
be42d247d8 | ||
|
e3727fca11 | ||
|
2808ae157b | ||
|
7ef6189c6e | ||
|
35040b3292 | ||
|
f2be2d9aae | ||
|
8584c55ac0 | ||
|
c2e30f4940 | ||
|
b6e4a08d48 | ||
|
54033225ac | ||
|
a4bb5514b1 | ||
|
f0b37800f6 | ||
|
9806ab55f9 | ||
|
181a21cb26 | ||
|
6cbe1ed226 | ||
|
d4b84fdb5d | ||
|
b804976947 | ||
|
ca4a85adc6 | ||
|
1bc5ae019a | ||
|
08e17ac1f8 | ||
|
464c4f724f | ||
|
aad164db57 | ||
|
fa120b2a31 | ||
|
0b286bfd69 | ||
|
1adba8f4ce | ||
|
76fb03a2a6 | ||
|
0008363a96 | ||
|
de12aee362 | ||
|
598bf43e71 | ||
|
8705e47d37 | ||
|
db30417e2e | ||
|
a76ad51664 | ||
|
3f197e3744 | ||
|
4f7df914cb | ||
|
5c4dc0f98a | ||
|
89c1c975e6 | ||
|
35d795d097 | ||
|
73f34b66be | ||
|
16eb1a3869 | ||
|
9b6310a936 | ||
|
fcb7cf8e3b | ||
|
1e85bacba9 | ||
|
e109e582f1 | ||
|
d4f4c47242 | ||
|
9f0732ab14 | ||
|
e9fd484067 | ||
|
1758531634 | ||
|
a057176ae6 | ||
|
70e82ca7d8 | ||
|
770f867ca2 | ||
|
d0b86facb3 | ||
|
67745f8b88 | ||
|
0635d7783f | ||
|
2712d693a0 | ||
|
928aaa12b6 | ||
|
007c256c5f | ||
|
3046804bf5 | ||
|
bc398cef53 | ||
|
4cd8ef3b3f | ||
|
93e695d93b | ||
|
50b38fd905 | ||
|
da97c1d533 | ||
|
46a7eb0f3f | ||
|
a8ef1ddac9 | ||
|
fee8530b1e | ||
|
d8d910d342 | ||
|
e8bd524b6d | ||
|
0766df39d7 | ||
|
9c589df6ab | ||
|
d3d170620a | ||
|
2a9b14cb0e | ||
|
9ed806b0ca | ||
|
870809c836 | ||
|
48e0e5bedf | ||
|
28c73d13ca | ||
|
144665ef3f | ||
|
f4992d842e | ||
|
2aa85228d7 | ||
|
1dacd096f6 | ||
|
902cec5364 | ||
|
c2b61defb3 | ||
|
3a0e803e3c | ||
|
bd62c79702 | ||
|
c85b56fd62 | ||
|
a59530d228 | ||
|
0373756eba | ||
|
ef40c2a543 | ||
|
7cb3310c5d | ||
|
f8960ddacb | ||
|
9926ac33cc | ||
|
448c23149f | ||
|
7840e2a13e | ||
|
622a6d08e5 | ||
|
6f6853eb85 | ||
|
716d40f943 | ||
|
5b3dbae572 | ||
|
01e145bd60 | ||
|
f72357e7ff | ||
|
42666390f4 | ||
|
417eaceb4c | ||
|
ca9c13543d | ||
|
a6777550a9 | ||
|
97cb50b876 | ||
|
83ed652839 | ||
|
0bc972514a | ||
|
f751bdf0b1 | ||
|
45d7e01e7c | ||
|
53c8e1548a | ||
|
c5cddd91af | ||
|
372bbf3a95 | ||
|
f0b0a7c6da | ||
|
256abed2ef | ||
|
1375e42749 | ||
|
4fdf2fb08f | ||
|
0077114cd4 | ||
|
f90deae8f7 | ||
|
4a575abbde | ||
|
c3f47a4f4b | ||
|
a947f9eea2 | ||
|
b05b673108 | ||
|
79d1d76817 | ||
|
dafd416b95 | ||
|
4988760ca3 | ||
|
1edce9a59d | ||
|
9bb9cf8833 | ||
|
2ada44f4da | ||
|
74c49574cb | ||
|
2730082b91 | ||
|
9f35310cb3 | ||
|
4215cdf3d6 | ||
|
b0a568b936 | ||
|
2012974f7e | ||
|
2d28a9bc71 | ||
|
554e307da4 | ||
|
4223a07183 | ||
|
e37a8cee3b | ||
|
b8ab88dcd5 | ||
|
d1531a8307 | ||
|
a2843a974a | ||
|
6881d370ae | ||
|
7ab471fe49 | ||
|
e03729958d | ||
|
a868d0bb06 | ||
|
5d37c6a664 | ||
|
3295e979fa | ||
|
b643acfbc5 | ||
|
3f8e53aa9c | ||
|
a79df278b5 | ||
|
ea8c48f1aa | ||
|
6adf9e4528 | ||
|
668d503d44 | ||
|
7f308cbf41 | ||
|
17a880f161 | ||
|
bb4ad5eb62 | ||
|
0b836b8712 | ||
|
b0d507b8bf | ||
|
18eb733904 | ||
|
26c8637206 | ||
|
2072567242 | ||
|
9727b4924e | ||
|
a1f2a14a03 | ||
|
079d3f5ea7 | ||
|
31f2a5d24d | ||
|
98ee1882cd | ||
|
acdf7f8052 | ||
|
b4541bf177 | ||
|
2657f39d07 | ||
|
8259de6dd7 | ||
|
bf00e5c0fa | ||
|
82296d9fed | ||
|
07b9a991b3 | ||
|
93c8bd6438 | ||
|
3bb9146496 | ||
|
55f3d5d8ec | ||
|
7b61464c93 | ||
|
322ba98f18 | ||
|
7502fa1e89 | ||
|
b2f6a2cd1a | ||
|
38bdc4f04f | ||
|
e68df7167f | ||
|
f5680ff032 | ||
|
5695c781c9 | ||
|
7bcb882425 | ||
|
74aea6f1dc | ||
|
78cb4296cc | ||
|
55cde4729d | ||
|
82bd1ae843 | ||
|
4823278f11 | ||
|
aac72ed3a3 | ||
|
db3bd77b6a | ||
|
0421ae2d87 | ||
|
abbc7e89a1 | ||
|
e3408075de | ||
|
cc85ae5a8f | ||
|
8f39ed4957 | ||
|
a7c0a76d14 | ||
|
0f1497b282 | ||
|
49a2061cc5 | ||
|
cd1df0ad83 | ||
|
b72488154a | ||
|
1a9dd6cca7 | ||
|
15d156cdaf | ||
|
6a1e058bdb | ||
|
fe36a1220e | ||
|
4d82935d1d | ||
|
7ae0086a69 | ||
|
3bd19a168a | ||
|
d8f44948a4 | ||
|
6b1ff707a2 | ||
|
776da6dd2b | ||
|
c93d1fb8a6 | ||
|
76d47df7fd | ||
|
c78c9ddd86 | ||
|
214a64732b | ||
|
e5498f214b | ||
|
02b993ec54 | ||
|
b413826299 | ||
|
7559959f35 | ||
|
d594f8018a | ||
|
83684f01d3 | ||
|
8d8a5b4e14 | ||
|
d485a45a61 | ||
|
c428c23c42 | ||
|
9b7d266e71 | ||
|
78f68468ce | ||
|
a04740a27e | ||
|
a7dea88063 | ||
|
cad0a84946 | ||
|
741af3e67e | ||
|
9c9d52b6b1 | ||
|
c8a5db7691 | ||
|
d1b1196d6d | ||
|
68a4bd2092 | ||
|
1774906fd6 | ||
|
d4bb64019f | ||
|
788bce686f | ||
|
ed9391cc6e | ||
|
a8c9219d09 | ||
|
63bc3f95e7 | ||
|
9aee65057e | ||
|
a62ddf8265 | ||
|
0fe16a0b40 | ||
|
fb3679d267 | ||
|
31dfa191f0 | ||
|
71732719b2 | ||
|
2601646f71 | ||
|
e22dd14596 | ||
|
489885fda9 | ||
|
0c88fb9a9a | ||
|
08af0b18df | ||
|
c93b6fcfdf | ||
|
dde52a5d0f | ||
|
9a14e8b659 | ||
|
7327ba9ed1 | ||
|
55c7473b6f | ||
|
5c3b58d653 | ||
|
432493801f | ||
|
e64366ab09 | ||
|
34a5cf8515 | ||
|
6c25c91146 | ||
|
8e290fb674 | ||
|
8798ea1a56 | ||
|
7b71d419fd | ||
|
53f60d6fed | ||
|
38c9d2f18a | ||
|
4e66c47f59 | ||
|
bff0e58f7f | ||
|
81072832e4 | ||
|
d9f4aed43c | ||
|
df899727ce | ||
|
a6b7a75136 | ||
|
a309a2053d | ||
|
16e9c3399c | ||
|
50e4de89b4 | ||
|
6cf8b1c9c6 | ||
|
2646d23387 | ||
|
ada2e70e28 | ||
|
ac150bb2e4 | ||
|
f27fa60413 | ||
|
1c47f73610 | ||
|
e92bf10531 | ||
|
91a8dbfa18 | ||
|
486353bbea | ||
|
9969bc4761 | ||
|
b5337fefa6 | ||
|
c5518e0ec3 | ||
|
43cc6a2e06 | ||
|
7e1ee9b66a | ||
|
2fcfa79ffd | ||
|
d963e9b562 | ||
|
8d1ae1a840 | ||
|
14ca5651df | ||
|
addaadac72 | ||
|
1eee0fe529 | ||
|
71c871caf7 | ||
|
5ef01b0b07 | ||
|
92ebbefd2f | ||
|
feedd4c2e3 | ||
|
98ac4d902e | ||
|
88b563269d | ||
|
5db20f0027 | ||
|
84f1d27fa8 | ||
|
17d8283144 | ||
|
b86760e467 | ||
|
5d7622428f | ||
|
da895ea97f | ||
|
eb6cd303de | ||
|
61f53d9974 | ||
|
6701445e9f | ||
|
bf9b1205b6 | ||
|
ab7c8ac4d1 | ||
|
209bc518c3 | ||
|
5ecb00daba | ||
|
3416728309 | ||
|
be94a1badc | ||
|
1c1596543d | ||
|
a35db5889d | ||
|
68ae1b80df | ||
|
5361694272 | ||
|
d8f03c4487 | ||
|
ed7f354afe | ||
|
b2d5819da7 | ||
|
83bbbd2be7 | ||
|
8fdb190118 | ||
|
d6f48b6e1d | ||
|
e669270bfb | ||
|
05c2c864a7 | ||
|
7206b4dbb8 | ||
|
97d1dab00a | ||
|
0cb197802a | ||
|
7e0a3c4ec3 | ||
|
99b6e27ab4 | ||
|
a1da758e6b | ||
|
7d33e0e3a2 | ||
|
ab7cd86146 | ||
|
918335e0e4 | ||
|
5bc6ad0165 | ||
|
dffed4b561 | ||
|
635c953801 | ||
|
d8d6ba44e4 | ||
|
7729d97a41 | ||
|
d9aac553c5 | ||
|
075e1bcf4f | ||
|
3247e79af4 | ||
|
44d0555778 | ||
|
a0b8b11a91 | ||
|
65512366d2 | ||
|
f2a9b35c6d | ||
|
2878ea5515 | ||
|
4a160ed999 | ||
|
1320c12780 | ||
|
7392f35991 | ||
|
93bab98859 | ||
|
bef0a952b5 | ||
|
a29508cb7a | ||
|
e2044cb560 | ||
|
69043d9c4e | ||
|
672eb6dfd7 | ||
|
cbc8d70151 | ||
|
4e70d138fe | ||
|
6f0bd441fe | ||
|
bb433b29ad | ||
|
62d5aa514b | ||
|
9e5c6a13c4 | ||
|
edde8f8bff | ||
|
e94fcba227 | ||
|
8eeedf9346 | ||
|
bec69ea968 | ||
|
a182be2d34 | ||
|
8bc5b35903 | ||
|
1292d4af68 | ||
|
4cf3bdb1fb | ||
|
bd5ea87ef7 | ||
|
860b44f33b | ||
|
7878466f4c | ||
|
ece858aaf3 | ||
|
4e4782c598 | ||
|
9fc1502ae6 | ||
|
c23803dedb | ||
|
a4a1224708 | ||
|
3ab1350a17 | ||
|
fde062b9fc | ||
|
b4b93b4016 | ||
|
f89ba4c5a9 | ||
|
7cdeede38a | ||
|
43258e5fd9 | ||
|
3a7b9c1e98 | ||
|
f595b7f670 | ||
|
a07bb582ec | ||
|
8fc14498f5 | ||
|
9116b57de3 | ||
|
c6e9dbd84c | ||
|
e7f7b0693c | ||
|
5c23160632 | ||
|
96e55e46ff | ||
|
fe12768114 | ||
|
3204582143 | ||
|
a6a67a2b3d | ||
|
6da0aaf77f | ||
|
6d9aed74c8 | ||
|
56a597e0a6 | ||
|
136b562a81 | ||
|
040d44d55b | ||
|
99a3997e08 | ||
|
3f6c4d1b2c | ||
|
2cd80b1b4c | ||
|
c8115ed44e | ||
|
0df57272fd | ||
|
6b1dd3aef6 | ||
|
8444d7d1d5 | ||
|
4e00a70834 | ||
|
08b00d71d4 | ||
|
064a1653fd | ||
|
22691553fb | ||
|
0e5bdc4e59 | ||
|
d2603c3d17 | ||
|
59615bc88b | ||
|
40f7e6ed26 | ||
|
4584c33068 | ||
|
313d1857af | ||
|
41c5d16888 | ||
|
92c099f499 | ||
|
10cc6fed7d | ||
|
53f156cb49 | ||
|
043665f8e5 | ||
|
d51f2fa79a | ||
|
2f50fcb9e7 | ||
|
a531650c77 | ||
|
1f7a76ef5e | ||
|
cfaaa723fe | ||
|
dba896670c | ||
|
85c5759af9 | ||
|
92df7338ca | ||
|
ad27f44b67 | ||
|
fbf654d824 | ||
|
972b335f36 | ||
|
37fde8e97c | ||
|
86ef17c75a | ||
|
92a58cd4f9 | ||
|
a86df49160 | ||
|
e46ffeb1bc | ||
|
9f2a521888 | ||
|
c8fda3da8d | ||
|
71b820487e | ||
|
b53c1bbffe | ||
|
b38ade5b69 | ||
|
9da9ef7153 | ||
|
beb7bbd3d9 | ||
|
52f2eedd6a | ||
|
b433a730bc | ||
|
6c26643e92 | ||
|
0e5ec6956e | ||
|
f0f5822d14 | ||
|
b5e07c42e6 | ||
|
b43a96d743 | ||
|
685108fdbe | ||
|
449a67eede | ||
|
d6c84df816 | ||
|
3d8de194ac | ||
|
ae6067b96a | ||
|
f1429d557a | ||
|
0031003a14 | ||
|
7f46524162 | ||
|
319128260a | ||
|
0d00e2b0f5 | ||
|
ca3cf1a7ac | ||
|
5cc5794f66 | ||
|
c6b79a8052 | ||
|
76e4837201 | ||
|
99cd7bfda6 | ||
|
1ca17fdeff | ||
|
790860f426 | ||
|
80a329d480 | ||
|
f3668ed815 | ||
|
10e76f65d7 | ||
|
ee37558946 | ||
|
4285205dca | ||
|
7b35909083 | ||
|
12c4463901 | ||
|
6b098768f6 | ||
|
7b611820c5 | ||
|
9ef7a961f6 | ||
|
5bb20a9c2d | ||
|
cadcf73362 | ||
|
baa1a86cfe | ||
|
e309425ee5 | ||
|
bec9ef36d2 | ||
|
a68aeddc7e | ||
|
2103420fb9 | ||
|
1ecd24af7d | ||
|
bac3249b50 | ||
|
449dff6282 | ||
|
ab3e901d7e | ||
|
7c3daf4c64 | ||
|
6e6cac380b | ||
|
f3538955c8 | ||
|
b5ea234701 | ||
|
934261d870 | ||
|
a8fb12dcd4 | ||
|
ff629989bb | ||
|
b05eefedea | ||
|
b7f42a3ff4 | ||
|
dd80bb3c17 | ||
|
0a6e05657e | ||
|
09de5a6021 | ||
|
46a89600cb | ||
|
a064ce97da | ||
|
9de675b385 | ||
|
64bc56cbb9 | ||
|
524cd78c5f | ||
|
119ba9dda0 | ||
|
876081fac6 | ||
|
ec1c488c44 | ||
|
81f278d3dc | ||
|
8d0b82cf36 | ||
|
dcf247ad3e | ||
|
1fd54927c5 | ||
|
9b4e325a2d | ||
|
91dcfea775 | ||
|
a65155ed1c | ||
|
cea843160c | ||
|
61e6f2db60 | ||
|
fb6deaec96 | ||
|
7eed1cf880 | ||
|
556c842685 | ||
|
e55f6ed212 | ||
|
b29387abdd | ||
|
801716fd92 | ||
|
9e4138da96 | ||
|
9a5e8f4a5a | ||
|
dc79339cae | ||
|
ec6bd0c2c1 | ||
|
da4976e3c7 | ||
|
4683ada2ce | ||
|
3c0c3b3abc | ||
|
99b9a5648a | ||
|
176ff06778 | ||
|
f090a73486 | ||
|
2236d77313 | ||
|
be1ff0445d | ||
|
4252fbfe4c | ||
|
3fc5f044f9 | ||
|
ed898f7fe2 | ||
|
9a617479cc | ||
|
7684bc7dcb | ||
|
677e7143a2 | ||
|
548847f83a | ||
|
05d6db5664 | ||
|
b456d770b6 | ||
|
8b175f0b71 | ||
|
7483e98f2b | ||
|
c5602cf88b | ||
|
0376bfeb10 | ||
|
b076b871a1 | ||
|
9f526a3ceb | ||
|
68ce5c79ef | ||
|
482fa566c5 | ||
|
b22b893623 | ||
|
addbd16b3b | ||
|
dd4e6d4aa0 | ||
|
607e51e218 | ||
|
53f692a78c | ||
|
a42fefacef | ||
|
79bde335fa | ||
|
beb523a921 | ||
|
b269023f7a | ||
|
f00b3612f8 | ||
|
3fac75cb79 | ||
|
9a9861e4b0 | ||
|
d07a2eae25 | ||
|
6dc007c8df | ||
|
a37f1fc256 | ||
|
0da60aafef | ||
|
5bdca32779 | ||
|
2052992a99 | ||
|
dd5cc79dd1 | ||
|
59b98959bc | ||
|
402539ed7f | ||
|
cc29d68337 | ||
|
9b4a7bd28c | ||
|
b61e3ee007 | ||
|
824ed4f3e9 | ||
|
2a5ed703bf | ||
|
7a4ffa140f | ||
|
017af8ff45 | ||
|
c13bee6fb5 | ||
|
53025756d8 | ||
|
682c4498ca | ||
|
6206cdde8d | ||
|
47127e2839 | ||
|
476e867dc9 | ||
|
5799b6ff33 | ||
|
40dc54b64b | ||
|
730d46f9a0 | ||
|
3b54763563 | ||
|
4b22680406 | ||
|
2ed460f054 | ||
|
e1d6583157 | ||
|
5a5178a025 | ||
|
8f67fcfd81 | ||
|
42bf37db83 | ||
|
b95147b09a | ||
|
4647bd2945 | ||
|
4237184767 | ||
|
624e1cef63 | ||
|
60f48ab3d9 | ||
|
d7462abeff | ||
|
3e8232e9db | ||
|
b1cb5b2d56 | ||
|
cb333a1425 | ||
|
8ab0cc5e0e | ||
|
b7bab4938f | ||
|
9c661744d5 | ||
|
ee73f03cb2 | ||
|
53e8f62c89 | ||
|
94b2dee48a | ||
|
512544ea9f | ||
|
2c85b38113 | ||
|
1c55f9d645 | ||
|
fd5aa5cb5a | ||
|
d8b4be55a6 | ||
|
6cbd0ce7fc | ||
|
1bfa0be26b | ||
|
650ee014ec | ||
|
b5ad081976 | ||
|
4291f0efc1 | ||
|
50a3db6b94 | ||
|
2d5ce49254 | ||
|
479f50fb76 | ||
|
b8a30601df | ||
|
f9b4652e74 | ||
|
98c4ec71c1 | ||
|
1e1266a4d3 | ||
|
b866229efb | ||
|
444af2ab0e | ||
|
9326fdace4 | ||
|
a9eedc6760 | ||
|
a7ca196ee0 | ||
|
b0c51b0292 | ||
|
54da9913eb | ||
|
ef5c360a30 | ||
|
c5c0fdbd05 | ||
|
0a4e8c6934 | ||
|
e1fc8dde39 | ||
|
62df0bb853 | ||
|
57d5a30726 | ||
|
88a7168821 | ||
|
babb49448a | ||
|
bb33c925de | ||
|
a1cf735391 | ||
|
ad2072688b | ||
|
4fb3a12770 | ||
|
15ed4020bc | ||
|
7d5f419a83 | ||
|
0a4cfc0d7e | ||
|
bab7265a90 | ||
|
234e5cb3fc | ||
|
01970dba27 | ||
|
6ac0e4a04e | ||
|
ec2cc5dfaf | ||
|
6d0f892f8a | ||
|
c9374038be | ||
|
c36e7b941c | ||
|
ce9a223eb9 | ||
|
3c7e7a8434 | ||
|
d286b2565c | ||
|
fae322fc7f | ||
|
f98b3aae01 | ||
|
7f82c89d95 | ||
|
d2ec7fbe45 | ||
|
e10f915100 | ||
|
a94864d654 | ||
|
3d354a1cb5 | ||
|
e6705f1089 | ||
|
c19a7ed400 | ||
|
4ccf38942b | ||
|
9735448e61 | ||
|
41535dea0c | ||
|
ae7b910e85 | ||
|
9773f361dd | ||
|
3b0c9f9e9d | ||
|
66c0e13aaf | ||
|
f8fc6d49d6 | ||
|
d7b146810e | ||
|
223e494d56 | ||
|
139cdd2d2e | ||
|
abcd8f5369 | ||
|
cbe7069227 | ||
|
a822f9e6d2 | ||
|
051ed8cb7b | ||
|
6d77bd18aa | ||
|
498465b705 | ||
|
90455beb4c | ||
|
d12f3ff2ff | ||
|
ae95ecf711 | ||
|
f278f21101 | ||
|
6d3f212531 | ||
|
64a1820ee6 | ||
|
20fc743a22 | ||
|
5d4969ac78 | ||
|
6d2f10fb7c | ||
|
bc39251426 | ||
|
06d66b87e9 | ||
|
5561069d50 | ||
|
0364128ba5 | ||
|
024b148c54 | ||
|
bfa40aec10 | ||
|
244e2bcfb9 | ||
|
947a0667a6 | ||
|
0972e79130 | ||
|
6f11715d24 | ||
|
cb78f34c1e | ||
|
55a3e94d57 | ||
|
033a97d4da | ||
|
878f1d49aa | ||
|
2c2d4f7434 | ||
|
040aad93f8 | ||
|
541d3c0e4b | ||
|
1b4c6dbf8b | ||
|
fd8be77635 | ||
|
6b482d87e0 | ||
|
27441353d9 | ||
|
7d3460deba | ||
|
0efebb337f | ||
|
996fc4ecbf | ||
|
2690d9b9d4 | ||
|
311c15d389 | ||
|
92a5982792 | ||
|
ab5bc6a503 | ||
|
71101d98f8 | ||
|
0e272f9d58 | ||
|
3b9d36f2d2 | ||
|
839888f445 | ||
|
ec808b1361 | ||
|
67fb4181f4 | ||
|
54eb60698b | ||
|
295c6b56eb | ||
|
7f92b886bd | ||
|
7224fd4b37 | ||
|
acc9259ddd | ||
|
e38cecfae0 | ||
|
4620ad7f29 | ||
|
77f5d05071 | ||
|
4db4629c75 | ||
|
d3ee3edc4e | ||
|
db09503426 | ||
|
c56cb70a24 | ||
|
c05879bada | ||
|
a612e42a99 | ||
|
6a92056608 | ||
|
8b479a1898 | ||
|
ab548df0b2 | ||
|
e3ea517d72 | ||
|
dbc368d5f6 | ||
|
ef41688e61 | ||
|
7f439137d6 | ||
|
91ea2a54f6 | ||
|
d386969004 | ||
|
3123574cf6 | ||
|
bb7cfcc124 | ||
|
7c1c32db80 | ||
|
776bbc7dde | ||
|
2e8f59115f | ||
|
d9952d5d85 | ||
|
5b76551790 | ||
|
b055a86c92 | ||
|
f67343c45c | ||
|
05b65f432c | ||
|
a5337b2fc8 | ||
|
0dd2884262 | ||
|
c824eae566 | ||
|
405c579f7e | ||
|
9d48044ea3 | ||
|
446a659bc8 | ||
|
c6eabb726c | ||
|
c646e3933a | ||
|
227ddbd378 | ||
|
fab6a5c610 | ||
|
ca4db90ce3 | ||
|
1d6951ed70 | ||
|
8ee9378dda | ||
|
219932d9cc | ||
|
be302793e1 | ||
|
b0720cfc2c | ||
|
463f219307 | ||
|
8e78f937c8 | ||
|
62fd6f37f2 | ||
|
96930c9d36 | ||
|
fb40c949e4 | ||
|
5116a57aea | ||
|
e38d2664ea | ||
|
e3b8948741 | ||
|
d53b6d9a87 | ||
|
d1f825c590 | ||
|
d7fa0ca2ef | ||
|
c0bd263684 | ||
|
bedc4206fb | ||
|
a090303be6 | ||
|
78dd20ef39 | ||
|
22de89c206 | ||
|
fb581ce47d | ||
|
25f1c222fd | ||
|
a0e0d4dc3f | ||
|
460038f3af | ||
|
2880eaa727 | ||
|
48fce40e5a | ||
|
74bb47626d | ||
|
1673a11022 | ||
|
bbc5e83e07 | ||
|
bab92a58ae | ||
|
f2aa8cddc0 | ||
|
1704c8e05f | ||
|
4ef113f28b | ||
|
13496f78d3 | ||
|
f0a1a9f96f | ||
|
73b8af1cbb | ||
|
b5870e9a57 | ||
|
0db7a67ef2 | ||
|
e1009373bc | ||
|
3ee60fa01b | ||
|
d8c7a32220 | ||
|
48ff686ae8 | ||
|
f64d155154 | ||
|
90ff917267 | ||
|
b1800bfc08 | ||
|
27cf7acc35 | ||
|
245c8a0aaf | ||
|
d7268853d4 | ||
|
5967044458 | ||
|
7216b8a5b9 | ||
|
4aba82d588 | ||
|
bc9fdd8426 | ||
|
67a217181f | ||
|
32cf5b29b8 | ||
|
8d896e7272 | ||
|
76d567e376 | ||
|
b4d01bbede | ||
|
264a12daaf | ||
|
233dedbcff | ||
|
946b1a240c | ||
|
4e4bcd4b23 | ||
|
bac0bd499c | ||
|
1231a9b30f | ||
|
db6c3b79f7 | ||
|
e8c3b6a620 | ||
|
569ec912d2 | ||
|
65f7704463 | ||
|
bb31186e51 | ||
|
b5c1bea18f | ||
|
b0c2005288 | ||
|
1032143f5b | ||
|
6bbf7e3526 | ||
|
eaf5179c76 | ||
|
27bf9e0a8e | ||
|
3b880d12a5 | ||
|
22b28d0023 | ||
|
0726a7c253 | ||
|
b93bb61e28 | ||
|
126883f5c1 | ||
|
e07119c914 | ||
|
83f9845f59 | ||
|
13ce4e0ea5 | ||
|
c2bd37ce45 | ||
|
8fd471dd39 | ||
|
e817f726c2 | ||
|
87fc17284c | ||
|
c13c2ad6eb | ||
|
3cb3d1ae4d | ||
|
ef14ae1b1c | ||
|
c1d6127e64 | ||
|
b7467482f9 | ||
|
11493d1a24 | ||
|
58184e31b4 | ||
|
2bfe6c8a91 | ||
|
a635ce96d5 | ||
|
15bb1fd6c3 | ||
|
70736f8e86 | ||
|
914f6767ea | ||
|
8f90764c01 | ||
|
09d0edd623 | ||
|
983be8d3da | ||
|
786f3392ef | ||
|
a4746b7f7f | ||
|
ccf3e01a46 | ||
|
bec0a2b80d | ||
|
ff495e201a | ||
|
22055c3f63 | ||
|
d93abe8554 | ||
|
654e7f05c8 | ||
|
01ae47c0d0 | ||
|
e0cefd0fa1 | ||
|
0dbc7a5b58 | ||
|
47d33e524c | ||
|
1736a6e008 | ||
|
b1f6963fbe | ||
|
3e3f782ab8 | ||
|
3754c7a6c5 | ||
|
65f47b1222 | ||
|
08c7e7048f | ||
|
d36e01bf3e | ||
|
5ef157d252 | ||
|
bb5715be90 | ||
|
37f8382f4b | ||
|
7ab731eacc | ||
|
fa113af977 | ||
|
62e0a55b1b | ||
|
91ea284723 | ||
|
f6fb605080 | ||
|
7c21f706e5 | ||
|
6567ee2b4d | ||
|
1c4abb7533 | ||
|
a53114d10f | ||
|
abdb1eeac0 | ||
|
9d72eaf675 | ||
|
ae3b6a57b0 | ||
|
c481170b97 | ||
|
612bb0019f | ||
|
3221832a8d | ||
|
5852d38b00 | ||
|
a964f55802 | ||
|
3c489c54bf | ||
|
c7af4d2f8b | ||
|
0c1c13be33 | ||
|
ff4a092dab | ||
|
0aab7f5628 | ||
|
0414ab5460 | ||
|
dbde0ff579 | ||
|
dbde2b157e | ||
|
e61e495931 | ||
|
911117f5a7 | ||
|
aad431aa62 | ||
|
54f35a5d35 | ||
|
f706d998e7 | ||
|
13d6a0f667 | ||
|
a4a160b061 | ||
|
9691bce3a4 | ||
|
88ec70511c | ||
|
3b37476d23 | ||
|
07c105234a | ||
|
259832bc75 | ||
|
6cd2319a15 | ||
|
4055b42e59 | ||
|
d48616736b | ||
|
6eadac8a75 | ||
|
120ef5d5cb | ||
|
da1f1eb08a | ||
|
e1f2d5e3b8 | ||
|
36b1413868 | ||
|
4f0f93459b | ||
|
a6571c5f58 | ||
|
16799e414b | ||
|
76e14c1c79 | ||
|
b6961f9311 | ||
|
039e211a09 | ||
|
333f2db979 | ||
|
1192557e1b | ||
|
3c19399a2a | ||
|
7f88e4ffdd | ||
|
ae2d1036d7 | ||
|
8233312c15 | ||
|
158b6fcd9e | ||
|
781965ddb1 | ||
|
3786b052a9 | ||
|
2c449e9820 | ||
|
113f770634 | ||
|
1cdf393f56 | ||
|
1631978790 | ||
|
92254b0276 | ||
|
5462bb91c4 | ||
|
65893178dc | ||
|
2bfdae8949 |
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 }}
|
23
.gitignore
vendored
|
@ -2,9 +2,6 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
env/
|
env/
|
||||||
|
@ -18,6 +15,7 @@ lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
wheelhouse/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
@ -33,12 +31,13 @@ pip-log.txt
|
||||||
pip-delete-this-directory.txt
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
# Unit test / coverage reports
|
# Unit test / coverage reports
|
||||||
htmlcov/
|
reports/
|
||||||
.tox/
|
.tox/
|
||||||
.coverage
|
.coverage
|
||||||
.cache
|
.cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
@ -57,7 +56,21 @@ target/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# Virtualenv
|
# Virtualenv
|
||||||
venv/
|
venv*/
|
||||||
|
|
||||||
# SQLite
|
# SQLite
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
|
# Vim Rope
|
||||||
|
.ropeproject/
|
||||||
|
|
||||||
|
# Cython artifacts
|
||||||
|
src/**/*.c
|
||||||
|
src/**/*.h
|
||||||
|
src/**/*.so
|
||||||
|
src/**/*.html
|
||||||
|
|
||||||
|
# Workspace for samples
|
||||||
|
.workspace/
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
|
24
CONTRIBUTORS.rst
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Dependency Injector Contributors
|
||||||
|
================================
|
||||||
|
|
||||||
|
+ Roman Mogylatov (rmk135)
|
||||||
|
+ Konstantin vz'One Enchant (sirkonst)
|
||||||
|
+ Terrence Brannon (metaperl)
|
||||||
|
+ Stanislav Lobanov (asyncee)
|
||||||
|
+ James Lafa (jameslafa)
|
||||||
|
+ Vlad Ghita (vlad-ghita)
|
||||||
|
+ Jeroen Rietveld (jeroenrietveld)
|
||||||
|
+ Dmitry Kuzmin (xotonic)
|
||||||
|
+ supakeen (supakeen)
|
||||||
|
+ 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) 2015, Roman
|
Copyright (c) 2024, Roman Mogylatov
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
@ -11,7 +11,7 @@ modification, are permitted provided that the following conditions are met:
|
||||||
this list of conditions and the following disclaimer in the documentation
|
this list of conditions and the following disclaimer in the documentation
|
||||||
and/or other materials provided with the distribution.
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
* Neither the name of objects nor the names of its
|
* Neither the name of "Dependency Injector" nor the names of its
|
||||||
contributors may be used to endorse or promote products derived from
|
contributors may be used to endorse or promote products derived from
|
||||||
this software without specific prior written permission.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
@ -25,4 +25,3 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
6
MANIFEST
|
@ -1,6 +0,0 @@
|
||||||
include objects/*
|
|
||||||
include README.md
|
|
||||||
include LICENSE
|
|
||||||
include VERSION
|
|
||||||
include requirements.txt
|
|
||||||
include setup.py
|
|
7
MANIFEST.in
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
recursive-include src/dependency_injector *.py* *.c py.typed
|
||||||
|
recursive-include tests *.py
|
||||||
|
include README.rst
|
||||||
|
include CONTRIBUTORS.rst
|
||||||
|
include LICENSE.rst
|
||||||
|
include setup.py
|
||||||
|
include tox.ini
|
65
Makefile
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
VERSION := $(shell python setup.py --version)
|
||||||
|
|
||||||
|
export COVERAGE_RCFILE := pyproject.toml
|
||||||
|
|
||||||
|
clean:
|
||||||
|
# Clean sources
|
||||||
|
find src -name '*.py[cod]' -delete
|
||||||
|
find src -name '__pycache__' -delete
|
||||||
|
find src -name '*.c' -delete
|
||||||
|
find src -name '*.h' -delete
|
||||||
|
find src -name '*.so' -delete
|
||||||
|
find src -name '*.html' -delete
|
||||||
|
# Clean tests
|
||||||
|
find tests -name '*.py[co]' -delete
|
||||||
|
find tests -name '__pycache__' -delete
|
||||||
|
# Clean examples
|
||||||
|
find examples -name '*.py[co]' -delete
|
||||||
|
find examples -name '__pycache__' -delete
|
||||||
|
|
||||||
|
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/ \;
|
||||||
|
|
||||||
|
docs-live:
|
||||||
|
sphinx-autobuild docs docs/_build/html
|
||||||
|
|
||||||
|
install: uninstall clean build
|
||||||
|
pip install -ve .
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
- pip uninstall -y -q dependency-injector 2> /dev/null
|
||||||
|
|
||||||
|
test:
|
||||||
|
# Unit tests with coverage report
|
||||||
|
coverage erase
|
||||||
|
coverage run -m pytest
|
||||||
|
coverage report
|
||||||
|
coverage html
|
||||||
|
|
||||||
|
check:
|
||||||
|
flake8 src/dependency_injector/
|
||||||
|
flake8 examples/
|
||||||
|
|
||||||
|
pydocstyle src/dependency_injector/
|
||||||
|
pydocstyle examples/
|
||||||
|
|
||||||
|
mypy tests/typing
|
||||||
|
|
||||||
|
test-publish: build
|
||||||
|
# Create distributions
|
||||||
|
python -m build --sdist
|
||||||
|
# Upload distributions to PyPI
|
||||||
|
twine upload --repository testpypi dist/dependency-injector-$(VERSION)*
|
||||||
|
|
||||||
|
publish:
|
||||||
|
# Merge release to master branch
|
||||||
|
git checkout master
|
||||||
|
git merge --no-ff release/$(VERSION) -m "Merge branch 'release/$(VERSION)' into master"
|
||||||
|
git push origin master
|
||||||
|
# Create and upload tag
|
||||||
|
git tag -a $(VERSION) -m 'version $(VERSION)'
|
||||||
|
git push --tags
|
170
README.md
|
@ -1,170 +0,0 @@
|
||||||
Objects
|
|
||||||
=======
|
|
||||||
|
|
||||||
Python catalogs of objects providers.
|
|
||||||
|
|
||||||
|
|
||||||
Example of objects catalog definition and usage:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"""
|
|
||||||
Concept example of objects catalogs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from objects import Catalog, Singleton, NewInstance, InitArg, Attribute
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
|
|
||||||
# Some example classes.
|
|
||||||
class ObjectA(object):
|
|
||||||
def __init__(self, db):
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectB(object):
|
|
||||||
def __init__(self, a, db):
|
|
||||||
self.a = a
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
|
|
||||||
# Catalog of objects providers.
|
|
||||||
class AppCatalog(Catalog):
|
|
||||||
"""
|
|
||||||
Objects catalog.
|
|
||||||
"""
|
|
||||||
|
|
||||||
database = Singleton(sqlite3.Connection,
|
|
||||||
InitArg('database', ':memory:'),
|
|
||||||
Attribute('row_factory', sqlite3.Row))
|
|
||||||
""" :type: (objects.Provider) -> sqlite3.Connection """
|
|
||||||
|
|
||||||
object_a = NewInstance(ObjectA,
|
|
||||||
InitArg('db', database))
|
|
||||||
""" :type: (objects.Provider) -> ObjectA """
|
|
||||||
|
|
||||||
object_b = NewInstance(ObjectB,
|
|
||||||
InitArg('a', object_a),
|
|
||||||
InitArg('db', database))
|
|
||||||
""" :type: (objects.Provider) -> ObjectB """
|
|
||||||
|
|
||||||
|
|
||||||
# Catalog static provides.
|
|
||||||
a1, a2 = AppCatalog.object_a(), AppCatalog.object_a()
|
|
||||||
b1, b2 = AppCatalog.object_b(), AppCatalog.object_b()
|
|
||||||
|
|
||||||
# Some asserts.
|
|
||||||
assert a1 is not a2
|
|
||||||
assert b1 is not b2
|
|
||||||
assert a1.db is a2.db is b1.db is b2.db is AppCatalog.database()
|
|
||||||
|
|
||||||
|
|
||||||
# Dependency injection (The Python Way) into class.
|
|
||||||
class Consumer(object):
|
|
||||||
|
|
||||||
dependencies = AppCatalog(AppCatalog.object_a,
|
|
||||||
AppCatalog.object_b)
|
|
||||||
|
|
||||||
def test(self):
|
|
||||||
a1 = self.dependencies.object_a()
|
|
||||||
a2 = self.dependencies.object_a()
|
|
||||||
|
|
||||||
b1 = self.dependencies.object_b()
|
|
||||||
b2 = self.dependencies.object_b()
|
|
||||||
|
|
||||||
# Some asserts.
|
|
||||||
assert a1 is not a2
|
|
||||||
assert b1 is not b2
|
|
||||||
assert a1.db is a2.db is b1.db is b2.db
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.dependencies.database()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise Exception('Database is not listed as a dependency')
|
|
||||||
|
|
||||||
Consumer().test()
|
|
||||||
|
|
||||||
|
|
||||||
# Dependency injection (The Python Way) into a callback.
|
|
||||||
def consumer_callback(dependencies=AppCatalog(AppCatalog.object_a,
|
|
||||||
AppCatalog.object_b)):
|
|
||||||
a1 = dependencies.object_a()
|
|
||||||
a2 = dependencies.object_a()
|
|
||||||
|
|
||||||
b1 = dependencies.object_b()
|
|
||||||
b2 = dependencies.object_b()
|
|
||||||
|
|
||||||
# Some asserts.
|
|
||||||
assert a1 is not a2
|
|
||||||
assert b1 is not b2
|
|
||||||
assert a1.db is a2.db is b1.db is b2.db
|
|
||||||
|
|
||||||
try:
|
|
||||||
dependencies.database()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise Exception('Database is not listed as a dependency')
|
|
||||||
```
|
|
||||||
|
|
||||||
Example of overriding object providers:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"""
|
|
||||||
Concept example of objects overrides.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from objects import Catalog, Singleton, NewInstance, InitArg, Attribute, overrides
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
|
|
||||||
# Some example class.
|
|
||||||
class ObjectA(object):
|
|
||||||
def __init__(self, db):
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectAMock(ObjectA):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Catalog of objects providers.
|
|
||||||
class AppCatalog(Catalog):
|
|
||||||
"""
|
|
||||||
Objects catalog.
|
|
||||||
"""
|
|
||||||
|
|
||||||
database = Singleton(sqlite3.Connection,
|
|
||||||
InitArg('database', ':memory:'),
|
|
||||||
Attribute('row_factory', sqlite3.Row))
|
|
||||||
""" :type: (objects.Provider) -> sqlite3.Connection """
|
|
||||||
|
|
||||||
object_a = NewInstance(ObjectA,
|
|
||||||
InitArg('db', database))
|
|
||||||
""" :type: (objects.Provider) -> ObjectA """
|
|
||||||
|
|
||||||
|
|
||||||
# Overriding AppCatalog by SandboxCatalog with some mocks.
|
|
||||||
@overrides(AppCatalog)
|
|
||||||
class SandboxCatalog(AppCatalog):
|
|
||||||
"""
|
|
||||||
Sandbox objects catalog with some mocks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
object_a = NewInstance(ObjectAMock,
|
|
||||||
InitArg('db', AppCatalog.database))
|
|
||||||
""" :type: (objects.Provider) -> ObjectA """
|
|
||||||
|
|
||||||
|
|
||||||
# Catalog static provides.
|
|
||||||
a1 = AppCatalog.object_a()
|
|
||||||
a2 = AppCatalog.object_a()
|
|
||||||
|
|
||||||
# Some asserts.
|
|
||||||
assert isinstance(a1, ObjectAMock)
|
|
||||||
assert isinstance(a2, ObjectAMock)
|
|
||||||
assert a1 is not a2
|
|
||||||
assert a1.db is a2.db is AppCatalog.database()
|
|
||||||
```
|
|
230
README.rst
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/logo.svg
|
||||||
|
:target: https://github.com/ets-labs/python-dependency-injector
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/dependency_injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Latest Version
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/l/dependency_injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: License
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Supported Python versions
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Supported Python implementations
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/dependency-injector
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/dependency-injector/month
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/dependency-injector/week
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/wheel/dependency-injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Wheel
|
||||||
|
|
||||||
|
.. 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:: 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
|
||||||
|
|
||||||
|
What is ``Dependency Injector``?
|
||||||
|
================================
|
||||||
|
|
||||||
|
``Dependency Injector`` is a dependency injection framework for Python.
|
||||||
|
|
||||||
|
It helps implement the dependency injection principle.
|
||||||
|
|
||||||
|
Key features of the ``Dependency Injector``:
|
||||||
|
|
||||||
|
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||||
|
``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 <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 <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 <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):
|
||||||
|
|
||||||
|
config = providers.Configuration()
|
||||||
|
|
||||||
|
api_client = providers.Singleton(
|
||||||
|
ApiClient,
|
||||||
|
api_key=config.api_key,
|
||||||
|
timeout=config.timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
service = providers.Factory(
|
||||||
|
Service,
|
||||||
|
api_client=api_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def main(service: Service = Provide[Container.service]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container()
|
||||||
|
container.config.api_key.from_env("API_KEY", required=True)
|
||||||
|
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
|
||||||
|
container.wire(modules=[__name__])
|
||||||
|
|
||||||
|
main() # <-- dependency is injected automatically
|
||||||
|
|
||||||
|
with container.api_client.override(mock.Mock()):
|
||||||
|
main() # <-- overridden dependency is injected automatically
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Visit the docs to know more about the
|
||||||
|
`Dependency injection and inversion of control in Python <https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html>`_.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
The package is available on the `PyPi`_::
|
||||||
|
|
||||||
|
pip install dependency-injector
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The documentation is available `here <https://python-dependency-injector.ets-labs.org/>`_.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
Choose one of the following:
|
||||||
|
|
||||||
|
- `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 <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
|
||||||
|
-------
|
||||||
|
|
||||||
|
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
Explicit is better than implicit
|
||||||
|
|
||||||
|
You need to specify how to assemble and where to inject the dependencies explicitly.
|
||||||
|
|
||||||
|
The power of the framework is in its simplicity.
|
||||||
|
``Dependency Injector`` is a simple tool for the powerful concept.
|
||||||
|
|
||||||
|
Frequently asked questions
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
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 😎
|
||||||
|
|
||||||
|
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 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
|
||||||
|
- it will be extra work in the beginning
|
||||||
|
- it will payoff as project grows
|
||||||
|
|
||||||
|
Have a question?
|
||||||
|
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
|
||||||
|
|
||||||
|
Found a bug?
|
||||||
|
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
|
||||||
|
|
||||||
|
Want to help?
|
||||||
|
- |star| Star the ``Dependency Injector`` on the `Github <https://github.com/ets-labs/python-dependency-injector/>`_
|
||||||
|
- |new| Start a new project with the ``Dependency Injector``
|
||||||
|
- |tell| Tell your friend about the ``Dependency Injector``
|
||||||
|
|
||||||
|
Want to contribute?
|
||||||
|
- |fork| Fork the project
|
||||||
|
- |pull| Open a pull request to the ``develop`` branch
|
||||||
|
|
||||||
|
.. _PyPi: https://pypi.org/project/dependency-injector/
|
||||||
|
|
||||||
|
.. |star| unicode:: U+2B50 U+FE0F .. star sign1
|
||||||
|
.. |new| unicode:: U+1F195 .. new sign
|
||||||
|
.. |tell| unicode:: U+1F4AC .. tell sign
|
||||||
|
.. |fork| unicode:: U+1F500 .. fork sign
|
||||||
|
.. |pull| unicode:: U+2B05 U+FE0F .. pull sign
|
192
docs/Makefile
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# User-friendly check for sphinx-build
|
||||||
|
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||||
|
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
# the i18n builder cannot share the environment and doctrees with the others
|
||||||
|
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
|
||||||
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " applehelp to make an Apple Help Book"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
|
@echo " epub to make an epub"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||||
|
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||||
|
@echo " text to make text files"
|
||||||
|
@echo " man to make manual pages"
|
||||||
|
@echo " texinfo to make Texinfo files"
|
||||||
|
@echo " info to make Texinfo files and run them through makeinfo"
|
||||||
|
@echo " gettext to make PO message catalogs"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " xml to make Docutils-native XML files"
|
||||||
|
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
html:
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
singlehtml:
|
||||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/dependency_injector.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/dependency_injector.qhc"
|
||||||
|
|
||||||
|
applehelp:
|
||||||
|
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||||
|
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||||
|
"~/Library/Documentation/Help or install it in your application" \
|
||||||
|
"bundle."
|
||||||
|
|
||||||
|
devhelp:
|
||||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished."
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/dependency_injector"
|
||||||
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/dependency_injector"
|
||||||
|
@echo "# devhelp"
|
||||||
|
|
||||||
|
epub:
|
||||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
|
latexpdf:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
latexpdfja:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||||
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
text:
|
||||||
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
|
man:
|
||||||
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
|
texinfo:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||||
|
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||||
|
"(use \`make info' here to do that automatically)."
|
||||||
|
|
||||||
|
info:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo "Running Texinfo files through makeinfo..."
|
||||||
|
make -C $(BUILDDIR)/texinfo info
|
||||||
|
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||||
|
|
||||||
|
gettext:
|
||||||
|
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||||
|
@echo "Testing of coverage in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/coverage/python.txt."
|
||||||
|
|
||||||
|
xml:
|
||||||
|
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||||
|
|
||||||
|
pseudoxml:
|
||||||
|
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
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;
|
||||||
|
}
|
1
docs/_static/logo.svg
vendored
Normal file
After Width: | Height: | Size: 5.8 KiB |
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>
|
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::
|
9
docs/api/containers.rst
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
dependency_injector.containers
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector.containers
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. disqus::
|
8
docs/api/errors.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
dependency_injector.errors
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector.errors
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
.. disqus::
|
12
docs/api/index.rst
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
API Documentation
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
top-level
|
||||||
|
providers
|
||||||
|
containers
|
||||||
|
wiring
|
||||||
|
errors
|
||||||
|
asgi-lifespan
|
10
docs/api/providers.rst
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
dependency_injector.providers
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector.providers
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
.. disqus::
|
8
docs/api/top-level.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
dependency_injector
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector
|
||||||
|
:members: __version__
|
||||||
|
|
||||||
|
|
||||||
|
.. disqus::
|
7
docs/api/wiring.rst
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
dependency_injector.wiring
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector.wiring
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. disqus::
|
313
docs/conf.py
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Dependency Injector documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Wed Apr 1 17:36:06 2015.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
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(".."))
|
||||||
|
|
||||||
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#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
|
||||||
|
# ones.
|
||||||
|
extensions = [
|
||||||
|
"alabaster",
|
||||||
|
"sphinx.ext.autodoc",
|
||||||
|
"sphinx_disqus.disqus",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
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"
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = "utf-8-sig"
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = "index"
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = "Dependency Injector"
|
||||||
|
copyright = "2024, Roman Mogylatov"
|
||||||
|
author = "Roman Mogylatov"
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = version
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#
|
||||||
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = "en"
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ""
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#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"]
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = "sphinx"
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||||
|
#keep_warnings = False
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# documentation.
|
||||||
|
# html_context = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
html_theme_path = [alabaster.get_path()]
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
#html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = None
|
||||||
|
|
||||||
|
# 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 = "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_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,
|
||||||
|
# using the given strftime format.
|
||||||
|
#html_last_updated_fmt = "%b %d, %Y"
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
#html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_sphinx = True
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# 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 = ""
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# A dictionary with options for the search language support, empty by 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"
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = "dependency_injectordoc"
|
||||||
|
|
||||||
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ("letterpaper" or "a4paper").
|
||||||
|
#"papersize": "letterpaper",
|
||||||
|
|
||||||
|
# The font size ("10pt", "11pt" or "12pt").
|
||||||
|
#"pointsize": "10pt",
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#"preamble": "",
|
||||||
|
|
||||||
|
# Latex figure (float) alignment
|
||||||
|
#"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"Roman Mogylatov", "manual"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output ---------------------------------------
|
||||||
|
|
||||||
|
# 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",
|
||||||
|
[author], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output -------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (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"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#texinfo_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#texinfo_domain_indices = True
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
#texinfo_no_detailmenu = False
|
||||||
|
|
||||||
|
autodoc_member_order = "bysource"
|
||||||
|
|
||||||
|
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 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::
|
72
docs/containers/declarative.rst
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
Declarative container
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.containers
|
||||||
|
|
||||||
|
:py:class:`DeclarativeContainer` is a class-based style of the providers definition.
|
||||||
|
|
||||||
|
You create the declarative container subclass, put the providers as attributes and create the
|
||||||
|
container instance.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/containers/declarative.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
The declarative container providers should only be used when you have the container instance.
|
||||||
|
Working with the providers of the container on the class level will influence all further
|
||||||
|
instances.
|
||||||
|
|
||||||
|
The declarative container can not have any methods or any other attributes then providers.
|
||||||
|
|
||||||
|
The container class provides next attributes:
|
||||||
|
|
||||||
|
- ``providers`` - the dictionary of all the container providers
|
||||||
|
- ``cls_providers`` - the dictionary of the container providers of the current container
|
||||||
|
- ``inherited_providers`` - the dictionary of all the inherited container providers
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/containers/declarative_inheritance.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
Injections in the declarative container are done the usual way:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/containers/declarative_injections.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
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::
|
25
docs/containers/dynamic.rst
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Dynamic container
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.containers
|
||||||
|
|
||||||
|
:py:class:`DynamicContainer` is a collection of the providers defined in the runtime.
|
||||||
|
|
||||||
|
You create the dynamic container instance and put the providers as attributes.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/containers/dynamic.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
The dynamic container is good for the case when your application structure depends on the
|
||||||
|
configuration file or some other source that you can reach only after application is already
|
||||||
|
running (database, api, etc).
|
||||||
|
|
||||||
|
In this example we use the configuration to fill in the dynamic container with the providers:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/containers/dynamic_runtime_creation.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
.. disqus::
|
||||||
|
|
29
docs/containers/index.rst
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
.. _containers:
|
||||||
|
|
||||||
|
Containers
|
||||||
|
==========
|
||||||
|
|
||||||
|
Containers are collections of the providers.
|
||||||
|
|
||||||
|
There are several use cases how you can use containers:
|
||||||
|
|
||||||
|
+ Keeping all the providers in a single container (most common).
|
||||||
|
+ Grouping of the providers from the same architectural layer (for example,
|
||||||
|
``Services``, ``Models`` and ``Forms`` containers).
|
||||||
|
+ Grouping of providers from the same functional groups (for example,
|
||||||
|
container ``Users``, that contains all functional parts of the ``users``
|
||||||
|
package).
|
||||||
|
|
||||||
|
Containers module API docs - :py:mod:`dependency_injector.containers`.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
declarative
|
||||||
|
dynamic
|
||||||
|
specialization
|
||||||
|
overriding
|
||||||
|
copying
|
||||||
|
reset_singletons
|
||||||
|
check_dependencies
|
||||||
|
traversal
|
40
docs/containers/overriding.rst
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
Container overriding
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.containers
|
||||||
|
|
||||||
|
The container can be overridden by the other container. All of the providers from the overriding
|
||||||
|
container will override the providers with the same names in the overridden container.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/containers/override.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
It helps in a testing. Also you can use it for configuring project for the different
|
||||||
|
environments: replace an API client with a stub on the dev or stage.
|
||||||
|
|
||||||
|
The container also has:
|
||||||
|
|
||||||
|
- ``container.overridden`` - tuple of all overriding containers.
|
||||||
|
- ``container.reset_last_overriding()`` - reset last overriding for each provider in the container.
|
||||||
|
- ``container.reset_override()`` - reset all overriding in the container.
|
||||||
|
|
||||||
|
: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::
|
25
docs/containers/specialization.rst
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Specialization of the container provider type
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.containers
|
||||||
|
|
||||||
|
You can make a restriction of the :py:class:`DeclarativeContainer` provider type:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/containers/declarative_provider_type.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 29-31
|
||||||
|
|
||||||
|
The emphasized lines will cause an error because ``other_provider`` is not a subtype of the
|
||||||
|
``ServiceProvider``. This helps to control the content of the container.
|
||||||
|
|
||||||
|
The same works for the :py:class:`DynamicContainer`:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/containers/dynamic_provider_type.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 23
|
||||||
|
|
||||||
|
The emphasized line will also cause an error.
|
||||||
|
|
||||||
|
.. 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::
|
68
docs/examples-other/chained-factories.rst
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
Chained Factories pattern
|
||||||
|
=========================
|
||||||
|
|
||||||
|
This example demonstrates "Chained Factories" pattern.
|
||||||
|
|
||||||
|
The idea of the pattern is in wrapping ``Factory`` into another ``Factory`` that adds
|
||||||
|
additional arguments.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
base_factory = providers.Factory(
|
||||||
|
SomeClass,
|
||||||
|
base_argument=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
concrete_factory = providers.Factory(
|
||||||
|
base_factory,
|
||||||
|
extra_argument=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
instance = concrete_factory()
|
||||||
|
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
|
||||||
|
|
||||||
|
Sample code
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Listing of the pattern example:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/factory-patterns/chained_factories.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Arguments priority
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Passing of the arguments works the same way like for any other :ref:`factory-provider`.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# 1. Keyword arguments of upper level factory are added to lower level factory
|
||||||
|
chained_dict_factory = providers.Factory(
|
||||||
|
providers.Factory(dict, 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}
|
||||||
|
|
||||||
|
# 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}
|
||||||
|
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
|
||||||
|
The "Chained Factories" pattern was suggested by the ``Dependency Injector`` users.
|
||||||
|
|
||||||
|
.. disqus::
|
74
docs/examples-other/factory-of-factories.rst
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
Factory of Factories pattern
|
||||||
|
============================
|
||||||
|
|
||||||
|
This example demonstrates "Factory of Factories" pattern.
|
||||||
|
|
||||||
|
The idea of the pattern is in creating a ``Factory`` that creates another ``Factory`` and adds
|
||||||
|
additional arguments.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
base_factory = providers.Factory(
|
||||||
|
providers.Factory
|
||||||
|
SomeClass,
|
||||||
|
base_argument=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
concrete_factory = providers.Factory(
|
||||||
|
OtherClass,
|
||||||
|
instance=base_factory(extra_argument=1),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
instance = concrete_factory()
|
||||||
|
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
|
||||||
|
|
||||||
|
Sample code
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Listing of the pattern example:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/factory-patterns/factory_of_factories.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Arguments priority
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Passing of the arguments works the same way like for any other :ref:`factory-provider`.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# 1. Keyword arguments of upper level factory are added to lower level factory
|
||||||
|
factory_of_dict_factories = providers.Factory(
|
||||||
|
providers.Factory,
|
||||||
|
dict,
|
||||||
|
arg1=1,
|
||||||
|
)
|
||||||
|
dict_factory = factory_of_dict_factories(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(
|
||||||
|
providers.Factory,
|
||||||
|
dict,
|
||||||
|
arg1=1,
|
||||||
|
)
|
||||||
|
dict_factory = factory_of_dict_factories(arg1=2)
|
||||||
|
print(dict_factory()) # prints: {"arg1": 2}
|
||||||
|
|
||||||
|
# 3. Keyword arguments provided from context have the most priority
|
||||||
|
factory_of_dict_factories = providers.Factory(
|
||||||
|
providers.Factory,
|
||||||
|
dict,
|
||||||
|
arg1=1,
|
||||||
|
)
|
||||||
|
dict_factory = factory_of_dict_factories(arg1=2)
|
||||||
|
print(dict_factory(arg1=3)) # prints: {"arg1": 3}
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
|
||||||
|
The "Factory of Factories" pattern was suggested by the ``Dependency Injector`` users.
|
||||||
|
|
||||||
|
.. disqus::
|
17
docs/examples-other/index.rst
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
Other examples
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
|
||||||
|
Framework
|
||||||
|
:description: This sections contains assorted Dependency Injector examples.
|
||||||
|
|
||||||
|
This sections contains assorted ``Dependency Injector`` examples.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
use-cases
|
||||||
|
password-hashing
|
||||||
|
chained-factories
|
||||||
|
factory-of-factories
|
30
docs/examples-other/password-hashing.rst
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
Password hashing example
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
|
||||||
|
Framework,Callable
|
||||||
|
:description: This example demonstrates a usage of the Callable provider.
|
||||||
|
|
||||||
|
This example demonstrates an injection of the ``Callable`` provider.
|
||||||
|
|
||||||
|
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/password-hashing>`_.
|
||||||
|
|
||||||
|
Sample code
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Listing of the pattern example:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/password-hashing/example.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Run the example
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Instructions for running:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python example.py
|
||||||
|
|
||||||
|
.. disqus::
|
74
docs/examples-other/use-cases.rst
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
Use cases example
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
|
||||||
|
Framework,DependenciesContainer
|
||||||
|
:description: This example demonstrates a usage of the DependenciesContainer provider.
|
||||||
|
|
||||||
|
This example demonstrates a usage of the ``DependenciesContainer`` provider.
|
||||||
|
|
||||||
|
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
|
||||||
|
|
||||||
|
Application structure
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Example application has next structure:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
|
||||||
|
./
|
||||||
|
└── example/
|
||||||
|
├── __init__.py
|
||||||
|
├── __main__.py
|
||||||
|
├── adapters.py
|
||||||
|
├── containers.py
|
||||||
|
└── usecases.py
|
||||||
|
|
||||||
|
Containers
|
||||||
|
----------
|
||||||
|
|
||||||
|
Listing of the ``example/containers.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/use-cases/example/containers.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Main module
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Listing of the ``example/__main__.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/use-cases/example/__main__.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
|
||||||
|
Run the application
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Instructions for running in the "test" mode:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python run.py test example@example.com
|
||||||
|
|
||||||
|
Instructions for running in the "prod" mode:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python run.py prod example@example.com
|
||||||
|
|
||||||
|
Adapters and use cases
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Listing of the ``example/adapters.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/use-cases/example/adapters.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Listing of the ``example/usecases.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/use-cases/example/usecases.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
.. disqus::
|
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::
|
89
docs/examples/application-multiple-containers.rst
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
.. _application-multiple-containers:
|
||||||
|
|
||||||
|
Application example (multiple containers)
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
|
||||||
|
Framework,AWS,boto3,client
|
||||||
|
:description: This example shows how you can create an application using multiple declarative
|
||||||
|
containers. We build an example Python micro application following the dependency
|
||||||
|
injection principle. It consists from several services with a domain logic that
|
||||||
|
have dependencies on database & AWS S3.
|
||||||
|
|
||||||
|
This example shows how you can create an application using multiple declarative containers. Using
|
||||||
|
multiple declarative containers is a good choice for a large application. For
|
||||||
|
building a moderate or a small size application refer to :ref:`application-single-container`.
|
||||||
|
|
||||||
|
We build an example micro application following the dependency injection principle. It consists
|
||||||
|
of several services with a domain logic. The services have dependencies on database & AWS S3.
|
||||||
|
|
||||||
|
.. image:: images/application.png
|
||||||
|
:width: 100%
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Start from the scratch or jump to the section:
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
:backlinks: none
|
||||||
|
|
||||||
|
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>`_.
|
||||||
|
|
||||||
|
Application structure
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Application consists of an ``example`` package, a configuration file and a ``requirements.txt``
|
||||||
|
file.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
./
|
||||||
|
├── example/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── __main__.py
|
||||||
|
│ ├── containers.py
|
||||||
|
│ └── services.py
|
||||||
|
├── config.yml
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
Containers
|
||||||
|
----------
|
||||||
|
|
||||||
|
Listing of the ``example/containers.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/containers.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Main module
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Listing of the ``example/__main__.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/__main__.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
Listing of the ``example/services.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/services.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Listing of the ``config.yml``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/config.yml
|
||||||
|
:language: yaml
|
||||||
|
|
||||||
|
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::
|
95
docs/examples/application-single-container.rst
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
.. _application-single-container:
|
||||||
|
|
||||||
|
Application example (single container)
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
|
||||||
|
Framework,AWS,boto3,client
|
||||||
|
:description: This example shows how you can create an application using a single declarative
|
||||||
|
container. We build an example Python micro application following the dependency
|
||||||
|
injection principle. It consists from several services with a domain logic that
|
||||||
|
have dependencies on database & AWS S3.
|
||||||
|
|
||||||
|
This example shows how you can create an application using a single declarative container. Using
|
||||||
|
a single declarative container is a good choice for small or moderate size application. For
|
||||||
|
building a large application refer to :ref:`application-multiple-containers`.
|
||||||
|
|
||||||
|
We build an example micro application following the dependency injection principle. It consists
|
||||||
|
of several services with a domain logic. The services have dependencies on database & AWS S3.
|
||||||
|
|
||||||
|
.. image:: images/application.png
|
||||||
|
:width: 100%
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Start from the scratch or jump to the section:
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
:backlinks: none
|
||||||
|
|
||||||
|
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>`_.
|
||||||
|
|
||||||
|
Application structure
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Application consists of an ``example`` package, several configuration files and a
|
||||||
|
``requirements.txt`` file.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
./
|
||||||
|
├── example/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── __main__.py
|
||||||
|
│ ├── containers.py
|
||||||
|
│ └── services.py
|
||||||
|
├── config.ini
|
||||||
|
├── logging.ini
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
Container
|
||||||
|
---------
|
||||||
|
|
||||||
|
Listing of the ``example/containers.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/application-single-container/example/containers.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Main module
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Listing of the ``example/__main__.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/application-single-container/example/__main__.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
Listing of the ``example/services.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/application-single-container/example/services.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Listing of the ``config.ini``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/application-single-container/config.ini
|
||||||
|
:language: ini
|
||||||
|
|
||||||
|
Listing of the ``logging.ini``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/application-single-container/logging.ini
|
||||||
|
:language: ini
|
||||||
|
|
||||||
|
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::
|
134
docs/examples/decoupled-packages.rst
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
.. _decoupled-packages:
|
||||||
|
|
||||||
|
Decoupled packages example (multiple containers)
|
||||||
|
================================================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
|
||||||
|
Framework,AWS,boto3,client
|
||||||
|
:description: This example shows how to use Dependency Injector to create Python decoupled packages.
|
||||||
|
To achieve a decoupling each package has a container with the components. When
|
||||||
|
a component needs a dependency from the outside of the package scope we use the
|
||||||
|
Dependency provider. The package container has no knowledge on where the
|
||||||
|
dependencies come from. It states a need that the dependencies must be provided.
|
||||||
|
This helps to decouple a package from the 3rd party dependencies and other
|
||||||
|
packages.
|
||||||
|
|
||||||
|
This example shows how to use ``Dependency Injector`` to create decoupled packages.
|
||||||
|
|
||||||
|
To achieve a decoupling each package has a container with the components. When a component needs a
|
||||||
|
dependency from the outside of the package scope we use the ``Dependency`` provider. The package
|
||||||
|
container has no knowledge on where the dependencies come from. It states a need that the
|
||||||
|
dependencies must be provided. This helps to decouple a package from the 3rd party dependencies
|
||||||
|
and other packages.
|
||||||
|
|
||||||
|
To wire the packages we use an application container. Application container has all 3rd party
|
||||||
|
dependencies and package containers. It wires the packages and dependencies to create a
|
||||||
|
complete application.
|
||||||
|
|
||||||
|
We build an example micro application that consists of 3 packages:
|
||||||
|
|
||||||
|
- ``user`` - a package with user domain logic, depends on a database
|
||||||
|
- ``photo`` - a package with photo domain logic, depends on a database and AWS S3
|
||||||
|
- ``analytics`` - a package with analytics domain logic, depends on the ``user`` and ``photo``
|
||||||
|
package components
|
||||||
|
|
||||||
|
.. image:: images/decoupled-packages.png
|
||||||
|
:width: 100%
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Start from the scratch or jump to the section:
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
:backlinks: none
|
||||||
|
|
||||||
|
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>`_.
|
||||||
|
|
||||||
|
Application structure
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Application consists of an ``example`` package, a configuration file and a ``requirements.txt``
|
||||||
|
file.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
./
|
||||||
|
├── example/
|
||||||
|
│ ├── analytics/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── containers.py
|
||||||
|
│ │ └── services.py
|
||||||
|
│ ├── photo/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── containers.py
|
||||||
|
│ │ ├── entities.py
|
||||||
|
│ │ └── repositories.py
|
||||||
|
│ ├── user/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── containers.py
|
||||||
|
│ │ ├── entities.py
|
||||||
|
│ │ └── repositories.py
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── __main__.py
|
||||||
|
│ └── containers.py
|
||||||
|
├── config.ini
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
Package containers
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Listing of the ``example/user/containers.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/user/containers.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Listing of the ``example/photo/containers.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/photo/containers.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Listing of the ``example/analytics/containers.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/analytics/containers.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Application container
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Application container consists of all packages and 3rd party dependencies. Its role is to wire
|
||||||
|
everything together in a complete application.
|
||||||
|
|
||||||
|
Listing of the ``example/containers.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/containers.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Package ``analytics`` has dependencies on the repositories from the ``user`` and
|
||||||
|
``photo`` packages. This is an example of how you can pass the dependencies from one package
|
||||||
|
to another.
|
||||||
|
|
||||||
|
Main module
|
||||||
|
-----------
|
||||||
|
Listing of the ``example/__main__.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/__main__.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Listing of the ``config.ini``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/decoupled-packages/config.ini
|
||||||
|
:language: ini
|
||||||
|
|
||||||
|
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/application.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
docs/examples/images/decoupled-packages.png
Normal file
After Width: | Height: | Size: 182 KiB |
BIN
docs/examples/images/django.png
Normal file
After Width: | Height: | Size: 382 KiB |
BIN
docs/examples/images/flask.png
Normal file
After Width: | Height: | Size: 647 KiB |
27
docs/examples/index.rst
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Example
|
||||||
|
:description: Python dependency injection examples.
|
||||||
|
|
||||||
|
Explore the examples to see the ``Dependency Injector`` in action.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
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 |
150
docs/index.rst
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
=================================================================
|
||||||
|
Dependency Injector --- Dependency injection framework for Python
|
||||||
|
=================================================================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:google-site-verification: V1hlKfpgL3AARAElwFcqP4qW1Smsx5bKSRU8O86i20Y
|
||||||
|
:keywords: Python,Dependency injection,DI,Inversion of Control,IoC,
|
||||||
|
IoC Container,Factory, Singleton, Design Patterns
|
||||||
|
: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
|
||||||
|
using Cython.
|
||||||
|
|
||||||
|
.. _index:
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/dependency_injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Latest Version
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/l/dependency_injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: License
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Supported Python versions
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Supported Python implementations
|
||||||
|
|
||||||
|
.. image:: https://static.pepy.tech/badge/dependency-injector
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://static.pepy.tech/badge/dependency-injector/month
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://static.pepy.tech/badge/dependency-injector/week
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/wheel/dependency-injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Wheel
|
||||||
|
|
||||||
|
.. 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:: 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
|
||||||
|
|
||||||
|
``Dependency Injector`` is a dependency injection framework for Python.
|
||||||
|
|
||||||
|
It helps implementing the dependency injection principle.
|
||||||
|
|
||||||
|
Key features of the ``Dependency Injector``:
|
||||||
|
|
||||||
|
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||||
|
``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
|
||||||
|
:ref:`provider-overriding`.
|
||||||
|
- **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`.
|
||||||
|
- **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`.
|
||||||
|
- **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):
|
||||||
|
|
||||||
|
config = providers.Configuration()
|
||||||
|
|
||||||
|
api_client = providers.Singleton(
|
||||||
|
ApiClient,
|
||||||
|
api_key=config.api_key,
|
||||||
|
timeout=config.timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
service = providers.Factory(
|
||||||
|
Service,
|
||||||
|
api_client=api_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def main(service: Service = Provide[Container.service]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container()
|
||||||
|
container.config.api_key.from_env("API_KEY", required=True)
|
||||||
|
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
|
||||||
|
container.wire(modules=[__name__])
|
||||||
|
|
||||||
|
main() # <-- dependency is injected automatically
|
||||||
|
|
||||||
|
with container.api_client.override(mock.Mock()):
|
||||||
|
main() # <-- overridden dependency is injected automatically
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Explore the documentation to know more about the ``Dependency Injector``.
|
||||||
|
|
||||||
|
.. _contents:
|
||||||
|
|
||||||
|
Contents
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
introduction/index
|
||||||
|
examples/index
|
||||||
|
tutorials/index
|
||||||
|
providers/index
|
||||||
|
containers/index
|
||||||
|
wiring
|
||||||
|
examples-other/index
|
||||||
|
api/index
|
||||||
|
main/feedback
|
||||||
|
main/changelog
|
315
docs/introduction/di_in_python.rst
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
Dependency injection and inversion of control in Python
|
||||||
|
=======================================================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Example
|
||||||
|
:description: This page describes a usage of the dependency injection and inversion of control
|
||||||
|
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 show how to use Dependency Injector providers overriding
|
||||||
|
feature for testing or configuring project in different environments and explains
|
||||||
|
why it's better than monkey-patching.
|
||||||
|
|
||||||
|
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 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
|
||||||
|
Python developer rarely needs. Python developers say that dependency injection can be implemented
|
||||||
|
easily using language fundamentals.
|
||||||
|
|
||||||
|
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?
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Let's see what the dependency injection is.
|
||||||
|
|
||||||
|
Dependency injection is a principle that helps to decrease coupling and increase cohesion.
|
||||||
|
|
||||||
|
.. image:: images/coupling-cohesion.png
|
||||||
|
|
||||||
|
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 superglue or welding. No easy way
|
||||||
|
to disassemble.
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
Cohesion often correlates with coupling. Higher cohesion usually leads to lower coupling and vice versa.
|
||||||
|
|
||||||
|
Low coupling brings flexibility. Your code becomes easier to change and test.
|
||||||
|
|
||||||
|
How to implement the dependency injection?
|
||||||
|
|
||||||
|
Objects do not create each other anymore. They provide a way to inject the dependencies instead.
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class ApiClient:
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.api_key = os.getenv("API_KEY") # <-- dependency
|
||||||
|
self.timeout = int(os.getenv("TIMEOUT")) # <-- dependency
|
||||||
|
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.api_client = ApiClient() # <-- dependency
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
service = Service() # <-- dependency
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class ApiClient:
|
||||||
|
|
||||||
|
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) -> None:
|
||||||
|
self.api_client = api_client # <-- dependency is injected
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
``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 and inject the objects like this:
|
||||||
|
|
||||||
|
.. 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.
|
||||||
|
|
||||||
|
Here comes the ``Dependency Injector``.
|
||||||
|
|
||||||
|
What does the Dependency Injector do?
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
With the dependency injection pattern, objects lose the responsibility of assembling
|
||||||
|
the dependencies. The ``Dependency Injector`` absorbs that responsibility.
|
||||||
|
|
||||||
|
``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 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):
|
||||||
|
|
||||||
|
config = providers.Configuration()
|
||||||
|
|
||||||
|
api_client = providers.Singleton(
|
||||||
|
ApiClient,
|
||||||
|
api_key=config.api_key,
|
||||||
|
timeout=config.timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
service = providers.Factory(
|
||||||
|
Service,
|
||||||
|
api_client=api_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def main(service: Service = Provide[Container.service]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container()
|
||||||
|
container.config.api_key.from_env("API_KEY", required=True)
|
||||||
|
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
|
||||||
|
container.wire(modules=[__name__])
|
||||||
|
|
||||||
|
main() # <-- dependency is injected automatically
|
||||||
|
|
||||||
|
with container.api_client.override(mock.Mock()):
|
||||||
|
main() # <-- overridden dependency is injected automatically
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 monkey-patching.
|
||||||
|
|
||||||
|
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 dependency injection, you patch the interface, not an implementation. This is a way more
|
||||||
|
stable approach.
|
||||||
|
|
||||||
|
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 provides you with three advantages:
|
||||||
|
|
||||||
|
- **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 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 a container. This
|
||||||
|
provides an overview and control of the application structure. It is easier to understand and
|
||||||
|
change it.
|
||||||
|
|
||||||
|
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 the benefits.
|
||||||
|
|
||||||
|
Is it worth using a framework for applying dependency injection?
|
||||||
|
|
||||||
|
The complexity of the dependency injection pattern implementation in Python is
|
||||||
|
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
|
||||||
|
- Other engineers are familiar with it
|
||||||
|
|
||||||
|
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 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 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?
|
||||||
|
------------
|
||||||
|
|
||||||
|
Choose one of the following as a next step:
|
||||||
|
|
||||||
|
- Look at the 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`
|
||||||
|
- Pass the tutorials:
|
||||||
|
- :ref:`flask-tutorial`
|
||||||
|
- :ref:`aiohttp-tutorial`
|
||||||
|
- :ref:`asyncio-daemon-tutorial`
|
||||||
|
- :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
|
||||||
|
------------
|
||||||
|
|
||||||
|
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::
|
BIN
docs/introduction/images/coupling-cohesion.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
18
docs/introduction/index.rst
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
|
||||||
|
:description: Current section of the documentation is provides an
|
||||||
|
overview of the dependency injection, inversion of
|
||||||
|
control and 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
|
||||||
|
|
||||||
|
di_in_python
|
||||||
|
key_features
|
||||||
|
installation
|
42
docs/introduction/installation.rst
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
``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
|
||||||
|
|
||||||
|
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>`_.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
To verify the installed version:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
>>> import dependency_injector
|
||||||
|
>>> dependency_injector.__version__
|
||||||
|
'4.39.0'
|
||||||
|
|
||||||
|
.. 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::
|
43
docs/introduction/key_features.rst
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
.. _key-features:
|
||||||
|
|
||||||
|
Key features
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
|
||||||
|
:description: This article describes key features of the Dependency Injector
|
||||||
|
framework.
|
||||||
|
|
||||||
|
Key features of the ``Dependency Injector``:
|
||||||
|
|
||||||
|
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||||
|
``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
|
||||||
|
:ref:`provider-overriding`.
|
||||||
|
- **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`.
|
||||||
|
- **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`.
|
||||||
|
- **Performance**. Fast. Written in ``Cython``.
|
||||||
|
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
|
||||||
|
|
||||||
|
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
Explicit is better than implicit
|
||||||
|
|
||||||
|
You need to specify how to assemble and where to inject the dependencies explicitly.
|
||||||
|
|
||||||
|
The power of the framework is in its simplicity.
|
||||||
|
``Dependency Injector`` is a simple tool for the powerful concept.
|
||||||
|
|
||||||
|
.. disqus::
|
2033
docs/main/changelog.rst
Normal file
8
docs/main/feedback.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Feedback
|
||||||
|
========
|
||||||
|
|
||||||
|
To post a question, bug report, a feature proposal or get some help open a
|
||||||
|
`Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_ or leave a comment
|
||||||
|
below.
|
||||||
|
|
||||||
|
.. 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::
|
20
docs/providers/callable.rst
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Callable provider
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Function,Method,Example
|
||||||
|
:description: Callable provider helps to make dependencies injection into functions. This page
|
||||||
|
demonstrates how to use a Callable provider.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
:py:class:`Callable` provider calls a function, a method or another callable.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/callable.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
``Callable`` provider handles an injection of the dependencies the same way like a
|
||||||
|
:ref:`factory-provider`.
|
||||||
|
|
||||||
|
.. disqus::
|
579
docs/providers/configuration.rst
Normal file
|
@ -0,0 +1,579 @@
|
||||||
|
.. _configuration-provider:
|
||||||
|
|
||||||
|
Configuration provider
|
||||||
|
======================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
|
||||||
|
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, 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
|
||||||
|
|
||||||
|
:py:class:`Configuration` provider provides configuration options to the other providers.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration.py
|
||||||
|
:language: python
|
||||||
|
:emphasize-lines: 7,12-13
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
It implements the principle "use first, define later".
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
:backlinks: none
|
||||||
|
|
||||||
|
Loading from an INI file
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
``Configuration`` provider can load configuration from an ``ini`` file using the
|
||||||
|
:py:meth:`Configuration.from_ini` method:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration_ini.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 12
|
||||||
|
|
||||||
|
where ``examples/providers/configuration/config.ini`` is:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/config.ini
|
||||||
|
:language: ini
|
||||||
|
|
||||||
|
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
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
``Configuration`` provider can load configuration from a ``yaml`` file using the
|
||||||
|
:py:meth:`Configuration.from_yaml` method:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration_yaml.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 12
|
||||||
|
|
||||||
|
where ``examples/providers/configuration/config.yml`` is:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/config.yml
|
||||||
|
:language: ini
|
||||||
|
|
||||||
|
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::
|
||||||
|
|
||||||
|
Loading of a yaml configuration requires ``PyYAML`` package.
|
||||||
|
|
||||||
|
You can install the ``Dependency Injector`` with an extra dependency::
|
||||||
|
|
||||||
|
pip install dependency-injector[yaml]
|
||||||
|
|
||||||
|
or install ``PyYAML`` directly::
|
||||||
|
|
||||||
|
pip install pyyaml
|
||||||
|
|
||||||
|
*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
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
``Configuration`` provider can load configuration from a Python ``dict`` using the
|
||||||
|
:py:meth:`Configuration.from_dict` method:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration_dict.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 12-19
|
||||||
|
|
||||||
|
Loading from an environment variable
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
``Configuration`` provider can load configuration from an environment variable using the
|
||||||
|
:py:meth:`Configuration.from_env` method:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration_env.py
|
||||||
|
:language: python
|
||||||
|
: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
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
``Configuration`` provider can load configuration from the multiple sources. Loaded
|
||||||
|
configuration is merged recursively over the existing configuration.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration_multiple.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 12-13
|
||||||
|
|
||||||
|
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
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
You can specify the type of the injected configuration value explicitly.
|
||||||
|
|
||||||
|
This helps when you read the value from an ini file or an environment variable and need to
|
||||||
|
convert it into an ``int`` or a ``float``.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration_type.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 19
|
||||||
|
|
||||||
|
``Configuration`` provider has next helper methods:
|
||||||
|
|
||||||
|
- ``.as_int()``
|
||||||
|
- ``.as_float()``
|
||||||
|
- ``.as_(callback, *args, **kwargs)``
|
||||||
|
|
||||||
|
The last method ``.as_(callback, *args, **kwargs)`` helps to implement other conversions.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration_type_custom.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 18
|
||||||
|
|
||||||
|
With the ``.as_(callback, *args, **kwargs)`` you can specify a function that will be called
|
||||||
|
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
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
You can inject invariant configuration options based on the value of the other configuration
|
||||||
|
option.
|
||||||
|
|
||||||
|
To use that you should provide the switch-value as an item of the configuration option that
|
||||||
|
contains sections ``config.options[config.switch]``:
|
||||||
|
|
||||||
|
- When the value of the ``config.switch`` is ``A``, the ``config.options.A`` is injected
|
||||||
|
- When the value of the ``config.switch`` is ``B``, the ``config.options.B`` is injected
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration_itemselector.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 15,30-31,38
|
||||||
|
|
||||||
|
.. disqus::
|
27
docs/providers/coroutine.rst
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Coroutine provider
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Coroutine,Asynchronous,
|
||||||
|
Asyncio,Example
|
||||||
|
:description: Coroutine provider creates a coroutine. This page demonstrates how to use a
|
||||||
|
Coroutine provider.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
:py:class:`Coroutine` provider creates a coroutine.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/coroutine.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The example works on Python 3.7+. For earlier versions use ``loop.run_until_complete()``.
|
||||||
|
|
||||||
|
``Coroutine`` provider handles an injection of the dependencies the same way like a
|
||||||
|
:ref:`factory-provider`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
``Coroutine`` provider returns ``True`` for ``asyncio.iscoroutinefunction()`` check.
|
||||||
|
|
||||||
|
.. disqus::
|
49
docs/providers/custom.rst
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
.. _create-provider:
|
||||||
|
|
||||||
|
Creating a custom provider
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Custom provider, Create
|
||||||
|
:description: This page demonstrates how to create a custom provider.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
You can create a custom provider.
|
||||||
|
|
||||||
|
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 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
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
.. 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 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.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
If you don't find needed provider in the ``providers`` module and experience troubles creating
|
||||||
|
one by your own - open a
|
||||||
|
`Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_.
|
||||||
|
|
||||||
|
I'll help you to resolve the issue if that's possible. If the new provider can be useful for
|
||||||
|
others I'll include it into the ``providers`` module.
|
||||||
|
|
||||||
|
.. disqus::
|
38
docs/providers/dependency.rst
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
.. _dependency-provider:
|
||||||
|
|
||||||
|
Dependency provider
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
:py:class:`Dependency` provider is a placeholder for a dependency of a certain 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,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::
|
242
docs/providers/factory.rst
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
.. _factory-provider:
|
||||||
|
|
||||||
|
Factory provider
|
||||||
|
================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Factory,Abstract Factory,
|
||||||
|
Pattern,Example,Aggregate
|
||||||
|
:description: Factory provider helps to implement dependency injection in Python. This page
|
||||||
|
demonstrates how to use Factory provider, inject the dependencies, and assemble
|
||||||
|
object graphs passing the injections deep inside. It also provides the examples
|
||||||
|
of the Abstract Factory pattern & provider and Factories Aggregation pattern.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
:py:class:`Factory` provider creates new objects.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/factory.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
The first argument of the ``Factory`` provider is a class, a factory function or a method
|
||||||
|
that creates an object.
|
||||||
|
|
||||||
|
The rest of the ``Factory`` positional and keyword arguments are the dependencies.
|
||||||
|
``Factory`` injects the dependencies every time when creates a new object. The dependencies are
|
||||||
|
injected following these rules:
|
||||||
|
|
||||||
|
+ If the dependency is a provider, this provider is called and the result of the call is injected.
|
||||||
|
+ If you need to inject the provider itself, you should use the ``.provider`` attribute. More at
|
||||||
|
:ref:`factory_providers_delegation`.
|
||||||
|
+ All other dependencies are injected *"as is"*.
|
||||||
|
+ Positional context arguments are appended after ``Factory`` positional dependencies.
|
||||||
|
+ Keyword context arguments have the priority over the ``Factory`` keyword dependencies with the
|
||||||
|
same name.
|
||||||
|
|
||||||
|
.. image:: images/factory_init_injections.png
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/factory_init_injections.py
|
||||||
|
: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
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
``Factory`` provider can pass the arguments to the underlying providers. This helps when you need
|
||||||
|
to assemble a nested objects graph and pass the arguments deep inside.
|
||||||
|
|
||||||
|
Consider the example:
|
||||||
|
|
||||||
|
.. image:: images/factory_init_injections_underlying.png
|
||||||
|
|
||||||
|
To create an ``Algorithm`` you need to provide all the dependencies: ``ClassificationTask``,
|
||||||
|
``Loss``, and ``Regularizer``. The last object in the chain, the ``Regularizer`` has a dependency
|
||||||
|
on the ``alpha`` value. The ``alpha`` value varies from algorithm to algorithm:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
Algorithm(
|
||||||
|
task=ClassificationTask(
|
||||||
|
loss=Loss(
|
||||||
|
regularizer=Regularizer(
|
||||||
|
alpha=alpha, # <-- the dependency
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
``Factory`` provider helps to deal with the such assembly. You need to create the factories for
|
||||||
|
all the classes and use special double-underscore ``__`` syntax for passing the ``alpha`` argument:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/factory_init_injections_underlying.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 44,49
|
||||||
|
|
||||||
|
When you use ``__`` separator in the name of the keyword argument the ``Factory`` looks for
|
||||||
|
the dependency with the same name as the left part of the ``__`` expression.
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
<dependency>__<keyword for the underlying provider>=<value>
|
||||||
|
|
||||||
|
If ``<dependency>`` is found the underlying provider will receive the
|
||||||
|
``<keyword for the underlying provider>=<value>`` as an argument.
|
||||||
|
|
||||||
|
.. _factory_providers_delegation:
|
||||||
|
|
||||||
|
Passing providers to the objects
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
When you need to inject the provider itself, but not the result of its call, use the ``.provider``
|
||||||
|
attribute of the provider that you're going to inject.
|
||||||
|
|
||||||
|
.. image:: images/factory_delegation.png
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/factory_delegation.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 28
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
You can create a specialized ``Factory`` provider that provides only specific type. For doing
|
||||||
|
this you need to create a subclass of the ``Factory`` provider and define the ``provided_type``
|
||||||
|
class attribute.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/factory_provided_type.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 12-14
|
||||||
|
|
||||||
|
.. _abstract-factory:
|
||||||
|
|
||||||
|
Abstract factory
|
||||||
|
----------------
|
||||||
|
|
||||||
|
:py:class:`AbstractFactory` provider helps when you need to create a provider of some base class
|
||||||
|
and the particular implementation is not yet know. ``AbstractFactory`` provider is a ``Factory``
|
||||||
|
provider with two peculiarities:
|
||||||
|
|
||||||
|
+ Provides only objects of a specified type.
|
||||||
|
+ Must be overridden before usage.
|
||||||
|
|
||||||
|
.. image:: images/abstract_factory.png
|
||||||
|
:width: 100%
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/abstract_factory.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 34
|
||||||
|
|
||||||
|
.. _factory-aggregate-provider:
|
||||||
|
|
||||||
|
Factory aggregate
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
:py:class:`FactoryAggregate` provider aggregates multiple factories.
|
||||||
|
|
||||||
|
.. 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%
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/factory_aggregate.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 33-37,47
|
||||||
|
|
||||||
|
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")``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
You can not override the ``FactoryAggregate`` provider.
|
||||||
|
|
||||||
|
.. 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/abstract_factory.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
docs/providers/images/async_mode.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
docs/providers/images/factory_aggregate.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/providers/images/factory_delegation.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
docs/providers/images/factory_init_injections.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/providers/images/factory_init_injections_underlying.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
docs/providers/images/overriding.png
Normal file
After Width: | Height: | Size: 13 KiB |
57
docs/providers/index.rst
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
.. _providers:
|
||||||
|
|
||||||
|
Providers
|
||||||
|
=========
|
||||||
|
|
||||||
|
Providers help to assemble the objects. They create objects and inject the dependencies.
|
||||||
|
|
||||||
|
Each provider is a callable. You call the provider like a function when you need to create an
|
||||||
|
object. Provider retrieves the underlying dependencies and inject them into the created object.
|
||||||
|
It causes the cascade effect that helps to assemble object graphs. See ``Factory``, ``Singleton``,
|
||||||
|
``Callable`` and other provider docs below.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
provider1()
|
||||||
|
│
|
||||||
|
├──> provider2()
|
||||||
|
│
|
||||||
|
├──> provider3()
|
||||||
|
│ │
|
||||||
|
│ └──> provider4()
|
||||||
|
│
|
||||||
|
└──> provider5()
|
||||||
|
│
|
||||||
|
└──> provider6()
|
||||||
|
|
||||||
|
Another providers feature is an overriding. You can override any provider with another provider.
|
||||||
|
This helps in testing. This also helps in overriding API clients with stubs for the development
|
||||||
|
or staging environment. See the example at :ref:`provider-overriding`.
|
||||||
|
|
||||||
|
If you need to inject not the whole object but the parts see :ref:`provided-instance`.
|
||||||
|
|
||||||
|
To create a new provider see :ref:`create-provider`.
|
||||||
|
|
||||||
|
Providers module API docs - :py:mod:`dependency_injector.providers`
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
factory
|
||||||
|
singleton
|
||||||
|
callable
|
||||||
|
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::
|
||||||
|
|
23
docs/providers/list.rst
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
List provider
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,List,Injection
|
||||||
|
:description: List provider helps to inject a list of the dependencies. This page demonstrates
|
||||||
|
how to use a List provider.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
:py:class:`List` provider provides a list of values.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/list.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 21-24
|
||||||
|
|
||||||
|
``List`` provider handles positional arguments the same way as a :ref:`factory-provider`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Keyword argument are not supported.
|
||||||
|
|
||||||
|
.. disqus::
|
17
docs/providers/object.rst
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
Object provider
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Object
|
||||||
|
:description: Object provider provides an object "as is". This page demonstrates how to use an
|
||||||
|
Object provider.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
:py:class:`Object` provider returns an object "as is".
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/object.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
.. disqus::
|
43
docs/providers/overriding.rst
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
.. _provider-overriding:
|
||||||
|
|
||||||
|
Provider overriding
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Override,Test,Unit
|
||||||
|
:description: This page demonstrates how to implement providers overriding. This helps in
|
||||||
|
testing and configuring the system for the multiple environments.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
You can override any provider with another provider.
|
||||||
|
|
||||||
|
When provider is overridden it calls to the overriding provider instead of providing
|
||||||
|
the object by its own.
|
||||||
|
|
||||||
|
This helps in testing. This also helps in overriding API clients with stubs for the development
|
||||||
|
or staging environment.
|
||||||
|
|
||||||
|
To override a provider you need to call the ``Provider.override()`` method. This method receives
|
||||||
|
a single argument called ``overriding``. If the ``overriding`` value is a provider, this provider
|
||||||
|
is called instead of the original. If value is not a provider, this value is returned instead of
|
||||||
|
calling the original provider.
|
||||||
|
|
||||||
|
.. image:: images/overriding.png
|
||||||
|
:width: 80%
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/overriding.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
You can override a provider multiple times. In that case the latest ``overriding`` value will be
|
||||||
|
used. The rest of the overriding values will form a stack.
|
||||||
|
|
||||||
|
To reset an overriding you can use the ``Provider.reset_override()`` or
|
||||||
|
``Provider.reset_last_overriding()`` methods.
|
||||||
|
|
||||||
|
You can use a context manager for overriding a provider ``with Provider.override():``. The
|
||||||
|
overriding will be reset when context closed.
|
||||||
|
|
||||||
|
.. disqus::
|
38
docs/providers/provided_instance.rst
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
.. _provided-instance:
|
||||||
|
|
||||||
|
Injecting provided object attributes, items, or call its methods
|
||||||
|
================================================================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Attribute,Method,Call
|
||||||
|
:description: This page demonstrates how to inject attributes, items or call method of the
|
||||||
|
provided instance.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
You can inject provided object attribute, item or result of its method call.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/provided_instance.py
|
||||||
|
:language: python
|
||||||
|
:emphasize-lines: 28-34
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
To use the feature you should use the ``.provided`` attribute of the injected provider. This
|
||||||
|
attribute helps to specify what happens with the provided instance before the injection. You can
|
||||||
|
use any combination of the following:
|
||||||
|
|
||||||
|
- an attribute of the provided object
|
||||||
|
- an item of the provided object
|
||||||
|
- a call of the provided object method
|
||||||
|
|
||||||
|
When you use a call of the provided instance method you can specify the injections for this
|
||||||
|
method like you do with any other provider.
|
||||||
|
|
||||||
|
You can do nested constructions:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/provided_instance_complex.py
|
||||||
|
:language: python
|
||||||
|
:emphasize-lines: 26-32
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
.. 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::
|
36
docs/providers/selector.rst
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
.. _selector-provider:
|
||||||
|
|
||||||
|
Selector provider
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
|
||||||
|
Selector,Polymorphism,Environment Variable,Flexibility
|
||||||
|
:description: Selector selects provider based on a configuration value or another callable.
|
||||||
|
This page demonstrates how to implement the polymorphism and increase the
|
||||||
|
flexibility of your application using the Selector provider.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
:py:class:`Selector` provider selects provider based on a configuration value or another callable.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/selector.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 16-20
|
||||||
|
|
||||||
|
The first argument of the ``Selector`` provider is called ``selector``. It can be an option of
|
||||||
|
a ``Configuration`` provider or any other callable. The ``selector`` callable has to return a
|
||||||
|
string value. This value is used as a key for selecting the provider.
|
||||||
|
|
||||||
|
The providers are provided as keyword arguments. Argument name is used as a key for selecting the
|
||||||
|
provider.
|
||||||
|
|
||||||
|
When a ``Selector`` provider is called, it gets a ``selector`` value and delegates the work to
|
||||||
|
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::
|
140
docs/providers/singleton.rst
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
.. _singleton-provider:
|
||||||
|
|
||||||
|
Singleton provider
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Singleton,Pattern,Example,
|
||||||
|
Threads,Multithreading,Scoped
|
||||||
|
:description: Singleton provider helps to provide a single object. This page
|
||||||
|
demonstrates how to use a Singleton provider. It also provides the example
|
||||||
|
of using a singleton and thread locals singleton in the multi-threaded
|
||||||
|
environment.
|
||||||
|
|
||||||
|
.. currentmodule:: dependency_injector.providers
|
||||||
|
|
||||||
|
:py:class:`Singleton` provider provides single object. It memorizes the first created object and
|
||||||
|
returns it on the rest of the calls.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/singleton.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
``Singleton`` provider handles dependencies injection the same way like a :ref:`factory-provider`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
``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:
|
||||||
|
|
||||||
|
- :ref:`factory-specialize-provided-type`
|
||||||
|
- :ref:`abstract-factory`
|
||||||
|
|
||||||
|
``Singleton`` provider scope is tied to the container. Two different containers will provider
|
||||||
|
two different singleton objects:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/singleton_multiple_containers.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
Resetting memorized object
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
To reset a memorized object you need to call the ``.reset()`` method of the ``Singleton``
|
||||||
|
provider.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/singleton_resetting.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 18
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
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
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
``Singleton`` provider is NOT thread-safe. You need to explicitly establish a synchronization for
|
||||||
|
using the ``Singleton`` provider in the multi-threading application. Otherwise you could trap
|
||||||
|
into the race condition problem: ``Singleton`` will create multiple objects.
|
||||||
|
|
||||||
|
There are two thread-safe singleton implementations out of the box:
|
||||||
|
|
||||||
|
+ :py:class:`ThreadSafeSingleton` - is a thread-safe version of a ``Singleton`` provider. You can use
|
||||||
|
in multi-threading applications without additional synchronization.
|
||||||
|
+ :py:class:`ThreadLocalSingleton` - is a singleton provider that uses thread-locals as a storage.
|
||||||
|
This type of singleton will manage multiple objects - the one object for the one thread.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/singleton_thread_locals.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 13,15
|
||||||
|
|
||||||
|
Implementing scopes
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
To implement a scoped singleton provider use a ``Singleton`` provider and reset its scope when
|
||||||
|
needed.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/singleton_scoped.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
The output should look like this (each request a ``Service`` object has a different address):
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
* Serving Flask app "singleton_scoped" (lazy loading)
|
||||||
|
* Environment: production
|
||||||
|
WARNING: This is a development server. Do not use it in a production deployment.
|
||||||
|
Use a production WSGI server instead.
|
||||||
|
* Debug mode: off
|
||||||
|
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||||
|
<__main__.Service object at 0x1099a9d90>
|
||||||
|
127.0.0.1 - - [25/Aug/2020 17:33:11] "GET / HTTP/1.1" 200 -
|
||||||
|
<__main__.Service object at 0x1099a9cd0>
|
||||||
|
127.0.0.1 - - [25/Aug/2020 17:33:17] "GET / HTTP/1.1" 200 -
|
||||||
|
<__main__.Service object at 0x1099a9d00>
|
||||||
|
127.0.0.1 - - [25/Aug/2020 17:33:18] "GET / HTTP/1.1" 200 -
|
||||||
|
<__main__.Service object at 0x1099a9e50>
|
||||||
|
127.0.0.1 - - [25/Aug/2020 17:33:18] "GET / HTTP/1.1" 200 -
|
||||||
|
<__main__.Service object at 0x1099a9d90>
|
||||||
|
127.0.0.1 - - [25/Aug/2020 17:33:18] "GET / HTTP/1.1" 200 -
|
||||||
|
|
||||||
|
.. disqus::
|
60
docs/providers/typing_mypy.rst
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
.. _provider-typing:
|
||||||
|
|
||||||
|
Typing and mypy
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Providers,Typing,Mypy,
|
||||||
|
Pattern,Example
|
||||||
|
:description: Dependency Injector providers are mypy-friendly. Providers module goes with the
|
||||||
|
typing stubs to provide the typing information to ``mypy``, IDEs and editors.
|
||||||
|
|
||||||
|
Providers are ``mypy``-friendly.
|
||||||
|
|
||||||
|
Providers module goes with the typing stubs. It provides typing information to ``mypy`` and your
|
||||||
|
IDE.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from dependency_injector import providers
|
||||||
|
|
||||||
|
|
||||||
|
class Animal:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Cat(Animal)
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
provider = providers.Factory(Cat)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
animal = provider() # mypy knows that animal is of type "Cat"
|
||||||
|
|
||||||
|
|
||||||
|
You can use ``Provider`` as a generic type. This helps when a provider is an argument of a
|
||||||
|
function or method.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 12
|
||||||
|
|
||||||
|
from dependency_injector import providers
|
||||||
|
|
||||||
|
|
||||||
|
class Animal:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Cat(Animal)
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
provider: providers.Provider[Animal] = providers.Factory(Cat)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
864
docs/tutorials/aiohttp.rst
Normal file
|
@ -0,0 +1,864 @@
|
||||||
|
.. _aiohttp-tutorial:
|
||||||
|
|
||||||
|
Aiohttp tutorial
|
||||||
|
================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Aiohttp,Tutorial,Education,Web,API,REST API,Example,DI,Dependency injection,
|
||||||
|
IoC,Inversion of control,Refactoring,Tests,Unit tests,Pytest,py.test,Bootstrap,
|
||||||
|
HTML,CSS
|
||||||
|
:description: This tutorial shows how to build an aiohttp application following the dependency
|
||||||
|
injection principle. You will create the REST API application, connect to the
|
||||||
|
Giphy API, cover it with the unit test and make some refactoring.
|
||||||
|
|
||||||
|
This tutorial shows how to build an ``aiohttp`` REST API application following the dependency
|
||||||
|
injection principle.
|
||||||
|
|
||||||
|
Start from the scratch or jump to the section:
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
:backlinks: none
|
||||||
|
|
||||||
|
You can find complete project on the
|
||||||
|
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
||||||
|
|
||||||
|
What are we going to build?
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. image:: https://media.giphy.com/media/apvx5lPCPsjN6/source.gif
|
||||||
|
|
||||||
|
We will build a REST API application that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
|
||||||
|
Let's call it Giphy Navigator.
|
||||||
|
|
||||||
|
How does Giphy Navigator work?
|
||||||
|
|
||||||
|
- Client sends a request specifying the search query and the number of results.
|
||||||
|
- Giphy Navigator returns a response in json format.
|
||||||
|
- The response contains:
|
||||||
|
- the search query
|
||||||
|
- the limit number
|
||||||
|
- the list of gif urls
|
||||||
|
|
||||||
|
Example response:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"query": "Dependency Injector",
|
||||||
|
"limit": 10,
|
||||||
|
"gifs": [
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/boxes-dependent-swbf2-6Eo7KzABxgJMY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/depends-J56qCcOhk6hKE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/web-series-ccstudios-bro-dependent-1lhU8KAVwmVVu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/TheBoysTV-friends-friend-weneedeachother-XxR9qcIwcf5Jq404Sx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/netflix-a-series-of-unfortunate-events-asoue-9rgeQXbwoK53pcxn7f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/black-and-white-sad-skins-Hs4YzLs2zJuLu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/always-there-for-you-i-am-here-PlayjhCco9jHBYrd9w"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/stream-famous-dollar-YT2dvOByEwXCdoYiA1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/i-love-you-there-for-am-1BhGzgpZXYWwWMAGB1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/life-like-twerk-9hlnWxjHqmH28"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
The task is naive and that's exactly what we need for the tutorial.
|
||||||
|
|
||||||
|
Prepare the environment
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Let's create the environment for the project.
|
||||||
|
|
||||||
|
First we need to create a project folder:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
mkdir giphynav-aiohttp-tutorial
|
||||||
|
cd giphynav-aiohttp-tutorial
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Project layout
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Create next structure in the current directory. All files should be empty. That's ok for now.
|
||||||
|
|
||||||
|
Initial project layout::
|
||||||
|
|
||||||
|
./
|
||||||
|
├── giphynavigator/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── application.py
|
||||||
|
│ ├── containers.py
|
||||||
|
│ └── handlers.py
|
||||||
|
├── venv/
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
Install the requirements
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Now it's time to install the project requirements. We will use next packages:
|
||||||
|
|
||||||
|
- ``dependency-injector`` - the dependency injection framework
|
||||||
|
- ``aiohttp`` - the web framework
|
||||||
|
- ``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
|
||||||
|
|
||||||
|
Put next lines into the ``requirements.txt`` file:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
dependency-injector
|
||||||
|
aiohttp
|
||||||
|
pyyaml
|
||||||
|
pytest-aiohttp
|
||||||
|
pytest-cov
|
||||||
|
|
||||||
|
and run next in the terminal:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
Let's also install the ``httpie``. It is a user-friendly command-line HTTP client for the API era.
|
||||||
|
We will use it for the manual testing.
|
||||||
|
|
||||||
|
Run the command in the terminal:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pip install httpie
|
||||||
|
|
||||||
|
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
|
||||||
|
will answer our requests in json format. There will be no payload for now.
|
||||||
|
|
||||||
|
Edit ``handlers.py``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
"""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))
|
||||||
|
|
||||||
|
gifs = []
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"query": query,
|
||||||
|
"limit": limit,
|
||||||
|
"gifs": gifs,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Now let's create a container. Container will keep all of the application components and their dependencies.
|
||||||
|
|
||||||
|
Edit ``containers.py``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
"""Containers module."""
|
||||||
|
|
||||||
|
from dependency_injector import containers
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
...
|
||||||
|
|
||||||
|
Container is empty for now. We will add the providers in the following sections.
|
||||||
|
|
||||||
|
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``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
"""Application module."""
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from .containers import Container
|
||||||
|
from . import handlers
|
||||||
|
|
||||||
|
|
||||||
|
def create_app() -> web.Application:
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
app = web.Application()
|
||||||
|
app.container = container
|
||||||
|
app.add_routes([
|
||||||
|
web.get("/", handlers.index),
|
||||||
|
])
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = create_app()
|
||||||
|
web.run_app(app)
|
||||||
|
|
||||||
|
Now we're ready to run our application
|
||||||
|
|
||||||
|
Do next in the terminal:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python -m giphynavigator.application
|
||||||
|
|
||||||
|
The output should be something like:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
======== Running on http://0.0.0.0:8080 ========
|
||||||
|
(Press CTRL+C to quit)
|
||||||
|
|
||||||
|
Let's check that it works. Open another terminal session and use ``httpie``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
http http://0.0.0.0:8080/
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
|
||||||
|
.. 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.10 aiohttp/3.6.2
|
||||||
|
|
||||||
|
{
|
||||||
|
"gifs": [],
|
||||||
|
"limit": 10,
|
||||||
|
"query": "Dependency Injector"
|
||||||
|
}
|
||||||
|
|
||||||
|
Minimal application is ready. Let's connect our application with the Giphy API.
|
||||||
|
|
||||||
|
Giphy API client
|
||||||
|
----------------
|
||||||
|
|
||||||
|
In this section we will integrate our application with the Giphy API.
|
||||||
|
|
||||||
|
We will create our own API client using ``aiohttp`` client.
|
||||||
|
|
||||||
|
Create ``giphy.py`` module in the ``giphynavigator`` package:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
:emphasize-lines: 6
|
||||||
|
|
||||||
|
./
|
||||||
|
├── giphynavigator/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── application.py
|
||||||
|
│ ├── containers.py
|
||||||
|
│ ├── giphy.py
|
||||||
|
│ └── handlers.py
|
||||||
|
├── venv/
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
and put next into it:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
"""Giphy client module."""
|
||||||
|
|
||||||
|
from aiohttp import ClientSession, ClientTimeout
|
||||||
|
|
||||||
|
|
||||||
|
class GiphyClient:
|
||||||
|
|
||||||
|
API_URL = "https://api.giphy.com/v1"
|
||||||
|
|
||||||
|
def __init__(self, api_key, timeout):
|
||||||
|
self._api_key = api_key
|
||||||
|
self._timeout = ClientTimeout(timeout)
|
||||||
|
|
||||||
|
async def search(self, query, limit):
|
||||||
|
"""Make search API call and return result."""
|
||||||
|
url = f"{self.API_URL}/gifs/search"
|
||||||
|
params = {
|
||||||
|
"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:
|
||||||
|
if response.status != 200:
|
||||||
|
response.raise_for_status()
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
Now we need to add ``GiphyClient`` into the container. The ``GiphyClient`` has two dependencies
|
||||||
|
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. 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-5,10-16
|
||||||
|
|
||||||
|
"""Containers module."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
from . import giphy
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
config = providers.Configuration(yaml_files=["config.yml"])
|
||||||
|
|
||||||
|
giphy_client = providers.Factory(
|
||||||
|
giphy.GiphyClient,
|
||||||
|
api_key=config.giphy.api_key,
|
||||||
|
timeout=config.giphy.request_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
./
|
||||||
|
├── giphynavigator/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── application.py
|
||||||
|
│ ├── containers.py
|
||||||
|
│ ├── giphy.py
|
||||||
|
│ └── handlers.py
|
||||||
|
├── venv/
|
||||||
|
├── config.yml
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
and put next into it:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
giphy:
|
||||||
|
request_timeout: 10
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
"""Application module."""
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from .containers import Container
|
||||||
|
from . import handlers
|
||||||
|
|
||||||
|
|
||||||
|
def create_app() -> web.Application:
|
||||||
|
container = Container()
|
||||||
|
container.config.giphy.api_key.from_env("GIPHY_API_KEY")
|
||||||
|
|
||||||
|
app = web.Application()
|
||||||
|
app.container = container
|
||||||
|
app.add_routes([
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
To create your own Giphy API key follow this
|
||||||
|
`guide <https://support.giphy.com/hc/en-us/articles/360020283431-Request-A-GIPHY-API-Key>`_.
|
||||||
|
|
||||||
|
The Giphy API client and the configuration setup is done. Let's proceed to the search service.
|
||||||
|
|
||||||
|
Search service
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Now it's time to add the ``SearchService``. It will:
|
||||||
|
|
||||||
|
- Perform the search.
|
||||||
|
- Format result data.
|
||||||
|
|
||||||
|
``SearchService`` will use ``GiphyClient``.
|
||||||
|
|
||||||
|
Create ``services.py`` module in the ``giphynavigator`` package:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
:emphasize-lines: 8
|
||||||
|
|
||||||
|
./
|
||||||
|
├── giphynavigator/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── application.py
|
||||||
|
│ ├── containers.py
|
||||||
|
│ ├── giphy.py
|
||||||
|
│ ├── handlers.py
|
||||||
|
│ └── services.py
|
||||||
|
├── venv/
|
||||||
|
├── config.yml
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
and put next into it:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
"""Services module."""
|
||||||
|
|
||||||
|
from .giphy import GiphyClient
|
||||||
|
|
||||||
|
|
||||||
|
class SearchService:
|
||||||
|
|
||||||
|
def __init__(self, giphy_client: GiphyClient):
|
||||||
|
self._giphy_client = giphy_client
|
||||||
|
|
||||||
|
async def search(self, query, limit):
|
||||||
|
"""Search for gifs and return formatted data."""
|
||||||
|
if not query:
|
||||||
|
return []
|
||||||
|
|
||||||
|
result = await self._giphy_client.search(query, limit)
|
||||||
|
|
||||||
|
return [{"url": gif["url"]} for gif in result["data"]]
|
||||||
|
|
||||||
|
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: 5,18-21
|
||||||
|
|
||||||
|
"""Containers module."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
from . import giphy, services
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
config = providers.Configuration(yaml_files=["config.yml"])
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
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 put the search into work. Let's inject ``SearchService`` into
|
||||||
|
the ``index`` handler. We will use :ref:`wiring` feature.
|
||||||
|
|
||||||
|
Edit ``handlers.py``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 4-7,10-14,18
|
||||||
|
|
||||||
|
"""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 = Provide[Container.search_service],
|
||||||
|
) -> web.Response:
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
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: 10
|
||||||
|
|
||||||
|
"""Containers module."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
from . import giphy, services
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
wiring_config = containers.WiringConfiguration(modules=[".handlers"])
|
||||||
|
|
||||||
|
config = providers.Configuration(yaml_files=["config.yml"])
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
Make sure the app is running:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python -m giphynavigator.application
|
||||||
|
|
||||||
|
and make a request to the API in the terminal:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
http http://0.0.0.0:8080/ query=="wow,it works" limit==5
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
|
||||||
|
.. code-block:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Length: 492
|
||||||
|
Content-Type: application/json; charset=utf-8
|
||||||
|
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/soulpancake-wow-work-xUe4HVXTPi0wQ2OAJC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://giphy.com/gifs/readingrainbow-teamwork-levar-burton-reading-rainbow-3o7qE1EaTWLQGDSabK"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limit": 5,
|
||||||
|
"query": "wow,it works"
|
||||||
|
}
|
||||||
|
|
||||||
|
.. image:: https://media.giphy.com/media/3oxHQCI8tKXoeW4IBq/source.gif
|
||||||
|
|
||||||
|
The search works!
|
||||||
|
|
||||||
|
Make some refactoring
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
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 ``handlers.py``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 14-15,17-18
|
||||||
|
|
||||||
|
"""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 = 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))
|
||||||
|
|
||||||
|
gifs = await search_service.search(query, limit)
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"query": query,
|
||||||
|
"limit": limit,
|
||||||
|
"gifs": gifs,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Let's update the config.
|
||||||
|
|
||||||
|
Edit ``config.yml``:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
:emphasize-lines: 3-5
|
||||||
|
|
||||||
|
giphy:
|
||||||
|
request_timeout: 10
|
||||||
|
default:
|
||||||
|
query: "Dependency Injector"
|
||||||
|
limit: 10
|
||||||
|
|
||||||
|
The refactoring is done. We've made it cleaner - hardcoded values are now moved to the config.
|
||||||
|
|
||||||
|
Tests
|
||||||
|
-----
|
||||||
|
|
||||||
|
In this section we will add some tests.
|
||||||
|
|
||||||
|
Create ``tests.py`` module in the ``giphynavigator`` package:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
:emphasize-lines: 9
|
||||||
|
|
||||||
|
./
|
||||||
|
├── giphynavigator/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── application.py
|
||||||
|
│ ├── containers.py
|
||||||
|
│ ├── giphy.py
|
||||||
|
│ ├── handlers.py
|
||||||
|
│ ├── services.py
|
||||||
|
│ └── tests.py
|
||||||
|
├── venv/
|
||||||
|
├── config.yml
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
and put next into it:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 32,59,73
|
||||||
|
|
||||||
|
"""Tests module."""
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from giphynavigator.application import create_app
|
||||||
|
from giphynavigator.giphy import GiphyClient
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app():
|
||||||
|
app = create_app()
|
||||||
|
yield app
|
||||||
|
app.container.unwire()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client(app, aiohttp_client, loop):
|
||||||
|
return loop.run_until_complete(aiohttp_client(app))
|
||||||
|
|
||||||
|
|
||||||
|
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"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
with app.container.giphy_client.override(giphy_client_mock):
|
||||||
|
response = await client.get(
|
||||||
|
"/",
|
||||||
|
params={
|
||||||
|
"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"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_index_no_data(client, app):
|
||||||
|
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||||
|
giphy_client_mock.search.return_value = {
|
||||||
|
"data": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
with app.container.giphy_client.override(giphy_client_mock):
|
||||||
|
response = await client.get("/")
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
data = await response.json()
|
||||||
|
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": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
with app.container.giphy_client.override(giphy_client_mock):
|
||||||
|
response = await client.get("/")
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
data = await response.json()
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
py.test giphynavigator/tests.py --cov=giphynavigator
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
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.10.0-final-0 ----------
|
||||||
|
Name Stmts Miss Cover
|
||||||
|
---------------------------------------------------
|
||||||
|
giphynavigator/__init__.py 0 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 37 0 100%
|
||||||
|
---------------------------------------------------
|
||||||
|
TOTAL 90 12 87%
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Take a look at the highlights in the ``tests.py``.
|
||||||
|
|
||||||
|
It emphasizes the overriding of the ``GiphyClient``. The real API call are mocked.
|
||||||
|
|
||||||
|
Conclusion
|
||||||
|
----------
|
||||||
|
|
||||||
|
In this tutorial we've built an ``aiohttp`` REST API application following the dependency
|
||||||
|
injection principle.
|
||||||
|
We've used the ``Dependency Injector`` as a dependency injection framework.
|
||||||
|
|
||||||
|
:ref:`containers` and :ref:`providers` helped to specify how to assemble search service and
|
||||||
|
giphy client.
|
||||||
|
|
||||||
|
:ref:`configuration-provider` helped to deal with reading YAML file and environment variable.
|
||||||
|
|
||||||
|
We used :ref:`wiring` feature to inject the dependencies into the ``index()`` handler.
|
||||||
|
:ref:`provider-overriding` feature helped in testing.
|
||||||
|
|
||||||
|
We kept all the dependencies injected explicitly. This will help when you need to add or
|
||||||
|
change something in future.
|
||||||
|
|
||||||
|
You can find complete project on the
|
||||||
|
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
||||||
|
|
||||||
|
What's next?
|
||||||
|
|
||||||
|
- Look at the other :ref:`tutorials`
|
||||||
|
- Know more about the :ref:`providers`
|
||||||
|
- Go to the :ref:`contents`
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
|
.. disqus::
|
1031
docs/tutorials/asyncio-daemon.rst
Normal file
BIN
docs/tutorials/asyncio-images/classes-01.png
Normal file
After Width: | Height: | Size: 23 KiB |