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: /opt/imh-python/lib/python3.9/site-packages/traitlets/config
Viewing File: /opt/imh-python/lib/python3.9/site-packages/traitlets/config/configurable.py
"""A base class for objects that are configurable.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import logging import typing as t from copy import deepcopy from textwrap import dedent from traitlets.traitlets import ( Any, Container, Dict, HasTraits, Instance, TraitType, default, observe, observe_compat, validate, ) from traitlets.utils import warnings from traitlets.utils.bunch import Bunch from traitlets.utils.text import indent, wrap_paragraphs from .loader import Config, DeferredConfig, LazyConfigValue, _is_section_key # ----------------------------------------------------------------------------- # Helper classes for Configurables # ----------------------------------------------------------------------------- if t.TYPE_CHECKING: LoggerType = t.Union[logging.Logger, logging.LoggerAdapter[t.Any]] else: LoggerType = t.Any class ConfigurableError(Exception): pass class MultipleInstanceError(ConfigurableError): pass # ----------------------------------------------------------------------------- # Configurable implementation # ----------------------------------------------------------------------------- class Configurable(HasTraits): config = Instance(Config, (), {}) parent = Instance("traitlets.config.configurable.Configurable", allow_none=True) def __init__(self, **kwargs: t.Any) -> None: """Create a configurable given a config config. Parameters ---------- config : Config If this is empty, default values are used. If config is a :class:`Config` instance, it will be used to configure the instance. parent : Configurable instance, optional The parent Configurable instance of this object. Notes ----- Subclasses of Configurable must call the :meth:`__init__` method of :class:`Configurable` *before* doing anything else and using :func:`super`:: class MyConfigurable(Configurable): def __init__(self, config=None): super(MyConfigurable, self).__init__(config=config) # Then any other code you need to finish initialization. This ensures that instances will be configured properly. """ parent = kwargs.pop("parent", None) if parent is not None: # config is implied from parent if kwargs.get("config", None) is None: kwargs["config"] = parent.config self.parent = parent config = kwargs.pop("config", None) # load kwarg traits, other than config super().__init__(**kwargs) # record traits set by config config_override_names = set() def notice_config_override(change: Bunch) -> None: """Record traits set by both config and kwargs. They will need to be overridden again after loading config. """ if change.name in kwargs: config_override_names.add(change.name) self.observe(notice_config_override) # load config if config is not None: # We used to deepcopy, but for now we are trying to just save # by reference. This *could* have side effects as all components # will share config. In fact, I did find such a side effect in # _config_changed below. If a config attribute value was a mutable type # all instances of a component were getting the same copy, effectively # making that a class attribute. # self.config = deepcopy(config) self.config = config else: # allow _config_default to return something self._load_config(self.config) self.unobserve(notice_config_override) for name in config_override_names: setattr(self, name, kwargs[name]) # ------------------------------------------------------------------------- # Static trait notifications # ------------------------------------------------------------------------- @classmethod def section_names(cls) -> list[str]: """return section names as a list""" return [ c.__name__ for c in reversed(cls.__mro__) if issubclass(c, Configurable) and issubclass(cls, c) ] def _find_my_config(self, cfg: Config) -> t.Any: """extract my config from a global Config object will construct a Config object of only the config values that apply to me based on my mro(), as well as those of my parent(s) if they exist. If I am Bar and my parent is Foo, and their parent is Tim, this will return merge following config sections, in this order:: [Bar, Foo.Bar, Tim.Foo.Bar] With the last item being the highest priority. """ cfgs = [cfg] if self.parent: cfgs.append(self.parent._find_my_config(cfg)) my_config = Config() for c in cfgs: for sname in self.section_names(): # Don't do a blind getattr as that would cause the config to # dynamically create the section with name Class.__name__. if c._has_section(sname): my_config.merge(c[sname]) return my_config def _load_config( self, cfg: Config, section_names: list[str] | None = None, traits: dict[str, TraitType[t.Any, t.Any]] | None = None, ) -> None: """load traits from a Config object""" if traits is None: traits = self.traits(config=True) if section_names is None: section_names = self.section_names() my_config = self._find_my_config(cfg) # hold trait notifications until after all config has been loaded with self.hold_trait_notifications(): for name, config_value in my_config.items(): if name in traits: if isinstance(config_value, LazyConfigValue): # ConfigValue is a wrapper for using append / update on containers # without having to copy the initial value initial = getattr(self, name) config_value = config_value.get_value(initial) elif isinstance(config_value, DeferredConfig): # DeferredConfig tends to come from CLI/environment variables config_value = config_value.get_value(traits[name]) # We have to do a deepcopy here if we don't deepcopy the entire # config object. If we don't, a mutable config_value will be # shared by all instances, effectively making it a class attribute. setattr(self, name, deepcopy(config_value)) elif not _is_section_key(name) and not isinstance(config_value, Config): from difflib import get_close_matches if isinstance(self, LoggingConfigurable): assert self.log is not None warn = self.log.warning else: def warn(msg: t.Any) -> None: return warnings.warn(msg, UserWarning, stacklevel=9) matches = get_close_matches(name, traits) msg = f"Config option `{name}` not recognized by `{self.__class__.__name__}`." if len(matches) == 1: msg += f" Did you mean `{matches[0]}`?" elif len(matches) >= 1: msg += " Did you mean one of: `{matches}`?".format( matches=", ".join(sorted(matches)) ) warn(msg) @observe("config") @observe_compat def _config_changed(self, change: Bunch) -> None: """Update all the class traits having ``config=True`` in metadata. For any class trait with a ``config`` metadata attribute that is ``True``, we update the trait with the value of the corresponding config entry. """ # Get all traits with a config metadata entry that is True traits = self.traits(config=True) # We auto-load config section for this class as well as any parent # classes that are Configurable subclasses. This starts with Configurable # and works down the mro loading the config for each section. section_names = self.section_names() self._load_config(change.new, traits=traits, section_names=section_names) def update_config(self, config: Config) -> None: """Update config and load the new values""" # traitlets prior to 4.2 created a copy of self.config in order to trigger change events. # Some projects (IPython < 5) relied upon one side effect of this, # that self.config prior to update_config was not modified in-place. # For backward-compatibility, we must ensure that self.config # is a new object and not modified in-place, # but config consumers should not rely on this behavior. self.config = deepcopy(self.config) # load config self._load_config(config) # merge it into self.config self.config.merge(config) # TODO: trigger change event if/when dict-update change events take place # DO NOT trigger full trait-change @classmethod def class_get_help(cls, inst: HasTraits | None = None) -> str: """Get the help string for this class in ReST format. If `inst` is given, its current trait values will be used in place of class defaults. """ assert inst is None or isinstance(inst, cls) final_help = [] base_classes = ", ".join(p.__name__ for p in cls.__bases__) final_help.append(f"{cls.__name__}({base_classes}) options") final_help.append(len(final_help[0]) * "-") for _, v in sorted(cls.class_traits(config=True).items()): help = cls.class_get_trait_help(v, inst) final_help.append(help) return "\n".join(final_help) @classmethod def class_get_trait_help( cls, trait: TraitType[t.Any, t.Any], inst: HasTraits | None = None, helptext: str | None = None, ) -> str: """Get the helptext string for a single trait. :param inst: If given, its current trait values will be used in place of the class default. :param helptext: If not given, uses the `help` attribute of the current trait. """ assert inst is None or isinstance(inst, cls) lines = [] header = f"--{cls.__name__}.{trait.name}" if isinstance(trait, (Container, Dict)): multiplicity = trait.metadata.get("multiplicity", "append") if isinstance(trait, Dict): sample_value = "<key-1>=<value-1>" else: sample_value = "<%s-item-1>" % trait.__class__.__name__.lower() if multiplicity == "append": header = f"{header}={sample_value}..." else: header = f"{header} {sample_value}..." else: header = f"{header}=<{trait.__class__.__name__}>" # header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__) lines.append(header) if helptext is None: helptext = trait.help if helptext != "": helptext = "\n".join(wrap_paragraphs(helptext, 76)) lines.append(indent(helptext)) if "Enum" in trait.__class__.__name__: # include Enum choices lines.append(indent("Choices: %s" % trait.info())) if inst is not None: lines.append(indent(f"Current: {getattr(inst, trait.name or '')!r}")) else: try: dvr = trait.default_value_repr() except Exception: dvr = None # ignore defaults we can't construct if dvr is not None: if len(dvr) > 64: dvr = dvr[:61] + "..." lines.append(indent("Default: %s" % dvr)) return "\n".join(lines) @classmethod def class_print_help(cls, inst: HasTraits | None = None) -> None: """Get the help string for a single trait and print it.""" print(cls.class_get_help(inst)) # noqa: T201 @classmethod def _defining_class( cls, trait: TraitType[t.Any, t.Any], classes: t.Sequence[type[HasTraits]] ) -> type[Configurable]: """Get the class that defines a trait For reducing redundant help output in config files. Returns the current class if: - the trait is defined on this class, or - the class where it is defined would not be in the config file Parameters ---------- trait : Trait The trait to look for classes : list The list of other classes to consider for redundancy. Will return `cls` even if it is not defined on `cls` if the defining class is not in `classes`. """ defining_cls = cls assert trait.name is not None for parent in cls.mro(): if ( issubclass(parent, Configurable) and parent in classes and parent.class_own_traits(config=True).get(trait.name, None) is trait ): defining_cls = parent return defining_cls @classmethod def class_config_section(cls, classes: t.Sequence[type[HasTraits]] | None = None) -> str: """Get the config section for this class. Parameters ---------- classes : list, optional The list of other classes in the config file. Used to reduce redundant information. """ def c(s: str) -> str: """return a commented, wrapped block.""" s = "\n\n".join(wrap_paragraphs(s, 78)) return "## " + s.replace("\n", "\n# ") # section header breaker = "#" + "-" * 78 parent_classes = ", ".join(p.__name__ for p in cls.__bases__ if issubclass(p, Configurable)) s = f"# {cls.__name__}({parent_classes}) configuration" lines = [breaker, s, breaker] # get the description trait desc = cls.class_traits().get("description") if desc: desc = desc.default_value if not desc: # no description from trait, use __doc__ desc = getattr(cls, "__doc__", "") # type:ignore[arg-type] if desc: lines.append(c(desc)) # type:ignore[arg-type] lines.append("") for name, trait in sorted(cls.class_traits(config=True).items()): default_repr = trait.default_value_repr() if classes: defining_class = cls._defining_class(trait, classes) else: defining_class = cls if defining_class is cls: # cls owns the trait, show full help if trait.help: lines.append(c(trait.help)) if "Enum" in type(trait).__name__: # include Enum choices lines.append("# Choices: %s" % trait.info()) lines.append("# Default: %s" % default_repr) else: # Trait appears multiple times and isn't defined here. # Truncate help to first line + "See also Original.trait" if trait.help: lines.append(c(trait.help.split("\n", 1)[0])) lines.append(f"# See also: {defining_class.__name__}.{name}") lines.append(f"# c.{cls.__name__}.{name} = {default_repr}") lines.append("") return "\n".join(lines) @classmethod def class_config_rst_doc(cls) -> str: """Generate rST documentation for this class' config options. Excludes traits defined on parent classes. """ lines = [] classname = cls.__name__ for _, trait in sorted(cls.class_traits(config=True).items()): ttype = trait.__class__.__name__ if not trait.name: continue termline = classname + "." + trait.name # Choices or type if "Enum" in ttype: # include Enum choices termline += " : " + trait.info_rst() # type:ignore[attr-defined] else: termline += " : " + ttype lines.append(termline) # Default value try: dvr = trait.default_value_repr() except Exception: dvr = None # ignore defaults we can't construct if dvr is not None: if len(dvr) > 64: dvr = dvr[:61] + "..." # Double up backslashes, so they get to the rendered docs dvr = dvr.replace("\\n", "\\\\n") lines.append(indent("Default: ``%s``" % dvr)) lines.append("") help = trait.help or "No description" lines.append(indent(dedent(help))) # Blank line lines.append("") return "\n".join(lines) class LoggingConfigurable(Configurable): """A parent class for Configurables that log. Subclasses have a log trait, and the default behavior is to get the logger from the currently running Application. """ log = Any(help="Logger or LoggerAdapter instance", allow_none=False) @validate("log") def _validate_log(self, proposal: Bunch) -> LoggerType: if not isinstance(proposal.value, (logging.Logger, logging.LoggerAdapter)): # warn about unsupported type, but be lenient to allow for duck typing warnings.warn( f"{self.__class__.__name__}.log should be a Logger or LoggerAdapter," f" got {proposal.value}.", UserWarning, stacklevel=2, ) return t.cast(LoggerType, proposal.value) @default("log") def _log_default(self) -> LoggerType: if isinstance(self.parent, LoggingConfigurable): assert self.parent is not None return t.cast(logging.Logger, self.parent.log) from traitlets import log return log.get_logger() def _get_log_handler(self) -> logging.Handler | None: """Return the default Handler Returns None if none can be found Deprecated, this now returns the first log handler which may or may not be the default one. """ if not self.log: return None logger: logging.Logger = ( self.log if isinstance(self.log, logging.Logger) else self.log.logger ) if not getattr(logger, "handlers", None): # no handlers attribute or empty handlers list return None return logger.handlers[0] CT = t.TypeVar("CT", bound="SingletonConfigurable") class SingletonConfigurable(LoggingConfigurable): """A configurable that only allows one instance. This class is for classes that should only have one instance of itself or *any* subclass. To create and retrieve such a class use the :meth:`SingletonConfigurable.instance` method. """ _instance = None @classmethod def _walk_mro(cls) -> t.Generator[type[SingletonConfigurable], None, None]: """Walk the cls.mro() for parent classes that are also singletons For use in instance() """ for subclass in cls.mro(): if ( issubclass(cls, subclass) and issubclass(subclass, SingletonConfigurable) and subclass != SingletonConfigurable ): yield subclass @classmethod def clear_instance(cls) -> None: """unset _instance for this class and singleton parents.""" if not cls.initialized(): return for subclass in cls._walk_mro(): if isinstance(subclass._instance, cls): # only clear instances that are instances # of the calling class subclass._instance = None # type:ignore[unreachable] @classmethod def instance(cls: type[CT], *args: t.Any, **kwargs: t.Any) -> CT: """Returns a global instance of this class. This method create a new instance if none have previously been created and returns a previously created instance is one already exists. The arguments and keyword arguments passed to this method are passed on to the :meth:`__init__` method of the class upon instantiation. Examples -------- Create a singleton class using instance, and retrieve it:: >>> from traitlets.config.configurable import SingletonConfigurable >>> class Foo(SingletonConfigurable): pass >>> foo = Foo.instance() >>> foo == Foo.instance() True Create a subclass that is retrieved using the base class instance:: >>> class Bar(SingletonConfigurable): pass >>> class Bam(Bar): pass >>> bam = Bam.instance() >>> bam == Bar.instance() True """ # Create and save the instance if cls._instance is None: inst = cls(*args, **kwargs) # Now make sure that the instance will also be returned by # parent classes' _instance attribute. for subclass in cls._walk_mro(): subclass._instance = inst if isinstance(cls._instance, cls): return cls._instance else: raise MultipleInstanceError( f"An incompatible sibling of '{cls.__name__}' is already instantiated" f" as singleton: {type(cls._instance).__name__}" ) @classmethod def initialized(cls) -> bool: """Has an instance been created?""" return hasattr(cls, "_instance") and cls._instance is not None