From 5560ac21df874a5818fa4d73654285f30358a3f4 Mon Sep 17 00:00:00 2001 From: ChrisKent Date: Tue, 2 Jul 2024 14:21:10 +0100 Subject: [PATCH] Added tooling for working with the alias file --- README.md | 73 +++++++++++++-- dist/primal_page-0.1.0-py3-none-any.whl | Bin 8548 -> 0 bytes dist/primal_page-0.1.0.tar.gz | Bin 7269 -> 0 bytes dist/primal_page-1.0.0-py3-none-any.whl | Bin 13668 -> 0 bytes dist/primal_page-1.0.0.tar.gz | Bin 11331 -> 0 bytes primal_page/aliases.py | 113 ++++++++++++++++++++++++ primal_page/errors.py | 28 ++++++ primal_page/main.py | 2 + primal_page/schemas.py | 50 ++++++++++- tests/test_regex.py | 55 +++++++++++- 10 files changed, 309 insertions(+), 12 deletions(-) delete mode 100644 dist/primal_page-0.1.0-py3-none-any.whl delete mode 100644 dist/primal_page-0.1.0.tar.gz delete mode 100644 dist/primal_page-1.0.0-py3-none-any.whl delete mode 100644 dist/primal_page-1.0.0.tar.gz create mode 100644 primal_page/aliases.py diff --git a/README.md b/README.md index 4227020..fee0668 100644 --- a/README.md +++ b/README.md @@ -218,13 +218,71 @@ $ primal-page [OPTIONS] COMMAND [ARGS]... **Commands**: +* `aliases`: Manage aliases * `build-index`: Build an index.json file from all schemes... * `create`: Create a new scheme in the required format -* `dev`: Development commands * `download`: Download schemes from the index.json * `modify`: Modify an existing scheme's metadata... * `remove`: Remove a scheme's version from the repo,... +## `primal-page aliases` + +Manage aliases + +**Usage**: + +```console +$ primal-page aliases [OPTIONS] COMMAND [ARGS]... +``` + +**Options**: + +* `--help`: Show this message and exit. + +**Commands**: + +* `add`: Add an alias:schemeid to the alias file +* `remove`: Remove an alias from the alias file + +### `primal-page aliases add` + +Add an alias:schemeid to the alias file + +**Usage**: + +```console +$ primal-page aliases add [OPTIONS] ALIASES_FILE ALIAS SCHEMEID +``` + +**Arguments**: + +* `ALIASES_FILE`: The path to the alias file to write to [required] +* `ALIAS`: The alias to add [required] +* `SCHEMEID`: The schemeid to add the alias refers to. In the form of 'schemename/ampliconsize/schemeversion' [required] + +**Options**: + +* `--help`: Show this message and exit. + +### `primal-page aliases remove` + +Remove an alias from the alias file + +**Usage**: + +```console +$ primal-page aliases remove [OPTIONS] ALIASES_FILE ALIAS +``` + +**Arguments**: + +* `ALIASES_FILE`: The path to the alias file to write to [required] +* `ALIAS`: The alias to add [required] + +**Options**: + +* `--help`: Show this message and exit. + ## `primal-page build-index` Build an index.json file from all schemes in the directory @@ -284,7 +342,6 @@ $ primal-page create [OPTIONS] SCHEMEPATH * `--fix / --no-fix`: Attempt to fix the scheme [default: no-fix] * `--help`: Show this message and exit. - ## `primal-page download` Download schemes from the index.json @@ -301,17 +358,17 @@ $ primal-page download [OPTIONS] COMMAND [ARGS]... **Commands**: -* `download-all`: Download all schemes from the index.json -* `download-scheme`: Download a scheme from the index.json +* `all`: Download all schemes from the index.json +* `scheme`: Download a scheme from the index.json -### `primal-page download download-all` +### `primal-page download all` Download all schemes from the index.json **Usage**: ```console -$ primal-page download download-all [OPTIONS] +$ primal-page download all [OPTIONS] ``` **Options**: @@ -320,14 +377,14 @@ $ primal-page download download-all [OPTIONS] * `--index-url TEXT`: The URL to the index.json [default: https://raw.githubusercontent.com/quick-lab/primerschemes/main/index.json] * `--help`: Show this message and exit. -### `primal-page download download-scheme` +### `primal-page download scheme` Download a scheme from the index.json **Usage**: ```console -$ primal-page download download-scheme [OPTIONS] SCHEMENAME AMPLICONSIZE SCHEMEVERSION +$ primal-page download scheme [OPTIONS] SCHEMENAME AMPLICONSIZE SCHEMEVERSION ``` **Arguments**: diff --git a/dist/primal_page-0.1.0-py3-none-any.whl b/dist/primal_page-0.1.0-py3-none-any.whl deleted file mode 100644 index 9f944c4a2411f40967700fbb32be1fa7a3fe7e37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8548 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 diff --git a/dist/primal_page-1.0.0-py3-none-any.whl b/dist/primal_page-1.0.0-py3-none-any.whl deleted file mode 100644 index 8e00ff365529fa247a3ff9447859e1fbd9e94a3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13668 zcmai*bCe|S(&yXLwr$(CF>Twdwrx(^wr$(CZF^eN-P1dF_dR>>Zx(OmiIbUs)TiRq z$@pf(6Y(g>fP$d{0Rch&F(RPSg?kCiKUe5~4C9a4J6Zw^ZS?I8%}p8f^(}2Jo%QwU z?LDGm8D!|FXQpPOV%6(Z6_}S;=2#RCK!6nf0h2H1?)~`<2xtlq2nhQxn30R6jmaON ziK+V^-n1qlcC!uX_l&J`{u~eKsxK34KnkUHG%XYd zMH`8>^?xwZ!c`~C6>!!t` zgSl)U(s7Go{4UsT5TYrY41QA5H)ICpf=XhS`yhF#Pi0kTFO*i$ou5oZ%81aF#$2ue;qik9iGl! z*I+2KSu%>0(n6(4v6eN=A5#b0#Rck#MB>z9KxLLUfHz=kSlIeNcnE5lNjIWokKacE zsr|Sca$*k{SaMS>CamI{K&W!WT$#O_PnlA@I!pW}qTWY+BVsyUb)YdPVApu^tGwBw zW0eQsqnM%)93rn zu5h+UR4y@Dm|}u4!+3V1i&U~k;)=uRp#~HQV)0w3VfdIIE4srk3&DdTUzHE?QFWA` zB-nN3zdiIw4xW_ip9jMuT?q)%NSgp?!O>FK9hf|iK=rrjiN3*tW;ia+dTNNqZbz0V~R4p6y&@8$H>cE&fK( zw<{xD8qY>OPlp7E=Rklb`5qmP$U}%TFxw}2c8MSG!z%2hMPqu~Rw2}tDQhyXGqqk` zw_WpXk3i>4;OsWkpbOTcYzAzLa&7f(N2WzwXc1jp# zo^<7Q)vLY-`U(%`_ID63=KH`nUv|M^>uZldsJtOxD5&Vl%U{+#x)25BIUl*j8~l1j z+|OE`p2m1x-Kw=9%Ke-w^xG@#5C|RED^fPeEukD=;J%Bb@+*&zLQ+8mwYzi)1OlHQ z@_gjRs7rBM-iG$P)M6L*f_w!5{MXXIpYAE*t-XCmV+V$}*Ts}5&o6NvXu?4r%1P$H z-VWsYK##tc8awDwm;Cq%swTgI?b>bIIZ;{DeDWG} zrY>tHTH=MT@_bD?02R>~B|f$y{vpuu>>XY9kVFLak^{wX3de9k8;L1}45a{n^Eo4) zzyby{)G72HCXXvOw~eplM4F#UfZ5ToC^+f+EeGV2b6M(QS=Y^GORoVT;WDGQwFuz- zc*Cdq(1$`?S*L3(XgKMWFjJ3Y?6OZuqK$mB`rU$2W|uu~&k1Y0Ba^^lR4bpc45dWg zWm}wycd4|)Fhy?Ne!9dOIUVx5W+P0}Xxgyyt|vv$Np)G#jAyMS9Ed4eM$q>IGD2l*dH>iNb($}dPqbP71Z-h|!yRr(Yd}cM&w$8+YK6x*(_%!uzH?V65rOR0lhZvla z(WJ_^-NhUygF}fFTb@ri|I8Jn58xsIpg=$!P(VQcNTW&E#(&+qD%+&&-Dx&ffe!UN2ByT;P=~aKwPLHy z9=#D~?S2!>`o8QiU2$H0qp{|TN3p#AB{3%89|{~o!t`aoB#Sib5nc3s4!NaNO&Q<0HK6kIcQrd_JI~P!pZq_0mb#Z zZJBS`lDO~;nug{%s9Wx@Zg`Uo`%ZC!^+7dA_tETbE?6*MOQamxx=@&XTNU-ql zEFuqc-LNbK@gN2S?VrG$=gh&O+oxwm%ZPYdoww(NCWHl#HQI0ho<1)RyqcR}P7rZ< z%@N%<(zM`Pi8%C7ABkTvufv&Zs3xeQ1J$CBBOTh%S2^@F3k*dYsKy|CHQTcwx!yGL z84pF$W-b6-ckmKLWakrF#iNMb0a)YL(7>$>R(js=in)E$;q~OXx!#VRTkwj&$tv2Y zAjpXtt)0>Yv*TRtk|8h2UX_BRTiJbald5SYbu40R7OE-7I?x>I%a^+3XiI3k0rZbib`~eVznDo130|mOYZ$I$fx&;5f;b%+Ey&< zoF+($a}s$-%>q+N6+cPVNp$9IQ`)A?It1{L!&vgFK#zm*0}(vL5FJh^M`oeSG+;~I zXQ|Z>CdG!ITHFSk>!p=Aa+4a8FkkDOP)~ zn-Ht)bH9qsI5jykqSm~9udSwG6esuimEY`VbtMvb2KUkB4v(l89ipe#O1x zM`pvxYRCcdb+9Or#U^x0YDm)Psb{<&0r4`Ls7jQMimPb7ptVT{pQmWmYhp9ESZ})S z(luh!)I#^+Cr4f)m#!TZmk-|j^0dW6h|s3V({uuCy_-Gi&hVlqEX?WN-HaO0bMBmx zX9nKy*M}VD7TH*o5J|}mWKxd&+1c_6pH#|H!!k+GE-!c}Re>*gd#+fu>X$tBtx5^S zL+{vMY$LF&9lmC02JG_mU1bd}{i2^zxw3e_;vaoZ48mCDjIHKN5=WT&B14rYD))-E z_3FER1ZJXdX)rLxs(U~j8fuK-};M8x{- z=;B+$p>BA{&v(`CG=$U&C|{w_5Z%FQ@jRLX5m0_vq^=pLbj(;I0I;XNpp7cu_{K6# zoL1eb)4GoX#X1fhEkfIhuwG$sIb7>vjGqyDw~12QbX0Xr{es0T+Jbdvl+(*Rbfi^# z@AWL3t5*(IJC?JP-tO45^WhW6PvR8w`EkOq0xf=5)P!CIg)QFE^7>>7hy=bPygDf& zyya%+rqHArluzq6gd_QViK9snqF!w|eD%*%Syc0txaqI$j_osmMit zpjECW&nd_;E`tZR2h$NuS#wc{2rwvlS)@?nPaD_&*rZeGc{5}1JWv`L7%H$5%a*ml z6(CNfNfFG@Le2;hJx{5VfM)<1fMcRP#a&n_96t$f01hK>FfqD_kUJqgBUFq~Wh6m` z>Ob+Q6z=|k1hEuC5Z7+Wy}YTGnwVK^lV$4+<5u060#)M7_D zR3PiD%Y=9)_2U>hI|!sE&kjz;w>8wkU(uOBUqPr-!$PgRxEoLFgY{LV_%Z&UyFz_j zU>-luup)>Ze^Hk`uYelhn8NeA_A0EXt(MeWWW^UHP~F$yYgIj_AuY@pIv{+>XJ0LL z@H$dcb}_%%0G=YdPh;TtPAv~jj(p_b4+}5(nE#G%?x<7mxb!w=K&$CA7$dsc@;K7gV zf&|wON%v0Mos*z4sn7N+JA#nL4+a@x9DluqNpCpQqCIAkCy}|~^cyR*i*ceaIKJmI zHg$HbsxT=woQ+u}P96V*t^L+rB$}?EW$|GF<#>SwyK!(a$kzVq>{Gx)yoUIykuPL+ z4n?KDsk2*s0>yhw_c|<2>8&gXyl*+f5)e11mYgiR8o|VfaVAxwj%HH`5OmV!Sl~^} zTTu;Y_yjgkQd7>M0WqN}aT)Q)1ocCI;%a}pQ_LJ|?5BBlJVES%nQ*K;ZiM3#l~=3u zU|F>Qnr5Yu1;QTeTe=PMCOvZszpc9#!hz-)U2R4l=y%$98_6i+ei+ywx%Kg&&6v1g zqyHX9V0e_n-epfv;nu>C!pdz+0mA*jp~QZ1OO52ikEQCFiSu2@Qep5r5zA$w>txLK z&~_6e;jJn?Ch7D{d%3bTS^h#i; z#pu2PwMM=RcVu@)9|746oQwm9R^YpjBI9Km4s#zzcB0sX26O&q+h8o%kU_Aj7e>K; z;Z-jUvzr(o;>WYW4Xu`1X&&i|k^(JVfbFHFGj03&)%L92PD|oWj`(8_z%7G+l)RlD z6+b?TwK*RF7tdfDyoZ|&8y0;v?IvJYG9DXW7Wrx1E*`?8Hv6a ztoycmtFGi;=0c^HkM3*s$~W_LI<1m5TKoM`>vU#KSI{e?T=E5LTLB(f|AzyEV}d=$ z(P9Ai0@4=U=RT4|cX@^rhKf)Vi;BzMC3S3FGan&)Q=tmW??LK1D#NQQg|Dsxjw%23 z58eXdMn?$hmQTy~yJ!5M-|B}P#;^Nj^#S&N5p~|olcT(GX1kqb5A!}c;0Rcy`ArId zs92|fjtuN9->)6S#!0Evhc3+!jw?YT2rdbE;L#K1YH91OgI~2LeL*t9k(#TH5|AC(^le+8japn&~}&^|Yj@x~AteXiy1uzSZ1qB* z9G@t#(rBdhV3Ry6_>tj}Aq&x1F?J`FkQ?^VvG0+M>M?Cn+0Dz4QMO-yp*D*lmrzmZ zp#nqv@Q~{gzX^tx%wY;DO0r#j0BWVCCWT&Xh?V_l&>*?MDf^J7U+KFT32N7|daWRT z${W>G_S?Vzl#X?8*%y2HNpg#`U+n`7qCU@ z`ESbkwZcyP?Tp7JT(8<;I`rco`V=lf6X?J8Vy#o|$s-HNjeJ2w;O3}BV!`}en*7|o z9Wir$AF^)kBIti$=Uj!%4we#To_Y{GAiO<;`alFNdMGtk7IYZgWs@u0)8RLQW1=(- z&jdpx%FH}R(Tb#QT4zwF)6S{3$e$<w;jO6+8ZK1VvN@sA z4tHZ2xkq?m&Ws`gX{0A#DH_b+#cDPxW;?zd4>z=K^sOX{P(Ww(cgggz+VeRNc?e4fPTmB)v+H+Khxb#~eB0uU}_{i8pCDgPo?F{)jRQ9ozO%pMd}{TA(&k zMd-UEj*vDX6*Z)%u3g`EIFToj@6rKzr96Zw7++eM43?GGq;@_~HPN(m_}#a&*Dw0vTyaiGPIrWm4s0fiGcf^xnf z{H{Cx3WRU-W+TeYz2|WC^HO5%WmI_xw`Rxn;ZqyN2Gz{XOn zD6swv)jvI9fdGQdK;+Pi0DCiFEa>Ib+mnd%_gINo$JoP$C9rKu;E<$Cc-mdzq#PxW zbl;c32aKr$H-c2~J_8HabR^nL>d7Hld=0M;ClITOp#WDS3aV z5TdSTJ3&+MAou(4LZu`b_Na_=Z@}_iCs>&7wk(N)Txk{Lj=&{*aXci45#IQ6X|S}p zG5Kz_Iq|pt9xqH+iP75^Jv_Xhk6Tj`caP(~$SyWB_`7YP?LJ#iL?4ntGEJP-=sE?1 zu$&?Tx$$X>to<;0_ud=ABF_ZzWaVm^XbLyMm=90%Uv950B+}!2szZVXkv=lU zQLnyG%;QtUVo+v^aQ!i`Vo}@76$MpvH|LS`RrGJH>16&&iN%CnW%)X2hy8UD;Fsj= zKIUm&MtN0f^^|hp+Zot-%W;&^sl!skrE-dN0O=Nb1zAP%@sIW0SlMMjw-&{~qt%?= zL~PUvStL0x;zjfNM9tnICmIN3X+LM^nh^zW1rRo(omRIPs6vi0XjQlvU5HOC%wlQr zF8qD?vcg3Wr#$V&QGJ4N_{OahJ5Vdj=;?jAwww`g2sN09COM!`fE@~westo8=C zY`kexR1HV&u~4_iK+oYD^U&|vjgTnF@2&gm$|4aW+_o-A28;FX7*1w8zV(vE`xlrG zd3TA0hq?NA&phA2jReG{XspMVwQZK#dSUZ5eof6#rMA+V7+(ZUK`xF&i z8@o$*dV_cfkB#RlLD_HlkZaAjXFsR=adda9V&P@_;kSj<>noEs=JOR)m+ElB4yrkt z>XYlRqUfXReytG1){#o*LASBDX(i;iYWu0SZB$0^5H(lFN6#-gHwCC%cES~*=T65r z4TB1lw7s(O>W1XQ+d}ZhTEp7yvQ(`a8p&j^<*ix0tt-x}?f4MY^L2DGDQ5c7kzc6c z7#Zc7Ygf2P@?+v!MbdF*p})~`acD@c`tc$r;2O!*V?ZTmmGS|4sd2B=hq49N=Ep}Q zEuNb4<7`_U=FA$Y5L>VyS5aR0K_QfME$Vf`{8CV@>t?N<)*Wh%N94o!ycFC-@SF9b z+>}bD`)XEw0H#)f2oNr!G4?mLZ7%_nH;VZ0BEViv>xXXk(sn{v@=p0O+r`t#-51{R z_g(jvxkojGFXBc-tQke#KNdfU=AG^mQh2?N$}%gab6PrRPX^Yb(ucR4Jk~CMcIzZ` zX-=BTBO6Epk0g@4y{<2o`jEuK@aA%2x<#wr73s1y8?YJ~^jXBSLy7=3wat>WTwcAh z4{0alkHQ$-PLLX5jeI#cjVqicke=Zl#iQF5Tx4-H!?Q~AUAcb^Bk{J;KM-p-khY+s zzc;Bz$qnfVW$TXclSqXiK}EPg=DLpV?QQQ&Dj#slVb^ari!&J1#ddc+KafRTbfl55 zO~yHn_}-n2As@Mf1qbiDPwuM(&0)v;!1w7e8lNR`l@!r6AX$D~wi zs?4S>lRN6`HbxkCsfAY%t}J}-Qn~8W zg}rYLqpmkEPK39dTRYV2H7k0;4-2!*t^_pW4^8xm;;LM}#rdA?1rG^Reuu^sGzIqG zw*!pqBtGTH6=uJCa;c4z#WES&^f`3PkSQ!rD9(^@fXZ^tqA|qhbm@I2%FEq@N!7AG zLnW$4rq&UMYlyUTK-lf#b@O|>HGH)Wu7*)Cel0xGc{GrM{OD3Dm`s-&23Dd;>M3g| zLkdH#OI4noq(@_baR98?Uu)s||I*^^5kDx|fv$LLN_*Ww5p)vUx+*?z7v9y;TuNQJ zp}MoEzB<<@(N2jU_APVY>24Le+N(bZ>SXD7+Qwaxt%ePx+;HT!#jnbjKiU;~rRmfR zX{!1azf7-^zX-v(Cl-gUXfuy6)N-ewh$5!?%^8R%zl13~|0DzeIo~MaWpv!E?Lk>X zrf=V$I>TFlkS*vPF1>r$e7q!W=EJP_{k^jP9!T#D4%D@+P@l?pNI@fD^Tlp zBA2qlqX{R7E;JKBgsSd{-Ajf}x-_K=Mv2K4m4F+%z9&qH`tC8vET%v%<(j_d5PZNK zaX*DA?Wu}(H>vx)X5 zH4D{kYfyj=nY91SXvJqm;~og3Kr@WHLVatP;H4xc+iSdVdq~$%u#?%GEn!@%OxiUXo0`Gy1RtvHa3JWWTRQ5(VE5;mkw79~1qu<_-8RTP;0Q z2BFmqid;-+&XO{={2Ho%LEnbjP;+6(B^!0$TJMg<6YkUeW`|$BrKyvTt7GCww$Jo3 zT?mKmT+D}6P$(0AJ_rsTssUoY5i5j+9?^il^Y=3 zlcJe1mUqGQV8CNi;o_}Yh#xS%bYP}t;<>vC6TopX)D#51px0C#wq*&{n*IyGC^ybS4?H>RE zKyjS|MJX1S1)C^hLNA@LS~-7SRA_e z2p80uT%)3{S2!MY-z!#*jVToACs>2%lyVI8K>lo@GXxN@F=DHv`}%vDaiQ=Ev6iAc zGmV{2zG!TT)&|WIF-?1!d@VK@q-1s*E|$w}-EZExYxw>1=Sy5&rqNFm;v|S>o(Px9 z9#^GSzR>`f_Kx}1L8BS}4cdU%M%Rkj&_!L9ByF*%17rl4&EnTFs~9$VJi^d*X+<_7 zwU-u94Ojn+7Uc3yQzXUZvL7+y9hp12LXu5MG-DbrTi2AWpPZqPzmxW?@bnXIz=_3s0!wWZo}U?7X#nSp z*mtiC4)v~Z+>MsISyAq6efA6=+%>FCX!7VCTB5hj%OvDDsDinA2YW()_bP8qpemyC zgFc=}HwB{RT#-Mnp7BIm$mw(=>c7}87=Qq_RISk0QrR&E?~n%(3Atd7EETSn*z9jn z59}R!Wha_uU9xS@a+{0DbUiv(*Tg6PsAATJHXM#Q%C5MG&zivAU&Fs`HSj1i=wDSH z$JW=1i&JSqb@w-9V>n!6B%fROYJJZX=aMR7zgaz@uyWoMS;sFoCsj4MXESYwWconY zK|s?YIz7{`$NzwVWqIW@rPJw_&34~cP>!-|~ z|LDY{M_}+1x<6OpDLx~d0y)Yi!yG*3#sKQdxV++@7DWrU8Oh+Vy#k8ZyPejXRyxUK z0SU_h+6^Je70kc_{YSchL@~VOc_cyVj6_o>W^r?%;{C6{waaE#x$0DN#Cdl$c9}=l zrJgP8A4i&GvL7BW3h4~+V&W`2v|-R#)pslAL{o7b`bWuYYlat=b@3OB6b8xcTPW-| zF{0mmrrsDyTPQ8%VF4k(DroV`MC7z8f4KLtwCAoH8Wd)EpF;!hsQZ9?@bDVBIZ^KP zd0IIaWvZk%f3^pd>C&`s5n2mH?!k`f?)mlSXyApAe8B>x&N`_KNald**B1y42z-faM@%K#aT>85K z#~yIW%YNFv8KsLhH`Fwc#3}u>C8{?4&3c0bI;-Wvmv1G;Ci5KYb$q&kwfg(RRU-}3 zBd^G?u4tO1aFU7G47jI|v}e2o>vjHQm#k<*sS`POef8xi4|*i2Iq9%h0yz>A7XuB< zqJ~0GdCJpw2X)db=~C@2i&v?Sd#n z*?u*WUz2%X@6B&F`T~_|zAoIE2JIQW+D&d(0;1*w9~T4`E*)nH6j;YUOi-_>7>EiR z8nN~#k3T@30~SZto4z~No3|r*VqdU(95(ah_oGThE}=dmToQ}Y zAyh)OL{7*|3d{^2p)63Sp*2LsV8dR^+h><>mnq3fm=^4sF+A}x8CgXFR z?->2<#Y%_})r+uW$r}xUjV7xPcfLKokB(eE3=+MMTu7VYiL+BvrF)K8Qze&z(WBw$ zS)mWK3k&DDmu|H|Vs%xhW8cZADD9m49x9tRrle?pZg6WvZ(A%jx-bW~2^X z#G8*3=?~;E!;&PjXFCvls{NI{QDu%&h&@N~n9S;&IZAQl<&ATeKhh^}0`Tg!)2rBW zO4%8QKmtn$Q|=1eySgDtizw5N9aBO~E1xTzW0p*Sd+H4kKkNen*<_lAb~xRMQOxik z{idRnFKD4>O{+Aqr7a)h-%nD`O-@gl-f-Py)K;BoK69{DUF!wGn=pO}ycLwd02JW^ znzVo}KRV5K15Lfc)Arh4x_+&zox*x2U?PsDwK>NEygLVJK5rMvPrjJuI3XNq3^Hs9 zBf~~JQazs!W(YF-0Ku7(HmT5rfeEDDw%szo`cSBG>6rm3^^57WFMP_@5>H%cFc@Dm zL@j~MFA_kBVp@EI&82I`^7Ekfw^d}$JLu?u1e%x4tPyMd?#Bw}iVCP1Ws?-ZN8o2z z@-}^jcszIWgYXx$po8$Dh~{DX7k@413vJl}V$V+Xv;mPP>p{p~DBBmWGc9O|JJ;SYyB8@Sw_JbwR=E4+=4Kt>Ej@48 z)f%{~<+`wnviZ3dLlTd=-9GqnmVIgF_jKHT&J9!W-zp#v4fwLMIbPzbROt#n$o~K)6k63%!2M)D1^%hz@~*RUqaZ*)g@4qX^q)#j$3)Ng=PP-H6PrOKVGNoZqgw9Y0t& zsAAC?i=Ej{7uIQH5Upw40R>NORr-P<|9FqgwDLvPLIYZM71w45#TzGqxqxSNv9=r) z&!vd*cG5|-@^75Xx)#V4rDkR7g6^KMLm50e4?`E%!7tC_wWv$9x-m~r$0YfY!gVIc zS?%-g3RIHI=EmkkEgI#;VpbOfde#uW;o-%3?fbIF=eY5hhxP|xKyyV~>E~i%>>KG9 zV}?&;11_(GOQljqUL+*%RP5v6{M8wnLACHA1H>L_ZWjb>Wz4qSdW#>ib5`Mv1-rOB zlGjX+eFY3=QkpgjVMm)7(7-EG*XYoVOed>mOIXdKP3@sgAv<}jIVLa6kOm3U2HIt79W@_NUZEP7iBo*T*h`Hm=+CLY zt9#*3Z#^4&CC76Ekms{slgSmFVc?8_%1&}hlJhc)=-zT)rrU^Zjw}Szc~HU}3s!8m z2o#tWMv@bApYx{5qoHBeRIG(%8JSM;Ob3Mq4%(Mty82^BFw5}V^$`xOH4<+9*(bf*j zUcUSsQ{-fBKDTsFh-od(3zqdhNyX4~RTOghU@$O*vHStov=>c^v*&a`q691t=5-a? zihY|GExGIPE7L=Xtf3!4V<~Te{umlMpd2)NKN;?+Kwz}&3-q6XU|Fu3p!z2OH2xUT ze-jAml44@Aa1%n%{)C9&bKRwPAShx{eK4Y(;bqOkrC{k1iWocmtGC$GoUP;tvdeXK+5XgVu zCl~yR5gsy%zG{vbqZ(3WUAAo^k8#XjC+D~GVlX+y$*!N@b{uopK6h79EoNe56DGq& zys-zQ*!lDxOb=*Lbu)rGv7C}`stM=TtZUkp?SK^0RzD3@y|dS((5upHQyEF?k%a7m zA;d{(vz&2*jx-$2v~43FH+j5_`k<`Kr4Y|#Apl>&jtsvwA@3vXM<%O{vJN>5<13=w zz;%a`S98yG&F^6^?^tOvPOs0wiUEsqviLIDLDUgkQRr8KwIa6cc;syw-Q_I`m^Toi z5-OgD_x8lO)AA9I)oeu|-ECA**JBePHNP=t(&Z_m8H|Yn-7K|c@B5)Bf|766WDmfu z?(>7C0ON@4n!fIEPRKmskA97;QqwUrd}e5}E{=tg8`*TzPfX5Y#E8iw>p5lb=+g2v z6EP=j+*lEiq!U~Q+CF(GLpu42A3YlxoocH$zc6-_QB4qfu^#ZMbq>=GoKkHVdVbJl zNOikVJ$PTK(n$hS5H@*7`dwz2eNI44;ScNS)}uU@%JsCH_u&N~ou7DFWP7pw;w}5^ zuZBgkZ}?b}t8Bq?kds<&5aVg;g~?O`t!c!^OBat1KnYpMx#e-f C>{(&} diff --git a/dist/primal_page-1.0.0.tar.gz b/dist/primal_page-1.0.0.tar.gz deleted file mode 100644 index e9a9c0f7372272c66e39f9438d0cf44b64c5ba28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11331 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 F005l_ddC0& diff --git a/primal_page/aliases.py b/primal_page/aliases.py new file mode 100644 index 0000000..fbc4751 --- /dev/null +++ b/primal_page/aliases.py @@ -0,0 +1,113 @@ +import json +import pathlib +import re + +import typer +from typing_extensions import Annotated + +from primal_page.schemas import validate_scheme_id + +app = typer.Typer(no_args_is_help=True) + +ALIASES_PATTERN = r"^[a-z0-9][a-z0-9-.]*[a-z0-9]$" + + +def validate_alias(alias: str) -> str: + if not re.match(ALIASES_PATTERN, alias): + raise typer.BadParameter( + f"({alias}). Must only contain a-z, 0-9, and -. Cannot start or end with -" + ) + return alias + + +@app.command(no_args_is_help=True) +def remove( + aliases_file: Annotated[ + pathlib.Path, + typer.Argument( + help="The path to the alias file to write to", + exists=True, + file_okay=True, + writable=True, + ), + ], + alias: Annotated[ + str, + typer.Argument( + help="The alias to add", + # No callback here because we don't want to validate removing the alias + ), + ], +): + """ + Remove an alias from the alias file + """ + # Read in the info.json file + with open(aliases_file) as f: + aliases = json.load(f) + + # Remove the alias, if it exists + aliases.pop(alias, None) + + # Write the new info.json file + with open(aliases_file, "w") as f: + json.dump(aliases, f, indent=4, sort_keys=True) + + +@app.command(no_args_is_help=True) +def add( + aliases_file: Annotated[ + pathlib.Path, + typer.Argument( + help="The path to the alias file to write to", + exists=True, + file_okay=True, + writable=True, + ), + ], + alias: Annotated[ + str, + typer.Argument( + help="The alias to add", + callback=validate_alias, + ), + ], + schemeid: Annotated[ + str, + typer.Argument( + help="The schemeid to add the alias refers to. In the form of 'schemename/ampliconsize/schemeversion'" + ), + ], +): + """ + Add an alias:schemeid to the alias file + """ + # Parse the schemeid + schemename, ampliconsize, schemeversion = validate_scheme_id(schemeid) + + # Read in the info.json file + with open(aliases_file) as f: + aliases = json.load(f) + + # Check if the alias already exists + if alias in aliases: + raise typer.BadParameter(f"({alias}) already exists in the alias file") + + # Add the alias + aliases[alias] = "/".join([schemename, ampliconsize, schemeversion]) + + # Write the new info.json file + with open(aliases_file, "w") as f: + json.dump(aliases, f, indent=4, sort_keys=True) + + +def parse_alias(aliases_file: pathlib.Path, alias: str) -> str: + with open(aliases_file) as f: + aliases = json.load(f) + if alias not in aliases: + raise typer.BadParameter(f"({alias}) does not exist in the alias file") + return aliases[alias] + + +if __name__ == "__main__": + app() diff --git a/primal_page/errors.py b/primal_page/errors.py index 42d60f2..de32708 100644 --- a/primal_page/errors.py +++ b/primal_page/errors.py @@ -43,3 +43,31 @@ class InvalidReference(UsageError): def __init__(self, message: str): super().__init__(message) + + +class InvalidSchemeID(UsageError): + """Raised when a schemeid is invalid""" + + def __init__(self, message: str): + super().__init__(message) + + +class InvalidSchemeName(UsageError): + """Raised when a schemename is invalid""" + + def __init__(self, message: str): + super().__init__(message) + + +class InvalidAmpliconSize(UsageError): + """Raised when a schemeversion is invalid""" + + def __init__(self, message: str): + super().__init__(message) + + +class InvalidSchemeVersion(UsageError): + """Raised when a schemeversion is invalid""" + + def __init__(self, message: str): + super().__init__(message) diff --git a/primal_page/main.py b/primal_page/main.py index 6748bc5..bb3efb8 100644 --- a/primal_page/main.py +++ b/primal_page/main.py @@ -9,6 +9,7 @@ from typing_extensions import Annotated from primal_page.__init__ import __version__ +from primal_page.aliases import app as aliases_app from primal_page.bedfiles import ( BEDFileResult, BedfileVersion, @@ -50,6 +51,7 @@ class FindResult(Enum): help="Download schemes from the index.json", ) app.add_typer(dev_app, name="dev", help="Development commands", hidden=True) +app.add_typer(aliases_app, name="aliases", help="Manage aliases") def typer_callback_version(value: bool): diff --git a/primal_page/schemas.py b/primal_page/schemas.py index 5ba9785..a181abb 100644 --- a/primal_page/schemas.py +++ b/primal_page/schemas.py @@ -6,6 +6,11 @@ from pydantic.functional_validators import AfterValidator from primal_page.bedfiles import BedfileVersion +from primal_page.errors import ( + InvalidSchemeID, + InvalidSchemeName, + InvalidSchemeVersion, +) INFO_SCHEMA = "v2.0.0" @@ -27,17 +32,58 @@ class SchemeStatus(Enum): VALIDATED = "validated" +def validate_scheme_id(schemeid) -> tuple[str, str, str]: + """ + Parse the schemeid into its components + :raises InvalidSchemeID: if the schemeid is invalid + """ + try: + ( + schemename, + ampliconsize, + schemeversion, + ) = schemeid.split("/") + except ValueError as e: + raise InvalidSchemeID( + f"{schemeid} needs to be in the form (schemename)/(ampliconsize)/(schemeversion)" + ) from e + + # Validate each part separately + try: + schemename = validate_schemename(schemename) + schemeversion = validate_schemeversion(schemeversion) + ampliconsize = int(ampliconsize) + except InvalidSchemeName as e: + raise InvalidSchemeID( + f"{schemename} is an invalid schemename in {schemeid}" + ) from e + except InvalidSchemeVersion as e: + raise InvalidSchemeID( + f"{schemeversion} is an invalid schemeversion in {schemeid}" + ) from e + except ValueError as e: + raise InvalidSchemeID( + f"{ampliconsize} is an invalid ampliconsize in {schemeid}" + ) from e + + return schemename, str(ampliconsize), schemeversion + + def validate_schemeversion(version: str) -> str: if not re.match(VERSION_PATTERN, version): - raise ValueError( + raise InvalidSchemeVersion( f"Invalid version: {version}. Must match be in form of v(int).(int).(int)" ) return version def validate_schemename(schemename: str) -> str: + """ + Validate the schemename + :raises InvalidSchemeName: if the schemename is invalid + """ if not re.match(SCHEMENAME_PATTERN, schemename): - raise ValueError( + raise InvalidSchemeName( f"Invalid schemename: {schemename}. Must only contain a-z, 0-9, and -. Cannot start or end with -" ) return schemename diff --git a/tests/test_regex.py b/tests/test_regex.py index e0045d5..c534529 100644 --- a/tests/test_regex.py +++ b/tests/test_regex.py @@ -5,8 +5,14 @@ V1_PRIMERNAME, V2_PRIMERNAME, ) +from primal_page.errors import ( + InvalidSchemeID, + InvalidSchemeName, + InvalidSchemeVersion, +) from primal_page.schemas import ( not_empty, + validate_scheme_id, validate_schemename, validate_schemeversion, ) @@ -40,7 +46,7 @@ def test_SchemeNamePattern_invalid(self): ] for name in invalid_names: - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemeName): validate_schemename(name) def test_VersionPattern_ValidVersions(self): @@ -71,7 +77,7 @@ def test_VersionPattern_InvalidVersions(self): ] for version in invalid_versions: - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemeVersion): validate_schemeversion(version) def test_V1PrimerName_ValidNames(self): @@ -149,5 +155,50 @@ def test_not_empty_empty(self): not_empty(test_case) +class TestValidateSchemeID(unittest.TestCase): + def test_valid_scheme_id(self): + valid_scheme_id = ["test123/400/v1.0.0", "test123/1000/v1.0.0"] + + # Valid scheme ids + for scheme_id in valid_scheme_id: + self.assertEqual(tuple(scheme_id.split("/")), validate_scheme_id(scheme_id)) + + def test_invalid_scheme_id_schemename(self): + # Scheme ids with invalid scheme names + invalid_scheme_id = ["test123-/400/v1.0.0", "test1A23/1000/v1.0.0"] + + # Invalid scheme ids + for scheme_id in invalid_scheme_id: + with self.assertRaises(InvalidSchemeID): + validate_scheme_id(scheme_id) + + def test_invalid_scheme_id_ampliconsize(self): + # Scheme ids with invalid ampliconsize + invalid_scheme_id = ["test123/40a0/v1.0.0", "test123/10.00/v1.0.0"] + + # Invalid scheme ids + for scheme_id in invalid_scheme_id: + with self.assertRaises(InvalidSchemeID): + validate_scheme_id(scheme_id) + + def test_invalid_scheme_id_schemeversion(self): + # Scheme ids with invalid ampliconsize + invalid_scheme_id = ["test123/400/v1", "test123/1000/1.0"] + + # Invalid scheme ids + for scheme_id in invalid_scheme_id: + with self.assertRaises(InvalidSchemeID): + validate_scheme_id(scheme_id) + + def test_invalid_scheme_id_structure(self): + # Scheme ids with invalid structure + invalid_scheme_id = ["test123/400", "test123/1000/1.0/test"] + + # Invalid scheme ids + for scheme_id in invalid_scheme_id: + with self.assertRaises(InvalidSchemeID): + validate_scheme_id(scheme_id) + + if __name__ == "__main__": unittest.main()