From a7038f0114209b42f9839c6360f56ab8e1c566cf Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 3 Oct 2026 17:29:50 +0300 Subject: [PATCH] docs: prose pass, fix string-injection validation timing, refresh social card --- README.md | 9 ++- docs/assets/social-card.png | Bin 11405 -> 11934 bytes docs/ecosystem.md | 10 +-- docs/experimental/lazy.md | 4 +- docs/index.md | 2 +- docs/integrations/fastapi.md | 52 ++++++++-------- docs/integrations/faststream.md | 10 +-- docs/integrations/litestar.md | 2 +- docs/introduction/generator-injection.md | 16 +++-- docs/introduction/injection.md | 71 +++++++++++----------- docs/introduction/ioc-container.md | 6 +- docs/introduction/multiple-containers.md | 2 +- docs/introduction/scopes.md | 12 ++-- docs/introduction/string-injection.md | 13 ++-- docs/introduction/tear-down.md | 8 +-- docs/introduction/type-based-injection.md | 8 +-- docs/migration/v2.md | 39 ++++++------ docs/migration/v3.md | 20 +++--- docs/migration/v4.md | 12 ++-- docs/providers/context-resources.md | 22 +++---- docs/providers/factories.md | 24 ++++---- docs/providers/object.md | 2 +- docs/providers/resources.md | 39 ++++++------ docs/providers/selector.md | 4 +- docs/providers/singleton.md | 34 +++++------ docs/providers/state.md | 6 +- docs/testing/provider-overriding.md | 6 +- 27 files changed, 212 insertions(+), 221 deletions(-) diff --git a/README.md b/README.md index 4c56cca7..766c42ec 100644 --- a/README.md +++ b/README.md @@ -39,17 +39,17 @@ pip install that-depends ## Ecosystem `that-depends` is part of the [`modern-python`](https://github.com/modern-python) family. -If you're starting a new project, consider [`modern-di`](https://github.com/modern-python/modern-di) — +If you're starting a new project, consider [`modern-di`](https://github.com/modern-python/modern-di), the newer DI framework from the same author, with separate framework adapters: -- [`modern-di`](https://github.com/modern-python/modern-di) — core DI framework with scopes +- [`modern-di`](https://github.com/modern-python/modern-di): core DI framework with scopes - [`modern-di-fastapi`](https://github.com/modern-python/modern-di-fastapi), [`modern-di-litestar`](https://github.com/modern-python/modern-di-litestar), [`modern-di-faststream`](https://github.com/modern-python/modern-di-faststream), [`modern-di-typer`](https://github.com/modern-python/modern-di-typer), [`modern-di-pytest`](https://github.com/modern-python/modern-di-pytest) -`that-depends` remains actively maintained — see the +`that-depends` remains actively maintained. See the [migration guide](https://modern-di.modern-python.org/migration/from-that-depends/) if you want to move existing projects across. @@ -61,5 +61,4 @@ want to move existing projects across. ## Part of `modern-python` -Browse the full list of templates and libraries in -[`modern-python`](https://github.com/modern-python) — see the org profile for the categorized index. +The [`modern-python`](https://github.com/modern-python) org profile has the full categorized list of templates and libraries. diff --git a/docs/assets/social-card.png b/docs/assets/social-card.png index 6ab02ea21df5216827b97a175513f66ff2dff1f8..9569337ef85f9516b170ecc0b759eb6386ee76a8 100644 GIT binary patch literal 11934 zcmc(F2|SeT+vxqw*p(%PY~!srDr+HRuOwS4ArTWH`<8v1Qb`h0)+~{dC1q!5MnYt% z?8Y*ZeUBLoGjpDK-|zpPbN>JDJKyh|-}jzx{AQlZ^W68f-1l`~>l1z9yxwjOAr1h* zZvAs-E&;#{g9r>8627d<97=>Q=nF=db-U6JeQU`NNQ}%Z&OYsKhITt%-B{IMWL$Qi z>q)Q>k@(H_zRy@g$lA)ZoQ?i+`C+A-7e7A)R-X1xUZ>1X_AM<;boaNvtuB5Xt!n1y z;CVmDG0-hK<F4&3Beia;ZmlseE%95I&YRNzlaIH}IvN0(4*fGamjj0uN22|6 z+Hx@0y$xE*!;JFp?9K_ISE-xzXhZkxe(V&(q_2mh|<3=SZ1H}LnbK4TI74=nt( zMt?8x-^;;&y#F8nw?jaDN-#@RufK7_d=bR6f!F=Vh0DB355ZeS{Mkn@^){3wFqY|j zk;}r5H;fUuOR2Ph6ZQfoESNK3^VxTHX)$&NWCabw62t4x*k#7@|J~q!c*Opd1)2-! zzLcy%PJZ{+=|BWU>lMYlU56E9Nvsl}=gYUaS>#Kcer(Y*=c?W_i2_B@ZoP{A*udj} zNgFWX(?N=+(ZfH)=K&B8kYphanB1yF!q2q9b3qI}DOdCR;4c(IUIeiR>{JyV4+>to zVXo0nVcdj>a$Eq42Y*avXJGi(f9#`AS&XvaOEasPv=Q21Cl&OezWq1>_(?AT&#iw< zg(t@Y1|oL&?>_-&vA|%Y8C;=PEkM<+)0E1`TjEi683j&t&(_MRA_^MF?3=GNt|VD_ z&fWW7p@Pi5+|^SupkAS*=I4IFRJ{EzV68EtRC2xp;Vo1s+>3UQg`iBkUs)nbd{xB z8mn+9z1K2TV4RP2XWzKg@~Dq$^?@`c!@?`vJCRS48$QZ+KLmnC`ZDJ1=fF9wJ$T*u zhMI-(_Y3FIRq@Hl!v&qDrm7%&reoFW1VEdYX9?;W%GNGte`W=4xUA!qbCt3O*7}ea zUS(Lc^RRZ*8%O!)sW_y8oV;c^1|LzIHU3k**Uw4A`;_K+2#E66i_kbm5j}x`s zA@l45EkRXQ8_-kg>%4d-%=!c{+CK4oXS@=V3$;)1MT3+UTlrWZn3X>>3gl1L_YGxa z#QM=KuNlP*l=o?XZiQtGh!00#{Fyzs$bF&ZaShz4dMUHC>6p1V_@-j7R70Eh3 z1q{NvF3s8$E+A&`GJFw-dyggh0c(Ep=O}qr$LaUIC-lt6xkEQ)e(1JO+r2*)b!VjO zUV2+;0^vixhWcfHVAQCQ+~F-mMk2(iMcx=P`b>!Hv7PLKW3m~ic{p*?u4+Ca*|y$S zKhJcQD~48#K1?QLk^7)`0X>bV4x9DSN$WG_$CblC`gC;Z#tG_u_iF7s8cDk`4k5=L zRxM#A!OJWNJ!)&}>Ru=sl0_JGA*h1FnS`(E2=tJ}>(pL96u#4YsNB$sf3SFVY^qLK z%FK4Liq>~4#MFPSkM-S**j{r5^HLfsW4E5`xutvRJ^yeFwVT*XsoFmzPmVD0*{@-Z z?&tg>hVdNfH&WvZRohbh2Cc~W+zcGFvWs~^B22Cz64pc#;GGl09I&(U4!&v`U_#gr zx?gCZ%>?juxG|ZR$LA5%v&N-v^@VQ4XHBK`ztUUG)pe(%CR`I6(yk+k!mJt~J`=(R zt(S#f12Hcq8vXB;K<^%j1nrhc2msc*I?_X?4xCKNQ#JTzIXGVzSw%j!sz{bQyZO0P zYFp?YOk5EYv`|D-=vS)d!Mojb??9R86XvZWKeZFUSC68sNk}Fm^2A_d0@!EWB>>xV-6-YBk~gO{5@I!3YuaQ#?PT zgP>mPo!)G&RyO`3>C0$Rby~}A*ivhBOul=_w*Pp*>AT5!VrK&P;N3A%a|Mf1IUX>p4;fLMaQw;8 zrvDG=2jz#lc0{gNa^e@+P5UL!OVNZduiuUwr6f1HTvK|1=Gg_#f0%T+1Gq}g#In2- zRA>2dBDlM+jc^4dkFk1rLzoyoaMtOj=m*jJI!Iy3Gk^?k}>VOmCY#5F2uwu^6D@ zOqXCIo;*nm4EuH1OD$+iCw@)xg0(--%i-|VQKK70Cbpb+D7{-Hhn;n#Y}wwcLyzh;yQ zBC*IggqH%%=ZZ^}?elHR?Ru8NQN0ep@!Pi52`8#7!SL{+@k6PX2x9MM zNSG(7Hl#X&k4b*f^VAgf3v{dMB*zl&*3_$WoK361kEK!P1_!*$J~zTdaxv)Vu#MjKpt$_T)(RJdkbP_X@%fizsMCCeQhw_ z9uERd=}WTc7TgiUK<&HNi3hh47=k@-?okPobs^btd2MdA5&kk)M%1_P#SXF*pkT{c zXQ?su-k6{>Aw~#1VS&C@EH*p#JLG0NV2X<7A;>`kB00O?JZwvRrlDT?IaORYaJbQ( zCe~8tvy8}_fZVuVY$mr?@`4c015H|>?y=gkJ*9{K?0{k$^}&fmz1# z4sR^$AvZNNdROCoGN;Dqd!1YiJm-cA=6$a|B-y>SG1X8IJ}FSm_UcV$jf$$Vb5`Ey zr+wy43&R;!2keS%OmS9AkJ&K+mQZMft!5`~7O~m=pD zd^A#J;m@(do9nPr8fh$)F%<7NbaUZvBBtbc&X zjTGNz9GwdN8$X45qDU4zs{3M#(%D%Hn>}=rPXB3U?E_$^VuJ~Suc0cK2r)%A77z{M z!(f@1FgSO?_6WMY;4WT3`>vqF2=^lvn|K&k6bJeX?%5p4j84b0u`vSv3xNJ9&uYZ} zpdgH3B>`}q+6ay^gNY;XtO%95H`uzyv+|5Q0txLX6j{5Dc(KCMRE_~#lMK>!@S5oz zh=G@dIQR3}vL}vr7Xzt0ysG?5Q^E}lcH$5#3n!L}mH!;bM4VeF9`ldz!2UT@9O%vX zPsE}b1Y3ZL%Qm_4&uQF{m(twmbkztO3qwjKjJ*SP_D@z00+D+6kS~8uVg)MUO-o6C zPTF8y+0*M$&ASr=wXb1^?^;aNRCkL!7*7tqf5s-SB?NafY5F)t`-wR+4iK*?V?43E zJ0Z|h6l&|5W*-XGqSm29US0j9Y%Z|PZRRs=VXUc{)1DXXcofv_!z35?jCdT}tbv{m z9$Sm}a0w8f0gG&5bvGY>V0O3iTv^a{Zi5u~#d$q=5@mZw|z`H*4appup7=i^V7IWC`N! z>swzQo%f5)E#(1aQZL@2u1E>d2{kMI-c@5QDxfg#Nu+oca^=^)S}S)ZEjf&fl0%l0 z<#7u{o7G#$(fMfjpb&b0#(Qs9jEnc6v-I2Y=VC}SGe#M?B9%6B^WbI>)IP%HTY)`U zKdGr*A$xFuRV5gO_Ry`lg%SreF@P)A2uY76PBULvR0vR`lUrrt5cE?3x5b@Ux8wus zKeSo#tcKvmWCHNT@m7pnDs&IP9UU}=*%!dC|Bkv;Nw3i z*=l*diAx^W`?#Y3S>o`|*4Yf(r?0D?P|NH9dF&b;RZRAIa=L3Bcg5RLB5oqwDisnkZ}X&6(&XTQM>mm&*De_s zhzOFil7ofvC|Sdzzg?i`y-J$3*cy19`P?thrq}qIhUa^^p2i&R?5}e9VynIvB63p* zswv6y?$k#|U0HUR7cTopR|gb@XJ0NmT3Grk zri6oka!9tVI6QHwSgx(7URfhz%`b$uzAiv|`lX$Ia+u(^d|u{*QJn2iz+>s+`KBUU z3o6>Ypk*F;lyVM7?d{QelUdQ#!InQoWsQ;a#Yxr(bd-ECZj-x2>O5n%*LNIT)=F+e z?}|J5(L9a$J8doAA*g+Wqz;H0uohSF?tE%Q`)IJqt89D`^S^wsdhX&5YP)^MGhu+F zLY;ksI(G^vJGd%#R4R)XoxfO{Om>JdDsesJ;c_3c9Bf@E>l4p_1LTy&5xzx{YR}Ni z-ycPNSuE%96)}^2xRTeS*4m*w<@k8d#w9(@?CoZ~If-g+rw$obD(PeD8=6LIW#8cQu>K8=eVFwD(5rQTD>agL7CyWAv0&J zxQ|*#L4DvuKdJ?~xuj@OeR^v5l&)*S&onje9s5fcM2+Rfn zeE4>DMS=V7Tdi~VY?;>pf75|4JyEZB@*k(triV&jd^lg4dzJ5SDT}sJ@Yy3rmFG+q z<1+We7shQI=-Sb7(y+0Jc+@NDp5fq<@%_2ng;~{vQ@H2v=3mbqGwca0<-*LBy+Wz> z#r^b9kBd)LORMisG)IQ66TU1uRkyvo{G(6dt=_=1x!CrMtYC+HG49K0+*r!l)O>bx zGP?^U7$1N2z%x;;%lXWSN4op1uH)Vaq|YF$Ej$gn9t5=`@<$uO-Ct!5W$*@HEFv}P z#Rh~Klrmq?vNWE^Hy<>>rhc&KGWW!37)hA!eNAfpmKTmqy?@kV&6(fG14vJ`xE1%8 z^=^M#rVrSv^Q&vccVSW@dhB1V^Xs@oyt*7A96iFMSxN174V$xwpLV=HJC<}ckhE;( zGFA4w1K;M7FurP#Equ;&e9rhP?zaUAE#?Qa7nckOa54;>K(zPSd}I#P5P#>Z14 zph(L<)i(Jn%F8n1u}_smMZQz>TMrq{lfc}(Jvc+M1b4lvZ((R)w0I~wNW(MzBNvX( zk^eE-+xbBPEpPqd6-Ujr@AXgRf#cLF<=sVx{^;H`!jP7pvGfQTD*r^$z3Se$|NQ)x z;c(7?SRopf&HT0Nn%(HxDRXLD=LLIQA-=@bbL5TSTJWWhw4dMJws9zY$*B)aTkcC# zD0>@mWbaqKlN8~0c4jiB=5ry^~+cAN>3;#1uo{nDk5oJ&_42cN56m{sa58a$U)1?@Va z)FG6%GWITKky@LQf2>1;Er|O0{eDohQR3O9+3i_0IMq4i`EB8VA|DAqFkNKD8lSGD zz2Rv%l>fE;-BwA#U`j_YaO}{Ma@N=gZ@tBx#01RiC$8lM-PvmWIB^5-E+?kYT|+IC zx6OO(R{yA=FGiBYb?0+%Ww<0n3}ANMs$tT`#yO9Svd*B}?u3QK^6?9; zg^_}+e}A=wud(9SMn+N3FyZ&x_6$X380BS+h8|UtY3chQi+kfNkrOaGh*Cmzh9R{( z=1#ePO@qr(!@}K($CoWCHFM_(uG>h*$(zgO=jTyfYOEF06|y8&&BDZSO*}vAR779L zuKw4{{O)^(0E@4a^g64{lg>~k@TvjlIcq1QfzdC+&kv@jJKUI-2#}hd4?YmAL)b?^ zKMA5P+b>1*>(|?R%^dbxAv__Sg7B;!V9f4dAR@W(JGSmDl%f=6_#NP1{rD-LVi1v3 z>^7dTn`)UbexrvouY-JTTKsd)^}eF-KgUgyZd)w1I&9um7fiATMLTK5O^0@8*)sF( z<;RBwh!0`_q zBVWqua#Hp~=Ejjx(fpWR9!!Jh7kY0Iw4wtakO8)E8<2h*?SF1P5KL1_tWFviNCa_c zhyCnd9$E(Z8zxGL``NQ^ffHJ(l~9q^>us;^Eo>OL&Jeb?Q^!ioL89v~R_9L#NMm#r zsJfo(BogSMmrk~xEP2=sDg{hpZ3rlNaBn3fYKfa_XDMo_pzd5^HXmsG!@{p?YKV;1JW&DV~LAig4E3 z$zi~XFNfS0HHS+T8puNE-_#nTIo^AA(Q1^mzulkzB;hr5b-dnL@epy#l*=8IY&-otMCrG-8Yfp~G;5s(dGlA5Y9$Y}--FVt(hsq(8h7nVTi- z&4ND@8|D63vfb)Y2eqGcXc0jw=+udK{Ll^mx;svKVNz2K)%0}L085ly0YxjWgR_?X zKDdvh-3@zIYCok9A}54W%hR7Xtiwvhc_4sQrx_t=pt1`+q;|82OdHhW*uK)M?&J~G zbQhdp{f-4JxZ+QuOc1`s9rR!GJGki#T}Bb?7Av0?Tm(Z}*RE?Wk+gF~J%u|xZ8=!TccaG*SJj9v;3;p9OR?(F zlTrgNxR)r4#Cv0(A0D5F@|C=%PZv?oWi7l)T=n+Vw_NKxIR5cDxSd!d593KHqFA5PaMt zu++!I#9aCztm)-pzsr$nzE>rDq=tnr(oN$QNXS?coy9f)^uWo!b?J9K6M2QP!*M>? zi^7<^8Dz>}KvDC7l98K9r>p_-SjzS5&3$jS%uc6nBzR-yT7#F;cz;YlMjcw-6oD`V z-fQ(Tq3_(swi1!osPF`Hf+i+dmY{}-OtfkDEZncjjforeub(=*!iQoePhS}qmGyP2} z;+IK50gm6vmleXiat;soUjtTh&$hZZI3g(Kta!9}nHPq16JQmj`8{Y9SIknVDE%iV z=n2ptnlJM)@j%S4{uuvptnd!`&`x-Co0i@u(BoVG04`l^t$9gaKoCzWV@m3HciJ1# zFI9OzWe~$@hrP_1yEYEiE)?tT1pON6eZhjC`gyM?GmTagw8thvy!ruB^QL1b7y9S0BQ!R){t#CRI2W=KRrBhsTTM=@s6)2Odb`Cqu z}sTFeCMzh$p z6Aqhai0&%ywT1-()iDP(eVT}C1$~ygm9gtHw9aXvpShmR+omOZC^5lTfrZ7v`C2%a z8kj(9hBmj_sp$Z$J^Fq+QXFW8Bde=;>k#;PNCjmrY7m(b$%j}``L)nD@pbVIJCGZ4 zympORa2y0Gf!;$;CSjU|i%d{ab1{&Z%ek}k?ilpP##F0NX>z!GjCQ;3$Fd4CE`kg{iMv~hItSfr3sS%&5xy3lIqyp*pc%sN#W35}SNLWNKzK9e=Pj z%n@wV95vereqh~j)Hse&udoFs-nY`-iCW8J2RGh8!)aDOLeI#~+?Yhd)rf5<(1m&B zgWm0yw+Kxw%&K89qio|FK}gHJM*x4WXZrPFql{whOR2Q>3zvv=^I1FIbDKcDAEkF<g}!ysNGNhD+BaYi zJKVw#@4~HFy$nFHtVumact(z6zU%Na%O49tFsee*7=g_e6?_yTe`BS1?qdji{j3L;)?$!{H)!GyyjbCa5Afo_$Lvg46I&4Zq1%T) zv4dW;j>3)!_XAlbm~1A~%5i$NP`f?U6ZxWW@d$8u!zJqExN_?QGpt3Xz-Dd?H<@7p zy5h0h4uGXGbpjOd-6J`BQt13vaG6Y~$f7EJwKt%$T9OIfyj919u|TX8(vCp3%k#F@`2K_t5%@t9 z2TaHh?9@iQPtV24(nrcC=aW4iYRZ5S(z^#>B^`KtIAR`y@9)K>R|Qk|M3^Gv{Vc*H zVC96Ww`f8H7ZOE}M5!I3Vhypqg+Wcdx})dW@J}`1BA*5DD%;s`P5XWk4Hw%qF}PMO zhLz#L+rrhoJrEiYmw$1?rsf^Ak_;CU&wv?d$DPLLvY!MI84(QQ!H*xCW{k~-Rs<-F zAy<%_!c-(|k$O5Ja{m~DQ-+Z;de8^p?>~b(2|$eNhpU$Vi133@?-M_w8^>Rv4tf)t zv%$GU;6|A{mgmJlS9b$xbN_jrB zbZ&3)vAd<2Hb~-EakXvg(kJd5dJ)hb47#)HgGk_^BMeFd9ZuN>vuOX#aQy*pefU3AWKv(=rO!a8cZ zFp*W(MOnoy&V~g8Qr)JLa+#W1=-f2BGp(g$uD66VxJ4mCPqP#huu~O1Hyo!bl*=rs zZ5b9P+Z_pAj^VxBzb|duS>Y1Ww3*RV*_|i6Sy2#9NI5x$H3t+vlJ=-TPfHY&MOxD^P@sO67Vk9f`mwV0mS(Dz zsVFuhXDf}Fn4b@k z3Y4da zxhOquT4Q||Ul+hbMBUHBg0&7WhukyE$o?FqZ zhG%aJEK!vbH|KZrbtL>+x`-}Q9jW7V&bpZ;P-o-%LUC56eZHSP1}Rxm$5_7r)xNP)%w&Vt3)q)-Z*(|5jhYfAU3Ch>G-*LxX?gtp}p6EgOKMlE}aH7DeRt1yw z_Ou9IU?cE2hjxXeJHOa8RSct`74T~>hO`|LjmVdr9EO__FP)Kr@5s9rkdNFDG4AW^ z;e0C5Tt!PBh#0R^twsAKd=dC@W1~S&_f=M%AE24*jv}`=4{R(}8o*Tru4QlWVfmCUw4jk)RLy6! z;G>;gXEV>OArG92%E-LcZE8FJ8f#yce&#v2FrU^FnU;QCK6v@X3|FM;dd+^8uk9sK zb(v`@yU}ozEUcX>va?Ro&hQd6x><5Va2I?UPj5g4X77``lHr7MNqfdJMEsU~X+PIp zk7g!GQgV2^R}`0)hBD7pEoxK3L8lJ?o4fEAkuT<3>p#B-m8_?t3LP84S(Z~(kkO7; zBaR%s9JO?|CgQ>EuW~8#w)1tM_~OoUPe_8yX185X2k^KWeRV>1!S7ZlpMjmb*^s zxr#3?cXVRBiN|G6aKF^RgYItO+Pd54em>adYwZ@$tNw{VDrqjFg7!}*3_lOfSdlPB z`o2tdp*q3_7+SIPjM-}9T%bRtY(Cgft3QbQ8LWq4V;n>jk~kJp90#XZq8JXwjR=4# zRf}J}FIVjU)F45ggXNE#Y#9CJmrv854J48st@rMOQR+jXK}&HZDSl*JSm%@N`pws3 z^0#Q!+_2u$E-=F6;6++`2A?B&j0;5H3DEdCkz`+p6dW6U}O&;KXj{{`27#=>-3tQjn4 zockZIxjmz8Lxq-FDzxX(uXYhVejX*d!TwqC7@*7Ov8!0oVDaE3p!?H1I+y9&8S}8O zR`i3TX1K@3XruiBZ2C$dV3*Bs+z0*@fWsu%u_NGR`ul)?moS5sznk!1BK`Mr@S`gg zJ|FQ9;NuhhufYGZRe$Z-e>L#$5@xXSZ#wc{P49nd-#;+__i_-e<&^V_A0YnHjQB+i#ihy)A3Mf^O z-i{)oh9X8fBoPQmO{64&gk%o)cV|BHX6~DLZ|;5f@dxMZz0cWe?S0l>`L4CT_fJ|` zO0L|v5&$511vL> zZ&*-~Z+pWP7m0Td^1A;tW~`@ppvto)e!ZdEA4g$Xs{W^}P7D8=KVRoLEC3ocM-J{k zgYTUh7(Fof3rW0?@++FGyvKFZGpf)jW6nZdb{{K%dm9@_#%m@@xx0o?HVE>MMxgiO4>16sh`m zP59ra^UstgzV8V5&+@=WvHw>K{ND)tZ?@!rKK##=CqsM;{`(J;5)O^Yxv!6O&HT%@Fxk+ zqKjo@h{N3k4dNwi3Pe^uhYSS7s>p>w!+kX(0obm73xLP1Mn;U^Iz zAVMz%dxO4K=@7}Gn+k-RWoJ4nBBYjg4E74}R*CL|r-mJdIP5pz9fVy5@+9mt5bE$S zd=>q-$3XZipM-=oF=$IB9(FP^M6ccOGp!!MXl-^%y~ALW;E_~YC5b9Ob(tpp8jy~a^VsZIVC z?@deq_6bHe_0sIO*1=(0O4`UOGcLqVi`OE7l8$r>YS94cmi163t zFwY~>0_qI!m$51)tm#)bhABcml#nYjQ$VNx{9qFvP5k|a2kn3N;wnM%ELjNzbg6CF zSk+>OBA1C`*UgQ{uZ54s6fFVS9g>aNY9kky{al$8hwKg{v0TNWg#kz+DFSGft5k?` z{|FB?nk%}tYmpMtzs5&lG&_4ipV-cBKyE>>vX-i?K!le9Ti{~{kr0kj)|UaqU`a$E zK%`o)xkjBrnzOsVoz|yNo=wLVteQ^$^ookalQTAqj3CgC2r5*bvqY=lzQ0F?kYLRG#=37^*kn<>&ayO%M zw($;&gkmhK@93l@o@oE0~j|a&WX!^h#lbO}B zXg4+UhO!5FwqbM<^^Xjxg_@Ms2j=fP+DZ))NEXw6S&X00U!t+!sMmWGv)`)75NUm3 zR7x9*N$A2WV}o>FRF`&#ld?uD=A`x?pD=Za+T4c9Lt9a<__&|{meJP<<@RQ>yHP$9 zIhJ91AyuV`@jlpxhJ9j1L5@#7^WADM^o1WdAF@*c#7|S?G3+j9z`thAA!LvRj^Ch9 z^%|l+m8PdAJab#hsO1g~W#+(JEbg`z8c39NT$R01Cy8b5_4<~rsob4^u5jeChmC&U zH8XGAi9iEa_qaY#u!z1x5j_?y1l$<@QrPqyXKYa5q(+QK^lgjUtVMgOD2c5+SDiA| zt5e`&-9AA7dCL#h3&HKqbpMu0AEHLyF$F4UDM^}_=_QKOP+#?c^yDFL1CQnV)p?NeDx%Rk}TkM=3s^W|m9+265-k%0s6nSNL3 z8`$A^yk5`74f+6M^a}xAr44&$dQ(0Yqv`%# z#kO&2QW@m$im!f2vV%QX-G}t$7h~E5txhg%!#e=4FVs@ScGc&K_!#=z3UBf@hRQ(r zt}FSNj5YpEj>h+s{Sk_M)z%}aWtWr8XQzX;(9hHS35&j?J)sv=pdJeJRt~{>DM)vM zG6;4n==^Sl>f0q1H4cy^qTf&=mmzMnbE|QnaX}T3s*`BajCzs&jIEDLiclzj|E*{@ zHS7&zpfeF)6}@{6g7t$Z9G})6qY1r#2H58!bYHmMc))UcoPMxE4#HEnziF#bcFdd3 zdGpM#v`1?!y8DHx!&9%huWv`8x!}H>2F~SQNO@+DY1NII%VpyO^ZNl`D=>pI=f7X* zy$%wz$ebnD0(OD%eU4{jXPgw7 zNhm<01L9ORQy-y-b6Kidlqbf86xTcE39-U?(xq1?{tl0NEix(eYVO$c;2H zNhqF)Fd1@&HVM5~LyKpTfn&AF5p!Zqz+@xjZ;dUUxa*9htMN)yAhV=drWhixKwv$X zQ)j#6Kkwgrg8s$d{inPBO_e}vZ2@Z=Yoz-GeX8%kS{b4Xb8KOswcPS6Y0?=!CbW=s z6%KR1BUB@`pK-hQKfL_=OPkx`*ZmKlSN}GaMM9@>h)h=nd?dj@oKElfL&a2@Maq7E zJXVzTnS!0JLqpQ(V!QFDrxL2)1C|)*XV4^(cmwWJX=Fveseg)Ox_GUTEQICcqwUab zP68dGiqYy|F6R4PBB%)mfs275;BEu2cO-wx_cQkTK z`9~_vcYwmhgTIyp1FFRL>}|s{V?*Gq%8eh3{8uQI2R{Ve&lmL`w>+0V6x@$tR4htV zFF@|^40vL3sG_=a=Deyh20QW(Tk{V`@E?6u5iA<6TJFPD=3O7CUO}WtdG_{_v@=hJ zn6EMZHLfwuDR|u$^wDYXTeHJlcP^?Z)y&j5PQ{x>Q+4P+I@pId_>OQClWx-beqURI>=&j zNAhdFV%7}Dg8=X7Q(SK2pSAtln#tJ@=IAa3lOM*$0#Ua`w2&24IB$)iCc@YhQ;1-i^U7gW4KZA|XJdYl8x28SySd@<5txJ;6Hsh|V(Hb(Vm5wSjeJ zOBVmyG~?YdUifQkB^1$Dkxl(m$|?k&z#`Ey1Ze@A<(E;iwg`Q7RXYB!4HVs`uRMD| zT#O!fHIboATHFJuD)T*~%Uv+yKL+r5o@)PgKUX%j-QX4+v9ORp*{sNz0ZZHpGE|_r zoPZCo=0|~v8BwLTz;xix0VOC-a@?wXwFDgUjEb>HFVa0>xobe1Bt5eHs&(mK2HeG1 zL#V3LULq(s_y|Jqqutbc{~~-A$$dT5L-G8}z&QlJA3Lq5@P0X9u>xiqJxBEaqDdME z4|ZU$`*Xkon3+8^Zv0caG*qY_aO+RCRERFUu}gwK8wx}hg^yvvKc89!6|U_3Ju3yP zRVWT=;~K&_e-5mG3RN1p(|^7qO{6{V@>SW2hF4-H0pWVWm#8sx7cp#&qVQwV{Lb#3 z4cFWJ>-dwAGLhuas}$iOwR6RcnMi_(Uric{wMGF7qE)TtlYxZuV-;@rO6TucqZt#6 zt_wXJA^F(0A6y1Q?^=Cm@R!yfa~HKq9%8KO09x1ttO)_aUz{(_$aCugSoyH^kh+O9 z@q+&wG>Y}TuYgv$S^}cPO*uPve#z=NAI)Pj$H+Sz(+DKyT99S!GPegb%wG$V0tStb zl`Fs0o{t13dx$i=QlEjh2#Tc$M#RsGcq8!7FN~=HN4c3W1jHb(*5KU$B-^+5ni6Vi zni2SmfSFNmosa8>t>pe_c}kXO>XX={6MH4-Bd)Uzi0E6YfM2H-kMJH!j zArttV^G^i5i^osPRx7k_mi%s|=o@u?8E1clI1+9KAfHyQRum~JMYgk^kgm|y zKC~h@yn27fL;_p;26j3j0V$2f;#iMEQgE=ax7T2Hiclv@tX5H+GWJr-+zqs=5&Rg1 z_g$;#+s0#NJGJ|7r2l5{KqOfc^?FrR_La)Qw z?~Z;&mA3m8d)Mt4J!Rr=a#fIqDOfrkwPizF*H}B%31nF+#CrkKmZjt^Mx|8PEK)7{LeQFZV75X1+RBW|b|IXnizfgb}P2&tFQj+8SL#DCT!J={h~|KQuTw z|L8J$B%+9q&KQQlPrm1?u8u7wk*=dx*v=N$f%CP9d0uuNv8k@loyEk3zoY z!|+`WNwn?9?~S7qK_}s@7S+#pp35JQw|Y}JIMW=H*HJS+>iTv;|T&-}$qrvi5r7 zSjLxm*7oia+l4gOZ|ey;;e<)xQ+drSYQd^)JXu31pG2zm-Sc9noS$R<;cwAEO}jDp zGO6s`D9uz|xpqaAP)>T)c(R;J-EdaR_ap0RpTjpi5ssHM7N^jRyko2=zTOKlGtYc# zCR|0JRn9f+TGCe!F`Xl5UEiAHUn5qHrFyPu>cMXg`?+ma=Rt0R)P8;4!z#&3hffI( z$yi>{cTQceJ!h*EHRSJ~5L5U4!m#d=H=Cty_G+j2Y@|icaf1tct^7WJ@Z|1_avyWh z&OExUM{h~DcLiNn?OFYbw1R!bj#{(0!lnh+te-|-60wfWdQK&_Wm#k6TXI#M;%wix zD7tN~^xqSq82kP4q^X&=o2+bZqV{F`otLZphMx6{wDS9W>-x8Z={KBPbZj1UHOfB` z5H=kS+{Y-Zv)cOJ`0NZ}kB)t}(lt*Si=H_a8I@rDHDBNx>kz#CxUQCuB3r+FQ{ zM@ed|3f&+Yp}iS6d!o2~_O)&x_0gvtT2IAu)2}fKS%Zac9fd5N?N$vZXg=g|JN`C; zapKB@J^G`?ih-_|p1Cf0OP92~Dw2=Bz2C4tn7?=gl74 z=xSJZYtjaCb81s4{0sZv3R3-cJutW$vdLiJ&T+)?d>X^0_KspX8!?S8s!DN<4=K*m z#yN}ShY=gUYDW=@J%2dwymn^k@)^@@I}%YDhJd!dt$V!=e%fu5ogs@c0YOqY`qWiM_jY*gHh^hC*f z58S|Z)@<7S>%)wflAT6*4vpIAPcbXkf9DYVV^yVR+1B_0fo8A1@(S!I-tv^p8DENM zk;wME@W$rx`SkLK&7mu#bIUndsE*q5YU7!dVw6qx6sW06TNW%@UB>!oJz^a*ug z^kG1(_qZI?0=%o#%01r%Ixe=Ji;bE==PNvY?Duh(X1&T~T=CUW&BQgUpU?(|Ltjni z2s`+vtUukH@P+O3yHC*HP=;B1Bg&!q)pqCe( z>tSkSeN-<)j1p8KitP_SBZ+YO#E1#b|IV&g1;^7z=;n7;yK{!Vr`e-99A zN%iQs_`>Lv&2;@VbfzYvYYN@G(JHqQdX-`C{omen-kdyciW6sM)OGXE;=dg7XseOL?giMO%U2R6QAM+St76TY3nxnt^pO-wTUf$VvP^C-?K9eAM!0-s*r|GctnN& zimsUCo(hL}>Y(J3F2(WQnk|Yxbv;X)xfQM-LWPQMzb`q z*B%QtBc>;{vzazv`u1xQ^$UMuMRzevQ0|2+tlkXSz8iWRc`Z9cr>HNdhqgzh2y2T| zvm}R_g?eZ;y7g`X(#|VRq2%qNWSA(Q5IM(16OkVC?AOta4*or!16`?*B$4dDJFtZF z^`5Vly%sskfyFh-CEMQXHCog zhdQ1GM(S4G_fq;cyi?SuFIRzB^ipUKaozz}CJl|K9=`Ey?-I}Uce?FDZD-9$$Nq=1 z#C-G8sGnrZ4}34eI^w*j<^q$HGrwwwBG6ITQ@Aqnd_?2LLmRhH)>>snT>^Q8Y_C{Q zB;dGAqjbD8-s~OkzgQHy0pWu`Q?}|AQw2;bB9(>D-`j<4JZ>1gqAGIb`E1*d6EC|{ zP4k(S4|kr=e)=lAP1gOJZKBmsD6hn1yW3+Yom~#-`RdJD1-I1$&>fLSPH%bKhybLy zgCEQTBVHrY>~MW87*HabW7VyhborEWM*o>?w1GQUZ&i<&PRMX5D^~sYJG|8ObFm^i zuMO@*U9z0LcFG*Yy1d!#_kFa!CeSv`MT}xm9H{OJ=ji;-GnzRldnNl=_B3j;KKj#A z(rTbRO~W9VID*L2j5i95(bwLxN`R6SrhBaL$&NDHS-Ht=VV8qvCaaKt7(cYh+@8+d zcT{_~g7w$ykx_-;PjrU5xCCrG{v^{iX)53tp}-{l72Z)l$+1}pj{40Yx3v{}XSYc> zvyo3T%^VBdYLArf%p^X;=(M|hmLl3{4!bGMNg+q;O=hm|k45SjbCuVlJa=)iaI(3B|0s1v$&r8P`g$jQ z6LNj)u6wzKGn_NwhwOq~eXWk36a<}C!8a4G)pf6+uMWIT#fY!DY^hEmpX9`W)Q&u< z5sQhLhgVPG&gic)bd~^5ud8TPrRL3FuW*)2N>mcXZtFFw9x3l%>6J0ekRkT0Fiy|j zF65-#P~NS8KN^W>)`}v#)nVM$?llz}SM!Y=zJ~#JCXK)53!5xX+)Nzw&vB0XY7DXtYN(4n+Ut<&i*;w5kEcQ9_`JM0>zT$3$ch1HL34!;>) zKE26|j37tmH>Hc6Ht~wDsN-Dqb@kG9kwD6qdMoU*?ycWE-sBmqwx4Mzxh*qj(b~6p z|3iNhmn+6pY3!2j$`EbRc3{@rUTJVMZVTrPqFL(VN8{GZt0>HO*0|~LE>$IH7 zi7QQZQEWS&GAGP+EoQ{Gwj-Ks=ew6yc`YiP^N<$a*z~)}d%X%fTs5RK(E9o8>kmJn z%|jiQn4Nn3&)+R9G2CzP23cPBPw}FgwYhBOd`0{SyjdN-K?t=SbG{?ETCf5NLqW63 zqL=_UC|@6`(AjS7kmVDW?BI%Gl>m$>p@YQiLp|k)?9+_ptt-vscxsu=v?l_S`Kvl5 z)!MO5vA=(j>=aa<{xk(Q`a4J36oAFV&4I_DW*yOz8+gSepzPRBjLwM2Gchy$;h7ef zaJu&wmsRv#f+@2?qx%?AaQ7eMy=X5P<42dVIThRd;%jXqM*14VH#R|-&hvl-y@(+7rWrZ(31i<9l0=i9*)=zMCeWY>jv`R zXgP{*xy0j4H=`ibFlM>XsfOSe5kf346Edvc{s?qY^o0wSbQxkM3rD**DbR}w6PB)4 zgA)K!fV*87K-lqp>2)GcaBQ;`oCtui&~?GoI+nnHRk$!g(|frK$tn@NLJCX^2@UXn zzqrltyKDr%hG4D|E8tAi;sZFG(<%?b6+m6x-AvwFW^Lbyn%t!d*8KcS9EsJDosaO5 zCYESF=-9+!F4e0s1Aqm8`fOTqXdAofhC^Da%mCLC)R(d*AUZ@AZ-Q`4KhTBHZg#x zSPQu@XzNk-_u>3;I&R)&1-)SwPNVd%qBCctm(wW-HL z`hyQTMo7q~(uz#I~T27luib|g+0U)y57Xl4)Z=4j*$568q6j`e2<$E=w7Vmc0q zi|s9gb@g?wrzJ^)g!z-WCqjSDM7xJ1b}sRRx1QGMyda~+B$YE6_N1-SAiImxKA3-w zI*0zmpw&l4g&y)lo(8j}d6~(qsmj{#(##>gp%TvQcO%8NrL6QIquipfNB7qb1V2nL zAVQRGBTbSA2Y4xUXcc9P#-u(_=f-TGtH!M-;ueNHo={7e!|}Xq!A(wNBxhxJ`Dptr z@7GeL;!(}wasrzbxdxj1+4FKd+qmO) zW4Zf6D3!cDAn2%E+0%n>MLKRP7jJ)u2$i>w;w-)n&Z!et)%rT;;VN%2)^yZ+J-9`Y3(dKPe)9&TNv2A%Xw8+!^*_+1JC@2_1^d|sO_*gmEd3)Gj#33 zOtKw0J#3=X{fCF)(-U2zrj9>KR+3+N=-;gN*!5LSHb!H$%8Z#_+trXoT;nW9*<0Jj z&>c9d5$oIQQvu71Uh&>TveSX{^spf}IOhmHm)dh-SI1y_t$JgKhGfpS9g$}e)Q7II z*e6lkhpnm)%Ea<;jY8$~yCpSnrz~8el7**R@^&X(+y23=iQ$1;NUaag*=Fb+zWR(u zJJ?$zD|`}=P>=ckTC%X|kBuFmk=WIPUKj-#JB-v@`3O1^CILGwSr%7Z^*ZKHU zG~Y5yO%KOBN2A|4J)K_pF828@yYCaOHGCL393)g{h13}iTI;;xn4xM&Zn@4XD`yXxW6pxBnogD9qV|KPg&pOBs$_eyK+~UIAjHGQ+*ATv za+^_}vh|u^ZY9jgV4VV4GchhM!ZF0&uN%c!`Qta_p~!LG5D6UTyo~8>3aS^Q=Qvkg zQ6VlYno1y_#x(8Vz!~2GePSU4Q=`NKX#JZF)H6-BC+NTF_d7_`Z9d%@-?+7gC)}io z)dn|d<0t3>*cgkN0CEv2fvbc?{rVS*Hqq`wI*ed|ZayoxjV zb?gVW$eXd9)KBI>K*@C8I~_a4M%b%JdQ08*b);^~hs~sq}QY^5# z?cKUUd6ZVzwqY7!Z(V4#+9(7cFyd$LZG)Lav2xZvx9k~KesKvgnIS%)oH8ZmLqGUS zi#W9C-$3}fH8gw*_oR#F_lF)p;44Yc!n_pDn2kuZD|?kxu%|kY+Se6OD<{|4VVBv_ z6<#YfjmjI9vyGxuKAe^zqP6d6WMMX{)JwBWgF~hr|Bx$dO`wWmaTs&B#Fq4-v~68; z{X8Z>Mg%Tq(X&6?L1UO#CU{e+n})Y|4isW#r!%PqeG0dSe%)(Rd%&hC{@iRJ|M~Zr z;B$j-VB&o@-UvnTFiYv!6Vu#f2cdk~2 z)BB)Cf+$S$FzKvXH@*JMGE;~Ei0!;tu49Q+;1i3V+yPlQ%P$UPBIp$urIFznaunOb z`vGjL&fAI7T}y09%i1Lzj`VxMO`so4P*K86Z-&h{68ZNLi~bi99jxXuujntNJxGN{ zA&|^JL<>TY&tCSSSktNm!<&KNiN6QHC^1}i(AR>k1M)=S67#bF_3Whr%Ex~vktEdW zSmr}ne;0bI!eHS4o)Go775axUsigN9olR~hn3WmB+VPJImxvcSrCZ78s$4;4tRgZt z1E`OE0({Q2I>Itgh&=f{AHik98_~cMhL6HrsPFlxe@|g#Fn0cR)BYoI?;pT){2Meo zm|ylc **Note**: If you don't want to use the custom router class, you can make use of `fastapi.Depends` instead. +> If you don't want to use the custom router class, you can use `fastapi.Depends` instead. === "Router class" @@ -90,7 +90,7 @@ This will enable you to use dependency injection in your `FastAPI` endpoints: ### Managing container context -If you wish to initialize the container context you can simply pass arguments to `create_fastapi_route_class()`: +To initialize the container context, pass arguments to `create_fastapi_route_class()`: ```python from that_depends import ContextScopes @@ -103,16 +103,16 @@ and the global context will be set. -## Integrating with FastAPI Using DIContextMiddleware +## Integrating with FastAPI using DIContextMiddleware The `DIContextMiddleware` can be used to manage context, but its features overlap with the [custom router class](#using-a-custom-router-class). The main advantage of using middleware is that you can set it up for your entire `FastAPI` application. -> **Note:** If you want to use both the `DIContextMiddleware` and the custom router class, you should not pass any arguments to `create_fastapi_route_class()`. +> If you want to use both the `DIContextMiddleware` and the custom router class, do not pass any arguments to `create_fastapi_route_class()`. -### Setting Up the FastAPI App +### Setting up the FastAPI app -You can use **`that-depends`**’s `DIContextMiddleware` so that any request automatically initializes the context for your container(s). This approach is convenient if you want to: +You can use the `DIContextMiddleware` from `that-depends` so that any request automatically initializes the context for your container(s). This approach is convenient if you want to: - Automatically initialize or tear down resources on each request. - Provide a global or request-level context dictionary you can read from your container. @@ -147,7 +147,7 @@ def get_time( ) ``` -- **`DIContextMiddleware`** automatically sets a “global context” for every request. +- `DIContextMiddleware` automatically sets a global context for every request. - The `Depends(MyContainer.current_time)` call is how you reference the container’s provider using the standard FastAPI injection system. To run this app: @@ -158,15 +158,15 @@ uvicorn main:app --reload When you make a request to `/`, you will see the current time printed, and behind the scenes the that-depends container is in a context. -> **Note**: If your container uses advanced context-based resources (e.g. `ContextResource`), you may also set `default_scope` in your container, or configure an explicit scope. See the advanced section below. +> If your container uses context-based resources such as `ContextResource`, you can also set `default_scope` in your container or configure an explicit scope. See the advanced section below. --- -## Examples of different Providers in FastAPI +## Examples of different providers in FastAPI -### Singleton Providers +### Singleton providers ```python # Suppose in mycontainer.py @@ -195,7 +195,7 @@ def read_settings(settings: AppSettings = Depends(MyAdvancedContainer.settings)) return {"db_url": settings.database_url} ``` -### Context Resources +### Context resources For “request-scoped” resources (e.g. a DB connection per request), you can use `ContextResource` in your container. This typically works in conjunction with `DIContextMiddleware` or a manual `container_context(...)` call. For example: @@ -247,7 +247,7 @@ async def read_db( When writing unit tests, you can use `TestClient` from `starlette.testclient` or `pytest-asyncio` with standard FastAPI patterns. The `DIContextMiddleware` approach ensures resources are created and torn down automatically each request, so no special arrangement is necessary. -**Example**: +For example: ```python import pytest @@ -268,21 +268,21 @@ def test_read_db(client: TestClient): --- -## Common Patterns and Tips +## Common patterns and tips -1. **Global vs. Request Context**: Decide whether your container’s dependencies should be globally shared (e.g., singletons) or created anew per request (e.g., database or session). -2. **Combining with FastAPI’s `Depends`**: Generally, you can pass `Depends(MyContainer.some_provider)` to route handlers. Under the hood, that-depends will be invoked. -3. **Overriding**: You can override a provider in tests by calling `MyContainer.some_provider.override_sync(...)` or using the context manager `with MyContainer.some_provider.override_context_sync(...):`. -4. **Performance**: If you have expensive creation logic (like a DB engine that can be reused globally), prefer using a `Singleton` or `Object` provider. If you need ephemeral resources, use `ContextResource` with the `DIContextMiddleware`. -5. **Custom Context**: If you do not want to rely on the middleware, you can manually create a context in any async function by calling `async with container_context(MyContainer):`. -6. **Multiple Containers**: You can define multiple containers and connect them (e.g., `ContainerA.connect_containers(ContainerB)`), or add them all to the `DIContextMiddleware`. For advanced usage, see the that-depends documentation on “container connection.” +- Decide whether your container's dependencies should be globally shared (e.g., singletons) or created anew per request (e.g., database or session). +- You can generally pass `Depends(MyContainer.some_provider)` to route handlers, and `that-depends` resolves the provider under the hood. +- You can override a provider in tests by calling `MyContainer.some_provider.override_sync(...)` or using the context manager `with MyContainer.some_provider.override_context_sync(...):`. +- If you have expensive creation logic (like a DB engine that can be reused globally), prefer a `Singleton` or `Object` provider. If you need ephemeral resources, use `ContextResource` with the `DIContextMiddleware`. +- If you do not want to rely on the middleware, you can create a context manually in any async function by calling `async with container_context(MyContainer):`. +- You can define multiple containers and connect them (e.g., `ContainerA.connect_containers(ContainerB)`), or add them all to the `DIContextMiddleware`. See [Usage with multiple containers](../introduction/multiple-containers.md) for details. -### Accessing the FastAPI Request or Other Context Items +### Accessing the FastAPI request or other context items Sometimes you want to pass the `fastapi.Request` (or other request-scoped data) into the container context so that providers can read it. You can do that either via the `DIContextMiddleware` (by customizing the `global_context` dynamically) or by writing your own dependency that calls `container_context()`. -**Example**: Writing a custom dependency that sets up the context with the current `Request`: +For example, a custom dependency that sets up the context with the current `Request`: ```python # request_deps.py diff --git a/docs/integrations/faststream.md b/docs/integrations/faststream.md index f860da0f..cef30598 100644 --- a/docs/integrations/faststream.md +++ b/docs/integrations/faststream.md @@ -1,11 +1,11 @@ # Usage with `FastStream` !!! info "See also" - [`modern-di-faststream`](https://github.com/modern-python/modern-di-faststream) — the + [`modern-di-faststream`](https://github.com/modern-python/modern-di-faststream) is the equivalent FastStream integration for [`modern-di`](https://github.com/modern-python/modern-di), the newer sibling DI framework. -`that-depends` is out of the box compatible with `faststream.Depends()`: +`that-depends` works out of the box with `faststream.Depends()`: ```python hl_lines="14" from typing import Annotated @@ -30,10 +30,10 @@ async def process( 1. This would be the same as `Provide[Container.suffix_factory]` -## Context Middleware +## Context middleware -If you are using [ContextResource](../providers/context-resources.md) provider, you likely will want to -initialize a context before processing message with `faststream.` +If you are using [ContextResource](../providers/context-resources.md) provider, you will likely want to +initialize a context before processing messages with `faststream.` `that-depends` provides integration for these use cases: diff --git a/docs/integrations/litestar.md b/docs/integrations/litestar.md index c7ba682e..cfd21735 100644 --- a/docs/integrations/litestar.md +++ b/docs/integrations/litestar.md @@ -1,7 +1,7 @@ # Usage with `Litestar` !!! info "See also" - [`modern-di-litestar`](https://github.com/modern-python/modern-di-litestar) — the equivalent + [`modern-di-litestar`](https://github.com/modern-python/modern-di-litestar) is the equivalent Litestar integration for [`modern-di`](https://github.com/modern-python/modern-di), the newer sibling DI framework. diff --git a/docs/introduction/generator-injection.md b/docs/introduction/generator-injection.md index 375654dc..4171b5dc 100644 --- a/docs/introduction/generator-injection.md +++ b/docs/introduction/generator-injection.md @@ -1,4 +1,4 @@ -# Injection into Generator Functions +# Injection into generator functions `that-depends` supports dependency injections into generator functions. However, this comes @@ -43,9 +43,9 @@ You can use the `@inject` decorator to inject dependencies into generator functi yield value ``` -## Supported Generators +## Supported generators -### Synchronous Generators +### Synchronous generators `that-depends` supports injection into sync generator functions with the following signature: @@ -60,7 +60,7 @@ This means that wrapping a sync generator with `@inject` will always preserve al - It will raise `StopIteration` when the generator is exhausted or otherwise returns. -### Asynchronous Generators +### Asynchronous generators `that-depends` supports injection into async generator functions with the following signature: @@ -73,7 +73,7 @@ This means that wrapping an async generator with `@inject` will have the followi - The generator will yield as expected - The generator will **not** accept values via `asend()` -If you need to send values to an async generator, you can simply resolve dependencies in the generator body: +If you need to send values to an async generator, you can resolve dependencies in the generator body: ```python @@ -95,7 +95,7 @@ as part of dependency injection into a generator. This is the case for both async and sync injection. -**For example:** +For example: ```python def sync_resource() -> typing.Iterator[float]: yield random.random() @@ -139,12 +139,10 @@ with container_context(scope=ContextScopes.REQUEST): next(injected()) ``` -Since no context initialization was needed, the generator will work as expected. - 1. Scope provided to `@inject` no longer matches scope of the `sync_provider` -### Container Context +### Container context Similarly to above, the `@container_context` also does **not** support generators: diff --git a/docs/introduction/injection.md b/docs/introduction/injection.md index a9d21ac1..4fefbb24 100644 --- a/docs/introduction/injection.md +++ b/docs/introduction/injection.md @@ -1,14 +1,14 @@ -# Injecting Providers in **that-depends** +# Injecting providers in `that-depends` -`that-depends` uses a decorator-based approach for both synchronous and asynchronous functions. By decorating a function with `@inject` and marking certain parameters as `Provide[...]`, **that-depends** will automatically resolve the specified providers at call time. +`that-depends` uses a decorator-based approach for both synchronous and asynchronous functions. By decorating a function with `@inject` and marking certain parameters as `Provide[...]`, `that-depends` will automatically resolve the specified providers at call time. --- ## Overview -In **that-depends**, you define your dependencies as `AbstractProvider` instances—e.g., `Singleton`, `Factory`, `Resource`, or others. These providers typically live inside a subclass of `BaseContainer`, making them globally accessible. +In `that-depends`, you define your dependencies as `AbstractProvider` instances, such as `Singleton`, `Factory`, or `Resource`. These providers typically live inside a subclass of `BaseContainer`, which makes them globally accessible. -When you want to use a provider in a function, you can mark a parameter’s **default value** as: +When you want to use a provider in a function, you can mark a parameter's default value as: ```python my_param = Provide[MyContainer.some_provider] @@ -18,11 +18,11 @@ You then decorate the function with `@inject`. This tells `that-depends` to auto --- -## Quick Start +## Quick start -Below is a simple example demonstrating how to define a container, declare a provider, and inject that provider into a function. +This example defines a container, declares a provider, and injects that provider into a function. -### 1. Define a Container and a Provider +### 1. Define a container and a provider ```python from that_depends import BaseContainer @@ -31,9 +31,9 @@ from that_depends.providers import Singleton class MyContainer(BaseContainer): greeting_provider = Singleton(lambda: "Hello from MyContainer") ``` -For more details on Containers, refer to the [Containers](ioc-container.md) documentation. +For more details on containers, refer to the [Containers](ioc-container.md) documentation. -### 2. Inject the Provider into a Function +### 2. Inject the provider into a function ```python from that_depends import inject, Provide @@ -48,7 +48,7 @@ Here: 1. We used `@inject` above `greet_user`. 2. We declared a parameter `greeting`, whose default value is `Provide[MyContainer.greeting_provider]`. -### 3. Call the Function +### 3. Call the function ```python print(greet_user()) # "Greeting: Hello from MyContainer" @@ -56,12 +56,12 @@ print(greet_user()) # "Greeting: Hello from MyContainer" --- -## The `@inject` Decorator in Detail +## The `@inject` decorator in detail -### Synchronous vs Asynchronous Functions +### Synchronous and asynchronous functions -`@inject` works on both sync and async functions. Just note that injecting async providers into sync functions is not supported. +`@inject` works on both sync and async functions, but you cannot inject async providers into sync functions. ```python @inject @@ -72,9 +72,9 @@ async def async_greet_user(greeting: str = Provide[MyContainer.greeting_provider --- -## Using `Provide[...]` as a Default +## Using `Provide[...]` as a default -It is recommended to wrap your provider in `Provide[...]` when using it as a default in an injected function since it provides correct type resolution: +Wrap your provider in `Provide[...]` when you use it as a default in an injected function, so that the parameter gets the correct type: ```python @inject @@ -88,17 +88,17 @@ def greet_user_direct( --- -## Injection Warnings +## Injection warnings If `@inject` finds **no** parameters whose default values are providers, it will issue a warning: > `Expected injection, but nothing found. Remove @inject decorator.` -This is to avoid accidentally decorating a function that doesn’t actually require injection. +The warning catches functions decorated by mistake that do not require injection. --- -## Specifying a Scope +## Specifying a scope By default, `@inject` uses the `ContextScopes.INJECT` scope. If you want to override that, do: @@ -111,7 +111,7 @@ def greet_user(greeting: str = Provide[MyContainer.greeting_provider]): ... ``` -When `greet_user` is called, **that-depends**: +When `greet_user` is called, `that-depends`: 1. Initializes the context for all `REQUEST` (or `ANY`) scoped `args` and `kwargs`. 2. Resolves all providers in the `args` and `kwargs` of the function. @@ -121,7 +121,7 @@ For more details regarding scopes and context management, see the [Context Resou --- -## Overriding Providers +## Overriding providers In tests or specialized scenarios, you may want to override a provider’s value temporarily. You can do so with the container’s `override_providers_sync()` method or the provider’s own `override_context_sync()`: @@ -139,24 +139,27 @@ For more details on overriding providers, see the [Overriding Providers](../test --- -## Frequently Asked Questions +## Frequently asked questions -1. **Do I need to call `@inject` every time I reference a provider?** - No—only when you want **automatic** injection of providers into function parameters. If you are resolving dependencies manually (e.g., `MyContainer.greeting_provider.resolve_sync()`), then `@inject` is not needed. +### Do I need to call `@inject` every time I reference a provider? - 2. **What if I provide a custom argument to a parameter that has a default provider?** - If you explicitly pass a value, that value overrides the injected default: +No, only when you want automatic injection of providers into function parameters. If you resolve dependencies manually (e.g., `MyContainer.greeting_provider.resolve_sync()`), you do not need `@inject`. - ~~~~python - @inject - def foo(x: int = Provide[MyContainer.number_factory]) -> int: - return x +### What if I provide a custom argument to a parameter that has a default provider? - print(foo()) # uses number_factory -> 42 - print(foo(99)) # explicitly uses 99 - ~~~~ +A value you pass explicitly overrides the injected default: -3. **Can I combine `@inject` with other decorators?** - Yes, you can. Generally, put `@inject` **below** others, depending on the order you need. If you run into issues, experiment with the order or handle context manually. +~~~~python +@inject +def foo(x: int = Provide[MyContainer.number_factory]) -> int: + return x + +print(foo()) # uses number_factory -> 42 +print(foo(99)) # explicitly uses 99 +~~~~ + +### Can I combine `@inject` with other decorators? + +Yes. Generally, put `@inject` below the others, depending on the order you need. If you run into issues, experiment with the order or handle context manually. --- diff --git a/docs/introduction/ioc-container.md b/docs/introduction/ioc-container.md index 07dac8ed..0f649a7c 100644 --- a/docs/introduction/ioc-container.md +++ b/docs/introduction/ioc-container.md @@ -1,9 +1,9 @@ -# The Dependency Injection Container +# The dependency injection container Containers serve as a central place to store and manage providers. You also define your dependency graph in the containers. -While providers can be defined outside of containers with that depends, this is not recommended +While providers can be defined outside of containers with `that-depends`, this is not recommended if you want to use any [context features](../providers/context-resources.md) @@ -39,4 +39,4 @@ class Container(BaseContainer): 1. The configuration will be resolved and then the `.db` attribute will be passed to the `create_db_session` creator as a keyword argument when resolving the `session` provider. 2. Depends on both the session and configuration providers. -3. Providers have the `cast` property that will change their type to the return type of their creator, use it to prevent type errors. +3. Providers have the `cast` property that will change their type to the return type of their creator; use it to prevent type errors. diff --git a/docs/introduction/multiple-containers.md b/docs/introduction/multiple-containers.md index aabd5af9..9b48a378 100644 --- a/docs/introduction/multiple-containers.md +++ b/docs/introduction/multiple-containers.md @@ -1,6 +1,6 @@ # Usage with multiple containers -You can use providers from other containers as following: +You can use providers from other containers as follows: ```python import datetime import typing diff --git a/docs/introduction/scopes.md b/docs/introduction/scopes.md index 5783a2e5..614fa785 100644 --- a/docs/introduction/scopes.md +++ b/docs/introduction/scopes.md @@ -1,11 +1,11 @@ -# Named Scopes +# Named scopes Named scopes allow you to define the lifecycle of a `ContextResource`. -In essence, they provide a tool to manage when `ContextResources` can be resolved and when they should be finalized. +They control when `ContextResources` can be resolved and when they should be finalized. Before continuing, make sure you're familiar with `ContextResource` providers by reading their [documentation](../providers/context-resources.md). -## Quick Start +## Quick start By default, `ContextResources` have the named scope `ANY`, meaning they will be re-initialized each time you enter a named scope. A container that defines a `ContextResource` without an explicit scope must assign `default_scope` before it, otherwise defining the container raises `DefaultScopeNotDefinedError`. @@ -133,7 +133,7 @@ await injected() # will resolve - `INJECT`: The default scope of the `@inject` wrapper. Read more in the [Named scopes with the @inject wrapper](#named-scopes-with-the-inject-wrapper) section. -> **Note:** The default scope, before entering any named scope, is `None`. You can pass `None` as a scope to providers, but since it cannot be entered, in most scenarios passing `None` simply means you did not specify a scope. +> The default scope, before entering any named scope, is `None`. You can pass `None` as a scope to providers, but since it cannot be entered, in most scenarios passing `None` means you did not specify a scope. ## Named scopes with the `@inject` wrapper @@ -146,7 +146,7 @@ def foo(...): ``` The `@inject` wrapper will enter a new context for each injected provider that matches the specified scope. -However, it will not enter the scope by default! +However, it does not enter the scope by default. Here is a simple example: ```python hl_lines="5 10" @@ -190,7 +190,7 @@ injected() 2. Context for `Container.provider` is initialized and will exit when the function returns. 3. This assertion will pass since the context for this provider is still the same. -This implementation might seem complex at first glance, but it provides the following advantages: +This design has the following advantages: - Only context for `ContextResource` providers you need is initialized. This improves performance. - It discourages explicit resolution via `.resolve()` or `.resolve_sync()` in the function body. diff --git a/docs/introduction/string-injection.md b/docs/introduction/string-injection.md index be88bde0..79b62923 100644 --- a/docs/introduction/string-injection.md +++ b/docs/introduction/string-injection.md @@ -17,10 +17,11 @@ To inject a provider by name, use the `Provide` marker with a string argument th Container.Provider[.attribute.attribute...] ``` -The string will be validated when it is passed to `Provide[]`, thus will raise an exception -immediately. +`Provide[]` checks the format of the string as soon as it receives it and raises a `ValueError` +immediately if the string does not match. It does not check that the container or provider exists; +that happens when the injected function is called. -**For example**: +For example: ```python from that_depends import BaseContainer, inject, Provide @@ -58,8 +59,8 @@ assert read() == "Damian" --- ## Considerations -This feature is primarily intended as a fallback when other options are not optimal or -simply not available, thus is recommended to be used sparingly. +This feature is intended mainly as a fallback when other options are unsuitable or +unavailable, so use it sparingly. If you do decide to use injection by name, consider the following: @@ -77,4 +78,4 @@ If you do decide to use injection by name, consider the following: injected() # will resolve ``` -- Validation of whether you have provided a correct container name and provider name will only happen when the function is called. +- `Provide[]` only checks the format of the string. The container and provider names are checked when the function is called, and an unknown container or provider raises a `ValueError` at that point. diff --git a/docs/introduction/tear-down.md b/docs/introduction/tear-down.md index 4e031508..f586c8e9 100644 --- a/docs/introduction/tear-down.md +++ b/docs/introduction/tear-down.md @@ -37,12 +37,12 @@ context manager. ## Propagation -Per default `that-depends` will propagate tear-down to dependent providers. +By default, `that-depends` propagates tear-down to dependent providers. This means that if you have defined a provider `A` that is dependent on provider `B`, when calling `await B.tear_down()`, this will also execute `await A.tear_down()`. -**For example:** +For example: ```python class MyContainer(BaseContainer): @@ -59,7 +59,7 @@ a_new = await MyContainer.A() assert a_new != a ``` -If you do not wish to propagate tear-down simply call `tear_down(propagate=False)` or `tear_down_sync(propagate=False)`. +To skip propagation, call `tear_down(propagate=False)` or `tear_down_sync(propagate=False)`. --- @@ -69,7 +69,7 @@ If you need to call tear-down from a sync context you can use the `tear_down_syn keep in mind that because dependent resources might be async, this will fail to correctly finalize these async resources. -Per default this will raise a `CannotTearDownSyncError`: +By default, this raises a `CannotTearDownSyncError`: ```python async def async_creator(val: float) -> typing.AsyncIterator[float]: diff --git a/docs/introduction/type-based-injection.md b/docs/introduction/type-based-injection.md index d57d0727..1e0977e7 100644 --- a/docs/introduction/type-based-injection.md +++ b/docs/introduction/type-based-injection.md @@ -3,7 +3,7 @@ `that-depends` also supports dependency injection without explicitly referencing the provider of the dependency. -## Quick Start +## Quick start In order to make use of this, you need to bind providers to the type they will provide: ```python @@ -11,7 +11,7 @@ class Container(BaseContainer): my_provider = providers.Factory(lambda: random.random()).bind(float) ``` -Then provide inject into your functions or generators: +Then inject into your functions or generators: === "Option 1" ```python @@ -29,7 +29,7 @@ Then provide inject into your functions or generators: ## Default bind -Per default, providers will **not** be bound to any type, even if your creator +By default, providers are **not** bound to any type, even if your creator function has type hints. So make sure to always bind your providers. You can also bind multiple types to the same provider: @@ -40,7 +40,7 @@ class Container(BaseContainer): ## Contravariant binding -Per default injection will be invariant to the bound types. +By default, injection is invariant to the bound types. If you wish to enable contravariance for your bound types you can do so by setting `#!python contravariant=True` in the `bind` method: diff --git a/docs/migration/v2.md b/docs/migration/v2.md index d268713e..12b4514b 100644 --- a/docs/migration/v2.md +++ b/docs/migration/v2.md @@ -1,6 +1,6 @@ # Migrating from 1.\* to 2.\* -## How to Read This Guide +## How to read this guide This guide is intended to help you migrate existing functionality from `that-depends` version `1.*` to `2.*`. The goal is to enable you to migrate as quickly as possible while making only the minimal necessary changes to your codebase. @@ -9,13 +9,12 @@ If you want to learn more about the new features introduced in `2.*`, please ref --- -## Deprecated or Removed Features +## Deprecated or removed features -### **`BaseContainer.init_async_resources()` removed** +### `BaseContainer.init_async_resources()` removed The method `BaseContainer.init_async_resources()` has been removed. Use `BaseContainer.init_resources()` instead. - **Example:** - If you are using containers, your setup might look like this: + For example, if you are using containers, your setup might look like this: ```python from that_depends import BaseContainer @@ -34,11 +33,10 @@ If you want to learn more about the new features introduced in `2.*`, please ref ``` --- -### **`that_depends.providers.AsyncResource` removed** +### `that_depends.providers.AsyncResource` removed The `AsyncResource` class has been removed. Use `providers.Resource` instead. - **Example:** Replace all instances of: ```python from that_depends.providers import AsyncResource @@ -52,12 +50,11 @@ If you want to learn more about the new features introduced in `2.*`, please ref --- -### **`BaseContainer` and its subclasses are no longer dynamic.** +### `BaseContainer` and its subclasses are no longer dynamic In `1.*`, you could define a container class and add providers to it dynamically. This feature has been removed in `2.*`. You must now define all providers in the container class itself. - **Example:** - In `1.*`, you could define a container and then dynamically set providers: + For example, in `1.*` you could define a container and then dynamically set providers: ```python from that_depends import BaseContainer @@ -76,7 +73,7 @@ If you want to learn more about the new features introduced in `2.*`, please ref ## Changes in the API -### **`container_context()` now requires a keyword argument for initial Context** +### `container_context()` now requires a keyword argument for initial context Previously, a global context could be initialized by passing a dictionary to the `container_context()` context manager: ```python @@ -95,7 +92,7 @@ async with container_context(global_context=my_global_context): --- -### **Context reset behavior changed in `container_context()`** +### Context reset behavior changed in `container_context()` Previously, calling `container_context(my_global_context)` would: - Set the global context to `my_global_context`, allowing values to be resolved using `fetch_context_item()`. This behavior remains the same. @@ -108,7 +105,7 @@ async with container_context(global_context=my_global_context, reset_all_contain assert fetch_context_item("some_key") == "some_value" ``` -> **Note:** `reset_all_containers=True` only reinitializes the context for `ContextResource` instances defined within containers (i.e., classes inheriting from `BaseContainer`). If you also need to reset contexts for resources defined outside containers, you must handle these explicitly. See the [ContextResource documentation](../providers/context-resources.md) for more details. +> `reset_all_containers=True` only reinitializes the context for `ContextResource` instances defined within containers (i.e., classes inheriting from `BaseContainer`). If you also need to reset contexts for resources defined outside containers, you must handle these explicitly. See the [ContextResource documentation](../providers/context-resources.md) for more details. Additionally, calling `container_context()` without any arguments will no longer reset the `global_context`, if you want to drop the `global_context` set `preserve_global_context=False`: @@ -120,11 +117,11 @@ async with container_context(preserve_global_context=False): --- -### **Container classes now require you to define `default_scope`** +### Container classes now require you to define `default_scope` In `2.*`, you must define the `default_scope` attribute in your container classes if you plan to define any `ContextResource` providers in that class. This attribute specifies the default scope for all `ContextResource` providers defined within the container. -**Example:** +For example: ```python from that_depends import BaseContainer, providers @@ -137,7 +134,7 @@ Setting the value of `default_context = None` maintains the same behaviours as i --- -## Potential Issues with `container_context()` +## Potential issues with `container_context()` If you have migrated the functionality as described above but still experience issues managing context resources, it might be due to improperly initializing resources when entering `container_context()`. @@ -158,11 +155,11 @@ To resolve such issues in `2.*`, consider the following suggestions: --- -### **Pass explicit arguments to `DIContextMiddleware`** +### Pass explicit arguments to `DIContextMiddleware` If you are using `DIContextMiddleware` with your ASGI application, you can now pass additional arguments. -**Example with `FastAPI`:** +For example, with `FastAPI`: ```python import fastapi @@ -180,10 +177,10 @@ This middleware will automatically initialize the context for the provided resou --- -### **Avoid entering `container_context()` without arguments** +### Avoid entering `container_context()` without arguments Pass all resources supporting context initialization (e.g., `providers.ContextResource` instances and `BaseContainer` subclasses) explicitly. -**Example:** +For example: ```python from that_depends import container_context @@ -201,6 +198,6 @@ Explicit initialization of container context is recommended to prevent unexpecte --- -## Further Help +## Further help If you continue to experience issues during migration, consider creating a [discussion](https://github.com/modern-python/that-depends/discussions) or opening an [issue](https://github.com/modern-python/that-depends/issues). diff --git a/docs/migration/v3.md b/docs/migration/v3.md index e749b86f..f447c65d 100644 --- a/docs/migration/v3.md +++ b/docs/migration/v3.md @@ -1,6 +1,6 @@ # Migrating from 2.\* to 3.\* -## How to Read This Guide +## How to read this guide This guide is intended to help you migrate existing functionality from `that-depends` version `2.*` to `3.*`. The goal is to enable you to migrate as quickly as possible while making only the minimal necessary changes to your codebase. @@ -9,9 +9,9 @@ If you want to learn more about the new features introduced in `3.*`, please ref --- -## Deprecated or Removed Features +## Deprecated or removed features -### **`container_context()`** can no longer be initialized without arguments. +### `container_context()` can no longer be initialized without arguments Previously the following code would reset the context for all all providers in all containers: ```python @@ -30,7 +30,7 @@ async with container_context(MyContainer_1, MyContainer_2, ...): --- -### **`container_context()`** no longer accepts `reset_all_containers` keyword argument. +### `container_context()` no longer accepts the `reset_all_containers` keyword argument You can no longer reset the context for all containers by using the `container_context` context manager. Previously you could have done something like this: @@ -47,7 +47,7 @@ async with container_context(MyContainer_1, MyContainer_2, ...): --- -### **`@inject(scope=...)`** no longer enters the scope. +### `@inject(scope=...)` no longer enters the scope The `@inject` decorator no longer enters the scope specified in the `scope` argument. @@ -71,7 +71,7 @@ For further details, please refer to the [scopes documentation](../introduction/ ## Changes in the API -### Changes to naming of methods. +### Changes to naming of methods You can expect the default implementation of provider and container methods to be async. This means that methods **not** explicitly ending with `_sync` are normally async. @@ -88,7 +88,7 @@ Other examples of similar changes include: --- -### Tear down propagation enabled per default. +### Tear-down propagation is enabled by default Tear down is now propagated to all dependencies by default. @@ -104,7 +104,7 @@ For more details regarding tear-down propagation see the [documentation](../intr --- -### Overriding is now async per default. +### Overriding is now async by default As mentioned [above](#changes-to-naming-of-methods), `.override()` methods are now async per default. @@ -122,10 +122,10 @@ This is also the case for the following methods: - `.override_context()` -> `.override_context_sync()` - `.reset_override()` -> `.reset_override_sync()` -> **Note:** Overrides now support tear-down, read more in the [documentation](../testing/provider-overriding.md) +> Overrides now support tear-down. Read more in the [documentation](../testing/provider-overriding.md) --- -## Further Help +## Further help If you continue to experience issues during migration, consider creating a [discussion](https://github.com/modern-python/that-depends/discussions) or opening an [issue](https://github.com/modern-python/that-depends/issues). diff --git a/docs/migration/v4.md b/docs/migration/v4.md index 95038036..0647b990 100644 --- a/docs/migration/v4.md +++ b/docs/migration/v4.md @@ -1,6 +1,6 @@ # Migrating from 3.\* to 4.\* -## How to Read This Guide +## How to read this guide This guide is intended to help you migrate existing functionality from `that-depends` version `3.*` to `4.*`. The goal is to enable you to migrate as quickly as possible while making only the minimal necessary changes to your codebase. @@ -13,7 +13,7 @@ If you want to learn more about the new internals introduced in `4.*`, please re ## Changes in the API -### **Collection providers now return read-only container types** +### Collection providers now return read-only container types In `4.*`, collection providers no longer resolve to mutable built-in containers: @@ -31,9 +31,9 @@ mapping = dict(MyContainer.mapping.resolve_sync()) --- -## Behaviour-Preserving Migration Examples +## Behaviour-preserving migration examples -### **`providers.List(...)`** +### `providers.List(...)` Previously in `3.*`, code like this returned a `list`: @@ -60,7 +60,7 @@ items.append("new-item") --- -### **`providers.Dict(...)`** +### `providers.Dict(...)` Previously in `3.*`, code like this returned a mutable `dict`: @@ -87,6 +87,6 @@ mapping["extra"] = "value" --- -## Further Help +## Further help If you continue to experience issues during migration, consider creating a [discussion](https://github.com/modern-python/that-depends/discussions) or opening an [issue](https://github.com/modern-python/that-depends/issues). diff --git a/docs/providers/context-resources.md b/docs/providers/context-resources.md index 497bc025..46c3bc0e 100644 --- a/docs/providers/context-resources.md +++ b/docs/providers/context-resources.md @@ -1,4 +1,4 @@ -# Context-Dependent Resources +# Context-dependent resources `that-depends` provides a way to manage two types of contexts: @@ -12,11 +12,11 @@ To interact with both types of contexts, there are two separate interfaces: and `ContextResource` providers implement. --- -## Quick Start +## Quick start You must initialize a context before you can resolve a `ContextResource`. -**Setup:** +Start with a container that defines `ContextResource` providers: ```python import typing @@ -55,7 +55,7 @@ await func() # returns "async resource" This will initialize a new context for `async_resource` each time `func` is called. --- -## Global Context +## Global context A global context can be initialized by using the `container_context` context manager. @@ -99,7 +99,7 @@ async with container_context(global_context={"key": "value"}): fetch_context_item("key") # returns 'value' ``` -Additionally, you can use the `global_context` argument in combination with `preserve_global_context` to +You can also use the `global_context` argument in combination with `preserve_global_context` to extend the global context. This merges the two contexts together by key, with the new `global_context` taking precedence: ```python async with container_context(global_context={"key_1": "value_1", "key_2": "value_2"}): @@ -128,7 +128,7 @@ with container_context(global_context={"key": 4}): --- -## Context Resources +## Context resources To resolve a `ContextResource`, you must first initialize a new context for that resource. ```python @@ -173,7 +173,7 @@ async def my_func(): ### More granular context initialization -If you do not wish to simply reinitialize the context for all containers, you can initialize a context for a specific container: +Instead of reinitializing the context for all containers, you can initialize a context for a specific container: ```python # this will init a new context for all ContextResources in MyContainer and any connected containers. async with container_context(MyContainer): @@ -189,7 +189,7 @@ async with container_context(MyContainer.async_resource): It is not necessary to use `container_context()` to do this. Instead, you can use the `SupportsContext` protocol described [here](#quick-reference). -### Context Hierarchy +### Context hierarchy Resources are cached in the context after their first resolution. They are torn down when `container_context` exits: @@ -226,8 +226,8 @@ Each time you call `await insert_into_database()`, a new instance of `session` w | Reset all resources in a container | `async with container_context(my_container):` | `async with my_container.context_async():` | `@my_container.context` | | Reset all sync resources in a container | `with container_context(my_container):` | `with my_container.context_sync():` | `@my_container.context` | -> **Note:** the `context()` wrapper is technically not part of the `SupportsContext` API, however all classes which -> implement this `SupportsContext` also implement this method. +> The `context()` wrapper is technically not part of the `SupportsContext` API, but all classes that +> implement `SupportsContext` also implement this method. --- ## Middleware @@ -236,7 +236,7 @@ For `ASGI` applications, `that-depends` provides the `DIContextMiddleware` to ma The `DIContextMiddleware` accepts containers and resources as arguments and automatically initializes the context for the provided resources when an endpoint is called. -**Example with `FastAPI`:** +For example, with `FastAPI`: ```python import fastapi from that_depends.providers import DIContextMiddleware, ContextResource diff --git a/docs/providers/factories.md b/docs/providers/factories.md index f3904b3b..6a9ef539 100644 --- a/docs/providers/factories.md +++ b/docs/providers/factories.md @@ -39,20 +39,20 @@ class DIContainer(BaseContainer): > Note: If you have a class that has dependencies which need to be resolved asynchronously, you can use `AsyncFactory` to create instances of that class. The factory will handle the async resolution of dependencies. -## Retrieving provider as a Callable +## Retrieving a provider as a callable When you use a factory‑based provider such as `Factory` (for sync logic) or `AsyncFactory` (for async logic), the resulting provider instance has two special properties: -- **`.provider`** — returns an *async callable* that, when awaited, resolves the resource. -- **`.provider_sync`** — returns a *sync callable* that, when called, resolves the resource. +- `.provider` returns an *async callable* that, when awaited, resolves the resource. +- `.provider_sync` returns a *sync callable* that, when called, resolves the resource. -You can think of these as no-argument functions that produce the resource you defined—similar to calling `resolve()` or `resolve_sync()` directly, but in a more convenient form when you want a standalone function handle. +You can think of these as no-argument functions that produce the resource you defined. They behave like calling `resolve()` or `resolve_sync()` directly and are convenient when you want a standalone function handle. --- -### Basic Usage +### Basic usage -#### Defining Providers in a Container +#### Defining providers in a container Suppose you have a `BaseContainer` subclass that defines both a sync and an async resource: @@ -79,11 +79,11 @@ Here, `sync_message` is a `Factory` which calls a plain function, while `async_m --- -#### Resolving Resources via `.provider` and `.provider_sync` +#### Resolving resources via `.provider` and `.provider_sync` The `.provider` property gives you an *async function* to await, and `.provider_sync` gives you a *synchronous* callable. They effectively wrap `.resolve()` and `.resolve_sync()`. -**Synchronous Resolution** +##### Synchronous resolution ```python # In a synchronous function or interactive session @@ -94,7 +94,7 @@ Hello from sync provider! Here, `provider_sync` is a no-argument function that immediately returns the resolved value. -**Asynchronous Resolution** +##### Asynchronous resolution ```python import asyncio @@ -111,7 +111,7 @@ Within an async function, `MyContainer.async_message.provider` gives a no-argume --- -### Passing the Provider Function Around +### Passing the provider function around Sometimes you may want to store or pass around the provider function itself (rather than resolving it immediately): @@ -134,7 +134,7 @@ Because `.provider_sync` is just a callable returning your dependency, it can be --- -### Example: Using Factories with Parameters +### Example: using factories with parameters `Factory` and `AsyncFactory` can accept dependencies (including other providers) as parameters: @@ -157,7 +157,7 @@ Under the hood, `greeting` calls `greet` with the result of `name.resolve_sync() --- -### Context Considerations +### Context considerations If your providers use `ContextResource` or require a named scope (for instance, `REQUEST`), you need to wrap your resolves in a context manager: diff --git a/docs/providers/object.md b/docs/providers/object.md index 14af879d..42fc0fdc 100644 --- a/docs/providers/object.md +++ b/docs/providers/object.md @@ -1,6 +1,6 @@ # Object -Object provider returns an object “as is”. +Object provider returns an object "as is". ```python from that_depends import BaseContainer, providers diff --git a/docs/providers/resources.md b/docs/providers/resources.md index 04166ebf..464a6ff6 100644 --- a/docs/providers/resources.md +++ b/docs/providers/resources.md @@ -1,4 +1,4 @@ -# Resource Provider +# Resource provider A `Resource` resolves once, caches the instance, and runs teardown logic from a generator or context manager. A plain `Singleton` has no teardown step. @@ -6,21 +6,18 @@ The creator can be a generator or async generator function, with teardown after or a class that implements `typing.ContextManager` or `typing.AsyncContextManager`. A `Resource` does not automatically integrate with `container_context`. -This makes `Resource` ideal for dependencies that need: - -1. A **single creation** step, -2. A **single finalization** step, -3. **Thread/async safety**—all consumers receive the same resource object, and concurrency is handled. +Use `Resource` for dependencies that need a single creation step, a single finalization step, and thread and async safety. +All consumers receive the same resource object, and concurrency is handled. --- -## How It Works +## How it works -### Defining a Sync or Async Resource +### Defining a sync or async resource -You can define your creation logic as either a **generator** or a **context manager** class (sync or async). +You can define your creation logic as either a generator or a context manager class (sync or async). -**Synchronous generator** example: +A synchronous generator: ```python import typing @@ -33,7 +30,7 @@ def create_sync_resource() -> typing.Iterator[str]: print("Tearing down sync resource") ``` -**Asynchronous generator** example: +An asynchronous generator: ```python import typing @@ -59,9 +56,9 @@ class MyContainer(BaseContainer): --- -## Resolving and Teardown +## Resolving and teardown -Once defined, you can explicitly **resolve** the resource and **tear it down**: +Once defined, you can explicitly resolve the resource and tear it down: ```python # Synchronous resource usage @@ -82,17 +79,17 @@ async def main(): asyncio.run(main()) ``` -- **`resolve_sync()`** or **`resolve()`**: Creates (if needed) and returns the resource instance. -- **`tear_down_sync()`** or **`tear_down()`**: Closes/cleans up the resource (triggering your `finally` block or exiting the context manager) and resets the cached instance to `None`. A subsequent resolve call will then recreate it. +- `resolve_sync()` or `resolve()` creates the resource instance if needed and returns it. +- `tear_down_sync()` or `tear_down()` cleans up the resource (running your `finally` block or exiting the context manager) and resets the cached instance to `None`. The next resolve call recreates it. --- -## Concurrency Safety +## Concurrency safety -`Resource` is **safe** to use under **threading** and **asyncio** concurrency. Internally, a lock ensures only one resource instance is created per container: +`Resource` is safe to use under threading and asyncio concurrency. Internally, a lock ensures only one resource instance is created per container: -- Multiple threads calling `resolve_sync()` simultaneously will produce a **single** instance for that container. -- Multiple coroutines calling `resolve()` simultaneously will likewise produce **only one** instance for that container in an async environment. +- Multiple threads calling `resolve_sync()` simultaneously will produce a single instance for that container. +- Multiple coroutines calling `resolve()` simultaneously will likewise produce only one instance for that container in an async environment. ```python # Even if multiple coroutines call resolve in parallel, @@ -106,9 +103,9 @@ MyContainer.sync_resource.resolve_sync() --- -## Using Context Managers Directly +## Using context managers directly -If your resource is a standard **context manager** or **async context manager** class, `Resource` will handle entering and exiting it under the hood. For example: +If your resource is a standard context manager or async context manager class, `Resource` will handle entering and exiting it under the hood. For example: ```python import typing diff --git a/docs/providers/selector.md b/docs/providers/selector.md index 9fc4a66c..5134a091 100644 --- a/docs/providers/selector.md +++ b/docs/providers/selector.md @@ -1,6 +1,6 @@ # Selector -The Selector provider chooses between provider based on a key. This resolves into a single dependency. +The Selector provider chooses between providers based on a key. This resolves into a single dependency. The selector can be a callable that returns a string, an instance of `AbstractProvider` or a string. @@ -50,7 +50,7 @@ class DIContainer(BaseContainer): ## Fixed string selectors -This can be useful for quickly testing. +This can be useful for quick testing. ```python class DIContainer(BaseContainer): diff --git a/docs/providers/singleton.md b/docs/providers/singleton.md index 81719915..77359dc1 100644 --- a/docs/providers/singleton.md +++ b/docs/providers/singleton.md @@ -1,8 +1,8 @@ -# Singleton Provider +# Singleton provider -A **Singleton** provider creates its instance once and caches it for all future injections or resolutions. When the instance is first requested (via `resolve_sync()` or `resolve()`), the underlying factory is called. On subsequent calls, the cached instance is returned without calling the factory again. +A `Singleton` provider creates its instance once and caches it for all future injections or resolutions. When the instance is first requested (via `resolve_sync()` or `resolve()`), the underlying factory is called. On subsequent calls, the cached instance is returned without calling the factory again. -## How it Works +## How it works ```python import random @@ -37,7 +37,7 @@ async def with_singleton(number: float = Provide[MyContainer.singleton]): ... ``` -### Teardown Support +### Teardown support If you need to reset the singleton (for example, in tests or at application shutdown), you can call: ```python await MyContainer.singleton.tear_down() @@ -49,15 +49,11 @@ For further details refer to the [teardown documentation](../introduction/tear-d --- -## Concurrency Safety +## Concurrency safety -`Singleton` is **thread-safe** and **async-safe**: - -1. **Async Concurrency** - If multiple coroutines call `resolve()` concurrently, the factory function is guaranteed to be called only once. All callers receive the same cached instance. - -2. **Thread Concurrency** - If multiple threads call `resolve_sync()` at the same time, the factory is only called once. All threads receive the same cached instance. +`Singleton` is thread-safe and async-safe. +If multiple coroutines call `resolve()` concurrently, the factory function is guaranteed to be called only once, and all callers receive the same cached instance. +If multiple threads call `resolve_sync()` at the same time, the factory is also called only once, and all threads receive the same cached instance. ```python import threading @@ -87,9 +83,9 @@ for t in threads: --- -## ThreadLocalSingleton Provider +## ThreadLocalSingleton provider -If you want each *thread* to have its own, separately cached instance, use **ThreadLocalSingleton**. This provider creates a new instance per thread and reuses that instance on subsequent calls *within the same thread*. +If you want each *thread* to have its own, separately cached instance, use `ThreadLocalSingleton`. This provider creates a new instance per thread and reuses that instance on subsequent calls *within the same thread*. ```python import random @@ -123,13 +119,13 @@ thread2.start() # thread1 and thread2 each get a different cached value ``` -You can still use `.resolve()` with `ThreadLocalSingleton`, which will also maintain isolation per thread. However, note that this does *not* isolate instances per asynchronous Task – only per OS thread. +You can still use `.resolve()` with `ThreadLocalSingleton`, which also keeps instances isolated per thread. The isolation is per OS thread, *not* per asynchronous task. --- ## Example with `pydantic-settings` -Consider a scenario where your application configuration is defined via [**pydantic-settings**](https://docs.pydantic.dev/latest/concepts/pydantic_settings/). Often, you only want to parse this configuration (e.g., from environment variables) once, then reuse it throughout the application. +Consider a scenario where your application configuration is defined via [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/). Often, you only want to parse this configuration (e.g., from environment variables) once, then reuse it throughout the application. ```python from pydantic_settings import BaseSettings @@ -147,9 +143,9 @@ class Settings(BaseSettings): db: DatabaseConfig = DatabaseConfig() ``` -### Defining the Container +### Defining the container -Below, we define a container with a **Singleton** provider for our settings. We also define a separate async factory that connects to the database using those settings. +Below, we define a container with a `Singleton` provider for our settings. We also define a separate async factory that connects to the database using those settings. ```python from that_depends import BaseContainer, providers @@ -171,7 +167,7 @@ class MyContainer(BaseContainer): ) ``` -### Injecting or Resolving in Code +### Injecting or resolving in code You can now inject these values directly into your functions with the `@inject` decorator: diff --git a/docs/providers/state.md b/docs/providers/state.md index 98d75792..ec4ff4a2 100644 --- a/docs/providers/state.md +++ b/docs/providers/state.md @@ -7,7 +7,7 @@ It is useful when you want to pass a value into your Container that other provid ## Creating a state provider -The `State` provider does not accept any arguments when it created. +The `State` provider does not accept any arguments when it is created. ```python from that_depends import BaseContainer, providers class Container(BaseContainer): @@ -31,12 +31,12 @@ class Container(BaseContainer): ``` -> Note: If you try to resolve a `State` provider without initializing it first it will raise an `StateNotInitializedError`. +> Note: If you try to resolve a `State` provider without initializing it first it will raise a `StateNotInitializedError`. ## Nested state -The `State` provider will always resolve the last initialize value. +The `State` provider will always resolve the last initialized value. ```python with Container.my_state.init(1): diff --git a/docs/testing/provider-overriding.md b/docs/testing/provider-overriding.md index 30b41ae3..19b82c8b 100644 --- a/docs/testing/provider-overriding.md +++ b/docs/testing/provider-overriding.md @@ -1,7 +1,7 @@ # Provider overriding Any provider in a container can be overridden, for example with a stub in tests. -**Override affects all providers that use the overridden provider (_see example_)**. +Overriding a provider affects all providers that use it, as the example below shows. ## Example @@ -105,7 +105,7 @@ def main(): --- ## Using with Litestar In order to be able to inject dependencies of any type instead of existing objects, -we need to **change the typing** for the injected parameter as follows: +we need to change the typing of the injected parameter as follows: ```python3 import typing @@ -156,7 +156,7 @@ router = Router( app = Litestar(route_handlers=[router]) ``` -Now we are ready to write tests with **overriding** and this will work with **any types**: +Tests can then override the dependency with a value of any type: ```python3 def test_litestar_endpoint_with_overriding() -> None: