PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /usr/lib/imh-dnskeyapi/venv/lib/python3.13/site-packages/blinker
Viewing File: /usr/lib/imh-dnskeyapi/venv/lib/python3.13/site-packages/blinker/base.py
from __future__ import annotations import collections.abc as c import sys import typing as t import weakref from collections import defaultdict from contextlib import contextmanager from functools import cached_property from inspect import iscoroutinefunction from ._utilities import make_id from ._utilities import make_ref from ._utilities import Symbol F = t.TypeVar("F", bound=c.Callable[..., t.Any]) ANY = Symbol("ANY") """Symbol for "any sender".""" ANY_ID = 0 class Signal: """A notification emitter. :param doc: The docstring for the signal. """ ANY = ANY """An alias for the :data:`~blinker.ANY` sender symbol.""" set_class: type[set[t.Any]] = set """The set class to use for tracking connected receivers and senders. Python's ``set`` is unordered. If receivers must be dispatched in the order they were connected, an ordered set implementation can be used. .. versionadded:: 1.7 """ @cached_property def receiver_connected(self) -> Signal: """Emitted at the end of each :meth:`connect` call. The signal sender is the signal instance, and the :meth:`connect` arguments are passed through: ``receiver``, ``sender``, and ``weak``. .. versionadded:: 1.2 """ return Signal(doc="Emitted after a receiver connects.") @cached_property def receiver_disconnected(self) -> Signal: """Emitted at the end of each :meth:`disconnect` call. The sender is the signal instance, and the :meth:`disconnect` arguments are passed through: ``receiver`` and ``sender``. This signal is emitted **only** when :meth:`disconnect` is called explicitly. This signal cannot be emitted by an automatic disconnect when a weakly referenced receiver or sender goes out of scope, as the instance is no longer be available to be used as the sender for this signal. An alternative approach is available by subscribing to :attr:`receiver_connected` and setting up a custom weakref cleanup callback on weak receivers and senders. .. versionadded:: 1.2 """ return Signal(doc="Emitted after a receiver disconnects.") def __init__(self, doc: str | None = None) -> None: if doc: self.__doc__ = doc self.receivers: dict[ t.Any, weakref.ref[c.Callable[..., t.Any]] | c.Callable[..., t.Any] ] = {} """The map of connected receivers. Useful to quickly check if any receivers are connected to the signal: ``if s.receivers:``. The structure and data is not part of the public API, but checking its boolean value is. """ self.is_muted: bool = False self._by_receiver: dict[t.Any, set[t.Any]] = defaultdict(self.set_class) self._by_sender: dict[t.Any, set[t.Any]] = defaultdict(self.set_class) self._weak_senders: dict[t.Any, weakref.ref[t.Any]] = {} def connect(self, receiver: F, sender: t.Any = ANY, weak: bool = True) -> F: """Connect ``receiver`` to be called when the signal is sent by ``sender``. :param receiver: The callable to call when :meth:`send` is called with the given ``sender``, passing ``sender`` as a positional argument along with any extra keyword arguments. :param sender: Any object or :data:`ANY`. ``receiver`` will only be called when :meth:`send` is called with this sender. If ``ANY``, the receiver will be called for any sender. A receiver may be connected to multiple senders by calling :meth:`connect` multiple times. :param weak: Track the receiver with a :mod:`weakref`. The receiver will be automatically disconnected when it is garbage collected. When connecting a receiver defined within a function, set to ``False``, otherwise it will be disconnected when the function scope ends. """ receiver_id = make_id(receiver) sender_id = ANY_ID if sender is ANY else make_id(sender) if weak: self.receivers[receiver_id] = make_ref( receiver, self._make_cleanup_receiver(receiver_id) ) else: self.receivers[receiver_id] = receiver self._by_sender[sender_id].add(receiver_id) self._by_receiver[receiver_id].add(sender_id) if sender is not ANY and sender_id not in self._weak_senders: # store a cleanup for weakref-able senders try: self._weak_senders[sender_id] = make_ref( sender, self._make_cleanup_sender(sender_id) ) except TypeError: pass if "receiver_connected" in self.__dict__ and self.receiver_connected.receivers: try: self.receiver_connected.send( self, receiver=receiver, sender=sender, weak=weak ) except TypeError: # TODO no explanation or test for this self.disconnect(receiver, sender) raise return receiver def connect_via(self, sender: t.Any, weak: bool = False) -> c.Callable[[F], F]: """Connect the decorated function to be called when the signal is sent by ``sender``. The decorated function will be called when :meth:`send` is called with the given ``sender``, passing ``sender`` as a positional argument along with any extra keyword arguments. :param sender: Any object or :data:`ANY`. ``receiver`` will only be called when :meth:`send` is called with this sender. If ``ANY``, the receiver will be called for any sender. A receiver may be connected to multiple senders by calling :meth:`connect` multiple times. :param weak: Track the receiver with a :mod:`weakref`. The receiver will be automatically disconnected when it is garbage collected. When connecting a receiver defined within a function, set to ``False``, otherwise it will be disconnected when the function scope ends.= .. versionadded:: 1.1 """ def decorator(fn: F) -> F: self.connect(fn, sender, weak) return fn return decorator @contextmanager def connected_to( self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY ) -> c.Generator[None, None, None]: """A context manager that temporarily connects ``receiver`` to the signal while a ``with`` block executes. When the block exits, the receiver is disconnected. Useful for tests. :param receiver: The callable to call when :meth:`send` is called with the given ``sender``, passing ``sender`` as a positional argument along with any extra keyword arguments. :param sender: Any object or :data:`ANY`. ``receiver`` will only be called when :meth:`send` is called with this sender. If ``ANY``, the receiver will be called for any sender. .. versionadded:: 1.1 """ self.connect(receiver, sender=sender, weak=False) try: yield None finally: self.disconnect(receiver) @contextmanager def muted(self) -> c.Generator[None, None, None]: """A context manager that temporarily disables the signal. No receivers will be called if the signal is sent, until the ``with`` block exits. Useful for tests. """ self.is_muted = True try: yield None finally: self.is_muted = False def send( self, sender: t.Any | None = None, /, *, _async_wrapper: c.Callable[ [c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]], c.Callable[..., t.Any] ] | None = None, **kwargs: t.Any, ) -> list[tuple[c.Callable[..., t.Any], t.Any]]: """Call all receivers that are connected to the given ``sender`` or :data:`ANY`. Each receiver is called with ``sender`` as a positional argument along with any extra keyword arguments. Return a list of ``(receiver, return value)`` tuples. The order receivers are called is undefined, but can be influenced by setting :attr:`set_class`. If a receiver raises an exception, that exception will propagate up. This makes debugging straightforward, with an assumption that correctly implemented receivers will not raise. :param sender: Call receivers connected to this sender, in addition to those connected to :data:`ANY`. :param _async_wrapper: Will be called on any receivers that are async coroutines to turn them into sync callables. For example, could run the receiver with an event loop. :param kwargs: Extra keyword arguments to pass to each receiver. .. versionchanged:: 1.7 Added the ``_async_wrapper`` argument. """ if self.is_muted: return [] results = [] for receiver in self.receivers_for(sender): if iscoroutinefunction(receiver): if _async_wrapper is None: raise RuntimeError("Cannot send to a coroutine function.") result = _async_wrapper(receiver)(sender, **kwargs) else: result = receiver(sender, **kwargs) results.append((receiver, result)) return results async def send_async( self, sender: t.Any | None = None, /, *, _sync_wrapper: c.Callable[ [c.Callable[..., t.Any]], c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]] ] | None = None, **kwargs: t.Any, ) -> list[tuple[c.Callable[..., t.Any], t.Any]]: """Await all receivers that are connected to the given ``sender`` or :data:`ANY`. Each receiver is called with ``sender`` as a positional argument along with any extra keyword arguments. Return a list of ``(receiver, return value)`` tuples. The order receivers are called is undefined, but can be influenced by setting :attr:`set_class`. If a receiver raises an exception, that exception will propagate up. This makes debugging straightforward, with an assumption that correctly implemented receivers will not raise. :param sender: Call receivers connected to this sender, in addition to those connected to :data:`ANY`. :param _sync_wrapper: Will be called on any receivers that are sync callables to turn them into async coroutines. For example, could call the receiver in a thread. :param kwargs: Extra keyword arguments to pass to each receiver. .. versionadded:: 1.7 """ if self.is_muted: return [] results = [] for receiver in self.receivers_for(sender): if not iscoroutinefunction(receiver): if _sync_wrapper is None: raise RuntimeError("Cannot send to a non-coroutine function.") result = await _sync_wrapper(receiver)(sender, **kwargs) else: result = await receiver(sender, **kwargs) results.append((receiver, result)) return results def has_receivers_for(self, sender: t.Any) -> bool: """Check if there is at least one receiver that will be called with the given ``sender``. A receiver connected to :data:`ANY` will always be called, regardless of sender. Does not check if weakly referenced receivers are still live. See :meth:`receivers_for` for a stronger search. :param sender: Check for receivers connected to this sender, in addition to those connected to :data:`ANY`. """ if not self.receivers: return False if self._by_sender[ANY_ID]: return True if sender is ANY: return False return make_id(sender) in self._by_sender def receivers_for( self, sender: t.Any ) -> c.Generator[c.Callable[..., t.Any], None, None]: """Yield each receiver to be called for ``sender``, in addition to those to be called for :data:`ANY`. Weakly referenced receivers that are not live will be disconnected and skipped. :param sender: Yield receivers connected to this sender, in addition to those connected to :data:`ANY`. """ # TODO: test receivers_for(ANY) if not self.receivers: return sender_id = make_id(sender) if sender_id in self._by_sender: ids = self._by_sender[ANY_ID] | self._by_sender[sender_id] else: ids = self._by_sender[ANY_ID].copy() for receiver_id in ids: receiver = self.receivers.get(receiver_id) if receiver is None: continue if isinstance(receiver, weakref.ref): strong = receiver() if strong is None: self._disconnect(receiver_id, ANY_ID) continue yield strong else: yield receiver def disconnect(self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY) -> None: """Disconnect ``receiver`` from being called when the signal is sent by ``sender``. :param receiver: A connected receiver callable. :param sender: Disconnect from only this sender. By default, disconnect from all senders. """ sender_id: c.Hashable if sender is ANY: sender_id = ANY_ID else: sender_id = make_id(sender) receiver_id = make_id(receiver) self._disconnect(receiver_id, sender_id) if ( "receiver_disconnected" in self.__dict__ and self.receiver_disconnected.receivers ): self.receiver_disconnected.send(self, receiver=receiver, sender=sender) def _disconnect(self, receiver_id: c.Hashable, sender_id: c.Hashable) -> None: if sender_id == ANY_ID: if self._by_receiver.pop(receiver_id, None) is not None: for bucket in self._by_sender.values(): bucket.discard(receiver_id) self.receivers.pop(receiver_id, None) else: self._by_sender[sender_id].discard(receiver_id) self._by_receiver[receiver_id].discard(sender_id) def _make_cleanup_receiver( self, receiver_id: c.Hashable ) -> c.Callable[[weakref.ref[c.Callable[..., t.Any]]], None]: """Create a callback function to disconnect a weakly referenced receiver when it is garbage collected. """ def cleanup(ref: weakref.ref[c.Callable[..., t.Any]]) -> None: # If the interpreter is shutting down, disconnecting can result in a # weird ignored exception. Don't call it in that case. if not sys.is_finalizing(): self._disconnect(receiver_id, ANY_ID) return cleanup def _make_cleanup_sender( self, sender_id: c.Hashable ) -> c.Callable[[weakref.ref[t.Any]], None]: """Create a callback function to disconnect all receivers for a weakly referenced sender when it is garbage collected. """ assert sender_id != ANY_ID def cleanup(ref: weakref.ref[t.Any]) -> None: self._weak_senders.pop(sender_id, None) for receiver_id in self._by_sender.pop(sender_id, ()): self._by_receiver[receiver_id].discard(sender_id) return cleanup def _cleanup_bookkeeping(self) -> None: """Prune unused sender/receiver bookkeeping. Not threadsafe. Connecting & disconnecting leaves behind a small amount of bookkeeping data. Typical workloads using Blinker, for example in most web apps, Flask, CLI scripts, etc., are not adversely affected by this bookkeeping. With a long-running process performing dynamic signal routing with high volume, e.g. connecting to function closures, senders are all unique object instances. Doing all of this over and over may cause memory usage to grow due to extraneous bookkeeping. (An empty ``set`` for each stale sender/receiver pair.) This method will prune that bookkeeping away, with the caveat that such pruning is not threadsafe. The risk is that cleanup of a fully disconnected receiver/sender pair occurs while another thread is connecting that same pair. If you are in the highly dynamic, unique receiver/sender situation that has lead you to this method, that failure mode is perhaps not a big deal for you. """ for mapping in (self._by_sender, self._by_receiver): for ident, bucket in list(mapping.items()): if not bucket: mapping.pop(ident, None) def _clear_state(self) -> None: """Disconnect all receivers and senders. Useful for tests.""" self._weak_senders.clear() self.receivers.clear() self._by_sender.clear() self._by_receiver.clear() class NamedSignal(Signal): """A named generic notification emitter. The name is not used by the signal itself, but matches the key in the :class:`Namespace` that it belongs to. :param name: The name of the signal within the namespace. :param doc: The docstring for the signal. """ def __init__(self, name: str, doc: str | None = None) -> None: super().__init__(doc) #: The name of this signal. self.name: str = name def __repr__(self) -> str: base = super().__repr__() return f"{base[:-1]}; {self.name!r}>" # noqa: E702 class Namespace(dict[str, NamedSignal]): """A dict mapping names to signals.""" def signal(self, name: str, doc: str | None = None) -> NamedSignal: """Return the :class:`NamedSignal` for the given ``name``, creating it if required. Repeated calls with the same name return the same signal. :param name: The name of the signal. :param doc: The docstring of the signal. """ if name not in self: self[name] = NamedSignal(name, doc) return self[name] class _PNamespaceSignal(t.Protocol): def __call__(self, name: str, doc: str | None = None) -> NamedSignal: ... default_namespace: Namespace = Namespace() """A default :class:`Namespace` for creating named signals. :func:`signal` creates a :class:`NamedSignal` in this namespace. """ signal: _PNamespaceSignal = default_namespace.signal """Return a :class:`NamedSignal` in :data:`default_namespace` with the given ``name``, creating it if required. Repeated calls with the same name return the same signal. """