graphviz-0.20.2/0000777000000000000000000000000014575736336012152 5ustar rootrootgraphviz-0.20.2/docs/0000777000000000000000000000000014575736336013102 5ustar rootrootgraphviz-0.20.2/docs/_static/0000777000000000000000000000000014575736336014530 5ustar rootrootgraphviz-0.20.2/docs/_static/angles.svg0000666000000000000000000002517614144623610016513 0ustar rootroot G cluster_1 Linear Angle Variations (white to black gradient) cluster_2 Radial Angle Variations (white to black gradient) n9 n9:360 n8 n8:315 n7 n7:270 n6 n6:225 n5 n5:180 n14 n14:180 n5->n14 n4 n4:135 n3 n3:90 n2 n2:45 n1 n1:0 n18 n18:360 n17 n17:315 n16 n16:270 n15 n15:225 n13 n13:135 n12 n12:90 n11 n11:45 n10 n10:0 graphviz-0.20.2/docs/_static/btree.svg0000666000000000000000000002102014144623610016323 0ustar rootroot g node0 G node1 E node0:f0->node1:f1 node4 R node0:f2->node4:f1 node2 B node1:f0->node2:f1 node3 F node1:f2->node3:f1 node7 A node2:f0->node7:f1 node8 C node2:f2->node8:f1 node5 H node4:f0->node5:f1 node6 Y node4:f2->node6:f1 graphviz-0.20.2/docs/_static/cluster.svg0000666000000000000000000001771514144623610016723 0ustar rootroot G cluster_0 process #1 cluster_1 process #2 a0 a0 a1 a1 a0->a1 a2 a2 a1->a2 b3 b3 a1->b3 a3 a3 a2->a3 a3->a0 end end a3->end b0 b0 b1 b1 b0->b1 b2 b2 b1->b2 b2->a3 b2->b3 b3->end start start start->a0 start->b0 graphviz-0.20.2/docs/_static/cluster_edge.svg0000666000000000000000000001357314144623610017705 0ustar rootroot G cluster0 cluster1 a a b b a->b c c a->c d d b->d f f b->f c->d e e c->e g g c->g d->e h h d->h e->g e->f graphviz-0.20.2/docs/_static/colors.svg0000666000000000000000000000342414144623610016533 0ustar rootroot RGB: #40e0d0 RGB: #40e0d0 RGBA: #ff000042 RGBA: #ff000042 HSV: 0.051 0.718 0.627 HSV: 0.051 0.718 0.627 name: deeppink name: deeppink graphviz-0.20.2/docs/_static/diamond.svg0000666000000000000000000000147614144623610016652 0ustar rootroot diamond <> graphviz-0.20.2/docs/_static/er.svg0000666000000000000000000001667514144623610015654 0ustar rootroot ER Entity Relation Diagram drawn by NEATO course course C-I C-I course--C-I n institute institute name1 name institute--name1 S-I S-I institute--S-I 1 student student name2 name student--name2 grade grade student--grade number number student--number S-C S-C student--S-C m name0 name name0--course code code code--course C-I--institute 1 S-C--course n S-I--student n graphviz-0.20.2/docs/_static/escapes.svg0000666000000000000000000000251214144623610016652 0ustar rootroot backslash \ multi_line centered left right graphviz-0.20.2/docs/_static/fdpclust.svg0000666000000000000000000001013014144623610017046 0ustar rootroot G clusterB clusterA clusterC e e e--__0:clusterB a a b b a--b C C D D C--D d d d--D f f d--f __1:clusterC--__2:clusterB graphviz-0.20.2/docs/_static/fsm.svg0000666000000000000000000002270514144623610016022 0ustar rootroot finite_state_machine LR_0 LR_0 LR_2 LR_2 LR_0->LR_2 SS(B) LR_1 LR_1 LR_0->LR_1 SS(S) LR_3 LR_3 LR_4 LR_4 LR_8 LR_8 LR_6 LR_6 LR_8->LR_6 S(b) LR_5 LR_5 LR_8->LR_5 S(a) LR_2->LR_4 S(A) LR_2->LR_6 SS(b) LR_2->LR_5 SS(a) LR_1->LR_3 S($end) LR_6->LR_6 S(b) LR_6->LR_5 S(a) LR_5->LR_5 S(a) LR_7 LR_7 LR_5->LR_7 S(b) LR_7->LR_8 S(b) LR_7->LR_5 S(a) graphviz-0.20.2/docs/_static/g_c_n.svg0000666000000000000000000000376414144623610016306 0ustar rootroot G agraph cluster1 acluster anode anode graphviz-0.20.2/docs/_static/hello.svg0000666000000000000000000000262114144623610016333 0ustar rootroot G Hello Hello World World Hello->World graphviz-0.20.2/docs/_static/holy-grenade.svg0000666000000000000000000000457514144623610017620 0ustar rootroot the holy hand grenade 1 1 2 2 1->2 3 3 2->3 lob lob 3->lob graphviz-0.20.2/docs/_static/html_table.svg0000666000000000000000000000250114144623610017340 0ustar rootroot html_table tab left right graphviz-0.20.2/docs/_static/literal_backslash.svg0000666000000000000000000000155014151056254020701 0ustar rootroot literal_backslash \\ \ graphviz-0.20.2/docs/_static/ni.svg0000666000000000000000000001100014144623610015625 0ustar rootroot ni 1 Ni! 2 Ni! 1--2 3 Ni! 2--3 4 Ni! 3--4 5 Ni! 4--5 graphviz-0.20.2/docs/_static/pet-shop.svg0000666000000000000000000000244614144623610016774 0ustar rootroot pet-shop parrot parrot dead dead parrot->dead graphviz-0.20.2/docs/_static/process.svg0000666000000000000000000001371414144367036016722 0ustar rootroot G run run intr intr run--intr kernel kernel run--kernel runbl runbl intr--runbl runbl--run zombie zombie kernel--zombie sleep sleep kernel--sleep runmem runmem kernel--runmem sleep--runmem swap swap sleep--swap runswap runswap swap--runswap runswap--runmem new new runswap--new new--runmem graphviz-0.20.2/docs/_static/qtconsole-source.png0000666000000000000000000003350414075133014020523 0ustar rootrootPNG  IHDRK罬sRGBgAMA a pHYsod6IDATx^yxzE@P6D up|vE@0OaEYdsCTr:M%lA%>>Pu;mqH^{ĩlA%]qzٮ{mvKm7w~j׫r^ju=ԋ_zkvS[lWUSm ]pR{Y7Hp&HݣCnh{쐱f^v[NJ[EJӷP{̚u*p~uW_[9\qq?xx5{e]X՝V/~Oc:fg8i0wW6.*MjMW AԵWJY3] R~:]qgNS '7:Am^myzGy68fݽpm)lfc& Z)f"άi*=C}RK-^Ny =SlK'm'xd֝U9 .N3Yw)̀gpw#읇g$G?0m ] zP]Ѥx PRօє4 FW+,FWfl8v{qꀷ^}3۠6j;nCMCm8C쟶z۶h84{f"S@Ftyxb|"Z7kbF0Dt9sZe2A(nY)%:\=޵Au9 i9dǾo6ƂROrwOV[︛E;'mz3;Xxw+>&xo1xR$_pA`'$`R> J3+9A\il8}V}+ F1G)nfm{;&fhL, FW+,LGx雎`Dj A(HD8ԽwJ˷ߥF pG'IMOZw);&t_q낾p1Wc ﮤUԫ FW+Nߪ3]džNԆiLdeA1)#\=L9?rwƄ+!MKo+[s-[ZwW DpRsj'}X/m?JA]AAcߏ|KɴdS P fd1{I~w  hn@ E AA  AAP  sp@AԹ`8  \0Aup[_%&c 0p,0%ñpw2 H VVԊRkCeQǽj].̓hV(l8Fj11[?txR 9z-&9-W 7EuP{gc*B6\,L3~P1-fVԾ=9?7pgéEAy^*w\ 4!kj:qc2~@A /wGmM5Soz2E]/c|!^G~|wzshy0H*peJ,+pr|\"I]=&&hr5_gMѤ@th84ZU^LV{Y}i2TM6晾IG-ۖL OcK$Jr F.98f4ǔǬ8nGʘ0( 1ad04VHc 20c 20c 0p,4a/,7뮻  :Qi8V  F~7`8g"rF8#4™pDp&"Ga8i?{aVeq]wOD8~lM]MOq\~}:ӣҧ!=7c@#Gt3$2Nz;dM֣}8wf&3Zj4jqbRz&َ$6pwv &#hO{'Ld O/` S/2~yV!헙+pj՗qz"BkxsfQR8zBw8|r2Gnە?4x~uaY˛džm'OσN\7|<`Pm,#Q"Chw g"rԛpwf7w\4DLD7X:8#4rWL,$Tug8q~t_-3n+Pޯ&mF*e*S{=a~GLx >!1b/ 07JI8w`&.b78vuh;8~.և_ 뉍;.'نkLrVx sP?!T/12^L/dvjm[wT_z(Z)#]xh]1?\ʏΟ$>㈗ 3țG2m??\/'asO*zvuh[wi9iop!2E֣:8TInn]FU76R~mG/-{;7!ob}eag6sד}Se<5S EBoU:qc궙0Epws y"%Q:prRmCq<}B>*rAro_qmk*r%'"nyyؠ~5c¸GتufLR݆1Df_Ump|q7n`bC-jco).Qӯ Nwxv}-G* .7uUp rn;S:y)ȃ4pS^NG܁ܶ eBęPW\:Y=񀋡l2Gc.n^|_M(oxcKNMrܛ0mڤI\*O >6~y)^?䯺_ >Yؙ'I>Xl&ŪCXgbbE>jڱalc|TO !1-fۓo-C/rD&`;0QB'`N}d]Y@ee~<ᅵ:P GT'2f7fp~38~))^?9&}H|Oʼ썿8:Xvơw6&8E+KeT,WCٶCǥ>vkdq_e8hFtձ^ݛ$qSr TMo8(y9XؼvpR$K;v8*85^1Qm~uc]޴O`k!7Ka wxsLN,l;xbInQg5,lqy5Ŵ=Gꐮxqo|}͛z=ah8^S5)\O_Ն:}Xܵ@:k7Eq2>9# GQSzpYew 4[7HPh% C֯?8N1Ԏtpm;ZESv6ax~F|$~gJ< [hbod ů/0D\y8~F(?k4'ʃ7vѧD G- Ƿ>DAx 39l[hJ.aԍù6h$Aj]߸;0ye9?u S][DØ3H D`8g$R|47_ᾭ-PmumS Y~w*dR~_`2<+H*p =0j♚If]['otalQm.fuҷ`存[3HdL^F޸m!}jRCOyn+bK>"x~HՔ;iл#:h;Nv'I&IW.Khf"xF .B:Eܧ_xyf.p܅Vw.o xܥ~M /vtsER_|FX O6oY?Oy/8~hHjzA'_Ӟ7 ؿ-R<&~S&B.01}T(7!oR,Lc}^=8 .9HǽqTʅ.y>biOmhHA|yT;~M1AuS>'~SMż}L\GS3뙗u n }X)b~9=x g$Rf8^^ 쁞;=5"`Oj_4_qL"pH㹎Ym.,8笺՘Ds2%p|'IsgpF"UYw8Nh)>vW!L5N!giwTµI*{`/,:V73k屗ڡr8zx|s國GKg&ql<>Lȃ_y^;^qe!o,8bU|ʛ%o@[~ef/CUZ׭'Xx8# G1^ t3ez`z'TPȚ:Rⓣ\f|62%zoܼQ}/Wy;^ԧ׮ܣ.`pŽg)`)3NǚPKpupz.b>f].΢ИAq~!/~._]~zLnr퀥3p_`5܅+s+0Ql;2n͟;f4LUnBWtYj]pCD'cRe,{ UWnf@HCw#.tp2 |DDkVJ#rׅ)>)59w8&4~5fⷦ>X# ж_/Umߘ pxXJ8# wW#c G. c`ʝ( 3E=*7â_vEQs7Bs Dy#*,_:h8,7H G_6q9MIprAݱh2ΔsAu=$ÁDpJ0 S̃o2 .FP3T^W ^&#b5ATO'K2!H*pI(B[~'S;_ެ714.Gphm%@MMY.-wm+en̊ߖW`yDpBH%;}5=L<6Dfc81NpWfd8; 8# H IpF"U0H3@H  0p/Lg"rF85Dp&"G=;ph= }™pDgE39@# LD`8g"rF8#iYaW4Zn<>k{5_.F,jOwoI},N]S7Z /LDpz0̙9R=QunuX.n+,uw:O!LtD q`UIy rڷ-O{gE}vs⧲`Xܵh7_)p/LD,*י2{/ywU\QO-(=&x.+Lz1Qj''HE8_DzQZspV'>D_/R>~7-X?p&"Go8&P7@bPM(Ԏ_E~z엙* o[j+ v$IޯE>.o3'-.'h3~'m&)KPzҺlμ?Iѐ+&(@1_V@]r6RR 4L g"r#` G4 Y*ISLhf'd>,℩JL8&m'r?N!\]&ܷ cJzo(kbI&Xzi<Be${~bI`RW;S엟*Fum%^ģ L@4a= \t%ΑS)LDz63x8)$LAr;5܄ ƯXؑ~A5SnOd\NqM RCfѱ%B蝴XI؟Ͷ900M3ƛP O`ބM+ˣv8r=.`8Jfj #ɘ71~BACOHx}87fboML$6ϸrN&7[kK*'&F]oʏ~y}hph3[chwꞁ'f;IQ]΁m&fg"rd8VW h`kl5`p&"G͆@i%+< p5&L}3殇6t Dp^M0DJ8D(p80cLD39j4!3,`C5@ڐ\/p39J2Qw|D!?D_39@# LD`8g"rF8#4™pDp&"G0h39@# LD`8g"rF8#4™pDp&"G0h39 q׫.L}[Rg}ԧ>?x#zMe/W7p <3™n8nVu#N?^y~{߫^׫ۥ<Qgl&v}? /g=Y; :? gG׽Էm}kW#8˞gp~]* ǟ'sk> \ejAGw\//fmRC‰'hIgM4v\Lesl8﨧?=L{ on,}H9> enA=OKҗ Iqz]/=W7n4#g-[}5Si?eγo8=a3ՠ;t73Ad֓ z$ O~Ҭ eo|Uz]> }>w?ؼw7}}>jM73gD=7_%˜gp|61u_}ߵ%ݱL$HO%]vE=iO3wYh63 QM?ڥŇ ꃜCg{/~@o8N M'/=li}1O z=[+k~@O8N gaD^uQΛ}Ɍ~GδIgh@y39p8IHs1jv0uSon<@`$2ZGuvyg馛wQ{ B~@y`WՕn88gW^y 9眣6nhHTFM7d < @El.20ob̅3r,4™dԙ  L)l`8g" p&"G0h39@# LD`8g"rT1AA]4AA\FgIENDB`graphviz-0.20.2/docs/_static/qtconsole.png0000666000000000000000000003763614075133014017237 0ustar rootrootPNG  IHDRN"#sRGBgAMA a pHYsod?3IDATx^y5Ey (1GAe&D6 UA48Ack5srӁBqI|~33fLB!&W_};0!g\uUf??L+Oyg#LB-O~b~_F';xkzCt@]:S-!B_3J3'ĘWQ`^כv{:+4;faw]^cWU/yy΋w7~F-!ZʊE[vƻ̛ιI'yG7g\qf뮛,{‚6ov4ŦSpjn0 - ̫ƥѷ!T%hbLZb*tU6oXϸ/~(>n:K+_=ؑc?jg~Ѽl} ^Uono0uOkG?h l~v1ۿ|"3!Ԃpm<2' >n{Kznc}ݑf'nm9O`6MGFy_@y13meʖޗ02SzF^_4x,딶A5@TF)+mi)&G"FC$Tg&^ZpvW?o6((́ݕ ]{+36vxd1OqTjmbnV?SVvBJ]䙴8LP1X[LDm,o̩ 虬9.4LMvWم>zD~]qscqݾ WZ2/BTŢ-;MM`Il>~IاO4ei1_V=&Bh+t3>v8ji`:>1Bi(tL_!Q_Bh&MB!0!0!0!0!0!0!0!``:`"`:`"`:`"`:`"`:`" a:Mon9_Xm3?7oOT3'*Ɩ/l 0V4p d{}L\H^e&EҲ,k%yיK b~ENAsQ+mVna3SWcAqm3N]K:u֔cLj^SMbg:_V|qH}_BˏBU??*HղF 'Zrwrnm3M9ɩL$Y,?A,nτLG 8]Q 6SΥj53 N2`qe:)d13ՠ)ioww^g5cqˑX10Yo:~G`7R O7<XhFB$6pg:{$n*g@i{E>GU6Hn٠ջX |U)37PR~|vBPgqۮ[4ӡCѨ0y(jrE3d[{/G ꉝ7Kh?&%{L^ְedDzqW*MwLGrG`70gGʺk׍P\C:=1 ,Gn,3ls0ڷCS}43tttVN2- f^I޲U2]>6uxVh ӑe(J;(6ğLlx3--F  ? kVghjz~me:Snh+K+mT%+8#f%W6zF#egSv LXjd\Y~~~mq=?CV1[fi<OeKi 6͐vgr6j4!oj{%Tٶ)f롞JJRNїC=kWӁm/[O#~q,f%g(/~d5B?u1Hg:j6Q!;aJ~g !+9hqV6U͍ik29KoN^k[u;g>v>/w+pigQoևw2" d=yK%O+$9_ UYM\/qEGu}-?FVjj{-3T??B׏$ c43Lt\SNFltQL7;DVB}J4}7rpP7\ezNgfme?RύD?&_^7 |8Qfii eQ˩S'5>[ZNIs{b I'8a^LBcY%Oٯ6YHCv?>[Fx0`}Y3.x"j?qfے8L hf"VF3ZR^oG%6Nޛ djē=L~[ ^b+L*t؃X3iZȴ -jdM7Y"{^ } linKYcfJ0~0-43t5/;*wOwqqoc*z㗲bUVY~-Vo9Pz ˸yrۛ^ofWNZC-~va:ۃgPۧ;'^~4]q7%,BWr-mX/mA?/ەOkC3t;_";Jq;9;wJo˯S _ҳn+;.3)jdW-v]?^FO1]B#=m> HmrӖ/Hm^U?[]2ůh@hf"V ~x;,ggNT۹%WMP`,[;X0VuX_~Vr6Pwkodp5,Z!П~~w;Q'TP4_WY-[~J.Us6s~Wp ^xZUgj|vY;8*5vC`Wm@J^[_~tHY >h=KCm 9hx*g(pGSWc4Ro߫m3pLĪ;!;N ;hvQ";iUs(<-q z0PG͵z [(rY xs hw0N!֙ڮFzCٺ8 3JwԟA8a믗~umqiS?P<LĪC!yX/|*獰snUL-I/O;\4C|]~ %"- ߮P4[ϯm[뽥]iވsv#wr? ?ROko`CcΠgq+43LPS5%DM?S}&f&bՉ(zc\"#*R~ê󶗿F3GâXux;D0f&b5=ӡ_1kpzԟBi-XoqϬ7 $@|kqX7'^3]DL*~zm{"h>bE?6NIFIǽ0imSd!L~,i#r(v}9BF3LH߉ʧE\l[VLC(pf.2\-;ƃJkVYa1lպ#zt~s-v u{V nO4?\Kzr2Ԧbc],j:y׷Bk˶h; вZAy4LG}{.;+;M6|Yg6O( 2 Sb?HVmf3$Wi[_ˏ/mԶ&VGA(׵>DW-Z? չ$kfo3O jmOCSfڷCaAr`ɣXug: azNNZ__5?Ckȗ]5cfRM&n{3&i)kx@~/"O}ůcj_)?v;iL?hݶ ˺̵u饕^MKcf{rvVj"f2ϖP%C KLj,g:jdH-C[&w1?xp;;heC:94ȇ(aP9m/00fʩS륤>BZL #F*Q<ۿiCmV#7wuYl^)DTg2t24^y|D0qa:2vb:2M@.r #,3LGiL!Yf&b,LGWk CaKA#0'0tC3\0ٴQ+Seu} j=^\!#L*tQ\+(8H:MG?ouF=Ho12# ƠTi3U~1 _tFe1~Y*)+K [Lj3v..D1)S%d@Whf"V5rIvveLthgXMtC3t@4D0f&bh43ea:R_áXa: LĊ+f&bh43)ufa3|%'5+B3t@4W L hf"VF3t@4DVXm͂(,z/4iG9g撗e+Dy3\C3t,qh(1:2`eƢPo1-bO1A9zKLjYtM?@QnApRl,A-$gjo?MeeÿkmOYUN|YP2)+^f&bLGmiV 堚후')'˟m_!U65~;kibHB3ZQWꃜ28\uAW4Z[lbn U9yv7 ;y,Aӑ2@hV^g? D0idP-0YC`[M;s0j6[sYAbtLjc ^dxOGꙎ2ؖxV,_6FB3Y>?xl6쿜p,˟f?x|}hf"V+tdK7%v?75֍'(x/ {$xʲBn:y:c˟d?8gJ|f&b. :%>8N̋'d43+LGx̵/|3543+LDXa: L hf"V+tW,DG,.7KkLXOD0m}˩;Xtd:Z Y.HHhlCޡfg,J'?ٜ|E hF"Ec1{b-o"ep'\cG3)t\~f62W.RfSir4#NMM7d⋋A6=|,u]G]LqgK!ľ_v -&!!7>IO2{oN;d2 rnhn:},7^veE E3)tMo2'pB1k:BSGocLG[1tIm+H!g7wqG׽uE/z5 }C'>bo/K0|##{9kLL?Y2yCl0bZG'XnhF"Ey سb* 1yc 䩑c9<jw}wf뭷BC |^뮻qy睭yIӾTIGfr4#NL0f^uf:} ,khF"Ea2| f$RԩXni{HS!\~f62W.RA9HQC8s[laԐװ#_W4#AޡfL }qO.RD3)aRN;mN O_6HhC[AsOYC^*^{~W4#A*[LX5tvm]zIx:JVHDLG=cH=μ=1Rߤ^+ьD&j:Jcm2S"Oqg֮][)[ꐺrK[hF"ES1%rW_m8ۏwN ѶTdYVʐM6!u f$R4U"_gm95Yr<"+{5"_dtM2l˒2l HhfL|^.\q}a<~+OˆIyg͚5vHhMG> &f$R(4#"LDa: HQhF"EB3)t@HЌD0f$R(4#"LDa: HQhF"EB3)t@HЌD0f$R(4#"LDa: HQhF"EB3)t@HЌD;.b77~{]wլ[ @3)Zk- !s1摏|dlH?" Hhɘkךh˿4V*rЌDrˆxSZq-)ӱ`ļ?b.HhI[o<Lq-s`hF"EKt+>Hq-9qYg6뭷93T7Hђ3wuWu;,R`hF"E3k:e_7tE]d͜}O6C<#y01̈́鐳\r9S;ۛ_lf3/x +^ 84ᄏ_d䑼̣h3??o2:`x4#np /|` ;l: }\z6BȲR%eJRN;d딺 @ y>?ۗs ￿}ի G}yԣez[afN=Tk:sOqOݏb:RJB3)t?6m]1~3;|;47c|򖷘m;Ϧ r6d 74|e:?S[oο 9Qg=JB3)t|[߲ycXk&/W&My:DِJ#AE//*MRcS҅u],w4#a=DǗ%͓<1K/5}Ck֬;h=h!u`:C3)tzyE{+g3噈}>" QOӂS!igq@qM7嗟#.JD3)tz#pY=H1__m\:)>f8묳%fM7Ed:/7ͩwy?FRXhF"ECaGfwC.˿Kn\>pLYy<>+^i\"e\~ˈQ{Ad: <2 +Djh1闃  V*HЦc_>.x :d4#M0|7yk*|f$R4=@3)tȍGyyK^b ܐ6;I?,W4#M hYNg‹f$Rԉ{V[m,^uWmƞőt4#LGh曛|+R:vᢋ.*R@3)twujnko,3xolf$R4Q"g=ve=~+eo4/-#:HQt\{J_zFNKӼ1Sꖶ{fM6_:w}im1%k׮5el.{Ÿ:.Bs!ؗ|I M `hiJpE򺰰`_ocvK,+eHYR]!up EN7(M?kjEnڼKͩj8;_~^HrDޙ!IyG2ooKb@1`44#6! ӡ!]k͝dɗ%~wqV<#yo&^04#!1tX>hF !t@HQi:.23wua:@E3)*M_n殿>tf$RT+tf$RTܪU0?[fMe:B! y܍7h'B!ƥ_zFeIENDB`graphviz-0.20.2/docs/_static/rank_same.svg0000666000000000000000000000613714144623610017176 0ustar rootroot A A C C A->C B B A->B X X Y Y X->Y D D C->D graphviz-0.20.2/docs/_static/round-table.svg0000666000000000000000000000427714144623610017455 0ustar rootroot A King Arthur B Sir Bedevere the Wise A->B L Sir Lancelot the Brave A->L B->L graphviz-0.20.2/docs/_static/splines.svg0000666000000000000000000000227314226660050016710 0ustar rootroot splines a b a->b c graphviz-0.20.2/docs/_static/structs.svg0000666000000000000000000001042314144623610016736 0ustar rootroot structs struct1 left middle right struct2 one two struct1:f1->struct2:f0 struct3 hello world b g h c d e f struct1:f2->struct3:here graphviz-0.20.2/docs/_static/structs_revisited.svg0000666000000000000000000000762514144623610021026 0ustar rootroot structs struct1 left middle right struct2 one two struct1:f1->struct2:f0 struct3 hello world b c d e f g h struct1:f2->struct3:here graphviz-0.20.2/docs/_static/traffic_lights.svg0000666000000000000000000002317714144623610020231 0ustar rootroot TrafficLights PetriNet Model TrafficLights Extracted from ConceptBase and layed out by Graphviz gy2 gy2 yellow2 yellow2 gy2->yellow2 yr2 yr2 red2 red2 yr2->red2 safe1 safe1 yr2->safe1 rg2 rg2 green2 green2 rg2->green2 gy1 gy1 yellow1 yellow1 gy1->yellow1 yr1 yr1 safe2 safe2 yr1->safe2 red1 red1 yr1->red1 rg1 rg1 green1 green1 rg1->green1 green2->gy2 yellow2->yr2 red2->rg2 safe2->rg2 green1->gy1 yellow1->yr1 red1->rg1 safe1->rg1 graphviz-0.20.2/docs/_static/unix.svg0000666000000000000000000007003714144623610016221 0ustar rootroot unix 5th Edition 5th Edition 6th Edition 6th Edition 5th Edition->6th Edition PWB 1.0 PWB 1.0 5th Edition->PWB 1.0 LSX LSX 6th Edition->LSX 1 BSD 1 BSD 6th Edition->1 BSD Mini Unix Mini Unix 6th Edition->Mini Unix Wollongong Wollongong 6th Edition->Wollongong Interdata Interdata 6th Edition->Interdata PWB 1.2 PWB 1.2 PWB 1.0->PWB 1.2 USG 1.0 USG 1.0 PWB 1.0->USG 1.0 2 BSD 2 BSD 1 BSD->2 BSD Unix/TS 3.0 Unix/TS 3.0 Interdata->Unix/TS 3.0 PWB 2.0 PWB 2.0 Interdata->PWB 2.0 7th Edition 7th Edition Interdata->7th Edition TS 4.0 TS 4.0 Unix/TS 3.0->TS 4.0 PWB 2.0->Unix/TS 3.0 8th Edition 8th Edition 7th Edition->8th Edition 32V 32V 7th Edition->32V V7M V7M 7th Edition->V7M Ultrix-11 Ultrix-11 7th Edition->Ultrix-11 Xenix Xenix 7th Edition->Xenix UniPlus+ UniPlus+ 7th Edition->UniPlus+ 9th Edition 9th Edition 8th Edition->9th Edition 3 BSD 3 BSD 32V->3 BSD V7M->Ultrix-11 2.8 BSD 2.8 BSD 2 BSD->2.8 BSD 2.8 BSD->Ultrix-11 2.9 BSD 2.9 BSD 2.8 BSD->2.9 BSD 4 BSD 4 BSD 3 BSD->4 BSD 4.1 BSD 4.1 BSD 4 BSD->4.1 BSD 4.1 BSD->8th Edition 4.1 BSD->2.8 BSD 4.2 BSD 4.2 BSD 4.1 BSD->4.2 BSD 4.3 BSD 4.3 BSD 4.2 BSD->4.3 BSD Ultrix-32 Ultrix-32 4.2 BSD->Ultrix-32 PWB 1.2->PWB 2.0 CB Unix 1 CB Unix 1 USG 1.0->CB Unix 1 USG 2.0 USG 2.0 USG 1.0->USG 2.0 CB Unix 2 CB Unix 2 CB Unix 1->CB Unix 2 USG 3.0 USG 3.0 USG 2.0->USG 3.0 CB Unix 3 CB Unix 3 CB Unix 2->CB Unix 3 Unix/TS++ Unix/TS++ CB Unix 3->Unix/TS++ PDP-11 Sys V PDP-11 Sys V CB Unix 3->PDP-11 Sys V CB Unix 3->TS 4.0 Unix/TS++->TS 4.0 USG 3.0->Unix/TS 3.0 Unix/TS 1.0 Unix/TS 1.0 Unix/TS 1.0->Unix/TS 3.0 System V.0 System V.0 TS 4.0->System V.0 System V.2 System V.2 System V.0->System V.2 System V.3 System V.3 System V.2->System V.3 graphviz-0.20.2/docs/_static/wide-unflatten-stagger-2.svg0000666000000000000000000001333514144623610021753 0ustar rootroot 0 0 1 1 0->1 2 2 0->2 3 3 0->3 4 4 0->4 5 5 0->5 6 6 0->6 7 7 0->7 8 8 0->8 9 9 0->9 graphviz-0.20.2/docs/_static/wide-unflatten-stagger-3.svg0000666000000000000000000001332714144623610021755 0ustar rootroot 0 0 1 1 0->1 2 2 0->2 3 3 0->3 4 4 0->4 5 5 0->5 6 6 0->6 7 7 0->7 8 8 0->8 9 9 0->9 graphviz-0.20.2/docs/_static/wide.svg0000666000000000000000000001312514144623610016161 0ustar rootroot 0 0 1 1 0->1 2 2 0->2 3 3 0->3 4 4 0->4 5 5 0->5 6 6 0->6 7 7 0->7 8 8 0->8 9 9 0->9 graphviz-0.20.2/docs/api.rst0000666000000000000000000021156014575730462014404 0ustar rootrootAPI Reference ============= .. autosummary:: :nosignatures: graphviz.Graph graphviz.Digraph graphviz.Source graphviz.escape graphviz.nohtml graphviz.ExecutableNotFound graphviz.CalledProcessError graphviz.RequiredArgumentError graphviz.render graphviz.pipe graphviz.pipe_string graphviz.unflatten graphviz.view graphviz.version .. hint:: The two main classes :class:`.Graph` and :class:`.Digraph` (creating **undirected** vs. **directed** graphs) have exactly the same API. Their division reflects the fact that both graph syntaxes cannot be mixed. Graph ----- .. autoclass:: graphviz.Graph :members: directed, name, comment, filename, directory, format, engine, encoding, renderer, formatter, graph_attr, node_attr, edge_attr, body, strict, __iter__, source, node, edge, edges, attr, subgraph, filepath, save, render, view, pipe, unflatten, _repr_mimebundle_, clear, copy Digraph ------- .. autoclass:: graphviz.Digraph :members: directed, name, comment, filename, directory, format, engine, encoding, renderer, formatter, graph_attr, node_attr, edge_attr, body, strict, __iter__, source, node, edge, edges, attr, subgraph, filepath, save, render, view, pipe, unflatten, _repr_mimebundle_, clear, copy Source ------ .. autoclass:: graphviz.Source :members: from_file, filename, directory, format, engine, encoding, renderer, formatter, __iter__, source, filepath, save, render, view, pipe, unflatten, _repr_mimebundle_, copy Quoting/escaping ---------------- .. autofunction:: graphviz.escape .. autofunction:: graphviz.nohtml Exceptions ---------- .. autoexception:: graphviz.ExecutableNotFound .. autoexception:: graphviz.CalledProcessError .. autoexception:: graphviz.RequiredArgumentError .. autoexception:: graphviz.FileExistsError Warnings -------- .. autoexception:: graphviz.UnknownSuffixWarning .. autoexception:: graphviz.FormatSuffixMismatchWarning .. autoexception:: graphviz.DotSyntaxWarning Low-level functions ------------------- The functions in this section are provided to work directly with existing files and strings instead of using the object-oriented DOT_ creation methods documented above. .. autofunction:: graphviz.render .. autofunction:: graphviz.pipe .. autofunction:: graphviz.pipe_string .. autofunction:: graphviz.pipe_lines .. autofunction:: graphviz.pipe_lines_string .. autofunction:: graphviz.unflatten .. autofunction:: graphviz.view Constants --------- Manually maintained allowlists for Graphviz_ **parameters** (cf. `man dot `_, `outputs `_, and ``dot -T:`` output): .. autodata:: graphviz.ENGINES :annotation: .. autodata:: graphviz.FORMATS :annotation: .. autodata:: graphviz.RENDERERS :annotation: .. autodata:: graphviz.FORMATTERS :annotation: Supported **IPython/Jupyter display formats**: .. autodata:: graphviz.SUPPORTED_JUPYTER_FORMATS :annotation: Names of **upstream binaries**: .. autodata:: graphviz.DOT_BINARY :annotation: .. autodata:: graphviz.UNFLATTEN_BINARY :annotation: Defaults -------- Functions for setting **package-wide defaults** for ``engine`` and ``format``: .. attention:: These functions are provided mainly to simplify testing but may also be used by end-users for convenience in scripts. They **should be avoided in library code**. Prefer passing or setting ``engine`` and ``format`` explicitly if you create a library that depends on this package. .. autofunction:: graphviz.set_default_engine .. autofunction:: graphviz.set_default_format Function for setting the **package-wide default for IPython/Jupyter display format**: .. attention:: This function is provided for end-users. Prefer `IPython.display`_ functions in library code. .. autofunction:: graphviz.set_jupyter_format Other ----- .. autofunction:: graphviz.version .. include:: _links.rst Online ``help()`` (internal) ---------------------------- Results of :func:`help` for :class:`graphviz.Graph`, :class:`graphviz.Digraph`, and :class:`graphviz.Source` for reference. .. attention:: The outputs in this section may contain (some) **internals** (implementation details). They serve to record some current implementation details and their changes. They mainly serve the development process (e.g. checking the MRO). They might be outdated. They **may change at any point** in time. See above for the full (public) API. First shalt thou take out the Holy Pin. Then shalt thou count to three, no more, no less. To **update** :func:`help` outputs below: .. code:: bash $ ./update-help.py To **debug**: remove ``+SKIP`` flags below and check output(s): .. code:: bash $ ./run-tests.py docs --doctest-report none Graph """"" Partially syntax-highlighted: https://github.com/xflr6/graphviz/blob/master/docs/api.rst#graph-1 .. doctest:: >>> import graphviz >>> help(graphviz.Graph) # doctest: +NORMALIZE_WHITESPACE +SKIP Help on class Graph in module graphviz.graphs: class Graph(graphviz.dot.GraphSyntax, BaseGraph) | Graph(name: Optional[str] = None, comment: Optional[str] = None, filename=None, directory=None, format: Optional[str] = None, engine: Optional[str] = None, encoding: Optional[str] = 'utf-8', graph_attr=None, node_attr=None, edge_attr=None, body=None, strict: bool = False, *, renderer: Optional[str] = None, formatter: Optional[str] = None) -> None | | Graph source code in the DOT language. | | Args: | name: Graph name used in the source code. | comment: Comment added to the first line of the source. | filename: Filename for saving the source | (defaults to ``name`` + ``'.gv'``). | directory: (Sub)directory for source saving and rendering. | format: Rendering output format (``'pdf'``, ``'png'``, ...). | engine: Layout command used (``'dot'``, ``'neato'``, ...). | renderer: Output renderer used (``'cairo'``, ``'gd'``, ...). | formatter: Output formatter used (``'cairo'``, ``'gd'``, ...). | encoding: Encoding for saving the source. | graph_attr: Mapping of ``(attribute, value)`` pairs for the graph. | node_attr: Mapping of ``(attribute, value)`` pairs set for all nodes. | edge_attr: Mapping of ``(attribute, value)`` pairs set for all edges. | body: Iterable of verbatim lines (including their final newline) | to add to the graph ``body``. | strict (bool): Rendering should merge multi-edges. | | Note: | All parameters are `optional` and can be changed under their | corresponding attribute name after instance creation. | | Method resolution order: | Graph | graphviz.dot.GraphSyntax | BaseGraph | graphviz.dot.Dot | graphviz.quoting.Quote | graphviz.rendering.Render | graphviz.saving.Save | graphviz.jupyter_integration.JupyterIntegration | graphviz.piping.Pipe | graphviz.unflattening.Unflatten | graphviz.encoding.Encoding | graphviz.base.Base | graphviz.base.LineIterable | graphviz.backend.mixins.Render | graphviz.backend.mixins.Pipe | graphviz.parameters.mixins.Parameters | graphviz.parameters.engines.Engine | graphviz.parameters.formats.Format | graphviz.parameters.renderers.Renderer | graphviz.parameters.formatters.Formatter | graphviz.parameters.base.ParameterBase | graphviz.copying.CopyBase | graphviz.backend.mixins.View | graphviz.backend.mixins.Unflatten | builtins.object | | Readonly properties defined here: | | directed | ``False`` | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | __annotations__ = {} | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.dot.GraphSyntax: | | __dict__ | dictionary for instance variables | | __weakref__ | list of weak references to the object | | ---------------------------------------------------------------------- | Methods inherited from BaseGraph: | | __init__(self, name: Optional[str] = None, comment: Optional[str] = None, filename=None, directory=None, format: Optional[str] = None, engine: Optional[str] = None, encoding: Optional[str] = 'utf-8', graph_attr=None, node_attr=None, edge_attr=None, body=None, strict: bool = False, *, renderer: Optional[str] = None, formatter: Optional[str] = None) -> None | Initialize self. See help(type(self)) for accurate signature. | | ---------------------------------------------------------------------- | Readonly properties inherited from BaseGraph: | | source | The generated DOT source code as string. | | ---------------------------------------------------------------------- | Methods inherited from graphviz.dot.Dot: | | __iter__(self, subgraph: bool = False) -> Iterator[str] | Yield the DOT source code line by line (as graph or subgraph). | | Yields: Line ending with a newline (``'\n'``). | | attr(self, kw: Optional[str] = None, _attributes=None, **attrs) -> None | Add a general or graph/node/edge attribute statement. | | Args: | kw: Attributes target | (``None`` or ``'graph'``, ``'node'``, ``'edge'``). | attrs: Attributes to be set (must be strings, may be empty). | | See the :ref:`usage examples in the User Guide `. | | clear(self, keep_attrs: bool = False) -> None | Reset content to an empty body, clear graph/node/egde_attr mappings. | | Args: | keep_attrs (bool): preserve graph/node/egde_attr mappings | | edge(self, tail_name: str, head_name: str, label: Optional[str] = None, _attributes=None, **attrs) -> None | Create an edge between two nodes. | | Args: | tail_name: Start node identifier | (format: ``node[:port[:compass]]``). | head_name: End node identifier | (format: ``node[:port[:compass]]``). | label: Caption to be displayed near the edge. | attrs: Any additional edge attributes (must be strings). | | Note: | The ``tail_name`` and ``head_name`` strings are separated | by (optional) colon(s) into ``node`` name, ``port`` name, | and ``compass`` (e.g. ``sw``). | See :ref:`details in the User Guide `. | | Attention: | When rendering ``label``, backslash-escapes | and strings of the form ``<...>`` have a special meaning. | See the sections :ref:`backslash-escapes` and | :ref:`quoting-and-html-like-labels` in the user guide for details. | | edges(self, tail_head_iter) -> None | Create a bunch of edges. | | Args: | tail_head_iter: Iterable of ``(tail_name, head_name)`` pairs | (format:``node[:port[:compass]]``). | | | Note: | The ``tail_name`` and ``head_name`` strings are separated | by (optional) colon(s) into ``node`` name, ``port`` name, | and ``compass`` (e.g. ``sw``). | See :ref:`details in the User Guide `. | | node(self, name: str, label: Optional[str] = None, _attributes=None, **attrs) -> None | Create a node. | | Args: | name: Unique identifier for the node inside the source. | label: Caption to be displayed (defaults to the node ``name``). | attrs: Any additional node attributes (must be strings). | | Attention: | When rendering ``label``, backslash-escapes | and strings of the form ``<...>`` have a special meaning. | See the sections :ref:`backslash-escapes` and | :ref:`quoting-and-html-like-labels` in the user guide for details. | | subgraph(self, graph=None, name: Optional[str] = None, comment: Optional[str] = None, graph_attr=None, node_attr=None, edge_attr=None, body=None) | Add the current content of the given sole ``graph`` argument | as subgraph or return a context manager | returning a new graph instance | created with the given (``name``, ``comment``, etc.) arguments | whose content is added as subgraph | when leaving the context manager's ``with``-block. | | Args: | graph: An instance of the same kind | (:class:`.Graph`, :class:`.Digraph`) as the current graph | (sole argument in non-with-block use). | name: Subgraph name (``with``-block use). | comment: Subgraph comment (``with``-block use). | graph_attr: Subgraph-level attribute-value mapping | (``with``-block use). | node_attr: Node-level attribute-value mapping | (``with``-block use). | edge_attr: Edge-level attribute-value mapping | (``with``-block use). | body: Verbatim lines to add to the subgraph ``body`` | (``with``-block use). | | See the :ref:`usage examples in the User Guide `. | | When used as a context manager, the returned new graph instance | uses ``strict=None`` and the parent graph's values | for ``directory``, ``format``, ``engine``, and ``encoding`` by default. | | Note: | If the ``name`` of the subgraph begins with | ``'cluster'`` (all lowercase) | the layout engine will treat it as a special cluster subgraph. | | ---------------------------------------------------------------------- | Methods inherited from graphviz.rendering.Render: | | render(self, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, view: bool = False, cleanup: bool = False, format: Optional[str] = None, renderer: Optional[str] = None, formatter: Optional[str] = None, neato_no_op: Union[bool, int, NoneType] = None, quiet: bool = False, quiet_view: bool = False, *, outfile: Union[os.PathLike, str, NoneType] = None, engine: Optional[str] = None, raise_if_result_exists: bool = False, overwrite_source: bool = False) -> str | Save the source to file and render with the Graphviz engine. | | Args: | filename: Filename for saving the source | (defaults to ``name`` + ``'.gv'``).s | directory: (Sub)directory for source saving and rendering. | view (bool): Open the rendered result | with the default application. | cleanup (bool): Delete the source file | after successful rendering. | format: The output format used for rendering | (``'pdf'``, ``'png'``, etc.). | renderer: The output renderer used for rendering | (``'cairo'``, ``'gd'``, ...). | formatter: The output formatter used for rendering | (``'cairo'``, ``'gd'``, ...). | neato_no_op: Neato layout engine no-op flag. | quiet (bool): Suppress ``stderr`` output | from the layout subprocess. | quiet_view (bool): Suppress ``stderr`` output | from the viewer process | (implies ``view=True``, ineffective on Windows platform). | outfile: Path for the rendered output file. | engine: Layout engine for rendering | (``'dot'``, ``'neato'``, ...). | raise_if_result_exists: Raise :exc:`graphviz.FileExistsError` | if the result file exists. | overwrite_source: Allow ``dot`` to write to the file it reads from. | Incompatible with ``raise_if_result_exists``. | | Returns: | The (possibly relative) path of the rendered file. | | Raises: | ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` | are unknown. | graphviz.RequiredArgumentError: If ``formatter`` is given | but ``renderer`` is None. | ValueError: If ``outfile`` is the same file as the source file | unless ``overwite_source=True``. | graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable | is not found. | graphviz.CalledProcessError: If the returncode (exit status) | of the rendering ``dot`` subprocess is non-zero. | RuntimeError: If viewer opening is requested but not supported. | | Example: | >>> doctest_mark_exe() | >>> import graphviz | >>> dot = graphviz.Graph(name='spam', directory='doctest-output') | >>> dot.render(format='png').replace('\', '/') | 'doctest-output/spam.gv.png' | >>> dot.render(outfile='spam.svg').replace('\', '/') | 'doctest-output/spam.svg' | | Note: | The layout command is started from the directory of ``filepath``, | so that references to external files | (e.g. ``[image=images/camelot.png]``) | can be given as paths relative to the DOT source file. | | view(self, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, cleanup: bool = False, quiet: bool = False, quiet_view: bool = False) -> str | Save the source to file, open the rendered result in a viewer. | | Convenience short-cut for running ``.render(view=True)``. | | Args: | filename: Filename for saving the source | (defaults to ``name`` + ``'.gv'``). | directory: (Sub)directory for source saving and rendering. | cleanup (bool): Delete the source file after successful rendering. | quiet (bool): Suppress ``stderr`` output from the layout subprocess. | quiet_view (bool): Suppress ``stderr`` output | from the viewer process (ineffective on Windows). | | Returns: | The (possibly relative) path of the rendered file. | | Raises: | graphviz.ExecutableNotFound: If the Graphviz executable | is not found. | graphviz.CalledProcessError: If the exit status is non-zero. | RuntimeError: If opening the viewer is not supported. | | Short-cut method for calling :meth:`.render` with ``view=True``. | | Note: | There is no option to wait for the application to close, | and no way to retrieve the application's exit status. | | ---------------------------------------------------------------------- | Methods inherited from graphviz.saving.Save: | | save(self, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, *, skip_existing: Optional[bool] = False) -> str | Save the DOT source to file. Ensure the file ends with a newline. | | Args: | filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) | directory: (Sub)directory for source saving and rendering. | skip_existing: Skip write if file exists (default: ``False``). | | Returns: | The (possibly relative) path of the saved source file. | | ---------------------------------------------------------------------- | Readonly properties inherited from graphviz.saving.Save: | | filepath | The target path for saving the DOT source file. | | ---------------------------------------------------------------------- | Data and other attributes inherited from graphviz.saving.Save: | | directory = '' | | ---------------------------------------------------------------------- | Methods inherited from graphviz.piping.Pipe: | | pipe(self, format: Optional[str] = None, renderer: Optional[str] = None, formatter: Optional[str] = None, neato_no_op: Union[bool, int, NoneType] = None, quiet: bool = False, *, engine: Optional[str] = None, encoding: Optional[str] = None) -> Union[bytes, str] | Return the source piped through the Graphviz layout command. | | Args: | format: The output format used for rendering | (``'pdf'``, ``'png'``, etc.). | renderer: The output renderer used for rendering | (``'cairo'``, ``'gd'``, ...). | formatter: The output formatter used for rendering | (``'cairo'``, ``'gd'``, ...). | neato_no_op: Neato layout engine no-op flag. | quiet (bool): Suppress ``stderr`` output | from the layout subprocess. | engine: Layout engine for rendering | (``'dot'``, ``'neato'``, ...). | encoding: Encoding for decoding the stdout. | | Returns: | Bytes or if encoding is given decoded string | (stdout of the layout command). | | Raises: | ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` | are unknown. | graphviz.RequiredArgumentError: If ``formatter`` is given | but ``renderer`` is None. | graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable | is not found. | graphviz.CalledProcessError: If the returncode (exit status) | of the rendering ``dot`` subprocess is non-zero. | | Example: | >>> doctest_mark_exe() | >>> import graphviz | >>> source = 'graph { spam }' | >>> graphviz.Source(source, format='svg').pipe()[:14] | b'>> graphviz.Source(source, format='svg').pipe(encoding='ascii')[:14] | '>> graphviz.Source(source, format='svg').pipe(encoding='utf-8')[:14] | ' 'graphviz.Source' | Return a new :class:`.Source` instance with the source | piped through the Graphviz *unflatten* preprocessor. | | Args: | stagger: Stagger the minimum length | of leaf edges between 1 and this small integer. | fanout: Fanout nodes with indegree = outdegree = 1 | when staggering (requires ``stagger``). | chain: Form disconnected nodes into chains | of up to this many nodes. | | Returns: | Prepocessed DOT source code (improved layout aspect ratio). | | Raises: | graphviz.RequiredArgumentError: If ``fanout`` is given | but ``stagger`` is None. | graphviz.ExecutableNotFound: If the Graphviz ``unflatten`` executable | is not found. | graphviz.CalledProcessError: If the returncode (exit status) | of the unflattening 'unflatten' subprocess is non-zero. | | See also: | Upstream documentation: | https://www.graphviz.org/pdf/unflatten.1.pdf | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.encoding.Encoding: | | encoding | The encoding for the saved source file. | | ---------------------------------------------------------------------- | Methods inherited from graphviz.base.Base: | | __str__(self) -> str | The DOT source code as string. | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.engines.Engine: | | engine | The layout engine used for rendering | (``'dot'``, ``'neato'``, ...). | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.formats.Format: | | format | The output format used for rendering | (``'pdf'``, ``'png'``, ...). | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.renderers.Renderer: | | renderer | The output renderer used for rendering | (``'cairo'``, ``'gd'``, ...). | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.formatters.Formatter: | | formatter | The output formatter used for rendering | (``'cairo'``, ``'gd'``, ...). | | ---------------------------------------------------------------------- | Methods inherited from graphviz.copying.CopyBase: | | copy(self) | Return a copied instance of the object. | | Returns: | An independent copy of the current object. Digraph """"""" Partially symntax-highlighed: https://github.com/xflr6/graphviz/blob/master/docs/api.rst#digraph-1 .. doctest:: >>> import graphviz >>> help(graphviz.Digraph) # doctest: +NORMALIZE_WHITESPACE +SKIP Help on class Digraph in module graphviz.graphs: class Digraph(graphviz.dot.DigraphSyntax, BaseGraph) | Digraph(name: Optional[str] = None, comment: Optional[str] = None, filename=None, directory=None, format: Optional[str] = None, engine: Optional[str] = None, encoding: Optional[str] = 'utf-8', graph_attr=None, node_attr=None, edge_attr=None, body=None, strict: bool = False, *, renderer: Optional[str] = None, formatter: Optional[str] = None) -> None | | Directed graph source code in the DOT language. | | Args: | name: Graph name used in the source code. | comment: Comment added to the first line of the source. | filename: Filename for saving the source | (defaults to ``name`` + ``'.gv'``). | directory: (Sub)directory for source saving and rendering. | format: Rendering output format (``'pdf'``, ``'png'``, ...). | engine: Layout command used (``'dot'``, ``'neato'``, ...). | renderer: Output renderer used (``'cairo'``, ``'gd'``, ...). | formatter: Output formatter used (``'cairo'``, ``'gd'``, ...). | encoding: Encoding for saving the source. | graph_attr: Mapping of ``(attribute, value)`` pairs for the graph. | node_attr: Mapping of ``(attribute, value)`` pairs set for all nodes. | edge_attr: Mapping of ``(attribute, value)`` pairs set for all edges. | body: Iterable of verbatim lines (including their final newline) | to add to the graph ``body``. | strict (bool): Rendering should merge multi-edges. | | Note: | All parameters are `optional` and can be changed under their | corresponding attribute name after instance creation. | | Method resolution order: | Digraph | graphviz.dot.DigraphSyntax | BaseGraph | graphviz.dot.Dot | graphviz.quoting.Quote | graphviz.rendering.Render | graphviz.saving.Save | graphviz.jupyter_integration.JupyterIntegration | graphviz.piping.Pipe | graphviz.unflattening.Unflatten | graphviz.encoding.Encoding | graphviz.base.Base | graphviz.base.LineIterable | graphviz.backend.mixins.Render | graphviz.backend.mixins.Pipe | graphviz.parameters.mixins.Parameters | graphviz.parameters.engines.Engine | graphviz.parameters.formats.Format | graphviz.parameters.renderers.Renderer | graphviz.parameters.formatters.Formatter | graphviz.parameters.base.ParameterBase | graphviz.copying.CopyBase | graphviz.backend.mixins.View | graphviz.backend.mixins.Unflatten | builtins.object | | Readonly properties defined here: | | directed | ``True`` | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | __annotations__ = {} | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.dot.DigraphSyntax: | | __dict__ | dictionary for instance variables | | __weakref__ | list of weak references to the object | | ---------------------------------------------------------------------- | Methods inherited from BaseGraph: | | __init__(self, name: Optional[str] = None, comment: Optional[str] = None, filename=None, directory=None, format: Optional[str] = None, engine: Optional[str] = None, encoding: Optional[str] = 'utf-8', graph_attr=None, node_attr=None, edge_attr=None, body=None, strict: bool = False, *, renderer: Optional[str] = None, formatter: Optional[str] = None) -> None | Initialize self. See help(type(self)) for accurate signature. | | ---------------------------------------------------------------------- | Readonly properties inherited from BaseGraph: | | source | The generated DOT source code as string. | | ---------------------------------------------------------------------- | Methods inherited from graphviz.dot.Dot: | | __iter__(self, subgraph: bool = False) -> Iterator[str] | Yield the DOT source code line by line (as graph or subgraph). | | Yields: Line ending with a newline (``'\n'``). | | attr(self, kw: Optional[str] = None, _attributes=None, **attrs) -> None | Add a general or graph/node/edge attribute statement. | | Args: | kw: Attributes target | (``None`` or ``'graph'``, ``'node'``, ``'edge'``). | attrs: Attributes to be set (must be strings, may be empty). | | See the :ref:`usage examples in the User Guide `. | | clear(self, keep_attrs: bool = False) -> None | Reset content to an empty body, clear graph/node/egde_attr mappings. | | Args: | keep_attrs (bool): preserve graph/node/egde_attr mappings | | edge(self, tail_name: str, head_name: str, label: Optional[str] = None, _attributes=None, **attrs) -> None | Create an edge between two nodes. | | Args: | tail_name: Start node identifier | (format: ``node[:port[:compass]]``). | head_name: End node identifier | (format: ``node[:port[:compass]]``). | label: Caption to be displayed near the edge. | attrs: Any additional edge attributes (must be strings). | | Note: | The ``tail_name`` and ``head_name`` strings are separated | by (optional) colon(s) into ``node`` name, ``port`` name, | and ``compass`` (e.g. ``sw``). | See :ref:`details in the User Guide `. | | Attention: | When rendering ``label``, backslash-escapes | and strings of the form ``<...>`` have a special meaning. | See the sections :ref:`backslash-escapes` and | :ref:`quoting-and-html-like-labels` in the user guide for details. | | edges(self, tail_head_iter) -> None | Create a bunch of edges. | | Args: | tail_head_iter: Iterable of ``(tail_name, head_name)`` pairs | (format:``node[:port[:compass]]``). | | | Note: | The ``tail_name`` and ``head_name`` strings are separated | by (optional) colon(s) into ``node`` name, ``port`` name, | and ``compass`` (e.g. ``sw``). | See :ref:`details in the User Guide `. | | node(self, name: str, label: Optional[str] = None, _attributes=None, **attrs) -> None | Create a node. | | Args: | name: Unique identifier for the node inside the source. | label: Caption to be displayed (defaults to the node ``name``). | attrs: Any additional node attributes (must be strings). | | Attention: | When rendering ``label``, backslash-escapes | and strings of the form ``<...>`` have a special meaning. | See the sections :ref:`backslash-escapes` and | :ref:`quoting-and-html-like-labels` in the user guide for details. | | subgraph(self, graph=None, name: Optional[str] = None, comment: Optional[str] = None, graph_attr=None, node_attr=None, edge_attr=None, body=None) | Add the current content of the given sole ``graph`` argument | as subgraph or return a context manager | returning a new graph instance | created with the given (``name``, ``comment``, etc.) arguments | whose content is added as subgraph | when leaving the context manager's ``with``-block. | | Args: | graph: An instance of the same kind | (:class:`.Graph`, :class:`.Digraph`) as the current graph | (sole argument in non-with-block use). | name: Subgraph name (``with``-block use). | comment: Subgraph comment (``with``-block use). | graph_attr: Subgraph-level attribute-value mapping | (``with``-block use). | node_attr: Node-level attribute-value mapping | (``with``-block use). | edge_attr: Edge-level attribute-value mapping | (``with``-block use). | body: Verbatim lines to add to the subgraph ``body`` | (``with``-block use). | | See the :ref:`usage examples in the User Guide `. | | When used as a context manager, the returned new graph instance | uses ``strict=None`` and the parent graph's values | for ``directory``, ``format``, ``engine``, and ``encoding`` by default. | | Note: | If the ``name`` of the subgraph begins with | ``'cluster'`` (all lowercase) | the layout engine will treat it as a special cluster subgraph. | | ---------------------------------------------------------------------- | Methods inherited from graphviz.rendering.Render: | | render(self, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, view: bool = False, cleanup: bool = False, format: Optional[str] = None, renderer: Optional[str] = None, formatter: Optional[str] = None, neato_no_op: Union[bool, int, NoneType] = None, quiet: bool = False, quiet_view: bool = False, *, outfile: Union[os.PathLike, str, NoneType] = None, engine: Optional[str] = None, raise_if_result_exists: bool = False, overwrite_source: bool = False) -> str | Save the source to file and render with the Graphviz engine. | | Args: | filename: Filename for saving the source | (defaults to ``name`` + ``'.gv'``).s | directory: (Sub)directory for source saving and rendering. | view (bool): Open the rendered result | with the default application. | cleanup (bool): Delete the source file | after successful rendering. | format: The output format used for rendering | (``'pdf'``, ``'png'``, etc.). | renderer: The output renderer used for rendering | (``'cairo'``, ``'gd'``, ...). | formatter: The output formatter used for rendering | (``'cairo'``, ``'gd'``, ...). | neato_no_op: Neato layout engine no-op flag. | quiet (bool): Suppress ``stderr`` output | from the layout subprocess. | quiet_view (bool): Suppress ``stderr`` output | from the viewer process | (implies ``view=True``, ineffective on Windows platform). | outfile: Path for the rendered output file. | engine: Layout engine for rendering | (``'dot'``, ``'neato'``, ...). | raise_if_result_exists: Raise :exc:`graphviz.FileExistsError` | if the result file exists. | overwrite_source: Allow ``dot`` to write to the file it reads from. | Incompatible with ``raise_if_result_exists``. | | Returns: | The (possibly relative) path of the rendered file. | | Raises: | ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` | are unknown. | graphviz.RequiredArgumentError: If ``formatter`` is given | but ``renderer`` is None. | ValueError: If ``outfile`` is the same file as the source file | unless ``overwite_source=True``. | graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable | is not found. | graphviz.CalledProcessError: If the returncode (exit status) | of the rendering ``dot`` subprocess is non-zero. | RuntimeError: If viewer opening is requested but not supported. | | Example: | >>> doctest_mark_exe() | >>> import graphviz | >>> dot = graphviz.Graph(name='spam', directory='doctest-output') | >>> dot.render(format='png').replace('\', '/') | 'doctest-output/spam.gv.png' | >>> dot.render(outfile='spam.svg').replace('\', '/') | 'doctest-output/spam.svg' | | Note: | The layout command is started from the directory of ``filepath``, | so that references to external files | (e.g. ``[image=images/camelot.png]``) | can be given as paths relative to the DOT source file. | | view(self, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, cleanup: bool = False, quiet: bool = False, quiet_view: bool = False) -> str | Save the source to file, open the rendered result in a viewer. | | Convenience short-cut for running ``.render(view=True)``. | | Args: | filename: Filename for saving the source | (defaults to ``name`` + ``'.gv'``). | directory: (Sub)directory for source saving and rendering. | cleanup (bool): Delete the source file after successful rendering. | quiet (bool): Suppress ``stderr`` output from the layout subprocess. | quiet_view (bool): Suppress ``stderr`` output | from the viewer process (ineffective on Windows). | | Returns: | The (possibly relative) path of the rendered file. | | Raises: | graphviz.ExecutableNotFound: If the Graphviz executable | is not found. | graphviz.CalledProcessError: If the exit status is non-zero. | RuntimeError: If opening the viewer is not supported. | | Short-cut method for calling :meth:`.render` with ``view=True``. | | Note: | There is no option to wait for the application to close, | and no way to retrieve the application's exit status. | | ---------------------------------------------------------------------- | Methods inherited from graphviz.saving.Save: | | save(self, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, *, skip_existing: Optional[bool] = False) -> str | Save the DOT source to file. Ensure the file ends with a newline. | | Args: | filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) | directory: (Sub)directory for source saving and rendering. | skip_existing: Skip write if file exists (default: ``False``). | | Returns: | The (possibly relative) path of the saved source file. | | ---------------------------------------------------------------------- | Readonly properties inherited from graphviz.saving.Save: | | filepath | The target path for saving the DOT source file. | | ---------------------------------------------------------------------- | Data and other attributes inherited from graphviz.saving.Save: | | directory = '' | | ---------------------------------------------------------------------- | Methods inherited from graphviz.piping.Pipe: | | pipe(self, format: Optional[str] = None, renderer: Optional[str] = None, formatter: Optional[str] = None, neato_no_op: Union[bool, int, NoneType] = None, quiet: bool = False, *, engine: Optional[str] = None, encoding: Optional[str] = None) -> Union[bytes, str] | Return the source piped through the Graphviz layout command. | | Args: | format: The output format used for rendering | (``'pdf'``, ``'png'``, etc.). | renderer: The output renderer used for rendering | (``'cairo'``, ``'gd'``, ...). | formatter: The output formatter used for rendering | (``'cairo'``, ``'gd'``, ...). | neato_no_op: Neato layout engine no-op flag. | quiet (bool): Suppress ``stderr`` output | from the layout subprocess. | engine: Layout engine for rendering | (``'dot'``, ``'neato'``, ...). | encoding: Encoding for decoding the stdout. | | Returns: | Bytes or if encoding is given decoded string | (stdout of the layout command). | | Raises: | ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` | are unknown. | graphviz.RequiredArgumentError: If ``formatter`` is given | but ``renderer`` is None. | graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable | is not found. | graphviz.CalledProcessError: If the returncode (exit status) | of the rendering ``dot`` subprocess is non-zero. | | Example: | >>> doctest_mark_exe() | >>> import graphviz | >>> source = 'graph { spam }' | >>> graphviz.Source(source, format='svg').pipe()[:14] | b'>> graphviz.Source(source, format='svg').pipe(encoding='ascii')[:14] | '>> graphviz.Source(source, format='svg').pipe(encoding='utf-8')[:14] | ' 'graphviz.Source' | Return a new :class:`.Source` instance with the source | piped through the Graphviz *unflatten* preprocessor. | | Args: | stagger: Stagger the minimum length | of leaf edges between 1 and this small integer. | fanout: Fanout nodes with indegree = outdegree = 1 | when staggering (requires ``stagger``). | chain: Form disconnected nodes into chains | of up to this many nodes. | | Returns: | Prepocessed DOT source code (improved layout aspect ratio). | | Raises: | graphviz.RequiredArgumentError: If ``fanout`` is given | but ``stagger`` is None. | graphviz.ExecutableNotFound: If the Graphviz ``unflatten`` executable | is not found. | graphviz.CalledProcessError: If the returncode (exit status) | of the unflattening 'unflatten' subprocess is non-zero. | | See also: | Upstream documentation: | https://www.graphviz.org/pdf/unflatten.1.pdf | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.encoding.Encoding: | | encoding | The encoding for the saved source file. | | ---------------------------------------------------------------------- | Methods inherited from graphviz.base.Base: | | __str__(self) -> str | The DOT source code as string. | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.engines.Engine: | | engine | The layout engine used for rendering | (``'dot'``, ``'neato'``, ...). | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.formats.Format: | | format | The output format used for rendering | (``'pdf'``, ``'png'``, ...). | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.renderers.Renderer: | | renderer | The output renderer used for rendering | (``'cairo'``, ``'gd'``, ...). | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.formatters.Formatter: | | formatter | The output formatter used for rendering | (``'cairo'``, ``'gd'``, ...). | | ---------------------------------------------------------------------- | Methods inherited from graphviz.copying.CopyBase: | | copy(self) | Return a copied instance of the object. | | Returns: | An independent copy of the current object. Source """""" Partially syntax-highlighted: https://github.com/xflr6/graphviz/blob/master/docs/api.rst#source-1 .. doctest:: >>> import graphviz >>> help(graphviz.Source) # doctest: +NORMALIZE_WHITESPACE +SKIP Help on class Source in module graphviz.sources: class Source(graphviz.rendering.Render, graphviz.saving.Save, graphviz.jupyter_integration.JupyterIntegration, graphviz.piping.Pipe, graphviz.unflattening.Unflatten) | Source(source: str, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, format: Optional[str] = None, engine: Optional[str] = None, encoding: Optional[str] = 'utf-8', *, renderer: Optional[str] = None, formatter: Optional[str] = None, loaded_from_path: Optional[os.PathLike] = None) -> None | | Verbatim DOT source code string to be rendered by Graphviz. | | Args: | source: The verbatim DOT source code string. | filename: Filename for saving the source (defaults to ``'Source.gv'``). | directory: (Sub)directory for source saving and rendering. | format: Rendering output format (``'pdf'``, ``'png'``, ...). | engine: Layout engine used (``'dot'``, ``'neato'``, ...). | encoding: Encoding for saving the source. | | Note: | All parameters except ``source`` are optional. All of them | can be changed under their corresponding attribute name | after instance creation. | | Method resolution order: | Source | graphviz.rendering.Render | graphviz.saving.Save | graphviz.jupyter_integration.JupyterIntegration | graphviz.piping.Pipe | graphviz.unflattening.Unflatten | graphviz.encoding.Encoding | graphviz.base.Base | graphviz.base.LineIterable | graphviz.backend.mixins.Render | graphviz.backend.mixins.Pipe | graphviz.parameters.mixins.Parameters | graphviz.parameters.engines.Engine | graphviz.parameters.formats.Format | graphviz.parameters.renderers.Renderer | graphviz.parameters.formatters.Formatter | graphviz.parameters.base.ParameterBase | graphviz.copying.CopyBase | graphviz.backend.mixins.View | graphviz.backend.mixins.Unflatten | builtins.object | | Methods defined here: | | __init__(self, source: str, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, format: Optional[str] = None, engine: Optional[str] = None, encoding: Optional[str] = 'utf-8', *, renderer: Optional[str] = None, formatter: Optional[str] = None, loaded_from_path: Optional[os.PathLike] = None) -> None | Initialize self. See help(type(self)) for accurate signature. | | __iter__(self) -> Iterator[str] | Yield the DOT source code read from file line by line. | | Yields: Line ending with a newline (``'\n'``). | | save(self, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, *, skip_existing: Optional[bool] = None) -> str | Save the DOT source to file. Ensure the file ends with a newline. | | Args: | filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) | directory: (Sub)directory for source saving and rendering. | skip_existing: Skip write if file exists (default: ``None``). | By default skips if instance was loaded from the target path: | ``.from_file(self.filepath)``. | | Returns: | The (possibly relative) path of the saved source file. | | ---------------------------------------------------------------------- | Class methods defined here: | | from_file(filename: Union[os.PathLike, str], directory: Union[os.PathLike, str, NoneType] = None, format: Optional[str] = None, engine: Optional[str] = None, encoding: Optional[str] = 'utf-8', renderer: Optional[str] = None, formatter: Optional[str] = None) -> 'Source' from builtins.type | Return an instance with the source string read from the given file. | | Args: | filename: Filename for loading/saving the source. | directory: (Sub)directory for source loading/saving and rendering. | format: Rendering output format (``'pdf'``, ``'png'``, ...). | engine: Layout command used (``'dot'``, ``'neato'``, ...). | encoding: Encoding for loading/saving the source. | | ---------------------------------------------------------------------- | Readonly properties defined here: | | source | The DOT source code as string. | | Normalizes so that the string always ends in a final newline. | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | __annotations__ = {'_loaded_from_path': typing.Optional[os.PathLike], ... | | ---------------------------------------------------------------------- | Methods inherited from graphviz.rendering.Render: | | render(self, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, view: bool = False, cleanup: bool = False, format: Optional[str] = None, renderer: Optional[str] = None, formatter: Optional[str] = None, neato_no_op: Union[bool, int, NoneType] = None, quiet: bool = False, quiet_view: bool = False, *, outfile: Union[os.PathLike, str, NoneType] = None, engine: Optional[str] = None, raise_if_result_exists: bool = False, overwrite_source: bool = False) -> str | Save the source to file and render with the Graphviz engine. | | Args: | filename: Filename for saving the source | (defaults to ``name`` + ``'.gv'``).s | directory: (Sub)directory for source saving and rendering. | view (bool): Open the rendered result | with the default application. | cleanup (bool): Delete the source file | after successful rendering. | format: The output format used for rendering | (``'pdf'``, ``'png'``, etc.). | renderer: The output renderer used for rendering | (``'cairo'``, ``'gd'``, ...). | formatter: The output formatter used for rendering | (``'cairo'``, ``'gd'``, ...). | neato_no_op: Neato layout engine no-op flag. | quiet (bool): Suppress ``stderr`` output | from the layout subprocess. | quiet_view (bool): Suppress ``stderr`` output | from the viewer process | (implies ``view=True``, ineffective on Windows platform). | outfile: Path for the rendered output file. | engine: Layout engine for rendering | (``'dot'``, ``'neato'``, ...). | raise_if_result_exists: Raise :exc:`graphviz.FileExistsError` | if the result file exists. | overwrite_source: Allow ``dot`` to write to the file it reads from. | Incompatible with ``raise_if_result_exists``. | | Returns: | The (possibly relative) path of the rendered file. | | Raises: | ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` | are unknown. | graphviz.RequiredArgumentError: If ``formatter`` is given | but ``renderer`` is None. | ValueError: If ``outfile`` is the same file as the source file | unless ``overwite_source=True``. | graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable | is not found. | graphviz.CalledProcessError: If the returncode (exit status) | of the rendering ``dot`` subprocess is non-zero. | RuntimeError: If viewer opening is requested but not supported. | | Example: | >>> doctest_mark_exe() | >>> import graphviz | >>> dot = graphviz.Graph(name='spam', directory='doctest-output') | >>> dot.render(format='png').replace('\', '/') | 'doctest-output/spam.gv.png' | >>> dot.render(outfile='spam.svg').replace('\', '/') | 'doctest-output/spam.svg' | | Note: | The layout command is started from the directory of ``filepath``, | so that references to external files | (e.g. ``[image=images/camelot.png]``) | can be given as paths relative to the DOT source file. | | view(self, filename: Union[os.PathLike, str, NoneType] = None, directory: Union[os.PathLike, str, NoneType] = None, cleanup: bool = False, quiet: bool = False, quiet_view: bool = False) -> str | Save the source to file, open the rendered result in a viewer. | | Convenience short-cut for running ``.render(view=True)``. | | Args: | filename: Filename for saving the source | (defaults to ``name`` + ``'.gv'``). | directory: (Sub)directory for source saving and rendering. | cleanup (bool): Delete the source file after successful rendering. | quiet (bool): Suppress ``stderr`` output from the layout subprocess. | quiet_view (bool): Suppress ``stderr`` output | from the viewer process (ineffective on Windows). | | Returns: | The (possibly relative) path of the rendered file. | | Raises: | graphviz.ExecutableNotFound: If the Graphviz executable | is not found. | graphviz.CalledProcessError: If the exit status is non-zero. | RuntimeError: If opening the viewer is not supported. | | Short-cut method for calling :meth:`.render` with ``view=True``. | | Note: | There is no option to wait for the application to close, | and no way to retrieve the application's exit status. | | ---------------------------------------------------------------------- | Readonly properties inherited from graphviz.saving.Save: | | filepath | The target path for saving the DOT source file. | | ---------------------------------------------------------------------- | Data and other attributes inherited from graphviz.saving.Save: | | directory = '' | | ---------------------------------------------------------------------- | Methods inherited from graphviz.piping.Pipe: | | pipe(self, format: Optional[str] = None, renderer: Optional[str] = None, formatter: Optional[str] = None, neato_no_op: Union[bool, int, NoneType] = None, quiet: bool = False, *, engine: Optional[str] = None, encoding: Optional[str] = None) -> Union[bytes, str] | Return the source piped through the Graphviz layout command. | | Args: | format: The output format used for rendering | (``'pdf'``, ``'png'``, etc.). | renderer: The output renderer used for rendering | (``'cairo'``, ``'gd'``, ...). | formatter: The output formatter used for rendering | (``'cairo'``, ``'gd'``, ...). | neato_no_op: Neato layout engine no-op flag. | quiet (bool): Suppress ``stderr`` output | from the layout subprocess. | engine: Layout engine for rendering | (``'dot'``, ``'neato'``, ...). | encoding: Encoding for decoding the stdout. | | Returns: | Bytes or if encoding is given decoded string | (stdout of the layout command). | | Raises: | ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` | are unknown. | graphviz.RequiredArgumentError: If ``formatter`` is given | but ``renderer`` is None. | graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable | is not found. | graphviz.CalledProcessError: If the returncode (exit status) | of the rendering ``dot`` subprocess is non-zero. | | Example: | >>> doctest_mark_exe() | >>> import graphviz | >>> source = 'graph { spam }' | >>> graphviz.Source(source, format='svg').pipe()[:14] | b'>> graphviz.Source(source, format='svg').pipe(encoding='ascii')[:14] | '>> graphviz.Source(source, format='svg').pipe(encoding='utf-8')[:14] | ' 'graphviz.Source' | Return a new :class:`.Source` instance with the source | piped through the Graphviz *unflatten* preprocessor. | | Args: | stagger: Stagger the minimum length | of leaf edges between 1 and this small integer. | fanout: Fanout nodes with indegree = outdegree = 1 | when staggering (requires ``stagger``). | chain: Form disconnected nodes into chains | of up to this many nodes. | | Returns: | Prepocessed DOT source code (improved layout aspect ratio). | | Raises: | graphviz.RequiredArgumentError: If ``fanout`` is given | but ``stagger`` is None. | graphviz.ExecutableNotFound: If the Graphviz ``unflatten`` executable | is not found. | graphviz.CalledProcessError: If the returncode (exit status) | of the unflattening 'unflatten' subprocess is non-zero. | | See also: | Upstream documentation: | https://www.graphviz.org/pdf/unflatten.1.pdf | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.encoding.Encoding: | | encoding | The encoding for the saved source file. | | ---------------------------------------------------------------------- | Methods inherited from graphviz.base.Base: | | __str__(self) -> str | The DOT source code as string. | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.base.LineIterable: | | __dict__ | dictionary for instance variables | | __weakref__ | list of weak references to the object | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.engines.Engine: | | engine | The layout engine used for rendering | (``'dot'``, ``'neato'``, ...). | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.formats.Format: | | format | The output format used for rendering | (``'pdf'``, ``'png'``, ...). | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.renderers.Renderer: | | renderer | The output renderer used for rendering | (``'cairo'``, ``'gd'``, ...). | | ---------------------------------------------------------------------- | Data descriptors inherited from graphviz.parameters.formatters.Formatter: | | formatter | The output formatter used for rendering | (``'cairo'``, ``'gd'``, ...). | | ---------------------------------------------------------------------- | Methods inherited from graphviz.copying.CopyBase: | | copy(self) | Return a copied instance of the object. | | Returns: | An independent copy of the current object. graphviz-0.20.2/docs/attributes.rst0000666000000000000000000000302314550602024015773 0ustar rootrootAttributes ---------- To directly add DOT_ ``att_stmt`` attribute statements, call the :meth:`~.Graph.attr` method of the :class:`.Graph` or :class:`.Digraph` instance with the wanted target as first argument and the attributes as keyword args. .. hint:: Attribute statements affect all **later** graphs, nodes, or edges within the same (sub-)graph. .. doctest:: >>> import graphviz >>> ni = graphviz.Graph('ni') # doctest: +NO_EXE >>> ni.attr('node', shape='rarrow') >>> ni.node('1', 'Ni!') >>> ni.node('2', 'Ni!') >>> ni.node('3', 'Ni!', shape='egg') >>> ni.attr('node', shape='star') >>> ni.node('4', 'Ni!') >>> ni.node('5', 'Ni!') If you omit the first :meth:`~.Graph.attr` argument, the method can be used to set arbitrary attributes as key-value pairs targeting the current (sub-)graph (e.g. for ``rankdir``, ``label``, or setting ``rank='same'`` within a subgraph context, :ref:`example `): .. doctest:: >>> ni.attr(rankdir='LR') # doctest: +NO_EXE >>> ni.edges(['12', '23', '34', '45']) >>> print(ni.source) # doctest: +NORMALIZE_WHITESPACE graph ni { node [shape=rarrow] 1 [label="Ni!"] 2 [label="Ni!"] 3 [label="Ni!" shape=egg] node [shape=star] 4 [label="Ni!"] 5 [label="Ni!"] rankdir=LR 1 -- 2 2 -- 3 3 -- 4 4 -- 5 } .. image:: _static/ni.svg :align: center .. include:: _links.rst graphviz-0.20.2/docs/basic_usage.rst0000666000000000000000000000663414550602024016065 0ustar rootrootBasic usage ----------- The :doc:`graphviz ` package provides two main classes: :class:`graphviz.Graph` and :class:`graphviz.Digraph`. They create graph descriptions in the DOT_ language for undirected and directed graphs respectively. They have the same :doc:`API `. .. hint:: :class:`.Graph` and :class:`.Digraph` produce different DOT syntax and have different values for :attr:`~.Graph.directed`. Create a graph by instantiating a new :class:`.Graph` or :class:`.Digraph` object: .. doctest:: >>> import graphviz >>> dot = graphviz.Digraph('round-table', comment='The Round Table') # doctest: +NO_EXE >>> dot # doctest: +ELLIPSIS Their constructors allow to set the graph's :attr:`~.Graph.name` identifier, the :attr:`~.Graph.filename` for the DOT source and the rendered graph, an optional :attr:`~.Graph.comment` for the first source code line, etc. Add nodes and edges to the graph object using its :meth:`~.Graph.node` and :meth:`~.Graph.edge` or :meth:`~.Graph.edges` methods: .. doctest:: >>> dot.node('A', 'King Arthur') # doctest: +NO_EXE >>> dot.node('B', 'Sir Bedevere the Wise') >>> dot.node('L', 'Sir Lancelot the Brave') >>> dot.edges(['AB', 'AL']) >>> dot.edge('B', 'L', constraint='false') The :meth:`~.Graph.node` method takes a ``name`` identifier as first argument and an optional ``label``. The :meth:`~.Graph.edge` method takes the names of start node and end node, while :meth:`~.Graph.edges` takes an iterable of name pairs. Keyword arguments are turned into (node and edge) attributes (see extensive `Graphviz docs on available attributes `_). Check the generated DOT source code: .. doctest:: >>> print(dot.source) # doctest: +NORMALIZE_WHITESPACE +NO_EXE // The Round Table digraph "round-table" { A [label="King Arthur"] B [label="Sir Bedevere the Wise"] L [label="Sir Lancelot the Brave"] A -> B A -> L B -> L [constraint=false] } Use the :meth:`~.Graph.render` method to save the DOT source code and render it with the default ``dot`` `layout engine `_ (see :ref:`below ` for using other layout engines). .. attention:: Skip/ignore any ``doctest_mark_exe()`` lines in documentation code examples. .. doctest:: >>> doctest_mark_exe() # skip this line >>> dot.render(directory='doctest-output').replace('\\', '/') 'doctest-output/round-table.gv.pdf' Passing ``view=True`` will automatically open the resulting (PDF, SVG, PNG, etc.) file with your system's default viewer application for the rendered file type. .. doctest:: >>> doctest_mark_exe() # skip this line >>> dot.render(directory='doctest-output', view=True) # doctest: +SKIP 'doctest-output/round-table.gv.pdf' .. image:: _static/round-table.svg :align: center .. include:: _links.rst .. attention:: Backslash-escapes and strings of the form ``<...>`` have a special meaning in the DOT_ language and are currently passed on as is by this library. If you need to render arbitrary strings literally (e.g. from user input), consider wrapping them with the :func:`graphviz.escape` function first. See the sections on :ref:`backslash-escapes` and :ref:`quoting-and-html-like-labels` below for details. graphviz-0.20.2/docs/changelog.rst0000666000000000000000000000005714135133776015554 0ustar rootroot.. _changelog: .. include:: ../CHANGES.rst graphviz-0.20.2/docs/conf.py0000666000000000000000000000572214575735742014407 0ustar rootroot# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath(os.pardir)) import graphviz # -- Project information ----------------------------------------------------- project = 'graphviz' copyright = '2013-2024, Sebastian Bank' author = 'Sebastian Bank' # The full version, including alpha/beta/rc tags release = '0.20.2' # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', 'sphinx_autodoc_typehints', # https://github.com/agronholm/sphinx-autodoc-typehints/issues/15 'sphinx.ext.viewcode', ] doctest_global_setup = '''\ import doctest as _doctest _doctest.register_optionflag('NO_EXE') def doctest_mark_exe(**kwargs): pass ''' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'py': ('https://docs.python.org/3', None), } graphviz-0.20.2/docs/custom_dot.rst0000666000000000000000000000214714151056254016000 0ustar rootrootCustom DOT statements --------------------- To add arbitrary statements to the created DOT_ source, you can use the :attr:`~.Graph.body` attribute of :class:`.Graph` and :class:`.Digraph` objects. It holds the verbatim :class:`list` of (:class:`str`) lines to be written to the source file (including their final newline). Use its ``append()`` or ``extend()`` method: .. doctest:: >>> import graphviz >>> rt = graphviz.Digraph(comment='The Round Table') # doctest: +NO_EXE >>> rt.body.append('\t"King Arthur" -> {\n\t\t"Sir Bedevere", "Sir Lancelot"\n\t}\n') >>> rt.edge('Sir Bedevere', 'Sir Lancelot', constraint='false') >>> print(rt.source) # doctest: +NORMALIZE_WHITESPACE // The Round Table digraph { "King Arthur" -> { "Sir Bedevere", "Sir Lancelot" } "Sir Bedevere" -> "Sir Lancelot" [constraint=false] } .. attention:: Note that you might need to correctly quote/escape identifiers and strings containing whitespace or other special characters when using this method. .. include:: _links.rst graphviz-0.20.2/docs/development.rst0000666000000000000000000001617414550602024016142 0ustar rootroot.. _development: Development =========== |PyPI version| |License| |Supported Python| |Wheel| |Downloads| - GitHub: https://github.com/xflr6/graphviz - Changelog: https://graphviz.readthedocs.io/en/latest/changelog.html - Issue Tracker: https://github.com/xflr6/graphviz/issues Installation ------------ |Binder-HEAD| Development environment **binder** : https://mybinder.org/v2/gh/xflr6/graphviz/HEAD Local installation ^^^^^^^^^^^^^^^^^^ Install in a venv_ in development mode (includes all ``extras_require``): .. code:: bash $ git clone https://github.com/xflr6/graphviz.git $ cd graphviz $ python -m venv .venv $ source .venv/bin/activate $ python -m pip install -r requirements.txt .. admonition:: Platform: Windows ``.venv\Script\activate.bat`` to replace ``source .venv/bin/activate`` .. hint:: alternatively: ``pip install -e .[dev,test,docs]`` (same as ``pip install -r requirements.txt``) Tests ----- |Build| |Codecov| - GitHub Actions `Build workflow `_ (Python 3.8 to 3.11, experimental: PyPy 3.8 to 3.9) - Codecov `test coverage `_ (`main branch `_) **Run the tests** (in the current environment): .. code:: bash $ python run-tests.py Run **only tests** that are expected to ``PASS`` or ``XFAIL`` **without Graphviz** executables: .. code:: bash $ python run-tests.py --skip-exe **Run the tests** with tox_ (**installing** into a virtualenv_ or many of them): .. code:: bash $ python -m tox **Run the static type checker** (pytype_, supported `platforms `_ and `Python versions `_): .. code:: bash $ pip install pytype $ pytype **Run the code linter** (flake8_): .. code:: bash $ python lint-code.py Documentation ------------- |Readthedocs-stable| |Readthedocs-latest| - Read the Docs Project Home: https://readthedocs.org/projects/graphviz/ - stable: https://graphviz.readthedocs.io - latest: https://graphviz.readthedocs.io/en/latest/ **Build the documentation** with sphinx_ and sphinx-rtd-theme_ (in the current environment): .. code:: bash $ python build-docs.py Overview -------- Use ``help()`` in the REPL to shows/structure methods and attributes in dependency order: - Introduction: https://graphviz.readthedocs.io/en/latest/api.html#online-help-internal - ``Graph``: https://github.com/xflr6/graphviz/blob/master/docs/api.rst#graph-1 - ``Digraph``: https://github.com/xflr6/graphviz/blob/master/docs/api.rst#digraph-1 - ``Source``: https://github.com/xflr6/graphviz/blob/master/docs/api.rst#source-1 .. tip:: In the above, cooperative multiple inheritance classes reveal their (diamond) MRO structure and methods are shown in **method resolution order** (MRO), which should be an extension of their dependency relation... TLDR; you might find this presentation helps to follow the implementation. .. include:: _links.rst .. |PyPI version| image:: https://img.shields.io/pypi/v/graphviz.svg :target: https://pypi.org/project/graphviz/ :alt: Latest PyPI Version .. |License| image:: https://img.shields.io/pypi/l/graphviz.svg :target: https://github.com/xflr6/graphviz/blob/master/LICENSE.txt :alt: License .. |Supported Python| image:: https://img.shields.io/pypi/pyversions/graphviz.svg :target: https://pypi.org/project/graphviz/ :alt: Supported Python Versions .. |Wheel| image:: https://img.shields.io/pypi/wheel/graphviz.svg :target: https://pypi.org/project/graphviz/ :alt: Wheel format .. |Downloads| image:: https://img.shields.io/pypi/dm/graphviz.svg :target: https://pypi.org/project/graphviz/#files :alt: Monthly downloads .. |Build| image:: https://github.com/xflr6/graphviz/actions/workflows/build.yaml/badge.svg?branch=master :target: https://github.com/xflr6/graphviz/actions/workflows/build.yaml?query=branch%3Amaster :alt: Build .. |Codecov| image:: https://codecov.io/gh/xflr6/graphviz/branch/master/graph/badge.svg :target: https://codecov.io/gh/xflr6/graphviz :alt: Codecov .. |Readthedocs-stable| image:: https://readthedocs.org/projects/graphviz/badge/?version=stable :target: https://graphviz.readthedocs.io/en/stable/ :alt: Readthedocs (stable) .. |Readthedocs-latest| image:: https://readthedocs.org/projects/graphviz/badge/?version=latest :target: https://graphviz.readthedocs.io/en/latest/ :alt: Readthedocs (latest) .. |Binder-HEAD| image:: https://img.shields.io/badge/launch-binder%20(HEAD)-E66581.svg?logo=data:image/png;base64,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 :target: https://mybinder.org/v2/gh/xflr6/graphviz/HEAD :alt: Binder (HEAD) graphviz-0.20.2/docs/engines.rst0000666000000000000000000000100014151056254015233 0ustar rootrootEngines ------- To use a different `layout engine `_ than the default ``dot`` when rendering your graph, you can use the ``engine`` argument on the constructor of :class:`.Graph` or :class:`.Digraph`. .. doctest:: >>> import graphviz # doctest: +NO_EXE >>> g = graphviz.Graph(engine='neato') You can also change the :attr:`~.Graph.engine` attribute on an existing instance: .. doctest:: >>> g.engine = 'circo' # doctest: +NO_EXE .. include:: _links.rst graphviz-0.20.2/docs/escapes.rst0000666000000000000000000000535614550602024015243 0ustar rootrootBackslash escapes ----------------- The Graphviz_ layout `engines `_ support a number of `escape sequences `_ such as ``\n``, ``\l``, ``\r`` (for placement of multi-line labels: *centered*, *left*-justified, *right*-justified) and ``\N``, ``\G``, ``\L`` (expanded to the current *node* name, *graph* name, object *label*). To be able to use them from this library (e.g., for labels), backslashes in strings are (mostly) passed on **as is**. .. attention:: This means that **literal** backslashes need to be **escaped** (doubled) by the user. As the backslash is also special in Python :class:`string ` literals a **second** level of doubling is needed. E.g. ``label='\\\\'`` for a label that is rendered as single literal backlash: ``\``. .. tip:: Doubling of backslashes can be avoided by using `raw string literals`_ (``r'...'``) instead. This is similar to the solution proposed for the stdlib :mod:`re` module. See also https://en.wikipedia.org/wiki/Leaning_toothpick_syndrome. .. doctest:: >>> import graphviz # doctest: +NO_EXE >>> e = graphviz.Digraph('escapes') >>> e.node('backslash', label=r'\\') >>> e.node('multi_line', label=r'centered\nleft\lright\r') >>> print(e.source) # doctest: +NORMALIZE_WHITESPACE digraph escapes { backslash [label="\\"] multi_line [label="centered\nleft\lright\r"] } .. image:: _static/escapes.svg :align: center To disable any special character meaning in a string (e.g. from user input to be rendered literally), use the :func:`graphviz.escape` function (similar to the :func:`re.escape` function): .. doctest:: >>> bs = graphviz.Digraph('literal_backslash') # doctest: +NO_EXE >>> bs.node(graphviz.escape('\\')) >>> print(bs.source) # doctest: +NORMALIZE_WHITESPACE digraph literal_backslash { "\\" } .. doctest:: >>> doctest_mark_exe() # skip this line >>> bs.render(format='svg', directory='doctest-output').replace('\\', '/') 'doctest-output/literal_backslash.gv.svg' .. image:: _static/literal_backslash.svg :align: center .. admonition:: Historical note To prevent breaking the internal quoting mechanism, the special meaning of ``\"`` as a backslash-escaped quote has been disabled since version ``0.14`` of this library. E.g. both ``label='"'`` and ``label='\\"'`` now produce the same DOT source ``[label="\""]`` (a label that renders as a literal quote). See also `examples/graphviz-escapes.ipynb `_ (`nbviewer `_). .. include:: _links.rst graphviz-0.20.2/docs/examples.rst0000666000000000000000000000523614550602024015433 0ustar rootroot.. _examples: Examples ======== .. tip:: The following code examples are included in the ``examples/`` directory of the `source repository/distribution `_. .. note:: Most of them recreate examples from the `graphviz.org gallery`_ or the `graphviz.org documentation`_. .. include:: _links.rst hello.py -------- .. literalinclude:: ../examples/hello.py :lines: 3- .. image:: _static/hello.svg :align: center process.py ---------- .. literalinclude:: ../examples/process.py :lines: 3- .. image:: _static/process.svg :align: center fsm.py ------ .. literalinclude:: ../examples/fsm.py :lines: 3- .. image:: _static/fsm.svg :align: center .. _cluster.py: cluster.py ---------- .. literalinclude:: ../examples/cluster.py :lines: 3- .. image:: _static/cluster.svg :align: center er.py ----- .. literalinclude:: ../examples/er.py :lines: 3- .. image:: _static/er.svg :align: center unix.py ------- .. literalinclude:: ../examples/unix.py :lines: 3- .. image:: _static/unix.svg :align: center structs.py ---------- .. literalinclude:: ../examples/structs.py :lines: 3- .. image:: _static/structs.svg :align: center structs_revisited.py -------------------- .. literalinclude:: ../examples/structs_revisited.py :lines: 3- .. image:: _static/structs_revisited.svg :align: center .. _btree.py: btree.py -------- .. literalinclude:: ../examples/btree.py :lines: 3- .. image:: _static/btree.svg :align: center traffic_lights.py ----------------- .. literalinclude:: ../examples/traffic_lights.py :lines: 3- .. image:: _static/traffic_lights.svg :align: center fdpclust.py ----------- .. literalinclude:: ../examples/fdpclust.py :lines: 3- .. image:: _static/fdpclust.svg :align: center cluster_edge.py --------------- .. literalinclude:: ../examples/cluster_edge.py :lines: 3- .. image:: _static/cluster_edge.svg :align: center g_c_n.py -------- .. literalinclude:: ../examples/g_c_n.py :lines: 3- .. image:: _static/g_c_n.svg :align: center angles.py --------- .. literalinclude:: ../examples/angles.py :lines: 3- .. image:: _static/angles.svg :align: center .. _rank_same.py: rank_same.py ------------ .. literalinclude:: ../examples/rank_same.py :lines: 3- .. image:: _static/rank_same.svg :align: center colors.py --------- .. literalinclude:: ../examples/colors.py :lines: 3- .. image:: _static/colors.svg :align: center graphviz-0.20.2/docs/existing_files.rst0000666000000000000000000000351414550602024016626 0ustar rootrootExisting files -------------- To directly render an existing DOT_ source file (e.g. created with other tools), you can use the :func:`graphviz.render` function. .. doctest:: >>> doctest_mark_exe() # skip this line >>> import pathlib >>> import graphviz >>> src = 'digraph "the holy hand grenade" { rankdir=LR; 1 -> 2 -> 3 -> lob }' >>> filepath = pathlib.Path('doctest-output/the-holy-hand-grenade.gv') >>> filepath.write_text(src, encoding='ascii') 66 >>> graphviz.render('dot', 'png', filepath).replace('\\', '/') 'doctest-output/the-holy-hand-grenade.gv.png' To directly display the rendered visualization of an existing DOT_ source file inside a Jupyter `notebook `_ or `Qt Console `_, you can use :meth:`graphviz.Source.from_file` (alternative constructor): .. image:: _static/qtconsole-source.png :align: center Note that :meth:`~.Source.render` and :meth:`~.Source.view` on :class:`.Source` instances returned by :meth:`graphviz.Source.from_file` skip writing the loaded file back. The same holds for :meth:`~.Source.save`. The instances resolve default ``.save(skip_existing=None)`` to ``.save(skip_existing_run=True)`` to skip writing the read :attr:`~.Source.source` back into the same file (specifically the same path that it was loaded from). Call ``.save(skip_existing=False)`` if you want to re-write the loaded source. .. admonition:: Historical note Before version ``0.18`` of this library, :meth:`.Source.save`, :meth:`.Source.render`, and :meth:`.Source.view`, wrote the content read into source back into the file. It was advised to use :func:`graphviz.render` and :func:`graphviz.view` to directly work on files if the superfluous saving needed to be avoided. .. include:: _links.rst graphviz-0.20.2/docs/formats.rst0000666000000000000000000000130014550602024015254 0ustar rootrootFormats ------- To use a different `output file format `_ than the default PDF, you can use the ``format`` argument when creating your :class:`.Graph` or :class:`.Digraph` object: .. doctest:: >>> import graphviz >>> g = graphviz.Graph(format='png') # doctest: +NO_EXE You can also change the :attr:`~.Graph.format` attribute on an existing graph object: .. doctest:: >>> doctest_mark_exe() # skip this line >>> dot = graphviz.Digraph('hello') >>> dot.edge('hello', 'world') >>> dot.format = 'svg' >>> dot.render(directory='doctest-output').replace('\\', '/') 'doctest-output/hello.gv.svg' .. include:: _links.rst graphviz-0.20.2/docs/index.rst0000666000000000000000000000107714251463736014740 0ustar rootroot.. graphviz documentation master file .. include:: ../README.rst User Guide ========== .. toctree:: :maxdepth: 2 manual Examples ======== .. toctree:: :maxdepth: 2 examples Notebooks ========= .. toctree:: :maxdepth: 2 notebooks API Reference ============= .. toctree:: :maxdepth: 2 api Project Info ============ .. toctree:: :maxdepth: 2 changelog license Development =========== .. toctree:: :maxdepth: 2 development release_process graphviz-0.20.2/docs/installation.rst0000666000000000000000000000364214455103174016323 0ustar rootrootInstallation ------------ :doc:`graphviz ` provides a simple pure-Python interface for the Graphviz_ graph-drawing software. It runs under Python 3.8+. To install it with pip_, run the following: .. code:: bash $ pip install graphviz For a system-wide install, this typically requires administrator access. For an isolated install, you can run the same inside a :mod:`venv` or a virtualenv_. The only dependency is a working installation of Graphviz_ (`download page `_, `archived versions `_, `installation procedure for Windows `_). After installing Graphviz, make sure that its ``bin/`` subdirectory containing the ``dot`` `layout command `_ for rendering graph descriptions is on your systems' ``PATH`` (sometimes done by the installer; setting ``PATH`` on `Linux `_, `Mac `_, and `Windows `_): On the command-line, ``dot -V`` should print the version of your Graphiz installation. .. admonition:: Platform: Windows Windows users might want to check the status of known issues (gvedit.exe__, sfdp__, commands__) and consider trying an older archived version as a workaround (e.g. graphviz-2.38.msi__). __ https://gitlab.com/graphviz/graphviz/-/issues/1816 __ https://gitlab.com/graphviz/graphviz/-/issues/1269 __ https://gitlab.com/graphviz/graphviz/-/issues/1753 __ https://www2.graphviz.org/Archive/stable/windows/graphviz-2.38.msi .. admonition:: Platform: Anaconda See the downstream conda-forge_ distribution `conda-forge/python-graphviz `_ (`feedstock `_), which should automatically ``conda install`` `conda-forge/graphviz `_ (`feedstock `_) as dependency. .. include:: _links.rst graphviz-0.20.2/docs/integration_with_viewers.rst0000666000000000000000000000262214550602024020733 0ustar rootrootIntegration with viewers ------------------------ On platforms such as Windows, viewer programs opened by :meth:`~.Graph.render` with ``view=True`` (or equivalently with the :meth:`~.Graph.view` shortcut-method) might **lock** the (PDF, PNG, etc.) file for as long as the viewer is open (blocking re-rendering it with a ``Permission denied`` error). .. tip:: You can use the :func:`tempfile.mktemp` function from the stdlib :mod:`tempfile` module to render to a different file for each invocation. This avoids needing to close the viewer window each time within such an incremental workflow (and also serves to preserves the intermediate steps). .. doctest:: >>> import tempfile # doctest: +NO_EXE >>> import graphviz >>> g = graphviz.Graph() >>> g.node('spam') .. doctest:: >>> doctest_mark_exe() # skip this line >>> g.view(tempfile.mktemp('.gv')) # doctest: +SKIP 'C:\\Users\\User\\AppData\\Local\\Temp\\tmp3aoie8d0.gv.pdf' >>> g.view(tempfile.mktemp('.gv')) # doctest: +SKIP 'C:\\Users\\User\\AppData\\Local\\Temp\\tmphh4ig7a_.gv.pdf' Other options: - use a viewer that `support live updates `_ - use the `Jupyter notebook`_ or `Qt Console `_ (display the current version of the rendered graph in repeated add/render/view cycles) .. include:: _links.rst graphviz-0.20.2/docs/jupyter_notebooks.rst0000666000000000000000000000227214432726140017404 0ustar rootrootJupyter notebooks ----------------- :class:`.Graph` and :class:`.Digraph` objects have a :meth:`~.Graph._repr_mimebundle_` method so they can be rendered and displayed directly inside a `Jupyter notebook`_. For an example, check the ``examples/graphviz-notebook.ipynb`` file in the `source repository/distribution `_ (or the same notebook in `nbviewer `_). This also allows direct displaying within the `Jupyter Qt Console`_ (also `the one `_ inside `Spyder IDE`_): .. image:: _static/qtconsole.png :align: center By default :meth:`~.Graph._repr_mimebundle_` uses ``'svg'`` format. You can use the :func:`graphviz.set_jupyter_format` to override the default format that is used for displaying in IPython/Jupyter. (`example `_, `nbviewer `_). .. hint:: You can also use ``display_svg()``, ``display_png()``, or ``.display_jpeg()`` from `IPython.display`_ to display the rendered :class:`.Graph` or :class:`.Digraph` as SVG, PNG or JPEG in IPython/Jupyter. .. include:: _links.rst graphviz-0.20.2/docs/license.rst0000666000000000000000000000010114073203716015226 0ustar rootroot.. _license: License ======= .. include:: ../LICENSE.txt graphviz-0.20.2/docs/manual.rst0000666000000000000000000000141714267055466015110 0ustar rootroot.. _manual: User Guide ========== .. include:: installation.rst .. include:: basic_usage.rst .. include:: formats.rst .. include:: piped_output.rst .. include:: jupyter_notebooks.rst .. include:: styling.rst .. _attributes: .. include:: attributes.rst .. _node-ports-compass: .. include:: node_ports.rst .. _backslash-escapes: .. include:: escapes.rst .. _quoting-and-html-like-labels: .. include:: quoting.rst .. _subgraphs-clusters: .. include:: subgraphs_and_clusters.rst .. _engines: .. include:: engines.rst .. include:: neato_no_op.rst .. include:: unflatten.rst .. include:: custom_dot.rst .. _using-raw-dot: .. include:: raw_dot.rst .. include:: existing_files.rst .. include:: integration_with_viewers.rst graphviz-0.20.2/docs/neato_no_op.rst0000666000000000000000000000214214550602024016106 0ustar rootroot``neato`` no-op flag -------------------- The `neato `_ layout engine supports an additional `rendering flag `_ that allows more control over the node positioning and the edge layout via the `pos `_, `overlap `_, and `splines `_ attributes. Use the ``neato_no_op`` keyword argument of :meth:`~.Graph.render` or :meth:`~.Graph.pipe` to pass it to the layout command: .. doctest:: >>> doctest_mark_exe() # skip this line >>> import graphviz >>> n = graphviz.Digraph(name='splines', engine='neato', ... graph_attr={'splines': 'true'}, ... node_attr={'shape': 'point'}) >>> n.node('a', pos='0,0!', color='blue') >>> n.node('b', pos='100,0!', color='green') >>> n.node('c', pos='50,50!', color='red') >>> n.edge('a', 'b', pos='0,0 30,66 70,60 100,0') >>> n.render(neato_no_op=2, directory='doctest-output').replace('\\', '/') 'doctest-output/splines.gv.pdf' .. image:: _static/splines.svg :align: center .. include:: _links.rst graphviz-0.20.2/docs/node_ports.rst0000666000000000000000000000224714151056254015775 0ustar rootrootNode ports & compass -------------------- The :meth:`~.Graph.edge` and :meth:`~.Graph.edges` methods use the colon-separated ``node[:port[:compass]]`` format for ``tail`` and ``head`` nodes. This allows to specify an optional node ``port`` plus an optional ``compass`` point the edge should aim at for the given tail or head node (:ref:`example `). .. caution:: As colons are used to indicate ``port`` and ``compass`` for edges, node names containing one or more literal colons ``:`` are currently not supported. `GH #54 `_ .. tip:: There is no such restriction for the ``label`` argument, so you can work around by choosing a colon-free ``name`` together with the wanted ``label`` as demonstrated below .. doctest:: >>> import graphviz # doctest: +NO_EXE >>> cpp = graphviz.Digraph('C++') >>> cpp.node('A', 'std::string') >>> cpp.node('B', '"spam"') >>> cpp.edge('A', 'B') >>> print(cpp.source) # doctest: +NORMALIZE_WHITESPACE digraph "C++" { A [label="std::string"] B [label="\"spam\""] A -> B } graphviz-0.20.2/docs/notebooks.rst0000666000000000000000000000142314224214124015610 0ustar rootroot.. _notebooks: Notebooks ========= - Render `graphviz.org gallery`_ examples with logging: `examples/graphviz-notebook.ipynb `_ (`source `_) - `Layout engine `_ comparison: `examples/graphviz-engines.ipynb `_ (`source `_) - Example for :meth:`graphviz.set_jupyter_format`: `examples/graphviz-jupyter-format.ipynb `_ (`source `_) - Verify `escaping `_ and quoting: `examples/graphviz-escapes.ipynb `_ (`source `_) .. include:: _links.rst graphviz-0.20.2/docs/piped_output.rst0000666000000000000000000000225514550602024016334 0ustar rootrootPiped output ------------ To directly access the raw results from the Graphviz_ ``dot`` `layout command `_ as binary :class:`bytes` or as decoded :class:`str` (for plain-text formats like SVG) instead of writing to a file, use the :meth:`~.Graph.pipe` method of your :class:`.Graph` or :class:`.Digraph` object: .. doctest:: >>> import graphviz >>> h = graphviz.Graph('hello', format='svg') # doctest: +NO_EXE >>> h.edge('Hello', 'World') .. doctest:: >>> doctest_mark_exe() # skip this line >>> h.pipe(format='pdf')[:4] b'%PDF' >>> print(h.pipe(encoding='utf-8')) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS .. tip:: Because :meth:`~.Graph.pipe` returns the raw ``stdout`` from the layout subprocess by default (:class:`bytes`), you usually want to decode the return value when piping into formats like ``'svg'`` or ``'plain'``, .. caution:: The output for :meth:`~.Graph.pipe` is buffered in memory, so avoid this method if the data size is large. .. include:: _links.rst graphviz-0.20.2/docs/quoting.rst0000666000000000000000000000403714550602024015301 0ustar rootrootQuoting and HTML-like labels ---------------------------- The graph-building methods of :class:`.Graph` and :class:`.Digraph` objects automatically take care of quoting (and escaping quotes) `where needed `_ (whitespace, keywords, double quotes, etc.): .. doctest:: >>> import graphviz # doctest: +NO_EXE >>> q = graphviz.Digraph() >>> q.edge('spam', 'eggs eggs') >>> q.edge('node', '"here\'s a quote"') >>> print(q.source) # doctest: +NORMALIZE_WHITESPACE digraph { spam -> "eggs eggs" "node" -> "\"here's a quote\"" } If a string starts with ``'<'`` and ends with ``'>'``, it is passed on **as is**, i.e. without quoting/escaping: The content between the angle brackets is treated by the Graphviz_ layout `engine `_ as special **HTML string** that can be used for `HTML-like labels `_: .. doctest:: >>> h = graphviz.Graph('html_table') # doctest: +NO_EXE >>> h.node('tab', label='''< ... ... ... ... ...
leftright
>''') .. image:: _static/html_table.svg :align: center For strings that should literally begin with ``'<'`` and end with ``'>'``, use the :func:`graphviz.nohtml` function to disable the special meaning of angled parenthesis and apply normal quoting/escaping: .. doctest:: >>> d = graphviz.Digraph('diamond', format='svg') # doctest: +NO_EXE >>> d.node('diamond', label=graphviz.nohtml('<>')) >>> print(d.source) # doctest: +NORMALIZE_WHITESPACE digraph diamond { diamond [label="<>"] } .. doctest:: >>> doctest_mark_exe() # skip this line >>> d.render(directory='doctest-output').replace('\\', '/') 'doctest-output/diamond.gv.svg' .. image:: _static/diamond.svg :align: center .. admonition:: Historical note Before version ``0.8.2``, the only workaround was to add leading or trailing space (``label=' <>'``): .. include:: _links.rst graphviz-0.20.2/docs/raw_dot.rst0000666000000000000000000000274414550602024015255 0ustar rootrootUsing raw DOT ------------- To render a ready-made DOT_ source code string (instead of assembling one with the higher-level interface of :class:`.Graph` or :class:`.Digraph`), create a :class:`graphviz.Source` object holding your DOT string: .. doctest:: >>> import graphviz # doctest: +NO_EXE >>> src = graphviz.Source('digraph "the holy hand grenade" { rankdir=LR; 1 -> 2 -> 3 -> lob }') >>> src #doctest: +ELLIPSIS Use the :meth:`~.Source.render` method to save and render it: .. doctest:: >>> doctest_mark_exe() # skip this line >>> src.render('doctest-output/holy-grenade.gv').replace('\\', '/') 'doctest-output/holy-grenade.gv.pdf' .. doctest:: >>> doctest_mark_exe() # skip this line >>> src.render('doctest-output/holy-grenade.gv', view=True).replace('\\', '/') # doctest: +SKIP 'doctest-output/holy-grenade.gv.pdf' .. image:: _static/holy-grenade.svg :align: center .. hint:: Apart from lacking editing methods, :class:`.Source` objects have the same basic API as the higher-level :class:`.Graph` and :class:`.Digraph` objects (e.g. :meth:`~.Source.save`, :meth:`~.Source.render`, :meth:`~.Source.view`, :meth:`~.Source.pipe` methods, :attr:`~.Source.engine` and :attr:`~.Source.format` attributes, Jupyter notebook :meth:`~.Source._repr_mimebundle_`, etc. See :class:`API docs <.Source>`). .. include:: _links.rst graphviz-0.20.2/docs/release_process.rst0000666000000000000000000000735414575735742017023 0ustar rootrootRelease process =============== Build ----- Update build dependencies: .. code:: bash $ pip install -U setuptools wheel twine Create ``release`` branch from main: .. code:: bash $ git checkout -b release **Cleanup** (remove all untracked files and directories): .. code:: bash $ git clean -f -d -x Update ``help()`` output: .. code:: bash $ python update-help.py Set **release version** (remove ``.dev0`` from ``$MAJOR.$MINOR[.$BUGFIX]`` version): - ``docs/conf.py`` - ``graphviz/__init__.py`` - ``setup.py`` Document release: - remove ``(in development)`` from ``CHANGES.rst`` header Run the tests, lint the code, and build the documentation: .. code:: bash $ python -m tox -r -- -W error -W ignore:"The signature of":PendingDeprecationWarning # --recreate, raise error on warning $ python lint-code.py --disable-noqa $ python build-docs.py -b doctest $ python build-docs.py $ git clean -f -d -x Commit to ``release`` branch and push to ``origin``: .. code:: bash $ git add * $ git commit -m "release $MAJOR.$MINOR[.$BUGFIX]" $ git push --set-upstream origin release - Check GitHub Actions ``relase`` `Build workflow `_ - Check Codecov ``release`` build `test coverage `_ **Build** and check the release files: .. code:: bash $ python setup.py sdist bdist_wheel $ python -m twine check --strict dist/* - ``dist/graphviz-$MAJOR.$MINOR[.$BUGFIX].zip`` - ``dist/graphviz-$MAJOR.$MINOR[.$BUGFIX]-py3-none-any.whl`` If changes are needed (and go back to: **Cleanup** step): .. code:: bash $ git commit --amend --date=now Switch to main branch and merge ``release``: .. code:: bash $ git switch master $ git merge --ff-only release **Tag** with annotated release version tag: .. code:: bash $ git tag -a -m "$MAJOR.$MINOR[.$BUGFIX] release" Bump **post-release version** to ``$MAJOR.$MINOR.[.$BUGFIX].dev0``: - ``docs/conf.py`` - ``graphviz/__init__.py`` - ``setup.py`` Document post-release: - add new ``Version $MAJOR.$MINOR[.$BUGFIX] (in development)`` heading to ``CHANGES.rst`` Commit version bump to main branch: .. code:: bash $ git commit -m "bump version for development" Publish ------- Publish the release with twine_: .. code:: bash $ python -m twine upload dist/* Push main branch and push all new tags: .. code:: bash $ git push --tags Update `stable `_ branch to the latest release: .. code:: bash $ git switch stable $ git merge --ff-only $MAJOR.$MINOR[.$BUGFIX] $ git push Verify ------ Verify publication: - Check `PyPI files `_ - Check GitHub `Main page `_ - Check GitHub Actions `main branch Build workflow `_ - Check Read the Docs `builds `_ - Check `latest release notes `_ - Check `stable release notes `_ - Check ``stable`` binder: https://mybinder.org/v2/gh/xflr6/graphviz/stable Install in default environment: .. code:: bash $ pip install -U graphviz $ python -c "import graphviz; print((graphviz.__version__, graphviz.version()))" Downstream ---------- - Check downstream `conda-forge release `_ .. include:: _links.rst graphviz-0.20.2/docs/round-table.png0000666000000000000000000003531014075132170016003 0ustar rootrootPNG  IHDRcbKGD IDATxyXwos#Mܠ/ Ј3ɘ`bn&n$flld&c<L">Z1j@!(xhljΝ;L?J^^^Xx1xNSBz BFjj*?@$aXl~aiooG~~>Ξ=cǎ󰱱󑘘$&!TSS=}ᅦ#.\իW#11 :ddd ==PՈ?uuAʕ+qahZ,[ ?8.]JW[;qj*.]˗#99mmmBE4  !!A萈8|06mڄX|W1b!"@Z/*˗/=Jž={*,୷«!Ģ@ 믿iӦa֭ŠP(glܸy{DHLLDyyyaϜ9DD"=ӯRT*1z)s-* [l۱sNT}\[lc3c2C՝2П|ɉ)JNP@TfkkT*EXBBٟ7'ѧT*;t{n,͉'L$G 2}BBȰQZZ H$Atl߾o雜lus/իܩz q݄PjxzzbԨQtV@ Fcc#\\\~dd$d2_@8x ??sNzz:JKK- BAP@ OOO'00Gdd$~;򾊌DZZݻH۷#%%/rfggcݺu`ux^/1f1TWWKP6Ο?7nŦk^{SO=L8 oZzz:ϟ߫&-[w1:,{BL͏9qvBqq1hAb >d={6|||pMSR6o B97x] 9x LHOOO]t5.{B͒XTFvv6ⷔcԨQ1cСb9,!m۶yxx?{ׁ~w@.df= {dp=m֡bwd2>4/]SRR:ǜ^ MP0Dvai":@J0̜9oXR899!00롡VBtHHH@QQȕ S dxqssáCpqvĉ;닔v؁ӧO/ ;BA%=...xW燨(CW_}%(--DZi&>CO‡~+W !N-dXz O?4^yo6q?Byw /`زe" k?uV]k5HQ[[gyسg~!Bi {'NO<… ɓx'?/pD.R$dɒ%(,,̙3͛7C.  lڴ K,QXXH![-¡C_Ә8q" FQxW .СC/1rHC#*)B 477+~m888`֭HNNС~P]]>}cضmy888!քA Ĕ/ >c466bƍx饗0i$C#PTT H3_Сb@ ;---ꫯ;눊ƍaxzz J‘#G~HR?[n͛1b#ĚQ@t:N<{ȑ#˗chllĩS_ ==xGcѢECڄ 0* Jo{ŋE!!!<|}}qX8z(!J҂yaӦMHJJD":DB*+c;3f`…X`͛X,tCZ瑙gիpvvF||<_ &!F>}=Ν;ׯ3f@ll,bbbQF RQQ8w^ N)S 66Gy?T 2p9q t:#** 3f@TT8Ϗ3PVVB 77999P(ɓ `:dB**$ZW"''޺u 1i$1eL8C5 PZZb\~7n7H &Ԍ30}t :!MTBjj*lllT*m^^^ DPP ۛPSS( TUUAP 2 2 xnnn`aժU={60yd,BE!b߾}xg1vX8pǏݻwQVV`mMM mllBA"puu b1Ű燕H$OV*hhhFFRFA}}=_t:~X[[[MPP_;cǎL&Æ puxt9BB!BS(ؼy3=[3붿1~\SS*zh4T*jh4444@V_$u wRHbbݝ>>> ///Z5Z-z-ر-g}!!Q@{ۇ qFcϞ=HLL:$B+gBԄ^xK,ܹs7իWb ,_?8&!B,ظq# aݺuBd:dH$߿s:$BjA Rbܹ µkר8ªUp5 Za2lP !p]lܸx?66T1={ॗ^ԩSq!C 2ۇj\t IAD"Ftnoގ?,!)":APt*NK">> ,&W2#//8|0\ԊfccoooSpFR 4 Z-Fkooﲏ3ggg$2D9r$vvv=D?~DVȑ#l2,]-WK`RSSSNĉ8qjkkV N,_x\RjojjBssas1nnn& M"r\\\`cc77<@mm-߿Zwww~VzD~#+G;cɋ. ͟ZCѢRk[5hP__'3BB888Z ikk! /&bZ[[;䉪*(AommmthmmEDD|||: gg.ֹt-[ uix cXtժR)_pR FNuvv yǧoeX^^wUUUw *++ \g񁗗"ꯜ sjh򩫫JBmm-_pJ<@UUU)񁿿? F1c 00AAAԬ! 2 ?wո~a$ |qss{ivV Jen>x߇B@KK ?{{{>OGPPΩUB}}=JKKQVVLQ^^Zʢ pPYYJ( ?r娪j@h Bpp0BBB'\!rܺur666G@@@<1jԨ; "ZCr9a<==;\af:VEEEܹ"Ν;s޽ߘdȑ7nƍojQSSJ~sr9*++q 4#Ə0~7nNJT*qBQQ s=Sرc \)Gpe2:ۗL2CTq %ˑb!777n܀N?0w\̙3݊w_bɃȍ !Zrŕ+WP]] ;;;L8CU W\T*Ņ p%(J899a鈎Ftt4bbb(DJ˗K.ҥK>0m4 .. ,WKR!33R˃V7'fϞYf2%FYYY>gϦc_KK J+W1iӦaܹ|?~СoZ[[O?"33rE||<0iqIDAT$4Bkk+Μ9T?~rnnnXp!-8X9sJ{.\\\pB> .naAѣtR$%%a…y@Bss3N:TARaƌX|90sL0=o߆T*űcp)!66IIIxGos/kرBHhkk˗4HJJ¢E؟!++ ?ߢ111HJJʕ+ dȫVf̟?7oFRRҠ? ҂CaݸpFe˖!)) K.ppBiwÇl")) ɘ5kVLoF_~O`ʕ HKK|Ic|gPTXb֯_ŋQByݻÇO?E~~>e]/ZzAP^zI$6mIJ{3*B~gkfccy? V.]Ė.]lllѣora2d]xmذ9::2www/ތGFao&H$ߟ{0!Z-;t3g/^Rzj&؜9sط~˴Za2l԰wy#F?'0@tlϞ=ϏI$o2Fӻ-l۶ml۶mBaRuu5KIIa Bo{N % -_1XCC?#fkkVZeUВcnƶws?7PyRz(8< fcǎe]}tp5̙c5S;N_Me@P*F@Srre/VRR)FVHӱ^{D"k.qg˖-c)))GD"{z\az 2D*fќpKV۶m8Z̃9H12QtZ7o۶h¶m8^0FmJOp/_BBB̺+ lÆ ,--ӸZ[[Ypp0>\akը[qN131c~W묱xBۑt>-]}]}7{Mee%`򊱷; L">`냒 W ~ei8ngo8|ڲ򁞝>tWv7ݮͭlVVVvd4G`μ陓M}}7{;<==Yss[ n(++Lt}2йٜ+>_]]Ͷm:lL1C x"V*vjjCwÙy7pG2{,MS;*iӦ+V|e`X7nկ~"""M +":4Ow5SNWz'/<@֓\L}7{ K^jVرc Lt 7#L7;5q M}hy\ ^鯫XJJJ:l؆=y7%>>D"wyH$<K cvmvz3?;}9c繺^keddչ@v\bz05/wFoj܆Ww7L&3y4X9I"ɴqMRFmR˦;iX$5VKkoogGflww]B@@{O?Ǝt:w%W0knyMŗØq^0fz&WLospnݺeVAӱ'͛7[&^j믍؅Cyqs^լnԆnfcccv7)K4hu잣5)j70OJJJUD?j[Q,qG+pwp1[GWi?عsg}?fZ󄆸hp]+ֲcDzիWyޞD"`0; ,$$T~#D0]M^d2Yflkl1<םlv>2rMd^Ikm{&7|cm7JZj9r$zbKJJ:Y@}Iܣgt7ݻ~2_y;}F\_KJx,Sn93mcqp./c˳26mc!噼jT|}m}ټyؘ1cXUUYl3vUs'?3 `?pod B 2^+O{p99paX*/4Wwv\Os5amذGZD1#l2⫯ŋ}b%***J9sSL1k۷oڵkb Mqqq;v,9ބO4_w}ck15'''8q˗/ҥK/y"&6dgg]q1lذDDD ++ jHMM8!=VXuȑ#w^M4`111l&ZVˢ6e{1'''avގt/s>2̙3IIIXr%2jB-FǏСC8vlmm~z$''#22Rz姟~w|$%%aɒ%!d0+++Çq!deea„ x^^^}uk Nɓ'tT*̜9IIIHJJc2 YJGEjj*N< VXY=\]]_T*os%K )) <$!bn޼T"''HLLĺuJ TT|wxx,X`$;Bz999ȀT*ŋqqqHJJŠ+)p}RSSq`Μ9G\\`kk+tJV#33RO+V )) ?p=za` }mmmĉ'B"::qqqGttt}1 *_T TgBR! qqqXx1-[wwwD]]=SN!##UUU9r$bcc!44T0 pʂT*EFF\Ncɒ%X`@_ Aff&222piܽ{9s(Y2,BFA^^rssqE;w ...[ԦOH$tVΝ;sRfΜ9sCP \ .իhjj?Νx,[ FdX/ ݺu .\Kk׮ FLL Ox{{ *!&555?._~:AAA={6>Ϙ1vvvB<ʕ+Bvv6QQQ[[[L<_ӦMCXX  l\t Cxx8bbbsbܸqB*|`999|tr9ሌDxx81e:=A,12((([X(̞=io \l梡 +"""$Z[[QTTB"??5 |ˆ#+*.QXX" ĉ'bĉ@zy&n޼Rj"ƍCDD"""Ѹq bC:oF~~~ݻ` SLAhh('@zKPqJKK'''*Q ֆR`r;;9!!! Ahh(1vX" y9BrCAPQQEpp0# CDDNګ6"W__k׮EEE,H1cbĉF`` C"' ݻwQRR R 1bD;p [ CEEE#?.! ((G|||[Cuu5d2d2_ pϹBR$ߟ߸ <44ƍSXCTkk+n߾\jtvvsDPP+QFэj( TTT9B?Or ķRDƃ+CeeL&Z?ooo5 ?|}}xzzt%Gmm-Av<羾C>QAVhN0G[[qX,-jnwuuFGGG888t8b\X,6zg>n7sn^҂V444jh4HWWW9A'|||w@4SձRk4~gFƍmprrⓋ _us \\\\nɩw !Skoll4T*t:_sE=%9]\Aȑ#b4_ VV+C!B:irB!P@!N@ BH'v:B!X>`sIENDB`graphviz-0.20.2/docs/styling.rst0000666000000000000000000000207114550602024015300 0ustar rootrootStyling ------- Use the ``graph_attr``, ``node_attr``, and ``edge_attr`` arguments of the :class:`.Graph` and :class:`.Digraph` constructors to change the default `attributes `_ for your graph, nodes, and edges. .. doctest:: >>> import graphviz # doctest: +NO_EXE >>> ps = graphviz.Digraph('pet-shop', node_attr={'shape': 'plaintext'}) >>> ps.node('parrot') >>> ps.node('dead') >>> ps.edge('parrot', 'dead') After creation, the :attr:`~.Graph.graph_attr`, :attr:`~.Graph.node_attr`, and :attr:`~.Graph.edge_attr` attributes be edited on instances: .. doctest:: >>> ps.graph_attr['rankdir'] = 'LR' # doctest: +NO_EXE >>> ps.edge_attr.update(arrowhead='vee', arrowsize='2') >>> print(ps.source) # doctest: +NORMALIZE_WHITESPACE digraph "pet-shop" { graph [rankdir=LR] node [shape=plaintext] edge [arrowhead=vee arrowsize=2] parrot dead parrot -> dead } .. image:: _static/pet-shop.svg :align: center .. include:: _links.rst graphviz-0.20.2/docs/subgraphs_and_clusters.rst0000666000000000000000000000444514550602024020362 0ustar rootrootSubgraphs & clusters -------------------- :class:`.Graph` and :class:`.Digraph` objects have a :meth:`~.Graph.subgraph` method for adding a subgraph to the instance. There are two ways to use it: Either with a ready-made instance of the same kind as the only argument (whose content is added as a subgraph) or omitting the ``graph`` argument (returning a context manager for defining the subgraph content more elegantly within a ``with``-block). First option, with ``graph`` as the only argument: .. doctest:: >>> import graphviz # doctest: +NO_EXE >>> p = graphviz.Graph(name='parent') >>> p.edge('spam', 'eggs') >>> c = graphviz.Graph(name='child', node_attr={'shape': 'box'}) >>> c.edge('foo', 'bar') >>> p.subgraph(c) Second usage, with a ``with``-block (omitting the ``graph`` argument): .. doctest:: >>> p = graphviz.Graph('parent') # doctest: +NO_EXE >>> p.edge('spam', 'eggs') >>> with p.subgraph(name='child', node_attr={'shape': 'box'}) as c: ... c.edge('foo', 'bar') Both produce the same result: .. doctest:: >>> print(p.source) # doctest: +NORMALIZE_WHITESPACE +NO_EXE graph parent { spam -- eggs subgraph child { node [shape=box] foo -- bar } } .. tip:: If the ``name`` of a subgraph begins with ``'cluster'`` (all lowercase), the layout engine treats it as a special **cluster** subgraph (:ref:`example ` ). See the `Subgraphs and Clusters` section in `DOT language `_. When :meth:`~.Graph.subgraph` is used as a context manager, the new graph instance is created with ``strict=None`` copying the **parent graph values** for ``directory``, ``engine``, ``format``, ``renderer``, ``formatter``, and ``encoding``: .. doctest:: >>> doctest_mark_exe() # skip this line >>> p = graphviz.Graph('parent', directory='doctest-output') >>> with p.subgraph(name='child') as c: ... c.edge('bacon', 'eggs') ... c.render().replace('\\', '/') 'doctest-output/child.gv.pdf' .. note:: These copied attributes are only relevant for rendering the subgraph **independently** (i.e. as a stand-alone graph) from within the ``with``-block. .. include:: _links.rst graphviz-0.20.2/docs/unflatten.rst0000666000000000000000000000301414550602024015605 0ustar rootrootUnflatten --------- To preprocess the DOT_ source of a :class:`.Graph` or :class:`.Digraph` with the `unflatten `_ preprocessor (`manpage `_, `PDF `_), use the :meth:`~.Graph.unflatten` method. .. doctest:: >>> import graphviz # doctest: +NO_EXE >>> w = graphviz.Digraph('wide') >>> w.edges(('0', str(i)) for i in range(1, 10)) .. doctest:: >>> doctest_mark_exe() # skip this line >>> w.view() # doctest: +SKIP .. image:: _static/wide.svg :align: center .. hint:: :meth:`~.Graph.unflatten` improves the aspect ratio of graphs with many leaves or disconnected nodes. .. doctest:: >>> u = w.unflatten(stagger=3) # doctest: +NO_EXE .. doctest:: >>> doctest_mark_exe() # skip this line >>> u.view() # doctest: +SKIP .. image:: _static/wide-unflatten-stagger-3.svg :align: center The method returns a :class:`.Source` object that you can :meth:`~.Source.render`, :meth:`~.Source.view`, etc. with the same basic API as :class:`.Graph` or :class:`.Digraph` objects (minus modification, see details :ref:`below `). .. doctest:: >>> u = w.unflatten(stagger=2) # doctest: +NO_EXE >>> u # doctest: +ELLIPSIS .. doctest:: >>> doctest_mark_exe() # skip this line >>> u.view() # doctest: +SKIP .. image:: _static/wide-unflatten-stagger-2.svg :align: center .. include:: _links.rst graphviz-0.20.2/docs/_links.rst0000666000000000000000000001040714226660050015073 0ustar rootroot.. _Graphviz: https://www.graphviz.org .. _graphviz.org gallery: https://www.graphviz.org/gallery/ .. _graphviz.org documentation: https://www.graphviz.org/documentation/ .. _upstream-download: https://www.graphviz.org/download/ .. _upstream-archived: https://www2.graphviz.org/Archive/stable/ .. _upstream-windows: https://forum.graphviz.org/t/new-simplified-installation-procedure-on-windows/224 .. _set-path-windows: https://www.computerhope.com/issues/ch000549.htm .. _set-path-linux: https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix .. _set-path-darwin: https://stackoverflow.com/questions/22465332/setting-path-environment-variable-in-osx-permanently .. _Anaconda: https://docs.anaconda.com/anaconda/install/ .. _conda-forge: https://conda-forge.org .. _conda-forge-python-graphviz: https://anaconda.org/conda-forge/python-graphviz .. _conda-forge-python-graphviz-feedstock: https://github.com/conda-forge/python-graphviz-feedstock .. _conda-forge-graphviz: https://anaconda.org/conda-forge/graphviz .. _conda-forge-graphviz-feedstock: https://github.com/conda-forge/graphviz-feedstock .. _pip: https://pip.pypa.io .. _venv: https://docs.python.org/3/library/venv.html#creating-virtual-environments .. _virtualenv: https://virtualenv.pypa.io .. _tox: https://tox.wiki .. _pytype: https://google.github.io/pytype/ .. _pytpe_platforms: https://google.github.io/pytype/#requirements .. _pytype_python_versions: https://google.github.io/pytype/support.html#python-version .. _sphinx: https://www.sphinx-doc.org .. _sphinx-rtd-theme: https://sphinx-rtd-theme.readthedocs.io .. _flake8: https://flake8.pycqa.org .. _twine: https://twine.readthedocs.io .. _DOT: https://www.graphviz.org/doc/info/lang.html .. _DOT command: https://graphviz.org/doc/info/command.html .. _DOT layouts: https://graphviz.org/docs/layouts/ .. _DOT outputs: https://graphviz.org/docs/outputs/ .. _DOT neato: https://graphviz.org/docs/layouts/neato/ .. _neato no-op: https://www.graphviz.org/doc/info/command.html#-n .. _DOT attrs: https://www.graphviz.org/doc/info/attrs.html .. _DOT pos: https://graphviz.org/docs/attrs/pos/ .. _DOT overlap: https://graphviz.org/docs/attrs/overlap/ .. _DOT splines: https://graphviz.org/docs/attrs/splines/ .. _DOT escString: https://www.graphviz.org/docs/attr-types/escString/ .. _DOT shapes: https://graphviz.org/doc/info/shapes.html .. _DOT shapes HTML: https://graphviz.org/doc/info/shapes.html#html .. _DOT manpage_pdf: https://www.graphviz.org/pdf/dot.1.pdf .. _DOT unflatten: https://linux.die.net/man/1/unflatten .. _DOT unflatten_pdf: https://www.graphviz.org/pdf/unflatten.1.pdf .. _Jupyter notebook: https://jupyter.org .. _Jupyter Qt Console: https://qtconsole.readthedocs.io .. _Spyder IDE: https://www.spyder-ide.org .. _Spyder ipythonconsole: https://docs.spyder-ide.org/current/panes/ipythonconsole.html .. _IPython.display: https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#functions .. _examples directory: https://github.com/xflr6/graphviz/tree/master/examples/ .. _GitHub graphviz-notebook.ipynb: https://github.com/xflr6/graphviz/blob/master/examples/graphviz-notebook.ipynb .. _GitHub graphviz-engines.ipynb: https://github.com/xflr6/graphviz/blob/master/examples/graphviz-engines.ipynb .. _GitHub graphviz-jupyter-format.ipynb: https://github.com/xflr6/graphviz/blob/master/examples/graphviz-jupyter-format.ipynb .. _GitHub graphviz-escapes.ipynb: https://github.com/xflr6/graphviz/blob/master/examples/graphviz-escapes.ipynb .. _nbviewer graphviz-notebook.ipynb: https://nbviewer.org/github/xflr6/graphviz/blob/master/examples/graphviz-notebook.ipynb .. _nbviewer graphviz-engines.ipynb: https://nbviewer.org/github/xflr6/graphviz/blob/master/examples/graphviz-engines.ipynb .. _nbviewer graphviz-jupyter-format.ipynb: https://nbviewer.org/github/xflr6/graphviz/blob/master/examples/graphviz-jupyter-format.ipynb .. _nbviewer graphviz-escapes.ipynb: https://nbviewer.org/github/xflr6/graphviz/blob/master/examples/graphviz-escapes.ipynb .. _raw string literals: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals .. _live viewer updates: https://superuser.com/questions/599442/pdf-viewer-that-handles-live-updating-of-pdf-doesnt-lock-the-file graphviz-0.20.2/examples/0000777000000000000000000000000014575736336013770 5ustar rootrootgraphviz-0.20.2/examples/angles.py0000666000000000000000000000221114151056254015567 0ustar rootroot#!/usr/bin/env python3 """https://www.graphviz.org/Gallery/gradient/angles.html""" import graphviz g = graphviz.Digraph('G', filename='angles.gv') g.attr(bgcolor='blue') with g.subgraph(name='cluster_1') as c: c.attr(fontcolor='white') c.attr('node', shape='circle', style='filled', fillcolor='white:black', gradientangle='360', label='n9:360', fontcolor='black') c.node('n9') for i, a in zip(range(8, 0, -1), range(360 - 45, -1, -45)): c.attr('node', gradientangle=f'{a:d}', label=f'n{i:d}:{a:d}') c.node(f'n{i:d}') c.attr(label='Linear Angle Variations (white to black gradient)') with g.subgraph(name='cluster_2') as c: c.attr(fontcolor='white') c.attr('node', shape='circle', style='radial', fillcolor='white:black', gradientangle='360', label='n18:360', fontcolor='black') c.node('n18') for i, a in zip(range(17, 9, -1), range(360 - 45, -1, -45)): c.attr('node', gradientangle=f'{a:d}', label=f'n{i:d}:{a:d}') c.node(f'n{i:d}') c.attr(label='Radial Angle Variations (white to black gradient)') g.edge('n5', 'n14') g.view() graphviz-0.20.2/examples/btree.py0000666000000000000000000000165314151056254015430 0ustar rootroot#!/usr/bin/env python3 """https://www.graphviz.org/pdf/dotguide.pdf, Figure 13""" import graphviz from graphviz import nohtml g = graphviz.Digraph('g', filename='btree.gv', node_attr={'shape': 'record', 'height': '.1'}) g.node('node0', nohtml(' | G|')) g.node('node1', nohtml(' | E|')) g.node('node2', nohtml(' | B|')) g.node('node3', nohtml(' | F|')) g.node('node4', nohtml(' | R|')) g.node('node5', nohtml(' | H|')) g.node('node6', nohtml(' | Y|')) g.node('node7', nohtml(' | A|')) g.node('node8', nohtml(' | C|')) g.edge('node0:f2', 'node4:f1') g.edge('node0:f0', 'node1:f1') g.edge('node1:f0', 'node2:f1') g.edge('node1:f2', 'node3:f1') g.edge('node2:f2', 'node8:f1') g.edge('node2:f0', 'node7:f1') g.edge('node4:f2', 'node6:f1') g.edge('node4:f0', 'node5:f1') g.view() graphviz-0.20.2/examples/cluster.py0000666000000000000000000000170414151056254016005 0ustar rootroot#!/usr/bin/env python3 """https://graphviz.org/Gallery/directed/cluster.html""" from graphviz import Digraph g = Digraph('G', filename='cluster.gv') # NOTE: the subgraph name needs to begin with 'cluster' (all lowercase) # so that Graphviz recognizes it as a special cluster subgraph with g.subgraph(name='cluster_0') as c: c.attr(style='filled', color='lightgrey') c.node_attr.update(style='filled', color='white') c.edges([('a0', 'a1'), ('a1', 'a2'), ('a2', 'a3')]) c.attr(label='process #1') with g.subgraph(name='cluster_1') as c: c.attr(color='blue') c.node_attr['style'] = 'filled' c.edges([('b0', 'b1'), ('b1', 'b2'), ('b2', 'b3')]) c.attr(label='process #2') g.edge('start', 'a0') g.edge('start', 'b0') g.edge('a1', 'b3') g.edge('b2', 'a3') g.edge('a3', 'a0') g.edge('a3', 'end') g.edge('b3', 'end') g.node('start', shape='Mdiamond') g.node('end', shape='Msquare') g.view() graphviz-0.20.2/examples/cluster_edge.py0000666000000000000000000000100114151056254016757 0ustar rootroot#!/usr/bin/env python3 """https://www.graphviz.org/pdf/dotguide.pdf, Figure 20""" import graphviz g = graphviz.Digraph('G', filename='cluster_edge.gv') g.attr(compound='true') with g.subgraph(name='cluster0') as c: c.edges(['ab', 'ac', 'bd', 'cd']) with g.subgraph(name='cluster1') as c: c.edges(['eg', 'ef']) g.edge('b', 'f', lhead='cluster1') g.edge('d', 'e') g.edge('c', 'g', ltail='cluster0', lhead='cluster1') g.edge('c', 'e', ltail='cluster0') g.edge('d', 'h') g.view() graphviz-0.20.2/examples/colors.py0000666000000000000000000000100414151056254015616 0ustar rootroot#!/usr/bin/env python3 """https://graphviz.org/docs/attr-types/color""" import graphviz g = graphviz.Graph(filename='colors.gv') red, green, blue = 64, 224, 208 assert f'#{red:x}{green:x}{blue:x}' == '#40e0d0' g.node('RGB: #40e0d0', style='filled', fillcolor='#40e0d0') g.node('RGBA: #ff000042', style='filled', fillcolor='#ff000042') g.node('HSV: 0.051 0.718 0.627', style='filled', fillcolor='0.051 0.718 0.627') g.node('name: deeppink', style='filled', fillcolor='deeppink') g.view() graphviz-0.20.2/examples/er.py0000666000000000000000000000212614151056254014731 0ustar rootroot#!/usr/bin/env python3 """https://graphviz.org/Gallery/undirected/ER.html""" import graphviz e = graphviz.Graph('ER', filename='er.gv', engine='neato') e.attr('node', shape='box') e.node('course') e.node('institute') e.node('student') e.attr('node', shape='ellipse') e.node('name0', label='name') e.node('name1', label='name') e.node('name2', label='name') e.node('code') e.node('grade') e.node('number') e.attr('node', shape='diamond', style='filled', color='lightgrey') e.node('C-I') e.node('S-C') e.node('S-I') e.edge('name0', 'course') e.edge('code', 'course') e.edge('course', 'C-I', label='n', len='1.00') e.edge('C-I', 'institute', label='1', len='1.00') e.edge('institute', 'name1') e.edge('institute', 'S-I', label='1', len='1.00') e.edge('S-I', 'student', label='n', len='1.00') e.edge('student', 'grade') e.edge('student', 'name2') e.edge('student', 'number') e.edge('student', 'S-C', label='m', len='1.00') e.edge('S-C', 'course', label='n', len='1.00') e.attr(label=r'\n\nEntity Relation Diagram\ndrawn by NEATO') e.attr(fontsize='20') e.view() graphviz-0.20.2/examples/fdpclust.py0000666000000000000000000000073514151056254016153 0ustar rootroot#!/usr/bin/env python3 """https://www.graphviz.org/Gallery/undirected/fdpclust.html""" import graphviz g = graphviz.Graph('G', filename='fdpclust.gv', engine='fdp') g.node('e') with g.subgraph(name='clusterA') as a: a.edge('a', 'b') with a.subgraph(name='clusterC') as c: c.edge('C', 'D') with g.subgraph(name='clusterB') as b: b.edge('d', 'f') g.edge('d', 'D') g.edge('e', 'clusterB') g.edge('clusterC', 'clusterB') g.view() graphviz-0.20.2/examples/fsm.py0000666000000000000000000000157114151056254015113 0ustar rootroot#!/usr/bin/env python3 """https://graphviz.org/Gallery/directed/fsm.html""" import graphviz f = graphviz.Digraph('finite_state_machine', filename='fsm.gv') f.attr(rankdir='LR', size='8,5') f.attr('node', shape='doublecircle') f.node('LR_0') f.node('LR_3') f.node('LR_4') f.node('LR_8') f.attr('node', shape='circle') f.edge('LR_0', 'LR_2', label='SS(B)') f.edge('LR_0', 'LR_1', label='SS(S)') f.edge('LR_1', 'LR_3', label='S($end)') f.edge('LR_2', 'LR_6', label='SS(b)') f.edge('LR_2', 'LR_5', label='SS(a)') f.edge('LR_2', 'LR_4', label='S(A)') f.edge('LR_5', 'LR_7', label='S(b)') f.edge('LR_5', 'LR_5', label='S(a)') f.edge('LR_6', 'LR_6', label='S(b)') f.edge('LR_6', 'LR_5', label='S(a)') f.edge('LR_7', 'LR_8', label='S(b)') f.edge('LR_7', 'LR_5', label='S(a)') f.edge('LR_8', 'LR_6', label='S(b)') f.edge('LR_8', 'LR_5', label='S(a)') f.view() graphviz-0.20.2/examples/graphviz-engines.ipynb0000666000000000000000000006514214432726150020304 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('0.20.dev0', (2, 40, 1))" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%cd -q ..\n", "\n", "import graphviz\n", "\n", "graphviz.__version__, graphviz.version()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "circo\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "A\n", "\n", "\n", "\n", "B\n", "\n", "B\n", "\n", "\n", "\n", "A->B\n", "\n", "\n", "\n", "\n", "\n", "C\n", "\n", "C\n", "\n", "\n", "\n", "A->C\n", "\n", "\n", "\n", "\n", "\n", "B->C\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "dot\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "A\n", "\n", "\n", "\n", "B\n", "\n", "B\n", "\n", "\n", "\n", "A->B\n", "\n", "\n", "\n", "\n", "\n", "C\n", "\n", "C\n", "\n", "\n", "\n", "A->C\n", "\n", "\n", "\n", "\n", "\n", "B->C\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "fdp\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "A\n", "\n", "\n", "\n", "B\n", "\n", "B\n", "\n", "\n", "\n", "A->B\n", "\n", "\n", "\n", "\n", "\n", "C\n", "\n", "C\n", "\n", "\n", "\n", "A->C\n", "\n", "\n", "\n", "\n", "\n", "B->C\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "neato\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "A\n", "\n", "\n", "\n", "B\n", "\n", "B\n", "\n", "\n", "\n", "A->B\n", "\n", "\n", "\n", "\n", "\n", "C\n", "\n", "C\n", "\n", "\n", "\n", "A->C\n", "\n", "\n", "\n", "\n", "\n", "B->C\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "osage\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "A\n", "\n", "\n", "\n", "B\n", "\n", "B\n", "\n", "\n", "\n", "A->B\n", "\n", "\n", "\n", "\n", "\n", "C\n", "\n", "C\n", "\n", "\n", "\n", "A->C\n", "\n", "\n", "\n", "\n", "\n", "B->C\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "patchwork\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "A\n", "\n", "\n", "\n", "B\n", "\n", "B\n", "\n", "\n", "\n", "C\n", "\n", "C\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "sfdp\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "A\n", "\n", "\n", "\n", "B\n", "\n", "B\n", "\n", "\n", "\n", "A->B\n", "\n", "\n", "\n", "\n", "\n", "C\n", "\n", "C\n", "\n", "\n", "\n", "A->C\n", "\n", "\n", "\n", "\n", "\n", "B->C\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "twopi\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "A\n", "\n", "\n", "\n", "B\n", "\n", "B\n", "\n", "\n", "\n", "A->B\n", "\n", "\n", "\n", "\n", "\n", "C\n", "\n", "C\n", "\n", "\n", "\n", "A->C\n", "\n", "\n", "\n", "\n", "\n", "B->C\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "from IPython import display\n", "\n", "dot = graphviz.Digraph()\n", "dot.edges(['AB', 'BC', 'AC'])\n", "\n", "for engine in sorted(graphviz.ENGINES):\n", " print(engine)\n", " dot.engine = engine\n", " display.display(dot)\n", " print()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.12" } }, "nbformat": 4, "nbformat_minor": 4 } graphviz-0.20.2/examples/graphviz-escapes.ipynb0000666000000000000000000004006314432726150020272 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('0.20.dev0', (2, 40, 1))" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%cd -q ..\n", "\n", "import graphviz\n", "\n", "graphviz.__version__, graphviz.version()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def make_graph(node_label=None, graph_name=None, **node_kwargs):\n", " result = graphviz.Digraph(name=graph_name)\n", " result.node('A', label=node_label, **node_kwargs)\n", " print(result)\n", " return result" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "digraph {\n", "\tA [label=\"\\\"\"]\n", "}\n", "\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", ""\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_graph(node_label='\"')" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "digraph {\n", "\tA [label=\"\\\"\"]\n", "}\n", "\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", ""\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_graph(node_label='\\\\\"')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/jovyan/graphviz/quoting.py:88: DotSyntaxWarning: expect syntax error scanning invalid quoted string: '\\\\'\n", " category=exceptions.DotSyntaxWarning)\n", "Error: : syntax error in line 2 scanning a quoted string (missing endquote? longer than 16384?)\n", "String starting:\"\"]\n", "}\n", "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "digraph {\n", "\tA [label=\"\\\"]\n", "}\n", "\n" ] } ], "source": [ "try:\n", " make_graph(node_label='\\\\').pipe(format='svg', encoding='utf-8');\n", "except graphviz.CalledProcessError as e:\n", " assert 'syntax error' in e.stderr" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "digraph {\n", "\tA [label=\"\\\\\"]\n", "}\n", "\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "\\\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_graph(node_label=r'\\\\')" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "digraph {\n", "\tA [label=\"\\\\\\\"\"]\n", "}\n", "\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "\\"\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_graph(node_label=r'\\\\\"')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "https://www.graphviz.org/doc/info/attrs.html#k:escString" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "digraph {\n", "\tA [label=\"node: \\N\"]\n", "}\n", "\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "node: A\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_graph(node_label=r'node: \\N')" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "digraph spam {\n", "\tA [label=\"graph: \\G\"]\n", "}\n", "\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "spam\n", "\n", "\n", "\n", "A\n", "\n", "graph: spam\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_graph(node_label=r'graph: \\G', graph_name='spam')" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "digraph {\n", "\tA [label=spam URL=\"https://example.org/\\L\"]\n", "}\n", "\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "\n", "spam\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_graph(node_label='spam', URL=r'https://example.org/\\L')" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "digraph {\n", "\tA [label=\"centered\\nleft\\lright\\r\"]\n", "}\n", "\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "centered\n", "left\n", "right\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_graph(node_label=r'centered\\nleft\\lright\\r')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.12" } }, "nbformat": 4, "nbformat_minor": 4 } graphviz-0.20.2/examples/graphviz-jupyter-format.ipynb0000666000000000000000000007477414432726150021657 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "d31aac11", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('0.20.dev0', (2, 40, 1))" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%cd -q ..\n", "\n", "import graphviz\n", "\n", "graphviz.__version__, graphviz.version()" ] }, { "cell_type": "code", "execution_count": 2, "id": "b8d54151", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'svg'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "graphviz.set_jupyter_format('png')" ] }, { "cell_type": "code", "execution_count": 3, "id": "9e904725", "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dot = graphviz.Digraph(comment='The Round Table')\n", "\n", "dot.node('A', 'King Arthur')\n", "dot.node('B', 'Sir Bedevere the Wise')\n", "dot.node('L', 'Sir Lancelot the Brave')\n", "\n", "dot.edges(['AB', 'AL'])\n", "dot.edge('B', 'L', constraint='false')\n", "\n", "dot" ] }, { "cell_type": "code", "execution_count": 4, "id": "a0a07753", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "King Arthur\n", "\n", "\n", "\n", "B\n", "\n", "Sir Bedevere the Wise\n", "\n", "\n", "\n", "A->B\n", "\n", "\n", "\n", "\n", "\n", "L\n", "\n", "Sir Lancelot the Brave\n", "\n", "\n", "\n", "A->L\n", "\n", "\n", "\n", "\n", "\n", "B->L\n", "\n", "\n", "\n", "\n", "\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython import display\n", "\n", "display.display_svg(dot)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.12" } }, "nbformat": 4, "nbformat_minor": 5 } graphviz-0.20.2/examples/graphviz-notebook.ipynb0000666000000000000000000010133114432726150020463 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[DEBUG@graphviz.backend.execute] run [PosixPath('dot'), '-V']\n" ] }, { "data": { "text/plain": [ "('0.20.dev0', (2, 40, 1))" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%cd -q ..\n", "\n", "import logging\n", "\n", "import graphviz\n", "\n", "logging.basicConfig(format='[%(levelname)s@%(name)s] %(message)s', level=logging.DEBUG)\n", "\n", "graphviz.__version__, graphviz.version()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[DEBUG@graphviz.backend.execute] run [PosixPath('dot'), '-Kdot', '-Tsvg']\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "A\n", "\n", "King Arthur\n", "\n", "\n", "\n", "B\n", "\n", "Sir Bedevere the Wise\n", "\n", "\n", "\n", "A->B\n", "\n", "\n", "\n", "\n", "\n", "L\n", "\n", "Sir Lancelot the Brave\n", "\n", "\n", "\n", "A->L\n", "\n", "\n", "\n", "\n", "\n", "B->L\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dot = graphviz.Digraph(comment='The Round Table')\n", "\n", "dot.node('A', 'King Arthur')\n", "dot.node('B', 'Sir Bedevere the Wise')\n", "dot.node('L', 'Sir Lancelot the Brave')\n", "\n", "dot.edges(['AB', 'AL'])\n", "dot.edge('B', 'L', constraint='false')\n", "\n", "dot" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[DEBUG@graphviz.backend.execute] run [PosixPath('dot'), '-Kdot', '-Tsvg']\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "the holy hand grenade\n", "\n", "\n", "\n", "1\n", "\n", "1\n", "\n", "\n", "\n", "2\n", "\n", "2\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "3\n", "\n", "3\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "lob\n", "\n", "lob\n", "\n", "\n", "\n", "3->lob\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "src = graphviz.Source('digraph \"the holy hand grenade\" { rankdir=LR; 1 -> 2 -> 3 -> lob }')\n", "src" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[DEBUG@graphviz.backend.execute] run [PosixPath('dot'), '-Kdot', '-Tsvg']\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "finite_state_machine\n", "\n", "\n", "\n", "LR_0\n", "\n", "\n", "LR_0\n", "\n", "\n", "\n", "LR_2\n", "\n", "LR_2\n", "\n", "\n", "\n", "LR_0->LR_2\n", "\n", "\n", "SS(B)\n", "\n", "\n", "\n", "LR_1\n", "\n", "LR_1\n", "\n", "\n", "\n", "LR_0->LR_1\n", "\n", "\n", "SS(S)\n", "\n", "\n", "\n", "LR_3\n", "\n", "\n", "LR_3\n", "\n", "\n", "\n", "LR_4\n", "\n", "\n", "LR_4\n", "\n", "\n", "\n", "LR_8\n", "\n", "\n", "LR_8\n", "\n", "\n", "\n", "LR_6\n", "\n", "LR_6\n", "\n", "\n", "\n", "LR_8->LR_6\n", "\n", "\n", "S(b)\n", "\n", "\n", "\n", "LR_5\n", "\n", "LR_5\n", "\n", "\n", "\n", "LR_8->LR_5\n", "\n", "\n", "S(a)\n", "\n", "\n", "\n", "LR_2->LR_4\n", "\n", "\n", "S(A)\n", "\n", "\n", "\n", "LR_2->LR_6\n", "\n", "\n", "SS(b)\n", "\n", "\n", "\n", "LR_2->LR_5\n", "\n", "\n", "SS(a)\n", "\n", "\n", "\n", "LR_1->LR_3\n", "\n", "\n", "S($end)\n", "\n", "\n", "\n", "LR_6->LR_6\n", "\n", "\n", "S(b)\n", "\n", "\n", "\n", "LR_6->LR_5\n", "\n", "\n", "S(a)\n", "\n", "\n", "\n", "LR_5->LR_5\n", "\n", "\n", "S(a)\n", "\n", "\n", "\n", "LR_7\n", "\n", "LR_7\n", "\n", "\n", "\n", "LR_5->LR_7\n", "\n", "\n", "S(b)\n", "\n", "\n", "\n", "LR_7->LR_8\n", "\n", "\n", "S(b)\n", "\n", "\n", "\n", "LR_7->LR_5\n", "\n", "\n", "S(a)\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# http://www.graphviz.org/content/fsm\n", "\n", "f = graphviz.Digraph('finite_state_machine', filename='fsm.gv')\n", "\n", "f.attr(rankdir='LR', size='8,5')\n", "\n", "f.attr('node', shape='doublecircle')\n", "f.node('LR_0')\n", "f.node('LR_3')\n", "f.node('LR_4')\n", "f.node('LR_8')\n", "\n", "f.attr('node', shape='circle')\n", "f.edge('LR_0', 'LR_2', label='SS(B)')\n", "f.edge('LR_0', 'LR_1', label='SS(S)')\n", "f.edge('LR_1', 'LR_3', label='S($end)')\n", "f.edge('LR_2', 'LR_6', label='SS(b)')\n", "f.edge('LR_2', 'LR_5', label='SS(a)')\n", "f.edge('LR_2', 'LR_4', label='S(A)')\n", "f.edge('LR_5', 'LR_7', label='S(b)')\n", "f.edge('LR_5', 'LR_5', label='S(a)')\n", "f.edge('LR_6', 'LR_6', label='S(b)')\n", "f.edge('LR_6', 'LR_5', label='S(a)')\n", "f.edge('LR_7', 'LR_8', label='S(b)')\n", "f.edge('LR_7', 'LR_5', label='S(a)')\n", "f.edge('LR_8', 'LR_6', label='S(b)')\n", "f.edge('LR_8', 'LR_5', label='S(a)')\n", "\n", "f" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[DEBUG@graphviz.backend.execute] run [PosixPath('dot'), '-Kdot', '-Tsvg']\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "G\n", "\n", "\n", "cluster0\n", "\n", "\n", "\n", "cluster1\n", "\n", "\n", "\n", "\n", "a\n", "\n", "a\n", "\n", "\n", "\n", "b\n", "\n", "b\n", "\n", "\n", "\n", "a->b\n", "\n", "\n", "\n", "\n", "\n", "c\n", "\n", "c\n", "\n", "\n", "\n", "a->c\n", "\n", "\n", "\n", "\n", "\n", "d\n", "\n", "d\n", "\n", "\n", "\n", "b->d\n", "\n", "\n", "\n", "\n", "\n", "f\n", "\n", "f\n", "\n", "\n", "\n", "b->f\n", "\n", "\n", "\n", "\n", "\n", "c->d\n", "\n", "\n", "\n", "\n", "\n", "e\n", "\n", "e\n", "\n", "\n", "\n", "c->e\n", "\n", "\n", "\n", "\n", "\n", "g\n", "\n", "g\n", "\n", "\n", "\n", "c->g\n", "\n", "\n", "\n", "\n", "\n", "d->e\n", "\n", "\n", "\n", "\n", "\n", "h\n", "\n", "h\n", "\n", "\n", "\n", "d->h\n", "\n", "\n", "\n", "\n", "\n", "e->g\n", "\n", "\n", "\n", "\n", "\n", "e->f\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# http://www.graphviz.org/pdf/dotguide.pdf, Figure 20\n", "\n", "g = graphviz.Digraph('G', filename='cluster_edge.gv')\n", "\n", "g.attr(compound='true')\n", "\n", "with g.subgraph(name='cluster0') as c:\n", " c.edges(['ab', 'ac', 'bd', 'cd'])\n", "\n", "with g.subgraph(name='cluster1') as c:\n", " c.edges(['eg', 'ef'])\n", "\n", "g.edge('b', 'f', lhead='cluster1')\n", "g.edge('d', 'e')\n", "g.edge('c', 'g', ltail='cluster0', lhead='cluster1')\n", "g.edge('c', 'e', ltail='cluster0')\n", "g.edge('d', 'h')\n", "\n", "g" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.12" } }, "nbformat": 4, "nbformat_minor": 4 } graphviz-0.20.2/examples/graphviz_transform_recipe.py0000666000000000000000000000706414151056254021605 0ustar rootroot#!/usr/bin/env python3 """graphviz.(Di)graph instance transformation: LazyGraph and LazyDigraph.""" import functools from unittest import mock import graphviz WRAPS = ['Graph', 'Digraph'] RENDERING_METHODS = ('pipe', 'save', 'render', 'view', 'unflatten') INPUT_PROPERTIES = ('source',) __all__ = ['LazyGraph', 'LazyDigraph'] def lazy_graph_cls(wrapped_cls_name: str): return functools.partial(create_lazy_graph_instance, wrapped_cls_name) def create_lazy_graph_instance(cls_name: str, *init_args, **init_kwargs) -> mock.NonCallableMagicMock: cls = getattr(graphviz, cls_name) if cls not in (graphviz.Graph, graphviz.Digraph): raise ValueError(f'cls_name: {cls_name!r}') fake = mock.create_autospec(cls, instance=True, spec_set=True) fake.copy.side_effect = NotImplementedError # TODO def make_real_inst(calls): dot = cls(*init_args, **init_kwargs) for method_name, args, kwargs in calls: method = getattr(dot, method_name) method(*args, **kwargs) # ignore return value return dot def make_methodcaller(method_name): def call_method(*args, **kwargs): last_call = fake.mock_calls.pop() assert last_call == getattr(mock.call, method_name)(*args, **kwargs) dot = make_real_inst(fake.mock_calls) method = getattr(dot, method_name) return method(*args, **kwargs) return call_method for method_name in RENDERING_METHODS: getattr(fake, method_name).side_effect = make_methodcaller(method_name) def make_property(property_name): def property_func(*args): last_call = property_mock.mock_calls.pop() assert last_call == mock.call(*args) assert not property_mock.mock_calls dot = make_real_inst(fake.mock_calls) property_obj = getattr(dot.__class__, property_name) property_func = property_obj.fset if args else property_obj.fget return property_func(dot, *args) property_mock = mock.PropertyMock(side_effect=property_func) return property_mock for property_name in INPUT_PROPERTIES: setattr(type(fake), property_name, make_property(property_name)) return fake LazyGraph, LazyDigraph = map(lazy_graph_cls, WRAPS) if __name__ == '__main__': dot = LazyDigraph(filename='round-table.gv', comment='The Round Table') dot.node('A', 'King Arthur') dot.node('B', 'Sir Bedevere the Wise') dot.node('L', 'Sir Lancelot the Brave') dot.edges(['AB', 'AL']) dot.edge('B', 'L', constraint='false') print(repr(dot), dot.mock_calls, dot.source, sep='\n') #dot.view() # noqa: E265 def transform(mock_calls): """Replace full name labels with first names.""" for call in mock_calls: method_name, args, kwargs = call if method_name == 'node': name, label = args label = label.split()[1] yield mock.call.node(name, label, **kwargs) else: yield call dot.mock_calls = list(transform(dot.mock_calls)) # reverse the Bedvedere -> Lancelot edge method_name, tail_head, edge_attrs = dot.mock_calls.pop() assert method_name == 'edge' dot.edge(*reversed(tail_head), **edge_attrs) print(repr(dot), dot.mock_calls, dot.source, sep='\n') #dot.view('round-table-transformed.gv') # noqa: E265 graphviz-0.20.2/examples/g_c_n.py0000666000000000000000000000101014151056254015357 0ustar rootroot#!/usr/bin/env python3 """https://www.graphviz.org/Gallery/gradient/g_c_n.html""" import graphviz g = graphviz.Graph('G', filename='g_c_n.gv') g.attr(bgcolor='purple:pink', label='agraph', fontcolor='white') with g.subgraph(name='cluster1') as c: c.attr(fillcolor='blue:cyan', label='acluster', fontcolor='white', style='filled', gradientangle='270') c.attr('node', shape='box', fillcolor='red:yellow', style='filled', gradientangle='90') c.node('anode') g.view() graphviz-0.20.2/examples/hello.py0000666000000000000000000000027714151056254015433 0ustar rootroot#!/usr/bin/env python3 """https://graphviz.org/Gallery/directed/hello.html""" import graphviz g = graphviz.Digraph('G', filename='hello.gv') g.edge('Hello', 'World') g.view() graphviz-0.20.2/examples/process.py0000666000000000000000000000101314151056254015773 0ustar rootroot#!/usr/bin/env python3 """https://graphviz.org/Gallery/undirected/process.html""" import graphviz g = graphviz.Graph('G', filename='process.gv', engine='sfdp') g.edge('run', 'intr') g.edge('intr', 'runbl') g.edge('runbl', 'run') g.edge('run', 'kernel') g.edge('kernel', 'zombie') g.edge('kernel', 'sleep') g.edge('kernel', 'runmem') g.edge('sleep', 'swap') g.edge('swap', 'runswap') g.edge('runswap', 'new') g.edge('runswap', 'runmem') g.edge('new', 'runmem') g.edge('sleep', 'runmem') g.view() graphviz-0.20.2/examples/rank_same.py0000666000000000000000000000067514151056254016272 0ustar rootroot#!/usr/bin/env python3 """https://stackoverflow.com/questions/25734244/how-do-i-place-nodes-on-the-same-level-in-dot""" import graphviz d = graphviz.Digraph(filename='rank_same.gv') with d.subgraph() as s: s.attr(rank='same') s.node('A') s.node('X') d.node('C') with d.subgraph() as s: s.attr(rank='same') s.node('B') s.node('D') s.node('Y') d.edges(['AB', 'AC', 'CD', 'XY']) d.view() graphviz-0.20.2/examples/structs.py0000666000000000000000000000177114151056254016037 0ustar rootroot#!/usr/bin/env python3 """https://www.graphviz.org/doc/info/shapes.html#html""" import graphviz s = graphviz.Digraph('structs', filename='structs.gv', node_attr={'shape': 'plaintext'}) s.node('struct1', '''<
left middle right
>''') s.node('struct2', '''<
one two
>''') s.node('struct3', '''<
hello
world
b g h
c d e
f
>''') s.edges([('struct1:f1', 'struct2:f0'), ('struct1:f2', 'struct3:here')]) s.view() graphviz-0.20.2/examples/structs_revisited.py0000666000000000000000000000073614151056254020115 0ustar rootroot#!/usr/bin/env python3 """https://www.graphviz.org/pdf/dotguide.pdf, Figure 12""" import graphviz s = graphviz.Digraph('structs', filename='structs_revisited.gv', node_attr={'shape': 'record'}) s.node('struct1', ' left| middle| right') s.node('struct2', ' one| two') s.node('struct3', r'hello\nworld |{ b |{c| d|e}| f}| g | h') s.edges([('struct1:f1', 'struct2:f0'), ('struct1:f2', 'struct3:here')]) s.view() graphviz-0.20.2/examples/traffic_lights.py0000666000000000000000000000203714151056254017314 0ustar rootroot#!/usr/bin/env python3 """https://www.graphviz.org/Gallery/directed/traffic_lights.html""" import graphviz t = graphviz.Digraph('TrafficLights', filename='traffic_lights.gv', engine='neato') t.attr('node', shape='box') for i in (2, 1): t.node(f'gy{i:d}') t.node(f'yr{i:d}') t.node(f'rg{i:d}') t.attr('node', shape='circle', fixedsize='true', width='0.9') for i in (2, 1): t.node(f'green{i:d}') t.node(f'yellow{i:d}') t.node(f'red{i:d}') t.node(f'safe{i:d}') for i, j in [(2, 1), (1, 2)]: t.edge(f'gy{i:d}', f'yellow{i:d}') t.edge(f'rg{i:d}', f'green{i:d}') t.edge(f'yr{i:d}', f'safe{j:d}') t.edge(f'yr{i:d}', f'red{i:d}') t.edge(f'safe{i:d}', f'rg{i:d}') t.edge(f'green{i:d}', f'gy{i:d}') t.edge(f'yellow{i:d}', f'yr{i:d}') t.edge(f'red{i:d}', f'rg{i:d}') t.attr(overlap='false') t.attr(label=r'PetriNet Model TrafficLights\n' r'Extracted from ConceptBase and layed out by Graphviz') t.attr(fontsize='12') t.view() graphviz-0.20.2/examples/unix.py0000666000000000000000000000347414151056254015315 0ustar rootroot#!/usr/bin/env python3 """https://graphviz.org/Gallery/directed/unix.html""" import graphviz u = graphviz.Digraph('unix', filename='unix.gv', node_attr={'color': 'lightblue2', 'style': 'filled'}) u.attr(size='6,6') u.edge('5th Edition', '6th Edition') u.edge('5th Edition', 'PWB 1.0') u.edge('6th Edition', 'LSX') u.edge('6th Edition', '1 BSD') u.edge('6th Edition', 'Mini Unix') u.edge('6th Edition', 'Wollongong') u.edge('6th Edition', 'Interdata') u.edge('Interdata', 'Unix/TS 3.0') u.edge('Interdata', 'PWB 2.0') u.edge('Interdata', '7th Edition') u.edge('7th Edition', '8th Edition') u.edge('7th Edition', '32V') u.edge('7th Edition', 'V7M') u.edge('7th Edition', 'Ultrix-11') u.edge('7th Edition', 'Xenix') u.edge('7th Edition', 'UniPlus+') u.edge('V7M', 'Ultrix-11') u.edge('8th Edition', '9th Edition') u.edge('1 BSD', '2 BSD') u.edge('2 BSD', '2.8 BSD') u.edge('2.8 BSD', 'Ultrix-11') u.edge('2.8 BSD', '2.9 BSD') u.edge('32V', '3 BSD') u.edge('3 BSD', '4 BSD') u.edge('4 BSD', '4.1 BSD') u.edge('4.1 BSD', '4.2 BSD') u.edge('4.1 BSD', '2.8 BSD') u.edge('4.1 BSD', '8th Edition') u.edge('4.2 BSD', '4.3 BSD') u.edge('4.2 BSD', 'Ultrix-32') u.edge('PWB 1.0', 'PWB 1.2') u.edge('PWB 1.0', 'USG 1.0') u.edge('PWB 1.2', 'PWB 2.0') u.edge('USG 1.0', 'CB Unix 1') u.edge('USG 1.0', 'USG 2.0') u.edge('CB Unix 1', 'CB Unix 2') u.edge('CB Unix 2', 'CB Unix 3') u.edge('CB Unix 3', 'Unix/TS++') u.edge('CB Unix 3', 'PDP-11 Sys V') u.edge('USG 2.0', 'USG 3.0') u.edge('USG 3.0', 'Unix/TS 3.0') u.edge('PWB 2.0', 'Unix/TS 3.0') u.edge('Unix/TS 1.0', 'Unix/TS 3.0') u.edge('Unix/TS 3.0', 'TS 4.0') u.edge('Unix/TS++', 'TS 4.0') u.edge('CB Unix 3', 'TS 4.0') u.edge('TS 4.0', 'System V.0') u.edge('System V.0', 'System V.2') u.edge('System V.2', 'System V.3') u.view() graphviz-0.20.2/graphviz/0000777000000000000000000000000014575736336014004 5ustar rootrootgraphviz-0.20.2/graphviz/backend/0000777000000000000000000000000014575736336015373 5ustar rootrootgraphviz-0.20.2/graphviz/backend/dot_command.py0000666000000000000000000000270114226660050020207 0ustar rootroot"""Check and assemble commands for running Graphviz ``dot``.""" import os import pathlib import typing from .. import exceptions from .. import parameters __all__ = ['DOT_BINARY', 'command'] DOT_BINARY = pathlib.Path('dot') def command(engine: str, format_: str, *, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None, neato_no_op: typing.Union[bool, int, None] = None ) -> typing.List[typing.Union[os.PathLike, str]]: """Return ``subprocess.Popen`` argument list for rendering. See also: Upstream documentation: - https://www.graphviz.org/doc/info/command.html#-K - https://www.graphviz.org/doc/info/command.html#-T - https://www.graphviz.org/doc/info/command.html#-n """ if formatter is not None and renderer is None: raise exceptions.RequiredArgumentError('formatter given without renderer') parameters.verify_engine(engine, required=True) parameters.verify_format(format_, required=True) parameters.verify_renderer(renderer, required=False) parameters.verify_formatter(formatter, required=False) output_format = [f for f in (format_, renderer, formatter) if f is not None] output_format_flag = ':'.join(output_format) cmd = [DOT_BINARY, f'-K{engine}', f'-T{output_format_flag}'] if neato_no_op: cmd.append(f'-n{neato_no_op:d}') return cmd graphviz-0.20.2/graphviz/backend/execute.py0000666000000000000000000001055214455106354017376 0ustar rootroot"""Run subprocesses with ``subprocess.run()`` and ``subprocess.Popen()``.""" import errno import logging import os import subprocess import sys import typing from .. import _compat __all__ = ['run_check', 'ExecutableNotFound', 'CalledProcessError'] log = logging.getLogger(__name__) BytesOrStrIterator = typing.Union[typing.Iterator[bytes], typing.Iterator[str]] @typing.overload def run_check(cmd: typing.Sequence[typing.Union[os.PathLike, str]], *, input_lines: typing.Optional[typing.Iterator[bytes]] = ..., encoding: None = ..., quiet: bool = ..., **kwargs) -> subprocess.CompletedProcess: """Accept bytes input_lines with default ``encoding=None```.""" @typing.overload def run_check(cmd: typing.Sequence[typing.Union[os.PathLike, str]], *, input_lines: typing.Optional[typing.Iterator[str]] = ..., encoding: str, quiet: bool = ..., **kwargs) -> subprocess.CompletedProcess: """Accept string input_lines when given ``encoding``.""" @typing.overload def run_check(cmd: typing.Sequence[typing.Union[os.PathLike, str]], *, input_lines: typing.Optional[BytesOrStrIterator] = ..., encoding: typing.Optional[str] = ..., capture_output: bool = ..., quiet: bool = ..., **kwargs) -> subprocess.CompletedProcess: """Accept bytes or string input_lines depending on ``encoding``.""" def run_check(cmd: typing.Sequence[typing.Union[os.PathLike, str]], *, input_lines: typing.Optional[BytesOrStrIterator] = None, encoding: typing.Optional[str] = None, quiet: bool = False, **kwargs) -> subprocess.CompletedProcess: """Run the command described by ``cmd`` with ``check=True`` and return its completed process. Raises: CalledProcessError: if the returncode of the subprocess is non-zero. """ log.debug('run %r', cmd) if not kwargs.pop('check', True): # pragma: no cover raise NotImplementedError('check must be True or omited') if encoding is not None: kwargs['encoding'] = encoding kwargs.setdefault('startupinfo', _compat.get_startupinfo()) try: if input_lines is not None: assert kwargs.get('input') is None assert iter(input_lines) is input_lines if kwargs.pop('capture_output'): kwargs['stdout'] = kwargs['stderr'] = subprocess.PIPE proc = _run_input_lines(cmd, input_lines, kwargs=kwargs) else: proc = subprocess.run(cmd, **kwargs) except OSError as e: if e.errno == errno.ENOENT: raise ExecutableNotFound(cmd) from e raise if not quiet and proc.stderr: _write_stderr(proc.stderr) try: proc.check_returncode() except subprocess.CalledProcessError as e: raise CalledProcessError(*e.args) return proc def _run_input_lines(cmd, input_lines, *, kwargs): popen = subprocess.Popen(cmd, stdin=subprocess.PIPE, **kwargs) stdin_write = popen.stdin.write for line in input_lines: stdin_write(line) stdout, stderr = popen.communicate() return subprocess.CompletedProcess(popen.args, popen.returncode, stdout=stdout, stderr=stderr) def _write_stderr(stderr) -> None: if isinstance(stderr, bytes): stderr_encoding = (getattr(sys.stderr, 'encoding', None) or sys.getdefaultencoding()) stderr = stderr.decode(stderr_encoding) sys.stderr.write(stderr) sys.stderr.flush() return None class ExecutableNotFound(RuntimeError): """:exc:`RuntimeError` raised if the Graphviz executable is not found.""" _msg = ('failed to execute {!r}, ' 'make sure the Graphviz executables are on your systems\' PATH') def __init__(self, args) -> None: super().__init__(self._msg.format(*args)) class CalledProcessError(subprocess.CalledProcessError): """:exc:`~subprocess.CalledProcessError` raised if a subprocess ``returncode`` is not ``0``.""" # noqa: E501 def __str__(self) -> 'str': return f'{super().__str__()} [stderr: {self.stderr!r}]' graphviz-0.20.2/graphviz/backend/mixins.py0000666000000000000000000000441714151056254017242 0ustar rootroot"""Mixin classes used by Base subclasses to inherit backend functionality.""" import os import typing from .. import parameters from . import piping from . import rendering from . import unflattening from . import viewing __all__ = ['Render', 'Pipe', 'Unflatten', 'View'] class Render(parameters.Parameters): """Parameters for calling and calling ``graphviz.render()``.""" def _get_render_parameters(self, outfile: typing.Union[os.PathLike, str, None] = None, raise_if_result_exists: bool = False, overwrite_source: bool = False, **kwargs): kwargs = self._get_parameters(**kwargs) kwargs.update(outfile=outfile, raise_if_result_exists=raise_if_result_exists, overwrite_filepath=overwrite_source) return [kwargs.pop('engine'), kwargs.pop('format')], kwargs @property def _render(_): # noqa: N805 """Simplify ``._render()`` mocking.""" return rendering.render class Pipe(parameters.Parameters): """Parameters for calling and calling ``graphviz.pipe()``.""" _get_format = staticmethod(rendering.get_format) _get_filepath = staticmethod(rendering.get_filepath) def _get_pipe_parameters(self, **kwargs): kwargs = self._get_parameters(**kwargs) return [kwargs.pop('engine'), kwargs.pop('format')], kwargs @property def _pipe_lines(_): # noqa: N805 """Simplify ``._pipe_lines()`` mocking.""" return piping.pipe_lines @property def _pipe_lines_string(_): # noqa: N805 """Simplify ``._pipe_lines_string()`` mocking.""" return piping.pipe_lines_string class Unflatten: @property def _unflatten(_): # noqa: N805 """Simplify ``._unflatten mocking.""" return unflattening.unflatten class View: """Open filepath with its default viewing application (platform-specific).""" _view_darwin = staticmethod(viewing.view_darwin) _view_freebsd = staticmethod(viewing.view_unixoid) _view_linux = staticmethod(viewing.view_unixoid) _view_windows = staticmethod(viewing.view_windows) graphviz-0.20.2/graphviz/backend/piping.py0000666000000000000000000002142614226660050017216 0ustar rootroot"""Pipe bytes, strings, or string iterators through Graphviz ``dot``.""" import typing from .. import _tools from . import dot_command from . import execute __all__ = ['pipe', 'pipe_string', 'pipe_lines', 'pipe_lines_string'] @_tools.deprecate_positional_args(supported_number=3) def pipe(engine: str, format: str, data: bytes, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None, neato_no_op: typing.Union[bool, int, None] = None, quiet: bool = False) -> bytes: """Return ``data`` (``bytes``) piped through ``engine`` into ``format`` as ``bytes``. Args: engine: Layout engine for rendering (``'dot'``, ``'neato'``, ...). format: Output format for rendering (``'pdf'``, ``'png'``, ...). data: Binary (encoded) DOT source bytes to render. renderer: Output renderer (``'cairo'``, ``'gd'``, ...). formatter: Output formatter (``'cairo'``, ``'gd'``, ...). neato_no_op: Neato layout engine no-op flag. quiet: Suppress ``stderr`` output from the layout subprocess. Returns: Binary (encoded) stdout of the layout command. Raises: ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are unknown. graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable is not found. graphviz.CalledProcessError: If the returncode (exit status) of the rendering ``dot`` subprocess is non-zero. Example: >>> doctest_mark_exe() >>> import graphviz >>> graphviz.pipe('dot', 'svg', b'graph { hello -- world }')[:14] b' str: """Return ``input_string`` piped through ``engine`` into ``format`` as string. Args: engine: Layout engine for rendering (``'dot'``, ``'neato'``, ...). format: Output format for rendering (``'pdf'``, ``'png'``, ...). input_string: Binary (encoded) DOT source bytes to render. encoding: Encoding to en/decode subprocess stdin and stdout (required). renderer: Output renderer (``'cairo'``, ``'gd'``, ...). formatter: Output formatter (``'cairo'``, ``'gd'``, ...). neato_no_op: Neato layout engine no-op flag. quiet: Suppress ``stderr`` output from the layout subprocess. Returns: Decoded stdout of the layout command. Raises: ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are unknown. graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable is not found. graphviz.CalledProcessError: If the returncode (exit status) of the rendering ``dot`` subprocess is non-zero. Example: >>> doctest_mark_exe() >>> import graphviz >>> graphviz.pipe_string('dot', 'svg', 'graph { spam }', ... encoding='ascii')[:14] ' bytes: r"""Return ``input_lines`` piped through ``engine`` into ``format`` as ``bytes``. Args: engine: Layout engine for rendering (``'dot'``, ``'neato'``, ...). format: Output format for rendering (``'pdf'``, ``'png'``, ...). input_lines: DOT source lines to render (including final newline). input_encoding: Encode input_lines for subprocess stdin (required). renderer: Output renderer (``'cairo'``, ``'gd'``, ...). formatter: Output formatter (``'cairo'``, ``'gd'``, ...). neato_no_op: Neato layout engine no-op flag. quiet: Suppress ``stderr`` output from the layout subprocess. Returns: Binary stdout of the layout command. Raises: ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are unknown. graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable is not found. graphviz.CalledProcessError: If the returncode (exit status) of the rendering ``dot`` subprocess is non-zero. Example: >>> doctest_mark_exe() >>> import graphviz >>> graphviz.pipe_lines('dot', 'svg', iter(['graph { spam }\n']), ... input_encoding='ascii')[:14] b' str: r"""Return ``input_lines`` piped through ``engine`` into ``format`` as string. Args: engine: Layout engine for rendering (``'dot'``, ``'neato'``, ...). format: Output format for rendering (``'pdf'``, ``'png'``, ...). input_lines: DOT source lines to render (including final newline). encoding: Encoding to en/decode subprocess stdin and stdout (required). renderer: Output renderer (``'cairo'``, ``'gd'``, ...). formatter: Output formatter (``'cairo'``, ``'gd'``, ...). neato_no_op: Neato layout engine no-op flag. quiet: Suppress ``stderr`` output from the layout subprocess. Returns: Decoded stdout of the layout command. Raises: ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are unknown. graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable is not found. graphviz.CalledProcessError: If the returncode (exit status) of the rendering ``dot`` subprocess is non-zero. Example: >>> doctest_mark_exe() >>> import graphviz >>> graphviz.pipe_lines_string('dot', 'svg', iter(['graph { spam }\n']), ... encoding='ascii')[:14] ' str: """Return format inferred from outfile suffix and/or given ``format``. Args: outfile: Path for the rendered output file. format: Output format for rendering (``'pdf'``, ``'png'``, ...). Returns: The given ``format`` falling back to the inferred format. Warns: graphviz.UnknownSuffixWarning: If the suffix of ``outfile`` is empty/unknown. graphviz.FormatSuffixMismatchWarning: If the suffix of ``outfile`` does not match the given ``format``. """ try: inferred_format = infer_format(outfile) except ValueError: if format is None: msg = ('cannot infer rendering format' f' from suffix {outfile.suffix!r}' f' of outfile: {os.fspath(outfile)!r}' ' (provide format or outfile with a suffix' f' from {get_supported_suffixes()!r})') raise exceptions.RequiredArgumentError(msg) warnings.warn(f'unknown outfile suffix {outfile.suffix!r}' f' (expected: {"." + format!r})', category=exceptions.UnknownSuffixWarning) return format else: assert inferred_format is not None if format is not None and format.lower() != inferred_format: warnings.warn(f'expected format {inferred_format!r} from outfile' f' differs from given format: {format!r}', category=exceptions.FormatSuffixMismatchWarning) return format return inferred_format def get_supported_suffixes() -> typing.List[str]: """Return a sorted list of supported outfile suffixes for exception/warning messages. >>> get_supported_suffixes() # doctest: +ELLIPSIS ['.bmp', ...] """ return [f'.{format}' for format in get_supported_formats()] def get_supported_formats() -> typing.List[str]: """Return a sorted list of supported formats for exception/warning messages. >>> get_supported_formats() # doctest: +ELLIPSIS ['bmp', ...] """ return sorted(parameters.FORMATS) def infer_format(outfile: pathlib.Path) -> str: """Return format inferred from outfile suffix. Args: outfile: Path for the rendered output file. Returns: The inferred format. Raises: ValueError: If the suffix of ``outfile`` is empty/unknown. >>> infer_format(pathlib.Path('spam.pdf')) # doctest: +NO_EXE 'pdf' >>> infer_format(pathlib.Path('spam.gv.svg')) 'svg' >>> infer_format(pathlib.Path('spam.PNG')) 'png' >>> infer_format(pathlib.Path('spam')) Traceback (most recent call last): ... ValueError: cannot infer rendering format from outfile: 'spam' (missing suffix) >>> infer_format(pathlib.Path('spam.wav')) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ValueError: cannot infer rendering format from suffix '.wav' of outfile: 'spam.wav' (unknown format: 'wav', provide outfile with a suffix from ['.bmp', ...]) """ if not outfile.suffix: raise ValueError('cannot infer rendering format from outfile:' f' {os.fspath(outfile)!r} (missing suffix)') start, sep, format_ = outfile.suffix.partition('.') assert sep and not start, f"{outfile.suffix!r}.startswith('.')" format_ = format_.lower() try: parameters.verify_format(format_) except ValueError: raise ValueError('cannot infer rendering format' f' from suffix {outfile.suffix!r}' f' of outfile: {os.fspath(outfile)!r}' f' (unknown format: {format_!r},' ' provide outfile with a suffix' f' from {get_supported_suffixes()!r})') return format_ def get_outfile(filepath: typing.Union[os.PathLike, str], *, format: str, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None) -> pathlib.Path: """Return ``filepath`` + ``[[.formatter].renderer].format``. See also: https://www.graphviz.org/doc/info/command.html#-O """ filepath = _tools.promote_pathlike(filepath) parameters.verify_format(format, required=True) parameters.verify_renderer(renderer, required=False) parameters.verify_formatter(formatter, required=False) suffix_args = (formatter, renderer, format) suffix = '.'.join(a for a in suffix_args if a is not None) return filepath.with_suffix(f'{filepath.suffix}.{suffix}') def get_filepath(outfile: typing.Union[os.PathLike, str]) -> pathlib.Path: """Return ``outfile.with_suffix('.gv')``.""" outfile = _tools.promote_pathlike(outfile) return outfile.with_suffix(f'.{DEFAULT_SOURCE_EXTENSION}') @typing.overload def render(engine: str, format: str, filepath: typing.Union[os.PathLike, str], renderer: typing.Optional[str] = ..., formatter: typing.Optional[str] = ..., neato_no_op: typing.Union[bool, int, None] = ..., quiet: bool = ..., *, outfile: typing.Union[os.PathLike, str, None] = ..., raise_if_result_exists: bool = ..., overwrite_filepath: bool = ...) -> str: """Require ``format`` and ``filepath`` with default ``outfile=None``.""" @typing.overload def render(engine: str, format: typing.Optional[str] = ..., filepath: typing.Union[os.PathLike, str, None] = ..., renderer: typing.Optional[str] = ..., formatter: typing.Optional[str] = ..., neato_no_op: typing.Union[bool, int, None] = ..., quiet: bool = False, *, outfile: typing.Union[os.PathLike, str, None] = ..., raise_if_result_exists: bool = ..., overwrite_filepath: bool = ...) -> str: """Optional ``format`` and ``filepath`` with given ``outfile``.""" @typing.overload def render(engine: str, format: typing.Optional[str] = ..., filepath: typing.Union[os.PathLike, str, None] = ..., renderer: typing.Optional[str] = ..., formatter: typing.Optional[str] = ..., neato_no_op: typing.Union[bool, int, None] = ..., quiet: bool = False, *, outfile: typing.Union[os.PathLike, str, None] = ..., raise_if_result_exists: bool = ..., overwrite_filepath: bool = ...) -> str: """Required/optional ``format`` and ``filepath`` depending on ``outfile``.""" @_tools.deprecate_positional_args(supported_number=3) def render(engine: str, format: typing.Optional[str] = None, filepath: typing.Union[os.PathLike, str, None] = None, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None, neato_no_op: typing.Union[bool, int, None] = None, quiet: bool = False, *, outfile: typing.Union[os.PathLike, str, None] = None, raise_if_result_exists: bool = False, overwrite_filepath: bool = False) -> str: r"""Render file with ``engine`` into ``format`` and return result filename. Args: engine: Layout engine for rendering (``'dot'``, ``'neato'``, ...). format: Output format for rendering (``'pdf'``, ``'png'``, ...). Can be omitted if an ``outfile`` with a known ``format`` is given, i.e. if ``outfile`` ends with a known ``.{format}`` suffix. filepath: Path to the DOT source file to render. Can be omitted if ``outfile`` is given, in which case it defaults to ``outfile.with_suffix('.gv')``. renderer: Output renderer (``'cairo'``, ``'gd'``, ...). formatter: Output formatter (``'cairo'``, ``'gd'``, ...). neato_no_op: Neato layout engine no-op flag. quiet: Suppress ``stderr`` output from the layout subprocess. outfile: Path for the rendered output file. raise_if_result_exists: Raise :exc:`graphviz.FileExistsError` if the result file exists. overwrite_filepath: Allow ``dot`` to write to the file it reads from. Incompatible with ``raise_if_result_exists``. Returns: The (possibly relative) path of the rendered file. Raises: ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are unknown. graphviz.RequiredArgumentError: If ``format`` or ``filepath`` are None unless ``outfile`` is given. graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. ValueError: If ``outfile`` and ``filename`` are the same file unless ``overwite_filepath=True``. graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable is not found. graphviz.CalledProcessError: If the returncode (exit status) of the rendering ``dot`` subprocess is non-zero. graphviz.FileExistsError: If ``raise_if_exists`` and the result file exists. Warns: graphviz.UnknownSuffixWarning: If the suffix of ``outfile`` is empty or unknown. graphviz.FormatSuffixMismatchWarning: If the suffix of ``outfile`` does not match the given ``format``. Example: >>> doctest_mark_exe() >>> import pathlib >>> import graphviz >>> assert pathlib.Path('doctest-output/spam.gv').write_text('graph { spam }') == 14 >>> graphviz.render('dot', 'png', 'doctest-output/spam.gv').replace('\\', '/') 'doctest-output/spam.gv.png' >>> graphviz.render('dot', filepath='doctest-output/spam.gv', ... outfile='doctest-output/spam.png').replace('\\', '/') 'doctest-output/spam.png' >>> graphviz.render('dot', outfile='doctest-output/spam.pdf').replace('\\', '/') 'doctest-output/spam.pdf' Note: The layout command is started from the directory of ``filepath``, so that references to external files (e.g. ``[image=images/camelot.png]``) can be given as paths relative to the DOT source file. See also: Upstream docs: https://www.graphviz.org/doc/info/command.html """ if raise_if_result_exists and overwrite_filepath: raise ValueError('overwrite_filepath cannot be combined' ' with raise_if_result_exists') filepath, outfile = map(_tools.promote_pathlike, (filepath, outfile)) if outfile is not None: format = get_format(outfile, format=format) if filepath is None: filepath = get_filepath(outfile) if (not overwrite_filepath and outfile.name == filepath.name and outfile.resolve() == filepath.resolve()): # noqa: E129 raise ValueError(f'outfile {outfile.name!r} must be different' f' from input file {filepath.name!r}' ' (pass overwrite_filepath=True to override)') outfile_arg = (outfile.resolve() if outfile.parent != filepath.parent else outfile.name) # https://www.graphviz.org/doc/info/command.html#-o args = ['-o', outfile_arg, filepath.name] elif filepath is None: raise exceptions.RequiredArgumentError('filepath: (required if outfile is not given,' f' got {filepath!r})') elif format is None: raise exceptions.RequiredArgumentError('format: (required if outfile is not given,' f' got {format!r})') else: outfile = get_outfile(filepath, format=format, renderer=renderer, formatter=formatter) # https://www.graphviz.org/doc/info/command.html#-O args = ['-O', filepath.name] cmd = dot_command.command(engine, format, renderer=renderer, formatter=formatter, neato_no_op=neato_no_op) if raise_if_result_exists and os.path.exists(outfile): raise exceptions.FileExistsError(f'output file exists: {os.fspath(outfile)!r}') cmd += args assert filepath is not None, 'work around pytype false alarm' execute.run_check(cmd, cwd=filepath.parent if filepath.parent.parts else None, quiet=quiet, capture_output=True) return os.fspath(outfile) graphviz-0.20.2/graphviz/backend/unflattening.py0000666000000000000000000000413414151056254020425 0ustar rootroot"""Pipe DOT source code through ``unflatten``.""" import pathlib import typing from ..encoding import DEFAULT_ENCODING from .. import _tools from .. import exceptions from . import execute __all__ = ['UNFLATTEN_BINARY', 'unflatten'] UNFLATTEN_BINARY = pathlib.Path('unflatten') @_tools.deprecate_positional_args(supported_number=1) def unflatten(source: str, stagger: typing.Optional[int] = None, fanout: bool = False, chain: typing.Optional[int] = None, encoding: str = DEFAULT_ENCODING) -> str: """Return DOT ``source`` piped through ``unflatten`` preprocessor as string. Args: source: DOT source to process (improve layout aspect ratio). stagger: Stagger the minimum length of leaf edges between 1 and this small integer. fanout: Fanout nodes with indegree = outdegree = 1 when staggering (requires ``stagger``). chain: Form disconnected nodes into chains of up to this many nodes. encoding: Encoding to encode unflatten stdin and decode its stdout. Returns: Decoded stdout of the Graphviz unflatten command. Raises: graphviz.RequiredArgumentError: If ``fanout`` is given but no ``stagger``. graphviz.ExecutableNotFound: If the Graphviz 'unflatten' executable is not found. graphviz.CalledProcessError: If the returncode (exit status) of the unflattening 'unflatten' subprocess is non-zero. See also: Upstream documentation: https://www.graphviz.org/pdf/unflatten.1.pdf """ if fanout and stagger is None: raise exceptions.RequiredArgumentError('fanout given without stagger') cmd = [UNFLATTEN_BINARY] if stagger is not None: cmd += ['-l', str(stagger)] if fanout: cmd.append('-f') if chain is not None: cmd += ['-c', str(chain)] proc = execute.run_check(cmd, input=source, encoding=encoding, capture_output=True) return proc.stdout graphviz-0.20.2/graphviz/backend/upstream_version.py0000666000000000000000000000373714151056254021344 0ustar rootroot"""Return the version number from running ``dot -V``.""" import logging import re import subprocess import typing from . import dot_command from . import execute VERSION_PATTERN = re.compile(r''' graphviz[ ] version[ ] (\d+)\.(\d+) (?:\.(\d+) (?: ~dev\.\d{8}\.\d{4} | \.(\d+) )? )? [ ] ''', re.VERBOSE) log = logging.getLogger(__name__) def version() -> typing.Tuple[int, ...]: """Return the upstream version number tuple from ``stderr`` of ``dot -V``. Returns: Two, three, or four ``int`` version ``tuple``. Raises: graphviz.ExecutableNotFound: If the Graphviz executable is not found. graphviz.CalledProcessError: If the exit status is non-zero. RuntimeError: If the output cannot be parsed into a version number. Example: >>> doctest_mark_exe() >>> import graphviz >>> graphviz.version() # doctest: +ELLIPSIS (...) Note: Ignores the ``~dev.`` portion of development versions. See also: Upstream release version entry format: https://gitlab.com/graphviz/graphviz/-/blob/f94e91ba819cef51a4b9dcb2d76153684d06a913/gen_version.py#L17-20 """ cmd = [dot_command.DOT_BINARY, '-V'] proc = execute.run_check(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='ascii') ma = VERSION_PATTERN.search(proc.stdout) if ma is None: raise RuntimeError(f'cannot parse {cmd!r} output: {proc.stdout!r}') return tuple(int(d) for d in ma.groups() if d is not None) graphviz-0.20.2/graphviz/backend/viewing.py0000666000000000000000000000431714151056254017402 0ustar rootroot"""Open files in platform-specific default viewing application.""" import logging import os import platform import subprocess import typing from .. import _tools __all__ = ['view'] PLATFORM = platform.system().lower() log = logging.getLogger(__name__) @_tools.deprecate_positional_args(supported_number=1) def view(filepath: typing.Union[os.PathLike, str], quiet: bool = False) -> None: """Open filepath with its default viewing application (platform-specific). Args: filepath: Path to the file to open in viewer. quiet: Suppress ``stderr`` output from the viewer process (ineffective on Windows). Raises: RuntimeError: If the current platform is not supported. Note: There is no option to wait for the application to close, and no way to retrieve the application's exit status. """ try: view_func = getattr(view, PLATFORM) except AttributeError: raise RuntimeError(f'platform {PLATFORM!r} not supported') view_func(filepath, quiet=quiet) @_tools.attach(view, 'darwin') def view_darwin(filepath: typing.Union[os.PathLike, str], *, quiet: bool) -> None: """Open filepath with its default application (mac).""" cmd = ['open', filepath] log.debug('view: %r', cmd) kwargs = {'stderr': subprocess.DEVNULL} if quiet else {} subprocess.Popen(cmd, **kwargs) @_tools.attach(view, 'linux') @_tools.attach(view, 'freebsd') def view_unixoid(filepath: typing.Union[os.PathLike, str], *, quiet: bool) -> None: """Open filepath in the user's preferred application (linux, freebsd).""" cmd = ['xdg-open', filepath] log.debug('view: %r', cmd) kwargs = {'stderr': subprocess.DEVNULL} if quiet else {} subprocess.Popen(cmd, **kwargs) @_tools.attach(view, 'windows') def view_windows(filepath: typing.Union[os.PathLike, str], *, quiet: bool) -> None: """Start filepath with its associated application (windows).""" # TODO: implement quiet=True filepath = os.path.normpath(filepath) log.debug('view: %r', filepath) os.startfile(filepath) # pytype: disable=module-attr graphviz-0.20.2/graphviz/backend/__init__.py0000666000000000000000000000141214151056254017462 0ustar rootroot"""Execute rendering and unflattening subprocesses, open files in viewer.""" from .dot_command import DOT_BINARY from .execute import ExecutableNotFound, CalledProcessError from .mixins import Render, Pipe, Unflatten, View from .piping import pipe, pipe_string, pipe_lines, pipe_lines_string from .rendering import render from .unflattening import UNFLATTEN_BINARY, unflatten from .upstream_version import version from .viewing import view __all__ = ['DOT_BINARY', 'UNFLATTEN_BINARY', 'render', 'pipe', 'pipe_string', 'pipe_lines', 'pipe_lines_string', 'unflatten', 'version', 'view', 'ExecutableNotFound', 'CalledProcessError', 'Render', 'Pipe', 'Unflatten', 'View'] graphviz-0.20.2/graphviz/parameters/0000777000000000000000000000000014575736336016147 5ustar rootrootgraphviz-0.20.2/graphviz/parameters/base.py0000666000000000000000000000072114144304656017417 0ustar rootroot"""Rendering parameter handling.""" from .. import copying __all__ = ['ParameterBase'] class ParameterBase(copying.CopyBase): """Rendering parameter.""" def _getattr_from_dict(self, attrname: str, *, default=None): """Return self.attrname if attrname is in the instance dictionary (as oposed to on the type).""" if attrname in self.__dict__: return getattr(self, attrname) return default graphviz-0.20.2/graphviz/parameters/engines.py0000666000000000000000000000326414151056254020136 0ustar rootroot"""Rendering engine parameter handling.""" import typing from . import base __all__ = ['ENGINES', 'verify_engine', 'Engine'] ENGINES = {'dot', # https://www.graphviz.org/pdf/dot.1.pdf 'neato', 'twopi', 'circo', 'fdp', 'sfdp', 'patchwork', 'osage'} DEFAULT_ENGINE = 'dot' REQUIRED = True def verify_engine(engine: str, *, required: bool = REQUIRED) -> None: if engine is None: if required: raise ValueError('missing engine') elif engine.lower() not in ENGINES: raise ValueError(f'unknown engine: {engine!r}' f' (must be one of {sorted(ENGINES)})') class Engine(base.ParameterBase): """Rendering engine parameter with ``'dot''`` default.""" _engine = DEFAULT_ENGINE _verify_engine = staticmethod(verify_engine) def __init__(self, *, engine: typing.Optional[str] = None, **kwargs) -> None: super().__init__(**kwargs) if engine is not None: self.engine = engine def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" engine = self._getattr_from_dict('_engine') if engine is not None: kwargs['engine'] = engine return super()._copy_kwargs(**kwargs) @property def engine(self) -> str: """The layout engine used for rendering (``'dot'``, ``'neato'``, ...).""" return self._engine @engine.setter def engine(self, engine: str) -> None: engine = engine.lower() self._verify_engine(engine) self._engine = engine graphviz-0.20.2/graphviz/parameters/formats.py0000666000000000000000000000460114226660050020153 0ustar rootroot"""Rendering format parameter handling.""" import typing from . import base __all__ = ['FORMATS', 'verify_format', 'Format'] FORMATS = {'bmp', # https://graphviz.org/docs/outputs/ 'canon', 'dot', 'gv', 'xdot', 'xdot1.2', 'xdot1.4', 'cgimage', 'cmap', 'eps', 'exr', 'fig', 'gd', 'gd2', 'gif', 'gtk', 'ico', 'imap', 'cmapx', 'imap_np', 'cmapx_np', 'ismap', 'jp2', 'jpg', 'jpeg', 'jpe', 'json', 'json0', 'dot_json', 'xdot_json', # Graphviz 2.40 'pct', 'pict', 'pdf', 'pic', 'plain', 'plain-ext', 'png', 'pov', 'ps', 'ps2', 'psd', 'sgi', 'svg', 'svgz', 'tga', 'tif', 'tiff', 'tk', 'vml', 'vmlz', 'vrml', 'wbmp', 'webp', 'xlib', 'x11'} DEFAULT_FORMAT = 'pdf' REQUIRED = True def verify_format(format: str, *, required: bool = REQUIRED) -> None: if format is None: if required: raise ValueError('missing format') elif format.lower() not in FORMATS: raise ValueError(f'unknown format: {format!r}' f' (must be one of {sorted(FORMATS)})') class Format(base.ParameterBase): """Rendering format parameter with ``'pdf'`` default.""" _format = DEFAULT_FORMAT _verify_format = staticmethod(verify_format) def __init__(self, *, format: typing.Optional[str] = None, **kwargs) -> None: super().__init__(**kwargs) if format is not None: self.format = format def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" format = self._getattr_from_dict('_format') if format is not None: kwargs['format'] = format return super()._copy_kwargs(**kwargs) @property def format(self) -> str: """The output format used for rendering (``'pdf'``, ``'png'``, ...).""" return self._format @format.setter def format(self, format: str) -> None: format = format.lower() self._verify_format(format) self._format = format graphviz-0.20.2/graphviz/parameters/formatters.py0000666000000000000000000000351314151056254020671 0ustar rootroot"""Rendering formatter parameter handling.""" import typing from . import base __all__ = ['FORMATTERS', 'verify_formatter', 'Formatter'] FORMATTERS = {'cairo', 'core', 'gd', 'gdiplus', 'gdwbmp', 'xlib'} REQUIRED = False def verify_formatter(formatter: typing.Optional[str], *, required: bool = REQUIRED) -> None: if formatter is None: if required: raise ValueError('missing formatter') elif formatter.lower() not in FORMATTERS: raise ValueError(f'unknown formatter: {formatter!r}' f' (must be None or one of {sorted(FORMATTERS)})') class Formatter(base.ParameterBase): """Rendering engine parameter (no default).""" _formatter = None _verify_formatter = staticmethod(verify_formatter) def __init__(self, *, formatter: typing.Optional[str] = None, **kwargs) -> None: super().__init__(**kwargs) self.formatter = formatter def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" formatter = self._getattr_from_dict('_formatter') if formatter is not None: kwargs['formatter'] = formatter return super()._copy_kwargs(**kwargs) @property def formatter(self) -> typing.Optional[str]: """The output formatter used for rendering (``'cairo'``, ``'gd'``, ...).""" return self._formatter @formatter.setter def formatter(self, formatter: typing.Optional[str]) -> None: if formatter is None: self.__dict__.pop('_formatter', None) else: formatter = formatter.lower() self._verify_formatter(formatter) self._formatter = formatter graphviz-0.20.2/graphviz/parameters/mixins.py0000666000000000000000000000264614151056254020020 0ustar rootroot"""Mixin classes used to inherit parameter functionality.""" import typing from . import engines from . import formats from . import renderers from . import formatters __all__ = ['Parameters'] class Parameters(engines.Engine, formats.Format, renderers.Renderer, formatters.Formatter): """Parameters for calling ``graphviz.render()`` and ``graphviz.pipe()``.""" def _get_parameters(self, *, engine: typing.Optional[str] = None, format: typing.Optional[str] = None, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None, verify: bool = False, **kwargs): if engine is None: engine = self.engine elif verify: self._verify_engine(engine) if format is None: format = self.format elif verify: self._verify_format(format) if renderer is None: renderer = self.renderer elif verify: self._verify_renderer(renderer) if formatter is None: formatter = self.formatter elif verify: self._verify_formatter(formatter) kwargs.update(engine=engine, format=format, renderer=renderer, formatter=formatter) return kwargs graphviz-0.20.2/graphviz/parameters/renderers.py0000666000000000000000000000372114151056254020475 0ustar rootroot"""Rendering renderer parameter handling.""" import typing from . import base __all__ = ['RENDERERS', 'verify_renderer', 'Renderer'] RENDERERS = {'cairo', # $ dot -T: 'dot', 'fig', 'gd', 'gdiplus', 'map', 'pic', 'pov', 'ps', 'svg', 'tk', 'vml', 'vrml', 'xdot'} REQUIRED = False def verify_renderer(renderer: typing.Optional[str], *, required: bool = REQUIRED) -> None: if renderer is None: if required: raise ValueError('missing renderer') elif renderer.lower() not in RENDERERS: raise ValueError(f'unknown renderer: {renderer!r}' f' (must be None or one of {sorted(RENDERERS)})') class Renderer(base.ParameterBase): """Rendering renderer parameter (no default).""" _renderer = None _verify_renderer = staticmethod(verify_renderer) def __init__(self, *, renderer: typing.Optional[str] = None, **kwargs) -> None: super().__init__(**kwargs) self.renderer = renderer def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" renderer = self._getattr_from_dict('_renderer') if renderer is not None: kwargs['renderer'] = renderer return super()._copy_kwargs(**kwargs) @property def renderer(self) -> typing.Optional[str]: """The output renderer used for rendering (``'cairo'``, ``'gd'``, ...).""" return self._renderer @renderer.setter def renderer(self, renderer: typing.Optional[str]) -> None: if renderer is None: self.__dict__.pop('_renderer', None) else: renderer = renderer.lower() self._verify_renderer(renderer) self._renderer = renderer graphviz-0.20.2/graphviz/parameters/__init__.py0000666000000000000000000000074214144304656020247 0ustar rootroot"""Hold and verify parameters for running Graphviz ``dot``.""" from .engines import ENGINES, verify_engine from .formats import FORMATS, verify_format from .renderers import RENDERERS, verify_renderer from .formatters import FORMATTERS, verify_formatter from . mixins import Parameters __all__ = ['ENGINES', 'FORMATS', 'RENDERERS', 'FORMATTERS', 'verify_engine', 'verify_format', 'verify_renderer', 'verify_formatter', 'Parameters'] graphviz-0.20.2/graphviz/base.py0000666000000000000000000000170514144304656015257 0ustar rootroot"""Iterables of DOT source code lines (including final newline).""" import typing from . import copying __all__ = ['Base'] class LineIterable: """Iterable of DOT Source code lines (mimics ``file`` objects in text mode).""" def __iter__(self) -> typing.Iterator[str]: # pragma: no cover r"""Yield the generated DOT source line by line. Yields: Line ending with a newline (``'\n'``). """ raise NotImplementedError('to be implemented by concrete subclasses') # Common base interface for all exposed classes class Base(LineIterable, copying.CopyBase): """LineIterator with ``.source`` attribute, that it returns for ``str()``.""" @property def source(self) -> str: # pragma: no cover raise NotImplementedError('to be implemented by concrete subclasses') def __str__(self) -> str: """The DOT source code as string.""" return self.source graphviz-0.20.2/graphviz/copying.py0000666000000000000000000000106514144304656016014 0ustar rootroot"""Create new instance copies with cooperative ``super()`` calls.""" __all__ = ['CopyBase'] class CopyBase: """Create new instance copies with cooperative ``super()`` calls.""" def copy(self): """Return a copied instance of the object. Returns: An independent copy of the current object. """ kwargs = self._copy_kwargs() return self.__class__(**kwargs) def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" return kwargs graphviz-0.20.2/graphviz/dot.py0000666000000000000000000003136714572104602015134 0ustar rootroot"""Create DOT code with method-calls.""" import contextlib import typing from . import _tools from . import base from . import quoting __all__ = ['GraphSyntax', 'DigraphSyntax', 'Dot'] def comment(line: str) -> str: """Return comment header line.""" return f'// {line}\n' def graph_head(name: str) -> str: """Return DOT graph head line.""" return f'graph {name}{{\n' def digraph_head(name: str) -> str: """Return DOT digraph head line.""" return f'digraph {name}{{\n' def graph_edge(*, tail: str, head: str, attr: str) -> str: """Return DOT graph edge statement line.""" return f'\t{tail} -- {head}{attr}\n' def digraph_edge(*, tail: str, head: str, attr: str) -> str: """Return DOT digraph edge statement line.""" return f'\t{tail} -> {head}{attr}\n' class GraphSyntax: """DOT graph head and edge syntax.""" _head = staticmethod(graph_head) _edge = staticmethod(graph_edge) class DigraphSyntax: """DOT digraph head and edge syntax.""" _head = staticmethod(digraph_head) _edge = staticmethod(digraph_edge) def subgraph(name: str) -> str: """Return DOT subgraph head line.""" return f'subgraph {name}{{\n' def subgraph_plain(name: str) -> str: """Return plain DOT subgraph head line.""" return f'{name}{{\n' def node(left: str, right: str) -> str: """Return DOT node statement line.""" return f'\t{left}{right}\n' class Dot(quoting.Quote, base.Base): """Assemble DOT source code.""" directed: bool _comment = staticmethod(comment) @staticmethod def _head(name: str) -> str: # pragma: no cover """Return DOT head line.""" raise NotImplementedError('must be implemented by concrete subclasses') @classmethod def _head_strict(cls, name: str) -> str: """Return DOT strict head line.""" return f'strict {cls._head(name)}' _tail = '}\n' _subgraph = staticmethod(subgraph) _subgraph_plain = staticmethod(subgraph_plain) _node = _attr = staticmethod(node) @classmethod def _attr_plain(cls, left: str) -> str: return cls._attr(left, '') @staticmethod def _edge(*, tail: str, head: str, attr: str) -> str: # pragma: no cover """Return DOT edge statement line.""" raise NotImplementedError('must be implemented by concrete subclasses') @classmethod def _edge_plain(cls, *, tail: str, head: str) -> str: """Return plain DOT edge statement line.""" return cls._edge(tail=tail, head=head, attr='') def __init__(self, *, name: typing.Optional[str] = None, comment: typing.Optional[str] = None, graph_attr=None, node_attr=None, edge_attr=None, body=None, strict: bool = False, **kwargs) -> None: super().__init__(**kwargs) self.name = name """str: DOT source identifier for the ``graph`` or ``digraph`` statement.""" self.comment = comment """str: DOT source comment for the first source line.""" self.graph_attr = dict(graph_attr) if graph_attr is not None else {} """~typing.Dict[str, str]: Attribute-value pairs applying to the graph.""" self.node_attr = dict(node_attr) if node_attr is not None else {} """~typing.Dict[str, str]: Attribute-value pairs applying to all nodes.""" self.edge_attr = dict(edge_attr) if edge_attr is not None else {} """~typing.Dict[str, str]: Attribute-value pairs applying to all edges.""" self.body = list(body) if body is not None else [] """~typing.List[str]: Verbatim DOT source lines including final newline.""" self.strict = strict """bool: Rendering should merge multi-edges.""" def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" return super()._copy_kwargs(name=self.name, comment=self.comment, graph_attr=dict(self.graph_attr), node_attr=dict(self.node_attr), edge_attr=dict(self.edge_attr), body=list(self.body), strict=self.strict) @_tools.deprecate_positional_args(supported_number=1) def clear(self, keep_attrs: bool = False) -> None: """Reset content to an empty body, clear graph/node/egde_attr mappings. Args: keep_attrs (bool): preserve graph/node/egde_attr mappings """ if not keep_attrs: for a in (self.graph_attr, self.node_attr, self.edge_attr): a.clear() self.body.clear() @_tools.deprecate_positional_args(supported_number=1) def __iter__(self, subgraph: bool = False) -> typing.Iterator[str]: r"""Yield the DOT source code line by line (as graph or subgraph). Yields: Line ending with a newline (``'\n'``). """ if self.comment: yield self._comment(self.comment) if subgraph: if self.strict: raise ValueError('subgraphs cannot be strict') head = self._subgraph if self.name else self._subgraph_plain else: head = self._head_strict if self.strict else self._head yield head(self._quote(self.name) + ' ' if self.name else '') for kw in ('graph', 'node', 'edge'): attrs = getattr(self, f'{kw}_attr') if attrs: yield self._attr(kw, self._attr_list(None, kwargs=attrs)) yield from self.body yield self._tail @_tools.deprecate_positional_args(supported_number=3) def node(self, name: str, label: typing.Optional[str] = None, _attributes=None, **attrs) -> None: """Create a node. Args: name: Unique identifier for the node inside the source. label: Caption to be displayed (defaults to the node ``name``). attrs: Any additional node attributes (must be strings). Attention: When rendering ``label``, backslash-escapes and strings of the form ``<...>`` have a special meaning. See the sections :ref:`backslash-escapes` and :ref:`quoting-and-html-like-labels` in the user guide for details. """ name = self._quote(name) attr_list = self._attr_list(label, kwargs=attrs, attributes=_attributes) line = self._node(name, attr_list) self.body.append(line) @_tools.deprecate_positional_args(supported_number=4) def edge(self, tail_name: str, head_name: str, label: typing.Optional[str] = None, _attributes=None, **attrs) -> None: """Create an edge between two nodes. Args: tail_name: Start node identifier (format: ``node[:port[:compass]]``). head_name: End node identifier (format: ``node[:port[:compass]]``). label: Caption to be displayed near the edge. attrs: Any additional edge attributes (must be strings). Note: The ``tail_name`` and ``head_name`` strings are separated by (optional) colon(s) into ``node`` name, ``port`` name, and ``compass`` (e.g. ``sw``). See :ref:`details in the User Guide `. Attention: When rendering ``label``, backslash-escapes and strings of the form ``<...>`` have a special meaning. See the sections :ref:`backslash-escapes` and :ref:`quoting-and-html-like-labels` in the user guide for details. """ tail_name = self._quote_edge(tail_name) head_name = self._quote_edge(head_name) attr_list = self._attr_list(label, kwargs=attrs, attributes=_attributes) line = self._edge(tail=tail_name, head=head_name, attr=attr_list) self.body.append(line) def edges(self, tail_head_iter) -> None: """Create a bunch of edges. Args: tail_head_iter: Iterable of ``(tail_name, head_name)`` pairs (format:``node[:port[:compass]]``). Note: The ``tail_name`` and ``head_name`` strings are separated by (optional) colon(s) into ``node`` name, ``port`` name, and ``compass`` (e.g. ``sw``). See :ref:`details in the User Guide `. """ edge = self._edge_plain quote = self._quote_edge self.body += [edge(tail=quote(t), head=quote(h)) for t, h in tail_head_iter] @_tools.deprecate_positional_args(supported_number=2) def attr(self, kw: typing.Optional[str] = None, _attributes=None, **attrs) -> None: """Add a general or graph/node/edge attribute statement. Args: kw: Attributes target (``None`` or ``'graph'``, ``'node'``, ``'edge'``). attrs: Attributes to be set (must be strings, may be empty). See the :ref:`usage examples in the User Guide `. """ if kw is not None and kw.lower() not in ('graph', 'node', 'edge'): raise ValueError('attr statement must target graph, node, or edge:' f' {kw!r}') if attrs or _attributes: if kw is None: a_list = self._a_list(None, kwargs=attrs, attributes=_attributes) line = self._attr_plain(a_list) else: attr_list = self._attr_list(None, kwargs=attrs, attributes=_attributes) line = self._attr(kw, attr_list) self.body.append(line) @_tools.deprecate_positional_args(supported_number=2) def subgraph(self, graph=None, name: typing.Optional[str] = None, comment: typing.Optional[str] = None, graph_attr=None, node_attr=None, edge_attr=None, body=None): """Add the current content of the given sole ``graph`` argument as subgraph or return a context manager returning a new graph instance created with the given (``name``, ``comment``, etc.) arguments whose content is added as subgraph when leaving the context manager's ``with``-block. Args: graph: An instance of the same kind (:class:`.Graph`, :class:`.Digraph`) as the current graph (sole argument in non-with-block use). name: Subgraph name (``with``-block use). comment: Subgraph comment (``with``-block use). graph_attr: Subgraph-level attribute-value mapping (``with``-block use). node_attr: Node-level attribute-value mapping (``with``-block use). edge_attr: Edge-level attribute-value mapping (``with``-block use). body: Verbatim lines to add to the subgraph ``body`` (``with``-block use). See the :ref:`usage examples in the User Guide `. When used as a context manager, the returned new graph instance uses ``strict=None`` and the parent graph's values for ``directory``, ``format``, ``engine``, and ``encoding`` by default. Note: If the ``name`` of the subgraph begins with ``'cluster'`` (all lowercase) the layout engine will treat it as a special cluster subgraph. """ if graph is None: kwargs = self._copy_kwargs() kwargs.pop('filename', None) kwargs.update(name=name, comment=comment, graph_attr=graph_attr, node_attr=node_attr, edge_attr=edge_attr, body=body, strict=None) subgraph = self.__class__(**kwargs) @contextlib.contextmanager def subgraph_contextmanager(*, parent): """Return subgraph and add to parent on exit.""" yield subgraph parent.subgraph(subgraph) return subgraph_contextmanager(parent=self) args = [name, comment, graph_attr, node_attr, edge_attr, body] if not all(a is None for a in args): raise ValueError('graph must be sole argument of subgraph()') if graph.directed != self.directed: raise ValueError(f'{self!r} cannot add subgraph of different kind:' f' {graph!r}') self.body += [f'\t{line}' for line in graph.__iter__(subgraph=True)] graphviz-0.20.2/graphviz/encoding.py0000666000000000000000000000212314146235142016121 0ustar rootroot"""Encoding parameter handling and default.""" import typing import codecs import locale from . import copying __all__ = ['DEFAULT_ENCODING', 'Encoding'] DEFAULT_ENCODING = 'utf-8' class Encoding(copying.CopyBase): """Encoding used for input and output with ``'utf-8'`` default.""" _encoding = DEFAULT_ENCODING def __init__(self, *, encoding: typing.Optional[str] = DEFAULT_ENCODING, **kwargs) -> None: super().__init__(**kwargs) self.encoding = encoding def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" return super()._copy_kwargs(encoding=self._encoding, **kwargs) @property def encoding(self) -> str: """The encoding for the saved source file.""" return self._encoding @encoding.setter def encoding(self, encoding: typing.Optional[str]) -> None: if encoding is None: encoding = locale.getpreferredencoding() codecs.lookup(encoding) # raise early self._encoding = encoding graphviz-0.20.2/graphviz/exceptions.py0000666000000000000000000000206114226660050016514 0ustar rootroot"""Commonly used exception classes.""" from .backend.execute import ExecutableNotFound, CalledProcessError __all__ = ['ExecutableNotFound', 'CalledProcessError', 'RequiredArgumentError', 'FileExistsError', 'UnknownSuffixWarning', 'FormatSuffixMismatchWarning', 'DotSyntaxWarning'] class RequiredArgumentError(TypeError): """:exc:`TypeError` raised if a required argument is missing.""" class FileExistsError(FileExistsError): """:exc:`FileExistsError` raised with ``raise_if_exists=True``.""" class UnknownSuffixWarning(RuntimeWarning): """:exc:`RuntimeWarning` raised if the suffix of ``outfile`` is unknown and the given ``format`` is used instead.""" class FormatSuffixMismatchWarning(UserWarning): """:exc:`UserWarning` raised if the suffix ``outfile`` does not match the given ``format``.""" class DotSyntaxWarning(RuntimeWarning): """:exc:`RuntimeWarning` raised if a quoted string is expected to cause a ``CalledProcessError`` from rendering.""" graphviz-0.20.2/graphviz/graphs.py0000666000000000000000000001051114224214124015611 0ustar rootrootr"""Assemble DOT source code objects. Example: >>> doctest_mark_exe() >>> import graphviz >>> dot = graphviz.Graph(comment='Mønti Pythøn ik den Hølie Grailen') >>> dot.node('Møøse') >>> dot.node('trained_by', 'trained by') >>> dot.node('tutte', 'TUTTE HERMSGERVORDENBROTBORDA') >>> dot.edge('Møøse', 'trained_by') >>> dot.edge('trained_by', 'tutte') >>> dot.node_attr['shape'] = 'rectangle' >>> print(dot.source) #doctest: +NORMALIZE_WHITESPACE // Mønti Pythøn ik den Hølie Grailen graph { node [shape=rectangle] "Møøse" trained_by [label="trained by"] tutte [label="TUTTE HERMSGERVORDENBROTBORDA"] "Møøse" -- trained_by trained_by -- tutte } >>> dot.render('doctest-output/m00se.gv').replace('\\', '/') 'doctest-output/m00se.gv.pdf' """ import typing from .encoding import DEFAULT_ENCODING from . import _tools from . import dot from . import jupyter_integration from . import piping from . import rendering from . import unflattening __all__ = ['Graph', 'Digraph'] class BaseGraph(dot.Dot, rendering.Render, jupyter_integration.JupyterIntegration, piping.Pipe, unflattening.Unflatten): """Dot language creation and source code rendering.""" @_tools.deprecate_positional_args(supported_number=2) def __init__(self, name: typing.Optional[str] = None, comment: typing.Optional[str] = None, filename=None, directory=None, format: typing.Optional[str] = None, engine: typing.Optional[str] = None, encoding: typing.Optional[str] = DEFAULT_ENCODING, graph_attr=None, node_attr=None, edge_attr=None, body=None, strict: bool = False, *, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None) -> None: if filename is None and name is not None: filename = f'{name}.{self._default_extension}' super().__init__(name=name, comment=comment, graph_attr=graph_attr, node_attr=node_attr, edge_attr=edge_attr, body=body, strict=strict, filename=filename, directory=directory, encoding=encoding, format=format, engine=engine, renderer=renderer, formatter=formatter) @property def source(self) -> str: """The generated DOT source code as string.""" return ''.join(self) class Graph(dot.GraphSyntax, BaseGraph): """Graph source code in the DOT language. Args: name: Graph name used in the source code. comment: Comment added to the first line of the source. filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``). directory: (Sub)directory for source saving and rendering. format: Rendering output format (``'pdf'``, ``'png'``, ...). engine: Layout command used (``'dot'``, ``'neato'``, ...). renderer: Output renderer used (``'cairo'``, ``'gd'``, ...). formatter: Output formatter used (``'cairo'``, ``'gd'``, ...). encoding: Encoding for saving the source. graph_attr: Mapping of ``(attribute, value)`` pairs for the graph. node_attr: Mapping of ``(attribute, value)`` pairs set for all nodes. edge_attr: Mapping of ``(attribute, value)`` pairs set for all edges. body: Iterable of verbatim lines (including their final newline) to add to the graph ``body``. strict (bool): Rendering should merge multi-edges. Note: All parameters are `optional` and can be changed under their corresponding attribute name after instance creation. """ @property def directed(self) -> bool: """``False``""" return False class Digraph(dot.DigraphSyntax, BaseGraph): """Directed graph source code in the DOT language.""" if Graph.__doc__ is not None: __doc__ += Graph.__doc__.partition('.')[2] @property def directed(self) -> bool: """``True``""" return True graphviz-0.20.2/graphviz/jupyter_integration.py0000666000000000000000000001054314432726140020446 0ustar rootroot"""Display rendered graph as SVG in Jupyter Notebooks and QtConsole.""" import typing from . import piping __all__ = ['JUPYTER_FORMATS', 'SUPPORTED_JUPYTER_FORMATS', 'DEFAULT_JUPYTER_FORMAT', 'get_jupyter_format_mimetype', 'JupyterIntegration'] _IMAGE_JPEG = 'image/jpeg' JUPYTER_FORMATS = {'jpeg': _IMAGE_JPEG, 'jpg': _IMAGE_JPEG, 'png': 'image/png', 'svg': 'image/svg+xml'} SUPPORTED_JUPYTER_FORMATS = set(JUPYTER_FORMATS) DEFAULT_JUPYTER_FORMAT = next(_ for _ in SUPPORTED_JUPYTER_FORMATS if _ == 'svg') MIME_TYPES = {'image/jpeg': '_repr_image_jpeg', 'image/png': '_repr_image_png', 'image/svg+xml': '_repr_image_svg_xml'} assert MIME_TYPES.keys() == set(JUPYTER_FORMATS.values()) SVG_ENCODING = 'utf-8' def get_jupyter_format_mimetype(jupyter_format: str) -> str: try: return JUPYTER_FORMATS[jupyter_format] except KeyError: raise ValueError(f'unknown jupyter_format: {jupyter_format!r}' f' (must be one of {sorted(JUPYTER_FORMATS)})') def get_jupyter_mimetype_format(mimetype: str) -> str: if mimetype not in MIME_TYPES: raise ValueError(f'unsupported mimetype: {mimetype!r}' f' (must be one of {sorted(MIME_TYPES)})') assert mimetype in JUPYTER_FORMATS.values() for format, jupyter_mimetype in JUPYTER_FORMATS.items(): if jupyter_mimetype == mimetype: return format raise RuntimeError # pragma: no cover class JupyterIntegration(piping.Pipe): """Display rendered graph as SVG in Jupyter Notebooks and QtConsole.""" _jupyter_mimetype = get_jupyter_format_mimetype(DEFAULT_JUPYTER_FORMAT) def _repr_mimebundle_(self, include: typing.Optional[typing.Iterable[str]] = None, exclude: typing.Optional[typing.Iterable[str]] = None, **_) -> typing.Dict[str, typing.Union[bytes, str]]: r"""Return the rendered graph as IPython mimebundle. Args: include: Iterable of mimetypes to include in the result. If not given or ``None``: ``['image/sxg+xml']``. exclude: Iterable of minetypes to exclude from the result. Overrides ``include``. Returns: Mapping from mimetypes to data. Example: >>> doctest_mark_exe() >>> import graphviz >>> dot = graphviz.Graph() >>> dot._repr_mimebundle_() # doctest: +ELLIPSIS {'image/svg+xml': '>> dot._repr_mimebundle_(include=['image/png']) # doctest: +ELLIPSIS {'image/png': b'\x89PNG... >>> dot._repr_mimebundle_(include=[]) {} >>> dot._repr_mimebundle_(include=['image/svg+xml', 'image/jpeg'], ... exclude=['image/svg+xml']) # doctest: +ELLIPSIS {'image/jpeg': b'\xff... >>> list(dot._repr_mimebundle_(include=['image/png', 'image/jpeg'])) ['image/jpeg', 'image/png'] See also: IPython documentation: - https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#functions - https://ipython.readthedocs.io/en/stable/config/integrating.html#MyObject._repr_mimebundle_ # noqa: E501 - https://nbviewer.org/github/ipython/ipython/blob/master/examples/IPython%20Kernel/Custom%20Display%20Logic.ipynb#Custom-Mimetypes-with-_repr_mimebundle_ # noqa: E501 """ include = set(include) if include is not None else {self._jupyter_mimetype} include -= set(exclude or []) return {mimetype: getattr(self, method_name)() for mimetype, method_name in MIME_TYPES.items() if mimetype in include} def _repr_image_jpeg(self) -> bytes: """Return the rendered graph as JPEG bytes.""" return self.pipe(format='jpeg') def _repr_image_png(self) -> bytes: """Return the rendered graph as PNG bytes.""" return self.pipe(format='png') def _repr_image_svg_xml(self) -> str: """Return the rendered graph as SVG string.""" return self.pipe(format='svg', encoding=SVG_ENCODING) graphviz-0.20.2/graphviz/piping.py0000666000000000000000000001574114226660050015632 0ustar rootroot"""Pipe DOT code objects through Graphviz ``dot``.""" import codecs import logging import typing from . import _tools from . import backend from . import exceptions from . import base from . import encoding __all__ = ['Pipe'] log = logging.getLogger(__name__) class Pipe(encoding.Encoding, base.Base, backend.Pipe): """Pipe source lines through the Graphviz layout command.""" @typing.overload def pipe(self, format: typing.Optional[str] = ..., renderer: typing.Optional[str] = ..., formatter: typing.Optional[str] = ..., neato_no_op: typing.Union[bool, int, None] = ..., quiet: bool = ..., *, engine: typing.Optional[str] = ..., encoding: None = ...) -> bytes: """Return bytes with default ``encoding=None``.""" @typing.overload def pipe(self, format: typing.Optional[str] = ..., renderer: typing.Optional[str] = ..., formatter: typing.Optional[str] = ..., neato_no_op: typing.Union[bool, int, None] = ..., quiet: bool = ..., *, engine: typing.Optional[str] = ..., encoding: str) -> str: """Return string when given encoding.""" @typing.overload def pipe(self, format: typing.Optional[str] = ..., renderer: typing.Optional[str] = ..., formatter: typing.Optional[str] = ..., neato_no_op: typing.Union[bool, int, None] = ..., quiet: bool = ..., *, engine: typing.Optional[str] = ..., encoding: typing.Optional[str]) -> typing.Union[bytes, str]: """Return bytes or string depending on encoding argument.""" def pipe(self, format: typing.Optional[str] = None, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None, neato_no_op: typing.Union[bool, int, None] = None, quiet: bool = False, *, engine: typing.Optional[str] = None, encoding: typing.Optional[str] = None) -> typing.Union[bytes, str]: """Return the source piped through the Graphviz layout command. Args: format: The output format used for rendering (``'pdf'``, ``'png'``, etc.). renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...). formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...). neato_no_op: Neato layout engine no-op flag. quiet (bool): Suppress ``stderr`` output from the layout subprocess. engine: Layout engine for rendering (``'dot'``, ``'neato'``, ...). encoding: Encoding for decoding the stdout. Returns: Bytes or if encoding is given decoded string (stdout of the layout command). Raises: ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are unknown. graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable is not found. graphviz.CalledProcessError: If the returncode (exit status) of the rendering ``dot`` subprocess is non-zero. Example: >>> doctest_mark_exe() >>> import graphviz >>> source = 'graph { spam }' >>> graphviz.Source(source, format='svg').pipe()[:14] b'>> graphviz.Source(source, format='svg').pipe(encoding='ascii')[:14] '>> graphviz.Source(source, format='svg').pipe(encoding='utf-8')[:14] ' typing.Union[bytes, str]: return self._pipe_future(format, renderer=renderer, formatter=formatter, neato_no_op=neato_no_op, quiet=quiet, engine=engine, encoding=encoding) def _pipe_future(self, format: typing.Optional[str] = None, *, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None, neato_no_op: typing.Union[bool, int, None] = None, quiet: bool = False, engine: typing.Optional[str] = None, encoding: typing.Optional[str] = None) -> typing.Union[bytes, str]: args, kwargs = self._get_pipe_parameters(engine=engine, format=format, renderer=renderer, formatter=formatter, neato_no_op=neato_no_op, quiet=quiet, verify=True) args.append(iter(self)) if encoding is not None: if codecs.lookup(encoding) is codecs.lookup(self.encoding): # common case: both stdin and stdout need the same encoding return self._pipe_lines_string(*args, encoding=encoding, **kwargs) try: raw = self._pipe_lines(*args, input_encoding=self.encoding, **kwargs) except exceptions.CalledProcessError as e: *args, output, stderr = e.args if output is not None: output = output.decode(self.encoding) if stderr is not None: stderr = stderr.decode(self.encoding) raise e.__class__(*args, output=output, stderr=stderr) else: return raw.decode(encoding) return self._pipe_lines(*args, input_encoding=self.encoding, **kwargs) graphviz-0.20.2/graphviz/quoting.py0000666000000000000000000001464414226660050016033 0ustar rootroot"""Quote strings to be valid DOT identifiers, assemble quoted attribute lists.""" import functools import re import typing import warnings from . import _tools from . import exceptions __all__ = ['quote', 'quote_edge', 'a_list', 'attr_list', 'escape', 'nohtml'] # https://www.graphviz.org/doc/info/lang.html # https://www.graphviz.org/doc/info/attrs.html#k:escString HTML_STRING = re.compile(r'<.*>$', re.DOTALL) ID = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*|-?(\.[0-9]+|[0-9]+(\.[0-9]*)?))$') KEYWORDS = {'node', 'edge', 'graph', 'digraph', 'subgraph', 'strict'} COMPASS = {'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'c', '_'} # TODO FINAL_ODD_BACKSLASHES = re.compile(r'(?(?:\\{2})*) \\? # treat \" same as " (?P") ''', flags=re.VERBOSE) ESCAPE_UNESCAPED_QUOTES = functools.partial(QUOTE_WITH_OPTIONAL_BACKSLASHES.sub, r'\g' r'\\' r'\g') @_tools.deprecate_positional_args(supported_number=1) def quote(identifier: str, is_html_string=HTML_STRING.match, is_valid_id=ID.match, dot_keywords=KEYWORDS, endswith_odd_number_of_backslashes=FINAL_ODD_BACKSLASHES.search, escape_unescaped_quotes=ESCAPE_UNESCAPED_QUOTES) -> str: r"""Return DOT identifier from string, quote if needed. >>> quote('') # doctest: +NO_EXE '""' >>> quote('spam') 'spam' >>> quote('spam spam') '"spam spam"' >>> quote('-4.2') '-4.2' >>> quote('.42') '.42' >>> quote('<spam>') '<spam>' >>> quote(nohtml('<>')) '"<>"' >>> print(quote('"')) "\"" >>> print(quote('\\"')) "\"" >>> print(quote('\\\\"')) "\\\"" >>> print(quote('\\\\\\"')) "\\\"" """ if is_html_string(identifier) and not isinstance(identifier, NoHtml): pass elif not is_valid_id(identifier) or identifier.lower() in dot_keywords: if endswith_odd_number_of_backslashes(identifier): warnings.warn('expect syntax error scanning invalid quoted string:' f' {identifier!r}', category=exceptions.DotSyntaxWarning) return f'"{escape_unescaped_quotes(identifier)}"' return identifier def quote_edge(identifier: str) -> str: """Return DOT edge statement node_id from string, quote if needed. >>> quote_edge('spam') # doctest: +NO_EXE 'spam' >>> quote_edge('spam spam:eggs eggs') '"spam spam":"eggs eggs"' >>> quote_edge('spam:eggs:s') 'spam:eggs:s' """ node, _, rest = identifier.partition(':') parts = [quote(node)] if rest: port, _, compass = rest.partition(':') parts.append(quote(port)) if compass: parts.append(compass) return ':'.join(parts) @_tools.deprecate_positional_args(supported_number=1) def a_list(label: typing.Optional[str] = None, kwargs=None, attributes=None) -> str: """Return assembled DOT a_list string. >>> a_list('spam', kwargs={'spam': None, 'ham': 'ham ham', 'eggs': ''}) # doctest: +NO_EXE 'label=spam eggs="" ham="ham ham"' """ result = [f'label={quote(label)}'] if label is not None else [] if kwargs: result += [f'{quote(k)}={quote(v)}' for k, v in _tools.mapping_items(kwargs) if v is not None] if attributes: if hasattr(attributes, 'items'): attributes = _tools.mapping_items(attributes) result += [f'{quote(k)}={quote(v)}' for k, v in attributes if v is not None] return ' '.join(result) @_tools.deprecate_positional_args(supported_number=1) def attr_list(label: typing.Optional[str] = None, kwargs=None, attributes=None) -> str: """Return assembled DOT attribute list string. Sorts ``kwargs`` and ``attributes`` if they are plain dicts (to avoid unpredictable order from hash randomization in Python < 3.7). >>> attr_list() # doctest: +NO_EXE '' >>> attr_list('spam spam', kwargs={'eggs': 'eggs', 'ham': 'ham ham'}) ' [label="spam spam" eggs=eggs ham="ham ham"]' >>> attr_list(kwargs={'spam': None, 'eggs': ''}) ' [eggs=""]' """ content = a_list(label, kwargs=kwargs, attributes=attributes) if not content: return '' return f' [{content}]' class Quote: """Quote strings to be valid DOT identifiers, assemble quoted attribute lists.""" _quote = staticmethod(quote) _quote_edge = staticmethod(quote_edge) _a_list = staticmethod(a_list) _attr_list = staticmethod(attr_list) def escape(s: str) -> str: r"""Return string disabling special meaning of backslashes and ``'<...>'``. Args: s: String in which backslashes and ``'<...>'`` should be treated as literal. Returns: Escaped string subclass instance. Raises: TypeError: If ``s`` is not a ``str``. Example: >>> import graphviz # doctest: +NO_EXE >>> print(graphviz.escape(r'\l')) \\l See also: Upstream documentation: https://www.graphviz.org/doc/info/attrs.html#k:escString """ return nohtml(s.replace('\\', '\\\\')) class NoHtml(str): """String subclass that does not treat ``'<...>'`` as DOT HTML string.""" __slots__ = () def nohtml(s: str) -> str: """Return string not treating ``'<...>'`` as DOT HTML string in quoting. Args: s: String in which leading ``'<'`` and trailing ``'>'`` should be treated as literal. Returns: String subclass instance. Raises: TypeError: If ``s`` is not a ``str``. Example: >>> import graphviz # doctest: +NO_EXE >>> g = graphviz.Graph() >>> g.node(graphviz.nohtml('<>-*-<>')) >>> print(g.source) # doctest: +NORMALIZE_WHITESPACE graph { "<>-*-<>" } """ return NoHtml(s) graphviz-0.20.2/graphviz/rendering.py0000666000000000000000000002000614550602024016304 0ustar rootroot"""Save DOT code objects, render with Graphviz ``dot``, and open in viewer.""" import logging import os import pathlib import typing from . import _tools from . import backend from . import saving __all__ = ['Render'] log = logging.getLogger(__name__) class Render(saving.Save, backend.Render, backend.View): """Write source lines to file and render with Graphviz.""" @_tools.deprecate_positional_args(supported_number=2) def render(self, filename: typing.Union[os.PathLike, str, None] = None, directory: typing.Union[os.PathLike, str, None] = None, view: bool = False, cleanup: bool = False, format: typing.Optional[str] = None, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None, neato_no_op: typing.Union[bool, int, None] = None, quiet: bool = False, quiet_view: bool = False, *, outfile: typing.Union[os.PathLike, str, None] = None, engine: typing.Optional[str] = None, raise_if_result_exists: bool = False, overwrite_source: bool = False) -> str: r"""Save the source to file and render with the Graphviz engine. Args: filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``).s directory: (Sub)directory for source saving and rendering. view (bool): Open the rendered result with the default application. cleanup (bool): Delete the source file after successful rendering. format: The output format used for rendering (``'pdf'``, ``'png'``, etc.). renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...). formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...). neato_no_op: Neato layout engine no-op flag. quiet (bool): Suppress ``stderr`` output from the layout subprocess. quiet_view (bool): Suppress ``stderr`` output from the viewer process (implies ``view=True``, ineffective on Windows platform). outfile: Path for the rendered output file. engine: Layout engine for rendering (``'dot'``, ``'neato'``, ...). raise_if_result_exists: Raise :exc:`graphviz.FileExistsError` if the result file exists. overwrite_source: Allow ``dot`` to write to the file it reads from. Incompatible with ``raise_if_result_exists``. Returns: The (possibly relative) path of the rendered file. Raises: ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are unknown. graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. ValueError: If ``outfile`` is the same file as the source file unless ``overwite_source=True``. graphviz.ExecutableNotFound: If the Graphviz ``dot`` executable is not found. graphviz.CalledProcessError: If the returncode (exit status) of the rendering ``dot`` subprocess is non-zero. RuntimeError: If viewer opening is requested but not supported. Example: >>> doctest_mark_exe() >>> import graphviz >>> dot = graphviz.Graph(name='spam', directory='doctest-output') >>> dot.render(format='png').replace('\\', '/') 'doctest-output/spam.gv.png' >>> dot.render(outfile='spam.svg').replace('\\', '/') 'doctest-output/spam.svg' Note: The layout command is started from the directory of ``filepath``, so that references to external files (e.g. ``[image=images/camelot.png]``) can be given as paths relative to the DOT source file. """ outfile = _tools.promote_pathlike(outfile) if outfile is not None: format = self._get_format(outfile, format=format) if directory is None: outfile = pathlib.Path(self.directory, outfile) args, kwargs = self._get_render_parameters(engine=engine, format=format, renderer=renderer, formatter=formatter, neato_no_op=neato_no_op, quiet=quiet, outfile=outfile, raise_if_result_exists=raise_if_result_exists, overwrite_source=overwrite_source, verify=True) if outfile is not None and filename is None: filename = self._get_filepath(outfile) filepath = self.save(filename, directory=directory, skip_existing=None) args.append(filepath) rendered = self._render(*args, **kwargs) if cleanup: log.debug('delete %r', filepath) os.remove(filepath) if quiet_view or view: self._view(rendered, format=self._format, quiet=quiet_view) return rendered def _view(self, filepath: typing.Union[os.PathLike, str], *, format: str, quiet: bool) -> None: """Start the right viewer based on file format and platform.""" methodnames = [ f'_view_{format}_{backend.viewing.PLATFORM}', f'_view_{backend.viewing.PLATFORM}', ] for name in methodnames: view_method = getattr(self, name, None) if view_method is not None: break else: raise RuntimeError(f'{self.__class__!r} has no built-in viewer' f' support for {format!r}' f' on {backend.viewing.PLATFORM!r} platform') view_method(filepath, quiet=quiet) @_tools.deprecate_positional_args(supported_number=2) def view(self, filename: typing.Union[os.PathLike, str, None] = None, directory: typing.Union[os.PathLike, str, None] = None, cleanup: bool = False, quiet: bool = False, quiet_view: bool = False) -> str: """Save the source to file, open the rendered result in a viewer. Convenience short-cut for running ``.render(view=True)``. Args: filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``). directory: (Sub)directory for source saving and rendering. cleanup (bool): Delete the source file after successful rendering. quiet (bool): Suppress ``stderr`` output from the layout subprocess. quiet_view (bool): Suppress ``stderr`` output from the viewer process (ineffective on Windows). Returns: The (possibly relative) path of the rendered file. Raises: graphviz.ExecutableNotFound: If the Graphviz executable is not found. graphviz.CalledProcessError: If the exit status is non-zero. RuntimeError: If opening the viewer is not supported. Short-cut method for calling :meth:`.render` with ``view=True``. Note: There is no option to wait for the application to close, and no way to retrieve the application's exit status. """ return self.render(filename=filename, directory=directory, view=True, cleanup=cleanup, quiet=quiet, quiet_view=quiet_view) graphviz-0.20.2/graphviz/saving.py0000666000000000000000000000526214567756406015653 0ustar rootroot"""Save DOT source lines to a file.""" import logging import os import typing from . import _defaults from . import _tools from . import base from . import encoding __all__ = ['Save'] log = logging.getLogger(__name__) class Save(encoding.Encoding, base.Base): """Save DOT source lines to file.""" directory: typing.Union[str, bytes] = '' _default_extension = _defaults.DEFAULT_SOURCE_EXTENSION _mkdirs = staticmethod(_tools.mkdirs) def __init__(self, *, filename: typing.Union[os.PathLike, str], directory: typing.Union[os.PathLike, str, None] = None, **kwargs) -> None: super().__init__(**kwargs) if filename is None: filename = f'{self.__class__.__name__}.{self._default_extension}' self.filename = os.fspath(filename) """str: Target file name for saving the DOT source file.""" if directory is not None: self.directory = os.fspath(directory) def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" assert 'directory' not in kwargs if 'directory' in self.__dict__: kwargs['directory'] = self.directory return super()._copy_kwargs(filename=self.filename, **kwargs) @property def filepath(self) -> str: """The target path for saving the DOT source file.""" return os.path.join(self.directory, self.filename) @_tools.deprecate_positional_args(supported_number=1) def save(self, filename: typing.Union[os.PathLike, str, None] = None, directory: typing.Union[os.PathLike, str, None] = None, *, skip_existing: typing.Optional[bool] = False) -> str: """Save the DOT source to file. Ensure the file ends with a newline. Args: filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) directory: (Sub)directory for source saving and rendering. skip_existing: Skip write if file exists (default: ``False``). Returns: The (possibly relative) path of the saved source file. """ if filename is not None: self.filename = filename if directory is not None: self.directory = directory filepath = self.filepath if skip_existing and os.path.exists(filepath): return filepath self._mkdirs(filepath) log.debug('write lines to %r', filepath) with open(filepath, 'w', encoding=self.encoding) as fd: for uline in self: fd.write(uline) return filepath graphviz-0.20.2/graphviz/sources.py0000666000000000000000000001404514567756406016046 0ustar rootroot"""Save DOT code objects, render with Graphviz dot, and open in viewer.""" import locale import logging import os import typing from .encoding import DEFAULT_ENCODING from . import _tools from . import saving from . import jupyter_integration from . import piping from . import rendering from . import unflattening __all__ = ['Source'] log = logging.getLogger(__name__) class Source(rendering.Render, saving.Save, jupyter_integration.JupyterIntegration, piping.Pipe, unflattening.Unflatten): """Verbatim DOT source code string to be rendered by Graphviz. Args: source: The verbatim DOT source code string. filename: Filename for saving the source (defaults to ``'Source.gv'``). directory: (Sub)directory for source saving and rendering. format: Rendering output format (``'pdf'``, ``'png'``, ...). engine: Layout engine used (``'dot'``, ``'neato'``, ...). encoding: Encoding for saving the source. Note: All parameters except ``source`` are optional. All of them can be changed under their corresponding attribute name after instance creation. """ @classmethod @_tools.deprecate_positional_args(supported_number=1) def from_file(cls, filename: typing.Union[os.PathLike, str], directory: typing.Union[os.PathLike, str, None] = None, format: typing.Optional[str] = None, engine: typing.Optional[str] = None, encoding: typing.Optional[str] = DEFAULT_ENCODING, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None) -> 'Source': """Return an instance with the source string read from the given file. Args: filename: Filename for loading/saving the source. directory: (Sub)directory for source loading/saving and rendering. format: Rendering output format (``'pdf'``, ``'png'``, ...). engine: Layout command used (``'dot'``, ``'neato'``, ...). encoding: Encoding for loading/saving the source. """ directory = _tools.promote_pathlike_directory(directory) filepath = (os.path.join(directory, filename) if directory.parts else os.fspath(filename)) if encoding is None: encoding = locale.getpreferredencoding() log.debug('read %r with encoding %r', filepath, encoding) with open(filepath, encoding=encoding) as fd: source = fd.read() return cls(source, filename=filename, directory=directory, format=format, engine=engine, encoding=encoding, renderer=renderer, formatter=formatter, loaded_from_path=filepath) @_tools.deprecate_positional_args(supported_number=1) def __init__(self, source: str, filename: typing.Union[os.PathLike, str, None] = None, directory: typing.Union[os.PathLike, str, None] = None, format: typing.Optional[str] = None, engine: typing.Optional[str] = None, encoding: typing.Optional[str] = DEFAULT_ENCODING, *, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None, loaded_from_path: typing.Optional[os.PathLike] = None) -> None: super().__init__(filename=filename, directory=directory, format=format, engine=engine, renderer=renderer, formatter=formatter, encoding=encoding) self._loaded_from_path = loaded_from_path self._source = source # work around pytype false alarm _source: str _loaded_from_path: typing.Optional[os.PathLike] def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" return super()._copy_kwargs(source=self._source, loaded_from_path=self._loaded_from_path, **kwargs) def __iter__(self) -> typing.Iterator[str]: r"""Yield the DOT source code read from file line by line. Yields: Line ending with a newline (``'\n'``). """ lines = self._source.splitlines(keepends=True) yield from lines[:-1] for line in lines[-1:]: suffix = '\n' if not line.endswith('\n') else '' yield line + suffix @property def source(self) -> str: """The DOT source code as string. Normalizes so that the string always ends in a final newline. """ source = self._source if not source.endswith('\n'): source += '\n' return source @_tools.deprecate_positional_args(supported_number=1) def save(self, filename: typing.Union[os.PathLike, str, None] = None, directory: typing.Union[os.PathLike, str, None] = None, *, skip_existing: typing.Optional[bool] = None) -> str: """Save the DOT source to file. Ensure the file ends with a newline. Args: filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) directory: (Sub)directory for source saving and rendering. skip_existing: Skip write if file exists (default: ``None``). By default skips if instance was loaded from the target path: ``.from_file(self.filepath)``. Returns: The (possibly relative) path of the saved source file. """ skip = (skip_existing is None and self._loaded_from_path and os.path.samefile(self._loaded_from_path, self.filepath)) if skip: log.debug('.save(skip_existing=None) skip writing Source.from_file(%r)', self.filepath) return super().save(filename=filename, directory=directory, skip_existing=skip) graphviz-0.20.2/graphviz/unflattening.py0000666000000000000000000000470214151056254017037 0ustar rootroot"""Pipe source through the Graphviz *unflatten* preprocessor.""" import typing import graphviz from . import _tools from . import base from . import backend from . import encoding __all__ = ['Unflatten'] class Unflatten(encoding.Encoding, base.Base, backend.Unflatten): """Pipe source through the Graphviz *unflatten* preprocessor.""" @_tools.deprecate_positional_args(supported_number=1) def unflatten(self, stagger: typing.Optional[int] = None, fanout: bool = False, chain: typing.Optional[int] = None) -> 'graphviz.Source': """Return a new :class:`.Source` instance with the source piped through the Graphviz *unflatten* preprocessor. Args: stagger: Stagger the minimum length of leaf edges between 1 and this small integer. fanout: Fanout nodes with indegree = outdegree = 1 when staggering (requires ``stagger``). chain: Form disconnected nodes into chains of up to this many nodes. Returns: Prepocessed DOT source code (improved layout aspect ratio). Raises: graphviz.RequiredArgumentError: If ``fanout`` is given but ``stagger`` is None. graphviz.ExecutableNotFound: If the Graphviz ``unflatten`` executable is not found. graphviz.CalledProcessError: If the returncode (exit status) of the unflattening 'unflatten' subprocess is non-zero. See also: Upstream documentation: https://www.graphviz.org/pdf/unflatten.1.pdf """ from . import sources out = self._unflatten(self.source, stagger=stagger, fanout=fanout, chain=chain, encoding=self.encoding) kwargs = self._copy_kwargs() return sources.Source(out, filename=kwargs.get('filename'), directory=kwargs.get('directory'), format=kwargs.get('format'), engine=kwargs.get('engine'), encoding=kwargs.get('encoding'), renderer=kwargs.get('renderer'), formatter=kwargs.get('formatter'), loaded_from_path=None) graphviz-0.20.2/graphviz/_compat.py0000666000000000000000000000214714455106354015770 0ustar rootroot"""Python 3.8 compatibility and platform compatibility.""" import platform import sys if sys.version_info < (3, 9): # pragma: no cover # pytype not supported import unittest.mock Literal = unittest.mock.MagicMock(name='Literal') else: # pragma: no cover from typing import Literal Literal = Literal # CAVEAT: use None instead of Literal[None] def get_startupinfo() -> None: """Return None for startupinfo argument of ``subprocess.Popen``.""" return None assert get_startupinfo() is None, 'get_startupinfo() defaults to a no-op' if platform.system() == 'Windows': # pragma: no cover import subprocess def get_startupinfo() -> subprocess.STARTUPINFO: # pytype: disable=module-attr """Return subprocess.STARTUPINFO instance hiding the console window.""" startupinfo = subprocess.STARTUPINFO() # pytype: disable=module-attr startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # pytype: disable=module-attr startupinfo.wShowWindow = subprocess.SW_HIDE # pytype: disable=module-attr return startupinfo graphviz-0.20.2/graphviz/_defaults.py0000666000000000000000000000431714151056254016311 0ustar rootroot"""Set package-wide default parameters and IPython/Jupyter display format.""" __all_ = ['DEFAULT_SOURCE_EXTENSION', 'set_default_engine', 'set_default_format', 'set_jupyter_format'] DEFAULT_SOURCE_EXTENSION = 'gv' def set_default_engine(engine: str) -> str: """Change the default ``engine`` and return the old default value. Args: engine: new default ``engine`` used by all present and newly created instances without explicitly set ``engine`` (``'dot'``, ``'neato'``, ...). Returns: The old default value used for ``engine``. """ from . import parameters parameters.verify_engine(engine) old_default_engine = parameters.Parameters._engine parameters.Parameters._engine = engine return old_default_engine def set_default_format(format: str) -> str: """Change the default ``format`` and return the old default value. Args: format: new default ``format`` used by all present and newly created instances without explicitly set ``format`` (``'pdf'``, ``'png'``, ...). Returns: The old default value used for ``format``. """ from . import parameters parameters.verify_format(format) old_default_format = parameters.Parameters._format parameters.Parameters._format = format return old_default_format def set_jupyter_format(jupyter_format: str) -> str: """Change the default mimetype format for ``_repr_mimebundle_()`` and return the old value. Args: jupyter_format: new default IPython/Jupyter display format used by all present and newly created instances (``'svg'``, ``'png'``, ...). Returns: The old default value used for IPython/Jupyter display format. """ from . import jupyter_integration mimetype = jupyter_integration.get_jupyter_format_mimetype(jupyter_format) old_mimetype = jupyter_integration.JupyterIntegration._jupyter_mimetype old_format = jupyter_integration.get_jupyter_mimetype_format(old_mimetype) jupyter_integration.JupyterIntegration._jupyter_mimetype = mimetype return old_format graphviz-0.20.2/graphviz/_tools.py0000666000000000000000000001435614567756406015667 0ustar rootroot"""Generic re-useable self-contained helper functions.""" import functools import inspect import itertools import logging import os import pathlib import typing import warnings __all__ = ['attach', 'mkdirs', 'mapping_items', 'promote_pathlike', 'promote_pathlike_directory', 'deprecate_positional_args'] log = logging.getLogger(__name__) def attach(object: typing.Any, /, name: str) -> typing.Callable: """Return a decorator doing ``setattr(object, name)`` with its argument. >>> spam = type('Spam', (object,), {})() # doctest: +NO_EXE >>> @attach(spam, 'eggs') ... def func(): ... pass >>> spam.eggs # doctest: +ELLIPSIS """ def decorator(func): setattr(object, name, func) return func return decorator def mkdirs(filename: typing.Union[os.PathLike, str], /, *, mode: int = 0o777) -> None: """Recursively create directories up to the path of ``filename`` as needed.""" dirname = os.path.dirname(filename) if not dirname: return log.debug('os.makedirs(%r)', dirname) os.makedirs(dirname, mode=mode, exist_ok=True) def mapping_items(mapping, /): """Return an iterator over the ``mapping`` items, sort if it's a plain dict. >>> list(mapping_items({'spam': 0, 'ham': 1, 'eggs': 2})) # doctest: +NO_EXE [('eggs', 2), ('ham', 1), ('spam', 0)] >>> from collections import OrderedDict >>> list(mapping_items(OrderedDict(enumerate(['spam', 'ham', 'eggs'])))) [(0, 'spam'), (1, 'ham'), (2, 'eggs')] """ result = iter(mapping.items()) if type(mapping) is dict: result = iter(sorted(result)) return result @typing.overload def promote_pathlike(filepath: typing.Union[os.PathLike, str], /) -> pathlib.Path: """Return path object for path-like-object.""" @typing.overload def promote_pathlike(filepath: None, /) -> None: """Return None for None.""" @typing.overload def promote_pathlike(filepath: typing.Union[os.PathLike, str, None], /, ) -> typing.Optional[pathlib.Path]: """Return path object or ``None`` depending on ``filepath``.""" def promote_pathlike(filepath: typing.Union[os.PathLike, str, None] ) -> typing.Optional[pathlib.Path]: """Return path-like object ``filepath`` promoted into a path object. See also: https://docs.python.org/3/glossary.html#term-path-like-object """ return pathlib.Path(filepath) if filepath is not None else None def promote_pathlike_directory(directory: typing.Union[os.PathLike, str, None], /, *, default: typing.Union[os.PathLike, str, None] = None, ) -> pathlib.Path: """Return path-like object ``directory`` promoted into a path object (default to ``os.curdir``). See also: https://docs.python.org/3/glossary.html#term-path-like-object """ return pathlib.Path(directory if directory is not None else default or os.curdir) def deprecate_positional_args(*, supported_number: int, ignore_argnames: typing.Sequence[str] = ('cls', 'self'), category: typing.Type[Warning] = PendingDeprecationWarning, stacklevel: int = 1): """Mark supported_number of positional arguments as the maximum. Args: supported_number: Number of positional arguments for which no warning is raised. ignore_argnames: Name(s) of arguments to ignore ('cls' and 'self' by default). category: Type of Warning to raise or None to return a nulldecorator returning the undecorated function. stacklevel: See :func:`warning.warn`. Returns: Return a decorator raising a category warning on more than supported_number positional args. See also: https://docs.python.org/3/library/exceptions.html#FutureWarning https://docs.python.org/3/library/exceptions.html#DeprecationWarning https://docs.python.org/3/library/exceptions.html#PendingDeprecationWarning """ assert supported_number > 0, f'supported_number at least one: {supported_number!r}' if category is None: def nulldecorator(func): """Return the undecorated function.""" return func return nulldecorator assert issubclass(category, Warning) stacklevel += 1 def decorator(func): signature = inspect.signature(func) argnames = [name for name, param in signature.parameters.items() if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and name not in ignore_argnames] log.debug('deprecate positional args: %s.%s(%r)', func.__module__, func.__qualname__, argnames[supported_number:]) @functools.wraps(func) def wrapper(*args, **kwargs): if len(args) > supported_number: call_args = zip(argnames, args) supported = itertools.islice(call_args, supported_number) supported = dict(supported) deprecated = dict(call_args) assert deprecated func_name = func.__name__.lstrip('_') func_name, sep, rest = func_name.partition('_legacy') assert not set or not rest wanted = ', '.join(f'{name}={value!r}' for name, value in deprecated.items()) warnings.warn(f'The signature of {func.__name__} will be reduced' f' to {supported_number} positional arg' f"{'s' if supported_number > 1 else ''}" f' {list(supported)}: pass {wanted}' ' as keyword arg(s)', stacklevel=stacklevel, category=category) return func(*args, **kwargs) return wrapper return decorator graphviz-0.20.2/graphviz/__init__.py0000666000000000000000000000666014575735742016125 0ustar rootroot# graphviz - create dot, save, render, view """Assemble DOT source code and render it with Graphviz. Example: >>> import graphviz # doctest: +NO_EXE >>> dot = graphviz.Digraph(comment='The Round Table') >>> dot.node('A', 'King Arthur') >>> dot.node('B', 'Sir Bedevere the Wise') >>> dot.node('L', 'Sir Lancelot the Brave') >>> dot.edges(['AB', 'AL']) >>> dot.edge('B', 'L', constraint='false') >>> print(dot) #doctest: +NORMALIZE_WHITESPACE // The Round Table digraph { A [label="King Arthur"] B [label="Sir Bedevere the Wise"] L [label="Sir Lancelot the Brave"] A -> B A -> L B -> L [constraint=false] } """ from ._defaults import set_default_engine, set_default_format, set_jupyter_format from .backend import (DOT_BINARY, UNFLATTEN_BINARY, render, pipe, pipe_string, pipe_lines, pipe_lines_string, unflatten, version, view) from .exceptions import (ExecutableNotFound, CalledProcessError, RequiredArgumentError, FileExistsError, UnknownSuffixWarning, FormatSuffixMismatchWarning, DotSyntaxWarning) from .graphs import Graph, Digraph from .jupyter_integration import SUPPORTED_JUPYTER_FORMATS from .parameters import ENGINES, FORMATS, RENDERERS, FORMATTERS from .quoting import escape, nohtml from .sources import Source __all__ = ['ENGINES', 'FORMATS', 'RENDERERS', 'FORMATTERS', 'DOT_BINARY', 'UNFLATTEN_BINARY', 'SUPPORTED_JUPYTER_FORMATS', 'Graph', 'Digraph', 'Source', 'escape', 'nohtml', 'render', 'pipe', 'pipe_string', 'pipe_lines', 'pipe_lines_string', 'unflatten', 'version', 'view', 'ExecutableNotFound', 'CalledProcessError', 'RequiredArgumentError', 'FileExistsError', 'UnknownSuffixWarning', 'FormatSuffixMismatchWarning', 'DotSyntaxWarning', 'set_default_engine', 'set_default_format', 'set_jupyter_format'] __title__ = 'graphviz' __version__ = '0.20.2' __author__ = 'Sebastian Bank ' __license__ = 'MIT, see LICENSE.txt' __copyright__ = 'Copyright (c) 2013-2024 Sebastian Bank' ENGINES = ENGINES """:class:`set` of known layout commands used for rendering (``'dot'``, ``'neato'``, ...).""" FORMATS = FORMATS """:class:`set` of known output formats for rendering (``'pdf'``, ``'png'``, ...).""" RENDERERS = RENDERERS """:class:`set` of known output renderers for rendering (``'cairo'``, ``'gd'``, ...).""" FORMATTERS = FORMATTERS """:class:`set` of known output formatters for rendering (``'cairo'``, ``'gd'``, ...).""" SUPPORTED_JUPYTER_FORMATS = SUPPORTED_JUPYTER_FORMATS """:class:`set` of supported formats for ``_repr_mimebundle_()`` (``'svg'``, ``'png'``, ...).""" DOT_BINARY = DOT_BINARY """:class:`pathlib.Path` of rendering command (``Path('dot')``).""" UNFLATTEN_BINARY = UNFLATTEN_BINARY """:class:`pathlib.Path` of unflatten command (``Path('unflatten')``).""" ExecutableNotFound = ExecutableNotFound CalledProcessError = CalledProcessError RequiredArgumentError = RequiredArgumentError FileExistsError = FileExistsError UnknownSuffixWarning = UnknownSuffixWarning FormatSuffixMismatchWarning = FormatSuffixMismatchWarning DotSyntaxWarning = DotSyntaxWarning graphviz-0.20.2/graphviz.egg-info/0000777000000000000000000000000014575736336015476 5ustar rootrootgraphviz-0.20.2/graphviz.egg-info/dependency_links.txt0000666000000000000000000000000114575736334021542 0ustar rootroot graphviz-0.20.2/graphviz.egg-info/PKG-INFO0000666000000000000000000003017414575736334016576 0ustar rootrootMetadata-Version: 2.1 Name: graphviz Version: 0.20.2 Summary: Simple Python interface for Graphviz Home-page: https://github.com/xflr6/graphviz Author: Sebastian Bank Author-email: sebastian.bank@uni-leipzig.de License: MIT Project-URL: Documentation, https://graphviz.readthedocs.io Project-URL: Changelog, https://graphviz.readthedocs.io/en/latest/changelog.html Project-URL: Issue Tracker, https://github.com/xflr6/graphviz/issues Project-URL: CI, https://github.com/xflr6/graphviz/actions Project-URL: Coverage, https://codecov.io/gh/xflr6/graphviz Keywords: graph visualization dot render Platform: any Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Scientific/Engineering :: Visualization Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE.txt Provides-Extra: dev Requires-Dist: tox>=3; extra == "dev" Requires-Dist: flake8; extra == "dev" Requires-Dist: pep8-naming; extra == "dev" Requires-Dist: wheel; extra == "dev" Requires-Dist: twine; extra == "dev" Provides-Extra: test Requires-Dist: pytest<8.1,>=7; extra == "test" Requires-Dist: pytest-mock>=3; extra == "test" Requires-Dist: pytest-cov; extra == "test" Requires-Dist: coverage; extra == "test" Provides-Extra: docs Requires-Dist: sphinx<7,>=5; extra == "docs" Requires-Dist: sphinx-autodoc-typehints; extra == "docs" Requires-Dist: sphinx-rtd-theme; extra == "docs" Graphviz ======== |PyPI version| |License| |Supported Python| |Wheel| |Downloads| |Build| |Codecov| |Readthedocs-stable| |Readthedocs-latest| |Binder-stable| This package facilitates the creation and rendering of graph descriptions in the DOT_ language of the Graphviz_ graph drawing software (`upstream repo`_) from Python. Create a graph object, assemble the graph by adding nodes and edges, and retrieve its DOT source code string. Save the source code to a file and render it with the Graphviz installation of your system. Use the ``view`` option/method to directly inspect the resulting (PDF, PNG, SVG, etc.) file with its default application. Graphs can also be rendered and displayed within `Jupyter notebooks`_ (formerly known as `IPython notebooks`_, `example `_, `nbviewer `_) as well as the `Jupyter QtConsole`_. Links ----- - GitHub: https://github.com/xflr6/graphviz - PyPI: https://pypi.org/project/graphviz/ - Documentation: https://graphviz.readthedocs.io - Changelog: https://graphviz.readthedocs.io/en/latest/changelog.html - Issue Tracker: https://github.com/xflr6/graphviz/issues - Download: https://pypi.org/project/graphviz/#files Installation ------------ This package runs under Python 3.8+, use pip_ to install: .. code:: bash $ pip install graphviz To render the generated DOT source code, you also need to install Graphviz_ (`download page `_, `archived versions `_, `installation procedure for Windows `_). Make sure that the directory containing the ``dot`` executable is on your systems' ``PATH`` (sometimes done by the installer; setting ``PATH`` on `Linux `_, `Mac `_, and `Windows `_). Anaconda_: see the conda-forge_ package `conda-forge/python-graphviz `_ (`feedstock `_), which should automatically ``conda install`` `conda-forge/graphviz `_ (`feedstock `_) as dependency. Quickstart ---------- Create a graph object: .. code:: python >>> import graphviz # doctest: +NO_EXE >>> dot = graphviz.Digraph(comment='The Round Table') >>> dot #doctest: +ELLIPSIS Add nodes and edges: .. code:: python >>> dot.node('A', 'King Arthur') # doctest: +NO_EXE >>> dot.node('B', 'Sir Bedevere the Wise') >>> dot.node('L', 'Sir Lancelot the Brave') >>> dot.edges(['AB', 'AL']) >>> dot.edge('B', 'L', constraint='false') Check the generated source code: .. code:: python >>> print(dot.source) # doctest: +NORMALIZE_WHITESPACE +NO_EXE // The Round Table digraph { A [label="King Arthur"] B [label="Sir Bedevere the Wise"] L [label="Sir Lancelot the Brave"] A -> B A -> L B -> L [constraint=false] } Save and render the source code (skip/ignore any ``doctest_mark_exe()`` lines): .. code:: python >>> doctest_mark_exe() # skip this line >>> dot.render('doctest-output/round-table.gv').replace('\\', '/') 'doctest-output/round-table.gv.pdf' Save and render and view the result: .. code:: python >>> doctest_mark_exe() # skip this line >>> dot.render('doctest-output/round-table.gv', view=True) # doctest: +SKIP 'doctest-output/round-table.gv.pdf' .. image:: https://raw.github.com/xflr6/graphviz/master/docs/_static/round-table.svg :align: center :alt: round-table.svg **Caveat:** Backslash-escapes and strings of the form ``<...>`` have a special meaning in the DOT language. If you need to render arbitrary strings (e.g. from user input), check the details in the `user guide`_. See also -------- - pygraphviz_ |--| full-blown interface wrapping the Graphviz C library with SWIG - graphviz-python_ |--| official Python bindings (`documentation `_) - pydot_ |--| stable pure-Python approach, requires pyparsing License ------- This package is distributed under the `MIT license`_. Development ----------- - Development documentation: https://graphviz.readthedocs.io/en/latest/development.html - Release process: https://graphviz.readthedocs.io/en/latest/release_process.html .. _Graphviz: https://www.graphviz.org .. _DOT: https://www.graphviz.org/doc/info/lang.html .. _upstream repo: https://gitlab.com/graphviz/graphviz/ .. _upstream-download: https://www.graphviz.org/download/ .. _upstream-archived: https://www2.graphviz.org/Archive/stable/ .. _upstream-windows: https://forum.graphviz.org/t/new-simplified-installation-procedure-on-windows/224 .. _set-path-windows: https://www.computerhope.com/issues/ch000549.htm .. _set-path-linux: https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix .. _set-path-darwin: https://stackoverflow.com/questions/22465332/setting-path-environment-variable-in-osx-permanently .. _pip: https://pip.pypa.io .. _Jupyter notebooks: https://jupyter.org .. _IPython notebooks: https://ipython.org/notebook.html .. _Jupyter QtConsole: https://qtconsole.readthedocs.io .. _notebook: https://github.com/xflr6/graphviz/blob/master/examples/graphviz-notebook.ipynb .. _notebook-nbviewer: https://nbviewer.org/github/xflr6/graphviz/blob/master/examples/graphviz-notebook.ipynb .. _Anaconda: https://docs.anaconda.com/anaconda/install/ .. _conda-forge: https://conda-forge.org .. _conda-forge-python-graphviz: https://anaconda.org/conda-forge/python-graphviz .. _conda-forge-python-graphviz-feedstock: https://github.com/conda-forge/python-graphviz-feedstock .. _conda-forge-graphviz: https://anaconda.org/conda-forge/graphviz .. _conda-forge-graphviz-feedstock: https://github.com/conda-forge/graphviz-feedstock .. _user guide: https://graphviz.readthedocs.io/en/stable/manual.html .. _pygraphviz: https://pypi.org/project/pygraphviz/ .. _graphviz-python: https://pypi.org/project/graphviz-python/ .. _graphviz-python-docs: https://www.graphviz.org/pdf/gv.3python.pdf .. _pydot: https://pypi.org/project/pydot/ .. _MIT license: https://opensource.org/licenses/MIT .. |--| unicode:: U+2013 .. |PyPI version| image:: https://img.shields.io/pypi/v/graphviz.svg :target: https://pypi.org/project/graphviz/ :alt: Latest PyPI Version .. |License| image:: https://img.shields.io/pypi/l/graphviz.svg :target: https://github.com/xflr6/graphviz/blob/master/LICENSE.txt :alt: License .. |Supported Python| image:: https://img.shields.io/pypi/pyversions/graphviz.svg :target: https://pypi.org/project/graphviz/ :alt: Supported Python Versions .. |Wheel| image:: https://img.shields.io/pypi/wheel/graphviz.svg :target: https://pypi.org/project/graphviz/#files :alt: Wheel format .. |Downloads| image:: https://img.shields.io/pypi/dm/graphviz.svg :target: https://pypistats.org/packages/graphviz :alt: Monthly downloads .. |Build| image:: https://github.com/xflr6/graphviz/actions/workflows/build.yaml/badge.svg?branch=master :target: https://github.com/xflr6/graphviz/actions/workflows/build.yaml?query=branch%3Amaster :alt: Build .. |Codecov| image:: https://codecov.io/gh/xflr6/graphviz/branch/master/graph/badge.svg :target: https://codecov.io/gh/xflr6/graphviz :alt: Codecov .. |Readthedocs-stable| image:: https://readthedocs.org/projects/graphviz/badge/?version=stable :target: https://graphviz.readthedocs.io/en/stable/ :alt: Readthedocs (stable) .. |Readthedocs-latest| image:: https://readthedocs.org/projects/graphviz/badge/?version=latest :target: https://graphviz.readthedocs.io/en/latest/ :alt: Readthedocs (latest) .. |Binder-stable| image:: https://img.shields.io/badge/launch-binder%20(stable)-579ACA.svg?logo=data:image/png;base64,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 :target: https://mybinder.org/v2/gh/xflr6/graphviz/stable :alt: Binder (stable) graphviz-0.20.2/graphviz.egg-info/requires.txt0000666000000000000000000000024614575736334020076 0ustar rootroot [dev] tox>=3 flake8 pep8-naming wheel twine [docs] sphinx<7,>=5 sphinx-autodoc-typehints sphinx-rtd-theme [test] pytest<8.1,>=7 pytest-mock>=3 pytest-cov coverage graphviz-0.20.2/graphviz.egg-info/SOURCES.txt0000666000000000000000000000676414575736336017377 0ustar rootrootCHANGES.rst LICENSE.txt MANIFEST.in README.rst build-docs.py conftest.py lint-code.py requirements.txt run-tests.py setup.cfg setup.py tox.ini try-examples.py update-help.py docs/_links.rst docs/api.rst docs/attributes.rst docs/basic_usage.rst docs/changelog.rst docs/conf.py docs/custom_dot.rst docs/development.rst docs/engines.rst docs/escapes.rst docs/examples.rst docs/existing_files.rst docs/formats.rst docs/index.rst docs/installation.rst docs/integration_with_viewers.rst docs/jupyter_notebooks.rst docs/license.rst docs/manual.rst docs/neato_no_op.rst docs/node_ports.rst docs/notebooks.rst docs/piped_output.rst docs/quoting.rst docs/raw_dot.rst docs/release_process.rst docs/round-table.png docs/styling.rst docs/subgraphs_and_clusters.rst docs/unflatten.rst docs/_static/angles.svg docs/_static/btree.svg docs/_static/cluster.svg docs/_static/cluster_edge.svg docs/_static/colors.svg docs/_static/diamond.svg docs/_static/er.svg docs/_static/escapes.svg docs/_static/fdpclust.svg docs/_static/fsm.svg docs/_static/g_c_n.svg docs/_static/hello.svg docs/_static/holy-grenade.svg docs/_static/html_table.svg docs/_static/literal_backslash.svg docs/_static/ni.svg docs/_static/pet-shop.svg docs/_static/process.svg docs/_static/qtconsole-source.png docs/_static/qtconsole.png docs/_static/rank_same.svg docs/_static/round-table.svg docs/_static/splines.svg docs/_static/structs.svg docs/_static/structs_revisited.svg docs/_static/traffic_lights.svg docs/_static/unix.svg docs/_static/wide-unflatten-stagger-2.svg docs/_static/wide-unflatten-stagger-3.svg docs/_static/wide.svg examples/angles.py examples/btree.py examples/cluster.py examples/cluster_edge.py examples/colors.py examples/er.py examples/fdpclust.py examples/fsm.py examples/g_c_n.py examples/graphviz-engines.ipynb examples/graphviz-escapes.ipynb examples/graphviz-jupyter-format.ipynb examples/graphviz-notebook.ipynb examples/graphviz_transform_recipe.py examples/hello.py examples/process.py examples/rank_same.py examples/structs.py examples/structs_revisited.py examples/traffic_lights.py examples/unix.py graphviz/__init__.py graphviz/_compat.py graphviz/_defaults.py graphviz/_tools.py graphviz/base.py graphviz/copying.py graphviz/dot.py graphviz/encoding.py graphviz/exceptions.py graphviz/graphs.py graphviz/jupyter_integration.py graphviz/piping.py graphviz/quoting.py graphviz/rendering.py graphviz/saving.py graphviz/sources.py graphviz/unflattening.py graphviz.egg-info/PKG-INFO graphviz.egg-info/SOURCES.txt graphviz.egg-info/dependency_links.txt graphviz.egg-info/requires.txt graphviz.egg-info/top_level.txt graphviz/backend/__init__.py graphviz/backend/dot_command.py graphviz/backend/execute.py graphviz/backend/mixins.py graphviz/backend/piping.py graphviz/backend/rendering.py graphviz/backend/unflattening.py graphviz/backend/upstream_version.py graphviz/backend/viewing.py graphviz/parameters/__init__.py graphviz/parameters/base.py graphviz/parameters/engines.py graphviz/parameters/formats.py graphviz/parameters/formatters.py graphviz/parameters/mixins.py graphviz/parameters/renderers.py tests/_common.py tests/conftest.py tests/dot_red.png tests/test_all_classes.py tests/test_graphs.py tests/test_init.py tests/test_jupyter_integration.py tests/test_parameters.py tests/test_quoting.py tests/test_saving.py tests/test_sources.py tests/test_tools.py tests/backend/conftest.py tests/backend/test_execute.py tests/backend/test_piping.py tests/backend/test_rendering.py tests/backend/test_unflattening.py tests/backend/test_upstream_version.py tests/backend/test_viewing.pygraphviz-0.20.2/graphviz.egg-info/top_level.txt0000666000000000000000000000001114575736334020216 0ustar rootrootgraphviz graphviz-0.20.2/tests/0000777000000000000000000000000014575736336013314 5ustar rootrootgraphviz-0.20.2/tests/backend/0000777000000000000000000000000014575736336014703 5ustar rootrootgraphviz-0.20.2/tests/backend/conftest.py0000666000000000000000000000100014144304656017055 0ustar rootroot"""pytest fixtures for backend.""" import pytest @pytest.fixture def mock_run(mocker): yield mocker.patch('subprocess.run', autospec=True) @pytest.fixture def mock_popen(mocker): yield mocker.patch('subprocess.Popen', autospec=True) @pytest.fixture def mock_startfile(mocker, platform): if platform == 'windows': kwargs = {'autospec': True} else: kwargs = {'create': True, 'new_callable': mocker.Mock} yield mocker.patch('os.startfile', **kwargs) graphviz-0.20.2/tests/backend/test_execute.py0000666000000000000000000000656614144304656017757 0ustar rootrootimport errno import io import subprocess import pytest import graphviz from graphviz.backend import execute import _common @pytest.fixture def empty_path(monkeypatch): monkeypatch.setenv('PATH', '') @pytest.mark.usefixtures('empty_path') @pytest.mark.parametrize( 'func, args', [(graphviz.render, ['dot', 'pdf', 'nonfilepath']), (graphviz.pipe, ['dot', 'pdf', b'nongraph']), (graphviz.unflatten, ['graph {}']), (graphviz.version, [])]) def test_missing_executable(func, args): with pytest.raises(graphviz.ExecutableNotFound, match=r'execute'): func(*args) def test_run_check_oserror(): with pytest.raises(OSError) as e: execute.run_check(_common.INVALID_CMD) assert e.value.errno in (errno.EACCES, errno.EINVAL) def test_run_check_called_process_error_mocked(capsys, mock_run, quiet, stdout='I am the messiah', stderr='I am not the messiah!'): mock_run.return_value = subprocess.CompletedProcess(_common.INVALID_CMD, returncode=500, stdout=stdout, stderr=stderr) with pytest.raises(execute.CalledProcessError, match=stderr): execute.run_check(_common.INVALID_CMD, capture_output=True, quiet=quiet) assert capsys.readouterr() == ('', '' if quiet else stderr) def test_run_check_input_lines_mocked(mocker, sentinel, mock_popen, line=b'sp\xc3\xa4m'): # noqa: N803 mock_sys_stderr = mocker.patch('sys.stderr', autospec=True, flush=mocker.Mock(), encoding=sentinel.encoding) mock_out = mocker.create_autospec(bytes, instance=True, name='mock_out') mock_err = mocker.create_autospec(bytes, instance=True, name='mock_err', **{'__len__.return_value': 1}) proc = mock_popen.return_value proc.configure_mock(args=_common.INVALID_CMD, returncode=0, stdin=mocker.create_autospec(io.BytesIO, instance=True)) proc.communicate.return_value = (mock_out, mock_err) result = execute.run_check(proc.args, input_lines=iter([line]), capture_output=True) # subprocess.CompletedProcess.__eq__() is not implemented assert isinstance(result, subprocess.CompletedProcess) assert result.args is proc.args assert result.returncode == proc.returncode assert result.stdout is mock_out assert result.stderr is mock_err mock_popen.assert_called_once_with(_common.INVALID_CMD, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=_common.StartupinfoMatcher()) proc.communicate.assert_called_once_with() mock_out.decode.assert_not_called() mock_err.decode.assert_called_once_with(sentinel.encoding) mock_sys_stderr.write.assert_called_once_with(mock_err.decode.return_value) mock_sys_stderr.flush.assert_called_once_with() graphviz-0.20.2/tests/backend/test_piping.py0000666000000000000000000001711114567756406017604 0ustar rootrootimport io import re import subprocess import pytest import graphviz import _common SVG_PATTERN = r'(?s)^<\?xml .+\s*$' @pytest.mark.exe @pytest.mark.xfail('graphviz.version() == (2, 36, 0)', reason='https://bugs.launchpad.net/ubuntu/+source/graphviz/+bug/1694108') def test_pipe_invalid_data(capsys, quiet, engine='dot', format_='svg'): with pytest.raises(subprocess.CalledProcessError) as e: graphviz.pipe(engine, format_, b'nongraph', quiet=quiet) assert e.value.returncode == 1 assert 'syntax error in line' in str(e.value) out, err = capsys.readouterr() assert out == '' if quiet: assert err == '' else: assert 'syntax error in line' in err def test_pipe_pipe_invalid_data_mocked(mocker, sentinel, mock_run, quiet): mock_sys_stderr = mocker.patch('sys.stderr', autospec=True, flush=mocker.Mock(), encoding=sentinel.encoding) mock_out = mocker.create_autospec(bytes, instance=True, name='mock_out') mock_err = mocker.create_autospec(bytes, instance=True, name='mock_err', **{'__len__.return_value': 1}) mock_run.return_value = subprocess.CompletedProcess(_common.INVALID_CMD, returncode=5, stdout=mock_out, stderr=mock_err) with pytest.raises(subprocess.CalledProcessError) as e: graphviz.pipe('dot', 'png', b'nongraph', quiet=quiet) assert e.value.returncode == 5 assert e.value.cmd == _common.INVALID_CMD assert e.value.stdout is mock_out assert e.value.stderr is mock_err e.value.stdout = sentinel.new_stdout assert e.value.stdout is sentinel.new_stdout mock_run.assert_called_once_with([_common.EXPECTED_DOT_BINARY, '-Kdot', '-Tpng'], input=b'nongraph', capture_output=True, startupinfo=_common.StartupinfoMatcher()) if not quiet: mock_out.decode.assert_not_called() mock_err.decode.assert_called_once_with(sentinel.encoding) mock_sys_stderr.write.assert_called_once_with(mock_err.decode.return_value) mock_sys_stderr.flush.assert_called_once_with() @pytest.mark.exe @pytest.mark.parametrize( 'engine, format_, renderer, formatter, pattern', [('dot', 'svg', None, None, SVG_PATTERN), ('dot', 'ps', 'ps', 'core', r'%!PS-'), # Error: remove_overlap: Graphviz not built with triangulation library pytest.param('sfdp', 'svg', None, None, SVG_PATTERN, marks=pytest.mark.xfail('graphviz.version() > (2, 38, 0)' " and platform.system().lower() == 'windows'", reason='https://gitlab.com/graphviz/graphviz/-/issues/1269'))]) def test_pipe(capsys, engine, format_, renderer, formatter, pattern, data=b'graph { spam }'): with pytest.deprecated_call(match=r'3 positional args'): out = graphviz.pipe(engine, format_, data, renderer, formatter).decode('ascii') if pattern is not None: assert re.match(pattern, out) assert capsys.readouterr() == ('', '') def test_pipe_mocked(capsys, mock_run, quiet): mock_run.return_value = subprocess.CompletedProcess(_common.INVALID_CMD, returncode=0, stdout=b'stdout', stderr=b'stderr') assert graphviz.pipe('dot', 'png', b'nongraph', quiet=quiet) == b'stdout' mock_run.assert_called_once_with([_common.EXPECTED_DOT_BINARY, '-Kdot', '-Tpng'], input=b'nongraph', capture_output=True, startupinfo=_common.StartupinfoMatcher()) assert capsys.readouterr() == ('', '' if quiet else 'stderr') def test_pipe_string_mocked(capsys, mock_run, quiet, encoding='ascii'): mock_run.return_value = subprocess.CompletedProcess(_common.INVALID_CMD, returncode=0, stdout='stdout', stderr='stderr') assert graphviz.pipe_string('dot', 'png', 'nongraph', encoding=encoding, quiet=quiet) == 'stdout' mock_run.assert_called_once_with([_common.EXPECTED_DOT_BINARY, '-Kdot', '-Tpng'], input='nongraph', encoding=encoding, capture_output=True, startupinfo=_common.StartupinfoMatcher()) assert capsys.readouterr() == ('', '' if quiet else 'stderr') def test_pipe_lines_mocked(capsys, mock_popen, quiet, input_encoding='ascii'): proc = mock_popen.return_value proc.configure_mock(args=_common.EXPECTED_DOT_BINARY, returncode=0, stdin=io.BytesIO(), stdout=io.BytesIO(b'stdout'), stderr=io.BytesIO(b'stderr')) proc.communicate.side_effect = lambda: (proc.stdout.read(), proc.stderr.read()) assert graphviz.pipe_lines('dot', 'png', iter(['nongraph\n']), input_encoding=input_encoding, quiet=quiet) == b'stdout' assert proc.stdin.getvalue() == b'nongraph\n' mock_popen.assert_called_once_with([_common.EXPECTED_DOT_BINARY, '-Kdot', '-Tpng'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=_common.StartupinfoMatcher()) assert capsys.readouterr() == ('', '' if quiet else 'stderr') def test_pipe_lines_string_mocked(capsys, mock_popen, quiet, encoding='ascii'): proc = mock_popen.return_value proc.configure_mock(args=_common.INVALID_CMD, returncode=0, stdin=io.StringIO(), stdout=io.StringIO('stdout'), stderr=io.StringIO('stderr')) proc.communicate.side_effect = lambda: (proc.stdout.read(), proc.stderr.read()) assert graphviz.pipe_lines_string('dot', 'png', iter(['nongraph\n']), encoding=encoding, quiet=quiet) == 'stdout' assert proc.stdin.getvalue() == 'nongraph\n' mock_popen.assert_called_once_with([_common.EXPECTED_DOT_BINARY, '-Kdot', '-Tpng'], encoding=encoding, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=_common.StartupinfoMatcher()) assert capsys.readouterr() == ('', '' if quiet else 'stderr') graphviz-0.20.2/tests/backend/test_rendering.py0000666000000000000000000002314414567756406020276 0ustar rootrootimport contextlib import pathlib import os import shutil import subprocess import pytest import graphviz from graphviz import _tools from graphviz.backend import rendering import _common TEST_FILES_DIRECTORY = pathlib.Path(__file__).parent.parent @pytest.fixture(scope='module') def files_path(): return TEST_FILES_DIRECTORY @pytest.mark.exe def test_render_missing_file(quiet, engine='dot', format_='pdf'): with pytest.raises(subprocess.CalledProcessError) as e: graphviz.render(engine, format_, 'nonexisting', quiet=quiet) assert e.value.returncode == 2 @pytest.mark.parametrize( 'args, expected_exception, match', [(['', 'pdf', 'nonfilepath'], ValueError, r'unknown engine'), (['dot', '', 'nonfilepath'], ValueError, r'unknown format'), (['dot', 'ps', 'nonfilepath', '', None], ValueError, r'unknown renderer'), (['dot', 'ps', 'nonfilepath', None, 'core'], graphviz.RequiredArgumentError, r'without renderer'), (['dot', 'ps', 'nonfilepath', 'ps', ''], ValueError, r'unknown formatter')], ids=lambda x: getattr(x, '__name__', x)) def test_render_unknown_parameter_raises(args, expected_exception, match, supported_number=3): checker = (pytest.deprecated_call(match=rf'{supported_number:d} positional args') if len(args) > supported_number else contextlib.nullcontext()) with pytest.raises(expected_exception, match=match), checker: graphviz.render(*args) @pytest.mark.exe @pytest.mark.parametrize( 'format_, renderer, formatter, expected_suffix', [('pdf', None, None, 'pdf'), pytest.param('plain', 'dot', 'core', 'core.dot.plain', marks=pytest.mark.xfail('graphviz.version() == (5, 0, 1)', reason='https://gitlab.com/graphviz/graphviz/-/issues/2270'))]) @pytest.mark.parametrize('engine', ['dot']) def test_render(capsys, tmp_path, engine, format_, renderer, formatter, expected_suffix, filename='hello.gv', data=b'digraph { hello -> world }'): lpath = tmp_path / filename assert lpath.write_bytes(data) == len(data) == lpath.stat().st_size rendered = lpath.with_suffix(f'{lpath.suffix}.{expected_suffix}') with pytest.deprecated_call(match=r'3 positional args'): result = graphviz.render(engine, format_, str(lpath), renderer, formatter) assert result == str(rendered) assert rendered.exists() assert rendered.stat().st_size assert capsys.readouterr() == ('', '') @pytest.mark.exe def test_render_img(capsys, tmp_path, files_path, engine='dot', format_='pdf'): subdir = tmp_path / 'subdir' subdir.mkdir() img_path = subdir / 'dot_red.png' shutil.copy(files_path / img_path.name, img_path) assert img_path.exists() assert img_path.stat().st_size gv_path = subdir / 'img.gv' rendered = gv_path.with_suffix(f'{gv_path.suffix}.{format_}') gv_rel, rendered_rel = (p.relative_to(tmp_path) for p in (gv_path, rendered)) assert all(str(s).startswith('subdir') for s in (gv_rel, rendered_rel)) gv_path.write_text(f'graph {{ red_dot [image="{img_path.name}"] }}', encoding='ascii') with _common.as_cwd(tmp_path): assert graphviz.render(engine, format_, gv_rel) == str(rendered_rel) assert rendered.exists() assert rendered.stat().st_size assert capsys.readouterr() == ('', '') @pytest.mark.exe def test_render_outfile_differnt_parent(capsys, tmp_path, engine='dot', outfile='spam.pdf', source='graph { spam }'): outfile = tmp_path / 'rendered' / outfile outfile.parent.mkdir() assert not outfile.exists() filepath = tmp_path / 'sources' / outfile.with_suffix('.gv').name filepath.parent.mkdir() assert filepath.write_text(source) == len(source) == filepath.stat().st_size result = graphviz.render(engine, filepath=filepath, outfile=outfile) assert result == os.fspath(outfile) assert outfile.exists() assert outfile.stat().st_size assert capsys.readouterr() == ('', '') @pytest.mark.parametrize( 'directory', [None, 'dot_sources']) def test_render_mocked(capsys, mock_run, quiet, directory, filepath='nonfilepath'): mock_run.return_value = subprocess.CompletedProcess(_common.INVALID_CMD, returncode=0, stdout='stdout', stderr='stderr') if directory is not None: filepath = os.path.join(directory, filepath) result = graphviz.render('dot', 'pdf', filepath, neato_no_op=True, quiet=quiet) assert result == f'{filepath}.pdf' mock_run.assert_called_once_with([_common.EXPECTED_DOT_BINARY, '-Kdot', '-Tpdf', '-n1', '-O', 'nonfilepath'], capture_output=True, cwd=_tools.promote_pathlike(directory), startupinfo=_common.StartupinfoMatcher()) assert capsys.readouterr() == ('', '' if quiet else 'stderr') @pytest.mark.parametrize( 'args, kwargs, expected_exception, match', [(['dot'], {}, graphviz.RequiredArgumentError, r'filepath: \(required'), (['dot'], {'format': 'png'}, graphviz.RequiredArgumentError, r'filepath: \(required'), (['dot'], {'filepath': 'spam'}, graphviz.RequiredArgumentError, r'format: \(required'), (['dot', 'svg'], {}, graphviz.RequiredArgumentError, r'filepath: \(required'), (['dot', 'gv', 'spam.gv'], {'outfile': 'spam.gv'}, ValueError, r"outfile 'spam\.gv' must be different from input file 'spam\.gv'"), (['dot'], {'outfile': 'spam.png', 'raise_if_result_exists': True, 'overwrite_filepath': True}, ValueError, r'overwrite_filepath cannot be combined with raise_if_result_exists'), (['dot'], {'outfile': 'spam.png', 'raise_if_result_exists': True}, graphviz.FileExistsError, r"output file exists: 'spam.png'")]) def test_render_raises_mocked(tmp_path, mock_run, args, kwargs, expected_exception, match): if issubclass(expected_exception, FileExistsError): existing_outfile = tmp_path / kwargs['outfile'] existing_outfile.touch() with _common.as_cwd(tmp_path): with pytest.raises(expected_exception, match=match): graphviz.render(*args, **kwargs) @pytest.mark.parametrize( 'filepath, kwargs, expected_fspath', [('spam.gv', {'format': 'pdf'}, 'spam.gv.pdf'), ('spam.gv', {'format': 'plain', 'renderer': 'dot'}, 'spam.gv.dot.plain')]) def test_get_outfile(filepath, kwargs, expected_fspath): result = rendering.get_outfile(filepath, **kwargs) assert os.fspath(result) == expected_fspath @pytest.mark.parametrize( 'outfile, expected_fspath', [('spam.pdf', 'spam.gv'), ('spam', 'spam.gv')]) def test_get_filepath(outfile, expected_fspath): result = rendering.get_filepath(outfile) assert os.fspath(result) == expected_fspath @pytest.mark.parametrize( 'outfile_name, format, expected_result', [('spam.gv.pdf', None, 'pdf'), ('spam.jpeg', None, 'jpeg'), ('spam.SVG', None, 'svg'), ('spam.pdf', None, 'pdf'), ('spam.pdf', 'pdf', 'pdf')]) def test_get_format(outfile_name, format, expected_result): outfile = pathlib.Path(outfile_name) result = rendering.get_format(outfile, format=format) assert result == expected_result @pytest.mark.parametrize( 'outfile_name, format, expected_result, expected_warning, match', [('spam.jpg', 'jpeg', 'jpeg', graphviz.FormatSuffixMismatchWarning, r"expected format 'jpg' from outfile differs from given format: 'jpeg'"), ('spam.dot', 'plain', 'plain', graphviz.FormatSuffixMismatchWarning, r"expected format 'dot' from outfile differs from given format: 'plain'"), ('spam', 'svg', 'svg', graphviz.UnknownSuffixWarning, r"unknown outfile suffix '' \(expected: '\.svg'\)"), ('spam.peng', 'png', 'png', graphviz.UnknownSuffixWarning, r"unknown outfile suffix '.peng' \(expected: '\.png'\)"), ('spam', 'pdf', 'pdf', graphviz.UnknownSuffixWarning, r"unknown outfile suffix '' \(expected: '\.pdf'\)")]) def test_get_format_warns(outfile_name, format, expected_result, expected_warning, match): outfile = pathlib.Path(outfile_name) with pytest.warns(expected_warning, match=match): result = rendering.get_format(outfile, format=format) assert result == expected_result @pytest.mark.parametrize( 'outfile_name, expected_exception, match', [('spam', graphviz.RequiredArgumentError, r"cannot infer rendering format from suffix '' of outfile: 'spam'"), ('spam.peng', graphviz.RequiredArgumentError, r"cannot infer rendering format from suffix '.peng' of outfile: 'spam.peng'"), ('spam.wav', graphviz.RequiredArgumentError, r"cannot infer rendering format from suffix '.wav' of outfile: 'spam.wav'")]) def test_get_format_raises(outfile_name, expected_exception, match): outfile = pathlib.Path(outfile_name) with pytest.raises(expected_exception, match=match): rendering.get_format(outfile, format=None) graphviz-0.20.2/tests/backend/test_unflattening.py0000666000000000000000000000330614224214124020765 0ustar rootrootimport re import subprocess import pytest import graphviz import _common def test_unflatten_stagger_missing(): with pytest.raises(graphviz.RequiredArgumentError, match=r'without stagger'): graphviz.unflatten('graph {}', fanout=True) @pytest.mark.exe @pytest.mark.parametrize( 'source, kwargs, expected', [('digraph {1 -> 2; 1 -> 3; 1 -> 4}', {'stagger': 3, 'fanout': True, 'chain': 42}, 'digraph { 1 -> 2 [minlen=1]; 1 -> 3 [minlen=2]; 1 -> 4 [minlen=3]; }')]) def test_unflatten(source, kwargs, expected): result = graphviz.unflatten(source, **kwargs) normalized = re.sub(r'\s+', ' ', result.strip()) assert normalized == expected def test_unflatten_mocked(capsys, sentinel, mock_run, stagger=10, fanout=True, chain=23): mock_run.return_value = subprocess.CompletedProcess(_common.INVALID_CMD, returncode=0, stdout=sentinel.stdout, stderr='') result = graphviz.unflatten('nonsource', stagger=stagger, fanout=fanout, chain=chain) assert result is sentinel.stdout mock_run.assert_called_once_with([_common.EXPECTED_UNFLATTEN_BINARY, '-l', '10', '-f', '-c', '23'], input='nonsource', capture_output=True, startupinfo=_common.StartupinfoMatcher(), encoding='utf-8') assert capsys.readouterr() == ('', '') graphviz-0.20.2/tests/backend/test_upstream_version.py0000666000000000000000000000440414144304656021707 0ustar rootrootimport subprocess import pytest import graphviz import _common @pytest.mark.exe(xfail=True, raises=graphviz.ExecutableNotFound) def test_version(capsys): result = graphviz.version() assert isinstance(result, tuple) and result assert all(isinstance(d, int) for d in result) assert capsys.readouterr() == ('', '') @pytest.mark.parametrize( 'stdout, expected', [('dot - graphviz version 1.2.3 (mocked)', (1, 2, 3)), ('dot - graphviz version 2.43.20190912.0211 (20190912.0211)\n', (2, 43, 20190912, 211)), ('dot - graphviz version 2.44.2~dev.20200927.0217 (20200927.0217)\n', (2, 44, 2)), ('dot - graphviz version 2.44.1 (mocked)\n', (2, 44, 1)), ('dot - graphviz version 2.44.2~dev.20200704.1652 (mocked)\n', (2, 44, 2))]) def test_version_mocked(mock_run, stdout, expected): mock_run.return_value = subprocess.CompletedProcess(_common.INVALID_CMD, returncode=0, stdout=stdout, stderr=None) assert graphviz.version() == expected mock_run.assert_called_once_with([_common.EXPECTED_DOT_BINARY, '-V'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=_common.StartupinfoMatcher(), encoding='ascii') def test_version_parsefail_mocked(mock_run): mock_run.return_value = subprocess.CompletedProcess(_common.INVALID_CMD, returncode=0, stdout='nonversioninfo', stderr=None) with pytest.raises(RuntimeError, match=r'nonversioninfo'): graphviz.version() mock_run.assert_called_once_with([_common.EXPECTED_DOT_BINARY, '-V'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=_common.StartupinfoMatcher(), encoding='ascii') graphviz-0.20.2/tests/backend/test_viewing.py0000666000000000000000000000156514144304656017757 0ustar rootrootimport subprocess import pytest import graphviz @pytest.mark.usefixtures('unknown_platform') def test_view_unknown_platform(): with pytest.raises(RuntimeError, match=r'platform'): graphviz.view('nonfilepath') def test_view_mocked(mocker, mock_platform, mock_popen, mock_startfile, quiet): assert graphviz.view('nonfilepath', quiet=quiet) is None if mock_platform == 'windows': mock_startfile.assert_called_once_with('nonfilepath') return if quiet: kwargs = {'stderr': subprocess.DEVNULL} else: kwargs = {} if mock_platform == 'darwin': mock_popen.assert_called_once_with(['open', 'nonfilepath'], **kwargs) elif mock_platform in ('linux', 'freebsd'): mock_popen.assert_called_once_with(['xdg-open', 'nonfilepath'], **kwargs) else: raise RuntimeError graphviz-0.20.2/tests/conftest.py0000666000000000000000000000527314155453170015504 0ustar rootroot"""pytest markers and fixtures.""" import pytest SKIP_EXE = '--skip-exe' ONLY_EXE = '--only-exe' def pytest_configure(config): config.addinivalue_line('markers', f'exe(xfail): skip/xfail if {SKIP_EXE} is given') def pytest_collection_modifyitems(config, items): if config.getoption(ONLY_EXE, None) or config.getoption(SKIP_EXE, None): only = config.getoption(ONLY_EXE, None) for item in items: exe_marker = _make_exe_marker(item, only=only) if exe_marker is not None: item.add_marker(exe_marker) def _make_exe_marker(item, only: bool = False): def make_kwargs(**kwargs): return kwargs exe_values = [make_kwargs(*m.args, **m.kwargs) for m in item.iter_markers(name='exe')] if only: if exe_values: return None return pytest.mark.skip(reason=f'skipped by {ONLY_EXE} flag') elif exe_values: assert len(exe_values) == 1 kwargs, = exe_values if kwargs.pop('xfail', None): kwargs.setdefault('reason', f'xfail by {SKIP_EXE} flag') return pytest.mark.xfail(**kwargs) else: kwargs.setdefault('reason', f'skipped by {SKIP_EXE} flag') return pytest.mark.skip(**kwargs) return None @pytest.fixture(scope='session') def platform(): import platform return platform.system().lower() @pytest.fixture(params=[False, True], ids=lambda q: f'quiet={q!r}') def quiet(request): return request.param @pytest.fixture def sentinel(mocker): return mocker.sentinel @pytest.fixture def mock_render(mocker): yield mocker.patch('graphviz.backend.rendering.render', autospec=True) @pytest.fixture def mock_pipe(mocker): yield mocker.patch('graphviz.backend.piping.pipe', autospec=True) @pytest.fixture def mock_pipe_lines(mocker): yield mocker.patch('graphviz.backend.piping.pipe_lines', autospec=True) @pytest.fixture def mock_pipe_lines_string(mocker): yield mocker.patch('graphviz.backend.piping.pipe_lines_string', autospec=True) @pytest.fixture def mock_unflatten(mocker): yield mocker.patch('graphviz.backend.unflattening.unflatten', autospec=True) @pytest.fixture(params=['darwin', 'freebsd', 'linux', 'windows'], ids=lambda p: f'platform={p!r}') def mock_platform(monkeypatch, request): monkeypatch.setattr('graphviz.backend.viewing.PLATFORM', request.param) yield request.param @pytest.fixture def unknown_platform(monkeypatch, name='nonplatform'): monkeypatch.setattr('graphviz.backend.viewing.PLATFORM', name) yield name graphviz-0.20.2/tests/dot_red.png0000666000000000000000000000263514073203716015430 0ustar rootrootPNG  IHDR@@gAMA a cHRMz&u0`:pQ<PLTENN$$ ((;;>>****((**8877&&$$KKDDFF;;99 33@@ 99&&##7799))++..))CC;;UURR&& %%UURR,,))((66++##IICC @@==&&99 --++,,<<AA(( NNtRNS[Z+ʀ)UUYV).li$#Ĥkm*(XTXQ*'~X˨Zǁ}VPR&lff((SR&~'Ẏ[5IbKGD)Ԏ6tIME %zr IDATXý;[Q*R1ĈRU{VFQJVZ)ZTS=\7PqJ,Ch<ժ^nl#/=y zC@4xXH)GEL1xl dDJR0 I3 Ӂ:/? /)Y7W9}.Ow 8vWpH,aPr%(E`Ͽ62 *K+#o@F"h @!xA  Z/vCyE ^`qu+wk|O+HX'KU1 ^ qӆZax\GF/삯~gǾ+c;Y &Z!v.3BU՘FE world }\n') return cls() @pytest.fixture def invalid_dot(cls): if cls.__name__ == 'Source': return cls('graph { spam -- \\ }') else: invalid_dot = cls() with pytest.warns(graphviz.DotSyntaxWarning, match=r'syntax error'): invalid_dot.edge('spam', '\\') return invalid_dot def test_copy(cls, dot): assert type(dot) is cls assert dot.copy() is not dot assert dot.copy() is not dot.copy() assert type(dot.copy()) is type(dot) assert dot.copy().__dict__ == dot.__dict__ == dot.copy().__dict__ def test_str(dot): assert str(dot) == dot.source @pytest.mark.parametrize( 'parameter, expected_exception, match', [('engine', ValueError, r'unknown engine'), ('format', ValueError, r'unknown format'), ('renderer', ValueError, r'unknown renderer'), ('formatter', ValueError, r'unknown formatter'), ('encoding', LookupError, r'encoding')]) def test_invalid_parameter_raises_valuerror(dot, parameter, expected_exception, match): with pytest.raises(expected_exception, match=match): setattr(dot, parameter, 'invalid_parameter') def test_encoding_none(dot): dot_copy = dot.copy() dot_copy.encoding = None assert dot_copy.encoding == locale.getpreferredencoding() @pytest.mark.exe @pytest.mark.parametrize( 'kwargs', [{'engine': 'spam'}]) def test_render_raises_before_save(tmp_path, cls, kwargs, filename='dot.gv'): args = ['graph { spam }'] if cls.__name__ == 'Source' else [] dot = cls(*args, filename=filename, directory=tmp_path) expected_source = tmp_path / filename assert not expected_source.exists() with pytest.raises(ValueError, match=r''): dot.render(**kwargs) assert not expected_source.exists() pdf = dot.render(engine='dot') assert pdf == f'{expected_source}.pdf' assert expected_source.exists() assert expected_source.stat().st_size @pytest.mark.parametrize( 'kwargs', [{'engine': 'spam'}, {'format': 'spam'}, {'renderer': 'spam'}, {'formatter': 'spam'}]) def test_render_raises_before_save_mocked(tmp_path, mock_render, cls, kwargs, filename='dot.gv'): args = [''] if cls.__name__ == 'Source' else [] dot = cls(*args, filename=filename, directory=tmp_path) expected_source = tmp_path / filename assert not expected_source.exists() first_arg = next(iter(kwargs)) with pytest.raises(ValueError, match=f'unknown {first_arg}'): dot.render(**kwargs) assert not expected_source.exists() def test_render_mocked(mocker, mock_render, dot): mock_save = mocker.patch.object(dot, 'save', autospec=True) mock_view = mocker.patch.object(dot, '_view', autospec=True) mock_remove = mocker.patch('os.remove', autospec=True) assert dot.render(cleanup=True, view=True) is mock_render.return_value mock_save.assert_called_once_with(None, None, skip_existing=None) mock_render.assert_called_once_with(dot.engine, dot.format, mock_save.return_value, renderer=None, formatter=None, neato_no_op=None, outfile=None, raise_if_result_exists=False, overwrite_filepath=False, quiet=False) mock_remove.assert_called_once_with(mock_save.return_value) mock_view.assert_called_once_with(mock_render.return_value, format=dot.format, quiet=False) def test_render_outfile_mocked(mocker, mock_render, dot): mock_save = mocker.patch.object(dot, 'save', autospec=True) mock_view = mocker.patch.object(dot, '_view', autospec=True) mock_remove = mocker.patch('os.remove', autospec=True) outfile = 'spam.pdf' assert dot.render(outfile=outfile, raise_if_result_exists=True, overwrite_source=True, cleanup=True, view=True) is mock_render.return_value expected_filename = pathlib.Path('spam.gv') mock_save.assert_called_once_with(expected_filename, None, skip_existing=None) mock_render.assert_called_once_with(dot.engine, dot.format, mock_save.return_value, renderer=None, formatter=None, neato_no_op=None, outfile=pathlib.Path(outfile), raise_if_result_exists=True, overwrite_filepath=True, quiet=False) mock_remove.assert_called_once_with(mock_save.return_value) mock_view.assert_called_once_with(mock_render.return_value, format=dot.format, quiet=False) def test_format_renderer_formatter_mocked(mocker, mock_render, quiet, cls, filename='format.gv', format='jpg', renderer='cairo', formatter='core'): dot = cls(*[''] if cls.__name__ == 'Source' else [], filename=filename, format=format, renderer=renderer, formatter=formatter) assert dot.format == format assert dot.renderer == renderer assert dot.formatter == formatter mock_save = mocker.patch.object(dot, 'save', autospec=True) assert dot.render(quiet=quiet) is mock_render.return_value mock_save.assert_called_once_with(None, None, skip_existing=None) mock_render.assert_called_once_with('dot', format, mock_save.return_value, renderer=renderer, formatter=formatter, neato_no_op=None, outfile=None, raise_if_result_exists=False, overwrite_filepath=False, quiet=quiet) @pytest.mark.parametrize( 'neato_no_op', [None, False, True, 0, 1, 2]) def test_neato_no_op_mocked(mocker, mock_render, quiet, cls, neato_no_op, engine='neato', filename='neato_no_op.gv', format='svg'): dot = cls(*[''] if cls.__name__ == 'Source' else [], engine=engine, filename=filename, format=format) mock_save = mocker.patch.object(dot, 'save', autospec=True) assert dot.render(neato_no_op=neato_no_op, quiet=quiet) is mock_render.return_value mock_save.assert_called_once_with(None, None, skip_existing=None) mock_render.assert_called_once_with(engine, format, mock_save.return_value, renderer=None, formatter=None, neato_no_op=neato_no_op, outfile=None, raise_if_result_exists=False, overwrite_filepath=False, quiet=quiet) def test_save_mocked(mocker, dot, filename='nonfilename', directory='nondirectory'): mock_makedirs = mocker.patch('os.makedirs', autospec=True) mock_open = mocker.patch('builtins.open', mocker.mock_open()) with pytest.deprecated_call(match=r'1 positional arg\b'): assert dot.save(filename, directory) == dot.filepath assert dot.filename == filename assert dot.directory == directory mock_makedirs.assert_called_once_with(dot.directory, 0o777, exist_ok=True) mock_open.assert_called_once_with(dot.filepath, 'w', encoding=dot.encoding) expected_calls = ([mocker.call(dot.source)] if type(dot).__name__ == 'Source' else [mocker.call(mocker.ANY), mocker.call('}\n')]) assert mock_open.return_value.write.call_args_list == expected_calls @pytest.mark.exe def test_pipe(dot, encoding='utf-8'): svg = dot.pipe(format='svg', encoding=encoding) assert svg.startswith(' world; }' else: assert normalized.startswith('digraph {' if dot.directed else 'graph {') def test_unflatten_mocked(sentinel, mock_unflatten, dot): kwargs = {'stagger': sentinel.stagger, 'fanout': sentinel.fanout, 'chain': sentinel.chain} result = dot.unflatten(**kwargs) assert result is not None assert isinstance(result, graphviz.Source) assert type(result) is graphviz.Source assert result.source is mock_unflatten.return_value assert result.filename == dot.filename assert result.directory == dot.directory assert result.engine == dot.engine assert result.format == dot.format assert result.renderer == dot.renderer assert result.formatter == dot.formatter assert result.encoding == dot.encoding assert result._loaded_from_path is None mock_unflatten.assert_called_once_with(dot.source, encoding=dot.encoding, **kwargs) def test_view_mocked(mocker, dot): mock_render = mocker.patch.object(dot, 'render', autospec=True) kwargs = {'filename': 'filename', 'directory': 'directory', 'cleanup': True, 'quiet': True, 'quiet_view': True} assert dot.view(**kwargs) is mock_render.return_value mock_render.assert_called_once_with(view=True, **kwargs) def test__view_unknown_platform(unknown_platform, dot): with pytest.raises(RuntimeError, match=r'support'): dot._view('name', format='png', quiet=False) def test__view_mocked(mocker, sentinel, mock_platform, dot): _view_platform = mocker.patch.object(dot, f'_view_{mock_platform}', autospec=True) kwargs = {'quiet': False} assert dot._view(sentinel.name, format='png', **kwargs) is None _view_platform.assert_called_once_with(sentinel.name, **kwargs) graphviz-0.20.2/tests/test_graphs.py0000666000000000000000000001701314315456776016212 0ustar rootrootimport itertools import pytest import graphviz BASE_GRAPHS = [graphviz.Graph, graphviz.Digraph] @pytest.fixture(params=BASE_GRAPHS) def cls(request): return request.param @pytest.fixture(params=list(itertools.permutations(BASE_GRAPHS, 2)), ids=lambda c: f'{c[0].__name__}, {c[1].__name__}') def classes(request): return request.param def test_init_filename(cls): assert cls().filename == f'{cls.__name__}.gv' assert type('Subcls', (cls,), {})().filename == 'Subcls.gv' assert cls('spam').filename == 'spam.gv' @pytest.mark.parametrize( 'cls, body_lines, expected', [(graphviz.Graph, ['\tspam -- {\n', '\t\teggs, ham\n', '\t}\n'], 'graph {\n\tspam -- {\n\t\teggs, ham\n\t}\n}\n'), (graphviz.Digraph, ['\tspam -> {\n', '\t\teggs, ham\n', '\t}\n'], 'digraph {\n\tspam -> {\n\t\teggs, ham\n\t}\n}\n')], ids=lambda p: getattr(p, '__name__', '...')) def test_init_body(cls, body_lines, expected): dot = cls(body=iter(body_lines)) assert dot.source == expected @pytest.mark.exe @pytest.mark.parametrize( 'cls, expected', [(graphviz.Graph, 'graph {\n\tC\n}\n'), (graphviz.Digraph, 'digraph {\n\tC\n}\n')], ids=lambda p: getattr(p, '__name__', '...')) def test_subgraph_render(capsys, tmp_path, cls, expected): lpath = tmp_path / 's1.gv' rendered = lpath.with_suffix('.gv.pdf') dot = cls() dot.edge('A', 'B') with dot.subgraph() as s1: s1.node('C') result = s1.render(str(lpath)) assert result == str(rendered) assert lpath.read_text(encoding='ascii') == expected assert rendered.exists() assert rendered.stat().st_size assert capsys.readouterr() == ('', '') @pytest.mark.parametrize( 'keep_attrs', [False, True]) def test_clear(cls, keep_attrs): kwargs = {f'{a}_attr': {a: a} for a in ('graph', 'node', 'edge')} c = cls(**kwargs) assert all(getattr(c, k) == v for k, v in kwargs.items()) c.node('spam') assert len(c.body) == 1 body = c.body c.clear(keep_attrs=keep_attrs) assert c.body == [] assert c.body is body if keep_attrs: assert all(getattr(c, k) == v for k, v in kwargs.items()) else: assert all(getattr(c, k) == {} for k in kwargs) def test_iter_subgraph_strict(cls): with pytest.raises(ValueError, match=r'strict'): cls().subgraph(cls(strict=True)) @pytest.mark.parametrize( 'cls, expected', [(graphviz.Graph, 'strict graph {\n}\n'), (graphviz.Digraph, 'strict digraph {\n}\n')], ids=lambda p: getattr(p, '__name__', '...')) def test_iter_strict(cls, expected): assert cls(strict=True).source == expected def test_attr_invalid_kw(cls): with pytest.raises(ValueError, match=r'attr'): cls().attr('spam') @pytest.mark.parametrize( 'cls, expected', [(graphviz.Graph, 'graph {\n\tspam=eggs\n}\n'), (graphviz.Digraph, 'digraph {\n\tspam=eggs\n}\n')], ids=lambda p: getattr(p, '__name__', '...')) def test_attr_kw_none(cls, expected): dot = cls() dot.attr(spam='eggs') assert dot.source == expected @pytest.mark.parametrize( 'cls, expected', [(graphviz.Graph, 'graph {\n\tA [label="%s"]\n\tB [label="%s"]\n}\n' % (r'\\', r'\"\\\"')), (graphviz.Digraph, 'digraph {\n\tA [label="%s"]\n\tB [label="%s"]\n}\n' % (r'\\', r'\"\\\"'))], ids=lambda p: getattr(p, '__name__', '...')) def test_escaped_quotes_and_escapes(cls, expected): dot = cls() dot.node('A', label='\\\\') dot.node('B', label=r'"\\"') assert dot.source == expected @pytest.mark.parametrize( 'cls, expected', [(graphviz.Graph, 'graph {\n\t// comment\n\tsubgraph name {\n\t}\n}\n'), (graphviz.Digraph, 'digraph {\n\t// comment\n\tsubgraph name {\n\t}\n}\n')], ids=lambda p: getattr(p, '__name__', '...')) def test_subgraph_graph_none(cls, expected): dot = cls(directory='nondirectory', format='png', encoding='ascii', engine='neato') assert dot.strict is False with dot.subgraph(name='name', comment='comment') as child: assert child.directory == dot.directory assert child.format == dot.format assert child.engine == dot.engine assert child.encoding == dot.encoding assert child.strict is None assert dot.source == expected def test_subgraph_graph_notsole(cls): with pytest.raises(ValueError, match=r'sole'): cls().subgraph(cls(), name='spam') def test_subgraph_mixed(classes): cls1, cls2 = classes with pytest.raises(ValueError, match=r'kind'): cls1().subgraph(cls2()) @pytest.mark.parametrize( 'cls, expected', [(graphviz.Graph, 'graph {\n\t{\n\t}\n}\n'), (graphviz.Digraph, 'digraph {\n\t{\n\t}\n}\n')], ids=lambda p: getattr(p, '__name__', '...')) def test_subgraph_reflexive(cls, expected): # guard against potential infinite loop dot = cls() dot.subgraph(dot) assert dot.source == expected def test_subgraph(): s1 = graphviz.Graph() s1.node('A') s1.node('B') s1.node('C') s1.edge('A', 'B', constraint='false') s1.edges(['AC', 'BC']) s2 = graphviz.Graph() s2.node('D') s2.node('E') s2.node('F') s2.edge('D', 'E', constraint='false') s2.edges(['DF', 'EF']) dot = graphviz.Graph() dot.subgraph(s1) dot.subgraph(s2) dot.attr('edge', style='dashed') dot.edges(['AD', 'BE', 'CF']) assert dot.source == '''graph { \t{ \t\tA \t\tB \t\tC \t\tA -- B [constraint=false] \t\tA -- C \t\tB -- C \t} \t{ \t\tD \t\tE \t\tF \t\tD -- E [constraint=false] \t\tD -- F \t\tE -- F \t} \tedge [style=dashed] \tA -- D \tB -- E \tC -- F } ''' def test_label_html(): """https://www.graphviz.org/doc/info/shapes.html#html""" dot = graphviz.Digraph('structs', node_attr={'shape': 'plaintext'}) dot.node('struct1', '''<
left middle right
>''') dot.node('struct2', '''<
one two
>''') dot.node('struct3', '''<
hello
world
b g h
c d e
f
>''') dot.edge('struct1:f1', 'struct2:f0') dot.edge('struct1:f2', 'struct3:here') assert dot.source == '''digraph structs { \tnode [shape=plaintext] \tstruct1 [label=<
left middle right
>] \tstruct2 [label=<
one two
>] \tstruct3 [label=<
hello
world
b g h
c d e
f
>] \tstruct1:f1 -> struct2:f0 \tstruct1:f2 -> struct3:here } ''' graphviz-0.20.2/tests/test_init.py0000666000000000000000000000776014151056254015662 0ustar rootrootimport pytest import graphviz DEFAULT_ENGINE = 'dot' DEFAULT_FORMAT = 'pdf' DEFAULT_JUPYTER_FORMAT = 'svg' DEFAULT_JUPYTER_MIMETYPE = 'image/svg+xml' def test_set_default_engine_invalid(): with pytest.raises(ValueError, match=r'unknown engine'): graphviz.set_default_engine('nonengine') def test_set_default_format_invalid(): with pytest.raises(ValueError, match=r'unknown format'): graphviz.set_default_format('nonformat') def test_set_default_engine(monkeypatch, *, engine='neato', explicit_engine='sfdp'): assert len({DEFAULT_ENGINE, engine, explicit_engine}) == 3 from graphviz.parameters import Parameters assert Parameters._engine == DEFAULT_ENGINE # isolate the test monkeypatch.setattr('graphviz.parameters.Parameters._engine', DEFAULT_ENGINE) assert Parameters._engine == DEFAULT_ENGINE g1 = graphviz.Graph() assert g1.engine == DEFAULT_ENGINE g2 = graphviz.Graph(engine=explicit_engine) assert g2.engine == explicit_engine old = graphviz.set_default_engine(engine) assert old == DEFAULT_ENGINE assert g1.engine == engine assert g2.engine == explicit_engine g3 = graphviz.Graph() assert g3.engine == engine g4 = graphviz.Graph(engine=explicit_engine) assert g4.engine == explicit_engine old = graphviz.set_default_engine(DEFAULT_ENGINE) assert old == engine assert g1.engine == DEFAULT_ENGINE assert g2.engine == explicit_engine assert g3.engine == DEFAULT_ENGINE assert g4.engine == explicit_engine def test_set_default_format(monkeypatch, *, format='png', explicit_format='jpeg'): assert len({DEFAULT_FORMAT, format, explicit_format}) == 3 from graphviz.parameters import Parameters assert Parameters._format == DEFAULT_FORMAT # isolate the test monkeypatch.setattr('graphviz.parameters.Parameters._format', DEFAULT_FORMAT) assert Parameters._format == DEFAULT_FORMAT g1 = graphviz.Graph() assert g1.format == DEFAULT_FORMAT g2 = graphviz.Graph(format=explicit_format) assert g2.format == explicit_format old = graphviz.set_default_format(format) assert old == DEFAULT_FORMAT assert g1.format == format assert g2.format == explicit_format g3 = graphviz.Graph() assert g3.format == format g4 = graphviz.Graph(format=explicit_format) assert g4.format == explicit_format old = graphviz.set_default_format(DEFAULT_FORMAT) assert old == format assert g1.format == DEFAULT_FORMAT assert g2.format == explicit_format assert g3.format == DEFAULT_FORMAT assert g4.format == explicit_format def test_set_jupyter_format(monkeypatch, *, jupyter_format='jpg', expected_old_format='svg', expected_normalized_format='jpeg', expected_mimetype='image/jpeg'): assert len({DEFAULT_JUPYTER_MIMETYPE, jupyter_format}) == 2 from graphviz import jupyter_integration assert (jupyter_integration.JupyterIntegration._jupyter_mimetype == DEFAULT_JUPYTER_MIMETYPE) # isolate the test monkeypatch.setattr('graphviz.jupyter_integration.JupyterIntegration._jupyter_mimetype', DEFAULT_JUPYTER_MIMETYPE) assert (jupyter_integration.JupyterIntegration._jupyter_mimetype == DEFAULT_JUPYTER_MIMETYPE) g1 = graphviz.Graph() assert g1._jupyter_mimetype == DEFAULT_JUPYTER_MIMETYPE old = graphviz.set_jupyter_format(jupyter_format) assert old == jupyter_integration.DEFAULT_JUPYTER_FORMAT assert g1._jupyter_mimetype == expected_mimetype g2 = graphviz.Graph() assert g2._jupyter_mimetype == expected_mimetype old = graphviz.set_jupyter_format(DEFAULT_JUPYTER_FORMAT) assert old == expected_normalized_format assert g1._jupyter_mimetype == DEFAULT_JUPYTER_MIMETYPE assert g2._jupyter_mimetype == DEFAULT_JUPYTER_MIMETYPE graphviz-0.20.2/tests/test_jupyter_integration.py0000666000000000000000000000417314432726140021017 0ustar rootrootimport pytest import graphviz from graphviz import jupyter_integration EXPECTED_SVG_ENCODING = 'utf-8' def test_get_jupyter_format_mimetype_invalid_raises_unknown(): with pytest.raises(ValueError, match=r'unknown'): jupyter_integration.get_jupyter_format_mimetype('Brian!') def test_get_jupyter_mimetype_format_normalizes(): assert jupyter_integration.get_jupyter_mimetype_format( jupyter_integration.get_jupyter_format_mimetype('jpg')) == 'jpeg' def test_get_jupyter_mimetype_format_raises_unsupported(): with pytest.raises(ValueError, match=r"unsupported .*\(must be one of .+'image/svg\+xml'"): jupyter_integration.get_jupyter_mimetype_format('A boy called Brian!') @pytest.mark.exe def test_repr_image_svg_xml_encoding(input_encoding='latin1'): assert input_encoding != EXPECTED_SVG_ENCODING dot = graphviz.Graph(comment='Mønti Pythøn ik den Hølie Grailen', encoding=input_encoding) result = dot._repr_image_svg_xml() assert result.startswith('') @pytest.mark.exe @pytest.mark.parametrize('input_encoding', ['utf-8', 'ascii', 'latin1']) def test_repr_image_svg_xml_encoding_mocked(mocker, mock_pipe_lines_string, mock_pipe_lines, input_encoding): dot = graphviz.Graph(encoding=input_encoding) result = dot._repr_image_svg_xml() if input_encoding == 'utf-8': assert result is mock_pipe_lines_string.return_value mock_pipe_lines_string.assert_called_once() mock_pipe_lines.assert_not_called() assert (mock_pipe_lines_string.call_args.kwargs['encoding'] == EXPECTED_SVG_ENCODING) else: assert result is mock_pipe_lines.return_value.decode.return_value mock_pipe_lines.assert_called_once() mock_pipe_lines_string.assert_not_called() assert 'encoding' not in mock_pipe_lines.call_args.kwargs (mock_pipe_lines.return_value.decode .assert_called_once_with(EXPECTED_SVG_ENCODING)) graphviz-0.20.2/tests/test_parameters.py0000666000000000000000000000336014151056254017052 0ustar rootrootimport pytest import graphviz from graphviz import parameters VERIFY_FUNCS = [parameters.verify_engine, parameters.verify_format, parameters.verify_renderer, parameters.verify_formatter] @pytest.mark.parametrize( 'cls', [graphviz.Graph, graphviz.Digraph, graphviz.Source]) def test_parameters(cls, engine='patchwork', format='tiff', renderer='map', formatter='core'): args = [''] if cls is graphviz.Source else [] dot = cls(*args, engine=engine, format=format, renderer=renderer, formatter=formatter) assert isinstance(dot, cls) assert type(dot) is cls assert dot.engine == engine assert dot.format == format assert dot.renderer == renderer assert dot.formatter == formatter dot_copy = dot.copy() assert dot_copy is not dot assert isinstance(dot_copy, cls) assert type(dot_copy) is cls assert dot.engine == engine assert dot.format == format assert dot_copy.renderer == renderer assert dot_copy.formatter == formatter @pytest.mark.parametrize( 'verify_func', VERIFY_FUNCS) def test_verify_parameter_raises_unknown(verify_func): with pytest.raises(ValueError, match=r'unknown .*\(must be .*one of'): verify_func('Brian!') @pytest.mark.parametrize( 'verify_func', VERIFY_FUNCS) def test_verify_parameter_none_required_false_passes(verify_func): assert verify_func(None, required=False) is None @pytest.mark.parametrize( 'verify_func', VERIFY_FUNCS) def test_verify_parameter_none_required_raises_missing(verify_func): with pytest.raises(ValueError, match=r'missing'): verify_func(None, required=True) graphviz-0.20.2/tests/test_quoting.py0000666000000000000000000000357414550602024016377 0ustar rootrootimport sys import warnings import pytest import graphviz from graphviz import quoting @pytest.mark.parametrize( 'char', ['G', 'E', 'T', 'H', 'L', 'l']) def test_deprecated_escape(recwarn, char): warnings.simplefilter('always') escape = eval(rf'"\{char}"') assert len(recwarn) == 1 w = recwarn.pop(DeprecationWarning if sys.version_info < (3, 12) else SyntaxWarning) assert str(w.message).startswith('invalid escape sequence') assert escape == f'\\{char}' assert quoting.quote(escape) == f'"\\{char}"' @pytest.mark.parametrize( 'identifier, expected', [('"spam"', r'"\"spam\""'), ('node', '"node"'), ('EDGE', '"EDGE"'), ('Graph', '"Graph"'), ('\\G \\N \\E \\T \\H \\L', '"\\G \\N \\E \\T \\H \\L"'), ('\\n \\l \\r', '"\\n \\l \\r"'), ('\r\n', '"\r\n"'), ('\\\\n', r'"\\n"'), ('\u0665.\u0660', '"\u0665.\u0660"'), ('\\"spam', r'"\"spam"'), ('\\\\"spam', r'"\\\"spam"'), ('\\\\\\"spam', r'"\\\"spam"'), ('\\\\\\\\"spam', r'"\\\\\"spam"')]) def test_quote(identifier, expected): assert quoting.quote(identifier) == expected @pytest.mark.parametrize( 'attributes, expected', [([('spam', 'eggs')], ' [spam=eggs]'), ({'spam': 'eggs'}, ' [spam=eggs]')]) def test_attr_list(attributes, expected): assert quoting.attr_list(attributes=attributes) == expected @pytest.mark.parametrize( 'string, expected, expected_quoted', [('spam', 'spam', 'spam'), ('<>-*-<>', '<>-*-<>', '"<>-*-<>"')]) def test_nohtml(string, expected, expected_quoted): result = graphviz.nohtml(string) assert isinstance(result, str) assert isinstance(result, quoting.NoHtml) assert result == expected quoted = quoting.quote(result) assert isinstance(quoted, str) assert quoted == expected_quoted graphviz-0.20.2/tests/test_saving.py0000666000000000000000000000071714141625140016174 0ustar rootrootimport graphviz def test_saves_source_from_file(tmp_path, src='graph spam { spam }'): path = tmp_path / 'spam.gv' path.write_text(src) stat_before = path.stat() source = graphviz.Source.from_file(path) source.save() assert path.stat().st_mtime == stat_before.st_mtime, 'file not overwritten' assert path.read_text() == src, 'file contents unchanged' assert ''.join(source) == f'{src}\n', 'src with final newline' graphviz-0.20.2/tests/test_sources.py0000666000000000000000000000520414567756406016412 0ustar rootrootimport locale import pytest import graphviz SOURCE = {'source': 'digraph { hello -> world }\n', 'filename': 'hello.gv', 'directory': 'test-output', 'format': 'PNG', 'engine': 'NEATO', 'encoding': 'utf-8'} @pytest.fixture(scope='module') def source(): return graphviz.Source(**SOURCE) @pytest.mark.parametrize( 'parameter', ['engine', 'format', 'encoding']) def test_source_parameter(source, parameter): if parameter != 'encoding': assert not SOURCE[parameter].islower() assert getattr(source, parameter) == SOURCE[parameter].lower() def test_init(source): assert source.source == SOURCE['source'] assert source.filename == SOURCE['filename'] assert source.directory == SOURCE['directory'] def test_init_filename(): assert graphviz.Source('').filename == 'Source.gv' assert type('Subcls', (graphviz.Source,), {'name': 'name'})('').filename == 'Subcls.gv' def test_filepath(platform, source): if platform == 'windows': assert source.filepath == 'test-output\\hello.gv' else: assert source.filepath == 'test-output/hello.gv' def test_from_file(tmp_path, filename='hello.gv', directory='source_hello', data='digraph { hello -> world }', encoding='utf-8', deprecation_match=r'1 positional arg\b'): lpath = tmp_path / directory lpath.mkdir() (lpath / filename).write_text(data, encoding=encoding) with pytest.deprecated_call(match=deprecation_match): source = graphviz.Source.from_file(filename, str(lpath)) assert source.encoding == 'utf-8' with pytest.deprecated_call(match=deprecation_match): source = graphviz.Source.from_file(filename, str(lpath), encoding=None) assert source.encoding == locale.getpreferredencoding() renderer = 'xdot' formatter = 'core' with pytest.deprecated_call(match=deprecation_match): source = graphviz.Source.from_file(filename, str(lpath), encoding=encoding, renderer=renderer, formatter=formatter) assert source.source == data + '\n' assert source.filename == filename assert source.directory == str(lpath) assert source.encoding == encoding assert source.renderer == renderer assert source.formatter == formatter def test_source_iter(source): source_without_newline = graphviz.Source(source.source + source.source.rstrip()) lines = list(source_without_newline) assert lines == list(source) * 2 graphviz-0.20.2/tests/test_tools.py0000666000000000000000000000371214224214124016041 0ustar rootrootimport functools import os import warnings import pytest from graphviz import _tools import _common def itertree(root): for path, dirs, files in os.walk(root): base = os.path.relpath(path, root) rel_path = functools.partial(os.path.join, base if base != '.' else '') for is_file, names in enumerate((dirs, files)): for n in names: yield bool(is_file), rel_path(n).replace('\\', '/') def test_mkdirs_invalid(tmp_path): with _common.as_cwd(tmp_path): (tmp_path / 'spam.eggs').write_bytes(b'') with pytest.raises(OSError): _tools.mkdirs('spam.eggs/spam') def test_mkdirs(tmp_path): with _common.as_cwd(tmp_path): _tools.mkdirs('spam.eggs') assert list(itertree(str(tmp_path))) == [] for _ in range(2): _tools.mkdirs('spam/eggs/spam.eggs') assert list(itertree(str(tmp_path))) == [(False, 'spam'), (False, 'spam/eggs')] @pytest.mark.parametrize('category, match', [(FutureWarning, r" third='third' "), (DeprecationWarning, r" third='third' "), (PendingDeprecationWarning, r" third='third' "), (None, None)]) def test_deprecate_positional_args(category, match): @_tools.deprecate_positional_args(supported_number=2, category=category) def func(first, second, third=None, **kwargs): pass with warnings.catch_warnings(): warnings.simplefilter('error') func('first', 'second', third='third', extra='extra') if category is not None: with pytest.warns(category, match=match): func('first', 'second', 'third', extra='extra') else: with warnings.catch_warnings(): warnings.simplefilter('error') func('first', 'second', 'third', extra='extra') graphviz-0.20.2/tests/_common.py0000666000000000000000000000267414455106354015312 0ustar rootroot"""Test helpers and test globals.""" import contextlib import os import pathlib import platform import subprocess __all__ = ['EXPECTED_DOT_BINARY', 'EXPECTED_UNFLATTEN_BINARY', 'EXPECTED_DEFAULT_ENGINE', 'EXPECTED_DEFAULT_ENCODING', 'INVALID_CMD', 'as_cwd', 'check_startupinfo', 'StartupinfoMatcher'] EXPECTED_DOT_BINARY = pathlib.Path('dot') EXPECTED_UNFLATTEN_BINARY = pathlib.Path('unflatten') EXPECTED_DEFAULT_ENGINE = 'dot' EXPECTED_DEFAULT_ENCODING = 'utf-8' INVALID_CMD = [''] @contextlib.contextmanager def as_cwd(path): """Return a context manager, which changes to the path's directory during the managed ``with`` context.""" cwd = pathlib.Path().resolve() os.chdir(path) yield os.chdir(cwd) def check_startupinfo(startupinfo) -> bool: # noqa: N803 return startupinfo is None if platform.system().lower() == 'windows': def check_startupinfo(startupinfo) -> bool: # noqa: N803,F811 return (isinstance(startupinfo, subprocess.STARTUPINFO) and startupinfo.dwFlags & subprocess.STARTF_USESHOWWINDOW and startupinfo.wShowWindow == subprocess.SW_HIDE) class StartupinfoMatcher: """Verify the given startupinfo argument is as expected for the plaform.""" def __eq__(self, startupinfo) -> bool: return check_startupinfo(startupinfo) graphviz-0.20.2/build-docs.py0000666000000000000000000000420114155453170014530 0ustar rootroot#!/usr/bin/env python3 # flake8: noqa """Build the docs with https://www.sphinx-doc.org.""" import functools import pathlib import sys import webbrowser from sphinx.cmd import build SELF = pathlib.Path(__file__) SOURCE = pathlib.Path('docs') TARGET = SOURCE / '_build' RESULT = TARGET / 'index.html' BROWSER_OPEN = '--open' SKIP_OPEN_RESULT = '--no-open' DEFAULT_ARGS = [BROWSER_OPEN, '-W', '-n', '-v', str(SOURCE), str(TARGET)] OPEN_RESULT = BROWSER_OPEN in DEFAULT_ARGS print = functools.partial(print, sep='\n') args = sys.argv[1:] print(f'run {[SELF.name] + args}') if not args: args = DEFAULT_ARGS if SKIP_OPEN_RESULT in args: open_result = None args = [a for a in args for name, value in [a.partition('=')[::2]] if name not in (SKIP_OPEN_RESULT, BROWSER_OPEN)] elif any(a.partition('=')[0] == BROWSER_OPEN for a in args): open_result = RESULT values = {value for a in args for name, value in [a.partition('=')[::2]] if name == BROWSER_OPEN and value} if values: if len(values) != 1: raise ValueError(f'conflicting {BROWSER_OPEN}: {values}') value, = values if value: open_result = open_result.parent / value args = [a for a in args if a.partition('=')[0] != BROWSER_OPEN] if not args: # no pytest args given args = [a for a in DEFAULT_ARGS if a != SKIP_OPEN_RESULT and a.partition('=')[0] != BROWSER_OPEN] if args == ['-b', 'doctest']: args += ['-W', str(SOURCE), str(SOURCE / '_doctest')] print('', f'sphinx.cmd.build.main({args})',) returncode = build.main(args) status = 'FAILED' if returncode else 'PASSED' print('', f'{status} (returncode {returncode!r})', end='') try: if 'doctest' not in args: print('', f'index: {RESULT}', f'assert {RESULT!r}.stat().st_size', end='') assert open_result.stat().st_size, f'non-empty {open_result}' if open_result: print('', f'webbrowser.open({open_result!r})', end='') webbrowser.open(open_result) finally: sys.exit(returncode) graphviz-0.20.2/CHANGES.rst0000666000000000000000000005375614575735742013774 0ustar rootrootChangelog ========= Version 0.20.2 -------------- Drop Python 3.7 support (end of life 27 Jun 2023). Tag Python 3.11 and 3.12 support. Add caveat about ``labe`` escaping/quoting to ``.node()`` and ``.render()`` API docs. Document that ``doctest_skip_exe()`` lines in doctest should be ignored. Improve internal ``tools.deprecate_positional_args()`` decorator and fix incorect test assertion. Update GitHub actions. Version 0.20.1 -------------- Fix documentation building: upgrade to Sphinx 5.0. Fix broken user guide links in API documentation. Version 0.20 ------------ Add keyword-only ``neato_no_op`` argument to ``.render()``, ``.pipe()``, and stand-alone ``graphviz.render()`` and ``graphviz.pipe()``. When building a ``Graph`` or ``Digraph``, warn about an expected DOT syntax error in rendering when passing a string that ends with an odd number of backslashes (e.g. invalid ``dot.node('spam', label='\\')`` instead of correct ``..., label=r'\\'`` for a node labled as a backslash). Increase visibility of ``graphviz.escape()`` in the documentation. Version 0.19.2 -------------- Drop Python 3.6 support (end of life 23 Dec 2021). Fix ``ExecutableNotFound`` and ``CalledProcessError`` in ``graphviz.__all__``. Better document ``0.18`` change of behaviour for the ``body`` argument/attribute (lines need to include their final newline). Version 0.19.1 -------------- Fix undecoded ``CalledProcessError.stdout`` and ``.stderr`` when ``.pipe()`` call with an ``encoding`` different from ``self.encoding`` fails. Fix missing project root ``conftest.py`` in source distribution. Extend ``examples/graphviz-escapes.ipynb``. Improve test coverage. Increase build scripts verbosity. Version 0.19 ------------ Add ``PendingDeprecationWarning`` to calls using positional arguments that will be **deprecated in a later version**. The future API will allow from one to three positional arguments depending on the method or function. Keyword-only arguments where not around when this library was created. This signals dependents and in general users to start updating or pinning to the wanted version (or range). Crucially, this helps new users with a safer API that allows to avoid some common mistakes. Warnings reported in tests. Add keyword-only ``outfile`` argument to ``.render()`` and stand-alone ``graphviz.render()``. Allows to override the rendered output file name: ``.render(filename='spam.gv', outfile='spam.pdf')`` Allows to derive the ``format`` and the ``filename`` from the rendered ``outfile`` name: ``.render(outfile='spam.svg')`` Tries to infer default ``format`` from the ``outfile`` suffix. You can override by setting ``format`` explicitly. Warns with a ``graphviz.FormatSuffixMismatchWarning`` if there is a mismatch between given ``format`` and the inferred format from ``outfile`` suffix. Warns with a ``graphviz.UnknownSuffixWarning`` if ``format`` is given and ``outfile`` uses a suffix that cannot be mapped to a supported format. Add ``graphviz.set_jupyter_format()`` to set the output ``format`` used by the Jupyter visualization of ``graphviz.Graph``, ``graphviz.Digraph``, and ``graphviz.Source`` (supported formats: ``'svg'``, ``'png'``, ``'jpeg'``). Replace ``_repr_svg_()`` internally with ``_repr_mimebundle_(include, exclude)`` returning a mimebundle ``{'image/svg+xml', '`_ Christoph Boeddeker. Add keyword-only ``raise_if_result_exists`` argument to ``.render()`` and stand-alone ``graphviz.render()``. Raises ``graphviz.FileExistsError`` if the rendered file already exists. Add support to for ``.render()`` and stand-alone ``.render()`` to overwrite the input source file with the rendered output when using the ``outfile`` keyword-only argument. This probably only makes sense for text-based Graphviz formats such as ``dot`` or ``plain``. You need to specify ``overwrite_filepath=True`` to enable this. Add ``graphviz.CalledProcessError`` derived from ``subprocess.CalledProcessError`` so users can choose either one in their excepts. Add ``graphviz.FileExistsError`` derived from ``FileExistsError`` so users can choose either one in their excepts. Add ``--only-exe`` flag to ``run-tests.py`` (overrides ``--skip-exe``). Add ``--no-open`` and ``--open`` flags to ``build-docs.py``. Add ``lint-code.py`` and use in build job. Increase doctest coverage. Extend type annotations. Accept path-like objects for ``filename``, ``directory``, and ``filepath``. Extend and improve documentation. Improve build tests. Version 0.18.2 -------------- Fix ``filepath`` fallback to ``name`` of ``Graph/Digraph`` for when filepath is not present (restore ``graphviz.Graph('spam').filename == 'spam.gv'`` broken in 0.18). Fix unintended API docs reference to internal ``backend`` name for ``DOT_BINARY`` and ``UNFLATTEN_BINARY``. Moved to public API as ``graphviz.DOT_BINARY`` and ``graphviz.UNFLATTEN_BINARY``. Fix broken documentation links. Docs: re-render most SVGs and notebooks with upstream Graphviz 2.49.3. Version 0.18.1 -------------- Fix ``TypeError: argument of type 'WindowsPath' is not iterable`` on Windows platform under Python 3.6 and 3.7 (work around https://bugs.python.org/issue41649). Update outdated examples source links. Improve mode structure and separation of concerns. Improve test structure and coverage. Improve output of ``try-examples.py``. Add exit status for CI. Disable `view()`. Add ``build-docs.py`` script for development. Add ``update-help.py`` script for development. Version 0.18 ------------ Change of beaviour: File endings are now normalized so that all DOT source outputs end with a final newline (Unix convention, simplifies concatenation). This includes DOT source files written by ``.render()``, ``.view()``, or ``.save()`` as well was ``.source`` generated or loaded from ``Source`` (or ``Source.from_file()``). Change of behaviour: ``Source`` instances created by ``Source.from_file()`` no nonger write the content read into ``.source`` back into the file. Use ``.save(skip_existing=False)`` before calling ``.render()`` or ``.view()`` if you want to overwrite the file to produce the previous (less safe) behaviour. Change of undocumented behaviour: When iterating over a ``Graph``, ``Digraph``, or ``Source`` instance, the yielded lines now include a final newline (``'\n'``). This mimics iteration over ``file`` object lines in text mode. Change of behaviour: When adding custom DOT statements using the ``body`` argument of ``Graph`` or ``Digraph`` or appending to the ``body`` attribute of an instance, the lines now need to include their final newline (``'\n'``). When passing invalid parameters such as unknown ``engine``, ``format``, etc., ``.render()`` now raises early before writing the file. Call ``.save()`` explicitly to produce the previous (less safe) behaviour. Add optional keyword-only ``encoding`` argument to ``pipe()``. Returns the decoded stdout from the rendering process (e.g. ``format='svg'``). Delegates encoding/decoding to ``subprocess`` in the common case (input and output encoding are the same, e.g. default ``encoding='utf-8'``). Used by the Jupyter notebook integration. Add optional keyword-only ``engine`` argument to ``.pipe()`` and ``.render()``. Add optional keyword-only ``renderer`` and ``formatter`` arguments to ``Graph()``, ``Digraph()``, ``Source()`` and ``Source.from_file()`` to set default renderers and formatters (similar to ``format``). Used by ``.pipe()``, ``.render()``, and ``.view()`` if not given as method-argument. Add ``pipe_string()``, ``pipe_lines()``, and ``pipe_lines_string()``. Pipe ``input_string``, return ``string``. Pipe ``input_lines`` incrementally, return ``bytes``. Pipe ``input_lines`` incrementally, return ``string``. Add ``set_default_engine()`` and ``set_default_format()`` Add ``DOT_BINARY`` and ``UNFLATTEN_BINARY``. Restructure the internal class hierarchy using multiple-inheritance with cooperative ``super()`` calling: ``Graph`` now inherits both from ``Dot`` and from ``Render``, and both of them inherit from ``Base`` which defines their common interface: Lines of DOT source code that ``Dot`` generates (also ``Source``) and rendering iterates over. This might break some undocumented use of subclassing and require adatation (e.g. if the methods don't use cooperative ``super()`` calling convention or if the MRO has conflicts, supposedly rare). Improve test separation. Improve test coverage of running the tests with ``--skip-exe``. Add ``pytype`` checking and ``flake8`` to build workflow. Extend type annotations. Add https://mybinder.org config with head development environment. Add launch badge to code repository. Improve documentation and examples. Add development docs. Document release process. Version 0.17 ------------ Drop Python 2 support. Tag Python 3.10 support. Migrate CI to GitHub actions. Add ``pypy3`` to matrix. Tests: implement ``--skip-exe`` via custom ``pytest`` marker. Documentation: point Anaconda users to ``conda-forge/python-graphviz``. Move type hints from docstrings to type annotations. Improve doctests. Examples: standardize import convention and modernize. Re-render example notebooks with Graphviz 2.46.1. Version 0.16 ------------ Add ``.unflatten()`` method to ``Graph``, ``Digraph``, and ``Source``. Add standalone ``unflatten()``. Make ``Source.__str__()`` return the ``.source`` instead of the ``repr()`` (like ``Graph`` and ``Digraph``). Render with ``dot -K ...`` instead of `` ...`` internally (work around `upstream issue `_). Add documentation hint to archived upstream version for Windows. Re-render most documentation graphs with Graphviz 2.44.1. Version 0.15 ------------ ``Graph`` and ``Digraph`` instances created via the context-manager returned by ``subgraph()`` now (re)use ``directory``, ``format``, ``engine``, and ``encoding`` from the parent instead of using defaults (behavioral change). Note that these attributes are only relevant when rendering the subgraph independently (i.e. as a stand-alone graph) from within the ``with``-block, which was previously underdocumented. PR `#116 `_ BMaxV. To reflect that the DOT language does not allow subgraph statements to specify ``strict`` (i.e. no way to override the setting of the containing graph), instances created via the context-manager are now ``strict=None`` instead of ``False`` (so they continue to render stand-alone as non-strict by default). Drop Python 3.5 support and tag Python 3.9 support. Add documentation link to new upstream installation procedure for Windows. Version 0.14.2 -------------- Adapt ``graphviz.version()`` to support the Graphviz Release version entry format introduced with ``2.44.2`` (``version()`` is needed to run the tests). Version 0.14.1 -------------- Document the colon-separated ``node[:port[:compass]]`` format used for ``tail`` and ``head`` points in the ``edge()``- and ``edges()``-methods. PR `#101 `_ Michał Góral. Version 0.14 ------------ Improve handling of escaped quotes (``\"``). Different from other layout engine escapes sequences such as ``\l`` and ``\N`` (which are passed on as is by default), there is no use case for backslash-escaping a literal quote character because escaping of quotes is done by this library. Therefore, a backslash-escaped quote (e.g. in ``label='\\"'``) is now treated the same as a plain unescaped quote, i.e. both ``label='"'`` and ``label='\\"'`` produce the same DOT source ``[label="\""]`` (a label that renders as a literal quote). Before this change, use of ``'\\"'`` could break the quoting mechanism creating invalid or unintended DOT, possibly leading to syntax errors from the rendering process. Add notebook section to documentation. Add ``sphinx.ext.viewcode`` to docs (note that this currently lacks links for methods, so that not all of the code is linked; use the source repo for reading on). Improve test and doc building config. Version 0.13.2 -------------- Fix missing support for four-part versions in ``graphviz.version()``. Version 0.13.1 -------------- Tag Python 3.8 support. Fix quoting for non-ASCII numerals. Version 0.13 ------------ Add explicit support for layout engine escape sequences such as ``\l`` and ``\N``. These already worked implicitly before but where broken by backslash escaping in ``0.12``, which is reverted by this release. Escaping now resembles the stdlib ``re`` module: literal backslashes need to be escaped (doubled), which is most conveniently done by using raw string literals for strings that use escape sequences (including escaped backslashes), e.g. ``label=r'\\'``. Add ``escape()`` function (resembling ``re.escape()``) for disabling all meta-characters in a string for rendering. Use ``logging`` in example notebook, add notebooks demonstrating layout engines and escape sequence usage, improve tests with parametrization. Version 0.12 ------------ Fix missing escaping of backslashes, e.g. in labels (pull request DNGros). Add ``quiet`` argument to standalone ``view()`` function, and ``quiet_view`` argument on ``.render()`` and ``.view()`` methods. Suppresses the ``stderr`` output of started viewer processes (unavailable on Windows). Add basic debug logging via the stdlib ``logging`` module. Reformatted some examples, improved tests by using autospec for mocks. Version 0.11.1 -------------- Include ``stderr`` in ``str()`` of raised ``subprocess.CalledProcessError``. Version 0.11 ------------ Add ``quiet`` argument to ``.render()`` and ``.pipe()`` methods of ``Graph``, ``Digraph``, and ``Source`` objects, allowing to suppress ``stderr`` of the layout subprocess (parity with stand-alone ``render()`` and ``pipe()`` functions). The rendering process for ``render()`` methods and stand-alone function is now started from the directory of the rendered dot source file. This allows to render graph descriptions that use relative paths inline (e.g. for referring to image files to be included) by using paths relative to the source file location. Previously, such relative paths would need to be given relative to the directory from which ``render()`` was started, so this change is backwards incompatible for code that relied on the previous behaviour. Drop Python 3.4 support. Version 0.10.1 -------------- Fix broken renderer argument in ``pipe()`` method and function. Version 0.10 ------------ Add ``format`` argument to ``Graph/Digraph.render()``. This follows stand-alone ``render()`` function and mirrors the ``Graph/Digraph.pipe()`` method (usually, ``format`` is set on the instance). Add ``renderer`` and ``formatter`` arguments to ``Graph/Digraph.render()`` and ``pipe()`` methods, as well as stand-alone ``render()`` and ``pipe()`` functions. Version 0.9 ----------- Use ``sys.stderr`` to write stderr output from rendering process to stderr (instead of file descriptor inheritance). Ensures stderr is passed in special environments such as IDLE. Suppress rendering process ``stdout`` in ``render()``. Make ``quiet=True`` also suppress ``stderr`` on success of ``render()`` and ``pipe()`` (exit-status ``0``). Include ``stderr`` from rendering process in ``CalledProcessError`` exception. Version 0.8.4 ------------- Tag Python 3.7 support (work around subprocess ``close_fds`` issue on Windows). Version 0.8.3 ------------- Fix compatibility with ``python -OO``. Version 0.8.2 ------------- Add ``nohtml()`` to support labels of the form ``'<...>'`` (disabling their default treatment as HTML strings). Make default ``'utf-8'`` ``encoding`` more visible. Set ``encoding = locale.getpreferredencoding()`` when ``encoding`` argument/property is set to ``None`` explicitly (follow stdlib ``io.open()`` behaviour). Version 0.8.1 ------------- Add ``Source.from_file()``-classmethod (simpler in-line SVG display of ready-made .gv files within Jupyter). Drop Python 3.3 support. Version 0.8 ----------- Add ``clear()``-method for ``Graph`` and ``Digraph``. Add ``grapviz.version()`` function. Drop dot source extra indent for edge statements following dotguide examples. Include LICENSE file in wheel. Version 0.7.1 ------------- Fix ``TypeError`` in ``graphviz.pipe()`` with invalid dot code under Python 3. Add ``copy()``-method for ``Graph``, ``Digraph``, and ``Source``. Add ``graphviz.render(..., quiet=True)``. Fix ``graphivz.view()`` exception on unsupported platform. Raise a dedicated ``RuntimeError`` subclass ``graphviz.ExecutableNotFound`` when the Graphviz executables are not found. Port tests from ``nose/unittest`` to ``pytest``, extend, use mocks. Version 0.7 ----------- Support setting top-level attrs with ``g.attr(key=value)``. Add context manager usage of ``subgraph()`` for adding a subgraph in a with-block. Add json-based output formats to known ``FORMATS`` (Graphviz 2.40+). Drop extra indent level for DOT source with nonempty ``graph/node/edge_attr``. Add a final newline to a saved DOT source file if it does not end with one. Raise ``subprocess.CalledProcessError`` on non-zero exit status from rendering. Raise early when adding a ``subgraph()`` with ``strict=True`` (avoid DOT syntax error). Make undocumented ``quote()``, ``quote_edge()``, and ``attributes()`` methods private. Version 0.6 ----------- Drop Python 2.6 support (use ``graphviz<0.6`` there). Improve tests for ``mkdirs()``. Better document adding custom DOT using the ``body`` attribute. Add ``view()``-support for FreeBSD (pull request Julien Gamba). Version 0.5.2 ------------- Add ``ENGINES`` and ``FORMATS`` to the documented public API. Version 0.5.1 ------------- Fixed PY3 compatibility. Version 0.5 ----------- Add low-level functions ``render()``, ``pipe()``, and ``view()`` for directly working with existing files and strings. Support all ``render()``-arguments in the ``view()``-short-cut-method. Version 0.4.10 -------------- Added ``'patchwork'`` engine. Version 0.4.9 ------------- Add support for ``strict`` graphs and digraphs. Hide ``render/pipe()`` subprocess console window on Windows when invoked from non-console process (e.g. from IDLE). Improve documentation markup/wording. Make ``TestNoent`` more robust. Version 0.4.8 ------------- Make ``_repr_svg_()`` available on ``Source`` (pull request RafalSkolasinski). Version 0.4.7 ------------- Fixed ``view()``-method on Linux under Python 3 (pull request Antony Lee). Version 0.4.6 ------------- Fixed ``view()``-method on Linux and Darwin (pull request Eric L. Frederich). Version 0.4.5 ------------- Added example for HTML-like labels (``structs.py``). Added ``Source`` class for rendering verbatim DOT source code. Added Python 2.6 support (pull request Jim Crist). Version 0.4.4 ------------- Added the ``pipe()``-method directly returning the ``stdout`` of rendering. Added ``_repr_svg_()`` for inline rendering in IPython notebooks. Version 0.4.3 ------------- Added examples generating some of the graphs from the Graphviz Gallery. Added sphinx-based API documentation. Version 0.4.2 ------------- Added support for HTML-like labels. Version 0.4.1 ------------- Added support for less common output formats. Removed dropped formats (``'dia'``, ``'pcl'``). Added ``'osage'`` layout engine. Documented ``format`` and ``engine`` options in the README. The ``view()`` convenience method now returns the result file name (like render()). Version 0.4 ----------- Added ``attr()`` method for inline switching of node/edge attributes. Added ``subgraph()`` method (obsoletes separate ``Subgraph`` class). Add ``cleanup`` option to ``render()``. Replaced ``dry`` option on ``render()`` with separate ``save()`` method. Removed undocumented ``append()`` and ``extend()`` methods (if needed, the ``body`` attribute can be edited directly). Version 0.3.5 ------------- Skip empty ``comment`` when creating DOT source. Document ``graph_attr``, ``node_attr``, and ``edge_attr`` in the README. More informative exception when Graphviz executables cannot be called. Version 0.3.4 ------------- Fixed missing identifier quoting for DOT keywords (thanks to Paulo Urio). Version 0.3.3 ------------- Made ``format`` and ``engine`` case-insensitive. Version 0.3.2 ------------- Indent ``graph_attr``, ``node_attr``, and ``edge_attr`` lines, adapt nodes and edges. Version 0.3.1 ------------- Fixed ``view()`` failing on paths with forward slashes on Windows. Version 0.3 ----------- Added Python 3.3+ support. Made attributes order stable (sorting plain dicts). Fixed edgeop in undirected graphs. Version 0.2.2 ------------- Support pdf opening on Linux. Fixed rendering filenames w/spaces. Version 0.2.1 ------------- Fixed rendering on Mac OS X. Version 0.2 ----------- Added format selection, use ``'PDF``' as default. Added engines selection, use ``'dot'`` as default. Added source encoding, use ``'UTF-8'`` as default. Changed constructor arguments order, removed ``compile()`` and ``save()``-method, reimplemented compilation in ``render()`` method, make interface more similar to gv.3python (backwards incompatible change). Double-quote-sign escaping, attribute list quoting. ``mkdirs()`` now correctly supports current directory filenames. Version 0.1.1 ------------- Removed automatic ``'-'`` to ``'−'`` replacement from labels. Fixed documentation typos. Version 0.1 ----------- First public release. graphviz-0.20.2/conftest.py0000666000000000000000000000340014224214124014317 0ustar rootroot"""pytest command line options and doctest flag definition/setup.""" import doctest import unittest.mock NO_EXE = doctest.register_optionflag('NO_EXE') class NoExeChecker(doctest.OutputChecker): # noqa: E302 def check_output(self, want, got, optionflags, *args, **kwargs) -> bool: if optionflags & NO_EXE: return True return super().check_output(want, got, optionflags, *args, **kwargs) unittest.mock.patch.object(doctest, 'OutputChecker', new=NoExeChecker).start() # noqa: E305 import pytest # noqa: E402 SKIP_EXE = '--skip-exe' ONLY_EXE = '--only-exe' def pytest_addoption(parser): parser.addoption(SKIP_EXE, action='store_true', help='Skip tests with pytest.mark.exe.' ' Xfail tests with pytest.mark.exe(xfail=True).' ' Skip doctests with doctest_mark_exe().' ' Xfail doctests with doctest_mark_exe(xfail=True).' ' exe marks tests requiring backend.DOT_BINARY.') parser.addoption(ONLY_EXE, action='store_true', help='Skip tests without pytest.mark.exe.' ' Overrides --skip-exe.' ' exe marks tests requiring backend.DOT_BINARY.') @pytest.fixture(autouse=True) def doctests(pytestconfig, doctest_namespace): def doctest_mark_exe(**kwargs): return None if pytestconfig.getoption(SKIP_EXE): def doctest_mark_exe(*, reason=SKIP_EXE, xfail: bool = False, **kwargs): # noqa: F811 return (pytest.xfail(reason=reason, **kwargs) if xfail else pytest.skip(reason, **kwargs)) doctest_namespace.update(doctest_mark_exe=doctest_mark_exe) graphviz-0.20.2/LICENSE.txt0000666000000000000000000000212314575735742013773 0ustar rootrootThe MIT License (MIT) Copyright (c) 2013-2024 Sebastian Bank Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. graphviz-0.20.2/lint-code.py0000666000000000000000000000131014155453170014357 0ustar rootroot#!/usr/bin/env python3 """Run code linting with https://flake8.pycqa.org.""" import pathlib import platform import subprocess import sys SELF = pathlib.Path(__file__) PYTHON = 'py' if platform.system() == 'Windows' else 'python' CMD = [PYTHON, '-m', 'flake8'] print('run', [SELF.name] + sys.argv[1:]) cmd = CMD + sys.argv[1:] print(f'subprocess.run({cmd!r})') try: proc = subprocess.run(cmd, check=True) except subprocess.CalledProcessError as e: assert e.returncode != 0, f'non-zero returncode: {e}' print('FAILED:', e) sys.exit(e.returncode) else: assert proc.returncode == 0, f'passed: {proc}' print('PASSED:', proc) sys.exit(proc.returncode) graphviz-0.20.2/MANIFEST.in0000666000000000000000000000051114155453170013667 0ustar rootrootinclude README.rst LICENSE.txt CHANGES.rst include requirements.txt include conftest.py build-docs.py lint-code.py run-tests.py try-examples.py update-help.py include tox.ini recursive-include tests *.py *.png recursive-include examples *.py *.ipynb recursive-include docs *.rst *.txt *.py *.png *.svg prune docs/_build graphviz-0.20.2/PKG-INFO0000666000000000000000000003017414575736336013254 0ustar rootrootMetadata-Version: 2.1 Name: graphviz Version: 0.20.2 Summary: Simple Python interface for Graphviz Home-page: https://github.com/xflr6/graphviz Author: Sebastian Bank Author-email: sebastian.bank@uni-leipzig.de License: MIT Project-URL: Documentation, https://graphviz.readthedocs.io Project-URL: Changelog, https://graphviz.readthedocs.io/en/latest/changelog.html Project-URL: Issue Tracker, https://github.com/xflr6/graphviz/issues Project-URL: CI, https://github.com/xflr6/graphviz/actions Project-URL: Coverage, https://codecov.io/gh/xflr6/graphviz Keywords: graph visualization dot render Platform: any Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Scientific/Engineering :: Visualization Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE.txt Provides-Extra: dev Requires-Dist: tox>=3; extra == "dev" Requires-Dist: flake8; extra == "dev" Requires-Dist: pep8-naming; extra == "dev" Requires-Dist: wheel; extra == "dev" Requires-Dist: twine; extra == "dev" Provides-Extra: test Requires-Dist: pytest<8.1,>=7; extra == "test" Requires-Dist: pytest-mock>=3; extra == "test" Requires-Dist: pytest-cov; extra == "test" Requires-Dist: coverage; extra == "test" Provides-Extra: docs Requires-Dist: sphinx<7,>=5; extra == "docs" Requires-Dist: sphinx-autodoc-typehints; extra == "docs" Requires-Dist: sphinx-rtd-theme; extra == "docs" Graphviz ======== |PyPI version| |License| |Supported Python| |Wheel| |Downloads| |Build| |Codecov| |Readthedocs-stable| |Readthedocs-latest| |Binder-stable| This package facilitates the creation and rendering of graph descriptions in the DOT_ language of the Graphviz_ graph drawing software (`upstream repo`_) from Python. Create a graph object, assemble the graph by adding nodes and edges, and retrieve its DOT source code string. Save the source code to a file and render it with the Graphviz installation of your system. Use the ``view`` option/method to directly inspect the resulting (PDF, PNG, SVG, etc.) file with its default application. Graphs can also be rendered and displayed within `Jupyter notebooks`_ (formerly known as `IPython notebooks`_, `example `_, `nbviewer `_) as well as the `Jupyter QtConsole`_. Links ----- - GitHub: https://github.com/xflr6/graphviz - PyPI: https://pypi.org/project/graphviz/ - Documentation: https://graphviz.readthedocs.io - Changelog: https://graphviz.readthedocs.io/en/latest/changelog.html - Issue Tracker: https://github.com/xflr6/graphviz/issues - Download: https://pypi.org/project/graphviz/#files Installation ------------ This package runs under Python 3.8+, use pip_ to install: .. code:: bash $ pip install graphviz To render the generated DOT source code, you also need to install Graphviz_ (`download page `_, `archived versions `_, `installation procedure for Windows `_). Make sure that the directory containing the ``dot`` executable is on your systems' ``PATH`` (sometimes done by the installer; setting ``PATH`` on `Linux `_, `Mac `_, and `Windows `_). Anaconda_: see the conda-forge_ package `conda-forge/python-graphviz `_ (`feedstock `_), which should automatically ``conda install`` `conda-forge/graphviz `_ (`feedstock `_) as dependency. Quickstart ---------- Create a graph object: .. code:: python >>> import graphviz # doctest: +NO_EXE >>> dot = graphviz.Digraph(comment='The Round Table') >>> dot #doctest: +ELLIPSIS Add nodes and edges: .. code:: python >>> dot.node('A', 'King Arthur') # doctest: +NO_EXE >>> dot.node('B', 'Sir Bedevere the Wise') >>> dot.node('L', 'Sir Lancelot the Brave') >>> dot.edges(['AB', 'AL']) >>> dot.edge('B', 'L', constraint='false') Check the generated source code: .. code:: python >>> print(dot.source) # doctest: +NORMALIZE_WHITESPACE +NO_EXE // The Round Table digraph { A [label="King Arthur"] B [label="Sir Bedevere the Wise"] L [label="Sir Lancelot the Brave"] A -> B A -> L B -> L [constraint=false] } Save and render the source code (skip/ignore any ``doctest_mark_exe()`` lines): .. code:: python >>> doctest_mark_exe() # skip this line >>> dot.render('doctest-output/round-table.gv').replace('\\', '/') 'doctest-output/round-table.gv.pdf' Save and render and view the result: .. code:: python >>> doctest_mark_exe() # skip this line >>> dot.render('doctest-output/round-table.gv', view=True) # doctest: +SKIP 'doctest-output/round-table.gv.pdf' .. image:: https://raw.github.com/xflr6/graphviz/master/docs/_static/round-table.svg :align: center :alt: round-table.svg **Caveat:** Backslash-escapes and strings of the form ``<...>`` have a special meaning in the DOT language. If you need to render arbitrary strings (e.g. from user input), check the details in the `user guide`_. See also -------- - pygraphviz_ |--| full-blown interface wrapping the Graphviz C library with SWIG - graphviz-python_ |--| official Python bindings (`documentation `_) - pydot_ |--| stable pure-Python approach, requires pyparsing License ------- This package is distributed under the `MIT license`_. Development ----------- - Development documentation: https://graphviz.readthedocs.io/en/latest/development.html - Release process: https://graphviz.readthedocs.io/en/latest/release_process.html .. _Graphviz: https://www.graphviz.org .. _DOT: https://www.graphviz.org/doc/info/lang.html .. _upstream repo: https://gitlab.com/graphviz/graphviz/ .. _upstream-download: https://www.graphviz.org/download/ .. _upstream-archived: https://www2.graphviz.org/Archive/stable/ .. _upstream-windows: https://forum.graphviz.org/t/new-simplified-installation-procedure-on-windows/224 .. _set-path-windows: https://www.computerhope.com/issues/ch000549.htm .. _set-path-linux: https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix .. _set-path-darwin: https://stackoverflow.com/questions/22465332/setting-path-environment-variable-in-osx-permanently .. _pip: https://pip.pypa.io .. _Jupyter notebooks: https://jupyter.org .. _IPython notebooks: https://ipython.org/notebook.html .. _Jupyter QtConsole: https://qtconsole.readthedocs.io .. _notebook: https://github.com/xflr6/graphviz/blob/master/examples/graphviz-notebook.ipynb .. _notebook-nbviewer: https://nbviewer.org/github/xflr6/graphviz/blob/master/examples/graphviz-notebook.ipynb .. _Anaconda: https://docs.anaconda.com/anaconda/install/ .. _conda-forge: https://conda-forge.org .. _conda-forge-python-graphviz: https://anaconda.org/conda-forge/python-graphviz .. _conda-forge-python-graphviz-feedstock: https://github.com/conda-forge/python-graphviz-feedstock .. _conda-forge-graphviz: https://anaconda.org/conda-forge/graphviz .. _conda-forge-graphviz-feedstock: https://github.com/conda-forge/graphviz-feedstock .. _user guide: https://graphviz.readthedocs.io/en/stable/manual.html .. _pygraphviz: https://pypi.org/project/pygraphviz/ .. _graphviz-python: https://pypi.org/project/graphviz-python/ .. _graphviz-python-docs: https://www.graphviz.org/pdf/gv.3python.pdf .. _pydot: https://pypi.org/project/pydot/ .. _MIT license: https://opensource.org/licenses/MIT .. |--| unicode:: U+2013 .. |PyPI version| image:: https://img.shields.io/pypi/v/graphviz.svg :target: https://pypi.org/project/graphviz/ :alt: Latest PyPI Version .. |License| image:: https://img.shields.io/pypi/l/graphviz.svg :target: https://github.com/xflr6/graphviz/blob/master/LICENSE.txt :alt: License .. |Supported Python| image:: https://img.shields.io/pypi/pyversions/graphviz.svg :target: https://pypi.org/project/graphviz/ :alt: Supported Python Versions .. |Wheel| image:: https://img.shields.io/pypi/wheel/graphviz.svg :target: https://pypi.org/project/graphviz/#files :alt: Wheel format .. |Downloads| image:: https://img.shields.io/pypi/dm/graphviz.svg :target: https://pypistats.org/packages/graphviz :alt: Monthly downloads .. |Build| image:: https://github.com/xflr6/graphviz/actions/workflows/build.yaml/badge.svg?branch=master :target: https://github.com/xflr6/graphviz/actions/workflows/build.yaml?query=branch%3Amaster :alt: Build .. |Codecov| image:: https://codecov.io/gh/xflr6/graphviz/branch/master/graph/badge.svg :target: https://codecov.io/gh/xflr6/graphviz :alt: Codecov .. |Readthedocs-stable| image:: https://readthedocs.org/projects/graphviz/badge/?version=stable :target: https://graphviz.readthedocs.io/en/stable/ :alt: Readthedocs (stable) .. |Readthedocs-latest| image:: https://readthedocs.org/projects/graphviz/badge/?version=latest :target: https://graphviz.readthedocs.io/en/latest/ :alt: Readthedocs (latest) .. |Binder-stable| image:: https://img.shields.io/badge/launch-binder%20(stable)-579ACA.svg?logo=data:image/png;base64,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 :target: https://mybinder.org/v2/gh/xflr6/graphviz/stable :alt: Binder (stable) graphviz-0.20.2/README.rst0000666000000000000000000002437214550602024013624 0ustar rootrootGraphviz ======== |PyPI version| |License| |Supported Python| |Wheel| |Downloads| |Build| |Codecov| |Readthedocs-stable| |Readthedocs-latest| |Binder-stable| This package facilitates the creation and rendering of graph descriptions in the DOT_ language of the Graphviz_ graph drawing software (`upstream repo`_) from Python. Create a graph object, assemble the graph by adding nodes and edges, and retrieve its DOT source code string. Save the source code to a file and render it with the Graphviz installation of your system. Use the ``view`` option/method to directly inspect the resulting (PDF, PNG, SVG, etc.) file with its default application. Graphs can also be rendered and displayed within `Jupyter notebooks`_ (formerly known as `IPython notebooks`_, `example `_, `nbviewer `_) as well as the `Jupyter QtConsole`_. Links ----- - GitHub: https://github.com/xflr6/graphviz - PyPI: https://pypi.org/project/graphviz/ - Documentation: https://graphviz.readthedocs.io - Changelog: https://graphviz.readthedocs.io/en/latest/changelog.html - Issue Tracker: https://github.com/xflr6/graphviz/issues - Download: https://pypi.org/project/graphviz/#files Installation ------------ This package runs under Python 3.8+, use pip_ to install: .. code:: bash $ pip install graphviz To render the generated DOT source code, you also need to install Graphviz_ (`download page `_, `archived versions `_, `installation procedure for Windows `_). Make sure that the directory containing the ``dot`` executable is on your systems' ``PATH`` (sometimes done by the installer; setting ``PATH`` on `Linux `_, `Mac `_, and `Windows `_). Anaconda_: see the conda-forge_ package `conda-forge/python-graphviz `_ (`feedstock `_), which should automatically ``conda install`` `conda-forge/graphviz `_ (`feedstock `_) as dependency. Quickstart ---------- Create a graph object: .. code:: python >>> import graphviz # doctest: +NO_EXE >>> dot = graphviz.Digraph(comment='The Round Table') >>> dot #doctest: +ELLIPSIS Add nodes and edges: .. code:: python >>> dot.node('A', 'King Arthur') # doctest: +NO_EXE >>> dot.node('B', 'Sir Bedevere the Wise') >>> dot.node('L', 'Sir Lancelot the Brave') >>> dot.edges(['AB', 'AL']) >>> dot.edge('B', 'L', constraint='false') Check the generated source code: .. code:: python >>> print(dot.source) # doctest: +NORMALIZE_WHITESPACE +NO_EXE // The Round Table digraph { A [label="King Arthur"] B [label="Sir Bedevere the Wise"] L [label="Sir Lancelot the Brave"] A -> B A -> L B -> L [constraint=false] } Save and render the source code (skip/ignore any ``doctest_mark_exe()`` lines): .. code:: python >>> doctest_mark_exe() # skip this line >>> dot.render('doctest-output/round-table.gv').replace('\\', '/') 'doctest-output/round-table.gv.pdf' Save and render and view the result: .. code:: python >>> doctest_mark_exe() # skip this line >>> dot.render('doctest-output/round-table.gv', view=True) # doctest: +SKIP 'doctest-output/round-table.gv.pdf' .. image:: https://raw.github.com/xflr6/graphviz/master/docs/_static/round-table.svg :align: center :alt: round-table.svg **Caveat:** Backslash-escapes and strings of the form ``<...>`` have a special meaning in the DOT language. If you need to render arbitrary strings (e.g. from user input), check the details in the `user guide`_. See also -------- - pygraphviz_ |--| full-blown interface wrapping the Graphviz C library with SWIG - graphviz-python_ |--| official Python bindings (`documentation `_) - pydot_ |--| stable pure-Python approach, requires pyparsing License ------- This package is distributed under the `MIT license`_. Development ----------- - Development documentation: https://graphviz.readthedocs.io/en/latest/development.html - Release process: https://graphviz.readthedocs.io/en/latest/release_process.html .. _Graphviz: https://www.graphviz.org .. _DOT: https://www.graphviz.org/doc/info/lang.html .. _upstream repo: https://gitlab.com/graphviz/graphviz/ .. _upstream-download: https://www.graphviz.org/download/ .. _upstream-archived: https://www2.graphviz.org/Archive/stable/ .. _upstream-windows: https://forum.graphviz.org/t/new-simplified-installation-procedure-on-windows/224 .. _set-path-windows: https://www.computerhope.com/issues/ch000549.htm .. _set-path-linux: https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix .. _set-path-darwin: https://stackoverflow.com/questions/22465332/setting-path-environment-variable-in-osx-permanently .. _pip: https://pip.pypa.io .. _Jupyter notebooks: https://jupyter.org .. _IPython notebooks: https://ipython.org/notebook.html .. _Jupyter QtConsole: https://qtconsole.readthedocs.io .. _notebook: https://github.com/xflr6/graphviz/blob/master/examples/graphviz-notebook.ipynb .. _notebook-nbviewer: https://nbviewer.org/github/xflr6/graphviz/blob/master/examples/graphviz-notebook.ipynb .. _Anaconda: https://docs.anaconda.com/anaconda/install/ .. _conda-forge: https://conda-forge.org .. _conda-forge-python-graphviz: https://anaconda.org/conda-forge/python-graphviz .. _conda-forge-python-graphviz-feedstock: https://github.com/conda-forge/python-graphviz-feedstock .. _conda-forge-graphviz: https://anaconda.org/conda-forge/graphviz .. _conda-forge-graphviz-feedstock: https://github.com/conda-forge/graphviz-feedstock .. _user guide: https://graphviz.readthedocs.io/en/stable/manual.html .. _pygraphviz: https://pypi.org/project/pygraphviz/ .. _graphviz-python: https://pypi.org/project/graphviz-python/ .. _graphviz-python-docs: https://www.graphviz.org/pdf/gv.3python.pdf .. _pydot: https://pypi.org/project/pydot/ .. _MIT license: https://opensource.org/licenses/MIT .. |--| unicode:: U+2013 .. |PyPI version| image:: https://img.shields.io/pypi/v/graphviz.svg :target: https://pypi.org/project/graphviz/ :alt: Latest PyPI Version .. |License| image:: https://img.shields.io/pypi/l/graphviz.svg :target: https://github.com/xflr6/graphviz/blob/master/LICENSE.txt :alt: License .. |Supported Python| image:: https://img.shields.io/pypi/pyversions/graphviz.svg :target: https://pypi.org/project/graphviz/ :alt: Supported Python Versions .. |Wheel| image:: https://img.shields.io/pypi/wheel/graphviz.svg :target: https://pypi.org/project/graphviz/#files :alt: Wheel format .. |Downloads| image:: https://img.shields.io/pypi/dm/graphviz.svg :target: https://pypistats.org/packages/graphviz :alt: Monthly downloads .. |Build| image:: https://github.com/xflr6/graphviz/actions/workflows/build.yaml/badge.svg?branch=master :target: https://github.com/xflr6/graphviz/actions/workflows/build.yaml?query=branch%3Amaster :alt: Build .. |Codecov| image:: https://codecov.io/gh/xflr6/graphviz/branch/master/graph/badge.svg :target: https://codecov.io/gh/xflr6/graphviz :alt: Codecov .. |Readthedocs-stable| image:: https://readthedocs.org/projects/graphviz/badge/?version=stable :target: https://graphviz.readthedocs.io/en/stable/ :alt: Readthedocs (stable) .. |Readthedocs-latest| image:: https://readthedocs.org/projects/graphviz/badge/?version=latest :target: https://graphviz.readthedocs.io/en/latest/ :alt: Readthedocs (latest) .. |Binder-stable| image:: https://img.shields.io/badge/launch-binder%20(stable)-579ACA.svg?logo=data:image/png;base64,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 :target: https://mybinder.org/v2/gh/xflr6/graphviz/stable :alt: Binder (stable) graphviz-0.20.2/requirements.txt0000666000000000000000000000016614141276260015421 0ustar rootroot# development environment: --editable install in development mode (includes all extras_require) -e .[dev,test,docs] graphviz-0.20.2/run-tests.py0000666000000000000000000000122714224214124014443 0ustar rootroot#!/usr/bin/env python3 # flake8: noqa """Run the tests with https://pytest.org.""" import pathlib import platform import sys import pytest SELF = pathlib.Path(__file__) ARGS = [#'--skip-exe', #'--only-exe', #'--collect-only', #'--verbose', #'--pdb', #'--exitfirst', # a.k.a. -x #'-W', 'error', #'--doctest-report none', ] if platform.system() == 'Windows' and 'idlelib' in sys.modules: ARGS += ['--capture=sys', '--color=no'] print('run', [SELF.name] + sys.argv[1:]) args = ARGS + sys.argv[1:] print(f'pytest.main({args!r})') sys.exit(pytest.main(args)) graphviz-0.20.2/setup.cfg0000666000000000000000000000127314575736336013776 0ustar rootroot[metadata] license_files = LICENSE.txt [sdist] formats = zip [tool:pytest] minversion = 6 testpaths = README.rst docs graphviz tests addopts = --doctest-modules --doctest-glob='*.rst' --ignore=docs/conf.py --doctest-continue-on-failure -r fsxX --durations=10 --cov --cov-report=term --cov-report=html --strict-config --strict-markers log_cli = true log_cli_level = WARNING log_file = test-log.txt log_file_level = DEBUG [coverage:run] source = graphviz branch = false omit = */conftest.py [pytype] inputs = graphviz [flake8] ignore = E126,E128,W503 max-line-length = 100 exclude = docs, build, .tox [egg_info] tag_build = tag_date = 0 graphviz-0.20.2/setup.py0000666000000000000000000000360114575735742013664 0ustar rootrootimport pathlib from setuptools import setup, find_packages setup( name='graphviz', version='0.20.2', author='Sebastian Bank', author_email='sebastian.bank@uni-leipzig.de', description='Simple Python interface for Graphviz', keywords='graph visualization dot render', license='MIT', url='https://github.com/xflr6/graphviz', project_urls={ 'Documentation': 'https://graphviz.readthedocs.io', 'Changelog': 'https://graphviz.readthedocs.io/en/latest/changelog.html', 'Issue Tracker': 'https://github.com/xflr6/graphviz/issues', 'CI': 'https://github.com/xflr6/graphviz/actions', 'Coverage': 'https://codecov.io/gh/xflr6/graphviz', }, packages=find_packages(), platforms='any', python_requires='>=3.8', extras_require={ 'dev': ['tox>=3', 'flake8', 'pep8-naming', 'wheel', 'twine'], 'test': ['pytest>=7,<8.1', # https://github.com/pytest-dev/pytest/issues/12123 'pytest-mock>=3', 'pytest-cov', 'coverage'], 'docs': ['sphinx>=5,<7', 'sphinx-autodoc-typehints', 'sphinx-rtd-theme'], }, long_description=pathlib.Path('README.rst').read_text(encoding='utf-8'), long_description_content_type='text/x-rst', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Topic :: Scientific/Engineering :: Visualization', ], ) graphviz-0.20.2/tox.ini0000666000000000000000000000022714567546160013461 0ustar rootroot[tox] envlist = py{312,311,310,39,38} skip_missing_interpreters = true [testenv] extras = test commands = python -X dev run-tests.py {posargs} graphviz-0.20.2/try-examples.py0000666000000000000000000000274714155453170015152 0ustar rootroot#!/usr/bin/env python3 # flake8: noqa """Import ``graphviz`` here and run all scripts in the ``examples/`` dir.""" import os import pathlib import sys import unittest.mock import warnings import graphviz # noqa: F401 SELF = pathlib.Path(__file__) EXAMPLES = pathlib.Path('examples') IO_KWARGS = {'encoding': 'utf-8'} DEFAULT_FORMAT = 'pdf' print('run', [SELF.name] + sys.argv[1:]) os.chdir(EXAMPLES) graphviz.set_default_format(DEFAULT_FORMAT) raised = [] with unittest.mock.patch.object(graphviz.graphs.BaseGraph, '_view') as mock_view: for path in pathlib.Path().glob('*.py'): print(path) code = path.read_text(**IO_KWARGS) try: exec(code) except Exception as e: raised.append(e) warnings.warn(e) else: if path.name.endswith('_recipe.py'): continue rendered = f'{path.stem}.gv.{DEFAULT_FORMAT}' assert pathlib.Path(rendered).stat().st_size, f'non-empty {rendered}' mock_view.assert_called_once_with(rendered, format=DEFAULT_FORMAT, quiet=False) mock_view.reset_mock() if not raised: print('PASSED: all examples passed without raising') sys.exit(None) else: message = f'FAILED: {len(raised)} examples raised (WARNING)' print(message, *raised, sep='\n') sys.exit(message) graphviz-0.20.2/update-help.py0000666000000000000000000001063114270422612014712 0ustar rootroot#!/usr/bin/env python3 """Update the ``help()`` outputs in ``docs/api.rst``.""" import contextlib import difflib import io import operator import pathlib import re import sys import typing import graphviz SELF = pathlib.Path(__file__) ALL_CLASSES = [graphviz.Graph, graphviz.Digraph, graphviz.Source] ARGS_LINE = re.compile(r'(?:class | \| {2})\w+\(') WRAP_AFTER = 80 INDENT = ' ' * 4 TARGET = pathlib.Path('docs/api.rst') PATTERN_TMPL = (r''' ( \ {{4}}>>>\ help\(graphviz\.{cls_name}\).*\n) \ {{4}}Help\ on\ class\ {cls_name} \ in\ module\ graphviz\.(?:graphs|sources):\n \ {{4}}\n (?:.*\n)+? \ {{4}}\n ''') IO_KWARGS = {'encoding': 'utf-8'} def get_help(obj) -> str: print(f'capture help() output for {obj}') with io.StringIO() as buf: with contextlib.redirect_stdout(buf): help(obj) buf.seek(0) return ''.join(iterlines(buf)) def rpartition_initial(value: str, *, sep: str) -> typing.Tuple[str, str, str]: """Return (value, '', '') if sep not in value else value.rpartition(sep).""" _, sep_found, _ = parts = value.rpartition(sep) return tuple(reversed(parts)) if not sep_found else parts def iterarguments(unwrapped_line: str) -> typing.Iterator[str]: """Yield unwrapped line of argument definitions divided into one line per arg. >>> list(iterarguments('spam: str, eggs: typing.Union[str, None], ham')) ['spam: str,', 'eggs: typing.Union[str, None],', 'ham'] """ pos = 0 bracket_level = paren_level = 0 for i, char in enumerate(unwrapped_line): if char == '[': bracket_level += 1 elif char == ']': bracket_level -= 1 elif char == '(': paren_level += 1 elif char == ')': paren_level -= 1 elif (bracket_level == 0 and paren_level == 0 and char == ',' and unwrapped_line[i + 1: i + 3].strip() != '*'): pos_including_comma = i + 1 yield unwrapped_line[pos:pos_including_comma].lstrip() pos = pos_including_comma yield unwrapped_line[pos:].lstrip() def iterlines(stdout_lines, *, line_indent: str = INDENT, wrap_after: int = WRAP_AFTER) -> typing.Iterator[str]: """Yield post-processed help() stdout lines: rstrip, indent, wrap.""" for line in stdout_lines: line = line.rstrip() + '\n' line = line.replace("``'\\n'``", r"``'\\n'``") if len(line) > wrap_after and ARGS_LINE.match(line): indent = line_indent + ' ' * (line.index('(') + 1) *start, rest = line.partition('(') argument_line, *rest = rpartition_initial(rest, sep=' -> ') arguments = list(iterarguments(argument_line)) print(len(line), 'character line wrapped into', len(arguments), 'lines') assert len(arguments) > 1, 'wrapped long argument line' line = ''.join(start + [f'\n{indent}'.join(arguments)] + rest) yield line_indent + line print('run', [SELF.name] + sys.argv[1:]) help_docs = {cls.__name__: get_help(cls) for cls in ALL_CLASSES} print('read', TARGET) target = target_before = TARGET.read_text(**IO_KWARGS) for cls_name, doc in help_docs.items(): print('replace', cls_name, 'PATTERN_TMPL match') pattern = re.compile(PATTERN_TMPL.format(cls_name=cls_name), flags=re.VERBOSE) target, found = pattern.subn(fr'\1{doc}', target, count=1) assert found, f'replaced {cls_name} section' target = target.replace(INDENT + '\n', INDENT + '\n') if target == target_before: print(f'PASSED: unchanged {TARGET} (OK)') sys.exit(None) else: print('write', TARGET) splitlines = operator.methodcaller('splitlines', keepends=True) target_before, target = map(splitlines, (target_before, target)) print(len(target_before), 'lines before') print(len(target), 'lines after') with TARGET.open('w', **IO_KWARGS) as f: for line in target: f.write(line) for diff in difflib.context_diff(target_before, target): print(diff) message = f'FAILED: changed {TARGET!r} (WARNING)' print(f'sys.exit({message!r})') sys.exit(message)