diff --git a/.gitignore b/.gitignore index 9beb605..4997e24 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ config.yaml env/ .vagrant/ .idea +.vscode/ # Elastic Beanstalk Files .elasticbeanstalk/* diff --git a/Pipfile.lock b/Pipfile.lock index 740a86e..5010cb5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -18,11 +18,11 @@ "default": { "amqp": { "hashes": [ - "sha256:19a917e260178b8d410122712bac69cb3e6db010d68f6101e7307508aded5e68", - "sha256:19d851b879a471fcfdcf01df9936cff924f422baa77653289f7095dedd5fb26a" + "sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8", + "sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d" ], "index": "pypi", - "version": "==2.5.1" + "version": "==2.5.2" }, "appdirs": { "hashes": [ @@ -42,19 +42,19 @@ }, "celery": { "hashes": [ - "sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9", - "sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be" + "sha256:7c544f37a84a5eadc44cab1aa8c9580dff94636bb81978cdf9bf8012d9ea7d8f", + "sha256:d3363bb5df72d74420986a435449f3c3979285941dff57d5d97ecba352a0e3e2" ], "index": "pypi", - "version": "==4.3.0" + "version": "==4.4.0" }, "certifi": { "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], "index": "pypi", - "version": "==2019.9.11" + "version": "==2019.11.28" }, "chardet": { "hashes": [ @@ -89,10 +89,11 @@ }, "flask-assets": { "hashes": [ - "sha256:6031527b89fb3509d1581d932affa5a79dd348cfffb58d0aef99a43461d47847" + "sha256:1dfdea35e40744d46aada72831f7613d67bf38e8b20ccaaa9e91fdc37aa3b8c2", + "sha256:2845bd3b479be9db8556801e7ebc2746ce2d9edb4e7b64a1c786ecbfc1e5867b" ], "index": "pypi", - "version": "==0.12" + "version": "==2.0" }, "flask-compress": { "hashes": [ @@ -111,26 +112,27 @@ }, "flask-minify": { "hashes": [ - "sha256:666957ee8621eaad0ead4e71e7c193ea8cf9f8f550ad02497ae55a82050c2c5e" + "sha256:67f854c98bcdfca77b0dd9c812bb947dee4e790f155938499d3c8a033995389b", + "sha256:cb8ef148287d933f6ae0437beb4e40c517b76b5ca548034afad497313c2e2136" ], "index": "pypi", - "version": "==0.11" + "version": "==0.15" }, "flask-sqlalchemy": { "hashes": [ - "sha256:0c9609b0d72871c540a7945ea559c8fdf5455192d2db67219509aed680a3d45a", - "sha256:8631bbea987bc3eb0f72b1f691d47bd37ceb795e73b59ab48586d76d75a7c605" + "sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327", + "sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d" ], "index": "pypi", - "version": "==2.4.0" + "version": "==2.4.1" }, "gunicorn": { "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" + "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", + "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" ], "index": "pypi", - "version": "==19.9.0" + "version": "==20.0.4" }, "htmlmin": { "hashes": [ @@ -155,10 +157,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:652234b6ab8f2506ae58e528b6fbcc668831d3cc758e1bc01ef438d328b68cdb", - "sha256:6f264986fb88042bc1f0535fa9a557e6a376cfe5679dc77caac7fe8b5d43d05f" + "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45", + "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f" ], - "version": "==0.22" + "markers": "python_version < '3.8'", + "version": "==1.3.0" }, "itsdangerous": { "hashes": [ @@ -170,11 +173,11 @@ }, "jinja2": { "hashes": [ - "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", - "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", + "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" ], "index": "pypi", - "version": "==2.10.1" + "version": "==2.10.3" }, "jsmin": { "hashes": [ @@ -185,11 +188,11 @@ }, "kombu": { "hashes": [ - "sha256:55274dc75eb3c3994538b0973a0fadddb236b698a4bc135b8aa4981e0a710b8f", - "sha256:e5f0312dfb9011bebbf528ccaf118a6c2b5c3b8244451f08381fb23e7715809b" + "sha256:2a9e7adff14d046c9996752b2c48b6d9185d0b992106d5160e1a179907a5d4ac", + "sha256:67b32ccb6fea030f8799f8fd50dd08e03a4b99464ebc4952d71d8747b1a52ad1" ], "index": "pypi", - "version": "==4.6.4" + "version": "==4.6.7" }, "lesscpy": { "hashes": [ @@ -242,47 +245,52 @@ }, "marshmallow": { "hashes": [ - "sha256:4b790b6910c0261d22de296ed42569affcbd80097eb770eb6088a79bef69c330", - "sha256:fe31ac05fbde972909a752d12b808cbbc15a50ed0a692410e1b227ff10b483ff" + "sha256:0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e", + "sha256:3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f" ], - "version": "==3.0.5" + "version": "==3.3.0" }, "marshmallow-sqlalchemy": { "hashes": [ - "sha256:b53ae45f6f113ae5433211786129ecb6eaf3646a3a333e769eeb22593b6dbe9c", - "sha256:c4dd561ff42f39e44619e6558a28da7154fea62ffada6815403f1381762c87db" + "sha256:0d72beaf777f8b420c4dc94684252ae0e0a79556ccc4128129d2588f9ff72888", + "sha256:93fd8fad2b33d92a1ae58328eeb0f39ed174858d82f9e7084a174df7b41fd3a4" ], "index": "pypi", - "version": "==0.19.0" + "version": "==0.21.0" }, "more-itertools": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", + "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564" ], - "version": "==7.2.0" + "version": "==8.0.2" }, "numpy": { "hashes": [ - "sha256:05dbfe72684cc14b92568de1bc1f41e5f62b00f714afc9adee42f6311738091f", - "sha256:0d82cb7271a577529d07bbb05cb58675f2deb09772175fab96dc8de025d8ac05", - "sha256:10132aa1fef99adc85a905d82e8497a580f83739837d7cbd234649f2e9b9dc58", - "sha256:12322df2e21f033a60c80319c25011194cd2a21294cc66fee0908aeae2c27832", - "sha256:16f19b3aa775dddc9814e02a46b8e6ae6a54ed8cf143962b4e53f0471dbd7b16", - "sha256:3d0b0989dd2d066db006158de7220802899a1e5c8cf622abe2d0bd158fd01c2c", - "sha256:438a3f0e7b681642898fd7993d38e2bf140a2d1eafaf3e89bb626db7f50db355", - "sha256:5fd214f482ab53f2cea57414c5fb3e58895b17df6e6f5bca5be6a0bb6aea23bb", - "sha256:73615d3edc84dd7c4aeb212fa3748fb83217e00d201875a47327f55363cef2df", - "sha256:7bd355ad7496f4ce1d235e9814ec81ee3d28308d591c067ce92e49f745ba2c2f", - "sha256:7d077f2976b8f3de08a0dcf5d72083f4af5411e8fddacd662aae27baa2601196", - "sha256:a4092682778dc48093e8bda8d26ee8360153e2047826f95a3f5eae09f0ae3abf", - "sha256:b458de8624c9f6034af492372eb2fee41a8e605f03f4732f43fc099e227858b2", - "sha256:e70fc8ff03a961f13363c2c95ef8285e0cf6a720f8271836f852cc0fa64e97c8", - "sha256:ee8e9d7cad5fe6dde50ede0d2e978d81eafeaa6233fb0b8719f60214cf226578", - "sha256:f4a4f6aba148858a5a5d546a99280f71f5ee6ec8182a7d195af1a914195b21a2" - ], - "index": "pypi", - "version": "==1.17.2" + "sha256:0a7a1dd123aecc9f0076934288ceed7fd9a81ba3919f11a855a7887cbe82a02f", + "sha256:0c0763787133dfeec19904c22c7e358b231c87ba3206b211652f8cbe1241deb6", + "sha256:3d52298d0be333583739f1aec9026f3b09fdfe3ddf7c7028cb16d9d2af1cca7e", + "sha256:43bb4b70585f1c2d153e45323a886839f98af8bfa810f7014b20be714c37c447", + "sha256:475963c5b9e116c38ad7347e154e5651d05a2286d86455671f5b1eebba5feb76", + "sha256:64874913367f18eb3013b16123c9fed113962e75d809fca5b78ebfbb73ed93ba", + "sha256:683828e50c339fc9e68720396f2de14253992c495fdddef77a1e17de55f1decc", + "sha256:6ca4000c4a6f95a78c33c7dadbb9495c10880be9c89316aa536eac359ab820ae", + "sha256:75fd817b7061f6378e4659dd792c84c0b60533e867f83e0d1e52d5d8e53df88c", + "sha256:7d81d784bdbed30137aca242ab307f3e65c8d93f4c7b7d8f322110b2e90177f9", + "sha256:8d0af8d3664f142414fd5b15cabfd3b6cc3ef242a3c7a7493257025be5a6955f", + "sha256:9679831005fb16c6df3dd35d17aa31dc0d4d7573d84f0b44cc481490a65c7725", + "sha256:a8f67ebfae9f575d85fa859b54d3bdecaeece74e3274b0b5c5f804d7ca789fe1", + "sha256:acbf5c52db4adb366c064d0b7c7899e3e778d89db585feadd23b06b587d64761", + "sha256:ada4805ed51f5bcaa3a06d3dd94939351869c095e30a2b54264f5a5004b52170", + "sha256:c7354e8f0eca5c110b7e978034cd86ed98a7a5ffcf69ca97535445a595e07b8e", + "sha256:e2e9d8c87120ba2c591f60e32736b82b67f72c37ba88a4c23c81b5b8fa49c018", + "sha256:e467c57121fe1b78a8f68dd9255fbb3bb3f4f7547c6b9e109f31d14569f490c3", + "sha256:ede47b98de79565fcd7f2decb475e2dcc85ee4097743e551fe26cfc7eb3ff143", + "sha256:f58913e9227400f1395c7b800503ebfdb0772f1c33ff8cb4d6451c06cabdf316", + "sha256:fe39f5fd4103ec4ca3cb8600b19216cd1ff316b4990f4c0b6057ad982c0a34d5" + ], + "index": "pypi", + "version": "==1.17.4" }, "ply": { "hashes": [ @@ -329,11 +337,11 @@ }, "pyparsing": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", + "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" ], "index": "pypi", - "version": "==2.4.2" + "version": "==2.4.5" }, "pyscss": { "hashes": [ @@ -352,38 +360,36 @@ }, "pytz": { "hashes": [ - "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", - "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" ], "index": "pypi", - "version": "==2019.2" + "version": "==2019.3" }, "pyyaml": { "hashes": [ - "sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", - "sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", - "sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", - "sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", - "sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", - "sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", - "sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", - "sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", - "sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", - "sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", - "sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", - "sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", - "sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8" + "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", + "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", + "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", + "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", + "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", + "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", + "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", + "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", + "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", + "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", + "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" ], "index": "pypi", - "version": "==5.1.2" + "version": "==5.2" }, "redis": { "hashes": [ - "sha256:98a22fb750c9b9bb46e75e945dc3f61d0ab30d06117cbb21ff9cd1d315fedd3b", - "sha256:c504251769031b0dd7dd5cf786050a6050197c6de0d37778c80c08cb04ae8275" + "sha256:3613daad9ce5951e426f460deddd5caf469e08a3af633e9578fc77d362becf62", + "sha256:8d0fc278d3f5e1249967cba2eb4a5632d19e45ce5c09442b8422d15ee2c22cc2" ], "index": "pypi", - "version": "==3.3.8" + "version": "==3.3.11" }, "requests": { "hashes": [ @@ -395,48 +401,53 @@ }, "scipy": { "hashes": [ - "sha256:0baa64bf42592032f6f6445a07144e355ca876b177f47ad8d0612901c9375bef", - "sha256:243b04730d7223d2b844bda9500310eecc9eda0cba9ceaf0cde1839f8287dfa8", - "sha256:2643cfb46d97b7797d1dbdb6f3c23fe3402904e3c90e6facfe6a9b98d808c1b5", - "sha256:396eb4cdad421f846a1498299474f0a3752921229388f91f60dc3eda55a00488", - "sha256:3ae3692616975d3c10aca6d574d6b4ff95568768d4525f76222fb60f142075b9", - "sha256:435d19f80b4dcf67dc090cc04fde2c5c8a70b3372e64f6a9c58c5b806abfa5a8", - "sha256:46a5e55850cfe02332998b3aef481d33f1efee1960fe6cfee0202c7dd6fc21ab", - "sha256:75b513c462e58eeca82b22fc00f0d1875a37b12913eee9d979233349fce5c8b2", - "sha256:7ccfa44a08226825126c4ef0027aa46a38c928a10f0a8a8483c80dd9f9a0ad44", - "sha256:89dd6a6d329e3f693d1204d5562dd63af0fd7a17854ced17f9cbc37d5b853c8d", - "sha256:a81da2fe32f4eab8b60d56ad43e44d93d392da228a77e229e59b51508a00299c", - "sha256:a9d606d11eb2eec7ef893eb825017fbb6eef1e1d0b98a5b7fc11446ebeb2b9b1", - "sha256:ac37eb652248e2d7cbbfd89619dce5ecfd27d657e714ed049d82f19b162e8d45", - "sha256:cbc0611699e420774e945f6a4e2830f7ca2b3ee3483fca1aa659100049487dd5", - "sha256:d02d813ec9958ed63b390ded463163685af6025cb2e9a226ec2c477df90c6957", - "sha256:dd3b52e00f93fd1c86f2d78243dfb0d02743c94dd1d34ffea10055438e63b99d" - ], - "index": "pypi", - "version": "==1.3.1" + "sha256:00af72998a46c25bdb5824d2b729e7dabec0c765f9deb0b504f928591f5ff9d4", + "sha256:0902a620a381f101e184a958459b36d3ee50f5effd186db76e131cbefcbb96f7", + "sha256:1e3190466d669d658233e8a583b854f6386dd62d655539b77b3fa25bfb2abb70", + "sha256:2cce3f9847a1a51019e8c5b47620da93950e58ebc611f13e0d11f4980ca5fecb", + "sha256:3092857f36b690a321a662fe5496cb816a7f4eecd875e1d36793d92d3f884073", + "sha256:386086e2972ed2db17cebf88610aab7d7f6e2c0ca30042dc9a89cf18dcc363fa", + "sha256:71eb180f22c49066f25d6df16f8709f215723317cc951d99e54dc88020ea57be", + "sha256:770254a280d741dd3436919d47e35712fb081a6ff8bafc0f319382b954b77802", + "sha256:787cc50cab3020a865640aba3485e9fbd161d4d3b0d03a967df1a2881320512d", + "sha256:8a07760d5c7f3a92e440ad3aedcc98891e915ce857664282ae3c0220f3301eb6", + "sha256:8d3bc3993b8e4be7eade6dcc6fd59a412d96d3a33fa42b0fa45dc9e24495ede9", + "sha256:9508a7c628a165c2c835f2497837bf6ac80eb25291055f56c129df3c943cbaf8", + "sha256:a144811318853a23d32a07bc7fd5561ff0cac5da643d96ed94a4ffe967d89672", + "sha256:a1aae70d52d0b074d8121333bc807a485f9f1e6a69742010b33780df2e60cfe0", + "sha256:a2d6df9eb074af7f08866598e4ef068a2b310d98f87dc23bd1b90ec7bdcec802", + "sha256:bb517872058a1f087c4528e7429b4a44533a902644987e7b2fe35ecc223bc408", + "sha256:c5cac0c0387272ee0e789e94a570ac51deb01c796b37fb2aad1fb13f85e2f97d", + "sha256:cc971a82ea1170e677443108703a2ec9ff0f70752258d0e9f5433d00dda01f59", + "sha256:dba8306f6da99e37ea08c08fef6e274b5bf8567bb094d1dbe86a20e532aca088", + "sha256:dc60bb302f48acf6da8ca4444cfa17d52c63c5415302a9ee77b3b21618090521", + "sha256:dee1bbf3a6c8f73b6b218cb28eed8dd13347ea2f87d572ce19b289d6fd3fbc59" + ], + "index": "pypi", + "version": "==1.4.1" }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" ], "index": "pypi", - "version": "==1.12.0" + "version": "==1.13.0" }, "sqlalchemy": { "hashes": [ - "sha256:2f8ff566a4d3a92246d367f2e9cd6ed3edeef670dcd6dda6dfdc9efed88bcd80" + "sha256:bfb8f464a5000b567ac1d350b9090cf081180ec1ab4aa87e7bca12dab25320ec" ], "index": "pypi", - "version": "==1.3.8" + "version": "==1.3.12" }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", + "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" ], "index": "pypi", - "version": "==1.25.3" + "version": "==1.25.7" }, "vine": { "hashes": [ @@ -448,18 +459,19 @@ }, "webassets": { "hashes": [ - "sha256:e7d9c8887343123fd5b32309b33167428cb1318cdda97ece12d0907fd69d38db" + "sha256:167132337677c8cedc9705090f6d48da3fb262c8e0b2773b29f3352f050181cd", + "sha256:a31a55147752ba1b3dc07dee0ad8c8efff274464e08bbdb88c1fd59ffd552724" ], "index": "pypi", - "version": "==0.12.1" + "version": "==2.0" }, "werkzeug": { "hashes": [ - "sha256:00d32beac38fcd48d329566f80d39f10ec2ed994efbecfb8dd4b320062d05902", - "sha256:0a24d43be6a7dce81bae05292356176d6c46d63e42a0dd3f9504b210a9cfaa43" + "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7", + "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4" ], "index": "pypi", - "version": "==0.15.6" + "version": "==0.16.0" }, "xlrd": { "hashes": [ @@ -469,6 +481,51 @@ "index": "pypi", "version": "==1.2.0" }, + "xxhash": { + "hashes": [ + "sha256:06d3350a7586cf4938c5941c3744ab7f633204acb3c4c3d96f694fb0ee8d1bcc", + "sha256:100a97c2ea069f1f03107d1a6894094fa59fb17707bc779e0e127597cac956f9", + "sha256:1cdd06fa8cd9a0238b6deed08ac21455d45cda7c92665356093f36bec1707a71", + "sha256:1d6e4f9c1a8db495f256932c21a321388ac351503525503f587304fef24b60ff", + "sha256:203ba0c2df5bd9a6990dd8521b0d5b03cf7bebac0790cc740c18e61f0aeccc89", + "sha256:217dfbbebeeb84523577410dbb5ae04b86aadec705f074006a329c039684d4f6", + "sha256:2e17e1c9970b8cd6cf80eab92dd4b45b482d90b6b7b1d55d7904ff5858952408", + "sha256:2f7930f70db5bc36415ac13d7b8f4605c50b03f03f13d482b0df110f2a6249e0", + "sha256:37531a8518683594ba05030065f5e8a0f27d49febfe7cd43ad605317677fd314", + "sha256:431051a97c72f8b06b62e26a0f11364a67379825eb650850428611120d102c0c", + "sha256:449f22427435e046430a0f6b72bd87acf6e414205e8038dd6c8bf91466438452", + "sha256:4afcdfe638f49f82a6f24373f85c811cafff32438fbdaedfd540bf1d44ff5b81", + "sha256:507cb7dfd4eba033987b812333705b64ba484a0ac1cec3225dfb155935f80f4f", + "sha256:511e59c0a07c05565ecd10184c9a1fccd1d8be31f37b21b733ab7d049510c825", + "sha256:54e005dbd92f220362556df74b3f6898d3a04a6ad734dd47f70849cab99d0702", + "sha256:54eb964b79bd2409872425b3bfd6c42755dd4586cd1fdbb2ffc1075327df9a14", + "sha256:555c923477a05d560ba1ec81de215c64a22cc2bc4c1d5c9f739523e6f3f3736d", + "sha256:749967f82721639404f5ef6d985d5a0971e12abc6a8f55bfd4b30bad89020d7e", + "sha256:7b656261429d752709d5025b3f2099c2ab383e18e7084fb6bd7534259f0b718d", + "sha256:86838d6fad9ec3719cb292f41d81c7f0115946be17acfc58e9aad252346baa56", + "sha256:8b6b1afe7731d7d9cbb0398b4a811ebb5e6be5c174f72c68abf81f919a435de9", + "sha256:8c4c70bf607811a17ecbd91b7cea85b54064c3d950019e4d2d7e3d0ae4d0bef8", + "sha256:97d11269b08268fde1db033ff1cf1d165e6f9322a38b37a11fc96b5b20a83c5e", + "sha256:9bbc63663b6ed704b051bfdc9f4ba45c97b38ef42786a41a49f55cdadcbe6085", + "sha256:9fbd9ae4b51c8b4a49562983e5413e0b757601bafee59ee8c9cdfeb8b9cb07cf", + "sha256:a10712d0cbf7fff9def2ea194291bfcfc5891be11b2ce243bcbc3a3bc009f8e6", + "sha256:a98347a9985b27f814cf978400fe2dcecf155f986d60b1752176f964212d0880", + "sha256:b2acce56c5b9fd8f8b0ac1c4f451c7b883f17c8e9a0e33a9ec5c280d4f7937f8", + "sha256:bd006bf01fa7f267eab4ede198d828face8ba5457c47271adea1120947c7f0e1", + "sha256:bf2ffb29906afa4c6b0520b68bf521ad15254e0e409e849250c209a3cac8c23f", + "sha256:cf4909947d9e9244ad38adec49291ddc8338157fef70df651bc8255168c07767", + "sha256:d5a2c3922dea3ed2dab17e1748620f0edfa3748bf287f12df5ef997bbf7a0978", + "sha256:d9cd8c83a0473cd16b91e598fc1497cc6c03430320598c16adf7468eeb146c76", + "sha256:dad68b19e1a5c51bf72329b3f95e50465fd10f85e87bb104f435511ad0cddbf5", + "sha256:dd25dc975bda87983c0c4dbc65b0cf4724a0aed6d299485ce42051cbb20b7b0c", + "sha256:dd38149723aa982d43e09a05a20bd213097faf80245ea9975f84caf3bdb8d3c6", + "sha256:ddf0c922b832e59bb503ef5962e3aba55e63114d6deb46021c319b626cb4fad1", + "sha256:e4e2b96fcc14be4477ee1d4d60c86efe70cd3326724de427c6915113e8f4cda3", + "sha256:e8084f19e7e9cbb91ae8f8b9cd1a2a237cabeae7287e8996573527dec9d34089", + "sha256:f09f8c90494870b60df554196c3ff8531dfcf74ab1a69e7946079e8e73ab0881" + ], + "version": "==1.4.3" + }, "zipp": { "hashes": [ "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", diff --git a/Procfile b/Procfile index 2503d99..1562dbb 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -worker: celery -A gavel:celery worker +worker: PYTHONUNBUFFERED=true celery -A gavel:celery worker -B -E --loglevel=info web: python initialize.py && gunicorn gavel:app diff --git a/app.json b/app.json index f22f885..7381b9f 100644 --- a/app.json +++ b/app.json @@ -15,21 +15,71 @@ }, "description": "An awesome judging system for hackathons", "env": { - "ADMIN_PASSWORD": "change-this-before-deploying", - "SECRET_KEY": "randomly-generate-this-before-deploying", - "DISABLE_EMAIL": "true", - "BASE_URL": "https://example.com/", - "EMAIL_FROM": "_unused_", - "EMAIL_USER": "_unused_", - "EMAIL_PASSWORD": "_unused_", - "IGNORE_CONFIG_FILE": "true", - "EMAIL_HOST": "smtp.gmail.com", - "EMAIL_PORT": "587" + "ADMIN_PASSWORD": { + "description": "Password for the administrator account.", + "value": "change-this-before-deploying" + }, + "SECRET_KEY": { + "description": "Secret key used to hash requests and keys.", + "generator": "secret" + }, + "DISABLE_EMAIL": { + "description": "Email sending is disabled if set to true.", + "value": "false" + }, + "BASE_URL": { + "description": "The base URL of the app.", + "value": "https://.herokuapp.com" + }, + "EMAIL_FROM": { + "description": "Who the emails are sent from. Use the format: Sender Name ", + "value": "_unused_" + }, + "EMAIL_PROVIDER": { + "description": "What service emails are sent from. You have a choice between smtp, mailgun, and sendgrid. All services require EMAIL_FROM. SMTP requires EMAIL_USER and EMAIL_PASSWORD, alongside EMAIL_HOST and EMAIL_PORT. Mailgun requires MAILGUN_DOMAIN, and MAILGUN_API_KEY. Sendgrid requires SENDGRID_API_KEY. *Make sure that the field below is either smtp, mailgun, or sendgrid.*", + "value": "smtp" + }, + "EMAIL_USER": { + "description": "Must be populated with an SMTP username if EMAIL_PROVIDER is set to smtp.", + "value": "_unused_" + }, + "EMAIL_PASSWORD": { + "description": "Must be populated with an SMTP password if EMAIL_PROVIDER is set to smtp.", + "value": "_unused_" + }, + "EMAIL_HOST": { + "description": "Must be populated with an SMTP host if EMAIL_PROVIDER is set to smtp. Defaults to gmail", + "value": "smtp.gmail.com" + }, + "EMAIL_PORT": { + "description": "Must be populated with an SMTP port if EMAIL_PROVIDER is set to smtp.", + "value": "587" + }, + "SENDGRID_API_KEY": { + "description": "Must be populated with a Sendgrid API key if EMAIL_PROVIDER is set to sendgrid.", + "value": "_unused_" + }, + "MAILGUN_DOMAIN": { + "description": "Must be populated with a Mailgun domain if EMAIL_PROVIDER is set to mailgun.", + "value": "_unused_" + }, + "MAILGUN_API_KEY": { + "description": "Must be populated with a Mailgun API key if EMAIL_PROVIDER is set to mailgun", + "value": "_unused_" + }, + "IGNORE_CONFIG_FILE": { + "description": "MUST be set to true in order for these environment variables to work.", + "value": "true" + } }, + "website": "https://gavel.weareasterisk.com/", + "repository": "https://github.com/weareasterisk/gavel", + "logo": "https://cdn.weareasterisk.com/product-assets/gavel/icon.png", + "success_url": "/admin", "keywords": [ "gavel", "python", "flask" ], "name": "Gavel Judging System" -} +} \ No newline at end of file diff --git a/config.template.yaml b/config.template.yaml index 3825dc9..3a435e2 100644 --- a/config.template.yaml +++ b/config.template.yaml @@ -166,3 +166,29 @@ email_body: null # event to this list (https://github.com/anishathalye/gavel/wiki/Users), at the # very least. send_stats: null + + +# can also be specified as the 'EMAIL_PROVIDER' environment variable +# defaults to 'smtp' +# +# This setting determines the method used to send emails to judges. User currently +# has the choice between smtp, sendgrid, and mailgun +email_provider: 'smtp' + +# can also be specified as the 'SENDGRID_API_KEY' environment variable +# defaults to null +# +# This setting must be populated if the email_provider is sendgrid +sendgrid_api_key: null + +# can also be specified as the 'MAILGUN_DOMAIN' environment variable +# defaults to null +# +# This setting must be populated if the email_provider is mailgun +mailgun_domain: null + +# can also be specified as the 'MAILGUN_API_KEY' environment variable +# defaults to null +# +# This setting must be populated if the email_provider is mailgun +mailgun_api_key: null \ No newline at end of file diff --git a/gavel/controllers/admin.py b/gavel/controllers/admin.py index 0fc5b7a..4eb2bae 100644 --- a/gavel/controllers/admin.py +++ b/gavel/controllers/admin.py @@ -568,4 +568,4 @@ async def email_invite_links(annotators): body = '\n\n'.join(utils.get_paragraphs(raw_body)) emails.append((annotator.email, settings.EMAIL_SUBJECT, body)) - utils.send_emails.delay(emails) + utils.send_emails.apply_async(args=[emails]) diff --git a/gavel/controllers/judge.py b/gavel/controllers/judge.py index cb091db..029e4b5 100644 --- a/gavel/controllers/judge.py +++ b/gavel/controllers/judge.py @@ -79,7 +79,7 @@ def index(): @app.route('/vote', methods=['POST']) @requires_open(redirect_to='index') @requires_active_annotator(redirect_to='index') -def vote(): +def vote(retries=0): annotator = get_current_annotator() if annotator.prev.id == int(request.form['prev_id']) and annotator.next.id == int(request.form['next_id']): if request.form['action'] in ['Skip', 'SkipAbsent', 'SkipBusy']: @@ -106,7 +106,15 @@ def vote(): if annotator.stop_next: annotator.active = False annotator.update_next(choose_next(annotator)) - db.session.commit() + try: + db.session.commit() + except Exception as e: + if retries < 3: + db.session.rollback() + # Try again + vote(retries+1) + else: + raise Exception("Judging commit error: " + e) return redirect(url_for('index')) @app.route('/report', methods=['POST']) diff --git a/gavel/settings.py b/gavel/settings.py index f44be17..efb2ae0 100644 --- a/gavel/settings.py +++ b/gavel/settings.py @@ -84,3 +84,7 @@ def _list(item): EMAIL_SUBJECT = c.get('email_subject', default=constants.DEFAULT_EMAIL_SUBJECT) EMAIL_BODY = c.get('email_body', default=constants.DEFAULT_EMAIL_BODY) SEND_STATS = _bool(c.get('send_stats', 'SEND_STATS', default=True)) +EMAIL_PROVIDER = c.get('email_provider', 'EMAIL_PROVIDER', default="smtp") +SENDGRID_API_KEY = c.get('sendgrid_api_key','SENDGRID_API_KEY', default="") +MAILGUN_DOMAIN = c.get('mailgun_domain', 'MAILGUN_DOMAIN', default="") +MAILGUN_API_KEY = c.get('mailgun_api_key', 'MAILGUN_API_KEY', default="") \ No newline at end of file diff --git a/gavel/utils.py b/gavel/utils.py index a7d4185..46b3dbe 100644 --- a/gavel/utils.py +++ b/gavel/utils.py @@ -15,6 +15,26 @@ import email import email.mime.multipart import email.mime.text +import json +import types + +import asyncio + +loop = asyncio.get_event_loop() + +def async_action(f): + @wraps(f) + def wrapped(*args, **kwargs): + return loop.run_until_complete(f(*args, **kwargs)) + return wrapped + +def async_future(f): + @wraps(f) + def wrapped(*args, **kwargs): + return asyncio.Future(f(*args, **kwargs)) + return wrapped + +sendgrid_url = "https://api.sendgrid.com/v3/mail/send" def gen_secret(length): return base64.b32encode(os.urandom(length))[:length].decode('utf8').lower() @@ -51,8 +71,35 @@ def get_paragraphs(message): paragraphs = [i.replace('\n', ' ') for i in paragraphs if i] return paragraphs -@celery.task +@celery.task(name='utils.send_emails') def send_emails(emails): + if settings.EMAIL_PROVIDER not in ["smtp", "sendgrid", "mailgun"]: + raise Exception("[EMAIL ERROR]: Invalid email provider. Please select one of: smtp, sendgrid, mailgun") + if settings.EMAIL_PROVIDER == "smtp": + send_smtp_emails.apply_async(args=[emails]) + else: + exceptions = [] + for e in emails: + to_address, subject, body = e + response = {} + to_adddress = to_address[0:] + try: + if settings.EMAIL_PROVIDER == "sendgrid": + response = loop.run_until_complete(sendgrid_send_email(to_address, subject, body)) + elif settings.EMAIL_PROVIDER == "mailgun": + response = loop.run_until_complete(mailgun_send_email(to_adddress, subject, body)) + if not (response.status_code == requests.codes.ok or response.status_code == requests.codes.accepted): + # all_errors = [error_obj["message"] for error_obj in response.json()["errors"]] + error_msg = to_address + exceptions.append(error_msg) + + except Exception as e: + exceptions.append(e) + if exceptions: + raise Exception("Error sending some emails. Please double-check your email authentication settings.", exceptions) + +@celery.task(name='utils.send_smtp_emails') +def send_smtp_emails(emails): ''' Send a batch of emails. @@ -93,6 +140,37 @@ def send_emails(emails): if exceptions: raise Exception('Error sending some emails: %s' % exceptions) +async def sendgrid_send_email(to_address, subject, body): + new_dict = {} + new_dict["personalizations"] = [] + new_dict["personalizations"].append({"to": [{"email": to_address}], "subject": subject}) + new_dict["from"] = {} + new_dict["from"]["email"] = settings.EMAIL_FROM + new_dict["subject"] = subject + new_dict["content"] = [] + new_dict["content"].append({"type": "text/plain", "value": body}) + headers = { + 'authorization': "Bearer " + settings.SENDGRID_API_KEY, + 'content-type': "application/json", + } + response = requests.post( + sendgrid_url, + data=json.dumps(new_dict), + headers=headers) + return response + +async def mailgun_send_email(to_address, subject, body): + api_url = "https://api.mailgun.net/v3/" + settings.MAILGUN_DOMAIN + "/messages" + mailgun_key = settings.MAILGUN_API_KEY + response = requests.post( + api_url, + auth=("api", mailgun_key), + data={"from": settings.EMAIL_FROM, + "to": [to_address], + "subject": subject, + "text": body}) + return response + def render_markdown(content): return Markup(markdown.markdown(content)) @@ -127,4 +205,4 @@ def cast_row(row): row[i] = str(int(item)) else: row[i] = str(item) - return row + return row \ No newline at end of file