From 8b4b77124f2fe5287ac0f3d8e6170f2439ae4d87 Mon Sep 17 00:00:00 2001 From: AdamMiltonBarker Date: Sun, 1 Aug 2021 22:20:15 +0200 Subject: [PATCH] 1.0.0 --- README.md | 15 +- assets/images/fiware.jpg | Bin 23802 -> 44297 bytes configuration/config.json | 5 - configuration/credentials.json | 2 +- docs/installation/ubuntu.md | 10 +- hiascdi.py | 334 ++++++++++++++++----------------- modules/broker.py | 15 +- modules/entities.py | 6 +- modules/helpers.py | 11 +- modules/mongodb.py | 85 +++++++++ modules/mqtt.py | 282 ++++++++++++++++++++++++++++ modules/subscriptions.py | 13 +- scripts/services.sh | 0 13 files changed, 573 insertions(+), 205 deletions(-) create mode 100644 modules/mongodb.py create mode 100644 modules/mqtt.py create mode 100644 scripts/services.sh diff --git a/README.md b/README.md index ecbb72c..fa767fc 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,21 @@ # Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss -# HIAS - Hospital Intelligent Automation Server -## HIASCDI - HIAS Contextual Data Interface +# HIASCDI - HIAS Contextual Data Interface ![HIAS - Hospital Intelligent Automation Server](assets/images/project-banner.jpg) -[![CURRENT RELEASE](https://img.shields.io/badge/CURRENT%20RELEASE-0.1.0-blue.svg)](https://github.com/AIIAL/HIASCDI/tree/0.1.0) -[![UPCOMING RELEASE](https://img.shields.io/badge/CURRENT%20DEV%20BRANCH-1.0.0-blue.svg)](https://github.com/AIIAL/HIASCDI/tree/1.0.0) [![LICENSE](https://img.shields.io/badge/LICENSE-MIT-blue.svg)](LICENSE) ![SemVer](https://img.shields.io/badge/semver-2.0.0-blue) +[![CURRENT RELEASE](https://img.shields.io/badge/CURRENT%20RELEASE-1.0.0-blue.svg)](https://github.com/AIIAL/HIASCDI/tree/1.0.0) +[![UPCOMING RELEASE](https://img.shields.io/badge/CURRENT%20DEV%20BRANCH-2.0.0-blue.svg)](https://github.com/AIIAL/HIASCDI/tree/2.0.0) ![SemVer](https://img.shields.io/badge/semver-2.0.0-blue) [![Contributions Welcome!](https://img.shields.io/badge/Contributions-Welcome-lightgrey.svg)](CONTRIBUTING.md) [![Issues](https://img.shields.io/badge/Issues-Welcome-lightgrey.svg)](issues) + +[![Documentation Status](https://readthedocs.org/projects/hiascdi/badge/?version=latest)](https://hiascdi.readthedocs.io/en/latest/?badge=latest) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5002/badge)](https://bestpractices.coreinfrastructure.org/projects/5002) [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/) ![Compliance Tests](https://img.shields.io/badge/Compliance%20Tests-TODO-red) ![Unit Tests](https://img.shields.io/badge/Unit%20Tests-TODO-red) ![Functional Tests](https://img.shields.io/badge/Functional%20Tests-TODO-red) -[![Documentation Status](https://readthedocs.org/projects/hiascdi/badge/?version=latest)](https://hiascdi.readthedocs.io/en/latest/?badge=latest) -[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5002/badge)](https://bestpractices.coreinfrastructure.org/projects/5002) [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/) [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/) +[![LICENSE](https://img.shields.io/badge/LICENSE-MIT-blue.svg)](LICENSE) + -[![Contributions Welcome!](https://img.shields.io/badge/Contributions-Welcome-lightgrey.svg)](CONTRIBUTING.md) [![Issues](https://img.shields.io/badge/Issues-Welcome-lightgrey.svg)](issues)   diff --git a/assets/images/fiware.jpg b/assets/images/fiware.jpg index 45deff4bcd1319174e991a83a6e1a322873f5456..8ed1321eaffd1f477fa6a44a07577064f90973ce 100644 GIT binary patch literal 44297 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 z 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 F{{pV+gi-(i literal 23802 zcmb??bx_>RwrvRR?iNCD4elB&xVyW%I|O$R4DJlB!QGwU65IzH-1Fu;_uQ&?@1O6T znwqMaJyred>fXKfT5I=2DJe*yA`u~d`0xQ$M*5q|hYugMK74@sjsORV^d(ZvLE^O+2?f+9uevIAe=!h67~YNq8T^89&^6Ccpt+@6fHjs!-03_ zr0kj6n~HAu)1y04-$wEbK11S2kpEs|>cge}6QSZEDETjGfPiuwt-Wz!@(o?Z;89n% zhSDo{TA#AFB9*ft{Tr_;bcEinFBQ!G#J$i6K_JR(#-~7`u)LzJrI%~Pz8P|*amD%t z&)CPS31pc7(pQ>6VL83-YTCfA#M{srmJmPBj)zVUZXlMjzoi;V-m$=351?-O+E3n_ zu&3YB*C0nThLZWymh;e%tp%q~e+3 zkKLyhS?H=XMcT0Wr*)m*?2dtlx)on<6gIN3L#ev2J8G|Lncp)tTbIEKW1)z*C$LWU zofd@g**&iKLB%h_^0^-Z4ICf$(4!=PA^8Hv4t7q1Pedl)pL2B&`r4fGocz+1kXp8& zF+LyS;fY@_<8b22WECFR(P56jiG-#dsocN>e@W(!nQX?H>T{V8;@+yi-2hP8-p@N> ziy)+#8;Q$9X;h27YuWG5CVp1`rY5J8$SfYCfve8xo=mJ!mGv`AyV0R2O#{GTzoY}Q zzuiD4q1ErWTk<8RjOhCoS`YVtK0PTgQKed{0IBT7{&(6_-s==-%{Eyjnd9`{R;5}7)vP)8@H$(m!oeK8zD`? zKXl(cM`0r_dV=*nv~TU+BQLaoHheIA)eA;A@PG-vk`BRJ1l})|BY%g2n-+2!!u)-J z9(oE*SB{5Fu2qC0gBH7&(*h3Q0&chO!1bfYj8D=4c% zV7`Z_z;Q(WL93CbprM-frp0ad!;uq7De4`ohsGYmxtIKrwy|SmNp$WXd^a=u*zq@L zmofLewpvwmxzAi;LbJ#Q8GmYRMvKhw2_`RU`b-ze*>bPvY0Jw^HWR-n?KZz)Udl@e!nD(-M{gi z8ch3+>f@LIpGM&|=@fynAjGH%bh~Hf!{bQA;}qh1DIv2a>^SA%xMSUkOO zQ0t;T{0BM`=0%A>RYukcoa?Wb1H%b&lF9mcKAvCro|xQxT`8o58cNpg{{Y0w%tq#P zk=F~ZXf+8vlhY>H2#>vTSMe<^`EWc3jf!(Pk2h2SiCZK53pq|W=3mMSbEi||s+HKZ zFu7J~F*>7eJg9RQ&KMXH0CO6bB2+u2*+P|uO}~7WDAuL-o7w50p?3C8m25=gqOs^I z2^JT$G48cIgDad}*+XXsiUmM^8nvV;O7rWA^X1!$T?7ZNP(d>00pl^qlG>z^Lgp8A{{`o?iVZv?6XVJ zj|E<^{cY_9q=mn0tyQImBKTy^@0eE*mJ^WkLrw|LKJ<%ijEO5cTU63)V=;GE>=8YG zI)EQ)`br!LKR#)5RPbwRK`;llcX}txbD$yC_ zl%xiR9f>uy&go4U%zz7z9sFy2c$h$ov-z+_TtiVq=!T$(z8zrn0X z$tZ?>k9IkBOp&V}0Fi8ql@#7`82UK%j>&VRDEmKbk~?IHbBIaM>^hd3wfVuJ$17Cn z;E#eihfJjfZcZbNMmZ}4)o5P|lCW&xQVe|~v#6CHPN7AMh_onG)C5Sj%()*uHi4Ba z6n(7vBW3GA0fj!^p|WCQ3Kq0(`p zG8EuZ!GoE)x{r1mbJ+W;IiKb!f6M$aCvA{~EWLdwPH$JZiS3=4PG|Awk7U4n^c zV?P(FE6UaCNcKz5a9@YKZ5Sto;w4Jmy+bl+#xz5N;nCa&bM)g((HFV?)=wsbsU#M2 zRxeiPMw>Ei=>)KW0y3DRH5l%Ek8u3+f0Mrio2{c%C>amjnUa?uDnH+AgufrDyzb9J zdr%`*;1?Ap4Y<{*~$x z)iEDK(eGn-6^s|Gu)o3LQd=fRCILl|PDY4?Nu7vD^f7UKEAgp9F>!KO8m&1ZSr7r| z!{NJMu#I#qkFf#C;m`Lsc@P}kPX6aUjkqe@t(b!xIKrQ89~>xUs6Ooyqjr*B1f5mz z{|rztm%F?>^lOM+%&4cA8HlYCp1=4IFWkq8fFO(oJQM31h)2+(NoZyaTvBFBZzX>E zW_HrNTjbb5lMT%XLLc4Xa?<l+u+?H`R?(35Q}pp(N2;HY(EN*!FZV|vH>8-rk@_J6W>(66%(T4 zqQN1N-4_%nwPjy;T>9l;2}>SvC4`7XwT|7s3nEn~#wqEX$Ms+oR)?n7C3G%O9b= zo7?NBt)!31LRSTty--^sCh6m74KIslCoywY+aWnOBa@5kWnbR6^nQfrr`SZN3$_kZ_8a#d%u@K*QACfhy1dF91PtnjpN+M9UsvBWE|1KsQ+m;k_U z*{3X|1BRsAf1&+(3;R1F0zYQWIhO)%u_1a5D$H73TuDY;{J-@YM5Sf>CkaXqh>#2! zsx}wm!tzJ*m|5j8Mn>~|$Bi*6Xu{QTXKib0o-L-3adyVhLq-c=)F9Q%c6WEeh3P}b z;O3^0z^4NT$`nY%!jHUpd0sj{pkbVvmu8Xf1p&1s>B+Dg+i<^LMKj-;d{F-O#o{#T zRtzq84@4%^y(!_H{?H~Ym6yy-xQ!iUndzW42pXIRq)&V2@eFc(346@;n#@PW5_Zw| zc#3nKGY+;4kCn5D=lRBnx31-2+G1Qjs5)pYu}Et;+Fs?fxm%T9S>>(x-fv`}tKgb* zh_I{m?W0U&qOt^{xz>UvA`u<6N)!3b6*~X>mo_t(wBFIL2}o+v@#L@Q0iL#a)HEB# zMkvRA_2&r)7%Q+uoa^sGE$73C+ktR3P3b)<@D@Tcwaraen6o{odV0S&ywO`Xpu%(A z#u7=!6jf<)f*#nPOG#M0kZ-HnyEq@5pFfzDSC$ZOZ(|TJsoZCcbabryzC6e^_K;Ve zx^(l)AYwywG@Oa7)VB|R|9Rzim8C!;h)&Ylt{*-yQ~vXY@+uMbfCS;)WaK5_58#k7 zVQ}gdqR~El`1C>Mo2Z)C+Swn^RJ|kbqo+&xG}9vl4mM2bAS0O2-fTMM&uJ2?M6%Z_ zjg7u%g1#0W_g+#${O_h`f52y694%F8W-%>{QCL*bSVOebtidptktD>&4sJChjvK=5 zP@mN}+k+kIM1}H932@pO{s7w_JhxuDXNtUXk0^d%h#^o|#L)vp5dQi6G5w$KKVTUD z^#h{TKVSX>QLMUuJ?DRo`yY#bF+%*m=lkz*|6@_r1eH3m8ur6X5nm_FS6oKh$u8(*gz{;dj_H}gzo#gct(MK< zuv(!JaKNJ`LqYe{n3U6pm*!W}13w}mMSs2w!w`#<4c+z5r2&X$kAnpmM~2+=zNd?~ z*rZgi71QGln8)nl#$e70xN&G;r5j7s^Yq-i%N(BYF>Qw#KY&ou7i(O^bUfw&5%&SZh#rp=$ z0fSN~l#^IcP>DpQ#A-kMo!Znq1`L##cB5UvE>fWyDLM5~(?cJ*sS{MfomFC0LnucQ zRi~<|{N|PxS(9`N;($ZR5*IQFAYH#3RgBLi9ZOryWtum1xBAL~7lD#K6k276FpqU# zG*8Vor;FQ>77sf*0Z61c#a&5L#EWJ1?ZAYTQ94u})#R z7Mse1hA)qn%7hdjQ4nOPetdmmXNA((!YgLGk^rRr882&CGZ_O-n5&pU=S!v^K0F+b z;ps+W9Rss}UGT3x+x`559-VQSO0$_axAyALDZXr2mZXWKKe}H`+}wTCNS<*tZ3+`_ zSp3lqcloZIk2y1XNTj>J1^iht9+%gOcTg>iOWs^Yq>LfI@zcJkGNXvp7ZLHtz6Qu@b*sH+1XvXf{D!|wh*yRgmVv7H8qo`X7OGbdl!R9sZ! ztPr7(?c-jn`{#Wm09A>W3`UoliHt1DjkTnFN}^Bu^Rs3ao&S|=Ty2|!2hBSsX>XZW zmnGjj6DwJFy%;$;mS9k8BH>#Z2@Cm&@+pp?&37A&fp*RunukU${VjT#Q&h;fT06_y zcx9CpR_;BXUi2S)g<*6q=%d$f;YntyH|-p~*9#*x2bjiBnCKtmnkC+$ z8sm+Ga)kT+KtY8Ka~s;XYMIP+i^^$3>X60rWs2~SKfqQN)}#()jDxpEeL;M2du@+C zXhp#X-r@sErZjo{gEb@Sixwtw9W7Zg-Tr`!0NgdM zM=btoDt(fx-qH_$JFED*hOcs;jS?%@NaQlS?0IHnSJ_CzhvmmUEjJHqC8J=@ay~gh zW)A^xi(%+%^%zo0?@a=xI$q74pXbT0f+oOzN>qxA)7W`ZRIED?0T2lZ2gr4aYUyp; zGS~FBWt>%W zhZqX!7C!o)B zjBy?bfg=hLl3PA70*+!!slKx550~|Jfj=bNax^qi_^ti${V%Tc_(|*QS3@AT>TXyt zN}#Y#{#kI}piQstXQ2!01Xpv}*l)8Uya!zHDiXQGqgUU6gOQ*d6gFZ?p$q$%#%_BP z*qx}63N19LM(oOmZef+5b0s~iC^~@)czc_9RgAkzsS0rYEL!%^zFdA<3;D9_`6cqXBq{j6Faw8X9M_I)8oXhZ#65wx zWQNd2vA-9;gaFbtY06b02ikN%pxBOR^7_>)$&-N#4?}HxEvWCUTDis+&>HuZw|}pd zY+#5U5Yh1JLpxJCHu6*l*#x0wlEdJzIJcn?FE5fA&ByaO9AQ$XS<8+M%zJQ27;2S$ z6y~)xjvT-fuJN^rWuUZS%tU|BwOaW?D^Y-O;UD&cs73B-N|iDds+~(d*4Spn#=-k> zv8ckYfU18up65^fCs9)qm0w4F5P( z+wWmS#fxk@I)$_L>M8eL+l_vg(hDqL?Cwa!F@6s(A%Xx`q&C&EG&9A)n778vt>t4G z7%Obdjzk%!2I*L!{nJ=`Isv#B-W-u}q=Z}daJ8GGhk)TXsI_;{eDENAIAhIpV=b+i z_l)107@ang%aMHMTFdZx|9Hjk>%NblXR)*z`9VxZGo&W#P(lecwnn9%Tq#yyv7|B4 zchPZ3?5y3x1n8UF%i8U7l9BDLhKvcv}7AImpj;BaYxW|%EyrNfMA-9AO>0}~k&X!N9`55swsVTL**sl!mrH)5|+PH%O=F%ECwmK zTA6(OyJ+v+tz7>IGPQ;yNiZy(_>m9`K91JN5`Xu8%OV}=bJinS9&%MA57J1D`dfC@ zw-sHO4@4RtL|1%r1Us*0wAMtNdwZn_ii4DKm6T?wUlIa-l4B=Cwdmw>KM^UsKfwR> zSaQ{*&iw^+8e=qYhEOEHv6J(mZ&CK60z0@{?w$N}?2@OPR%Klgg0RfXHv|lsoeLR< z@FR^RJO^z0MZhbg&!b29oDIbkemb>+BX`GBsgDwuJi>};3dou?%whia+Q_oWJB{6m z>3i4HM(KY87^jTayUbb^>-L;^G!LT>kH;F2jq`?^nUpt2Q@xu7oO$n}z*rgRM{VZ8 z!HTMgL`1*bxt*Gfu04ye&1t%YHNIHHg2N7~%k;V4X37-!5fITLwU-uH)3=uvg815nz&)tqQp@fr;v<&FU$DQnx35{? zT>U;FY4b}`wInD9erF@3B#X)R3}VRQDx$)Eh|NSc7B8dQ2Qs%(Ft#+55lUT{ag+@;)IS@5_1oc@-#kZi76uXxLRNB#V>oh%(>+;*|Q zwQ2SVYW+q5`Eynw#;m$n*7wm_gp<>K+u5i`_m5e8G&6zJ!E?@nz!upWo1}`heVQ#m z@_|yWpJONc$MhP7A9ViL-()m#+}w8qZGj;Lz++&DqB^Tu*l>Qry_E=BF;?BTYSO>; zc2SnkzXgPJ_E+fOgUs8<2c|GDbXP-&w8M`#_&C7w8fu`|71BUl@hR=L*s)nI=a_NFS2 zGo7hsBH|<}g85MpEa2tOGosj#)<`a;u9cNTfSD5)w3x?mGf@BAdTR{3Fl+ododB~Z zKos6*`GzN*>E>Q3UcTaU$HaYAs@j+r8R5Kf$BEH?Wm7D^_Qiy=DcfLpOVoSrFs#S7 z@-=4Nk?2>WDxOMG3Zz`AzI$o~f(bl0Tq5-_G<|cK-tFgM3|k^fx1o=&KA#U-$wI@B z01+;oY#K&}L#12JmgmhnK?mMA0t6#_KcPFO*aDd&R1vjyZ<8wR2ejWu`o3=R6fug4 zDL;zI{(#5hOh$NCSSR%D4OR>8*<_!*DQQHP`_n#N#=lo(7rV4Um=!pWyf-xW5u{tlm`yTtZB3uSE!ApXC35M7 zh`gfV?zQ8U9xolVG&%%GIN)J^LA$+95cY{LGvF0*yx4`^%w(kY&_^F}2azakG%{aK zeW6`(cR2Ohp8mZJjF1`wp9;mF2lLBr!Oq_~mHS&R?RNr@`Fi=7*QKaWI|OxEkE*hx zVNrOM|rrfim&m*mYu!?-q`i_9$K2RtK@~*M{JP=-9ah$w^@>5;iJ?{JZuDNb*>N%iYnRLXD?A2^nUj(7A zYDvO+YyH>5s~}~Y4zkOH{l}QDfin(LL_u=Z-{5<34OaV2q1>Gwa-zLAc8}y3_cX^Q z32!s&3GJP6(?Y(Nm^Z@QsU|K!W}rtVjB}>%JM&g(Dz^nprBSV`GM-oW?xy@tQr%J1`mPjoXE zL>N`}!U;(t^=vVQ}N9~um3W5|GgS&URU(!*qBrW>ywvN%&1H6nQU0;5g z4&QjS8(mlb3GL)fEHE;7?4ETNypP9yoYxn(d&B5g|JFTmbF}gWusxv4Vb2BOt_dS5 zb~U;0Ll+A;LR(lneG>A6HZWi2Jyr#N?u{jVgr-3oO_`_z3*cGP8yY~WOx3$hvfhx% zx>r)4tlN>7OGF_Z%w3~+h@xr~A(3cU3e>{KdwizfxHnC+YZ$lA?&6V#77R2;6P?+$ zPH$Gh@m59(nq+ZtW!~4zwcy{=bFOd&?lHTJR*H#{u!wMe1jAvqVlk3pFt0*SVg_Qv z(bS3$CEg$s8Yz2VKQKt4%#fzW_OUri-Kx2?-g|S4LajtXMY@i}9ah)!Xl&}b-X=qDtDlI!*1m@-1=QSx-j`2lz{j@(h2EvgyMeIU@58JYSYFgbq-2mQyE2RTk_v@nZ5V2V=~)21r6 z!SF%kGK-ov271mbcTY`&0Ol^6VY(;Hz(vp=c;6_Um=D`kBWQtx>HdPh-?jFILSnvf3Rn%66919udI80<2~L`sysI#; zp!IbVH_6vQKRzgZ0KrL4Rf!q{Nj3sK&377<47p9BshgsDl__f&yt)c2mpiBUmdHSj z2>=Z4qnT`KT#hq}dQSOUu^gkB@{AWO&8Zph#h_~~53|Gsg?c?tcI~xK`(Qdy@IYYx z0mvl`nwiDn(E39nSerr*$u@R_{VBb-q{|Y^-w+YSRfWqP-JvW% zY-Nc-?lKU~kyGUT?sMI+o#}w|{J0PT$5+&sWywBJME5@Mt9yo+QbJ&Un4PpPJE|~+ zd80*O%sy{j#40)`XRnn+n5#D?p-Z5CeohT14^9YmaaPHX=zDL(62`!>)v%TIH(Tla zA=4Wh-Ef?+i^POP<(KBu@j>m&if6lwJ{{zbyQ1lEw<%D_+hNH=rNJENjMkYcM}~}P z-bY7dftNh$Z6(y4p5sDTz$&xKRPj@^y*dFc&DX6{H!_4iJ`^=gNCfv=3U=Oi`We`WH< zyM9#{bai=;dvlhWf91+8F_XV2w#Rp5)5`8%Co7oj8>66rWx^cn0U{SwqIQ*Fa*}+z zds0Gy=0KHDZ~bW=w$723YZauFW9&Td{&j|j8lsrg7Kc5$=7FmbC~*ua6al%_*)7oT zfeEE;B^|~~F$q4oBgaBf(1WwrY^=vxxBU&5e=e(Z%Iy?odBGneGpB55A-J-p=aZGP7~(YewZ|X0m7Nttg)Nb4(6lrbal=dKBftak!k48$LG!Vgn9?%kVOW3 zOd%=K&+8tvIOFW{Fgzz%CCd-cFUo#IK2juIm55z!P=bU~F%n7x6gWNTi>x9!>pQ3J z#^f=(%~H>LGn9^PIJVjDM@$*O^ZY!_EM`X(Y@sZnbC*%$b@OGbTwDH>#`e^8cl~fF zm0L73oA*~>4T9lw_74WpI1eKt z^d|1PG%4-qj7Vr6S$Zi2-CG6K)?OHiUJ?kCj5hoc@a0bz^s?p|!DX5wlt*q%?y`-!DatGgEM9XfOFZ`nmO>HfwH76S8qr}D;kr6QVh$rMe) z2tKMLUA$3CKSgAR=S-BdKHjf0Pg_A*HU*W$<^OO?mHyC0 z7rmK0cZEB5bkcH}_-k)@yYVtkn^^GP%_MCQ0K(iz`G<^7gA{5$EPDN0U zLne)hudvbcs*W+nje5qZ?cPiDljVz>9juc}G zRT$mx73Vlpegf&h5Q*OeO4v-+y6W4;GG)3iA1%1(t6026-7;W9 zS&uV9$xd?3+^n>LK5hA*QEl>P34@NRDdJu(^+vMPWkF<0F+*6oEf+^m`ljyWG-t~v zr&iP?XgN2$*6osCNU{OTpjIz$>}%?2qj(_OhjHeGe1)s!{gl8V&Kv{n%56WH0{ zKVI4iF%Jh+oBmcbe~^_hmKa30gsC!JjG`VSZCv@b*kE_9s#j`nBR85Mm0fDbUtGNw zEKp3(0f*{l?-0KWES$w)jkHBt@Iu;47q6do1vsin6bSBG+B5b7s6bViZLvplx*QcN z(@j#p* zos*Tx<>R8P6u%jtm9& z9_i@l5`i|Q7MoVOZ$8zY69TU5vr;H_!J|z;mrCGwO-p3%YWtq!^+AT8sfJsMhh;2D zz8CJ_*FB8M$orYnr&pb7q%@!GY*QI@O;zD?LQd*fbSZR?El&qsghqjga-zdNR+4(YOa<=Cg! z|C#3#%6{n(xC3QWhLQ`jg>Xj5P3)%0>po8J0c_s|is?4|Z;+O1w8LjK10Q+vSgt`w zoBWo@XX+CO}Ggo8BZS1|If1LAMdt~QqXT!nIHW~bYHuA104l@dWU218k} z~^3vN=hlZZjZ}-bA%7P0qN7(_iZS1Ep6q7dQ4oJIhF*9(n zhOP-F?pd|hW|90xK&3R4O8)0i1YzAzT45FbSG^x!u}a?4US+|RkUnfMAIs?VsiQ4L z>D^PZJS%rY+egRlt`yIQ*oC(ocZPNCw!)+A0;`D@|9P7aRg-lIX@#_mhAoMTn{DY# zQU+be^5HWAE?&6AmaGZvsb%ez~*vG1w+TnT)MYQG4Lklhco)1HAzL-mj&Ij=Tz(&BoBC)15^q|WBtZ) z*sP2GW^y`LfrGt|?0%@PM*WiQ?V`ajC}nDRE+4D5SV-p* zgo7_BS$8N>ptRd%|KjEO;Phv!&A~c3jR6nyN>QKPLLklDtBJLGWC~X z_B6xu%BV_(ABR6Tf@$Y_WC9F&IUm{(ALSoq`Jr0bK`gAg!4U;Uonn}Dw%aBd!EMykF$oi-aRfO z+EHuu3HAVn?=akY9pU#a7O(FoxXdKunqHlrofgpNDSh(lZH$;z9Utcg_MGObd|N=R zxpHQ>yOh1of-dS2emP+bGWt0W>L+0kRYCroS zV|t%(M!gKbu0dT=YW`;_=ed$gi1BfiLE8`EXP?CMLjvIdANZGHdVKqiFpe&@;85d;iaD(W%)2oP73tnA?qOBJ?uU8qvTWAwI;u(F1YS;K4ys6-UV&1k!A!5* z*sj$U>yU}}FcdPiWJ5aR=`N_}K_;E$d>3rkHRP%bf8=-lG9`<>;W6OCwb>NUS(%Q3 z25ZUjQL~vW|=j@-V62shKS7(K%Zp)i2ydpw8EcOnMdRbwiIkv(`PfYuru(+jC5g;$H|?Z#V72(bS&^~|xjTK@LtHWJcM0HNql zy*C0a7b_$2eY?DYo=?fs*04wt%2!V)A;Y%`$Nca}o%P=6pc*X&%euP|LrvD>vc{sY z`xfdNgPv$P>X8+%bT#@3FFslN9GWy{BW=8c=&(Xsm{&fbyacn)a%`9-6oizR3A2f@ zDRRRIu+UanJ_eL_Gy{qDjiyIX?}r_V;DzOmPAfQ zbin=eed^LDq0~jzHg=X#!Rc2}*U9f}d{a)fFi3N?+z{;?t4>G^X^8FeQb3@3>)gsQ z_x>$>K(s%=|C+(afof#0BIChz>RFJ3n^SHz#IhYnLUf)(Of*i3RYb}cxdvpSeLqOP zK71pvG1i6x1m@Gzx7QR9^9hOfaj*)nRbwA-@;zJzXF5Au&FzIW)d(%G-Y2&Pl` zz3KQNcPPCH;aSn6w~Rol&1t-lH`(n0{aNKw?j@cIPICA28}d=V=lhBJF6LIYQ25Mj z>q)!$E0~*1G-WelmvY=i?>BA?X_0rp*TjQNK4k6B(ftEc#>}=TMN=;81()b%QD_H8 zx9)H_t#pLD#2KFB0A?sqk*S$kcBoYN%#&|5X6PV_Br*~4F2@iwE8E+%2K>Bw_gg5w zc4v7Cg9ZyAWw{baAJB8zNj+~E=~plaaCAH(4|v6zv9GRInFq6aqVJeR|8Q*0UO7G3 zm|q-hJ_pkcD6e{~XWG;DAWHv6=Y^^XJ>! zn$S9|?}cxs1L3$#Z*tJx+z!O58rZufo5{ZMTxO1)u~Mt3_$vCD=qK3K=gleHG4KK6 zV&nw`U)0=NS8HW2^UKXp=8SABd)rB_6)1A1ujy;ynEqr(SccAc-51bh%2MeA8}C$j{Q z{e1wK`_$keT20n!t9M(cE1&QJ?aFaI1ye7K|<&7~!(I-{V9zkq*^n_cQYy^b#90nr^<8!uVOqvWkQekh@FJ69Gv_iN zS4FG#gj`Jwj^W=rW7qVYUE}yw`SKc&V_T!A@M$}PrSWkEf!@i#cRT(NVfok4aWw3a zpWAbMqUa6Dugy92Fk7PjYdG27XDcWD1iu1lde!WuOWmcx=#ES)Tie~84i?L0Q@{*} z_kxJ-<~*-+V5f-E@0~qwd#!RABfAWK!KJM~*I&xZe4Z;ez#6~f<%XTv;dyqr#P9SS zVC87bcVuPUZF940l>RVPF#tX_J1*o7cso7fZm>B>=IscCopp;d`|L4hAcSX9szF4Q z=NRYWE}IHl|A9h0?}k?s?keo!3j^^H*K2vS%k)zzWQ(Z=ZUm6R*ecF$B{N*YkScgPL+Chz?z{lFt{qy)rXYU+&XuYG?W6a22*=>N=dg3M=8n8Tpo`5Qs{ui>2w2xn=Q8(8)>f8M zKd1Vd3Y(oTafTCwwZSY3dlC2CRx3DtT8iM@6RfDoVxt&-m@lgPy|Y z>?p5PKuu4!XA9}Uuge6ztDEc&||kDE8FV>#x$ z9Yi5}+vgK<|0~aUdXlx|+RO-e@Fd@-QS9t1h?Qxz!|L>dwK+Z$13Ym5IF1~z>;Cyy z&Ad?YP;Z(h3-ltas-+nPA8V~YZkqu2_jIiD5@b&Thi|1SOJnoHuy)@fezQ6Q`S!M5((mo0t;Q!n`Q zIMH-B?%7SEvbL|HQc~FVOyn@kJzEhoj@#TQcFRL8cJ%5g-%Gw1D49T1ksHSpxWHNE zghZTD8||TtP*!s^ok_*~sPj;B@%m1xW+x`ED<%DKze!dJ1uYJILu0sC;!mfP|G4&& zU-6?xD{A*;`??QF-(1}Gw(y=DM=blOPfoqHfW7bNH&u5Y{o-rR)?pLy-{e5S7)m{Jr@D~k3@On+ef!Dsg=aSe$rdU>g^b*G3!w1KMM)W^IMN!XT zVo{a`UNM2*#(xKey|!cVXuF#rhQSxFRh3yalMc!znC#F-t687%uLR!1d6Z-ePzbgB_ z!akYY4`qjpquEL>0^iLo4eUA?*2FftjlZIAGzQx))ru{b{l#QX8}Dp55hU~7J#WNA z;xUy#V(AV&eb~`mn99mefKdNI^Rb)G9Us5q&*>qb;UBe2>5-84M1}rf89#c(Q#_n0 z%0XI$Ko)sTMiJFfmdoIu9WzCCA=)dEKrt^k&gA%boYnf9PwDOfHwhf-2Q}+8N9g2T zIsD5ln7@U{IwCukIc`P_-(U=1#{Ko09S&mq)2wyu*6Z`~UR|(tpOHBON61E#ec$ar z`a|nsvuVv?q)&w6KW+0YO09xB36~RJ>g4bInu*?S2g70>-D(`78Ys>xbz4ka`^?sz za)IZgaM+L&2)3pb$`kdqd!{6Pg9S`ok{a0wxNf2oqdd2rguB0P;ydizPYACyC+mn4 z{e8ayJqf?>8w+_~PBH4N@D_!yLi(tle=!4Y8#BC|K?{PU=S6#N_FGe&q6wvmiqj4( zpY_#eOILQ|;WKn(ZRV@V>)a$nRn2g0TdaZo{a%rPtbUFqZKf4l{!T8PZLfescf_!t z_d@bqePOxol`zl&M4yGE)yw=l$_#}kEy+*I+IYPU{I_u=D9B>!z@?Q4Fs~$kC-N3a zGuu-C)HfsrzWI9A?W{3G4t-`fm^=0+>l%r_G5mWW12LxlZg36?Xl;?~1H+ieqq@y9 zu*B8Di@cxKf=*gnYdk&vPbb#_4A=hsNg`f|IwR-vAmzn?X&3p5kH}n6`+_`h-&Y3gkbI$jE z?>XO7(h0#8cJPgO+Ff&PR~2s-&I`Hv8zmJx z<=f8_hA)s#hs||5RiGa(1SdFVyD!A)9Ck&XSU|$V zMbV+8a0o&EQ@cO))mWmiLgH?7Jdj#=y;q~$r-@Kf0fS^wZ& zmw)5a$sv7X-NO#YJ^RSt&KvPT;MdyQ1$>l7*9zIHV?^thtYcW8UCw-A-&f-?F@LC0 z)*TqbDCgnS3aQy6)v%&@w=YpJkJ$ksA*L2jtz-?&oSwWa`sVt>748!QlJ_crJJ5^w zaX`dd3Y(%r*IT04FzgTeaH=W?-w#yk5w6GUXE=1nf=J9bXw4H~YZ@3E)RbjbTR-5Gx$sD!;#oNs=A{R5o`?+?$5ord&7P5v$p z7ToEx?j)QzpAMh*Pho?cXH5(wk&6^kqs6)$9UxN8+2UTHP--`m~%y69U_@g3RY?+!q|#{TqFcM#`Ju z81INk8alaSybJdkE`Ptmgp2YPT#YD_O^93?iJv^Dv77;X_ zI=9~H&9^hv8s*F{!Mk#C@bMkeB?CrIId1*5=)mVdc8M*1Ls|6z2u?m%IduPqd+U*~ z3wTML$NF)qa??pbEzI+%^YJViA<-rns&(Bj%Y;XC<^@iz8@N zxT^YNaWoqy#056ifONYyXF6B&TRc0$mne*!t5;~)ATBl37Ay2^yxq(DHVA0dUk+%& zN6-fnBG`x$P+fi=Ab&E{K&oEu^Ew=9jO>`89~z2CP>@KZ3>;mtYANx+g-XR`EGM3o z5;{F%O|OY@Xz?86rbs+D@Bm7sPrHyFu??{@N1O)h8USaarFPS-3u)%hr#JW)xSF4) z8wlZfumjB7EXvq(>t&~+lwYri6+uizfX*1@!wB{4>^@oHXesl|4N@*a?Q5?vWRVa? z8IqO1=0Xa-5vK{jg=^3=Hctf4?kK*Y zeOh@++#yZE)7b&Uf=LUg30Yu9DciW1yUYR~t3VcpGKX0wa}Xk2blKKI=guZgMzY}{ zt(l+b690%}#4~UE6++>D2{EmDVzC8#4CPq@t}BNSDojZtwvKHZ$9ePSqkFmc9%ia; zf2CWakBWKMo*-`8z*g2`?F6wKi!Rn>sPKAGU8EEOp@o>S@tX~6eKYqAmH}{H&TT4T z$R_pKHGF)^vGW4EyA2#ma7eBdHJ3k+T_Xfdmos458eTo;RG4}oh_|Z`$%oEZWhM`@ zMr+M)@$x!3i|I)~`JffAL@jSvcy=-hHN|O-Y?H|*CS(r=zFd8+Q9Z?yON<^UIYwnkVUd)-bjTm%YuT5ITov(|Eo z#;#?bizo6uf%Ga7E`aRXHq2mx#c%&SykG?+pS&{e_a!TWqt@PONMCN8>`mf_&dS6jVIs1qCr3MY=O8tIc}WNU0-=KWWR0;_h#VOy{(s85X`03 z7HnHw;oiK;BS{X(zB|IFGg}+2$l;7NlfXa6GpD9oA-2^ZqF9jMz;xwcK_pzJl+)YUFhKIPkgd9z=$mGcs$M zPxAoEPyF`ZruMvZ=2X5U#DhTyk0%>`*d>!&#z|`T&M@nkngAVVq8x%HACX-%1K0m_ z_0eIAsts55jXeryN_nO}KC24f;|XC8=x!+wE)rZ_0{U~%S44BDbwIn0Q~2d^O*LG7 zXNFJHC{-oVS^qFZ3PkyXaX?y0_)e-xq-kl~=T|XONdcW>;nytuQrsGs$-}o}bT<9~ z*JPwl1KT@LPu`(cxfqIWb*C7vE9OFkbY7^-=PHezse>=idADuMWHO?X@E2TmeF&jB zOOZSBW0M_K-;iYtnLw>F7fS6?+r1ZeviFy?rG^kpMnfhR)chyqjy2z!*GY2|gd@5W zd|*TuV`wM*DpC!dmsJ%>^|lnV4Qi)$eO;vxMHCvetu5hdm1O0LVhnywIIJ#{sEFud!uvd5gpxXeOkfmN(mI$BYsUw8)$x=e)vuAU%vCX00&#m3UA z#pfqJkH*j}N-bKwz8zp)d+v%z77$SF(QhjJ(Aku4uFs0Dd|%>xk^K~-fjq{cZqgG`LiJA|T91_|1tQ4u^dVT`J;Smu>!y?N8!I)e+culFF)c+mzPwyXEZ3|=IyV*7 zysBh*S~h(HFL5h)|FJm4AaZabKj@L)IuSR)`d#5xK9_p>;8$3hv7!T>N*qi<70}A{ zIrOIxoZQR|teRamm9v`;Pb(~~AdlUO-W6jx>ZjLD)3Nuz4vh-+I&p5kp-j-I%u;^d zQV7`0HM9pu^Okgd&zWdgzOtS>9C?*92VawD$SS)xY^yAzQmDt+p?z|4DA%^*NrbV2b@h!$4L)G(!}FL&Sn2v2xqSMEY}v}`xF&1{ z{!Jjb)v}gq(_{JC;kpYK1J}OulafVLEx2?Q)!Ur#s1`kg@VeVYLM6giHp^<@_FrF~ zis7ruGiHC~ZwF!n>EA*!Kpz_3U8y%bx(hP59p`@m5vS#=D$M4-IhA%}d2ICroo)ND zkmJ4}lc5VD)OapVxaG<#l#_<)nBi$RU?iW!pK}3*r`|8szJ_v6vLu#2eDu5}|m+(XN89oZ{QzK%VKsTRyx4acrc` z;Hp+(Q#-r`kmXJ7{rQu=*ncLWAY}Ga7X>wyNzT}xR}J5v&Ydb2&%%lS+_$x|yNL#c zg5-!{^+l1nz0iR7vyN_fix66lfqKEQItL-}k#DoOR#N>_1Sn9`v>z?lE_yf3o@|?aZ$h^mi3sowEL|5UqqGwa?jZ!6<$k?1fO0u(t*(qXX zn=-@MdZ&Z4Q+~vk_tgEIA&m@@K?tcrzmvnb3*1VTKC2U@UbeW>dO6=d!|y;m%a!|P zu;!xi@-3w&@faWleHGbGFzhpMn@D)OURlwH`J>`Hxixi$h1m=Pw_bb{EfFO&cMH z8cx-rbWC&gP7vEIkQ<{=#Z00yCZ< zT*CyFkC1~M((rcWk^Z&0+O*lC8YEE!dfkouqk200M4KQ@Fk`u=|E9{uw86KLdK zWft|PKwx=j2QzrdJ>Yq~Q-A;W*9kEhtf3S1VUty;~Z&qIVlA%Nu%=P|jB> zGlxGCoeL3wHm$zj`aoRSD}5^u?RXNH3v&m7?6UcC5 zu3g(qA#)Rr#BOyJ^dEx{YIxho$jj;^-=O6}ezzQxn^|{x!_4HO!oHYUs{`7bDJ`}17V5#Bp_j@nX_mUD z<58PUZk(tl8RWW){{D#J?5mwEH$|>qiz@FQ!yXwc22Zx8oAsi_bFnmPfpG5N#S@A+6O4UnTyR#Vz7DNJ2zwAK@6)0I z&h~PT)B8b{mp45|o%-Tyr<7O#*dZ*h#96wau7E|^Kga~Fw41DUwNJd8G{mU-s}?%D@)22i`9i>;Z{s6- zGGjtQlt-pFCDk}B_v7yj*$!wFf0PdIo?&X4a+WwI z;H`mfLuNDt*pB>cXs1$BZcK3XrCA`M`~DC3XGY4b9`uWrK@)(T&8&1zaD931<9b3Y zD-|25^4S**1603+;gU3$Giyj~B#QLs*fz{ZFlWT@Wk^e0o~3XmptIaPMHg1(63G&3@yeK1j{9JnnE`(j-cshz}^QYBT_I zSH^V-Fq}QLc@Q42e6h#ZZmgkFzrO5#X}+OQ)$`K#x>%#-Z{#P5;j%>vNycuqAIb4r zYc^~n$IS}M?FRwnky9%kn|w5OIp9F^%3HPEAi1R>RT(3#y1bFmut}Nbub)S*qJ2G! zeZxQxf$6EzTm`HVn3OK*G57_)`+<{VevQ7~N=S)wOW5gGatX%x9uyfqB<0+P42N;e z-LN=C+@C`s`x6a+78{>fo@~bN$fke~o5`w|lJ=DaYzEm!GN%Km$Q2W~UR`F^y#CjM zIBiXar;i!VhDldB9;eMKmYI98#lzU*1z~={g+sxDSh%cO^6)@+if-rqOha~A6^jAa z2wx%LSTU`G2E%_AG=21tSWP4nUz?%9nw`f!$Kt0 zLIP?oWP4xPcDP&Hk~e8V&ic?jcXOX|>tL%flKK+2vs2r~M)T+sil+%#KIr#)zsF2+ zr3Zdda=fJf?H7zwiPFF*`&XyT&CqWKJMIsz98`>4)vHI9KPHLozCUXE`8?=@mr?vN zxs6u!a3DKQ=L`PGQWF@kOGGIsr4Gwrc?V?B5m29O!*KBeYm8~P^oKx?*tOBAD%D0G zeCHJ(`#NC2Zq(DWctmQ?kFCX9U0WDvYiTYwCx30*SLBE(3GGpEJd@UEgGPd$YkOKF zkLYOrsIKmgE3Cbjqg)PE+d&;u(HA9qhY*-4;2%O5Z#3PRM!ep~S6Z5>gn+$6m;iby z`owvWEDW)U4Kbiara+?hh+~Zu?h`aKB`(4$e zNsU%t*E_;pluR)&RKNyU0XTmh#C)dwRQQ4Xl<&kNW+T&kzyzbHr+XPWcbCb7N!0#4 zmJrIoOUTLf;65yt%j(h~j!GJD@U55kSv5OBjktT=m2&)r;6a&%?U2k8Vr8r30yobv z1+7|yH-;licQ|X$lOS0GIa40>Zdz5|KS`Ewy?DpL?tG*?usWg;+zHu?o_g)0xToPOB=7GlIr5b@ zV@QNYh`$y=U#Q|V&2M62;egaaS!U81gTvo~6O6CP$>$9J@V6Q2dE0NaAgf|MU{Zvd zshYa7wp~d2yXnnHh+<}nc?7_H%b89?O8gX9KFiQ-7~{eT$UdE z@3mFl7X?}MJ%5(zc&WqDL8pLEN5G*Y-$CcI#$Fg?59p_9f#lGf0W=VH9?yLOoH{!Z**^3ZCa(B|MJp- zQ`#uH$(#n@1B}3mk6oIhr2#YoJfC8u0ZelWc#5KM>X*3KNK`)*#B=7 zzgYgo;}^@n^Y~Yazr^~*<1exP!{gs6{v-H*gZ7KZzf$}q`2R~i|A6)', methods=['GET']) def entityGet(_id): """ Responds to GET requests sent to the /v1/entities/<_id> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if request.args.get('type') is None: typeof = None @@ -270,20 +266,20 @@ def entityGet(_id): else: metadata = request.args.get('metadata') - return HIASCDI.entities.getEntity(typeof, _id, attrs, options, metadata, False, accepted) + return hiascdi.entities.getEntity(typeof, _id, attrs, options, metadata, False, accepted) @app.route('/entities/<_id>/attrs', methods=['GET']) def entityAttrsGet(_id): """ Responds to GET requests sent to the /v1/entities/<_id>/attrs API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if request.args.get('type') is None: typeof = None @@ -305,24 +301,24 @@ def entityAttrsGet(_id): else: metadata = request.args.get('metadata') - return HIASCDI.entities.getEntity(typeof, _id, attrs, options, metadata, True, accepted) + return hiascdi.entities.getEntity(typeof, _id, attrs, options, metadata, True, accepted) @app.route('/entities/<_id>/attrs', methods=['POST']) def entityPost(_id): """ Responds to POST requests sent to the /v1/entities/<_id>/attrs API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - query = HIASCDI.checkBody(request) + query = hiascdi.checkBody(request) if query is False: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400p"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400p"], accepted) if request.args.get('type') is None: typeof = None @@ -334,25 +330,25 @@ def entityPost(_id): else: options = request.args.get('options') - return HIASCDI.entities.updateEntityPost(_id, typeof, query, options, accepted) + return hiascdi.entities.updateEntityPost(_id, typeof, query, options, accepted) @app.route('/entities/<_id>/attrs', methods=['PATCH']) def entityPatch(_id): """ Responds to PATCH requests sent to the /v1/entities/<_id>/attrs API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - query = HIASCDI.checkBody(request) + query = hiascdi.checkBody(request) print(query) if query is False: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400p"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400p"], accepted) if request.args.get('type') is None: typeof = None @@ -365,26 +361,26 @@ def entityPatch(_id): options = request.args.get('options') if request.args.get('type') is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - return HIASCDI.entities.updateEntityPatch(_id, typeof, query, options, accepted) + return hiascdi.entities.updateEntityPatch(_id, typeof, query, options, accepted) @app.route('/entities/<_id>/attrs', methods=['PUT']) def entityPut(_id): """ Responds to PUT requests sent to the /v1/entities/<_id>/attrs API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - query = HIASCDI.checkBody(request) + query = hiascdi.checkBody(request) if query is False: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400p"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400p"], accepted) if request.args.get('type') is None: typeof = None @@ -396,38 +392,38 @@ def entityPut(_id): else: options = request.args.get('options') - return HIASCDI.entities.updateEntityPut(_id, typeof, query, options, accepted) + return hiascdi.entities.updateEntityPut(_id, typeof, query, options, accepted) @app.route('/entities/<_id>', methods=['DELETE']) def entityDelete(_id): """ Responds to DELETE requests sent to the /v1/entities/<_id> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - return HIASCDI.entities.deleteEntity(request.args.get('type'), _id, accepted) + return hiascdi.entities.deleteEntity(request.args.get('type'), _id, accepted) @app.route('/entities/<_id>/attrs/<_attr>', methods=['GET']) def entityAttrsGetAttr(_id, _attr): """ Responds to GET requests sent to the /v1/entities/<_id>/attrs/<_attr> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if _attr is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if request.args.get('type') is None: typeof = None @@ -444,259 +440,259 @@ def entityAttrsGetAttr(_id, _attr): else: metadata = request.args.get('metadata') - return HIASCDI.entities.getEntityAttribute(typeof, _id, _attr, metadata, False, accepted) + return hiascdi.entities.getEntityAttribute(typeof, _id, _attr, metadata, False, accepted) @app.route('/entities/<_id>/attrs/<_attr>', methods=['PUT']) def entityAttrPut(_id, _attr): """ Responds to PUT requests sent to the /v1/entities/<_id>/attrs/<_attr> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if _attr is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - query = HIASCDI.checkBody(request) + query = hiascdi.checkBody(request) if query is False: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400p"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400p"], accepted) if request.args.get('type') is None: typeof = None else: typeof = request.args.get('type') - return HIASCDI.entities.updateEntityAttrPut(_id, _attr, typeof, query, accepted) + return hiascdi.entities.updateEntityAttrPut(_id, _attr, typeof, query, accepted) @app.route('/entities/<_id>/attrs/<_attr>', methods=['DELETE']) def entityAttrDelete(_id,_attr): """ Responds to DELETE requests sent to the /v1/entities/<_id>/attrs/<_attr> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if _attr is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if request.args.get('type') is None: typeof = None else: typeof = request.args.get('type') - return HIASCDI.entities.deleteEntityAttribute(_id, _attr, typeof, accepted) + return hiascdi.entities.deleteEntityAttribute(_id, _attr, typeof, accepted) @app.route('/entities/<_id>/attrs/<_attr>/value', methods=['GET']) def entityAttrsGetAttrValue(_id, _attr): """ Responds to GET requests sent to the /v1/entities/<_id>/attrs/<_attr>/value API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if _attr is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if request.args.get('type') is None: typeof = None else: typeof = request.args.get('type') - return HIASCDI.entities.getEntityAttribute(typeof, _id, _attr, None, True, accepted) + return hiascdi.entities.getEntityAttribute(typeof, _id, _attr, None, True, accepted) @app.route('/entities/<_id>/attrs/<_attr>/value', methods=['PUT']) def entityAttrsPutAttrValue(_id, _attr): """ Responds to PUT requests sent to the /v1/entities/<_id>/attrs/<_attr>/value API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _id is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) if _attr is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - query = HIASCDI.checkBody(request, True) + query = hiascdi.checkBody(request, True) if query is False: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400p"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400p"], accepted) if request.args.get('type') is None: typeof = None else: typeof = request.args.get('type') - return HIASCDI.entities.updateEntityAttrPut(_id, _attr, typeof, query, True, accepted, content_type) + return hiascdi.entities.updateEntityAttrPut(_id, _attr, typeof, query, True, accepted, content_type) @app.route('/types', methods=['GET']) def typesGet(): """ Responds to GET requests sent to the /v1/types API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") - return HIASCDI.types.getTypes(request.args, accepted) + return hiascdi.types.getTypes(request.args, accepted) @app.route('/types', methods=['POST']) def typesPost(): """ Responds to POST requests sent to the /v1/types API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") - query = HIASCDI.checkBody(request) + query = hiascdi.checkBody(request) if query is False: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400p"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400p"], accepted) - return HIASCDI.types.createType(query, accepted) + return hiascdi.types.createType(query, accepted) @app.route('/types/<_type>', methods=['PATCH']) def typesPatch(_type): """ Responds to PATCH requests sent to the /v1/types/<_types> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") - query = HIASCDI.checkBody(request) + query = hiascdi.checkBody(request) if query is False: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400p"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400p"], accepted) - return HIASCDI.types.updateTypePatch(_type, query, accepted) + return hiascdi.types.updateTypePatch(_type, query, accepted) @app.route('/types/<_type>', methods=['GET']) def typeGet(_type): """ Responds to GET requests sent to the /v1/types/<_id> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _type is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - return HIASCDI.types.getType(_type, accepted) + return hiascdi.types.getType(_type, accepted) @app.route('/subscriptions', methods=['GET']) def subscriptionsGet(): """ Responds to GET requests sent to the /v1/subscriptions API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") - return HIASCDI.subscriptions.getSubscriptions(request.args, accepted) + return hiascdi.subscriptions.getSubscriptions(request.args, accepted) @app.route('/subscriptions', methods=['POST']) def subscriptionsPost(): """ Responds to POST requests sent to the /v1/subscriptions API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") - query = HIASCDI.checkBody(request) + query = hiascdi.checkBody(request) if query is False: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400p"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400p"], accepted) - return HIASCDI.subscriptions.createSubscription(query, accepted) + return hiascdi.subscriptions.createSubscription(query, accepted) @app.route('/subscriptions/<_subscription>', methods=['GET']) def subscriptionGet(_subscription): """ Responds to GET requests sent to the /v1/subscriptions/<_subscription> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _subscription is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - return HIASCDI.subscriptions.getSubscription(_subscription, accepted) + return hiascdi.subscriptions.getSubscription(_subscription, accepted) @app.route('/subscriptions/<_subscription>', methods=['PATCH']) def subscriptionPatch(_subscription): """ Responds to PATCH requests sent to the /v1/subscriptions/<_subscription> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _subscription is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - query = HIASCDI.checkBody(request) + query = hiascdi.checkBody(request) if query is False: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400p"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400p"], accepted) - return HIASCDI.subscriptions.updateSubscription(_subscription, query, accepted) + return hiascdi.subscriptions.updateSubscription(_subscription, query, accepted) @app.route('/subscriptions/<_subscription>', methods=['DELETE']) def subscriptionDelete(_subscription): """ Responds to DELETE requests sent to the /v1/subscriptions/<_subscription> API endpoint. """ - accepted, content_type = HIASCDI.processHeaders(request) + accepted, content_type = hiascdi.processHeaders(request) if accepted is False: - return HIASCDI.respond(406, HIASCDI.confs["errorMessages"][str(406)], "application/json") + return hiascdi.respond(406, hiascdi.confs["errorMessages"][str(406)], "application/json") if content_type is False: - return HIASCDI.respond(415, HIASCDI.confs["errorMessages"][str(415)], "application/json") + return hiascdi.respond(415, hiascdi.confs["errorMessages"][str(415)], "application/json") if _subscription is None: - return HIASCDI.respond(400, HIASCDI.helpers.confs["errorMessages"]["400b"], accepted) + return hiascdi.respond(400, hiascdi.confs["errorMessages"]["400b"], accepted) - return HIASCDI.subscriptions.deleteSubscription(_subscription, accepted) + return hiascdi.subscriptions.deleteSubscription(_subscription, accepted) def main(): - signal.signal(signal.SIGINT, HIASCDI.signal_handler) - signal.signal(signal.SIGTERM, HIASCDI.signal_handler) + signal.signal(signal.SIGINT, hiascdi.signal_handler) + signal.signal(signal.SIGTERM, hiascdi.signal_handler) - HIASCDI.iotConnection() - HIASCDI.mongoDbConnection() - HIASCDI.hiascdiConnection() - HIASCDI.configureEntities() - HIASCDI.configureTypes() - HIASCDI.configureSubscriptions() + hiascdi.iotConnection() + hiascdi.mongoDbConnection() + hiascdi.hiascdiConnection() + hiascdi.configureEntities() + hiascdi.configureTypes() + hiascdi.configureSubscriptions() - Thread(target=HIASCDI.life, args=(), daemon=True).start() + Thread(target=hiascdi.life, args=(), daemon=True).start() - app.run(host=HIASCDI.helpers.confs["host"], - port=HIASCDI.helpers.confs["port"]) + app.run(host=hiascdi.credentials["server"]["ip"], + port=hiascdi.credentials["server"]["port"]) if __name__ == "__main__": main() diff --git a/modules/broker.py b/modules/broker.py index b2bdc0c..9427959 100644 --- a/modules/broker.py +++ b/modules/broker.py @@ -57,9 +57,6 @@ def __init__(self, helpers, mongodb): "content-type": self.helpers.confs["contentType"] } - self.auth = (self.helpers.credentials["identifier"], - self.helpers.credentials["auth"]) - self.helpers.logger.info("HIASCDI initialization complete.") def checkAcceptsType(self, headers): @@ -113,6 +110,14 @@ def checkBody(self, payload, text=False): return response + def checkBool(self, value): + """ Checks if a value is a bool. """ + + boolList = ['True', 'False', 'true', 'false'] + if value in boolList: + return True + return False + def checkFloat(self, value): """ Checks if a value is a float. """ @@ -130,7 +135,9 @@ def checkInteger(self, value): def cast(self, val): """ Casts relevant values as float or int. """ - if self.checkFloat(val): + if self.checkBool(val): + val = True if val.lower() == "true" else False + elif self.checkFloat(val): val = float(val) elif self.checkInteger(val): val = int(val) diff --git a/modules/entities.py b/modules/entities.py index 88e47b7..3903614 100644 --- a/modules/entities.py +++ b/modules/entities.py @@ -39,7 +39,7 @@ from mgoquery import Parser -from subscriptions import subscriptions +from modules.subscriptions import subscriptions class entities(): """ HIASCDI Entities Module. @@ -57,6 +57,8 @@ def __init__(self, helpers, mongodb, broker): self.mongodb = mongodb self.broker = broker + self.subscriptions = subscriptions(self.helpers, self.mongodb, self.broker) + self.helpers.logger.info(self.program + " initialization complete.") def getEntities(self, arguments, accepted=[]): @@ -661,6 +663,8 @@ def updateEntityPost(self, _id, typeof, data, options, accepted=[]): - Update or Append Entity Attributes """ + #self.subscriptions.checkForSubscription(_id) + updated = False error = False _append = False diff --git a/modules/helpers.py b/modules/helpers.py index 7f08572..1b40a85 100644 --- a/modules/helpers.py +++ b/modules/helpers.py @@ -65,17 +65,17 @@ def __init__(self, ltype, log=True): '%(asctime)s - %(name)s - %(levelname)s - %(message)s') allLogHandler = handlers.TimedRotatingFileHandler( - os.path.dirname(os.path.abspath(__file__)) + '/../../../logs/all.log', when='H', interval=1, backupCount=0) + os.path.dirname(os.path.abspath(__file__)) + '/../logs/all.log', when='H', interval=1, backupCount=0) allLogHandler.setLevel(logging.INFO) allLogHandler.setFormatter(formatter) errorLogHandler = handlers.TimedRotatingFileHandler( - os.path.dirname(os.path.abspath(__file__)) + '/../../../logs/error.log', when='H', interval=1, backupCount=0) + os.path.dirname(os.path.abspath(__file__)) + '/../logs/error.log', when='H', interval=1, backupCount=0) errorLogHandler.setLevel(logging.ERROR) errorLogHandler.setFormatter(formatter) warningLogHandler = handlers.TimedRotatingFileHandler( - os.path.dirname(os.path.abspath(__file__)) + '/../../../logs/warning.log', when='H', interval=1, backupCount=0) + os.path.dirname(os.path.abspath(__file__)) + '/../logs/warning.log', when='H', interval=1, backupCount=0) warningLogHandler.setLevel(logging.WARNING) warningLogHandler.setFormatter(formatter) @@ -98,7 +98,4 @@ def loadConfs(self): self.confs = json.loads(confs.read()) with open(os.path.dirname(os.path.abspath(__file__)) + '/../configuration/credentials.json') as confs: - self.credentials = json.loads(confs.read()) - - with open(os.path.dirname(os.path.abspath(__file__)) + '/../../../configuration/config.json') as confs: - self.confs_core = json.loads(confs.read()) + self.credentials = json.loads(confs.read()) \ No newline at end of file diff --git a/modules/mongodb.py b/modules/mongodb.py new file mode 100644 index 0000000..ba8b205 --- /dev/null +++ b/modules/mongodb.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +""" HIASCDI MongoDB Helper Module. + +The HIASCDI MongoDB Helper Module provides MongoDB helper +functions to the HIASCDI application. + +MIT License + +Copyright (c) 2021 Asociación de Investigacion en Inteligencia Artificial +Para la Leucemia Peter Moss + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Contributors: +- Adam Milton-Barker + +""" + +import sys + +from pymongo import MongoClient + +class mongodb(): + """ HIASCDI MongoDB Helper Module. + + The HIASCDI MongoDB Helper Module provides MongoDB helper + functions to the HIASCDI application. + """ + + def __init__(self, helpers): + """ Initializes the class. """ + + self.program = "MongoDB Helper Module" + + self.helpers = helpers + self.confs = self.helpers.confs + self.credentials = self.helpers.credentials + + self.helpers.logger.info(self.program + " initialization complete.") + + def start(self): + """ Connects to HIAS MongoDB database. """ + + self.mongoCon = MongoClient( + self.credentials["mongodb"]["host"]) + + self.mongoConn = self.mongoCon[self.credentials["mongodb"]["db"]] + + self.mongoConn.authenticate(self.credentials["mongodb"]["un"], + self.credentials["mongodb"]["up"]) + + self.collextions = { + "Actuator": self.mongoConn.Actuators, + "Agent": self.mongoConn.Entities, + "Application": self.mongoConn.Entities, + "ApplicationZone": self.mongoConn.ApplicationZones, + "Automation": self.mongoConn.Automation, + "HIASCDI": self.mongoConn.Entities, + "HIASHDI": self.mongoConn.Entities, + "Device": self.mongoConn.Entities, + "Location": self.mongoConn.Entities, + "Model": self.mongoConn.Entities, + "Robotics": self.mongoConn.Entities, + "Patient": self.mongoConn.Entities, + "Sensors": self.mongoConn.Sensors, + "Staff": self.mongoConn.Entities, + "Thing": self.mongoConn.Entities, + "Zone": self.mongoConn.Entities + } diff --git a/modules/mqtt.py b/modules/mqtt.py new file mode 100644 index 0000000..ffbf444 --- /dev/null +++ b/modules/mqtt.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +""" HIAS iotJumpWay MQTT Module + +This module connects devices, applications, robots and software to the HIAS +iotJumpWay MQTT Broker. + +MIT License + +Copyright (c) 2021 Asociación de Investigacion en Inteligencia Artificial +Para la Leucemia Peter Moss + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Contributors: +- Adam Milton-Barker + +""" + +import json + +import paho.mqtt.client as pmqtt + +class mqtt(): + """HIAS iotJumpWay MQTT Module + + This module connects devices, applications, robots and software to + the HIAS iotJumpWay MQTT Broker. + """ + + def __init__(self, + helpers, + client_type, + configs): + """ Initializes the class. """ + + self.configs = configs + self.client_type = client_type + self.isConnected = False + + self.helpers = helpers + self.program = "HIAS iotJumpWay MQTT Module" + + self.mqtt_config = {} + self.module_topics = {} + + self.hiascdi = [ + 'host', + 'port', + 'location', + 'zone', + 'entity', + 'name', + 'un', + 'up' + ] + + self.helpers.logger.info(self.program + " initialization complete.") + + def configure(self): + """ Connection configuration. + + Configures the HIAS iotJumpWay MQTT connnection. + """ + + # Checks required parameters + self.client_id = self.configs['name'] + for param in self.hiascdi: + if self.configs[param] is None: + raise ConfigurationException(param + " parameter is required!") + + # Sets MQTT connection configuration + self.mqtt_config["tls"] = "/etc/ssl/certs/DST_Root_CA_X3.pem" + self.mqtt_config["host"] = self.configs['host'] + self.mqtt_config["port"] = self.configs['port'] + + # Sets MQTT topics + self.module_topics["statusTopic"] = '%s/HIASCDI/%s/%s/Status' % ( + self.configs['location'], self.configs['zone'], self.configs['entity']) + + # Sets MQTT callbacks + self.actuatorCallback = None + self.bciCallback = None + self.commandsCallback = None + self.integrityCallback = None + self.lifeCallback = None + self.modelCallback = None + self.sensorsCallback = None + self.stateCallback = None + self.statusCallback = None + self.zoneCallback = None + + self.helpers.logger.info( + "iotJumpWay " + self.client_type + " connection configured.") + + def start(self): + """ Connection + + Starts the HIAS iotJumpWay MQTT connection. + """ + + self.mClient = pmqtt.Client(client_id=self.client_id, clean_session=True) + self.mClient.will_set(self.module_topics["statusTopic"], "OFFLINE", 0, False) + self.mClient.tls_set(self.mqtt_config["tls"], certfile=None, keyfile=None) + self.mClient.on_connect = self.on_connect + self.mClient.on_message = self.on_message + self.mClient.on_publish = self.on_publish + self.mClient.on_subscribe = self.on_subscribe + self.mClient.username_pw_set(str(self.configs['un']), str(self.configs['up'])) + self.mClient.connect(self.mqtt_config["host"], self.mqtt_config["port"], 10) + self.mClient.loop_start() + + self.helpers.logger.info( + "iotJumpWay " + self.client_type + " connection started.") + + def on_connect(self, client, obj, flags, rc): + """ On connection + + On connection callback. + """ + + if self.isConnected != True: + self.isConnected = True + + self.helpers.logger.info("iotJumpWay " + self.client_type + " connection successful.") + self.helpers.logger.info("rc: " + str(rc)) + + self.statusPublish("ONLINE") + + def statusPublish(self, data): + """ Status publish + + Publishes a status. + """ + + self.mClient.publish(self.module_topics["statusTopic"], data) + self.helpers.logger.info("Published to " + self.client_type + " status.") + + def on_subscribe(self, client, obj, mid, granted_qos): + """ On subscribe + + On subscription callback. + """ + + self.helpers.logger.info("iotJumpWay " + self.client_type + " subscription") + + def on_message(self, client, obj, msg): + """ On message + + On message callback. + """ + + splitTopic = msg.topic.split("/") + connType = splitTopic[1] + + topic = splitTopic[4] + + self.helpers.logger.info(msg.payload) + self.helpers.logger.info("iotJumpWay " + connType + " " + msg.topic + " communication received.") + + if topic == 'Actuators': + if self.actuatorCallback == None: + self.helpers.logger.info( + connType + " actuator callback required (actuatorCallback) !") + else: + self.actuatorCallback(msg.topic, msg.payload) + elif topic == 'BCI': + if self.bciCallback == None: + self.helpers.logger.info( + connType + " BCI callback required (bciCallback) !") + else: + self.bciCallback(msg.topic, msg.payload) + elif topic == 'Commands': + if self.commandsCallback == None: + self.helpers.logger.info( + connType + " comands callback required (commandsCallback) !") + else: + self.commandsCallback(msg.topic, msg.payload) + elif topic == 'Integrity': + if self.integrityCallback == None: + self.helpers.logger.info( + connType + " Integrity callback required (integrityCallback) !") + else: + self.integrityCallback(msg.topic, msg.payload) + elif topic == 'Life': + if self.lifeCallback == None: + self.helpers.logger.info( + connType + " life callback required (lifeCallback) !") + else: + self.lifeCallback(msg.topic, msg.payload) + elif topic == 'Sensors': + if self.sensorsCallback == None: + self.helpers.logger.info( + connType + " status callback required (sensorsCallback) !") + else: + self.sensorsCallback(msg.topic, msg.payload) + elif topic == 'State': + if self.stateCallback == None: + self.helpers.logger.info( + connType + " life callback required (stateCallback) !") + else: + self.stateCallback(msg.topic, msg.payload) + elif topic == 'Status': + if self.statusCallback == None: + self.helpers.logger.info( + connType + " status callback required (statusCallback) !") + else: + self.statusCallback(msg.topic, msg.payload) + elif topic == 'Zone': + if self.zoneCallback == None: + self.helpers.logger.info( + connType + " status callback required (zoneCallback) !") + else: + self.zoneCallback(msg.topic, msg.payload) + + def publish(self, channel, data, channelPath = ""): + """ Publish + + Publishes a iotJumpWay MQTT payload. + """ + + if channel == "Custom": + channel = channelPath + else: + channel = '%s/HIASCDI/%s/%s/%s' % ( + self.configs['location'], self.configs['zone'], self.configs['entity'], channel) + + self.mClient.publish(channel, json.dumps(data)) + self.helpers.logger.info("Published to " + channel) + return True + + def subscribe(self, application = None, channelID = None, qos=0): + """ Subscribe + + Subscribes to an iotJumpWay MQTT channel. + """ + + channel = '%s/#' % (self.configs['location']) + self.mClient.subscribe(channel, qos=qos) + self.helpers.logger.info("-- Agent subscribed to all channels") + return True + + def on_publish(self, client, obj, mid): + """ On publish + + On publish callback. + """ + + self.helpers.logger.info("Published: "+str(mid)) + + def on_log(self, client, obj, level, string): + """ On log + + On log callback. + """ + + print(string) + + def disconnect(self): + """ Disconnect + + Disconnects from the HIAS iotJumpWay MQTT Broker. + """ + + self.statusPublish("OFFLINE") + self.mClient.disconnect() + self.mClient.loop_stop() diff --git a/modules/subscriptions.py b/modules/subscriptions.py index 4c4196b..3a79a0c 100644 --- a/modules/subscriptions.py +++ b/modules/subscriptions.py @@ -38,7 +38,6 @@ import sys from bson import json_util, ObjectId - from flask import Response @@ -229,4 +228,14 @@ def deleteSubscription(self, subscription, accepted=[]): else: self.helpers.logger.info("Mongo data delete FAILED") return self.broker.respond(400, self.helpers.confs["errorMessages"]["400b"], - {}, False, accepted) \ No newline at end of file + {}, False, accepted) + + def checkForSubscription(self, subscription): + """ Checks for subscriptions in an list """ + + self.helpers.logger.info("Checking for subscriptions") + subscriptions = self.mongodb.mongoConn.Subscriptions.find() + + for sub in subscriptions: + cursub = self.getSubscription(sub) + print(cursub) diff --git a/scripts/services.sh b/scripts/services.sh new file mode 100644 index 0000000..e69de29