diff --git a/Pipfile.lock b/Pipfile.lock index f3f6ea1..82d4e60 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -66,6 +66,14 @@ "markers": "python_version >= '3.5'", "version": "==3.3" }, + "motor": { + "hashes": [ + "sha256:3e36d29406c151b61342e6a8fa5e90c00c4723b76e30f11276a4373ea2064b7d", + "sha256:b076de44970f518177f0eeeda8b183f52eafa557775bfe3294e93bda18867a71" + ], + "markers": "python_version >= '3.7'", + "version": "==3.0.0" + }, "numpy": { "hashes": [ "sha256:1408c3527a74a0209c781ac82bde2182b0f0bf54dea6e6a363fe0cc4488a7ce7", @@ -135,34 +143,106 @@ "markers": "python_full_version >= '3.6.1'", "version": "==1.9.1" }, + "pymongo": { + "hashes": [ + "sha256:01721da74558f2f64a9f162ee063df403ed656b7d84229268d8e4ae99cfba59c", + "sha256:07564178ecc203a84f63e72972691af6c0c82d2dc0c9da66ba711695276089ba", + "sha256:0f53253f4777cbccc426e669a2af875f26c95bd090d88593287b9a0a8ac7fa25", + "sha256:10f09c4f09757c2e2a707ad7304f5d69cb8fdf7cbfb644dbacfe5bbe8afe311b", + "sha256:124d0e880b66f9b0778613198e89984984fdd37a3030a9007e5f459a42dfa2d3", + "sha256:147a23cd96feb67606ac957744d8d25b013426cdc3c7164a4f99bd8253f649e3", + "sha256:153b8f8705970756226dfeeb7bb9637e0ad54a4d79b480b4c8244e34e16e1662", + "sha256:193cc97d44b1e6d2253ea94e30c6f94f994efb7166e2452af4df55825266e88b", + "sha256:1a957cdc2b26eeed4d8f1889a40c6023dd1bd94672dd0f5ce327314f2caaefd4", + "sha256:1c81414b706627f15e921e29ae2403aab52e33e36ed92ed989c602888d7c3b90", + "sha256:21238b19243a42f9a34a6d39e7580ceebc6da6d2f3cf729c1cff9023cb61a5f1", + "sha256:2bfe6b59f431f40fa545547616f4acf0c0c4b64518b1f951083e3bad06eb368b", + "sha256:314b556afd72eb21a6a10bd1f45ef252509f014f80207db59c97372103c88237", + "sha256:31c50da4a080166bc29403aa91f4c76e0889b4f24928d1b60508a37c1bf87f9a", + "sha256:3be53e9888e759c49ae35d747ff77a04ff82b894dd64601e0f3a5a159b406245", + "sha256:44b36ccb90aac5ea50be23c1a6e8f24fbfc78afabdef114af16c6e0a80981364", + "sha256:4cadaaa5c19ad23fc84559e90284f2eb003c36958ebb2c06f286b678f441285f", + "sha256:60c470a58c5b62b1b12a5f5458f8e2f2f67b94e198d03dc5352f854d9230c394", + "sha256:6673ab3fbf3135cc1a8c0f70d480db5b2378c3a70af8d602f73f76b8338bdf97", + "sha256:68e1e49a5675748233f7b05330f092582cd52f2850b4244939fd75ba640593ed", + "sha256:69d0180bca594e81cdb4a2af328bdb4046f59e10aaeef7619496fe64f2ec918c", + "sha256:6bd5888997ea3eae9830c6cc7964b61dcfbc50eb3a5a6ce56ad5f86d5579b11c", + "sha256:701d331060dae72bf3ebdb82924405d14136a69282ccb00c89fc69dee21340b4", + "sha256:70216ec4c248213ae95ea499b6314c385ce01a5946c448fb22f6c8395806e740", + "sha256:72f338f6aabd37d343bd9d1fdd3de921104d395766bcc5cdc4039e4c2dd97766", + "sha256:764fc15418d94bce5c2f8ebdbf66544f96f42efb1364b61e715e5b33281b388d", + "sha256:766acb5b1a19eae0f7467bcd3398748f110ea5309cdfc59faa5185dcc7fd4dca", + "sha256:76892bbce743eb9f90360b3626ea92f13d338010a1004b4488e79e555b339921", + "sha256:773467d25c293f8e981b092361dab5fd800e1ba318403b7959d35004c67faedc", + "sha256:80cbf0b043061451660099fff9001a7faacb2c9c983842b4819526e2f944dc6c", + "sha256:83168126ae2457d1a19b2af665cafa7ef78c2dcff192d7d7b5dad6b36c73ae24", + "sha256:83cc3c35aeeceb67143914db67f685206e1aa37ea837d872f4bc28d7f80917c9", + "sha256:8a86e8c2ac2ec87141e1c6cb00bdb18a4560f06e5f96769abcd1dda24dc0e764", + "sha256:8a9bc4dcfc2bda69ee88cdb7a89b03f2b8eca668519b704384a264dea2db4209", + "sha256:8c223aea52c359cc8fdee5bd3475532590755c269ec4d4fe581acd47a44e9952", + "sha256:8cbb868e88c4eee1c53364bb343d226a3c0e959e791e6828030cb78f46cfcbe3", + "sha256:902e2c9030cb042c49750bc70d72d830d42c64ea0df5ff8630c171e065c93dd7", + "sha256:a25c0eb2d610b20e276e684be61c337396813b636b69373c17314283cb1a3b14", + "sha256:a3efdf154844244e0dabe902cf1827fdced55fa5b144adec2a86e5ce50a99b97", + "sha256:a6bf01b9237f794fa3bdad5089474067d28be7e199b356a18d3f247a45775f26", + "sha256:a7eb5b06744b911b6668b427c8abc71b6d624e72d3dfffed00988fa1b4340f97", + "sha256:b0be613d926c5dbb0d3fc6b58e4f2be4979f80ae76fda6e47309f011b388fe0c", + "sha256:b211e161b6cc2790e0d640ad38e0429d06c944e5da23410f4dc61809dba25095", + "sha256:b537dd282de1b53d9ae7cf9f3df36420c8618390f2da92100391f3ba8f3c141a", + "sha256:c549bb519456ee230e92f415c5b4d962094caac0fdbcc4ed22b576f66169764e", + "sha256:c69ef5906dcd6ec565d4d887ba97ceb2a84f3b614307ee3b4780cb1ea40b1867", + "sha256:c8b4a782aac43948308087b962c9ecb030ba98886ce6dee3ad7aafe8c5e1ce80", + "sha256:cc7ebc37b03956a070260665079665eae69e5e96007694214f3a2107af96816a", + "sha256:ccfdc7722df445c49dc6b5d514c3544cad99b53189165f7546793933050ac7fb", + "sha256:d8bb745321716e7a11220a67c88212ecedde4021e1de4802e563baef9df921d2", + "sha256:d94f535df9f539615bc3dbbef185ded3b609373bb44ca1afffcabac70202678a", + "sha256:d98d2a8283c9928a9e5adf2f3c0181e095579e9732e1613aaa55d386e2bcb6c5", + "sha256:dc24737d24ce0de762bee9c2a884639819485f679bbac8ab5be9c161ef6f9b2c", + "sha256:e08fe1731f5429435b8dea1db9663f9ed1812915ff803fc9991c7c4841ed62ad", + "sha256:e09cdf5aad507c8faa30d97884cc42932ed3a9c2b7f22cc3ccc607bae03981b3", + "sha256:e152c26ffc30331e9d57591fc4c05453c209aa20ba299d1deb7173f7d1958c22", + "sha256:e1b8f5e2f9637492b0da4d51f78ecb17786e61d6c461ead8542c944750faf4f9", + "sha256:e39cacee70a98758f9b2da53ee175378f07c60113b1fa4fae40cbaee5583181e", + "sha256:e64442aba81ed4df1ca494b87bf818569a1280acaa73071c68014f7a884e83f1", + "sha256:e7dcb73f683c155885a3488646fcead3a895765fed16e93c9b80000bc69e96cb", + "sha256:ecdcb0d4e9b08b739035f57a09330efc6f464bd7f942b63897395d996ca6ebd5", + "sha256:ed90a9de4431cbfb2f3b2ef0c5fd356e61c85117b2be4db3eae28cb409f6e2d5", + "sha256:f1c23527f8e13f526fededbb96f2e7888f179fe27c51d41c2724f7059b75b2fa", + "sha256:f47d5f10922cf7f7dfcd1406bd0926cef6d866a75953c3745502dffd7ac197dd", + "sha256:fe0820d169635e41c14a5d21514282e0b93347878666ec9d5d3bf0eed0649948", + "sha256:ff66014687598823b6b23751884b4aa67eb934445406d95894dfc60cb7bfcc18" + ], + "markers": "python_version >= '3.7'", + "version": "==4.2.0" + }, "scipy": { "hashes": [ - "sha256:02b567e722d62bddd4ac253dafb01ce7ed8742cf8031aea030a41414b86c1125", - "sha256:1166514aa3bbf04cb5941027c6e294a000bba0cf00f5cdac6c77f2dad479b434", - "sha256:1da52b45ce1a24a4a22db6c157c38b39885a990a566748fc904ec9f03ed8c6ba", - "sha256:23b22fbeef3807966ea42d8163322366dd89da9bebdc075da7034cee3a1441ca", - "sha256:28d2cab0c6ac5aa131cc5071a3a1d8e1366dad82288d9ec2ca44df78fb50e649", - "sha256:2ef0fbc8bcf102c1998c1f16f15befe7cffba90895d6e84861cd6c6a33fb54f6", - "sha256:3b69b90c9419884efeffaac2c38376d6ef566e6e730a231e15722b0ab58f0328", - "sha256:4b93ec6f4c3c4d041b26b5f179a6aab8f5045423117ae7a45ba9710301d7e462", - "sha256:4e53a55f6a4f22de01ffe1d2f016e30adedb67a699a310cdcac312806807ca81", - "sha256:6311e3ae9cc75f77c33076cb2794fb0606f14c8f1b1c9ff8ce6005ba2c283621", - "sha256:65b77f20202599c51eb2771d11a6b899b97989159b7975e9b5259594f1d35ef4", - "sha256:6cc6b33139eb63f30725d5f7fa175763dc2df6a8f38ddf8df971f7c345b652dc", - "sha256:70de2f11bf64ca9921fda018864c78af7147025e467ce9f4a11bc877266900a6", - "sha256:70ebc84134cf0c504ce6a5f12d6db92cb2a8a53a49437a6bb4edca0bc101f11c", - "sha256:83606129247e7610b58d0e1e93d2c5133959e9cf93555d3c27e536892f1ba1f2", - "sha256:93d07494a8900d55492401917a119948ed330b8c3f1d700e0b904a578f10ead4", - "sha256:9c4e3ae8a716c8b3151e16c05edb1daf4cb4d866caa385e861556aff41300c14", - "sha256:9dd4012ac599a1e7eb63c114d1eee1bcfc6dc75a29b589ff0ad0bb3d9412034f", - "sha256:9e3fb1b0e896f14a85aa9a28d5f755daaeeb54c897b746df7a55ccb02b340f33", - "sha256:a0aa8220b89b2e3748a2836fbfa116194378910f1a6e78e4675a095bcd2c762d", - "sha256:d3b3c8924252caaffc54d4a99f1360aeec001e61267595561089f8b5900821bb", - "sha256:e013aed00ed776d790be4cb32826adb72799c61e318676172495383ba4570aa4", - "sha256:f3e7a8867f307e3359cc0ed2c63b61a1e33a19080f92fe377bc7d49f646f2ec1" - ], - "markers": "python_version < '3.11' and python_version >= '3.8'", - "version": "==1.8.1" + "sha256:01c2015e132774feefe059d5354055fec6b751d7a7d70ad2cf5ce314e7426e2a", + "sha256:0424d1bbbfa51d5ddaa16d067fd593863c9f2fb7c6840c32f8a08a8832f8e7a4", + "sha256:10417935486b320d98536d732a58362e3d37e84add98c251e070c59a6bfe0863", + "sha256:12005d30894e4fe7b247f7233ba0801a341f887b62e2eb99034dd6f2a8a33ad6", + "sha256:16207622570af10f9e6a2cdc7da7a9660678852477adbcd056b6d1057a036fef", + "sha256:45f0d6c0d6e55582d3b8f5c58ad4ca4259a02affb190f89f06c8cc02e21bba81", + "sha256:5d1b9cf3771fd921f7213b4b886ab2606010343bb36259b544a816044576d69e", + "sha256:693b3fe2e7736ce0dbc72b4d933798eb6ca8ce51b8b934e3f547cc06f48b2afb", + "sha256:73b704c5eea9be811919cae4caacf3180dd9212d9aed08477c1d2ba14900a9de", + "sha256:79dd7876614fc2869bf5d311ef33962d2066ea888bc66c80fd4fa80f8772e5a9", + "sha256:7bad16b91918bf3288089a78a4157e04892ea6475fb7a1d9bcdf32c30c8a3dba", + "sha256:8d541db2d441ef87afb60c4a2addb00c3af281633602a4967e733ef4b7050504", + "sha256:8f2232c9d9119ec356240255a715a289b3a33be828c3e4abac11fd052ce15b1e", + "sha256:97a1f1e51ea30782d7baa8d0c52f72c3f9f05cb609cf1b990664231c5102bccd", + "sha256:adb6c438c6ef550e2bb83968e772b9690cb421f2c6073f9c2cb6af15ee538bc9", + "sha256:bb687d245b6963673c639f318eea7e875d1ba147a67925586abed3d6f39bb7d8", + "sha256:bd490f77f35800d5620f4d9af669e372d9a88db1f76ef219e1609cc4ecdd1a24", + "sha256:c0dfd7d2429452e7e94904c6a3af63cbaa3cf51b348bd9d35b42db7e9ad42791", + "sha256:d3a326673ac5afa9ef5613a61626b9ec15c8f7222b4ecd1ce0fd8fcba7b83c59", + "sha256:e2004d2a3c397b26ca78e67c9d320153a1a9b71ae713ad33f4a3a3ab3d79cc65", + "sha256:e2ac088ea4aa61115b96b47f5f3d94b3fa29554340b6629cd2bfe6b0521ee33b", + "sha256:f7c3c578ff556333f3890c2df6c056955d53537bb176698359088108af73a58f", + "sha256:fc58c3fcb8a724b703ffbc126afdca5a8353d4d5945d5c92db85617e165299e7" + ], + "markers": "python_version < '3.12' and python_version >= '3.8'", + "version": "==1.9.0" }, "sniffio": { "hashes": [ @@ -215,11 +295,11 @@ }, "attrs": { "hashes": [ - "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", - "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", + "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.4.0" + "markers": "python_version >= '3.5'", + "version": "==22.1.0" }, "babel": { "hashes": [ @@ -393,10 +473,10 @@ }, "flake8-isort": { "hashes": [ - "sha256:c4e8b6dcb7be9b71a02e6e5d4196cefcef0f3447be51e82730fb336fff164949", - "sha256:d814304ab70e6e58859bc5c3e221e2e6e71c958e7005239202fee19c24f82717" + "sha256:4f95b40706dbb507cff872b34683283662e945d6028d3c8257e69de5fc6b7446", + "sha256:dee69bc3c09f0832df88acf795845db8a6673b79237371a05fa927ce095248e5" ], - "version": "==4.1.1" + "version": "==4.1.2.post0" }, "h11": { "hashes": [ @@ -408,11 +488,11 @@ }, "identify": { "hashes": [ - "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa", - "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82" + "sha256:a3d4c096b384d50d5e6dc5bc8b9bc44f1f61cefebd750a7b3e9f939b53fb214d", + "sha256:feaa9db2dc0ce333b453ce171c0cf1247bbfde2c55fc6bb785022d411a1b78b5" ], "markers": "python_version >= '3.7'", - "version": "==2.5.1" + "version": "==2.5.2" }, "idna": { "hashes": [ @@ -450,7 +530,7 @@ "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" ], - "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "markers": "python_full_version >= '3.6.1' and python_version < '4'", "version": "==5.10.1" }, "jinja2": { @@ -522,34 +602,42 @@ "markers": "python_version >= '3.6'", "version": "==4.0.3" }, + "motor": { + "hashes": [ + "sha256:3e36d29406c151b61342e6a8fa5e90c00c4723b76e30f11276a4373ea2064b7d", + "sha256:b076de44970f518177f0eeeda8b183f52eafa557775bfe3294e93bda18867a71" + ], + "markers": "python_version >= '3.7'", + "version": "==3.0.0" + }, "mypy": { "hashes": [ - "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5", - "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66", - "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e", - "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56", - "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e", - "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d", - "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813", - "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932", - "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569", - "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b", - "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0", - "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648", - "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6", - "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950", - "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15", - "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723", - "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a", - "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3", - "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6", - "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24", - "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b", - "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d", - "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492" + "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655", + "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9", + "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3", + "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6", + "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0", + "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58", + "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103", + "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09", + "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417", + "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56", + "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2", + "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856", + "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0", + "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8", + "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27", + "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5", + "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71", + "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27", + "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe", + "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca", + "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf", + "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9", + "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c" ], "markers": "python_version >= '3.6'", - "version": "==0.961" + "version": "==0.971" }, "mypy-extensions": { "hashes": [ @@ -618,19 +706,19 @@ }, "pip": { "hashes": [ - "sha256:6d55b27e10f506312894a87ccc59f280136bad9061719fac9101bdad5a6bce69", - "sha256:a3edacb89022ef5258bf61852728bf866632a394da837ca49eb4303635835f17" + "sha256:0bbbc87dfbe6eed217beff0021f8b7dea04c8f4a0baa9d31dc4cff281ffc5b2b", + "sha256:50516e47a2b79e77446f0d05649f0d53772c192571486236b1905492bfc24bac" ], "markers": "python_version >= '3.7'", - "version": "==22.1.2" + "version": "==22.2.1" }, "pipenv": { "hashes": [ - "sha256:18420fbae2851d2b9d70d1e00f77a8c55d09704a177fe872a0a2da56e98eb018", - "sha256:55c35a1742d568bb0e521099c5fd7bfd373d41f088160f4ffdd4e6e703d0fd3b" + "sha256:374e63450220d7abb298cbaee06c4b02274e6f1cb2ce7b7b677fd5fd3dd30841", + "sha256:79d7cf8709b0c165ed3f136acc8ca4ff9771ee32117117cc21969e39911becf8" ], "markers": "python_version >= '3.7'", - "version": "==2022.7.4" + "version": "==2022.7.24" }, "platformdirs": { "hashes": [ @@ -729,6 +817,78 @@ "markers": "python_version >= '3.6'", "version": "==2.12.0" }, + "pymongo": { + "hashes": [ + "sha256:01721da74558f2f64a9f162ee063df403ed656b7d84229268d8e4ae99cfba59c", + "sha256:07564178ecc203a84f63e72972691af6c0c82d2dc0c9da66ba711695276089ba", + "sha256:0f53253f4777cbccc426e669a2af875f26c95bd090d88593287b9a0a8ac7fa25", + "sha256:10f09c4f09757c2e2a707ad7304f5d69cb8fdf7cbfb644dbacfe5bbe8afe311b", + "sha256:124d0e880b66f9b0778613198e89984984fdd37a3030a9007e5f459a42dfa2d3", + "sha256:147a23cd96feb67606ac957744d8d25b013426cdc3c7164a4f99bd8253f649e3", + "sha256:153b8f8705970756226dfeeb7bb9637e0ad54a4d79b480b4c8244e34e16e1662", + "sha256:193cc97d44b1e6d2253ea94e30c6f94f994efb7166e2452af4df55825266e88b", + "sha256:1a957cdc2b26eeed4d8f1889a40c6023dd1bd94672dd0f5ce327314f2caaefd4", + "sha256:1c81414b706627f15e921e29ae2403aab52e33e36ed92ed989c602888d7c3b90", + "sha256:21238b19243a42f9a34a6d39e7580ceebc6da6d2f3cf729c1cff9023cb61a5f1", + "sha256:2bfe6b59f431f40fa545547616f4acf0c0c4b64518b1f951083e3bad06eb368b", + "sha256:314b556afd72eb21a6a10bd1f45ef252509f014f80207db59c97372103c88237", + "sha256:31c50da4a080166bc29403aa91f4c76e0889b4f24928d1b60508a37c1bf87f9a", + "sha256:3be53e9888e759c49ae35d747ff77a04ff82b894dd64601e0f3a5a159b406245", + "sha256:44b36ccb90aac5ea50be23c1a6e8f24fbfc78afabdef114af16c6e0a80981364", + "sha256:4cadaaa5c19ad23fc84559e90284f2eb003c36958ebb2c06f286b678f441285f", + "sha256:60c470a58c5b62b1b12a5f5458f8e2f2f67b94e198d03dc5352f854d9230c394", + "sha256:6673ab3fbf3135cc1a8c0f70d480db5b2378c3a70af8d602f73f76b8338bdf97", + "sha256:68e1e49a5675748233f7b05330f092582cd52f2850b4244939fd75ba640593ed", + "sha256:69d0180bca594e81cdb4a2af328bdb4046f59e10aaeef7619496fe64f2ec918c", + "sha256:6bd5888997ea3eae9830c6cc7964b61dcfbc50eb3a5a6ce56ad5f86d5579b11c", + "sha256:701d331060dae72bf3ebdb82924405d14136a69282ccb00c89fc69dee21340b4", + "sha256:70216ec4c248213ae95ea499b6314c385ce01a5946c448fb22f6c8395806e740", + "sha256:72f338f6aabd37d343bd9d1fdd3de921104d395766bcc5cdc4039e4c2dd97766", + "sha256:764fc15418d94bce5c2f8ebdbf66544f96f42efb1364b61e715e5b33281b388d", + "sha256:766acb5b1a19eae0f7467bcd3398748f110ea5309cdfc59faa5185dcc7fd4dca", + "sha256:76892bbce743eb9f90360b3626ea92f13d338010a1004b4488e79e555b339921", + "sha256:773467d25c293f8e981b092361dab5fd800e1ba318403b7959d35004c67faedc", + "sha256:80cbf0b043061451660099fff9001a7faacb2c9c983842b4819526e2f944dc6c", + "sha256:83168126ae2457d1a19b2af665cafa7ef78c2dcff192d7d7b5dad6b36c73ae24", + "sha256:83cc3c35aeeceb67143914db67f685206e1aa37ea837d872f4bc28d7f80917c9", + "sha256:8a86e8c2ac2ec87141e1c6cb00bdb18a4560f06e5f96769abcd1dda24dc0e764", + "sha256:8a9bc4dcfc2bda69ee88cdb7a89b03f2b8eca668519b704384a264dea2db4209", + "sha256:8c223aea52c359cc8fdee5bd3475532590755c269ec4d4fe581acd47a44e9952", + "sha256:8cbb868e88c4eee1c53364bb343d226a3c0e959e791e6828030cb78f46cfcbe3", + "sha256:902e2c9030cb042c49750bc70d72d830d42c64ea0df5ff8630c171e065c93dd7", + "sha256:a25c0eb2d610b20e276e684be61c337396813b636b69373c17314283cb1a3b14", + "sha256:a3efdf154844244e0dabe902cf1827fdced55fa5b144adec2a86e5ce50a99b97", + "sha256:a6bf01b9237f794fa3bdad5089474067d28be7e199b356a18d3f247a45775f26", + "sha256:a7eb5b06744b911b6668b427c8abc71b6d624e72d3dfffed00988fa1b4340f97", + "sha256:b0be613d926c5dbb0d3fc6b58e4f2be4979f80ae76fda6e47309f011b388fe0c", + "sha256:b211e161b6cc2790e0d640ad38e0429d06c944e5da23410f4dc61809dba25095", + "sha256:b537dd282de1b53d9ae7cf9f3df36420c8618390f2da92100391f3ba8f3c141a", + "sha256:c549bb519456ee230e92f415c5b4d962094caac0fdbcc4ed22b576f66169764e", + "sha256:c69ef5906dcd6ec565d4d887ba97ceb2a84f3b614307ee3b4780cb1ea40b1867", + "sha256:c8b4a782aac43948308087b962c9ecb030ba98886ce6dee3ad7aafe8c5e1ce80", + "sha256:cc7ebc37b03956a070260665079665eae69e5e96007694214f3a2107af96816a", + "sha256:ccfdc7722df445c49dc6b5d514c3544cad99b53189165f7546793933050ac7fb", + "sha256:d8bb745321716e7a11220a67c88212ecedde4021e1de4802e563baef9df921d2", + "sha256:d94f535df9f539615bc3dbbef185ded3b609373bb44ca1afffcabac70202678a", + "sha256:d98d2a8283c9928a9e5adf2f3c0181e095579e9732e1613aaa55d386e2bcb6c5", + "sha256:dc24737d24ce0de762bee9c2a884639819485f679bbac8ab5be9c161ef6f9b2c", + "sha256:e08fe1731f5429435b8dea1db9663f9ed1812915ff803fc9991c7c4841ed62ad", + "sha256:e09cdf5aad507c8faa30d97884cc42932ed3a9c2b7f22cc3ccc607bae03981b3", + "sha256:e152c26ffc30331e9d57591fc4c05453c209aa20ba299d1deb7173f7d1958c22", + "sha256:e1b8f5e2f9637492b0da4d51f78ecb17786e61d6c461ead8542c944750faf4f9", + "sha256:e39cacee70a98758f9b2da53ee175378f07c60113b1fa4fae40cbaee5583181e", + "sha256:e64442aba81ed4df1ca494b87bf818569a1280acaa73071c68014f7a884e83f1", + "sha256:e7dcb73f683c155885a3488646fcead3a895765fed16e93c9b80000bc69e96cb", + "sha256:ecdcb0d4e9b08b739035f57a09330efc6f464bd7f942b63897395d996ca6ebd5", + "sha256:ed90a9de4431cbfb2f3b2ef0c5fd356e61c85117b2be4db3eae28cb409f6e2d5", + "sha256:f1c23527f8e13f526fededbb96f2e7888f179fe27c51d41c2724f7059b75b2fa", + "sha256:f47d5f10922cf7f7dfcd1406bd0926cef6d866a75953c3745502dffd7ac197dd", + "sha256:fe0820d169635e41c14a5d21514282e0b93347878666ec9d5d3bf0eed0649948", + "sha256:ff66014687598823b6b23751884b4aa67eb934445406d95894dfc60cb7bfcc18" + ], + "markers": "python_version >= '3.7'", + "version": "==4.2.0" + }, "pyparsing": { "hashes": [ "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", @@ -804,37 +964,37 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "markers": "python_version >= '3.7' and python_version < '4.0'", + "markers": "python_version >= '3.7' and python_version < '4'", "version": "==2.28.1" }, "scipy": { "hashes": [ - "sha256:02b567e722d62bddd4ac253dafb01ce7ed8742cf8031aea030a41414b86c1125", - "sha256:1166514aa3bbf04cb5941027c6e294a000bba0cf00f5cdac6c77f2dad479b434", - "sha256:1da52b45ce1a24a4a22db6c157c38b39885a990a566748fc904ec9f03ed8c6ba", - "sha256:23b22fbeef3807966ea42d8163322366dd89da9bebdc075da7034cee3a1441ca", - "sha256:28d2cab0c6ac5aa131cc5071a3a1d8e1366dad82288d9ec2ca44df78fb50e649", - "sha256:2ef0fbc8bcf102c1998c1f16f15befe7cffba90895d6e84861cd6c6a33fb54f6", - "sha256:3b69b90c9419884efeffaac2c38376d6ef566e6e730a231e15722b0ab58f0328", - "sha256:4b93ec6f4c3c4d041b26b5f179a6aab8f5045423117ae7a45ba9710301d7e462", - "sha256:4e53a55f6a4f22de01ffe1d2f016e30adedb67a699a310cdcac312806807ca81", - "sha256:6311e3ae9cc75f77c33076cb2794fb0606f14c8f1b1c9ff8ce6005ba2c283621", - "sha256:65b77f20202599c51eb2771d11a6b899b97989159b7975e9b5259594f1d35ef4", - "sha256:6cc6b33139eb63f30725d5f7fa175763dc2df6a8f38ddf8df971f7c345b652dc", - "sha256:70de2f11bf64ca9921fda018864c78af7147025e467ce9f4a11bc877266900a6", - "sha256:70ebc84134cf0c504ce6a5f12d6db92cb2a8a53a49437a6bb4edca0bc101f11c", - "sha256:83606129247e7610b58d0e1e93d2c5133959e9cf93555d3c27e536892f1ba1f2", - "sha256:93d07494a8900d55492401917a119948ed330b8c3f1d700e0b904a578f10ead4", - "sha256:9c4e3ae8a716c8b3151e16c05edb1daf4cb4d866caa385e861556aff41300c14", - "sha256:9dd4012ac599a1e7eb63c114d1eee1bcfc6dc75a29b589ff0ad0bb3d9412034f", - "sha256:9e3fb1b0e896f14a85aa9a28d5f755daaeeb54c897b746df7a55ccb02b340f33", - "sha256:a0aa8220b89b2e3748a2836fbfa116194378910f1a6e78e4675a095bcd2c762d", - "sha256:d3b3c8924252caaffc54d4a99f1360aeec001e61267595561089f8b5900821bb", - "sha256:e013aed00ed776d790be4cb32826adb72799c61e318676172495383ba4570aa4", - "sha256:f3e7a8867f307e3359cc0ed2c63b61a1e33a19080f92fe377bc7d49f646f2ec1" - ], - "markers": "python_version < '3.11' and python_version >= '3.8'", - "version": "==1.8.1" + "sha256:01c2015e132774feefe059d5354055fec6b751d7a7d70ad2cf5ce314e7426e2a", + "sha256:0424d1bbbfa51d5ddaa16d067fd593863c9f2fb7c6840c32f8a08a8832f8e7a4", + "sha256:10417935486b320d98536d732a58362e3d37e84add98c251e070c59a6bfe0863", + "sha256:12005d30894e4fe7b247f7233ba0801a341f887b62e2eb99034dd6f2a8a33ad6", + "sha256:16207622570af10f9e6a2cdc7da7a9660678852477adbcd056b6d1057a036fef", + "sha256:45f0d6c0d6e55582d3b8f5c58ad4ca4259a02affb190f89f06c8cc02e21bba81", + "sha256:5d1b9cf3771fd921f7213b4b886ab2606010343bb36259b544a816044576d69e", + "sha256:693b3fe2e7736ce0dbc72b4d933798eb6ca8ce51b8b934e3f547cc06f48b2afb", + "sha256:73b704c5eea9be811919cae4caacf3180dd9212d9aed08477c1d2ba14900a9de", + "sha256:79dd7876614fc2869bf5d311ef33962d2066ea888bc66c80fd4fa80f8772e5a9", + "sha256:7bad16b91918bf3288089a78a4157e04892ea6475fb7a1d9bcdf32c30c8a3dba", + "sha256:8d541db2d441ef87afb60c4a2addb00c3af281633602a4967e733ef4b7050504", + "sha256:8f2232c9d9119ec356240255a715a289b3a33be828c3e4abac11fd052ce15b1e", + "sha256:97a1f1e51ea30782d7baa8d0c52f72c3f9f05cb609cf1b990664231c5102bccd", + "sha256:adb6c438c6ef550e2bb83968e772b9690cb421f2c6073f9c2cb6af15ee538bc9", + "sha256:bb687d245b6963673c639f318eea7e875d1ba147a67925586abed3d6f39bb7d8", + "sha256:bd490f77f35800d5620f4d9af669e372d9a88db1f76ef219e1609cc4ecdd1a24", + "sha256:c0dfd7d2429452e7e94904c6a3af63cbaa3cf51b348bd9d35b42db7e9ad42791", + "sha256:d3a326673ac5afa9ef5613a61626b9ec15c8f7222b4ecd1ce0fd8fcba7b83c59", + "sha256:e2004d2a3c397b26ca78e67c9d320153a1a9b71ae713ad33f4a3a3ab3d79cc65", + "sha256:e2ac088ea4aa61115b96b47f5f3d94b3fa29554340b6629cd2bfe6b0521ee33b", + "sha256:f7c3c578ff556333f3890c2df6c056955d53537bb176698359088108af73a58f", + "sha256:fc58c3fcb8a724b703ffbc126afdca5a8353d4d5945d5c92db85617e165299e7" + ], + "markers": "python_version < '3.12' and python_version >= '3.8'", + "version": "==1.9.0" }, "setuptools": { "hashes": [ @@ -844,14 +1004,6 @@ "markers": "python_version >= '3.7'", "version": "==63.2.0" }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, "sniffio": { "hashes": [ "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663", @@ -869,11 +1021,11 @@ }, "sphinx": { "hashes": [ - "sha256:b18e978ea7565720f26019c702cd85c84376e948370f1cd43d60265010e1c7b0", - "sha256:d3e57663eed1d7c5c50895d191fdeda0b54ded6f44d5621b50709466c338d1e8" + "sha256:309a8da80cb6da9f4713438e5b55861877d5d7976b69d87e336733637ea12693", + "sha256:ba3224a4e206e1fbdecf98a4fae4992ef9b24b85ebf7b584bb340156eaf08d89" ], "markers": "python_version >= '3.6'", - "version": "==5.0.2" + "version": "==5.1.1" }, "sphinx-rtd-theme": { "hashes": [ @@ -947,19 +1099,12 @@ "markers": "python_version >= '3.6'", "version": "==0.19.1" }, - "testfixtures": { - "hashes": [ - "sha256:02dae883f567f5b70fd3ad3c9eefb95912e78ac90be6c7444b5e2f46bf572c84", - "sha256:7de200e24f50a4a5d6da7019fb1197aaf5abd475efb2ec2422fdcf2f2eb98c1d" - ], - "version": "==6.18.5" - }, "toml": { "hashes": [ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "tomli": { @@ -1010,11 +1155,11 @@ }, "urllib3": { "hashes": [ - "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", - "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" + "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc", + "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4.0'", - "version": "==1.26.10" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", + "version": "==1.26.11" }, "uvicorn": { "hashes": [ @@ -1026,11 +1171,11 @@ }, "virtualenv": { "hashes": [ - "sha256:288171134a2ff3bfb1a2f54f119e77cd1b81c29fc1265a2356f3e8d14c7d58c4", - "sha256:b30aefac647e86af6d82bfc944c556f8f1a9c90427b2fb4e3bfbf338cb82becf" + "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db", + "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==20.15.1" + "markers": "python_version >= '3.6'", + "version": "==20.16.2" }, "virtualenv-clone": { "hashes": [ diff --git a/setup.cfg b/setup.cfg index edfde36..652cd80 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,8 @@ install_requires = diffcalc-core fastapi uvicorn + pymongo + motor [options.extras_require] # For development tests/docs diff --git a/src/diffcalc_API/__init__.py b/src/diffcalc_API/__init__.py index 9d42edd..d291cf1 100644 --- a/src/diffcalc_API/__init__.py +++ b/src/diffcalc_API/__init__.py @@ -1,10 +1,7 @@ -from . import config, server +from . import config, database, server from ._version_git import __version__ # __all__ defines the public API for the package. # Each module also defines its own __all__. -__all__ = [ - "__version__", - "server", - "config", -] + +__all__ = ["__version__", "server", "config", "database"] diff --git a/src/diffcalc_API/config.py b/src/diffcalc_API/config.py index 72353df..2f16109 100644 --- a/src/diffcalc_API/config.py +++ b/src/diffcalc_API/config.py @@ -1,9 +1,8 @@ -# this file is for defining constants - -SAVE_PICKLES_FOLDER = "/dls/tmp/ton99817/diffcalc_pickles" +SAVE_PICKLES_FOLDER = "/" VECTOR_PROPERTIES = ["n_hkl", "n_phi", "surf_nhkl", "surf_nphi"] CONSTRAINTS_WITH_NO_VALUE = {"a_eq_b", "bin_eq_bout", "mu_is_gam", "bisect"} + ALL_CONSTRAINTS = { "delta", "gam" "qaz", diff --git a/src/diffcalc_API/database.py b/src/diffcalc_API/database.py new file mode 100644 index 0000000..106cf6c --- /dev/null +++ b/src/diffcalc_API/database.py @@ -0,0 +1,5 @@ +import motor.motor_asyncio +from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase + +client: AsyncIOMotorClient = motor.motor_asyncio.AsyncIOMotorClient() +database: AsyncIOMotorDatabase = client.test_db diff --git a/src/diffcalc_API/errors/constraints.py b/src/diffcalc_API/errors/constraints.py index 37b646b..b34541c 100644 --- a/src/diffcalc_API/errors/constraints.py +++ b/src/diffcalc_API/errors/constraints.py @@ -12,7 +12,7 @@ class Codes(ErrorCodes): CHECK_CONSTRAINT_EXISTS = 400 -responses = {code: ALL_RESPONSES[code] for code in np.unique(Codes().all_codes())} +responses = {code: ALL_RESPONSES[code] for code in np.unique(Codes.all_codes())} def check_constraint_exists(constraint: str) -> None: diff --git a/src/diffcalc_API/errors/definitions.py b/src/diffcalc_API/errors/definitions.py index 8e88a8e..6c90536 100644 --- a/src/diffcalc_API/errors/definitions.py +++ b/src/diffcalc_API/errors/definitions.py @@ -1,4 +1,5 @@ -from typing import Any, Dict, Union +from enum import IntEnum +from typing import Any, Dict, List, Union from diffcalc.util import DiffcalcException from pydantic import BaseModel @@ -25,14 +26,10 @@ class DiffcalcExceptionModel(BaseModel): detail: str -class ErrorCodes: - def all_codes(self): - attributes = [ - attr - for attr in dir(self) - if (not attr.startswith("__")) and (not attr == "all_codes") - ] - return [getattr(self, attr) for attr in attributes] +class ErrorCodes(IntEnum): + @classmethod + def all_codes(cls) -> List[int]: + return [val.value for val in cls] ####################################################################################### diff --git a/src/diffcalc_API/errors/hkl.py b/src/diffcalc_API/errors/hkl.py index 5cb7ac2..2091190 100644 --- a/src/diffcalc_API/errors/hkl.py +++ b/src/diffcalc_API/errors/hkl.py @@ -16,7 +16,7 @@ class Codes(ErrorCodes): CALCULATE_UB_MATRIX = 400 -responses = {code: ALL_RESPONSES[code] for code in np.unique(Codes().all_codes())} +responses = {code: ALL_RESPONSES[code] for code in np.unique(Codes.all_codes())} def check_valid_miller_indices(miller_indices: Tuple[float, float, float]) -> None: diff --git a/src/diffcalc_API/errors/ub.py b/src/diffcalc_API/errors/ub.py index d9b9ec5..d17514e 100644 --- a/src/diffcalc_API/errors/ub.py +++ b/src/diffcalc_API/errors/ub.py @@ -20,7 +20,7 @@ class Codes(ErrorCodes): CHECK_PROPERTY_IS_VALID = 400 -responses = {code: ALL_RESPONSES[code] for code in np.unique(Codes().all_codes())} +responses = {code: ALL_RESPONSES[code] for code in np.unique(Codes.all_codes())} def check_params_not_empty(params: SetLatticeParams) -> None: diff --git a/src/diffcalc_API/routes/constraints.py b/src/diffcalc_API/routes/constraints.py index f79d5ed..34fe984 100644 --- a/src/diffcalc_API/routes/constraints.py +++ b/src/diffcalc_API/routes/constraints.py @@ -1,52 +1,72 @@ -from typing import Dict, Union +from typing import Dict, Optional, Union -from fastapi import APIRouter, Body, Depends, Response +from fastapi import APIRouter, Body, Depends, Query, Response from diffcalc_API.services import constraints as service -from diffcalc_API.stores.pickling import get_store -from diffcalc_API.stores.protocol import HklCalcStore +from diffcalc_API.stores.protocol import HklCalcStore, get_store router = APIRouter(prefix="/constraints", tags=["constraints"]) @router.get("/{name}") -async def get_constraints(name: str, store: HklCalcStore = Depends(get_store)): - content = await service.get_constraints(name, store) - +async def get_constraints( + name: str, + store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), +): + content = await service.get_constraints(name, store, collection) return Response(content=content, media_type="application/text") -@router.put("/{name}/set") +@router.post("/{name}") async def set_constraints( name: str, constraints: Dict[str, Union[float, bool]] = Body( example={"qaz": 0, "alpha": 0, "eta": 0} ), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.set_constraints(name, constraints, store) + await service.set_constraints(name, constraints, store, collection) - return {"message": f"constraints updated (replaced) for crystal {name}"} + return { + "message": ( + f"constraints updated (replaced) for crystal {name} in " + + f"collection {collection}" + ) + } -@router.patch("/{name}/unconstrain/{property}") +@router.delete("/{name}/{property}") async def remove_constraint( name: str, property: str, store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.remove_constraint(name, property, store) + await service.remove_constraint(name, property, store, collection) - return {"message": f"unconstrained {property} for crystal {name}. "} + return { + "message": ( + f"unconstrained {property} for crystal {name} in " + + f"collection {collection}. " + ) + } -@router.patch("/{name}/constrain/{property}") +@router.patch("/{name}/{property}") async def set_constraint( name: str, property: str, value: Union[float, bool] = Body(...), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.set_constraint(name, property, value, store) + await service.set_constraint(name, property, value, store, collection) - return {"message": f"constrained {property} for crystal {name}. "} + return { + "message": ( + f"constrained {property} for crystal {name} in collection " + + f"{collection}. " + ) + } diff --git a/src/diffcalc_API/routes/hkl.py b/src/diffcalc_API/routes/hkl.py index e1257e1..da28024 100644 --- a/src/diffcalc_API/routes/hkl.py +++ b/src/diffcalc_API/routes/hkl.py @@ -3,10 +3,9 @@ from fastapi import APIRouter, Depends, Query, Response from diffcalc_API.services import hkl as service -from diffcalc_API.stores.pickling import get_store -from diffcalc_API.stores.protocol import HklCalcStore +from diffcalc_API.stores.protocol import HklCalcStore, get_store -router = APIRouter(prefix="/calculate", tags=["hkl"]) +router = APIRouter(prefix="/hkl", tags=["hkl"]) SingleConstraint = Union[Tuple[str, float], str] @@ -19,8 +18,9 @@ async def calculate_ub( first_tag: Optional[Union[int, str]] = Query(default=None, example="refl1"), second_tag: Optional[Union[int, str]] = Query(default=None, example="plane"), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - content = await service.calculate_ub(name, first_tag, second_tag, store) + content = await service.calculate_ub(name, first_tag, second_tag, store, collection) return Response(content=content, media_type="application/text") @@ -30,9 +30,10 @@ async def lab_position_from_miller_indices( miller_indices: Tuple[float, float, float] = Query(example=[0, 0, 1]), wavelength: float = Query(..., example=1.0), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): positions = await service.lab_position_from_miller_indices( - name, miller_indices, wavelength, store + name, miller_indices, wavelength, store, collection ) return {"payload": positions} @@ -46,8 +47,11 @@ async def miller_indices_from_lab_position( ), wavelength: float = Query(..., example=1.0), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - hkl = await service.miller_indices_from_lab_position(name, pos, wavelength, store) + hkl = await service.miller_indices_from_lab_position( + name, pos, wavelength, store, collection + ) return {"payload": hkl} @@ -59,8 +63,11 @@ async def scan_hkl( inc: PositionType = Query(..., example=(0.1, 0, 0.1)), wavelength: float = Query(..., example=1), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - scan_results = await service.scan_hkl(name, start, stop, inc, wavelength, store) + scan_results = await service.scan_hkl( + name, start, stop, inc, wavelength, store, collection + ) return {"payload": scan_results} @@ -72,8 +79,11 @@ async def scan_wavelength( inc: float = Query(..., example=0.2), hkl: PositionType = Query(..., example=(1, 0, 1)), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - scan_results = await service.scan_wavelength(name, start, stop, inc, hkl, store) + scan_results = await service.scan_wavelength( + name, start, stop, inc, hkl, store, collection + ) return {"payload": scan_results} @@ -87,9 +97,10 @@ async def scan_constraint( hkl: PositionType = Query(..., example=(1, 0, 1)), wavelength: float = Query(..., example=1.0), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): scan_results = await service.scan_constraint( - name, constraint, start, stop, inc, hkl, wavelength, store + name, constraint, start, stop, inc, hkl, wavelength, store, collection ) return {"payload": scan_results} diff --git a/src/diffcalc_API/routes/ub.py b/src/diffcalc_API/routes/ub.py index 08e271d..99403c8 100644 --- a/src/diffcalc_API/routes/ub.py +++ b/src/diffcalc_API/routes/ub.py @@ -1,6 +1,6 @@ -from typing import Tuple +from typing import Optional, Tuple -from fastapi import APIRouter, Body, Depends, Response +from fastapi import APIRouter, Body, Depends, Query, Response from diffcalc_API.errors.ub import check_params_not_empty, check_property_is_valid from diffcalc_API.examples import ub as examples @@ -13,36 +13,51 @@ SetLatticeParams, ) from diffcalc_API.services import ub as service -from diffcalc_API.stores.pickling import get_store -from diffcalc_API.stores.protocol import HklCalcStore +from diffcalc_API.stores.protocol import HklCalcStore, get_store router = APIRouter(prefix="/ub", tags=["ub"]) @router.get("/{name}") -async def get_ub(name: str, store: HklCalcStore = Depends(get_store)): - content = await service.get_ub(name, store) +async def get_ub( + name: str, + store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), +): + content = await service.get_ub(name, store, collection) return Response(content=content, media_type="application/text") -@router.put("/{name}/reflection") +@router.post("/{name}/reflection") async def add_reflection( name: str, params: AddReflectionParams = Body(..., example=examples.add_reflection), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.add_reflection(name, params, store) - return {"message": f"added reflection for UB Calculation of crystal {name}"} + await service.add_reflection(name, params, store, collection) + return { + "message": ( + f"added reflection for UB Calculation of crystal {name} in " + + f"collection {collection}" + ) + } -@router.patch("/{name}/reflection") +@router.put("/{name}/reflection") async def edit_reflection( name: str, params: EditReflectionParams = Body(..., example=examples.edit_reflection), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.edit_reflection(name, params, store) - return {"message": f"reflection with tag/index {params.tag_or_idx} edited. "} + await service.edit_reflection(name, params, store, collection) + return { + "message": ( + f"reflection of crystal {name} in collection {collection} " + + f"with tag/index {params.tag_or_idx} edited. " + ) + } @router.delete("/{name}/reflection") @@ -50,29 +65,49 @@ async def delete_reflection( name: str, params: DeleteParams = Body(..., example={"tag_or_idx": "refl1"}), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.delete_reflection(name, params.tag_or_idx, store) # TODO Change this! - return {"message": f"reflection with tag/index {params.tag_or_idx} deleted."} - - -@router.put("/{name}/orientation") + await service.delete_reflection( + name, params.tag_or_idx, store, collection + ) # TODO Change this! + return { + "message": ( + f"reflection of crystal {name} in collection {collection} " + + f"with tag/index {params.tag_or_idx} deleted." + ) + } + + +@router.post("/{name}/orientation") async def add_orientation( name: str, params: AddOrientationParams = Body(..., example=examples.add_orientation), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.add_orientation(name, params, store) - return {"message": f"added orientation for UB Calculation of crystal {name}"} + await service.add_orientation(name, params, store, collection) + return { + "message": ( + f"added orientation for UB Calculation of crystal {name} in " + + f"collection {collection}" + ) + } -@router.patch("/{name}/orientation") +@router.put("/{name}/orientation") async def edit_orientation( name: str, params: EditOrientationParams = Body(..., example=examples.edit_orientation), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.edit_orientation(name, params, store) - return {"message": f"orientation with tag/index {params.tag_or_idx} edited."} + await service.edit_orientation(name, params, store, collection) + return { + "message": ( + f"orientation of crystal {name} in collection {collection} " + + f"with tag/index {params.tag_or_idx} edited." + ) + } @router.delete("/{name}/orientation") @@ -80,9 +115,15 @@ async def delete_orientation( name: str, params: DeleteParams = Body(..., example={"tag_or_idx": "plane"}), store: HklCalcStore = Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.delete_orientation(name, params.tag_or_idx, store) - return {"message": f"reflection with tag or index {params.tag_or_idx} deleted."} + await service.delete_orientation(name, params.tag_or_idx, store, collection) + return { + "message": ( + f"reflection of crystal {name} in collection {collection} with " + + f"tag or index {params.tag_or_idx} deleted." + ) + } @router.patch("/{name}/lattice") @@ -91,18 +132,30 @@ async def set_lattice( params: SetLatticeParams = Body(example=examples.set_lattice), store: HklCalcStore = Depends(get_store), _=Depends(check_params_not_empty), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.set_lattice(name, params, store) - return {"message": f"lattice has been set for UB calculation of crystal {name}"} + await service.set_lattice(name, params, store, collection) + return { + "message": ( + f"lattice has been set for UB calculation of crystal {name} in " + + f"collection {collection}" + ) + } -@router.patch("/{name}/{property}") +@router.put("/{name}/{property}") async def modify_property( name: str, property: str, target_value: Tuple[float, float, float] = Body(..., example=[1, 0, 0]), store: HklCalcStore = Depends(get_store), _=Depends(check_property_is_valid), + collection: Optional[str] = Query(default=None, example="B07"), ): - await service.modify_property(name, property, target_value, store) - return {"message": f"{property} has been set for UB calculation of crystal {name}"} + await service.modify_property(name, property, target_value, store, collection) + return { + "message": ( + f"{property} has been set for UB calculation of crystal {name} in " + + f"collection {collection}" + ) + } diff --git a/src/diffcalc_API/server.py b/src/diffcalc_API/server.py index 7b132ef..b64b3f1 100644 --- a/src/diffcalc_API/server.py +++ b/src/diffcalc_API/server.py @@ -1,14 +1,18 @@ +from typing import Optional + from diffcalc.util import DiffcalcException -from fastapi import Depends, FastAPI, Request, responses +from fastapi import Depends, FastAPI, Query, Request, responses from diffcalc_API.errors.constraints import responses as constraints_responses from diffcalc_API.errors.definitions import DiffcalcAPIException from diffcalc_API.errors.hkl import responses as hkl_responses from diffcalc_API.errors.ub import responses as ub_responses -from diffcalc_API.stores.pickling import get_store +from diffcalc_API.stores.protocol import get_store, setup_store from . import routes +setup_store("diffcalc_API.stores.mongo.MongoHklCalcStore") + app = FastAPI(responses=get_store().responses) app.include_router(routes.ub.router, responses=ub_responses) @@ -55,14 +59,22 @@ async def server_exceptions_middleware(request: Request, call_next): @app.post("/{name}") -async def create_hkl_object(name: str, repo=Depends(get_store)): - await repo.create(name) +async def create_hkl_object( + name: str, + store=Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), +): + await store.create(name, collection) - return {"message": f"file for crystal {name} created"} + return {"message": f"crystal {name} in collection {collection} created"} @app.delete("/{name}") -async def delete_hkl_object(name: str, repo=Depends(get_store)): - await repo.delete(name) - - return {"message": f"file for crystal {name} deleted"} +async def delete_hkl_object( + name: str, + store=Depends(get_store), + collection: Optional[str] = Query(default=None, example="B07"), +): + await store.delete(name, collection) + + return {"message": f"crystal {name} in collection {collection} deleted"} diff --git a/src/diffcalc_API/services/constraints.py b/src/diffcalc_API/services/constraints.py index fea4f77..60118ea 100644 --- a/src/diffcalc_API/services/constraints.py +++ b/src/diffcalc_API/services/constraints.py @@ -1,4 +1,4 @@ -from typing import Dict, Union +from typing import Dict, Optional, Union from diffcalc.hkl.constraints import Constraints @@ -7,8 +7,10 @@ from diffcalc_API.stores.protocol import HklCalcStore -async def get_constraints(name: str, store: HklCalcStore) -> str: - hklcalc = await store.load(name) +async def get_constraints( + name: str, store: HklCalcStore, collection: Optional[str] +) -> str: + hklcalc = await store.load(name, collection) return str(hklcalc.constraints) @@ -16,8 +18,9 @@ async def set_constraints( name: str, constraints: Dict[str, Union[float, bool]], store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) boolean_constraints = set(constraints.keys()).intersection( CONSTRAINTS_WITH_NO_VALUE @@ -27,20 +30,21 @@ async def set_constraints( hklcalc.constraints = Constraints(constraints) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) async def remove_constraint( name: str, property: str, store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) check_constraint_exists(property) setattr(hklcalc.constraints, property, None) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) async def set_constraint( @@ -48,8 +52,9 @@ async def set_constraint( property: str, value: Union[float, bool], store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) check_constraint_exists(property) if property in CONSTRAINTS_WITH_NO_VALUE: @@ -57,4 +62,4 @@ async def set_constraint( setattr(hklcalc.constraints, property, value) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) diff --git a/src/diffcalc_API/services/hkl.py b/src/diffcalc_API/services/hkl.py index 894fb83..22d6b61 100644 --- a/src/diffcalc_API/services/hkl.py +++ b/src/diffcalc_API/services/hkl.py @@ -19,8 +19,9 @@ async def lab_position_from_miller_indices( miller_indices: Tuple[float, float, float], wavelength: float, store: HklCalcStore, + collection: Optional[str], ) -> List[Dict[str, float]]: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) check_valid_miller_indices(miller_indices) all_positions = hklcalc.get_position(*miller_indices, wavelength) @@ -33,8 +34,9 @@ async def miller_indices_from_lab_position( pos: Tuple[float, float, float, float, float, float], wavelength: float, store: HklCalcStore, + collection: Optional[str], ) -> Tuple[Any, ...]: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) position = hklcalc.get_hkl(Position(*pos), wavelength) return tuple(np.round(position, 16)) @@ -46,8 +48,9 @@ async def scan_hkl( inc: PositionType, wavelength: float, store: HklCalcStore, + collection: Optional[str], ) -> Dict[str, List[Dict[str, float]]]: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) axes_values = [ generate_axis(start[i], stop[i], inc[i]) if inc[i] != 0 else [0] for i in range(3) @@ -70,8 +73,9 @@ async def scan_wavelength( inc: float, hkl: PositionType, store: HklCalcStore, + collection: Optional[str], ) -> Dict[str, List[Dict[str, float]]]: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) check_valid_scan_bounds(start, stop, inc) wavelengths = np.arange(start, stop + inc, inc) result = {} @@ -92,8 +96,9 @@ async def scan_constraint( hkl: PositionType, wavelength: float, store: HklCalcStore, + collection: Optional[str], ) -> Dict[str, List[Dict[str, float]]]: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) check_valid_scan_bounds(start, stop, inc) result = {} for value in np.arange(start, stop + inc, inc): @@ -125,10 +130,11 @@ async def calculate_ub( first_tag: Optional[Union[int, str]], second_tag: Optional[Union[int, str]], store: HklCalcStore, + collection: Optional[str], ) -> str: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) calculate_ub_matrix(hklcalc, first_tag, second_tag) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) return str(np.round(hklcalc.ubcalc.UB, 6)) diff --git a/src/diffcalc_API/services/ub.py b/src/diffcalc_API/services/ub.py index 1f9ffed..c37f96e 100644 --- a/src/diffcalc_API/services/ub.py +++ b/src/diffcalc_API/services/ub.py @@ -1,4 +1,4 @@ -from typing import Tuple, Union +from typing import Optional, Tuple, Union from diffcalc.hkl.geometry import Position @@ -13,8 +13,8 @@ from diffcalc_API.stores.protocol import HklCalcStore -async def get_ub(name: str, store: HklCalcStore) -> str: - hklcalc = await store.load(name) +async def get_ub(name: str, store: HklCalcStore, collection: Optional[str]) -> str: + hklcalc = await store.load(name, collection) return str(hklcalc.ubcalc) @@ -23,8 +23,9 @@ async def add_reflection( name: str, params: AddReflectionParams, store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) hklcalc.ubcalc.add_reflection( params.hkl, @@ -33,15 +34,16 @@ async def add_reflection( params.tag, ) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) async def edit_reflection( name: str, params: EditReflectionParams, store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) reflection = get_reflection(hklcalc, params.tag_or_idx) hklcalc.ubcalc.edit_reflection( @@ -52,28 +54,30 @@ async def edit_reflection( params.tag_or_idx if isinstance(params.tag_or_idx, str) else None, ) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) async def delete_reflection( name: str, tag_or_idx: Union[str, int], store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) _ = get_reflection(hklcalc, tag_or_idx) hklcalc.ubcalc.del_reflection(tag_or_idx) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) async def add_orientation( name: str, params: AddOrientationParams, store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) position = Position(*params.position) if params.position else None hklcalc.ubcalc.add_orientation( @@ -83,15 +87,16 @@ async def add_orientation( params.tag, ) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) async def edit_orientation( name: str, params: EditOrientationParams, store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) orientation = get_orientation(hklcalc, params.tag_or_idx) hklcalc.ubcalc.edit_orientation( @@ -102,28 +107,31 @@ async def edit_orientation( params.tag_or_idx if isinstance(params.tag_or_idx, str) else None, ) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) async def delete_orientation( name: str, tag_or_idx: Union[str, int], store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) _ = get_orientation(hklcalc, tag_or_idx) hklcalc.ubcalc.del_orientation(tag_or_idx) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) -async def set_lattice(name: str, params: SetLatticeParams, store: HklCalcStore) -> None: - hklcalc = await store.load(name) +async def set_lattice( + name: str, params: SetLatticeParams, store: HklCalcStore, collection: Optional[str] +) -> None: + hklcalc = await store.load(name, collection) hklcalc.ubcalc.set_lattice(name=name, **params.dict()) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) async def modify_property( @@ -131,9 +139,10 @@ async def modify_property( property: str, target_value: Tuple[float, float, float], store: HklCalcStore, + collection: Optional[str], ) -> None: - hklcalc = await store.load(name) + hklcalc = await store.load(name, collection) setattr(hklcalc.ubcalc, property, target_value) - await store.save(name, hklcalc) + await store.save(name, hklcalc, collection) diff --git a/src/diffcalc_API/stores/mongo.py b/src/diffcalc_API/stores/mongo.py new file mode 100644 index 0000000..8a53770 --- /dev/null +++ b/src/diffcalc_API/stores/mongo.py @@ -0,0 +1,77 @@ +from typing import Any, Dict, Optional + +import numpy as np +from diffcalc.hkl.calc import HklCalculation +from diffcalc.hkl.constraints import Constraints +from diffcalc.ub.calc import UBCalculation +from motor.motor_asyncio import AsyncIOMotorCollection as Collection +from pymongo.results import DeleteResult + +from diffcalc_API.database import database +from diffcalc_API.errors.definitions import ( + ALL_RESPONSES, + DiffcalcAPIException, + ErrorCodes, +) + + +class Codes(ErrorCodes): + OVERWRITE_ERROR = 405 + DOCUMENT_NOT_FOUND_ERROR = 404 + + +class OverwriteError(DiffcalcAPIException): + def __init__(self, name: str) -> None: + self.detail = ( + f"Document already exists for crystal {name}!" + f"\nEither delete via DELETE request to this URL " + f"or change the existing properties. " + ) + self.status_code = Codes.OVERWRITE_ERROR + + +class DocumentNotFoundError(DiffcalcAPIException): + def __init__(self, name: str, action: str) -> None: + self.detail = f"Document for crystal {name} not found! Cannot {action}." + self.error_code = Codes.DOCUMENT_NOT_FOUND_ERROR + + +class MongoHklCalcStore: + def __init__( + self, + ) -> None: + self.responses = { + code: ALL_RESPONSES[code] for code in np.unique(Codes.all_codes()) + } + + async def create(self, name: str, collection: Optional[str]) -> None: + coll: Collection = database[collection if collection else "default"] + + if await coll.find_one({"ubcalc.name": name}): + raise OverwriteError(name) + + ubcalc = UBCalculation(name=name) + constraints = Constraints() + hkl = HklCalculation(ubcalc, constraints) + + await coll.insert_one(hkl.asdict) + + async def delete(self, name: str, collection: Optional[str]) -> None: + coll: Collection = database[collection if collection else "default"] + result: DeleteResult = await coll.delete_one({"ubcalc.name": name}) + if result.deleted_count == 0: + raise DocumentNotFoundError(name, "delete") + + async def save( + self, name: str, hkl: HklCalculation, collection: Optional[str] + ) -> None: + coll: Collection = database[collection if collection else "default"] + await coll.find_one_and_update({"ubcalc.name": name}, {"$set": hkl.asdict}) + + async def load(self, name: str, collection: Optional[str]) -> HklCalculation: + coll: Collection = database[collection if collection else "default"] + hkl_json: Optional[Dict[str, Any]] = await coll.find_one({"ubcalc.name": name}) + if not hkl_json: + raise DocumentNotFoundError(name, "load") + + return HklCalculation.fromdict(hkl_json) diff --git a/src/diffcalc_API/stores/pickling.py b/src/diffcalc_API/stores/pickling.py index de2b289..d57de1d 100644 --- a/src/diffcalc_API/stores/pickling.py +++ b/src/diffcalc_API/stores/pickling.py @@ -1,5 +1,6 @@ import pickle from pathlib import Path +from typing import Optional import numpy as np from diffcalc.hkl.calc import HklCalculation @@ -12,75 +13,85 @@ DiffcalcAPIException, ErrorCodes, ) -from diffcalc_API.stores.protocol import HklCalcStore class Codes(ErrorCodes): - attempting_to_overwrite = 405 - check_file_exists = 404 + OVERWRITE_ERROR = 405 + FILE_NOT_FOUND_ERROR = 404 -def attempting_to_overwrite(filename: str) -> None: - pickled_file = Path(SAVE_PICKLES_FOLDER) / filename - if (pickled_file).is_file(): - message = ( - f"File already exists for crystal {filename}!" +class OverwriteError(DiffcalcAPIException): + def __init__(self, name): + self.detail = ( + f"File already exists for crystal {name}!" f"\nEither delete via DELETE request to this URL " f"or change the existing properties. " ) - raise DiffcalcAPIException(status_code=405, detail=message) + self.status_code = Codes.OVERWRITE_ERROR - return - -def check_file_exists(pickled_file: Path, name: str) -> None: - if not (pickled_file).is_file(): - message = ( +class FileNotFoundError(DiffcalcAPIException): + def __init__(self, name): + self.detail = ( f"File for crystal {name} not found." f"\nYou need to post to" f" http://localhost:8000/{name}" f" first to generate the pickled file.\n" ) - raise DiffcalcAPIException(status_code=404, detail=message) + self.status_code = Codes.FILE_NOT_FOUND_ERROR class PicklingHklCalcStore: - _root_directory: Path + _root_directory: Path = Path(SAVE_PICKLES_FOLDER) - def __init__(self, root_directory: Path) -> None: - self._root_directory = root_directory + def __init__(self) -> None: self.responses = { - code: ALL_RESPONSES[code] for code in np.unique(Codes().all_codes()) + code: ALL_RESPONSES[code] for code in np.unique(Codes.all_codes()) } - async def create(self, name: str) -> None: - attempting_to_overwrite(name) + async def create(self, name: str, collection: Optional[str]) -> None: + pickled_file = ( + Path(SAVE_PICKLES_FOLDER) / (collection if collection else "default") / name + ) + + if (pickled_file).is_file(): + raise OverwriteError(name) + + if not (pickled_file.parent).is_dir(): + pickled_file.parent.mkdir() ubcalc = UBCalculation(name=name) constraints = Constraints() hkl = HklCalculation(ubcalc, constraints) - await self.save(name, hkl) + await self.save(name, hkl, collection) + + async def delete(self, name: str, collection: Optional[str]) -> None: + pickled_file = ( + Path(SAVE_PICKLES_FOLDER) / (collection if collection else "default") / name + ) + if not pickled_file.is_file(): + raise FileNotFoundError(name) - async def delete(self, name: str) -> None: - pickle_file_path = Path(SAVE_PICKLES_FOLDER) / name - check_file_exists(pickle_file_path, name) - Path(pickle_file_path).unlink() + Path(pickled_file).unlink() - async def save(self, name: str, calc: HklCalculation) -> None: - file_path = self._root_directory / name + async def save( + self, name: str, calc: HklCalculation, collection: Optional[str] + ) -> None: + file_path = ( + self._root_directory / (collection if collection else "default") / name + ) with open(file_path, "wb") as stream: pickle.dump(obj=calc, file=stream) - async def load(self, name: str) -> HklCalculation: - file_path = self._root_directory / name - check_file_exists(file_path, name) + async def load(self, name: str, collection: Optional[str]) -> HklCalculation: + file_path = ( + self._root_directory / (collection if collection else "default") / name + ) + if not file_path.is_file(): + raise FileNotFoundError(name) with open(file_path, "rb") as stream: hkl: HklCalculation = pickle.load(stream) return hkl - - -def get_store() -> HklCalcStore: - return PicklingHklCalcStore(Path(SAVE_PICKLES_FOLDER)) diff --git a/src/diffcalc_API/stores/protocol.py b/src/diffcalc_API/stores/protocol.py index 057d3a0..751c1b2 100644 --- a/src/diffcalc_API/stores/protocol.py +++ b/src/diffcalc_API/stores/protocol.py @@ -1,4 +1,5 @@ -from typing import Any, Dict, Protocol, Union +from importlib import import_module +from typing import Any, Dict, Optional, Protocol, Union from diffcalc.hkl.calc import HklCalculation @@ -10,14 +11,31 @@ class HklCalcStore(Protocol): responses: Dict[Union[int, str], Dict[str, Any]] - async def create(self, name: str) -> None: + async def create(self, name: str, collection: Optional[str]) -> None: ... - async def delete(self, name: str) -> None: + async def delete(self, name: str, collection: Optional[str]) -> None: ... - async def save(self, name: str, calc: HklCalculation) -> None: + async def save( + self, name: str, calc: HklCalculation, collection: Optional[str] + ) -> None: ... - async def load(self, name: str) -> HklCalculation: + async def load(self, name: str, collection: Optional[str]) -> HklCalculation: ... + + +STORE: Optional[HklCalcStore] = None + + +def get_store() -> HklCalcStore: + if STORE is None: + raise ValueError() + return STORE + + +def setup_store(store_location: str, *args) -> None: + global STORE + path, clsname = store_location.rsplit(".", 1) + STORE = getattr(import_module(path), clsname)(*args) diff --git a/tests/conftest.py b/tests/conftest.py index d7034dc..ade95f8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Union +from typing import Any, Dict, Optional, Union from diffcalc.hkl.calc import HklCalculation @@ -8,14 +8,19 @@ def __init__(self, hkl: HklCalculation): self.hkl = hkl self.responses: Dict[Union[int, str], Dict[str, Any]] = {} - async def create(self, name: str) -> None: + async def create(self, name: str, collection: Optional[str]) -> None: pass - async def delete(self, name: str) -> None: + async def delete(self, name: str, collection: Optional[str]) -> None: pass - async def save(self, name: str, calc: HklCalculation) -> None: + async def save( + self, name: str, calc: HklCalculation, collection: Optional[str] + ) -> None: pass - async def load(self, name: str) -> HklCalculation: + async def load(self, name: str, collection: Optional[str]) -> HklCalculation: return self.hkl + + def use_hkl(self, hkl: HklCalculation): + self.hkl = hkl diff --git a/tests/test_constraints.py b/tests/test_constraints.py index af169f0..05d2707 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -8,8 +8,7 @@ from diffcalc_API.errors.constraints import Codes from diffcalc_API.server import app -from diffcalc_API.stores.pickling import get_store -from diffcalc_API.stores.protocol import HklCalcStore +from diffcalc_API.stores.protocol import HklCalcStore, get_store from tests.conftest import FakeHklCalcStore dummy_hkl = HklCalculation(UBCalculation(name="dummy"), Constraints()) @@ -27,8 +26,8 @@ def client() -> TestClient: def test_set_constraints(client: TestClient): - response = client.put( - "/constraints/test/set", + response = client.post( + "/constraints/test?collection=B07", json={ "delta": 1, "bin_eq_bout": 1, @@ -47,21 +46,21 @@ def test_set_constraints(client: TestClient): def test_set_varying_number_of_constraints_and_with_incorrect_fields( client: TestClient, ): - wrong_field_response = client.put( - "/constraints/test/set", + wrong_field_response = client.post( + "/constraints/test", json={ "fakeField": 10, }, ) - assert wrong_field_response.status_code == 400 # make this more concrete. + assert wrong_field_response.status_code == 400 assert ( ast.literal_eval(wrong_field_response._content.decode())["type"] == "" ) - too_many_fields_response = client.put( - "/constraints/test/set", + too_many_fields_response = client.post( + "/constraints/test", json={"delta": 1, "bin_eq_bout": 1, "mu": 2, "omega": 5}, ) @@ -73,8 +72,8 @@ def test_set_varying_number_of_constraints_and_with_incorrect_fields( } dummy_hkl.constraints = Constraints() - too_few_fields_response = client.put( - "/constraints/test/set", + too_few_fields_response = client.post( + "/constraints/test", json={"delta": 1, "bin_eq_bout": 1}, ) @@ -84,14 +83,14 @@ def test_set_varying_number_of_constraints_and_with_incorrect_fields( def test_set_and_remove_constraint(client: TestClient): dummy_hkl.constraints = Constraints() set_response = client.patch( - "/constraints/test/constrain/alpha", + "/constraints/test/alpha", json=1, ) assert set_response.status_code == 200 assert dummy_hkl.constraints.asdict == {"alpha": 1.0} - remove_response = client.patch("/constraints/test/unconstrain/alpha") + remove_response = client.delete("/constraints/test/alpha") assert remove_response.status_code == 200 assert dummy_hkl.constraints.asdict == {} @@ -100,14 +99,14 @@ def test_set_and_remove_constraint(client: TestClient): def test_set_or_remove_nonexisting_constraint(client: TestClient): dummy_hkl.constraints = Constraints() set_response = client.patch( - "/constraints/test/constrain/fake", + "/constraints/test/fake", json=1, ) assert set_response.status_code == Codes.CHECK_CONSTRAINT_EXISTS assert dummy_hkl.constraints.asdict == {} - remove_response = client.patch("/constraints/test/unconstrain/fake") + remove_response = client.delete("/constraints/test/fake") assert remove_response.status_code == Codes.CHECK_CONSTRAINT_EXISTS assert dummy_hkl.constraints.asdict == {} diff --git a/tests/test_hklcalc.py b/tests/test_hklcalc.py index eff2580..2ce40c4 100644 --- a/tests/test_hklcalc.py +++ b/tests/test_hklcalc.py @@ -8,11 +8,10 @@ from diffcalc_API.errors.hkl import Codes from diffcalc_API.server import app -from diffcalc_API.stores.pickling import get_store -from diffcalc_API.stores.protocol import HklCalcStore +from diffcalc_API.stores.protocol import HklCalcStore, get_store from tests.conftest import FakeHklCalcStore -dummy_hkl = HklCalculation(UBCalculation(name="sixcircle"), Constraints()) +dummy_hkl = HklCalculation(UBCalculation(name="dummy"), Constraints()) dummy_hkl.ubcalc.set_lattice("SiO2", 4.913, 5.405) dummy_hkl.ubcalc.n_hkl = (1, 0, 0) @@ -29,7 +28,7 @@ def dummy_get_store() -> HklCalcStore: return FakeHklCalcStore(dummy_hkl) -@pytest.fixture +@pytest.fixture() def client() -> TestClient: app.dependency_overrides[get_store] = dummy_get_store @@ -37,9 +36,8 @@ def client() -> TestClient: def test_miller_indices_stay_the_same_after_transformation(client: TestClient): - lab_positions = client.get( - "/calculate/test/position/lab", + "/hkl/test/position/lab", params={"miller_indices": [0, 0, 1], "wavelength": 1}, ) @@ -48,7 +46,7 @@ def test_miller_indices_stay_the_same_after_transformation(client: TestClient): for pos in possible_positions: miller_positions = client.get( - "/calculate/test/position/hkl", + "/hkl/test/position/hkl", params={ "pos": [ pos["mu"], @@ -72,7 +70,7 @@ def test_scan_hkl( client: TestClient, ): lab_positions = client.get( - "/calculate/test/scan/hkl", + "/hkl/test/scan/hkl", params={ "start": [1, 0, 1], "stop": [2, 0, 2], @@ -90,7 +88,7 @@ def test_scan_wavelength( client: TestClient, ): lab_positions = client.get( - "/calculate/test/scan/wavelength", + "/hkl/test/scan/wavelength", params={ "start": 1, "stop": 2, @@ -108,7 +106,7 @@ def test_scan_constraint( client: TestClient, ): lab_positions = client.get( - "/calculate/test/scan/alpha", + "/hkl/test/scan/alpha", params={ "start": 1, "stop": 2, @@ -125,7 +123,7 @@ def test_scan_constraint( def test_invalid_scans(client: TestClient): invalid_miller_indices = client.get( - "/calculate/test/scan/hkl", + "/hkl/test/scan/hkl", params={ "start": [0, 0, 0], "stop": [1, 0, 1], @@ -137,7 +135,7 @@ def test_invalid_scans(client: TestClient): assert invalid_miller_indices.status_code == Codes.CHECK_VALID_MILLER_INDICES invalid_wavelength_scan = client.get( - "/calculate/test/scan/wavelength", + "/hkl/test/scan/wavelength", params={ "start": 1, "stop": 2, @@ -151,7 +149,7 @@ def test_invalid_scans(client: TestClient): def test_calc_ub(client: TestClient): response = client.get( - "/calculate/test/UB", params={"first_tag": "refl1", "second_tag": "plane"} + "/hkl/test/UB", params={"first_tag": "refl1", "second_tag": "plane"} ) expected_ub = ( "[[ 1.27889 -0. 0. ], [-0. 1.278111 0.04057 ]," @@ -164,7 +162,7 @@ def test_calc_ub(client: TestClient): def test_calc_ub_fails_when_incorrect_tags(client: TestClient): response = client.get( - "/calculate/test/UB", params={"first_tag": "one", "second_tag": "two"} + "/hkl/test/UB", params={"first_tag": "one", "second_tag": "two"} ) assert response.status_code == Codes.CALCULATE_UB_MATRIX diff --git a/tests/test_ubcalc.py b/tests/test_ubcalc.py index d937c12..a36d564 100644 --- a/tests/test_ubcalc.py +++ b/tests/test_ubcalc.py @@ -8,8 +8,7 @@ from diffcalc_API.errors.ub import Codes from diffcalc_API.server import app -from diffcalc_API.stores.pickling import get_store -from diffcalc_API.stores.protocol import HklCalcStore +from diffcalc_API.stores.protocol import HklCalcStore, get_store from tests.conftest import FakeHklCalcStore dummy_hkl = HklCalculation(UBCalculation(name="dummy"), Constraints()) @@ -27,7 +26,7 @@ def client() -> TestClient: def test_add_reflection(client: TestClient): - response = client.put( + response = client.post( "/ub/test/reflection", json={ "hkl": [0, 0, 1], @@ -45,7 +44,7 @@ def test_add_reflection(client: TestClient): def test_edit_reflection(client: TestClient): dummy_hkl.ubcalc.add_reflection([0, 0, 1], Position(7, 0, 10, 0, 0, 0), 12, "foo") - response = client.patch( + response = client.put( "/ub/test/reflection", json={ "energy": 13, @@ -72,7 +71,7 @@ def test_delete_reflection(client: TestClient): def test_edit_or_delete_reflection_fails_for_non_existing_reflection( client: TestClient, ): - edit_response = client.patch( + edit_response = client.put( "/ub/test/reflection", json={ "energy": 13, @@ -89,7 +88,7 @@ def test_edit_or_delete_reflection_fails_for_non_existing_reflection( def test_add_orientation(client: TestClient): - response = client.put( + response = client.post( "/ub/test/orientation", json={ "hkl": [0, 1, 0], @@ -106,7 +105,7 @@ def test_add_orientation(client: TestClient): def test_edit_orientation(client: TestClient): dummy_hkl.ubcalc.add_orientation([0, 0, 1], [0, 0, 1], None, "bar") - response = client.patch( + response = client.put( "/ub/test/orientation", json={ "xyz": [1, 1, 0], @@ -139,7 +138,7 @@ def test_delete_orientation(client: TestClient): def test_edit_or_delete_orientation_fails_for_non_existing_orientation( client: TestClient, ): - edit_response = client.patch( + edit_response = client.put( "/ub/test/orientation", json={ "xyz": [1, 1, 0], @@ -181,7 +180,7 @@ def test_set_lattice_fails_for_empty_data(client: TestClient): def test_modify_property(client: TestClient): - response = client.patch( + response = client.put( "/ub/test/n_hkl", json=[0, 0, 1], ) @@ -191,7 +190,7 @@ def test_modify_property(client: TestClient): def test_modify_non_existent_property(client: TestClient): - response = client.patch( + response = client.put( "/ub/test/silly_property", json=[0, 0, 1], )