From 48272448ad59c43683a20b0a9e0875626593b0ba Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 4 Dec 2024 12:01:00 -0800 Subject: [PATCH] [Developer Experience] Add node typing (#5676) * [Developer Experience] Add node typing * Shim StrEnum * nit * nit * nit --- .github/workflows/test-launch.yml | 2 +- comfy/comfy_types/README.md | 43 +++ .../__init__.py} | 13 + comfy/comfy_types/examples/example_nodes.py | 28 ++ comfy/comfy_types/examples/input_options.png | Bin 0 -> 19341 bytes comfy/comfy_types/examples/input_types.png | Bin 0 -> 16809 bytes comfy/comfy_types/examples/required_hint.png | Bin 0 -> 19286 bytes comfy/comfy_types/node_typing.py | 274 ++++++++++++++++++ nodes.py | 12 +- 9 files changed, 366 insertions(+), 6 deletions(-) create mode 100644 comfy/comfy_types/README.md rename comfy/{comfy_types.py => comfy_types/__init__.py} (75%) create mode 100644 comfy/comfy_types/examples/example_nodes.py create mode 100644 comfy/comfy_types/examples/input_options.png create mode 100644 comfy/comfy_types/examples/input_types.png create mode 100644 comfy/comfy_types/examples/required_hint.png create mode 100644 comfy/comfy_types/node_typing.py diff --git a/.github/workflows/test-launch.yml b/.github/workflows/test-launch.yml index 42f1dbe9..5d665d6a 100644 --- a/.github/workflows/test-launch.yml +++ b/.github/workflows/test-launch.yml @@ -28,7 +28,7 @@ jobs: - name: Start ComfyUI server run: | python main.py --cpu 2>&1 | tee console_output.log & - wait-for-it --service 127.0.0.1:8188 -t 600 + wait-for-it --service 127.0.0.1:8188 -t 30 working-directory: ComfyUI - name: Check for unhandled exceptions in server log run: | diff --git a/comfy/comfy_types/README.md b/comfy/comfy_types/README.md new file mode 100644 index 00000000..869851e7 --- /dev/null +++ b/comfy/comfy_types/README.md @@ -0,0 +1,43 @@ +# Comfy Typing +## Type hinting for ComfyUI Node development + +This module provides type hinting and concrete convenience types for node developers. +If cloned to the custom_nodes directory of ComfyUI, types can be imported using: + +```python +from comfy_types import IO, ComfyNodeABC, CheckLazyMixin + +class ExampleNode(ComfyNodeABC): + @classmethod + def INPUT_TYPES(s) -> InputTypeDict: + return {"required": {}} +``` + +Full example is in [examples/example_nodes.py](examples/example_nodes.py). + +# Types +A few primary types are documented below. More complete information is available via the docstrings on each type. + +## `IO` + +A string enum of built-in and a few custom data types. Includes the following special types and their requisite plumbing: + +- `ANY`: `"*"` +- `NUMBER`: `"FLOAT,INT"` +- `PRIMITIVE`: `"STRING,FLOAT,INT,BOOLEAN"` + +## `ComfyNodeABC` + +An abstract base class for nodes, offering type-hinting / autocomplete, and somewhat-alright docstrings. + +### Type hinting for `INPUT_TYPES` + +![INPUT_TYPES auto-completion in Visual Studio Code](examples/input_types.png) + +### `INPUT_TYPES` return dict + +![INPUT_TYPES return value type hinting in Visual Studio Code](examples/required_hint.png) + +### Options for individual inputs + +![INPUT_TYPES return value option auto-completion in Visual Studio Code](examples/input_options.png) diff --git a/comfy/comfy_types.py b/comfy/comfy_types/__init__.py similarity index 75% rename from comfy/comfy_types.py rename to comfy/comfy_types/__init__.py index 70cf4b15..19ec33f9 100644 --- a/comfy/comfy_types.py +++ b/comfy/comfy_types/__init__.py @@ -1,5 +1,6 @@ import torch from typing import Callable, Protocol, TypedDict, Optional, List +from .node_typing import IO, InputTypeDict, ComfyNodeABC, CheckLazyMixin class UnetApplyFunction(Protocol): @@ -30,3 +31,15 @@ class UnetParams(TypedDict): UnetWrapperFunction = Callable[[UnetApplyFunction, UnetParams], torch.Tensor] + + +__all__ = [ + "UnetWrapperFunction", + UnetApplyConds.__name__, + UnetParams.__name__, + UnetApplyFunction.__name__, + IO.__name__, + InputTypeDict.__name__, + ComfyNodeABC.__name__, + CheckLazyMixin.__name__, +] diff --git a/comfy/comfy_types/examples/example_nodes.py b/comfy/comfy_types/examples/example_nodes.py new file mode 100644 index 00000000..b6465f39 --- /dev/null +++ b/comfy/comfy_types/examples/example_nodes.py @@ -0,0 +1,28 @@ +from comfy_types import IO, ComfyNodeABC, InputTypeDict +from inspect import cleandoc + + +class ExampleNode(ComfyNodeABC): + """An example node that just adds 1 to an input integer. + + * Requires an IDE configured with analysis paths etc to be worth looking at. + * Not intended for use in ComfyUI. + """ + + DESCRIPTION = cleandoc(__doc__) + CATEGORY = "examples" + + @classmethod + def INPUT_TYPES(s) -> InputTypeDict: + return { + "required": { + "input_int": (IO.INT, {"defaultInput": True}), + } + } + + RETURN_TYPES = (IO.INT,) + RETURN_NAMES = ("input_plus_one",) + FUNCTION = "execute" + + def execute(self, input_int: int): + return (input_int + 1,) diff --git a/comfy/comfy_types/examples/input_options.png b/comfy/comfy_types/examples/input_options.png new file mode 100644 index 0000000000000000000000000000000000000000..ac859bbc0c15728e6f5464f9388f44d5e38dd650 GIT binary patch literal 19341 zcmaI81zZ$dw>>;`C`flpNh=`TjYvr-F_eUqbax9X3W6vNN;lHoNF&`P4MTT*XW)6? zd+&GO|D7K&FnMC{wbxqv4DVEx<*+cxFd+~KmV&&rIs}544S^tNqoaab{I$lo!5=|y zq@+|8q@-x<9NwC}u{MQ3m_K`e7Ljier)V>H@souRO(;z8b(S)bgbc+q!U)6EUxYf& zTvdnnzVpyO;`kU~)zlQ(mJ=9JT7X>;Ky!+)xHbP{jgH}(^U<%cz4=P&9heAg7C|z- zS)-H{GwZwQkHyE2<8=v<3fE*Hv`WU(#u-WSoM!U^^{|DdP5mHq_v8!5Lss`_1kum@ zT}~}@vouLFznuCmT4}YS2o@a|F#{V+m4nmH?p-m5Y!cv5js`;=@I{es# z>e{Fynf|L%!4nbzBGq8b-+O^KO*^)|&4*v&3Em^jaF$;^Mn!hFV%L*?g3Zjbm<2it=s_03+33~lV74MaKgFh09)3$}&SN#>=K)LR=HQ(Gs9 zl!K{}lj&<3*EdcUG;#{hRki)Gi6Iagh=R0)hTG)UjIYKQ=Vrd^3POz(i}zWNN?Wpu z-gydCtlO%ckzWa#>j^%JH{CL|)~c-f6*4{@mq^7xoWSt30rq`)c7@H-B|XXQJGUrH zcLrhkgS-+{RU+{D~mjk{8DO(&ZP$~}4wq6gpZd0(74W4*}M z;1iby7Xoox6G9wZaFJqx|NQ6b@ee%sAOBqaA9vk7Ld~zHrLaz`P{O{X>+E1;XuQOU zI{Zq&wmfW{mm3EalGQTNaWF`6s7Vw$SOaZLWHOL$N%G;JvBoX7A~>~_AI1W2rgpPd z&fqL@oXZTg=!ix6_OgfuUpI&G;pR5Vb>dYJE^lN5`WsIHiY6`YQkmBJ2BH@CNchN6 zbmaB@I`Fqn-$up`EozH4eN%YxslECk9UWa-{Eu$fHOj;T6TNlO+T|nL$I8k2@7Hq? zwjk3pbNF=AQ=WhF=V0R*4qnAwVOFqS)-lQSh|ec3U#_Tlb#s@;QaAZbx|GhEE3F-z zn+o;r^=gol(U}`cO5>9$@m9+5AsIne4T3OsHo2->s_N#F53l24&8bEgvU;Km_1{;0 zwnaWZjPje2<=$H^$@j9B66L|V|22?Le2=o%xGK~Kxu-pkbme3|b~5pB++m$u z>E!-57yKWZa-Lp$14n|5r*uE?ZC2Iu#YTvToIhKPU#H1&9dhXgS!Ph0Q_rml;dJyg zCT0-ZyKKbA{dh6IwZ!<|%a@4G4C$*%tI;6`WpkI$0|o31PQ&Id;=!U-W-=}8T zdD9^l`Mf=LWXL|13tv~ULE_-&dAYvU1{ZA_w)q(K_DEZyP45YM%7{gdt>pL`Nu&s! zU4NjxegZA(Lv5Fm>}-k;1@E4Jd=l+CkZ?rdIlkz8R}oBNHg z`)xm*pL7$#8Jl`0RMST5T2<2?_wjgq8N?{ldz-0QgH1|}lfP09WiPSk&KWbd0&XPA zm{Q9#={jf$KJ`-yKE?C<;7Sfc_j9t~4ou7im$+7PQ-#Z!9>UgmsNiyWK;kEoyyVb& zKWp?vFuU1)aAa9jH>k@5Ps4MJW3ooLzpVTDwZU+*LvkfVPP+v|khLS5jGxYo#?n-m zB=S3{e>YR2|LNp1W7Xyy<#3A|H9VQ!*Q;xMU(*}Adiitzj`;kuu*3XkBhq=B1;@<# z_dQ<%tVksMSy$KJNJ%sM+vamZ=3O-v)G{U$Y)ba7W9isfrq9tGD}K1dA?zgR@7T)N z_}Z?zU)hizY1&4PAWUDxWt3wvt#Q>fl-gNIWBHBqzLiXOMA<8ut6@|_7%RWVz^Z}T zVewi>wz%MP6n^Ux`V6+QvxO^0(YfU$NxILd(eSq)2Z4QTydLq&5hVZ-~+yd>n`O<{vor3M*w<#}iKN}8g(n#A+ zwGhC({{>?I3Cm^{X`QLsue;vIU3hwY(+r!iXh$lz$+-4CczF;A6QU6N6Xt!-!>%)c zNQ1(nsWh*a*5q`QFV)?bTeja!9|u>>u*!sn18;Bm$$Jr3oaF799+dS z6}9L2^d4>%^wdd;>(26Ax|x;p$uilek~X8m0%ESF%|AAgtT18#xCLtS zx!1x>xVVFjA;a#KLU@&@zYCHm%T0ucw(LSR-H>@B2i$n^hP08^{ubxkEcBAX!skLL zG=yAHUp?a%2`JgIa{aypo%o9x)R*#Ja;KxKU72s*Tuc{25xw-7N`=IqJ2-xqXx^(@ z`$Am57Il}oeym}Q!|10mCh|E^4Rh?aLyNR#bo?A)GP)FPE)vjVk+nr>^zc{_8 zY0hQ4fXOyKSpRcCZdX_}~s(K_=RKu?^uwxqFMe{jq=Ih*R9>oPP_xn7Y=F&GoiB^5Vo!cfF$E zN@`8Caw6Flwy*iLKJEJ}Qgsq~a6XrAGH8XFdN&iA9QW!!*W>eQfOvGnG}qDLK- zYYkuITu^qkebz<#D5=uU+()#eiI2WE(7=qnc&?*w)Fzdl28pC5&Ja5y18Z>WHM4h% zXk15aILbZ$MMHOu&0=q^o1U5PnA)tCuC!DA5U*+~v1ia}=ZL+7Iph5iE{fe^cfI36 zu7iC9YiQ2Sxunoe^S6%gTyG`#XCJ`|!zTiryZekSVV3$c`9*Xs754 z5T>hzmO7+GGzRgO4isI<$TR8Xr|J?{A=**M?);(6+dBDFu2qA$c`vT0q;z!B(#s01 zS+)a`g{n-V=Ge>}YI}M=%O%0v%cN%41E4Rp3{uU-yRbFJO^?Olt8SdYn+Wck%+}Qv zp*Yj6D&iDKXEk3X@XWh8a|Me-OuCB60G&&lKnZxDggr%I#$!5Hm z@*WWd;7$?xQ=|N$58T|2XRZ9UKlbU0Gxat)iCx<`uIC_`FrK*fCbVv5cdNc`pl%R7 z)y>XsGp~Hq+wkDK7+dH=vz*HZRSPdn z=9#(OgX_C9%8RBi-6?Sduu~yi7ZTHf4#V9|r_5yC-?=3vk zHP-vE!_HB-6cM|I!TWq`4EBk#OHqYtqwhr5)f;K#ewxU7D%}sb=acf13F_Mqd&I#m_VocB+>HYf2 zLp`7L8e2nNqBV0PmA>B+XCVo+<>xEVzpanF*z5g1v-+=yL~uRHz&6O=S^18O8H8Aw zp+&<+hx%eRd7-YZ;6O@BPVQ8@U}6a;gwadCTa4yi7_+<-3xwy!nN@V>$4_U&1POah z`ah)|i;@@Yi9uD1s6r)+-U%D~G6WA)vm?m2IuUnO3|a_;8I_OPbo(ir4j0>0l~Cua z`@TIhYK-fP6u3&5XoEZ^1T2bSH$+$#5E==}m?u>;%^>Qk*Ou2u4f26Pc zirhptWI?^*sZtGMP91u;IvQt6NWqN&qxA4XjyZl|O$22Gmqe2F@CqP4L2V*ylbPlP zHX`r3AD7|?vEpG-yYNm};xeuo&u57)-~Z_;+OZr)s+u2pu*A~0mzy3h9E#?&%v>-k zV;I2fa{WzN&kyM_$wXV+`xtjL`yzu-8ywuh{2!aK!v}?nJqGvosQcq^k<4?~iC;#_ zpBy*jH<^!rMAMr}Jbz=XQRA%ct7YBTUpspLC_J0r+x@Z6(|z)rK5uJks>E8q5 zeaE2h&s-M=DVz3wx?`I_rrF!;M^&#fTE6G?DX0(^U7p$1E|mpoTztEN^iegM8O&Vi zE7fl}OOJ>}Q2U$-v2Dtn)Ss{&lJ;@zCgsVHJ#I;#n)b}ND9iViY$+htn~EPWWC@kZ zv>@gYIq^>}y%0f4#ZG_Nt+r#5Hfk{wPm=F=y(Qv#1LOc2W9?3H4rH2pH&HCKcgw5I z|CswEvGj0C)brpR=!(sFXbT31XTG!@0aix#K+I{g@qxu+2jT2 z_6rrtEWXfdFN9vycXX^=H-(2Aq zcvYCR?c4MFw0`uymsZp~N5^U5ul1O$kh3AWLQ*W~#Rm>ln5{zruOiS{A7-xLY>>ci zd`1QSOB12`h$XKToJNKrvi=X5`)`Km{`*QwWaHy-I_$w*@e}wXZDT&2f3bbwB9ed; z7wmb>601THj1-|6cE&J9$pGkTwW*{fjVD0N#;Tg#C%FOODOrJy-isQ;&&JD~2oznk zj5ZE*S|N>mc;!2uNXgYM?rRH_FAvhW#&SL4TPu;49!D#$1Db1KkXd$u`8MbZCJe9_x;LTfS9OXF-*BWMSs;T0w>|1qvCyK_wU z(j$OkQ8@vy+ZSNa+TuyK4MKgEQTg=wL=dl-Wzd^d#k}z=+UuUN2{KTCFM^-MzKcuM1-?JoV-j$DbtB9SUJ3lnulh^RopYXpC z_$%mZ@+hYPqa|ywzDqMaa+4CB{O88t1XHAa%K9J5S|kt}VP5FFUo;~78!SA_#(w=F zRK({pqQ~L(d~n=rOU!n$`Dbo;eo6KuLfv{sdy+HL`?yTj+HJs}J5)2SJerRNP<}O> zkl})n#Fy{#I0JHfc4~=&UFY?Dau&-hlsvaxW`p5tw2i#J4}DK_Ml6R%M@wRD z1I!JqT0UDxt3hPAUSP_$-y@fpT{t^~&Qy7UlQhVQLe#4sgSaKU`C!sqpso~?M z`X`VPLfLPHPV1))&N7|ex1sRo(8j=j{?98C^}v_N&{s$F*qkpARq1wqGO2NK(%FUc z*|K0l9lq|YxO;p%SxpjA32osN3CPr+!Y=$CK5^wexHo0L^_cskNI7Nyfh;{d$Y+{Z zQW}dfg{b0!xRc)$p9@#S2aK6uN^4Ooeg*}drGp?Jn^&sX$fK^ zK9eadWnD@sh zq@y#0s7CHFt5~}UtpBC)KB=nq3Z=BV5_@=TehbyCELim6XB5$s&lZOCjrKoWb&Aad zy?apEVJ5{tqP@yXpQ8$x@-06pVSsj&sGQX~KEp0~XFT*y-;G2Cv z+dn%S@vLC958q%om0^*}ynC6S0Vahq8>`FDemd`j&)6=YtDNlPMrLY3-0Sff{Ob=Bmg zjZgVphIe6bC|fSBY51zsr!!ey!wKI+Ju$hNz+H5P(Rv;%E0G5zMOq5?SXif^Wmvjl z;)f!Qt-&gbyOM>!KI}IY^x($Z@$Yqz%-Z4Hi`-f4mARb*pG=K?ihrx;R$i!m;LEJH z&^D9M1w9`Ydwd_qOU=t~t{@Hu*#S~t<(E*c(q$}EDUWK3n7@@grT=L~k%i~Znn7E8 zrFo=<4_3|H&y9oDV0?V!1v>0AxL6ySRRfnS@^Zc~^$&9OOm6Yq$+l;2l zJ0*yKN}hrQ^JC`Po?N|H*VVnz zw#}T<&VShkyppt3T=koyLSN^9Ua@f30QC=3f5QW=J{W+_mu6ihwCSG|`(z%x<)& zwR{!ZHZ41OMP7!%%dTUB`5ju@rFmii78NO?$(-nRyI3e;XFX?eBiDD?f3ov9m#^*p z(kaaU8M^oOa`}l?L9i_GkZp%xg10brZ-Mr$G24SamdnUvJr~x^bXhqYzH}Pd7Clwx zj%wa2&9n6C{q+^9bfjGas-K=TdD*GUDvzGlnc(Qb>GiJ2tYTJ{E^`d9(NoJwyz7o&M)!;>^kr{VLT{*%{{UJ7tdG#$e+(}YT)@!>(I0WMFcDT<^ zs~>YIx-9N%=GmN+twI`Sky9|3&W|vwPES;rmOkE4F3q$yFj4=Za(x-B%^!ngsfShX zHVusCF{{zVDg3h;v^MW|r^(*1BFyB4lI8fvBxQ&evtz%D3%ES{zm2Iy-zbZXou819 z+w-xARnapRsRz4#@OKK%8lqxPhU2E{qvTw#WQ+;o$v#ofBy6ipxz56VO2_g}kGjG? z3%gEZ*hkb$#eoG+G<@E7Oy^fHFeW27o6btmw+*LRObBkRFEBNvjx5fZKbS9|Dm!PF z99Bv(s^LySZ2X6Qayc*z)MoC?bvE=}Qo4%OK1jXLn%60sQC`)aKQ47XgphE9#2{TngwOdXmqQN$uw3_4Hxx={#2SL0Kq1 z17iX+X%Fn$-2N&Qw{q!kw}_DSF@I3g$6V;UDzdpre~cHzsR(ix&|eX(yD@2qxDSxN ziec1Wtd!?)S3b3(f^jtj8eQ@NQ`_+DB0`hPsV4~!DM55%)IsN!|2`uFa^XAeZG)3S z5z_9WJmUPU#@Ev%BSA(6N4s2Qh(^!LvDp*GI8NSn+7?s?S!~lNt$e?D`(6T_&3E1F zYXxTz&0_t|uAaG&mor91Bv~N&B_SYURlb$G0_q=U0$X6I zh|dW-=Z@UPB3ZY8?kP?~JZArBTLnsGiZjm>Y5;NAXkS%u(^atp$!jq9xImIcDt{yW z8%;~eHoSF3?%}SBcFpx;L`9yaeSg9^{ZmvSmtRvpvjIk@xVG=n?2;Ooq@`OwDF!xL zdF60YEh#2LkLgDX5OL5YMhJg+Y_+2BkG3urOo*3W(k|L*)6bvQR#V?Q#s1!RiJ4QT zc)v&eOsuh1E?vJ{g_rht5dF1F$*Lo{^*9MHVwc>*q7~wYf@f%r4Abp?fNVE%Z>BCAPfZ6*#JiFF`Xb4Z7>eVPn1C z1*hvgo=$4QRImP^o98O|4rSm@q2B(WqR2gR7wXogKw0a!mN_;6GHligSdu$bmn6;4 zBIkFX^^G}fzFF>YyAS>dXR?1eI~DLi4?lCVf6LYOz*a^?%>l2JX}#$9=bM-J-1=b+ z*|SYH2G^K#y_2C+-P&$wIWC(&ls#^qyA?!F)?Ujb%m9nOR;(pv9PXs1`*QD1SAQ9BQu4gFuyx*7aAL0JhwCqSjxn+fP50CQ45J$+tKr zhK;rnUc{Epw|C{DXMIlGu8g=#Y%tZ(ad~ka{LEMFbUZe~SL_5Qq|BEe5n{;T0lHu{C9kkB5JM=9R_?8q6LhpbyePW;QPLBUA4+DqM!Mo zvQJp~chdMYkx(TIfk(#pfLe&kT`ITjLqAk5tRDsA*l?yqq;?LJL_4VsP!UD2e$;I9 z4H@cta*1&xoOUCjJNn>OIPp$9kLmYOE6)Uzc0XJ`sjy7EnafIk|6#wcQGa`9u0eR- zC-8&-G!PBsO$l*OGBjDWj@}3X^}t1*p`I=1jq^b#U)IKEJB7fStMEC&{r2~yfuA4B z8IN;`Zh^v&IQD^@&+ceLQ?J@7Tdt#+A%-&zw$7^b;%f{Ez z5*B|uKj$HX#>O9CS0FS|XkGU3L501kiI}+(&2RqL5o`3s4^FDT_X{%t&O)YYt#I^) zSc`hpcwz4*!v2AE$3Y%W^@#N;mX6qrf4U09s$xfcGqa&-DiM~~Hkj_IT9wK-HokP; z@18H}xoFDMwYrtllj!?#^r``}dr!ls8tubWSuHUjEEu}#HT%yoa_Zd?#7Ge4$+gf> za+vdG>M?Y89&8S(XbG9)5G)Qzs>^OJKfxSZ9PA(foXArvXFXHUG;UGi~K+}_$V=`wk)z0;>&7jHQ{xI zthgfVAB5HDSdg-2{U*4-@p3x)X8p)radUOP*s}~m!txI6#cxw8sES@>Cks2#bxfJW z-^fSdT<`a{lwHq4=V-?rv;2k#$v1&iq>Y(;5li(h*-PF*B7)-Kqv|LniI5>|Is;6D$ln)B9VySsm)&u*z#{E7GK)hlLp_TWK_ ztwG+Xs3-^|KR=(Dnc466>x~yx&VrQc>gqUnc!;4bgKxft?w?42G!s+}-)WvpE|sc+ zh(B)NN;|f#*?#)k%*=SD=NrjW>o|js=}LPfQ&UqelKYiDSLc3TU!@PyJbsKTBO@b% zSw8dln-bSIrGa&)mjD3!z3-zUSwAIraBvXB>a|4z$6(Z?B_$EdttSv8+M3DZL0G;* zk#2g9gWTHMN+TpRwZrvu(Be~UEUE;K7nMor#JnB9VVO>Z;Tr7FWUcY`#!^YOLx;Nvz z%u0Y66cpq{mLk%xud5r;)1!1&o0R{7>;vlh`Z~YUnrej<%AtpdSziJML^XGWnT>7B z?i%*d^{alq7LwmIUY-U^vF(iDm#<$Rf*Bhb8A0=l>bur$2lMmtzJxME#C_@y5Gs;7 zTu;1lK#E|8PZ<>pq$D1RsCo%CJWk_qBBq*Y%H;f_$nfXSA5~4w;DiJUccq+o*|eS*+;a$Z7<|US|$3^R+C>bOe|Y8{0pS3n913QF{-wPMf}dEl$uTipK4F;$uE@ zbsZ<*$k^4@_37I;j5=s!P!RH$FJB-KNhzsi`*g-=4Aek`M~@!Ol($zSF~j>v0USRN zId9ZaE>o$T<(hZ1X)#&(pT=}DDMZnPrx_TPSFH5Lqd~@NTnqcR187omaujXPSRXx- z09(dG_JQJl&4&8iuQ$NPpgF-_eK6w(w zhk`pea6cP@6Y|Xb)D+j*oTmEXN=-{^G#BQSsEn4cwaL3VXUK)TeKPpx4`R(sUy7nM zj}thHbY=n`Y4eo4xuxZ?%_PrDHliPynGac56lbBYUSZ^G-Ahy+%XL-(!(EPjp+=CZ zm0PEyE~3jc^yiNt)?fV?gP-aW*&4IIij!ds$wfC=eSc)2a6 zec{0X4=}n71h7pKIG?_M4<8=Z_!1N2ZsS_vS6zX0UC3wouI1n2WH=KrUL$1QP%zmIF{r3FV|2X*dKaYkT zHE*ZOe(?A(B2J(Oj>>JhTs#+QU=APHJUTs|dRuHg@kID!+L7NCM!^BC9XA4N{%(3& zkC`Cc&s{L8$*_^rXYZcZ#dgKM)B{^h*p4}Dx~kG?9S?wr&uI(w;!>p83MU>e*6Eyn zQe+?u`)djvri0g_#*M{|{K)9276Ac4!w^JUD;7(ud( z=If%>%wS94{B=$M9f|Ppa6=xl)S>lHQBeV(K7GPob;g&$RZvja-z+J4lAIsSz`!6c zFYiZ#4loyt6$1m~^dR}>{m*A#>FDXDIpPZ)JW-(l8E_xUif3yeLb|%UA6t#Gow?OP zA2d!7W@zOih+iuuvDX5dDmXY8dh})CTF)e@7BSl$4pPj7Y%HV=A zqhn#URoXAv%{L;j=~oj((KnfuO5h+D+T{M9*Xb||E z4<*wvF`)tUe_8J(;3P=N!ouP;s^H;KO@YItoDe1zifwd#c~-eoe{uV=&{`6(6A&)P z6(x|MwA|eIY0Pv0w4>;MBUA%Z;ma3-Gtd?R=Yk0A=oJE(!+nA$X20{jrGgml%+`rk z#P^y305O^T^#&dW$Oy!o(4gP1qjd3l%}OcoHa9mxP5QgUXEXWt$Q=Z8*xph{_fAY~ zZ1aL$Blxm@DwR8OPFaADhex9jWq@#$otY%*=EmP+R(c7wvSP)i;0F~7L{jnV-*^CF zdgA%|tqdaC0v?E6;ynAx5wkgIS++Qw{UT1xxBik;7=(8h?TaY-o^RanHSl?`uzMK_ zPBMXH{2ZCG1&b=o%4)1TnT<%7i#+%Cx`4SsM80E9e(UU%2gvFwK*i0%f_Zj!2AOxP zY>Z*mR#j7@MN@Mft6~nfc=Kj`%)W+}mKM5D$2wJRBc-JktLJi}iU4OKpi6-?II|N8 z5T2f%e!dBf8W|!m?ktx%B#}Gf5zH3YG{9-PxX#HyKu!*eeAM5QPskNuC-rJvdReEZ zte-YiALeVZ>gej8b&JFI*2xO+5wLsN+1c{XpUVI~lz!qEeejNlZ4+5lW4+7^+81w7N!OSq)}DX!mRP&g))s(R?r`$rdJGd#=| zrMS2_tDxZ6)3+Q5zN*c!!iRi(d?{T&hSqVYsUwlluy=t@OoIX{nC63(32Qry2oDc9 zS9lI!x)OAG2!e#+l{_p!LZ#;7tw{{vjkszl|0a%b`Uu}OaDi7UaM@txkdTq43`;}L z*NO-TKtvDlJ3Vzf%YL3DoV@$@>^0MdeBoI4Q5-;+g_clPw;UjT0lUW{4LoXnbP{j{ zau7T2NOek#h;a8RY%spfkgL(G^yuty?yZMJaEL4+uB3Foq_p(Pwr$vgNB-HfKpJ$> z$l?iWJAR5HoqSbh0@ZAdqN#1@1qk$Czka2prO_E~G*e<9d5CDRXo{C_bIW){5y7ug5OV^T36ugs2$w}WgfEK&Unwl>H#J4;n{HA{d_s;lEc zQp(B*QBY8P%Koy%DCX%_U@tE(%PT2KzkC@thJ&I;K#PuO{WRWd8|+ize6A`poG&#_ zMAbU2t6NW%W9j5;iOS|{r56?9KvV&r*?G%Dw%p$ss|8=mG~IAeGh}5^0cLyIx`vk2 z1Lh0J;r{03rB|UiQ#jq?(o(VeftkSNn=+ko>!$)reF*~i!1R|_Rt|v{$^jarsHh0| z#NXn0*pkS)lOR1KW0%x@X|>k<sfp-lUKRZ~hnT8q|aH8q3p-{Rc*hQ160H z?7pYqQc~V+PS#9!I3`%oAlu$PEX@}FFl|s(H#RqMsi`?#oBHGV z9)L0ehT#)4XIo(c-aIQSYmMuouA~G{Cx5iiKF#}R_=O6su<*1MNfUVSR1Fqfz|xkx zzq<0bgBo&6;v7~)Z-Egd3efAdBMALk(bKah<=e(hXJ9XWYDsi~<63b(YP zB4&@7W1%S8UHZER?t%1A7sWzt@$=Dw3j{hIGORyCLubqQx9UFy4;kU{P!=eAQ12iK-85^xFOxz2!rAPq8<3E=@mp^e~A8JHvl0D9j6`p zML_xkDg}7jE}7!|#LowZ*8Kir3)2VFA3l5-FEPCb!be&yhz7lA!Ww8HvrsEtHljRe zosQ@jcfW88dGKxIdf+!tI-)hl>xTfPNYBL7?azD11oDd?h@PehCk-2N(J(NeM9~8Y zK*!FG1(pOX-e<8$=5TMSi;3%L*j_PEi2J)JC;U@?|Guu8Ci~F7Lu7du@6^*KPp>ed z_)705r7HisL_5gy#`iirvoSUONr9T4o^HQA$qNEXTNJ1Z%mA9hD!qf1Ua4D3zzs3) zUJIr62?M;X>u7L%Jj`aQ+?kmV&a}Zd7D7_#4FbTpvLSDqr}4G=sj7y}K^Pz`PbM?V z%gYVAM*jS9=#I?4gH8lu^qDJgw$h~IV;j(Ov8-`wRi%}9Nj)-95%CSkmN8V}xu)^)=7Z22pw1<+M_Ma3xd8_&L! zuk;wers(YK6gM(Tt!?mh_B+l+{gIJD$H355D0AhVSOexY*TRL1dlYHJ60?Dt>(5-- zdZ%z2=h;@ksh!~$|EZFGC6thsmcoRu8pgIZ!S3+2Kf-ODlIEeAzPAtD)d4&5T)+QU zTETG+TtdkDU`%P)AhNV_Wh5jJK#BA6svW~qPU;qM7k#{8_JSn_P+LhyM{baNpFV%~ z2XsV~H~co*5PJ=U?YruxqnixpiNWV-01X-bOoJZw?wy2-OZi;Hh!qdnkDosQ-%aiS z<$|9A8)$t{83CIf-#(VO$s>wD>FwXK;JaIeu4l2lojsk>;b5G z^hm)+Uqb^qeURC8Oio6|eS2rNU~Ds0J-f)Xn+ULT$l7kwYT8w+0L9t97B&Ub_<0o;!8?9*4{g zFYlg z{xdE9yJboTvl$*4S%}ewuJ0LN$Hc^l)I(kK%Vum&Tql@ATbhcq?(|4rfkDH^>FH_O z1NZ6t`~P6>f@k(nF1y+t+Hc5Z*zu84l_lm<9UN?XtQQc@Jbv@j zeafb@r>7N&4$sThM}bX#fUcI}-O+*bum2U(Di(kKFXD%^@7|Xz#%a)RR*GO(kP5~G zhT;^#eQOm$;5rEKEgJw?4`ttfs+sS;6RF@x6djOlevXyma9>18O%3_edZfNKdf z)ul9iK*c5**f)NAI&4b8AW(sp>Mvj| z{zzy=ZhOY=)0Tj|0;;v#_&qKT9Sr5kw-n$uj#vWj1%9bTguAv|20|TlQVm=8708>EF zuB~wYLmYusQMm1S0+Z3wA_A5|T?ih|UD1_58kw0R1bQ7z%NUdg0J~r=pz=U3fEZnB z4!%diXG;OJNk`Ic?hUFwRh_w-dGqGYXKf^T1%*A#-ZW<8t?^=D@`^h-@x*c(U?(aw zsSE=hfLIwPhI_MqhbC*-dk~Ui-T|={eKgf|8^H3!q-<9 z@bwc2UI!`6se+AiM(Tp4`#TD+4g zYALw?q9P&m3OeuS8V*ZRp z?NiNvcN6Vg#Ne_Ny!?N=0t1F$?ymmFU4!hQo1mS9_8BuKxk2evt2t$@J*FU>0!-iH7+4WRoSF?`lIMtZkSiH4qw8p_HaDR>AmWkR?A z&4`aMQ?7x|{CB_N(Oc}>Izh$;Iw_2ds1p+tJ{6O1{;zWFch&<-nIZ?wk)X|o^F_+P z-crOh;)l?%d%4BNk;i)ya1`Sw9z+!i{rIZX?6>~Otw4}~DZ=Zcib_YSROnwvmv%!g zanNRffJV)yFJF)$%&e?ZV9?W=)I>xfckZT>r?QGZ7B6u60%(8;|9w$exVd?H+=5Y^ z!+jfI)j!=s_zk9;atay$U!GWo%4%;<&sIsz+zjRhcD=~Rj4G0=xH8bHVE2`jf@IS_ z7m@=fqBovTDvBP+C%{#XHYiiTRY1Mq; zjJ(_?Uhk{pN{<8pA3#xP(D{8XE2kYS0TWD0`Ut8%eB@3dUrP&| zqynMgY@Skm@m6#DYb`)5U}NmJfJk*^y5p-5KfnrMOqadAq0Jrzj0 zPxSAslJ6RmMUTlo0Mmz@&lVYyAncAel<^#Cpd8gwL*q-WFW@|(-;RM>aK}Dr{mNe* zr!jjtSNtpLmv;m%s37oKUO_dr?b1*%^X*PYo;f1)JT7BVg_)}xAIOgJX~gM9xD0KsjbKK!q{YZvlZ*Q-&yZd0%KIPvvPRhzE_mU^eAAl#YBriPzGz_}g!|Cww@XnXty?|M1 zYigzf`3!I!=xQLRYDwS0-#Xw$iio{p6g}{TZmH8@N_K}KJKtTx&`aRJ`QX-;b%Tx| zf_b<8f40m6W^Bz(=jdH(C_&Qk`(6tJolk*{1{!{>A-6LU{-(rOm8%;?UubLY+6W#L z4EVIXJUSUU`4#L~>yAVNcNcOhtOcMJv>5j{f%L1oe@kmIAcEVme77}|^5fCgpPj8q z{Wk^$?d|Q#V%jz~`NCU`b@{)35dg;{U&}LQ#<N60>8^p9aYcmQ|d0g)es1>}&&oP5}Xd|853>__?!M_RGXBccp+0 zKfLK?09p_ra0J=E%BI8ZmAz5jx4&08*x6%{N^~zuswz&L7YT1eM2#ur0X0l}{?ED- z;2je3@?qL;NPcT;7LYY3;SA7F0jFd@rWJOL18MrAeNsqdu+J0H`8UyT!V9+d}<)Fj@kBzFZjzs|vY;o#{C9Oi#bg+S@=E!yq`oG{tf}=t! zw+)!*5C~{(z+V6`(Mzb2|84qaB?kuu(eUyT0#6AdnC$%k0ymcw6h51mfE1*C^aum= zZGZ}fcfa5b+AD5uZX+&|Psz!g4XdIJ7i39eE*03W#Mlf9l29nslP6F9E+8hENY}8s z!Wl*$XjM&EgVRRbe{pJnu6u22>W(F|_AHkZd;-b#fB8_m5oa~THDXR{1AZb3K$iv> zSX;B*9bW(uCILG?+U)G@HI)IMU;5PS{LfM1B8HeS59ug+QCt8RUm^ZalVx5yS$J|UMaKWO2OLrs&RpKQ865Ux%J zz#BWOL?%=#x_Rd4m(0(LgP@VBO2f?^{f{6-Vf_~dZP_Lp>}333{u0s(_v+ujhZh3u z|Di>P`Vd~Y(!`CPd;{Db!cV-zNA_X=DJVan`w9p=kd|2^mcPo&(^68zMP&(O=ouJ( zcMuQ~Hd*ihuMm`3_h6;|19xabyXo)_F=!crxP%{jIJ+rxz2UIAw5RAW zxC$||V8eZA^9URY!fduDD0%&cutdHwmAz6vg&Yawn7;!r^RO9D1Cb`+g{b=Y+RsA!2qD|VOVlXP&*ogXnXcIxi95MqZe^Y?vzg#2-74K`ClMoOwyrgJ2@VN_@-n*4QcN4pLx z7DMLcsXu6;Atfa>>l6$Y2Tv8j?-S;HnuGoraHlUiKY1`l?}bb;WY5?>6O7y30RL@a zwU1IEYHsH%eVGosCQD3`-pT1{<>dD9=S?&Sjygg@Dir(VhPu1N>FV4WoV)0>Pk1PG z5ThvL(_m2XcJ3H4_sw_9G93}!I89~(dWF)`(i{yIea@#y@F!oU!JphYJDWr-xVo}Z zf3G(%Fpz}+^T~aw5F@ag*?Jr&dCKcfI^UFJ4-O7=fOETEg#ejZFuH~)YlxdL+l>Qq^sH3 z*f`gTwOolOPtDDZO-_;pLm+z0_fL=@5U`5i|H^`G4NLTI3okFdCPUT!B#{?1I3Qjw2O*6Dr*lW!X=(`cJBYWpx3hKf zbJVl5CajazbcNtEnWyMpcIaAmh@$VXe?3)x@`|5g^v|EDr%#{0CJ6zhW5l-NXr-6D z@Pp~A@HS&;{K?K-clq{1n+K6^Ud2kLej*|!uG_6GE>2xvf1_XT^{S?(25g;Xm0qk9 z7om}vS-X%+>BM1q_i{;+u>0cG_1fS=TgKaXc$4wQ(#S|cyFgFi_`s|*`zxnGQdbpd z6KF6K{Hmcs8nJiwlp}Zbe~lP z6JzPw+1M;BEfvdcSidQ;e3oB3+8hJZQe=#-cHN=kqk?ZzUulc#`180ceIRG=MHOta zP(`v5MLeR4%zeq`F@$bC6>CKDNE8+ouA_1*bj>zQXK{C=5tGu%DN%;W@97dUK~6c=HHHVFI+Eu={DWo z+OjYMeU|?rMe29`EGVjv9huG zAPIqReDP%laeWuk5L&{!kTx$ETJKyvP(z1^8|*QYC}L2Fe$WK5!32K}K1+K=S`vLK6UB7V0+u literal 0 HcmV?d00001 diff --git a/comfy/comfy_types/examples/input_types.png b/comfy/comfy_types/examples/input_types.png new file mode 100644 index 0000000000000000000000000000000000000000..27e031ccf9c32958da0d41164b567e252c1b0c5c GIT binary patch literal 16809 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 literal 0 HcmV?d00001 diff --git a/comfy/comfy_types/node_typing.py b/comfy/comfy_types/node_typing.py new file mode 100644 index 00000000..056b1aa6 --- /dev/null +++ b/comfy/comfy_types/node_typing.py @@ -0,0 +1,274 @@ +"""Comfy-specific type hinting""" + +from __future__ import annotations +from typing import Literal, TypedDict +from abc import ABC, abstractmethod +from enum import Enum + + +class StrEnum(str, Enum): + """Base class for string enums. Python's StrEnum is not available until 3.11.""" + + def __str__(self) -> str: + return self.value + + +class IO(StrEnum): + """Node input/output data types. + + Includes functionality for ``"*"`` (`ANY`) and ``"MULTI,TYPES"``. + """ + + STRING = "STRING" + IMAGE = "IMAGE" + MASK = "MASK" + LATENT = "LATENT" + BOOLEAN = "BOOLEAN" + INT = "INT" + FLOAT = "FLOAT" + CONDITIONING = "CONDITIONING" + SAMPLER = "SAMPLER" + SIGMAS = "SIGMAS" + GUIDER = "GUIDER" + NOISE = "NOISE" + CLIP = "CLIP" + CONTROL_NET = "CONTROL_NET" + VAE = "VAE" + MODEL = "MODEL" + CLIP_VISION = "CLIP_VISION" + CLIP_VISION_OUTPUT = "CLIP_VISION_OUTPUT" + STYLE_MODEL = "STYLE_MODEL" + GLIGEN = "GLIGEN" + UPSCALE_MODEL = "UPSCALE_MODEL" + AUDIO = "AUDIO" + WEBCAM = "WEBCAM" + POINT = "POINT" + FACE_ANALYSIS = "FACE_ANALYSIS" + BBOX = "BBOX" + SEGS = "SEGS" + + ANY = "*" + """Always matches any type, but at a price. + + Causes some functionality issues (e.g. reroutes, link types), and should be avoided whenever possible. + """ + NUMBER = "FLOAT,INT" + """A float or an int - could be either""" + PRIMITIVE = "STRING,FLOAT,INT,BOOLEAN" + """Could be any of: string, float, int, or bool""" + + def __ne__(self, value: object) -> bool: + if self == "*" or value == "*": + return False + if not isinstance(value, str): + return True + a = frozenset(self.split(",")) + b = frozenset(value.split(",")) + return not (b.issubset(a) or a.issubset(b)) + + +class InputTypeOptions(TypedDict): + """Provides type hinting for the return type of the INPUT_TYPES node function. + + Due to IDE limitations with unions, for now all options are available for all types (e.g. `label_on` is hinted even when the type is not `IO.BOOLEAN`). + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_datatypes + """ + + default: bool | str | float | int | list | tuple + """The default value of the widget""" + defaultInput: bool + """Defaults to an input slot rather than a widget""" + forceInput: bool + """`defaultInput` and also don't allow converting to a widget""" + lazy: bool + """Declares that this input uses lazy evaluation""" + rawLink: bool + """When a link exists, rather than receiving the evaluated value, you will receive the link (i.e. `["nodeId", ]`). Designed for node expansion.""" + tooltip: str + """Tooltip for the input (or widget), shown on pointer hover""" + # class InputTypeNumber(InputTypeOptions): + # default: float | int + min: float + """The minimum value of a number (``FLOAT`` | ``INT``)""" + max: float + """The maximum value of a number (``FLOAT`` | ``INT``)""" + step: float + """The amount to increment or decrement a widget by when stepping up/down (``FLOAT`` | ``INT``)""" + round: float + """Floats are rounded by this value (``FLOAT``)""" + # class InputTypeBoolean(InputTypeOptions): + # default: bool + label_on: str + """The label to use in the UI when the bool is True (``BOOLEAN``)""" + label_on: str + """The label to use in the UI when the bool is False (``BOOLEAN``)""" + # class InputTypeString(InputTypeOptions): + # default: str + multiline: bool + """Use a multiline text box (``STRING``)""" + placeholder: str + """Placeholder text to display in the UI when empty (``STRING``)""" + # Deprecated: + # defaultVal: str + dynamicPrompts: bool + """Causes the front-end to evaluate dynamic prompts (``STRING``)""" + + +class HiddenInputTypeDict(TypedDict): + """Provides type hinting for the hidden entry of node INPUT_TYPES.""" + + node_id: Literal["UNIQUE_ID"] + """UNIQUE_ID is the unique identifier of the node, and matches the id property of the node on the client side. It is commonly used in client-server communications (see messages).""" + unique_id: Literal["UNIQUE_ID"] + """UNIQUE_ID is the unique identifier of the node, and matches the id property of the node on the client side. It is commonly used in client-server communications (see messages).""" + prompt: Literal["PROMPT"] + """PROMPT is the complete prompt sent by the client to the server. See the prompt object for a full description.""" + extra_pnginfo: Literal["EXTRA_PNGINFO"] + """EXTRA_PNGINFO is a dictionary that will be copied into the metadata of any .png files saved. Custom nodes can store additional information in this dictionary for saving (or as a way to communicate with a downstream node).""" + dynprompt: Literal["DYNPROMPT"] + """DYNPROMPT is an instance of comfy_execution.graph.DynamicPrompt. It differs from PROMPT in that it may mutate during the course of execution in response to Node Expansion.""" + + +class InputTypeDict(TypedDict): + """Provides type hinting for node INPUT_TYPES. + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_more_on_inputs + """ + + required: dict[str, tuple[IO, InputTypeOptions]] + """Describes all inputs that must be connected for the node to execute.""" + optional: dict[str, tuple[IO, InputTypeOptions]] + """Describes inputs which do not need to be connected.""" + hidden: HiddenInputTypeDict + """Offers advanced functionality and server-client communication. + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_more_on_inputs#hidden-inputs + """ + + +class ComfyNodeABC(ABC): + """Abstract base class for Comfy nodes. Includes the names and expected types of attributes. + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview + """ + + DESCRIPTION: str + """Node description, shown as a tooltip when hovering over the node. + + Usage:: + + # Explicitly define the description + DESCRIPTION = "Example description here." + + # Use the docstring of the node class. + DESCRIPTION = cleandoc(__doc__) + """ + CATEGORY: str + """The category of the node, as per the "Add Node" menu. + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#category + """ + EXPERIMENTAL: bool + """Flags a node as experimental, informing users that it may change or not work as expected.""" + DEPRECATED: bool + """Flags a node as deprecated, indicating to users that they should find alternatives to this node.""" + + @classmethod + @abstractmethod + def INPUT_TYPES(s) -> InputTypeDict: + """Defines node inputs. + + * Must include the ``required`` key, which describes all inputs that must be connected for the node to execute. + * The ``optional`` key can be added to describe inputs which do not need to be connected. + * The ``hidden`` key offers some advanced functionality. More info at: https://docs.comfy.org/essentials/custom_node_more_on_inputs#hidden-inputs + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#input-types + """ + return {"required": {}} + + OUTPUT_NODE: bool + """Flags this node as an output node, causing any inputs it requires to be executed. + + If a node is not connected to any output nodes, that node will not be executed. Usage:: + + OUTPUT_NODE = True + + From the docs: + + By default, a node is not considered an output. Set ``OUTPUT_NODE = True`` to specify that it is. + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#output-node + """ + INPUT_IS_LIST: bool + """A flag indicating if this node implements the additional code necessary to deal with OUTPUT_IS_LIST nodes. + + All inputs of ``type`` will become ``list[type]``, regardless of how many items are passed in. This also affects ``check_lazy_status``. + + From the docs: + + A node can also override the default input behaviour and receive the whole list in a single call. This is done by setting a class attribute `INPUT_IS_LIST` to ``True``. + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_lists#list-processing + """ + OUTPUT_IS_LIST: tuple[bool] + """A tuple indicating which node outputs are lists, but will be connected to nodes that expect individual items. + + Connected nodes that do not implement `INPUT_IS_LIST` will be executed once for every item in the list. + + A ``tuple[bool]``, where the items match those in `RETURN_TYPES`:: + + RETURN_TYPES = (IO.INT, IO.INT, IO.STRING) + OUTPUT_IS_LIST = (True, True, False) # The string output will be handled normally + + From the docs: + + In order to tell Comfy that the list being returned should not be wrapped, but treated as a series of data for sequential processing, + the node should provide a class attribute `OUTPUT_IS_LIST`, which is a ``tuple[bool]``, of the same length as `RETURN_TYPES`, + specifying which outputs which should be so treated. + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_lists#list-processing + """ + + RETURN_TYPES: tuple[IO] + """A tuple representing the outputs of this node. + + Usage:: + + RETURN_TYPES = (IO.INT, "INT", "CUSTOM_TYPE") + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#return-types + """ + RETURN_NAMES: tuple[str] + """The output slot names for each item in `RETURN_TYPES`, e.g. ``RETURN_NAMES = ("count", "filter_string")`` + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#return-names + """ + OUTPUT_TOOLTIPS: tuple[str] + """A tuple of strings to use as tooltips for node outputs, one for each item in `RETURN_TYPES`.""" + FUNCTION: str + """The name of the function to execute as a literal string, e.g. `FUNCTION = "execute"` + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#function + """ + + +class CheckLazyMixin: + """Provides a basic check_lazy_status implementation and type hinting for nodes that use lazy inputs.""" + + def check_lazy_status(self, **kwargs) -> list[str]: + """Returns a list of input names that should be evaluated. + + This basic mixin impl. requires all inputs. + + :kwargs: All node inputs will be included here. If the input is ``None``, it should be assumed that it has not yet been evaluated. \ + When using ``INPUT_IS_LIST = True``, unevaluated will instead be ``(None,)``. + + Params should match the nodes execution ``FUNCTION`` (self, and all inputs by name). + Will be executed repeatedly until it returns an empty list, or all requested items were already evaluated (and sent as params). + + Comfy Docs: https://docs.comfy.org/essentials/custom_node_lazy_evaluation#defining-check-lazy-status + """ + + need = [name for name in kwargs if kwargs[name] is None] + return need diff --git a/nodes.py b/nodes.py index 260bb5e1..1cb4b5a5 100644 --- a/nodes.py +++ b/nodes.py @@ -1,3 +1,4 @@ +from __future__ import annotations import torch import os @@ -24,6 +25,7 @@ import comfy.sample import comfy.sd import comfy.utils import comfy.controlnet +from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict import comfy.clip_vision @@ -44,16 +46,16 @@ def interrupt_processing(value=True): MAX_RESOLUTION=16384 -class CLIPTextEncode: +class CLIPTextEncode(ComfyNodeABC): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(s) -> InputTypeDict: return { "required": { - "text": ("STRING", {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}), - "clip": ("CLIP", {"tooltip": "The CLIP model used for encoding the text."}) + "text": (IO.STRING, {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}), + "clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}) } } - RETURN_TYPES = ("CONDITIONING",) + RETURN_TYPES = (IO.CONDITIONING,) OUTPUT_TOOLTIPS = ("A conditioning containing the embedded text used to guide the diffusion model.",) FUNCTION = "encode"