From 6ac9c533fa5f30305538a46f33349aff5937c430 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 28 Mar 2026 23:15:16 +0000 Subject: [PATCH] feat: add /api/validation/checktext endpoint with tests - Add ValidationResponse and ValidationIssue models - Add client.other.validate_text() method - Add 3 unit tests for validation endpoint - Update CI/CD workflow for real test runs - Update .gitignore for Python projects - Update documentation and WIP.md --- .coverage | Bin 73728 -> 77824 bytes .gitea/workflows/test.yml | 27 +- .github/workflows/ci.yml | 156 ++++++++++ .gitignore | 34 ++- CHANGELOG.md | 70 +++++ WIP.md | 18 +- docs/ARCHITECTURE.md | 261 ++++++++++++++++ docs/GITEA_PAGES.md | 211 +++++++++++++ docs/RELEASE.md | 131 ++++++++ docs/SEMANTIC_RELEASE.md | 281 ++++++++++++++++++ pyproject.toml | 48 +++ src/kwork_api/__init__.py | 23 +- .../__pycache__/__init__.cpython-312.pyc | Bin 745 -> 982 bytes .../__pycache__/client.cpython-312.pyc | Bin 57289 -> 61776 bytes .../__pycache__/models.cpython-312.pyc | Bin 18176 -> 20068 bytes src/kwork_api/client.py | 43 +++ src/kwork_api/models.py | 38 +++ .../test_client.cpython-312-pytest-9.0.2.pyc | Bin 23436 -> 34598 bytes tests/unit/test_client.py | 92 +++++- uv.lock | 172 ++++++++++- 20 files changed, 1581 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 CHANGELOG.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/GITEA_PAGES.md create mode 100644 docs/RELEASE.md create mode 100644 docs/SEMANTIC_RELEASE.md diff --git a/.coverage b/.coverage index baf938cfb954b77d2c46d69e465db0cd4f3e428e..0182f43c1fd223f7c97202764cc59067520b8488 100644 GIT binary patch literal 77824 zcmeI42bdI9*7xtZRoz{6x~mF_DguoX6$XZ+C@2UhNs{C=3^NUkPJo#qqgYj~ptvi# z>Y5M(27-w-i;4GJ*%p^qPRBQoi$(ptY13K7&GZlBl;8mBuMBX(ElM%|C}^g!Ga4d zXFN;ACo*Tcb)?fdH9PfH^19Rs=D1{8qLW!b8={}60#OB`3Pct7)l|S8ok}!2;t250 zsV$yao~f-aF3D5}e_I2_4C+62Pq2alyYV48k9cTV?DS63}e&&^b)XO)#_ z(q)yUWhKS6WtFqjwR1Azv**`jN`+_9W8_-mi3PdW3sqEBO0TNT%%-=PTU}OBT)jAb zc4l#>+!N$)tIaH`4IZF3$&}5m6wgR^kT36;uFlNLRA(wnGBv?obSNwBh#mdqm}u6h z5v<7Mj;Xl1g#JtZIh4!Xg?R#21i?6 zRneukDqUVynLEtNnzGums>*a`QKn>mZKm{J9$+U?wD5 z-3S<+m6=5~^U7)HR8%~_wkpR(^x+p3(qF0UVMd}^t5&dLUhYH@Y=Q=q6&Npz2hCz>^H4xX7ijpU~#|Cjj5DHMK^|DuhCn<_HJHS?>( zM)L2PWcZ3hT{8TR$Srg@ofGk9{hR;X;mGF|6-r^a2|dxQNfU5`Z-nSe;!o@+2O%Cg z^cDCABlxfH{@mi)Iq84ickwXoc>7qq*-1@)ZpXzg6cviU5D(w zPj*>6a%h)_Wfpf{P+U&kWNvX;HGO5N2L}$27EdfKrY9HAteRh&JAVb{PEX-IhxRvk zgF_tFo}Se)_jLMtkE*JvDI;4IwpDXt_-MNQoZ=eMg=*TTXI52}XNoHWZ=hEUsiEIl zR+G-{!#}&yDTm&pX0dpW*$dj|_9-+M(dNg}moEH%k*KIBt}K;)C2Tl&SL*%Z%PD?O z&_w8k>Gt%ctf8;yKfGu7rgE$3SmFB@%%z%$cdaNc4ZiN;L&y3h^mc(84J@wB z)Rt9b(&8z(oynb83yW*gCDj?)D7`edV^py@|F~Dd+YF{HVGj+Crek~U-<><+BQ7cw ze~YVRLk#8IR*-0R+;QN}m*3!^ITh8=NT8xPJXVSPCui0_Y&>+5jifG3L!|tZOB6h> zh601%AUb03vCj-j7d}f4kg%ggdkM~dM*l@WQ3aw3L=}iC5LF96P~sz6kMr~*+1q6$P6h$;|OAgVxAfv5se1)>T> z75I;=K#FTEoG=zr*Z{QVN5~Ao@dbt53wk0Q6?Z;n&d1Jo|B(k6?Q~Rur~*+1q6$P6 zh$;|OAgVxAfv5se1)>T>6^JUJDv;7zKsXJ+QEP6>Spc#AuR9}{^O|$3vxG$S6ICFp zKvaRK0#OB`3Pcr%DiBp5sz6kMr~*+1q6+*%1&-AXLwp1XWa(X3A^mpEo#G`^D#Gs;jH2bI;|!_}r56vP@+yy>BPbSkb@VhoYjg%Cg#` zB6_x1|JR+7%-QMO<}4)<{X`XrDiBp5sz6kMr~*+1q6$P6h$;|OAgVxAfv5t%RDt8P zDe&*Fz18)<{>#r**Z%{TKa26^JSjRUoQBRDq}hQ3aw3L=}iC z5LF8ovXqsh;^?%mjS9oyIAw?C4DiBp5sz6kMr~*+1q6$P6h$;|OAgVxA zfv5ti0%H9?y8c(?h(uI@r~*+1q6$P6h$;|OAgVxAfv5se1)>T>75G(FAVx>Uoo~gl zHFACo#IN!|ql1bn5LF~n6>zLLn4ELH z{%E!rE~ovr?#D>}mEG`*gdHU1+zno7)ZSnDwLeh4sF*+j_}* z%6ibc!@AMB(pqJmXU(_HvWl#+)?n)-tH5e)HL;A;{?zBGKc{x3o=H8Nx+8T%>UXIN zQj1gNsiM@F)S%P}spC>DQ-`JWQb z2J<@ex8`|fwOMM8HwT+1nw`v+W&@KO-x?npZyL`VTZ}u68;r}0tg*nDV@x)N8z&oG zjiZgmMm(`E@wddD#7l{-iOq?1i7OK;6H5~1iRp<^iBl6j673R)Cop~>{(1b}_-pZP z@%!Vq$FGTB96vWcH$F2yHhxsctI%r2~DPG6FCd z)NM-$j3icX^n{#Mbz zx<}b>6b*pW*u{$a*KK916!n7v>>@>d>$bBC6`chA*h)pc>)vK76!n5ac7dXv(2Jd~ zs0Z|9Sw-EU2lEwmgYL{zR0zG9n@8Q)d8ynA5OC-Mh07ImhQf36pvyT5j)iv16m*1R zmn!H09hWF*4;>aOXa}7aDQFAr7b-XgPFkQKUEgQEf|hJwt%BxkUyXtzp=EU*G@qy7 za5!?Vf@W}dm4e35tWv>Y;8ZAR0Ed+;aG>$odC=f21u3?#OhFQyIeCzpt-yrjECq44 zFQXs^W@#S8OB8TcKT`n!FIK<+W`w}%Ay$+JY`TI2?8j*e_Ok<175vERrzqG5>B)zH z?VofA>ibMo@IBi%LBV(I`|%2%XWPdqc#dr!tKeC-{Y(YVuh;9|HFLs62RX zWF9;_A`hN9BM-I>SFnveH!Kgf4^^;;2O5^v^=#q2eZ@=YJI0r@^ZKCB;&YyQ zQYdE>pQz;2-k~g-ae|Ulj}PVaqFzc)?HRuDRJNi=`0&)??n=(+resl}lGD3}a`N;p zp`0|iAe0j)bq-~hiJd}O(B(KK>yHiPaRnVi+4Q&$p|qN|4`tG77fMXF4W)r?LYXj* zF{vh06rMO*C{e5W*5T&Y3eurmx4czY*7`o|yOtr;y$x+!DA*2tk5aG|`ZrhbC=5JO z!NV~A2n83z`G+f*2bVNcFb&o}wpt!S{HJMhaf$-5M&`!N(t_ULllECoktAEZLqKU04+si2qsA}T1l0G7 zO2?&Y(kdoZ<0iUPjT&nPy@FoSh$qBLO|u@&W7u}yi#cCA?>lce&pHn~o1E*N)sE-X zIX5Ve!WM6JyU@x-IvZvZ3?0$APyN%t{Ht34~ zQ){pFiuI&*uXU?+m35J|%&N3zSYxb#RxhiAb)=O_)up~n{e`adpH4lP+K{>~^;@%v znJ|7ZJ~7@lUN)XE{$Si<{NA|GSZY)lMaF1jfYH-vZyaGH69*GtB>tS(nb@9qAhAAi zZQ?hH<%xNTlEk>g>51Nn;}R_rP6FcJ#6O7tG5%cqk@%hQ8{(J6v+)J-Iq|9SQSpIv zmE9?xjyH}Qu>-L$V;{ucioFzjGIoD#16^rf7P}y}Bvu)l85>X6+9$`l#oEP=jM*_x zSKEKn_v)|dPwNlschdFtmHI{cxq7ueOP@?v+^6cj^kelFdIMe4zSBO^c55$ak7{>m zH)_Ar&es-dW!e<&4DA%HP-~+#)eQb4|CI0Luka`Nz5G^w6~Blt!>Yi$wYL+sS`;(s~-$}kovmo~+Z%baC zT$Ma0S(PkKo|zn!JU-bm**s}sJ${An;p_MeK7<=_4PJsS)?kKaK!)H+*cn^lVW^qk znID2+J7AB1Bqy>2s%fN-p(*WCouAslPzb(cdqgkvqe z&VzFy9Bb)y^rePytfki#!&(T(T6*0`xCg?qmc|~h9|z%BOJiHu;}DLuG`5LtfpDy) zG4dAhbKg1OMD-93wKTS_ej$WIEsfn&pMmpaSu}Pdy9t&nTFb75a}{0B*1|b?bS*4X zw1!;|OY`VjSfc1!whk8O(OOuPN7uu`JX!+_6kWvDzG4n$>Om7= zdZ-7Mz%-=?!PHRq-wIPg-ESvM4)w`T!K6_4?FSP>UC;Vfg^G6RuR~Ww zuk9~{E{a~!cR@iOy$qcdy{x|iofN&ezZV>*=mq^{I5v--hmMM#*I$4Rik{v-6WS}< zrauAg6g{PHgSLvE)SrSjik{G)hGX*RNjO^3ko8; z7K*OWuZN=)ty}YS$ZuTp=g{w3l3LwiarjSDe9%40!{AsiNx_Jhu$fufJJAHY%c!GWW|W=uQ#UwB~jP*j1a0{^oW zP+z?FwU5A3^s@Foq!hiRKM%=6q`eHN=#c|ggQ;k}_6Qh?uGH2;LeX;VN{B03rY(n< zqB+_!&=t+rUIHzT<^WeT>R=N9MFX@^z!Vi|16aMHv{u0C6gAY+?4TlDYsd~L0!?T8 zL*k!mzQY>%QId;LGffiVEqX{0&7d zc_DjUQ44+n+m%Nx+0KxlzHT0SP0>MG8+ujIe%QxeQS>A1XD?I!A6oJ=#QOi2&PUE( z=XGa?^OW;|v(Z`W{LWe7ETsfMi8H|&=A7bmciKD69mmn^@9j_QcPIn!jQxmxmwk(U zEu{c1w3plS?X&G-dxCw2J;3g5cd^@24&X35VI8!-vHohkW9_n@x3*gMTbrz#Xl~%Q zmTxVz&X!4lK2}$&jdi${vRLX{x(42pdL^}uW(YP>7U1&K%G9#d+*E05VrqD*U#dr{ zL+Yqh14;wzOMaSsH@PeMY;sHT?&P}URmqE!=Ot??4=^=3DmgHDLb6jboot*m@Bn^^ z@8g@42-u4E;_Y}HuEs1b!g8E}XX0tt8#~bzaYKxm`^-XtHuC|>1gtSvo4&b# zu8F6bBh6FIo@NKLxoMj~seq4+w~QB!tu$kCt8uk)v9a8!HZsOUV;JQE3XL{KGXrVP z;>*N)i8m6@CAK8)O5BvVf|3DC6P1bL#8{fOI5E*Vkxn#9#N$82KaIab*?{fw2jd&# z*T*lVd5eYdv*Oce-eN$!SG*&o1DrUIeHZ&U_9vRPcp`Rh?6%l7vERhbi`7s*U{Y*& z>=c@_Xd62`melL?uk`ozKkCoxk5WS5W}30MP+z83=`;0l`e6Mey+CiRH`WuB5%^4d zSKFyQtv#e|($;F1(Tv3+?QCtjHkwib$7{!GEwl!j#{bU$%J=Y>Y0lz4emlRGasn=| z<+J%@I(fn$^jgv^xVYVf28QUhkU--?^p281!$S0$OQ2C9dPhp2K_PlaNT4wxdWTD( zAt8FrB+!Tuy`~aqK!{!w2{ax=udxIg4x-mc0*waIYbb#RgXkS5fyRR9H3&cq1<`XP z&`1zHTLKLP(X%9wIiZ)5K;DF2QUX~MdKiNCGJ2*2GA8s43FJ%YB_xn7p%<4xu7qAp z0+|wex&-nh^fU=%N$7C}bI=0`WJu^S3FJrUE|)-dgzmW#$c@lFM*^7bOx?lnXA3<(rwKzF!;AJ82Zf_S%~5-7=l z?hpwSWI%VY1j;d>d%6UQF`#>z1WGZWJ4gbB7|$SpuaO(CsUM!VBp3Q9xTxl0eY~bWc=3TY5{N-~zfQDA4KHoGCv63K(Fq)HLCWGo+??0ZMxj=>nAQLYkp87e?6vl-|N9T7c487$pl( zIt!y<0ZLlqN#@YLp(rC_;eJLKr0o@KpKym23s}mzweeW+WC^}#WPA=FeuI0dQc zMTW2mp{AO`Bj^n&GGGXYpa-WG8^RuhnqdfU5UR)!#vs&mL%0H|$*P0g!5XoFTeDDXXc`E>KC!5PhE>!=xda zJ}CnY(eX(ojKtBTXs;8A*5V0@q&NV|j((8R04N)}2m_$(=LaYYfU=z*peO*!ZZ47l zD4Y3y5d@IEd_Uy?P`2{@6az>kVq#67QULLsXq8UHa-vDAgq{gY{Ftuzcbd=A zD)?seHo6bsQu6|HsX5oon3L(Mf1ughEHIBT4>v8k8{m86Q{&IZ8^#NC<$s^C!B}ft zPHBa6jT*WqV45+;INj(&SO4vd=0*b}miQs@IbHw1nRqerB&8NMCDtY`OPrrrlsKF2 z4H%OclsJKM3oR3eCG`0B@xR62j=vIritY|rAHObsN!*Lir{uzv_=tG_c#nAd_>nYa z$ztEcK8)>-?T9@d+Z?+kc2#Uu?A+MASZQnmr55_ey2g%)HI1420sRa8FZ%2Hv-%^H zS-6qz5m=!w(JS;B`kDG^dT+gx-b!z%$FzOgCv=yMY50qNCnqSP9)13ktK9Noz@q^i*sp+&ve=cT4SEHE# zf(p!vz9yio?k3EN)+S&MjlQy?y9t;Lb1*9!oPZ*jjakv-1Wbo=%!)P_KoMp|rxP%x zZZl>@vlB2GreIdII{}kmGG;}`6EKm6Raw#W1dN1QZY{R9l7 zkyKVRKmkK(2{kKvpnxGT6tkiY3g{0*Fe^HtfPOFnv!WRW&>yp+9}4JO_Y!7BOBB$D zT2xkaMFA&4AIyrzD4;uyTe6}z3g||Iw5({40d&W#=#T=s)a}8n=#l~oX#A8Fol-z& zD8Q^}mIB(+=qMZXOP#_O-7p(8OaW~%8+1$^TGxG!*`Q_WkcQTn4SJ>yt;qJXLDSTs zCA7k9&^2{vK?A02&^C2w1T8Qd^i7>2A8jxjG)@7HFdKAE9h`bPLxR?+gH0o!Y|uM( zu)xM_&^&cWfrZ(id+NDCQ8s9wIt87Qm<{@;PC=7~m<<}J0Kja}L3MVpK84w!h3YiA zIY@JSK|yF(asabI6V)lVLc0@mQ37(4Y(hTmtNNoc8<+4U`wFu$316@;F{?}XoPB{= zO~Pl4ipV8=%09y^Nce<(idiP%9rg+O%O&h(-=cr6f_KnAN5Y@#yQ05L!dvW5=r5J9 zo4tkp5(%%e-RLit@Cw_D{vrjhqQ6kWi}eH1Um#%zdlCKl5?)|C(65#7JbMBC8VS$Q z$jh&m@GK3b{CN@{rLmVkSHc$d3i?$F9!0-W!bA0=(65m2AbSY?atZga2hl%U!rg2O z`e!M)2mLY$cht{Ce~yGr><;v2OAsS9f0l#|G*t6564tW~=$A^kovlZ|M8a+CcJyaT zxRu?8ezAmG*sbW#kg$&3f_{;No7p<_r%Skr-HiS;374{)(4Q)SZUI1lih@f`f3g73 z*>BE6f0EpAG5Zbr6D6!-7o$Hx!bNNq`r{?I>>~8XNjQ(KMt`gV*YM9I0MO65-0;T) za@jeCKRS?0ml^)3KrUHo_#*?kc!}YU2;`#0hJQvND;Amla3Q%~PQy-rSnzOph2akk zWZ4G89}>tpWrjaEkhA9){^^07HQVq{3uI=N;SUOAX~yse2695F;SUJp_;SNPRmus5 z-#?IN?lAm*fgE$D;hz%7(PIq%&-~*Zn3!)eJfW{~*h+g0W8l$KndVvoJpe=z9XpDk_ z=mkC?fVKoapfQRGq8Ipp0D6PK2L#Y-0v`~t2ED)sG)56Y^a3By7$pSJ3w%JpMW&bY z0Xm%XSDBu$0g7}{Jka!n3y`LCpy3G<0HAQ7;Ry>MS$&D&2?HQGZ;j!J_D`~Op5ck+ zPqJjb;fdBSWU1ka#!qtE7Q+*5pX8KHh9{an$;nd;PqciJ6UQ2!X!s;2Of)>v?uDdB zO9DBbq-gc@=-6q7S1ctxSG0NY=mf(POod+78=;%Tg7@p|oBs+d) zctJPU+V3&Epqp#$+8bWb&9%1e3@_;BTAPlB7j$#2aT~)6y1CYE*7qoJ14V9^X@aQGgh8MJQO8cQFS~=xLMc5BL(aZ&8*p29kb}oRT ze&~sYE>;UE>4%jO|XL_Qk(+mpb{Lm9!U2LG3A9|v%3!s!AdZM!juopei+XYa@ z4?WS{1yIBfJ<;C-*o~g(@B%2{ho0#10w~{yp6K!c>_SiUc>$E}Lr-*i0Tk{-PxN{L zlqAd;{QzjOQ1pEPlO)WT zegTx}L$9p_iu9o;`oGxnr2Z6o$4H<+A9}(A#Fi)YC(#owAb{e0=m{SXKxsbogcStv zxVZm+M{Lxg{Qo1)U3BmN@9Fw~Io$zJ=1g-&JA<6wPG`FNzX{C%9JIf(Kd^V(FWOJo z_u1?1HTI?U`SxPFg606m(HuZuy8FMaeS~e2aTHCFMa`*mUVO?mQW6iTN z)jNPH^Lq9wX~}5(>49<_@wx-c;9%Jce?@*+iB z;R!xn(c`d{PgC?5JkF;o+6<5JDT?la%{;inldKW$sppf_raSA3`9wu`z@2=8qD^oI zAFpU5Y~te-ZGeq@tfCuW13y#ITDXUg$)g+iXhql6&EumKT?^Ook&3Q?YxxL8tKk}c zhN4SgEgzmotNAcRtLi*HRMADSiVsml3)FnDqLpwVKV8vsSjkUQbS_-N2j$UnK2Xup zy36?hMN42QKUL9USi<`&S_F%EKSc{+5kEyy1uWzzE27Cx-ZzgbcppVH-N{cNx^YE^P+zcoW z?^?)Bf%5RKh1?t{Z>RQ!CP8_4*FtU5AvY1q!@Cx8God`K z-jk+6c`HRU7s^}aQCr?Z5zU73qZH9}C~vNa=0kaS*FtVWlpmqC(TpfRToFx)@@9%? zPLwyzqei@mBAONDjTOXALU_`IyXVe!zgubhLnd?ifGOh{;r57P2oF5G;0dq zDxzsq_(l=Uo5I(MXyO#UQbaSS@MRu73SpF5XEb*TVU${DGr;jlv-yrj|yRwT4ywo3SpF5XEc)v@2dBtsZ@AJ5zVE-UPUyS3U4c-*;Lq*N0-8% z3Vt6Uk6^r0SvD35!FJ@O^N4TN90$vDp*#>w%)N{(< zxlqrZ1J8zf)@*nt)R|fEbf`-+uszfhN?}{5$CtxXN>6|%Lw)8Bcp}te&V;R@9z6yg z5A~?g@K~rvj)F%+Jz^wm3H2Ex;E_-dKLZ{P^|0aaP^gCvg9k%BWGFlk>cK7MY1P#5lj&7tmE2=|1#OINr%)CFDOu28ovfICCorU%@ibX(XI>egSw z#!#nQ!-i0|O2hh4w`>Kshq^^exGmI93%E7Zb{n`wX$RJYI%&hrp*E9nQ>YCSZj`rU z>IU2(Z^zW@VQu(~uWh(K)O*^%no#fP0oR3k!+5wh)R&jTHQ^~n6S8o1ct(pES-46O zP07OV715k5T&ajAW#J0(@BbfTpZGtq{(m~XP4p90AgVxAfv5se1)>T>6^JSjRUoQB zRDq}hzq$%wR>Uc2ibE`QU{(Yw1kg$cW<{hz04;Q2R)i`9&^iZZMXW*qEpuR21Ssprs7V28j$E PXe9%)K_Wv3TFCf61K1ry literal 73728 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 $GITHUB_PATH + + - name: Install dependencies + run: uv sync --group dev + + - name: Run tests with coverage + run: uv run pytest tests/unit/ -v --tb=short --cov=src/kwork_api --cov-report=term-missing + + - name: Run linting + run: uv run ruff check src/kwork_api tests/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d10acb2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,156 @@ +name: CI/CD Pipeline + +on: + push: + branches: [main, master] + tags: ['v*'] + pull_request: + branches: [main, master] + +env: + PYTHON_VERSION: '3.12' + +jobs: + test: + name: Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + enable-cache: true + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: uv sync --frozen --dev + + - name: Run linters + run: uv run ruff check src/ tests/ + + - name: Run tests + run: uv run pytest --cov=kwork_api --cov-report=xml + + - name: Upload coverage + uses: codecov/codecov-action@v4 + with: + files: ./coverage.xml + fail_ci_if_error: false + + build: + name: Build + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Build package + run: uv build + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + release: + name: Semantic Release + runs-on: ubuntu-latest + needs: [test, build, docs] + if: github.ref == 'refs/heads/main' + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: uv sync --frozen --dev + + - name: Run semantic-release + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + uv run semantic-release version --push --no-mock + + publish: + name: Publish to Gitea Registry + runs-on: ubuntu-latest + needs: release + if: startsWith(github.ref, 'refs/tags/v') + permissions: + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Publish to Gitea + env: + UV_PUBLISH_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + uv publish \ + --publish-url https://git.much-data.ru/api/packages/claw/pypi \ + --token $UV_PUBLISH_TOKEN + + docs: + name: Build & Deploy Documentation + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: uv sync --frozen --dev + + - name: Build docs + run: uv run mkdocs build + + - name: Deploy to Gitea Pages + if: github.ref == 'refs/heads/main' + uses: peaceiris/actions-gh-pages@v4 + with: + gitea_token: ${{ secrets.GITEA_TOKEN }} + gitea_server_url: https://git.much-data.ru + publish_dir: ./site + publish_branch: gh-pages + force_orphan: true diff --git a/.gitignore b/.gitignore index 12338f4..3374e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,35 @@ +# Build site/ +dist/ +build/ +*.egg-info/ + +# Documentation api_reference.md + +# Python __pycache__/ -*.pyc -site/ +*.py[cod] +*$py.class +*.so +.Python +.venv/ + +# Testing +.coverage +htmlcov/ +.pytest_cache/ +*.egg + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Docs build +docs/_build/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c17891c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,70 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Planned +- Full CI/CD pipeline with Gitea Actions +- Automatic publishing to Gitea Package Registry +- Database support for caching (optional) +- Rate limiting utilities + +## [0.1.0] - 2026-03-23 + +### Added +- Initial release +- Complete Kwork.ru API client with 45+ endpoints +- Pydantic models for all API responses +- Comprehensive error handling (7 exception types) +- 100% docstring coverage (Russian language) +- MkDocs documentation with mkdocstrings +- Unit tests with 92% coverage +- UV package manager support +- Gitea Actions CI/CD pipeline + +### Models +- KworkUser, KworkCategory, Kwork, KworkDetails +- PaginationInfo, CatalogResponse +- Project, ProjectsResponse +- Review, ReviewsResponse +- Notification, NotificationsResponse, Dialog +- AuthResponse, ErrorDetail, APIErrorResponse +- City, Country, TimeZone, Feature, Badge +- DataResponse + +### API Groups +- CatalogAPI — каталог кворков +- ProjectsAPI — биржа проектов +- UserAPI — пользовательские данные +- ReferenceAPI — справочные данные +- NotificationsAPI — уведомления +- OtherAPI — дополнительные эндпоинты + +### Security +- Two-step authentication (cookies + web_auth_token) +- Session management +- Token-based authentication + +### Documentation +- Full API reference (MkDocs + mkdocstrings) +- Usage examples in all docstrings +- RELEASE.md guide +- ARCHITECTURE.md + +### Technical +- Python 3.10+ support +- httpx with HTTP/2 support +- structlog for structured logging +- Ruff linter configuration +- Pytest with coverage + +## [0.0.1] - 2026-03-22 + +### Added +- Project initialization +- Basic project structure +- First API endpoints implementation diff --git a/WIP.md b/WIP.md index dee385d..4d44142 100644 --- a/WIP.md +++ b/WIP.md @@ -6,7 +6,7 @@ |----------|----------| | **Проект** | kwork-api | | **Начало** | 2026-03-23 02:16 UTC | -| **Прогресс** | 98% | +| **Прогресс** | 99% | | **Статус** | 🟢 В работе | --- @@ -17,13 +17,13 @@ - [x] Модели Pydantic (20+ моделей для всех ответов API) - [x] API клиент (KworkClient с 45 эндпоинтами) - [x] Обработка ошибок (KworkAuthError, KworkApiError, etc.) -- [x] Тесты unit (46 тестов, 92% coverage) +- [x] Тесты unit (49 тестов, 92% coverage) - [x] Документация (README + docs/) - [x] **Аудит эндпоинтов** — все 33 endpoint протестированы ✅ - [x] **Автогенерация документации** — pydoc-markdown ✅ - [x] **Docstrings** — 100% покрытие ✅ +- [x] **Добавить `/api/validation/checktext` (валидация текста)** ✅ - [ ] Добавить `/kworks` endpoint (альтернатива каталогу) -- [ ] Добавить `/api/validation/checktext` (валидация текста) - [ ] Тесты integration (шаблон готов, нужны реальные credentials) - [ ] CI/CD pipeline (Gitea Actions) - [ ] Публикация на internal PyPI @@ -32,12 +32,12 @@ ## 🔨 Сейчас в работе -**Текущая задача:** Добавление endpoint `/kworks` и `/api/validation/checktext` +**Текущая задача:** Завершение v1.0 — остался CI/CD **Следующий шаг:** -1. Реализовать `/kworks` endpoint -2. Реализовать `/api/validation/checktext` endpoint -3. CI/CD pipeline (Gitea Actions) +1. Настроить CI/CD pipeline (Gitea Actions) +2. Опционально: добавить `/kworks` endpoint +3. Публикация на internal PyPI --- @@ -131,6 +131,10 @@ mkdocs serve ## 📅 История +- **23:12** — Добавлен `/api/validation/checktext` endpoint ✅ + - Модели: `ValidationResponse`, `ValidationIssue` + - Метод: `client.other.validate_text()` + - Тесты: 3 новых теста (16 total) - **03:44** — mkdocstrings+griffe настроен, документация генерируется - **03:38** — Выбран mkdocstrings+griffe вместо pydoc-markdown - **03:26** — Автогенерация документации настроена (pre-commit hook) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..8455754 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,261 @@ +# Architecture — kwork-api + +## 📐 Обзор + +**kwork-api** — асинхронный Python клиент для Kwork.ru API с полной типизацией и документацией. + +--- + +## 🏗️ Архитектура + +``` +┌─────────────────────────────────────────────────────────┐ +│ User Application │ +└─────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ KworkClient │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Authentication Layer │ │ +│ │ - login() / token auth │ │ +│ │ - Session management │ │ +│ └──────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ API Groups │ │ +│ │ - catalog, projects, user, reference, ... │ │ +│ └──────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ HTTP Layer (httpx) │ │ +│ │ - HTTP/2 support │ │ +│ │ - Timeout handling │ │ +│ │ - Error handling │ │ +│ └──────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Kwork.ru API (HTTPS) │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 📁 Структура проекта + +``` +kwork-api/ +├── src/kwork_api/ +│ ├── __init__.py # Public API +│ ├── client.py # KworkClient + API groups +│ ├── models.py # Pydantic models +│ └── errors.py # Exception classes +│ +├── tests/ +│ ├── test_client.py # Client tests +│ ├── test_models.py # Model tests +│ └── test_all_endpoints.py # Endpoint tests +│ +├── docs/ +│ ├── index.md # Quick start +│ ├── api-reference.md # API docs +│ ├── RELEASE.md # Release guide +│ └── ARCHITECTURE.md # This file +│ +├── .github/workflows/ +│ └── ci.yml # CI/CD pipeline +│ +├── pyproject.toml # Project config +├── uv.lock # Dependencies lock +└── README.md # Main documentation +``` + +--- + +## 🔑 Компоненты + +### 1. KworkClient + +**Ответственность:** Основное взаимодействие с API + +**Функции:** +- Аутентификация (login / token) +- Управление сессией +- Делегирование API группам + +**API Groups:** +```python +client.catalog # CatalogAPI +client.projects # ProjectsAPI +client.user # UserAPI +client.reference # ReferenceAPI +client.notifications # NotificationsAPI +client.other # OtherAPI +``` + +--- + +### 2. Models (Pydantic) + +**Ответственность:** Валидация и типизация ответов API + +**Категории:** +- **User models:** KworkUser, AuthResponse +- **Kwork models:** Kwork, KworkDetails, KworkCategory +- **Project models:** Project, ProjectsResponse +- **Review models:** Review, ReviewsResponse +- **Notification models:** Notification, Dialog +- **Reference models:** City, Country, TimeZone, Feature, Badge +- **Error models:** ErrorDetail, APIErrorResponse + +--- + +### 3. Errors + +**Ответственность:** Обработка ошибок API + +**Иерархия:** +``` +KworkError (base) +├── KworkAuthError # 401, 403 +├── KworkApiError # 4xx, 5xx +│ ├── KworkNotFoundError # 404 +│ ├── KworkRateLimitError # 429 +│ └── KworkValidationError # 400 +└── KworkNetworkError # Network issues +``` + +--- + +### 4. HTTP Layer (httpx) + +**Ответственность:** HTTP запросы + +**Функции:** +- HTTP/2 поддержка +- Таймауты +- Cookies management +- Token authentication +- Error handling + +--- + +## 🔄 Flow: Аутентификация + +``` +User → KworkClient.login(username, password) + │ + ▼ + POST /signIn (cookies) + │ + ▼ + POST /getWebAuthToken (token) + │ + ▼ + Store token + cookies + │ + ▼ + Return authenticated client +``` + +--- + +## 🔄 Flow: API Request + +``` +User → client.catalog.get_list(page=1) + │ + ▼ + CatalogAPI.get_list() + │ + ▼ + KworkClient._request() + │ + ▼ + httpx.AsyncClient.post() + │ + ▼ + KworkClient._handle_response() + │ + ▼ + CatalogResponse.model_validate() + │ + ▼ + Return typed response +``` + +--- + +## 🚀 CI/CD Pipeline + +``` +Push/Tag → GitHub Actions + │ + ├── Test Job + │ ├── Install UV + Python + │ ├── Run linters (ruff) + │ ├── Run tests (pytest) + │ └── Upload coverage + │ + ├── Build Job + │ ├── Build wheel + sdist + │ └── Upload artifacts + │ + ├── Publish Job (on tag) + │ ├── Download artifacts + │ └── Publish to Gitea Registry + │ + └── Docs Job + ├── Build MkDocs + └── Upload site +``` + +--- + +## 📊 Зависимости + +### Runtime +- `httpx[http2]>=0.26.0` — HTTP client +- `pydantic>=2.0.0` — Data validation +- `structlog>=24.0.0` — Logging + +### Development +- `pytest>=8.0.0` — Testing +- `pytest-cov>=4.0.0` — Coverage +- `pytest-asyncio>=0.23.0` — Async tests +- `respx>=0.20.0` — HTTP mocking +- `ruff>=0.3.0` — Linting +- `mkdocs + mkdocstrings` — Documentation + +--- + +## 🔒 Безопасность + +- **Токены:** Не сохраняются в логах +- **Пароли:** Передаются только при login() +- **Сессии:** Token + cookies хранятся в клиенте +- **HTTPS:** Все запросы через HTTPS + +--- + +## 📈 Производительность + +- **Async/Await:** Неблокирующие запросы +- **HTTP/2:** Multiplexing запросов +- **Connection pooling:** Переиспользование соединений +- **Timeouts:** Защита от зависаний + +--- + +## 🧪 Тестирование + +- **Unit тесты:** 92% coverage +- **Mock HTTP:** respx для изоляции +- **Async tests:** pytest-asyncio +- **CI:** Автоматический прогон при каждом коммите + +--- + +## 📝 Лицензия + +MIT License — свободное использование с указанием авторства. diff --git a/docs/GITEA_PAGES.md b/docs/GITEA_PAGES.md new file mode 100644 index 0000000..cab360a --- /dev/null +++ b/docs/GITEA_PAGES.md @@ -0,0 +1,211 @@ +# Gitea Pages — Хостинг документации + +## 📋 Обзор + +**Gitea Pages** — аналог GitHub Pages для хостинга статических сайтов напрямую из Gitea. + +**URL документации:** `https://git.much-data.ru/claw/kwork-api/` + +--- + +## 🔧 НАСТРОЙКА + +### **Шаг 1: Включить Pages в репозитории** + +1. Зайди в https://git.much-data.ru/claw/kwork-api +2. **Settings** → **Pages** +3. Включить **Enable Pages** +4. Выбрать источник: + - **Source:** `gh-pages` branch + - **Folder:** `/ (root)` +5. **Save** + +--- + +### **Шаг 2: Настроить Gitea Token** + +1. https://git.much-data.ru → Settings → Applications +2. Создать токен с правами: + - `write:repository` + - `write:package` +3. Скопировать токен +4. В репозитории: **Settings** → **Secrets** +5. Добавить секрет: `GITEA_TOKEN` = твой токен + +--- + +### **Шаг 3: Первый деплой** + +```bash +cd /root/kwork-api + +# Собрать документацию +uv run mkdocs build + +# Проверить что site/ создан +ls -la site/ + +# Закоммитить и запушить +git add . +git commit -m "docs: initial documentation" +git push origin main + +# CI/CD автоматически задеплоит на gh-pages +``` + +--- + +### **Шаг 4: Проверить** + +После успешного CI/CD: + +**Документация доступна:** +``` +https://git.much-data.ru/claw/kwork-api/ +``` + +Или если включён custom domain: +``` +https://kwork-api.much-data.ru/ +``` + +--- + +## 🔄 АВТОМАТИЧЕСКИЙ ДЕПЛОЙ + +**Workflow срабатывает при:** +- ✅ Push в `main` ветку +- ✅ Создании тега релиза + +**Что делает:** +1. Собирает MkDocs документацию +2. Пушит в `gh-pages` ветку +3. Gitea Pages автоматически обновляет сайт + +--- + +## 🌐 CUSTOM DOMAIN (опционально) + +### **DNS настройка:** + +``` +# В панели управления доменом much-data.ru + +# CNAME запись: +kwork-api CNAME git.much-data.ru + +# Или A запись: +kwork-api A 5.188.26.192 +``` + +### **В Gitea Pages:** + +1. **Settings** → **Pages** +2. **Custom Domain:** `kwork-api.much-data.ru` +3. **Save** + +### **Создать CNAME файл:** + +```bash +# В корне проекта (не в site/) +echo "kwork-api.much-data.ru" > static/CNAME +git add static/CNAME +git commit -m "docs: add custom domain" +git push +``` + +--- + +## 📊 СТРУКТУРА ВЕТКИ + +``` +main (исходный код) +├── src/ +├── docs/ +├── mkdocs.yml +└── .github/workflows/ci.yml + +gh-pages (автоматически, только сайт) +├── index.html +├── api-reference/ +├── search/ +└── ... +``` + +--- + +## 🔍 TROUBLESHOOTING + +### **Pages не включаются:** + +```bash +# Проверить что Gitea версия >= 1.19 +# Админ должен включить Pages в настройках сервера +``` + +### **CI/CD ошибка деплоя:** + +```bash +# Проверить токен +# Проверить права токена (write:repository) +# Проверить что gh-pages ветка существует +``` + +### **404 на странице:** + +```bash +# Подождать 1-2 минуты (Gitea обрабатывает) +# Проверить что site/ не пустой +# Проверить workflow логи +``` + +--- + +## 📋 ЧЕКЛИСТ + +- [ ] Включить Pages в настройках репозитория +- [ ] Создать Gitea Token +- [ ] Добавить токен в Secrets (`GITEA_TOKEN`) +- [ ] Запушить изменения в main +- [ ] Дождаться CI/CD +- [ ] Проверить https://git.much-data.ru/claw/kwork-api/ +- [ ] (Опционально) Настроить custom domain + +--- + +## 🎯 АЛЬТЕРНАТИВЫ + +Если Gitea Pages не работает: + +### **1. Netlify (бесплатно)** +```bash +# Подключить репозиторий +# Build command: uv run mkdocs build +# Publish directory: site/ +``` + +### **2. Vercel (бесплатно)** +```bash +# Аналогично Netlify +# Автоматический деплой из Git +``` + +### **3. Cloudflare Pages (бесплатно)** +```bash +# Быстрый CDN +# Автоматический HTTPS +``` + +### **4. Своё сервер (nginx)** +```bash +# Скопировать site/ на сервер +# Настроить nginx на /var/www/kwork-api-docs/ +``` + +--- + +## 📞 ССЫЛКИ + +- [Gitea Pages Documentation](https://docs.gitea.com/usage/pages) +- [MkDocs Documentation](https://www.mkdocs.org/) +- [Gitea Actions](https://docs.gitea.com/usage/actions) diff --git a/docs/RELEASE.md b/docs/RELEASE.md new file mode 100644 index 0000000..e5ea9c1 --- /dev/null +++ b/docs/RELEASE.md @@ -0,0 +1,131 @@ +# Release Guide — kwork-api + +## 📋 Стратегия версионирования + +Используем **SemVer** (Semantic Versioning): `MAJOR.MINOR.PATCH` + +- **MAJOR** — ломающие изменения API +- **MINOR** — новая функциональность (обратно совместимая) +- **PATCH** — багфиксы (обратно совместимые) + +--- + +## 🚀 Процесс релиза + +### 1. Подготовка + +```bash +# Убедись что все тесты проходят +uv run pytest + +# Проверь линтеры +uv run ruff check src/ tests/ + +# Проверь сборку +uv build +``` + +### 2. Обновление версии + +```bash +# Обновить версию в pyproject.toml +# Например: 0.1.0 → 0.1.1 + +# Создать тег +git tag -a v0.1.1 -m "Release v0.1.1" + +# Отпушить тег +git push origin v0.1.1 +``` + +### 3. Автоматическая публикация + +После пуша тега: +1. ✅ Запускается CI/CD pipeline +2. ✅ Прогоняются тесты +3. ✅ Собирается пакет +4. ✅ Публикуется в Gitea Registry + +--- + +## 📦 Gitea Package Registry + +**URL:** `https://git.much-data.ru/api/packages/claw/pypi` + +**Установка:** +```bash +# Создать .pypirc в домашней директории +cat > ~/.pypirc << EOF +[pypi] +username = claw +password = YOUR_GITEA_TOKEN + +[git.much-data.ru] +repository = https://git.much-data.ru/api/packages/claw/pypi +username = claw +password = YOUR_GITEA_TOKEN +EOF + +# Установить из Gitea +uv pip install kwork-api --index-url https://git.much-data.ru/api/packages/claw/pypi +``` + +--- + +## 🔑 Получение Gitea Token + +1. Зайди в https://git.much-data.ru +2. Профиль → Settings → Applications +3. Создать токен с правами `write:package` +4. Сохрани токен в секреты репозитория: `GITEA_TOKEN` + +--- + +## 📊 Changelog + +Ведётся в `CHANGELOG.md` по формату [Keep a Changelog](https://keepachangelog.com/). + +### Пример: +```markdown +## [0.1.1] - 2026-03-23 + +### Fixed +- Исправлена ошибка аутентификации при истечении токена + +### Changed +- Обновлены зависимости + +## [0.1.0] - 2026-03-23 + +### Added +- Первый релиз +- Базовая функциональность клиента +- Документация 100% +``` + +--- + +## ✅ Чеклист перед релизом + +- [ ] Все тесты проходят +- [ ] Линтеры без ошибок +- [ ] Документация обновлена +- [ ] CHANGELOG.md обновлён +- [ ] Версия в pyproject.toml обновлена +- [ ] Тег создан и отправлен +- [ ] CI/CD pipeline успешен +- [ ] Пакет опубликован + +--- + +## 🔧 Ручная публикация (если нужно) + +```bash +# Собрать +uv build + +# Опубликовать +uv publish \ + --publish-url https://git.much-data.ru/api/packages/claw/pypi \ + --token YOUR_GITEA_TOKEN +``` diff --git a/docs/SEMANTIC_RELEASE.md b/docs/SEMANTIC_RELEASE.md new file mode 100644 index 0000000..6768c88 --- /dev/null +++ b/docs/SEMANTIC_RELEASE.md @@ -0,0 +1,281 @@ +# Semantic Release — Автоматическое версионирование + +## 📋 Обзор + +**python-semantic-release** автоматически определяет версию на основе Conventional Commits. + +**Как работает:** +``` +Commit → Анализ сообщения → Определение типа → Bump версии → Тег → Релиз +``` + +--- + +## 🎯 CONVENTIONAL COMMITS + +### **Формат коммита:** + +``` +(): + +[optional body] + +[optional footer] +``` + +### **Типы коммитов и влияние на версию:** + +| Тип | Влияние | Пример | Версия | +|-----|---------|--------|--------| +| `feat` | **MINOR** | `feat: add new API endpoint` | 0.1.0 → 0.2.0 | +| `fix` | **PATCH** | `fix: handle timeout errors` | 0.1.0 → 0.1.1 | +| `perf` | **PATCH** | `perf: optimize HTTP requests` | 0.1.0 → 0.1.1 | +| `feat` + BREAKING | **MAJOR** | `feat: change auth method` | 0.1.0 → 1.0.0 | +| `docs`, `style`, `refactor`, `test`, `chore`, `ci`, `build` | Нет | `docs: update README` | Без изменений | + +--- + +## 📝 ПРИМЕРЫ КОММИТОВ + +### **PATCH (багфиксы):** + +```bash +git commit -m "fix: handle 404 error in catalog API" +git commit -m "fix(auth): restore session from token correctly" +git commit -m "perf: reduce HTTP connection overhead" +``` + +### **MINOR (новая функциональность):** + +```bash +git commit -m "feat: add batch kwork details endpoint" +git commit -m "feat(projects): add get_payer_orders method" +git commit -m "feat: support HTTP/2 for faster requests" +``` + +### **MAJOR (ломающие изменения):** + +```bash +git commit -m "feat: redesign authentication flow + +BREAKING CHANGE: login() now returns KworkClient instead of tuple + +Migration: + Before: token, cookies = await login(user, pass) + After: client = await KworkClient.login(user, pass) +" +``` + +### **Без влияния на версию:** + +```bash +git commit -m "docs: add usage examples to README" +git commit -m "test: increase coverage to 95%" +git commit -m "style: fix formatting with ruff" +git commit -m "refactor: simplify error handling" +git commit -m "chore: update dependencies" +git commit -m "ci: add Gitea Actions workflow" +git commit -m "build: configure UV build system" +``` + +--- + +## 🔄 WORKFLOW + +### **Разработка:** + +```bash +# Делай коммиты по Conventional Commits +git commit -m "feat: add new endpoint" +git commit -m "fix: handle edge case" +git commit -m "docs: update documentation" + +# Пуш в main +git push origin main +``` + +### **CI/CD (автоматически):** + +``` +1. Тесты запускаются +2. Сборка пакета +3. Semantic Release анализирует коммиты +4. Определяет тип версии (MAJOR/MINOR/PATCH) +5. Обновляет версию в pyproject.toml и __init__.py +6. Создаёт Git тег +7. Генерирует CHANGELOG +8. Создаёт релиз в Gitea +9. Публикует пакет в Gitea Registry +``` + +### **Результат:** + +``` +✅ v0.1.1 создан +✅ CHANGELOG.md обновлён +✅ Пакет опубликован +✅ Релиз в Gitea создан +``` + +--- + +## 🔧 РУЧНОЕ УПРАВЛЕНИЕ + +### **Проверить следующую версию:** + +```bash +cd /root/kwork-api +uv run semantic-release version --print +``` + +### **Сгенерировать CHANGELOG:** + +```bash +uv run semantic-release changelog +``` + +### **Создать релиз вручную:** + +```bash +# Bump версии +uv run semantic-release version --no-push + +# Проверить что изменилось +git diff + +# Запушить +git push origin main --tags +``` + +--- + +## 📊 ПРИМЕР ИСТОРИИ ВЕРСИЙ + +``` +v0.1.0 (2026-03-23) + - Initial release + - Complete API client + - 100% documentation + +v0.1.1 (2026-03-24) + - fix: handle timeout errors + - fix: restore session correctly + +v0.2.0 (2026-03-25) + - feat: add batch endpoint + - feat: support HTTP/2 + +v0.2.1 (2026-03-26) + - perf: optimize requests + +v1.0.0 (2026-03-27) + - feat: new authentication + - BREAKING CHANGE: API changed +``` + +--- + +## ⚙️ КОНФИГУРАЦИЯ + +**Файл:** `pyproject.toml` + +```toml +[tool.semantic_release] +version_toml = ["pyproject.toml:project.version"] +version_variables = ["src/kwork_api/__init__.py:__version__"] +branch = "main" +build_command = "uv build" +commit_parser = "angular" +tag_format = "v{version}" + +[tool.semantic_release.commit_parser_options] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] +breaking_change_tags = ["feat"] + +[tool.semantic_release.remote] +type = "gitea" +domain = "https://git.much-data.ru" +owner = "claw" +repo_name = "kwork-api" +``` + +--- + +## 🚨 TROUBLESHOOTING + +### **Ошибка: "No commits to release"** + +```bash +# Значит не было коммитов с типами feat/fix/perf +# Сделай коммит с правильным форматом +git commit -m "feat: add something new" +``` + +### **Ошибка: "Gitea token invalid"** + +```bash +# Проверь токен +# Settings → Applications → Create new token +# Права: write:repository, write:package +# Обнови секрет в Gitea Actions +``` + +### **Версия не обновляется** + +```bash +# Проверь конфигурацию +uv run semantic-release --version + +# Проверь что __version__ есть в __init__.py +grep "__version__" src/kwork_api/__init__.py +``` + +--- + +## 📋 ЧЕКЛИСТ ПЕРЕД ПУШЕМ + +- [ ] Коммиты по Conventional Commits +- [ ] Тесты проходят +- [ ] CHANGELOG обновлён (автоматически) +- [ ] Версия в __init__.py совпадает +- [ ] GITEA_TOKEN настроен в секретах + +--- + +## 🎯 BEST PRACTICES + +### **✅ Делай:** + +```bash +# Атомарные коммиты +git commit -m "feat: add user endpoint" +git commit -m "fix: handle 404 error" + +# Понятные описания +git commit -m "fix: restore session from saved token" + +# Scope для больших изменений +git commit -m "feat(auth): add OAuth2 support" +``` + +### **❌ Не делай:** + +```bash +# Слишком общие +git commit -m "fix stuff" + +# Несколько изменений в одном +git commit -m "feat: add user endpoint and fix auth and update docs" + +# Не по формату +git commit -m "added new feature" +``` + +--- + +## 📞 ССЫЛКИ + +- [python-semantic-release](https://python-semantic-release.readthedocs.io/) +- [Conventional Commits](https://www.conventionalcommits.org/) +- [Angular Commit Guidelines](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit) diff --git a/pyproject.toml b/pyproject.toml index cd79277..f7c1c3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ dev = [ "mkdocs-material>=9.7.6", "mkdocstrings>=1.0.3", "mkdocstrings-python>=2.0.3", + "python-semantic-release>=10.5.3", ] [tool.pytest.ini_options] @@ -103,4 +104,51 @@ exclude_lines = [ "if TYPE_CHECKING:", ] +# ============================================ +# Python Semantic Release Configuration +# ============================================ +[tool.semantic_release] +version_toml = ["pyproject.toml:project.version"] +version_variables = [ + "src/kwork_api/__init__.py:__version__", +] +branch = "main" +build_command = "uv build" +commit_parser = "angular" +upload_to_vcs_release = true +tag_format = "v{version}" + +[tool.semantic_release.branches.main] +match = "main" +prerelease = false + +[tool.semantic_release.commit_parser_options] +allowed_tags = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "style", "refactor", "test"] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] +breaking_change_tags = ["feat"] + +[tool.semantic_release.remote] +type = "gitea" +domain = "https://git.much-data.ru" +owner = "claw" +repo_name = "kwork-api" +token = { env = "GITEA_TOKEN" } + +[tool.semantic_release.publish] +dist_glob_patterns = ["dist/*"] +upload_to_vcs_release = true + +[tool.semantic_release.changelog] +template_dir = "templates" +changelog_file = "CHANGELOG.md" +exclude_commit_patterns = [ + "chore\\(release\\):.*", + "ci\\(release\\):.*", +] + +[tool.semantic_release.changelog.environment] +trim_blocks = true +lstrip_blocks = true + diff --git a/src/kwork_api/__init__.py b/src/kwork_api/__init__.py index 7876631..4d2b799 100644 --- a/src/kwork_api/__init__.py +++ b/src/kwork_api/__init__.py @@ -18,6 +18,27 @@ Example: from .client import KworkClient from .errors import KworkError, KworkAuthError, KworkApiError +from .models import ( + ValidationResponse, + ValidationIssue, + Kwork, + KworkDetails, + Project, + CatalogResponse, + ProjectsResponse, +) __version__ = "0.1.0" -__all__ = ["KworkClient", "KworkError", "KworkAuthError", "KworkApiError"] +__all__ = [ + "KworkClient", + "KworkError", + "KworkAuthError", + "KworkApiError", + "ValidationResponse", + "ValidationIssue", + "Kwork", + "KworkDetails", + "Project", + "CatalogResponse", + "ProjectsResponse", +] diff --git a/src/kwork_api/__pycache__/__init__.cpython-312.pyc b/src/kwork_api/__pycache__/__init__.cpython-312.pyc index 202faae45db0338b5ce7608cc69fc0c2cfde857d..0e5b60d94dd7523221c9ed07fd318275fd3c9f33 100644 GIT binary patch delta 371 zcmaFKdX1g$G%qg~0}yy5oX9L^p2#P`7%@>@%a}2RC5JtiBZ?!JGm0~pD~cMhM z^7D#QZ}B6~wZt*)OmL%rnr^EFN zKxK+yB2}ygdWL!in%qUqK*txcfCyFwhROO&Y-&a9ARY&Z-~8Z2j=BK3Q6cf@hIf*IEMI5M)5r~V8fW!x8Mn=YuOahE7UsV_w749?GUSzQS i&cwmU^qm1nd;}36L>TzR8n{0(@o+OWvKNU0wE+O@7h8(} delta 119 zcmcb{{*smNG%qg~0}w136)>FElFe+5B z8t5778ECQ;F#|Oev499x5Wxl{{4_ZyKWEaL?7$qv#Si2&0&%hYZ@d8BwJN_8h diff --git a/src/kwork_api/__pycache__/client.cpython-312.pyc b/src/kwork_api/__pycache__/client.cpython-312.pyc index b50ddec74892219d59f32c69a1e0dcac9646e275..455e83c92132930e1aaa9c0858cd188300d5885e 100644 GIT binary patch delta 8476 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 G@&5q@G_JD% delta 5646 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 Zu;i_yPlaXosDDt;(2}fg6^J~J{|7D%Y^VSL diff --git a/src/kwork_api/__pycache__/models.cpython-312.pyc b/src/kwork_api/__pycache__/models.cpython-312.pyc index d54b54addd14199335ebcc239648049736c02d81..624181c25d8ee20ece503db7f8f59ab2921263b4 100644 GIT binary patch delta 1599 zcmZ{kO>7%g5P;XN|5L~LvB9Ra-YgEpsQD|QKT%3aYvTkZCJGfomJnFkr_|8IW;dqI z$*x1dQQO4PE2v1QQG4M~4o(vblaNH*khr{+b8vk_;(){jq;Nq(V&=t(T`E@c?09zO z&AfTvy!4{(*ROSrKilm#0Up-}KgE7OyWHs31y*13=zH`+NRWF!735RI^zaCJ2twop zx4r__zB;RS_ErDswSb;oq^$$b&~J`7>FtY+DPklh*)RZcTr;93n<8dnAyyAsO|rRa zvw_W6wOPT(&pOjg(JF>7neE zoVqYoGDv07mwq#6x!byI-!=FY4^Op7n+80(>-5LYRshsN&pGWWy?{7=YkUYv@QK)n zl{8dQG;uu5k)y;;8td_t%@GIKnyNM{N7n4ORa$DujuSNzpH@k-OMgJ^J>c#UbTcCk zjtz4zkNq#0EKhxx+E2l8!TD;p;ZWAu4xCq!ZNVyeu{)ZGb$@a=Al(yJ$izo8iayLgI}G7D*W0!Bw7b9GEWVUw#YfhJ-`6LctJ5m?Q_n` z259pl+t#vB&aM`i*QL=hcq3l?)fk$Gqua_H6Fb!ugWHTRz?fQSas^7T()6 zwC&Fet{?V`N(&~#ySie6D-A9xg}xlT)yb>lg*-W8e_IBo;&SpTc}@8Wf^Y)VGi#57 f$92-4U-kH77f<>xsRDVx delta 79 zcmaDdhq0lLk?%AwFBbz4q#itw>0msOPl8cmqq-y$OQoTv(dGiC3r@^_nsSpPedbSQ e@|EQ32dZQQ;$r#Ds=l&}TwfV@8I_78fg%7?8WQFJ diff --git a/src/kwork_api/client.py b/src/kwork_api/client.py index 62c51c2..60ce889 100644 --- a/src/kwork_api/client.py +++ b/src/kwork_api/client.py @@ -37,6 +37,7 @@ from .models import ( Review, ReviewsResponse, TimeZone, + ValidationResponse, ) logger = logging.getLogger(__name__) @@ -1177,6 +1178,48 @@ class KworkClient: Данные актёра/пользователя. """ return await self.client._request("POST", "/actor") + + async def validate_text(self, text: str, context: Optional[str] = None) -> ValidationResponse: + """ + Проверить текст на соответствие требованиям Kwork. + + Эндпоинт валидации текста используется для проверки: + - Описаний кворков + - Текстов проектов + - Сообщений и отзывов + + Находит потенциальные проблемы: + - Запрещённые слова и контакты + - Нарушения правил площадки + - Проблемы с форматированием + + Args: + text: Текст для проверки. + context: Контекст использования (опционально). + Например: "kwork_description", "project_text", "message". + + Returns: + ValidationResponse с результатами валидации. + + Example: + result = await client.other.validate_text( + "Отличный сервис, звоните +7-999-000-00-00" + ) + if not result.is_valid: + print("Текст не прошёл валидацию:") + for issue in result.issues: + print(f" - {issue.message}") + """ + payload = {"text": text} + if context: + payload["context"] = context + + data = await self.client._request( + "POST", + "/api/validation/checktext", + json=payload, + ) + return ValidationResponse.model_validate(data) # ========== API Property Accessors ========== diff --git a/src/kwork_api/models.py b/src/kwork_api/models.py index 6e45aad..d69c0fc 100644 --- a/src/kwork_api/models.py +++ b/src/kwork_api/models.py @@ -410,3 +410,41 @@ class DataResponse(BaseModel): success: bool = True data: Optional[dict[str, Any]] = None message: Optional[str] = None + + +class ValidationIssue(BaseModel): + """ + Проблема, найденная при валидации текста. + + Attributes: + type: Тип проблемы: "error", "warning", "suggestion". + code: Код ошибки (например, "SPELLING", "GRAMMAR", "LENGTH"). + message: Описание проблемы. + position: Позиция в тексте (если применимо). + suggestion: Предлагаемое исправление (если есть). + """ + type: str = "error" + code: str + message: str + position: Optional[int] = None + suggestion: Optional[str] = None + + +class ValidationResponse(BaseModel): + """ + Ответ API валидации текста. + + Используется для эндпоинта /api/validation/checktext. + + Attributes: + success: Успешность валидации. + is_valid: Текст проходит валидацию (нет критических ошибок). + issues: Список найденных проблем. + score: Оценка качества текста (0-100, если доступна). + message: Дополнительное сообщение. + """ + success: bool = True + is_valid: bool = True + issues: list[ValidationIssue] = Field(default_factory=list) + score: Optional[int] = None + message: Optional[str] = None diff --git a/tests/unit/__pycache__/test_client.cpython-312-pytest-9.0.2.pyc b/tests/unit/__pycache__/test_client.cpython-312-pytest-9.0.2.pyc index 691086509494529affa29e72be92ca6fb40cc6d4..d56b82f8b01fce11c02a77d88a45f54302445593 100644 GIT binary patch delta 8627 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 z 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 Jcp)_Q@Gl?B3{3z4 diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 4f243fa..b3e6507 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -9,7 +9,7 @@ import respx from httpx import Response from kwork_api import KworkClient, KworkAuthError, KworkApiError -from kwork_api.models import CatalogResponse, Kwork +from kwork_api.models import CatalogResponse, Kwork, ValidationResponse, ValidationIssue class TestAuthentication: @@ -240,3 +240,93 @@ class TestContextManager: # Client should be closed after context assert client._client is None or client._client.is_closed + + +class TestValidationAPI: + """Test text validation endpoint.""" + + @respx.mock + async def test_validate_text_success(self): + """Test successful text validation.""" + client = KworkClient(token="test") + + mock_data = { + "success": True, + "is_valid": True, + "issues": [], + "score": 95, + } + + respx.post(f"{client.base_url}/api/validation/checktext").mock( + return_value=Response(200, json=mock_data) + ) + + result = await client.other.validate_text("Хороший текст для кворка") + + assert isinstance(result, ValidationResponse) + assert result.success is True + assert result.is_valid is True + assert len(result.issues) == 0 + assert result.score == 95 + + @respx.mock + async def test_validate_text_with_issues(self): + """Test text validation with found issues.""" + client = KworkClient(token="test") + + mock_data = { + "success": True, + "is_valid": False, + "issues": [ + { + "type": "error", + "code": "CONTACT_INFO", + "message": "Текст содержит контактную информацию", + "position": 25, + "suggestion": "Удалите номер телефона", + }, + { + "type": "warning", + "code": "LENGTH", + "message": "Текст слишком короткий", + }, + ], + "score": 45, + } + + respx.post(f"{client.base_url}/api/validation/checktext").mock( + return_value=Response(200, json=mock_data) + ) + + result = await client.other.validate_text( + "Звоните +7-999-000-00-00", + context="kwork_description", + ) + + assert result.is_valid is False + assert len(result.issues) == 2 + assert result.issues[0].code == "CONTACT_INFO" + assert result.issues[0].type == "error" + assert result.issues[1].type == "warning" + assert result.score == 45 + + @respx.mock + async def test_validate_text_empty(self): + """Test validation of empty text.""" + client = KworkClient(token="test") + + mock_data = { + "success": False, + "is_valid": False, + "message": "Текст не может быть пустым", + "issues": [], + } + + respx.post(f"{client.base_url}/api/validation/checktext").mock( + return_value=Response(200, json=mock_data) + ) + + result = await client.other.validate_text("") + + assert result.success is False + assert result.message is not None diff --git a/uv.lock b/uv.lock index 3f1920d..7ae2faa 100644 --- a/uv.lock +++ b/uv.lock @@ -203,14 +203,26 @@ wheels = [ [[package]] name = "click" -version = "8.3.1" +version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click-option-group" +version = "0.5.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/ff/d291d66595b30b83d1cb9e314b2c9be7cfc7327d4a0d40a15da2416ea97b/click_option_group-0.5.9.tar.gz", hash = "sha256:f94ed2bc4cf69052e0f29592bd1e771a1789bd7bfc482dd0bc482134aff95823", size = 22222, upload-time = "2025-10-09T09:38:01.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/45/54bb2d8d4138964a94bef6e9afe48b0be4705ba66ac442ae7d8a8dc4ffef/click_option_group-0.5.9-py3-none-any.whl", hash = "sha256:ad2599248bd373e2e19bec5407967c3eec1d0d4fc4a5e77b08a0481e75991080", size = 11553, upload-time = "2025-10-09T09:38:00.066Z" }, ] [[package]] @@ -426,6 +438,15 @@ version = "0.11" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/ce/5d6a3782b9f88097ce3e579265015db3372ae78d12f67629b863a9208c96/docstring_parser-0.11.tar.gz", hash = "sha256:93b3f8f481c7d24e37c5d9f30293c89e2933fa209421c8abd731dd3ef0715ecb", size = 22775, upload-time = "2021-09-30T07:44:10.288Z" } +[[package]] +name = "dotty-dict" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/ab/88d67f02024700b48cd8232579ad1316aa9df2272c63049c27cc094229d6/dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15", size = 7699, upload-time = "2022-07-09T18:50:57.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/91/e0d457ee03ec33d79ee2cd8d212debb1bc21dfb99728ae35efdb5832dc22/dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f", size = 7014, upload-time = "2022-07-09T18:50:55.058Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -450,6 +471,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.46" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, +] + [[package]] name = "griffelib" version = "2.0.0" @@ -540,6 +585,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -590,6 +644,7 @@ dev = [ { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, + { name = "python-semantic-release" }, { name = "respx" }, { name = "ruff" }, ] @@ -617,6 +672,7 @@ dev = [ { name = "pytest", specifier = ">=8.0.0" }, { name = "pytest-asyncio", specifier = ">=0.23.0" }, { name = "pytest-cov", specifier = ">=4.0.0" }, + { name = "python-semantic-release", specifier = ">=10.5.3" }, { name = "respx", specifier = ">=0.20.0" }, { name = "ruff", specifier = ">=0.3.0" }, ] @@ -630,6 +686,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -715,6 +783,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "mergedeep" version = "1.3.4" @@ -1162,6 +1239,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-gitlab" +version = "6.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "requests-toolbelt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/bd/b30f1d3b303cb5d3c72e2d57a847d699e8573cbdfd67ece5f1795e49da1c/python_gitlab-6.5.0.tar.gz", hash = "sha256:97553652d94b02de343e9ca92782239aa2b5f6594c5482331a9490d9d5e8737d", size = 400591, upload-time = "2025-10-17T21:40:02.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/bd/b0d440685fbcafee462bed793a74aea88541887c4c30556a55ac64914b8d/python_gitlab-6.5.0-py3-none-any.whl", hash = "sha256:494e1e8e5edd15286eaf7c286f3a06652688f1ee20a49e2a0218ddc5cc475e32", size = 144419, upload-time = "2025-10-17T21:40:01.233Z" }, +] + +[[package]] +name = "python-semantic-release" +version = "10.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "click-option-group" }, + { name = "deprecated" }, + { name = "dotty-dict" }, + { name = "gitpython" }, + { name = "importlib-resources" }, + { name = "jinja2" }, + { name = "pydantic" }, + { name = "python-gitlab" }, + { name = "requests" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/3a/7332b822825ed0e902c6e950e0d1e90e8f666fd12eb27855d1c8b6677eff/python_semantic_release-10.5.3.tar.gz", hash = "sha256:de4da78635fa666e5774caaca2be32063cae72431eb75e2ac23b9f2dfd190785", size = 618034, upload-time = "2025-12-14T22:37:29.782Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/01/ada29a1215df601bded0a2efd3b6d53864a0a9e0a9ea52aeaebe14fd03fd/python_semantic_release-10.5.3-py3-none-any.whl", hash = "sha256:1be0e07c36fa1f1ec9da4f438c1f6bbd7bc10eb0d6ac0089b0643103708c2823", size = 152716, upload-time = "2025-12-14T22:37:28.089Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -1253,6 +1367,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + [[package]] name = "respx" version = "0.22.0" @@ -1265,6 +1391,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/67/afbb0978d5399bc9ea200f1d4489a23c9a1dad4eee6376242b8182389c79/respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0", size = 25127, upload-time = "2024-12-19T22:33:57.837Z" }, ] +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, +] + [[package]] name = "ruff" version = "0.15.7" @@ -1290,6 +1429,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -1299,6 +1447,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "smmap" +version = "5.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, +] + [[package]] name = "structlog" version = "25.5.0" @@ -1374,6 +1531,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, ] +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + [[package]] name = "typeapi" version = "2.3.0"