diff --git a/Pipfile b/Pipfile index aac2cf4..c826f72 100644 --- a/Pipfile +++ b/Pipfile @@ -20,18 +20,21 @@ pytest-flask = "==0.12.0" pytest-runner = ">=3.0.0,<5" selenium = ">=3.4.3" Sphinx = ">=1.5.1" +python-dotenv = "*" +pytest-env = "*" [packages] appnope = {version = "*",sys_platform = "== 'darwin'"} arrow = ">=0.12.1" Babel = ">=2.4.0" +datacite = {git = "https://github.com/caltechlibrary/datacite.git",ref = "doi-mint-enhance"} Flask-BabelEx = ">=0.9.3" invenio = {version = "~=3.0.0",extras = ["base", "metadata", "postgresql", "auth", "elasticsearch6"]} invenio-accounts = ">=1.0.2" invenio-deposit = {editable = true,git = "https://github.com/galterlibrary/invenio-deposit.git",ref = "113_fix_dependencies"} invenio-records-files = {editable = true,git = "https://github.com/galterlibrary/invenio-records-files.git",ref = "make_non_pre"} invenio-files-rest = {editable = true,git = "https://github.com/galterlibrary/invenio-files-rest.git",ref = "make_non_pre"} -invenio-records-rest = ">=1.1.0" +invenio-records-rest = {version = ">=1.1.0",extras = ["datacite"]} IPython = "<7.0.0" marshmallow = ">=2.15.5,<2.30.0" SQLAlchemy-Continuum = "==1.3.4" diff --git a/Pipfile.lock b/Pipfile.lock index 48ac04e..5788334 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f96f123899d69047121e388b7a4208f81cc59fb2c281421643f218fad32b638b" + "sha256": "c2cbb5571d50edefba4847d16e544a6110bb08f02cbe0ad0fa568e1aea078c36" }, "pipfile-spec": 6, "requires": { @@ -18,16 +18,16 @@ "default": { "alembic": { "hashes": [ - "sha256:16505782b229007ae905ef9e0ae6e880fddafa406f086ac7d442c1aaf712f8c2" + "sha256:505d41e01dc0c9e6d85c116d0d35dbb0a833dcb490bf483b75abeb06648864e8" ], - "version": "==1.0.7" + "version": "==1.0.8" }, "amqp": { "hashes": [ - "sha256:16056c952e8029ce8db097edf0d7c2fe2ba9de15d30ba08aee2c5221273d8e23", - "sha256:6816eed27521293ee03aa9ace300a07215b11fee4e845588a9b863a7ba30addb" + "sha256:043beb485774ca69718a35602089e524f87168268f0d1ae115f28b88d27f92d7", + "sha256:35a3b5006ca00b21aaeec8ceea07130f07b902dd61bfe42815039835f962f5f1" ], - "version": "==2.4.1" + "version": "==2.4.2" }, "angular-gettext-babel": { "hashes": [ @@ -58,6 +58,13 @@ ], "version": "==0.24.0" }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, "babel": { "hashes": [ "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", @@ -114,36 +121,36 @@ }, "cffi": { "hashes": [ - "sha256:0b5f895714a7a9905148fc51978c62e8a6cbcace30904d39dcd0d9e2265bb2f6", - "sha256:27cdc7ba35ee6aa443271d11583b50815c4bb52be89a909d0028e86c21961709", - "sha256:2d4a38049ea93d5ce3c7659210393524c1efc3efafa151bd85d196fa98fce50a", - "sha256:3262573d0d60fc6b9d0e0e6e666db0e5045cbe8a531779aa0deb3b425ec5a282", - "sha256:358e96cfffc185ab8f6e7e425c7bb028931ed08d65402fbcf3f4e1bff6e66556", - "sha256:37c7db824b5687fbd7ea5519acfd054c905951acc53503547c86be3db0580134", - "sha256:39b9554dfe60f878e0c6ff8a460708db6e1b1c9cc6da2c74df2955adf83e355d", - "sha256:42b96a77acf8b2d06821600fa87c208046decc13bd22a4a0e65c5c973443e0da", - "sha256:5b37dde5035d3c219324cac0e69d96495970977f310b306fa2df5910e1f329a1", - "sha256:5d35819f5566d0dd254f273d60cf4a2dcdd3ae3003dfd412d40b3fe8ffd87509", - "sha256:5df73aa465e53549bd03c819c1bc69fb85529a5e1a693b7b6cb64408dd3970d1", - "sha256:7075b361f7a4d0d4165439992d0b8a3cdfad1f302bf246ed9308a2e33b046bd3", - "sha256:7678b5a667b0381c173abe530d7bdb0e6e3b98e062490618f04b80ca62686d96", - "sha256:7dfd996192ff8a535458c17f22ff5eb78b83504c34d10eefac0c77b1322609e2", - "sha256:8a3be5d31d02c60f84c4fd4c98c5e3a97b49f32e16861367f67c49425f955b28", - "sha256:9812e53369c469506b123aee9dcb56d50c82fad60c5df87feb5ff59af5b5f55c", - "sha256:9b6f7ba4e78c52c1a291d0c0c0bd745d19adde1a9e1c03cb899f0c6efd6f8033", - "sha256:a85bc1d7c3bba89b3d8c892bc0458de504f8b3bcca18892e6ed15b5f7a52ad9d", - "sha256:aa6b9c843ad645ebb12616de848cc4e25a40f633ccc293c3c9fe34107c02c2ea", - "sha256:bae1aa56ee00746798beafe486daa7cfb586cd395c6ce822ba3068e48d761bc0", - "sha256:bae96e26510e4825d5910a196bf6b5a11a18b87d9278db6d08413be8ea799469", - "sha256:bd78df3b594013b227bf31d0301566dc50ba6f40df38a70ded731d5a8f2cb071", - "sha256:c2711197154f46d06f73542c539a0ff5411f1951fab391e0a4ac8359badef719", - "sha256:d998c20e3deed234fca993fd6c8314cb7cbfda05fd170f1bd75bb5d7421c3c5a", - "sha256:df4f840d77d9e37136f8e6b432fecc9d6b8730f18f896e90628712c793466ce6", - "sha256:f5653c2581acb038319e6705d4e3593677676df14b112f13e0b5b44b6a18df1a", - "sha256:f7c7aa485a2e2250d455148470ffd0195eecc3d845122635202d7467d6f7b4cf", - "sha256:f9e2c66a6493147de835f207f198540a56b26745ce4f272fbc7c2f2cfebeb729" - ], - "version": "==1.12.1" + "sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f", + "sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11", + "sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d", + "sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891", + "sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf", + "sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c", + "sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed", + "sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b", + "sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a", + "sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585", + "sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea", + "sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f", + "sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33", + "sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145", + "sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a", + "sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3", + "sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f", + "sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd", + "sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804", + "sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d", + "sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92", + "sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f", + "sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84", + "sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb", + "sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7", + "sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7", + "sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35", + "sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889" + ], + "version": "==1.12.2" }, "chardet": { "hashes": [ @@ -168,27 +175,31 @@ }, "cryptography": { "hashes": [ - "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af", - "sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e", - "sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2", - "sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7", - "sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079", - "sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063", - "sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401", - "sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695", - "sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85", - "sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3", - "sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad", - "sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca", - "sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd", - "sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f", - "sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159", - "sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0", - "sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e", - "sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3", - "sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00" - ], - "version": "==2.5" + "sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1", + "sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705", + "sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6", + "sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1", + "sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8", + "sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151", + "sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d", + "sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659", + "sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537", + "sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e", + "sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb", + "sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c", + "sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9", + "sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5", + "sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad", + "sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a", + "sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460", + "sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd", + "sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6" + ], + "version": "==2.6.1" + }, + "datacite": { + "git": "https://github.com/caltechlibrary/datacite.git", + "ref": "6ab9ba19911108ec80437c50faafcb3c2dc67bab" }, "decorator": { "hashes": [ @@ -199,10 +210,10 @@ }, "dictdiffer": { "hashes": [ - "sha256:6de9370f3c0c7fb5cc8bdc9e10dbca6ff05c39d8e2e58a67eb98d32677a224ca", - "sha256:e4f94167d037f70c11c6a8e7e289d81c8c7117bc02132cd82a0ab8fcba43cc08" + "sha256:b6eed4cf74ed31ae9646257a9f802bb09e545ca817d5c0119d747b6a05b6a22d", + "sha256:cc398dc26600cdb9519b2c768157333a0967b24d64c3913077dd0794274395da" ], - "version": "==0.7.1" + "version": "==0.7.2" }, "dojson": { "hashes": [ @@ -266,10 +277,10 @@ }, "flask-caching": { "hashes": [ - "sha256:44fe827c6cc519d48fb0945fa05ae3d128af9a98f2a6e71d4702fd512534f227", - "sha256:e34f24631ba240e09fe6241e1bf652863e0cff06a1a94598e23be526bc2e4985" + "sha256:8c592ca7607545dc349babc5f8bdd7ca0c3aba467ce44a35f35b80aa23bf3b5f", + "sha256:be7e4896ec212365a8ed53cb8658a2e2f2b4f4d143101360214fcd96fa632f18" ], - "version": "==1.4.0" + "version": "==1.5.0" }, "flask-celeryext": { "hashes": [ @@ -505,10 +516,10 @@ "versioning" ], "hashes": [ - "sha256:2c012f3515ea0beb0167493775b3f6c54ea5d2fe3c1ae54d6ff1fc99df534fcc", - "sha256:cc3b913bd4bd7a8cf00e0e0ea3afbf2917b6ad8016df83806a335f8fd497a01e" + "sha256:78f2f9ecc690e5c40ad797ee40b45a7e762da9599acf89782354e887bd632d70", + "sha256:9a36bdfc060ca9e7a074276b2c17651da2874a6f1f45081ce9408f632459b7ae" ], - "version": "==1.0.2" + "version": "==1.0.3" }, "invenio-deposit": { "editable": true, @@ -596,10 +607,10 @@ }, "invenio-records": { "hashes": [ - "sha256:56487d0b7b800877e45f385dbba831fc988ca4df935ced281543dcaa1b6dd351", - "sha256:d25c865c7ba3be059a5699e5e135a46e6fbb06370b58754ef22c7fdf337a12f1" + "sha256:7228135f6b399e4d9070ec4feded814af58982f6cdb98ea236f9a3ccbb68e650", + "sha256:a1226e813f4d2592ef8165018b03b1995e12d38d6f3d3086782bc26b98793756" ], - "version": "==1.0.1" + "version": "==1.1.0" }, "invenio-records-files": { "editable": true, @@ -608,10 +619,10 @@ }, "invenio-records-rest": { "hashes": [ - "sha256:6b81d790c2ff4cc0c9f744375117c0b06edea10042600a6bb67dbdff4da7746d", - "sha256:9b067710b0e98ccb763576155423c423be2f8245a5a137dd2566f3e386bd23c2" + "sha256:5bec87e62593f914d21ad0ecbcc70750eb60b51259cb181f671b5a21e798eccd", + "sha256:892dd6a11aae194a4e494f519ebbb2bf9c7bda54c399d43cc9423eb44e2b0354" ], - "version": "==1.3.0" + "version": "==1.4.0" }, "invenio-records-ui": { "hashes": [ @@ -632,10 +643,10 @@ }, "invenio-search": { "hashes": [ - "sha256:b41c7db7cb9adba6d9f1b8ce332bec0b6b647b6ce5df4679620c59e74a9c5970", - "sha256:f24f0c1fe083e78574908590a463da80c1def9dd1753d8ba6ab8b123cad75131" + "sha256:13a3370fb508979f8d0fa2ffe834ae1abc39dc7fcf19e1aec6b2c2b9dedab8f8", + "sha256:2fa00558e6056bcf75065830d021880bda72f353d9fdca892a219c28f23a4eba" ], - "version": "==1.0.2" + "version": "==1.1.0" }, "invenio-search-ui": { "hashes": [ @@ -689,10 +700,10 @@ }, "jedi": { "hashes": [ - "sha256:571702b5bd167911fe9036e5039ba67f820d6502832285cde8c881ab2b2149fd", - "sha256:c8481b5e59d34a5c7c42e98f6625e633f6ef59353abea6437472c7ec2093f191" + "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b", + "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c" ], - "version": "==0.13.2" + "version": "==0.13.3" }, "jinja2": { "hashes": [ @@ -745,17 +756,17 @@ }, "jsonschema": { "hashes": [ - "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", - "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02" + "sha256:0c0a81564f181de3212efa2d17de1910f8732fa1b71c42266d983cd74304e20d", + "sha256:a5f6559964a3851f59040d3b961de5e68e70971afb88ba519d27e6a039efff1a" ], - "version": "==2.6.0" + "version": "==3.0.1" }, "kombu": { "hashes": [ - "sha256:529df9e0ecc0bad9fc2b376c3ce4796c41b482cf697b78b71aea6ebe7ca353c8", - "sha256:7a2cbed551103db9a4e2efafe9b63222e012a61a18a881160ad797b9d4e1d0a1" + "sha256:750579c10e4ce82636cebdaad3cfc96970b4c7c483a040bb94fb52040bc53a48", + "sha256:b1a68fe8a1144eab0359be6bbae5e03b6684c98b9b53de7a17ce6d64ef24e4ea" ], - "version": "==4.3.0" + "version": "==4.4.0" }, "ldap3": { "hashes": [ @@ -773,34 +784,34 @@ }, "lxml": { "hashes": [ - "sha256:0537eee4902e8bf4f41bfee8133f7edf96533dd175930a12086d6a40d62376b2", - "sha256:0562ec748abd230ab87d73384e08fa784f9b9cee89e28696087d2d22c052cc27", - "sha256:09e91831e749fbf0f24608694e4573be0ef51430229450c39c83176cc2e2d353", - "sha256:1ae4c0722fc70c0d4fba43ae33c2885f705e96dce1db41f75ae14a2d2749b428", - "sha256:1c630c083d782cbaf1f7f37f6cac87bda9cff643cf2803a5f180f30d97955cef", - "sha256:2fe74e3836bd8c0fa7467ffae05545233c7f37de1eb765cacfda15ad20c6574a", - "sha256:37af783c2667ead34a811037bda56a0b142ac8438f7ed29ae93f82ddb812fbd6", - "sha256:3f2d9eafbb0b24a33f56acd16f39fc935756524dcb3172892721c54713964c70", - "sha256:47d8365a8ef14097aa4c65730689be51851b4ade677285a3b2daa03b37893e26", - "sha256:510e904079bc56ea784677348e151e1156040dbfb736f1d8ea4b9e6d0ab2d9f4", - "sha256:58d0851da422bba31c7f652a7e9335313cf94a641aa6d73b8f3c67602f75b593", - "sha256:7940d5c2185ffb989203dacbb28e6ae88b4f1bb25d04e17f94b0edd82232bcbd", - "sha256:7cf39bb3a905579836f7a8f3a45320d9eb22f16ab0c1e112efb940ced4d057a5", - "sha256:9563a23c1456c0ab550c087833bc13fcc61013a66c6420921d5b70550ea312bf", - "sha256:95b392952935947e0786a90b75cc33388549dcb19af716b525dae65b186138fc", - "sha256:983129f3fd3cef5c3cf067adcca56e30a169656c00fcc6c648629dbb850b27fa", - "sha256:a0b75b1f1854771844c647c464533def3e0a899dd094a85d1d4ed72ecaaee93d", - "sha256:b5db89cc0ef624f3a81214b7961a99f443b8c91e88188376b6b322fd10d5b118", - "sha256:c0a7751ba1a4bfbe7831920d98cee3ce748007eab8dfda74593d44079568219a", - "sha256:c0c5a7d4aafcc30c9b6d8613a362567e32e5f5b708dc41bc3a81dac56f8af8bb", - "sha256:d4d63d85eacc6cb37b459b16061e1f100d154bee89dc8d8f9a6128a5a538e92e", - "sha256:da5e7e941d6e71c9c9a717c93725cda0708c2474f532e3680ac5e39ec57d224d", - "sha256:dccad2b3c583f036f43f80ac99ee212c2fa9a45151358d55f13004d095e683b2", - "sha256:df46307d39f2aeaafa1d25309b8a8d11738b73e9861f72d4d0a092528f498baa", - "sha256:e70b5e1cb48828ddd2818f99b1662cb9226dc6f57d07fc75485405c77da17436", - "sha256:ea825562b8cd057cbc9810d496b8b5dec37a1e2fc7b27bc7c1e72ce94462a09a" - ], - "version": "==4.3.1" + "sha256:0358b9e9642bc7d39aac5cffe9884a99a5ca68e5e2c1b89e570ed60da9139908", + "sha256:091a359c4dafebbecd3959d9013f1b896b5371859165e4e50b01607a98d9e3e2", + "sha256:1998e4e60603c64bcc35af61b4331ab3af087457900d3980e18d190e17c3a697", + "sha256:2000b4088dee9a41f459fddaf6609bba48a435ce6374bb254c5ccdaa8928c5ba", + "sha256:2afb0064780d8aaf165875be5898c1866766e56175714fa5f9d055433e92d41d", + "sha256:2d8f1d9334a4e3ff176d096c14ded3100547d73440683567d85b8842a53180bb", + "sha256:2e38db22f6a3199fd63675e1b4bd795d676d906869047398f29f38ca55cb453a", + "sha256:3181f84649c1a1ca62b19ddf28436b1b2cb05ae6c7d2628f33872e713994c364", + "sha256:37462170dfd88af8431d04de6b236e6e9c06cda71e2ca26d88ef2332fd2a5237", + "sha256:3a9d8521c89bf6f2a929c3d12ad3ad7392c774c327ea809fd08a13be6b3bc05f", + "sha256:3d0bbd2e1a28b4429f24fd63a122a450ce9edb7a8063d070790092d7343a1aa4", + "sha256:483d60585ce3ee71929cea70949059f83850fa5e12deb9c094ed1c8c2ec73cbd", + "sha256:4888be27d5cba55ce94209baef5bcd7bbd7314a3d17021a5fc10000b3a5f737d", + "sha256:64b0d62e4209170a2a0c404c446ab83b941a0003e96604d2e4f4cb735f8a2254", + "sha256:68010900898fdf139ac08549c4dba8206c584070a960ffc530aebf0c6f2794ef", + "sha256:872ecb066de602a0099db98bd9e57f4cfc1d62f6093d94460c787737aa08f39e", + "sha256:88a32b03f2e4cd0e63f154cac76724709f40b3fc2f30139eb5d6f900521b44ed", + "sha256:b1dc7683da4e67ab2bebf266afa68098d681ae02ce570f0d1117312273d2b2ac", + "sha256:b29e27ce9371810250cb1528a771d047a9c7b0f79630dc7dc5815ff828f4273b", + "sha256:ce197559596370d985f1ce6b7051b52126849d8159040293bf8b98cb2b3e1f78", + "sha256:d45cf6daaf22584eff2175f48f82c4aa24d8e72a44913c5aff801819bb73d11f", + "sha256:e2ff9496322b2ce947ba4a7a5eb048158de9d6f3fe9efce29f1e8dd6878561e6", + "sha256:f7b979518ec1f294a41a707c007d54d0f3b3e1fd15d5b26b7e99b62b10d9a72e", + "sha256:f9c7268e9d16e34e50f8246c4f24cf7353764affd2bc971f0379514c246e3f6b", + "sha256:f9c839806089d79de588ee1dde2dae05dc1156d3355dfeb2b51fde84d9c960ad", + "sha256:ff962953e2389226adc4d355e34a98b0b800984399153c6678f2367b11b4d4b8" + ], + "version": "==4.3.2" }, "mako": { "hashes": [ @@ -810,36 +821,36 @@ }, "markupsafe": { "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" ], - "version": "==1.1.0" + "version": "==1.1.1" }, "marshmallow": { "hashes": [ @@ -910,10 +921,10 @@ }, "pluggy": { "hashes": [ - "sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616", - "sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a" + "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f", + "sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746" ], - "version": "==0.8.1" + "version": "==0.9.0" }, "poyo": { "hashes": [ @@ -1006,6 +1017,12 @@ ], "version": "==0.1.1" }, + "pyrsistent": { + "hashes": [ + "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2" + ], + "version": "==0.14.11" + }, "python-dateutil": { "hashes": [ "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", @@ -1101,9 +1118,9 @@ }, "sqlalchemy": { "hashes": [ - "sha256:8027fa183f5be466030617a497b2d64e0e16c8d615e5a34bdf9fab6f66bf4723" + "sha256:11ead7047ff3f394ed0d4b62aded6c5d970a9b718e1dc6add9f5e79442cc5b14" ], - "version": "==1.2.18" + "version": "==1.3.0" }, "sqlalchemy-continuum": { "hashes": [ @@ -1240,10 +1257,10 @@ }, "ansible": { "hashes": [ - "sha256:040cc936f959b947800ffaa5f940d2508aaa41f899efe56b47a7442c89689150" + "sha256:3d67db258b492f3c8828c68dcb92438e67cb5b20c12924089eafdc0ada3edd87" ], "index": "pypi", - "version": "==2.7.7" + "version": "==2.7.8" }, "apipkg": { "hashes": [ @@ -1268,10 +1285,10 @@ }, "attrs": { "hashes": [ - "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", - "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" ], - "version": "==18.2.0" + "version": "==19.1.0" }, "babel": { "hashes": [ @@ -1319,36 +1336,36 @@ }, "cffi": { "hashes": [ - "sha256:0b5f895714a7a9905148fc51978c62e8a6cbcace30904d39dcd0d9e2265bb2f6", - "sha256:27cdc7ba35ee6aa443271d11583b50815c4bb52be89a909d0028e86c21961709", - "sha256:2d4a38049ea93d5ce3c7659210393524c1efc3efafa151bd85d196fa98fce50a", - "sha256:3262573d0d60fc6b9d0e0e6e666db0e5045cbe8a531779aa0deb3b425ec5a282", - "sha256:358e96cfffc185ab8f6e7e425c7bb028931ed08d65402fbcf3f4e1bff6e66556", - "sha256:37c7db824b5687fbd7ea5519acfd054c905951acc53503547c86be3db0580134", - "sha256:39b9554dfe60f878e0c6ff8a460708db6e1b1c9cc6da2c74df2955adf83e355d", - "sha256:42b96a77acf8b2d06821600fa87c208046decc13bd22a4a0e65c5c973443e0da", - "sha256:5b37dde5035d3c219324cac0e69d96495970977f310b306fa2df5910e1f329a1", - "sha256:5d35819f5566d0dd254f273d60cf4a2dcdd3ae3003dfd412d40b3fe8ffd87509", - "sha256:5df73aa465e53549bd03c819c1bc69fb85529a5e1a693b7b6cb64408dd3970d1", - "sha256:7075b361f7a4d0d4165439992d0b8a3cdfad1f302bf246ed9308a2e33b046bd3", - "sha256:7678b5a667b0381c173abe530d7bdb0e6e3b98e062490618f04b80ca62686d96", - "sha256:7dfd996192ff8a535458c17f22ff5eb78b83504c34d10eefac0c77b1322609e2", - "sha256:8a3be5d31d02c60f84c4fd4c98c5e3a97b49f32e16861367f67c49425f955b28", - "sha256:9812e53369c469506b123aee9dcb56d50c82fad60c5df87feb5ff59af5b5f55c", - "sha256:9b6f7ba4e78c52c1a291d0c0c0bd745d19adde1a9e1c03cb899f0c6efd6f8033", - "sha256:a85bc1d7c3bba89b3d8c892bc0458de504f8b3bcca18892e6ed15b5f7a52ad9d", - "sha256:aa6b9c843ad645ebb12616de848cc4e25a40f633ccc293c3c9fe34107c02c2ea", - "sha256:bae1aa56ee00746798beafe486daa7cfb586cd395c6ce822ba3068e48d761bc0", - "sha256:bae96e26510e4825d5910a196bf6b5a11a18b87d9278db6d08413be8ea799469", - "sha256:bd78df3b594013b227bf31d0301566dc50ba6f40df38a70ded731d5a8f2cb071", - "sha256:c2711197154f46d06f73542c539a0ff5411f1951fab391e0a4ac8359badef719", - "sha256:d998c20e3deed234fca993fd6c8314cb7cbfda05fd170f1bd75bb5d7421c3c5a", - "sha256:df4f840d77d9e37136f8e6b432fecc9d6b8730f18f896e90628712c793466ce6", - "sha256:f5653c2581acb038319e6705d4e3593677676df14b112f13e0b5b44b6a18df1a", - "sha256:f7c7aa485a2e2250d455148470ffd0195eecc3d845122635202d7467d6f7b4cf", - "sha256:f9e2c66a6493147de835f207f198540a56b26745ce4f272fbc7c2f2cfebeb729" - ], - "version": "==1.12.1" + "sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f", + "sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11", + "sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d", + "sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891", + "sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf", + "sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c", + "sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed", + "sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b", + "sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a", + "sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585", + "sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea", + "sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f", + "sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33", + "sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145", + "sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a", + "sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3", + "sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f", + "sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd", + "sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804", + "sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d", + "sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92", + "sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f", + "sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84", + "sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb", + "sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7", + "sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7", + "sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35", + "sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889" + ], + "version": "==1.12.2" }, "chardet": { "hashes": [ @@ -1411,27 +1428,27 @@ }, "cryptography": { "hashes": [ - "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af", - "sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e", - "sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2", - "sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7", - "sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079", - "sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063", - "sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401", - "sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695", - "sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85", - "sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3", - "sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad", - "sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca", - "sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd", - "sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f", - "sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159", - "sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0", - "sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e", - "sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3", - "sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00" - ], - "version": "==2.5" + "sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1", + "sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705", + "sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6", + "sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1", + "sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8", + "sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151", + "sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d", + "sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659", + "sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537", + "sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e", + "sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb", + "sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c", + "sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9", + "sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5", + "sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad", + "sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a", + "sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460", + "sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd", + "sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6" + ], + "version": "==2.6.1" }, "cssselect": { "hashes": [ @@ -1487,12 +1504,11 @@ }, "isort": { "hashes": [ - "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", - "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", - "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" + "sha256:144c4295314c0ed34fb034f838b2b7e242c52dd3eafdd6f5d49078692f582c0c", + "sha256:92a7ddacb0e7e10ed2976e6b5d58496dcda27a3f525c187a3a1a0ae5fa79ff1b" ], "index": "pypi", - "version": "==4.3.4" + "version": "==4.3.10" }, "itsdangerous": { "hashes": [ @@ -1510,36 +1526,36 @@ }, "markupsafe": { "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" ], - "version": "==1.1.0" + "version": "==1.1.1" }, "more-itertools": { "hashes": [ @@ -1580,17 +1596,17 @@ }, "pluggy": { "hashes": [ - "sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616", - "sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a" + "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f", + "sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746" ], - "version": "==0.8.1" + "version": "==0.9.0" }, "py": { "hashes": [ - "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", - "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" ], - "version": "==1.7.0" + "version": "==1.8.0" }, "pyasn1": { "hashes": [ @@ -1674,6 +1690,13 @@ "index": "pypi", "version": "==2.6.1" }, + "pytest-env": { + "hashes": [ + "sha256:7e94956aef7f2764f3c147d216ce066bf6c42948bb9e293169b1b1c880a580c2" + ], + "index": "pypi", + "version": "==0.6.2" + }, "pytest-flask": { "hashes": [ "sha256:be3551e5d8cccd2f26e51fa4268398619be18b9e2300fdab7f4b2aeaeee0a588", @@ -1713,6 +1736,14 @@ "index": "pypi", "version": "==4.4" }, + "python-dotenv": { + "hashes": [ + "sha256:a84569d0e00d178bc5b957f7ff208bf49287cbf61857c31c258c4a91f571527b", + "sha256:c9b1ddd3cdbe75c7d462cb84674d87130f4b948f090f02c7d7144779afb99ae0" + ], + "index": "pypi", + "version": "==0.10.1" + }, "pytz": { "hashes": [ "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", diff --git a/README.rst b/README.rst index 7605597..c0e5d48 100644 --- a/README.rst +++ b/README.rst @@ -58,7 +58,11 @@ instructions. You only need to execute them once to setup your environment: Note: You may want to add ``PIPENV_VENV_IN_PROJECT=1`` to your shell (``.bashrc``, ``config.fish``...) for ease of use. -3. Start the containers for the services +3. Ask your colleagues for the current `.env` file and place it in the root + directory of the project. This file contains the sensitive or + "live specific" environment variables you will need. + +4. Start the containers for the services .. code-block:: console @@ -68,7 +72,7 @@ instructions. You only need to execute them once to setup your environment: This will create and run 4 docker containers. These containers will then keep themselves running even across reboots. -4. Execute the Invenio initial bootstrap and setup code +5. Execute the Invenio initial bootstrap and setup code .. code-block:: console diff --git a/cd2h_repo_project/config.py b/cd2h_repo_project/config.py index 3098f55..e7a666c 100644 --- a/cd2h_repo_project/config.py +++ b/cd2h_repo_project/config.py @@ -27,6 +27,17 @@ ) from cd2h_repo_project.modules.records.search import RecordsSearch +# When run in a container (production-like-environments), the container +# infrastructure takes care of loading environment variables, but when +# running outside a container (development-like-environments) we need to do it +# ourselves. Since dotenv is only needed in a development environment, it is +# only installed in that environment. +try: + from dotenv import load_dotenv + load_dotenv(verbose=True) +except ImportError: + pass + def _(x): """Identity function used to trigger string extraction.""" @@ -65,9 +76,9 @@ def _(x): # Theme configuration # =================== #: Site name -THEME_SITENAME = _('Next Generation Research Discovery') +THEME_SITENAME = _('Next Generation Research Repository') #: Frontpage title. -THEME_FRONTPAGE_TITLE = _('Next Generation Research Discovery') +THEME_FRONTPAGE_TITLE = _('Next Generation Research Repository') # THEME_HEADER_LOGIN_TEMPLATE = 'invenio_theme/header_login.html' # Email configuration @@ -123,14 +134,27 @@ def _(x): 'cd2h-repo-project@localhost/cd2h-repo-project' ) -# Datacite and related Invenio-Pidstore integration +# Digital Object Identifier (DOI), Datacite and Invenio-Pidstore integration # ================================================= -PIDSTORE_DATACITE_DOI_PREFIX = '10.5072' # Test prefix, CHANGE ME in PROD -PIDSTORE_DATACITE_TESTMODE = True # Set to False in PROD - +DOI_REGISTER_SIGNALS = False +"""Set this to True to mint DOIs.""" +DOI_PUBLISHER = "YOUR PLATFORM NAME" +"""REQUIRED if DOI_REGISTER_SIGNALS is True. Set this to your repository's + institution or name.""" PIDSTORE_DATACITE_USERNAME = '' +"""REQUIRED if DOI_REGISTER_SIGNALS is True. Set this to your DataCite client + account.""" PIDSTORE_DATACITE_PASSWORD = '' -PIDSTORE_DATACITE_URL = '' +"""REQUIRED if DOI_REGISTER_SIGNALS is True. Set this to your DataCite client + account password.""" +PIDSTORE_DATACITE_DOI_PREFIX = '' +"""REQUIRED if DOI_REGISTER_SIGNALS is True. Change this to your institution's + DOI prefix.""" +PIDSTORE_DATACITE_TESTMODE = True +"""Whether to interact with DataCite in test mode or not. + Set to False in production""" +PIDSTORE_DATACITE_URL = "https://mds.datacite.org" +"""The DataCite minting endpoint.""" # JSONSchemas # =========== @@ -161,6 +185,9 @@ def _(x): #: should be set to the correct host and it is strongly recommended to only #: route correct hosts to the application. APP_ALLOWED_HOSTS = ['localhost', '127.0.0.1'] +#: It should be set to your server name +SERVER_NAME = 'localhost:5000' +PREFERRED_URL_SCHEME = 'https' # OAI-PMH # ======= diff --git a/cd2h_repo_project/modules/doi/__init__.py b/cd2h_repo_project/modules/doi/__init__.py new file mode 100644 index 0000000..9cb435c --- /dev/null +++ b/cd2h_repo_project/modules/doi/__init__.py @@ -0,0 +1 @@ +"""Digital Object Identifier module.""" diff --git a/cd2h_repo_project/modules/doi/ext.py b/cd2h_repo_project/modules/doi/ext.py new file mode 100644 index 0000000..1ae70ab --- /dev/null +++ b/cd2h_repo_project/modules/doi/ext.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2018 NU,FSM,GHSL. +# +# This is free software; you can redistribute it and/or modify it +# under the terms of the MIT License; see LICENSE file for more details. + +"""Flask extension for DOI.""" + + +class DigitalObjectIdentifier(object): + """Digital Object Identifier extension.""" + + def __init__(self, app=None): + """Extension initialization.""" + if app: + self.init_app(app) + + def init_app(self, app): + """Flask application initialization.""" + app.extensions['cd2h-doi'] = self diff --git a/cd2h_repo_project/modules/doi/links.py b/cd2h_repo_project/modules/doi/links.py new file mode 100644 index 0000000..63cb1b0 --- /dev/null +++ b/cd2h_repo_project/modules/doi/links.py @@ -0,0 +1,6 @@ +"""DOI links.""" + + +def doi_url_for(doi_value): + """Return the URL for the DOI.""" + return 'https://doi.org/' + str(doi_value).strip('/') diff --git a/cd2h_repo_project/modules/doi/minters.py b/cd2h_repo_project/modules/doi/minters.py new file mode 100644 index 0000000..cab43af --- /dev/null +++ b/cd2h_repo_project/modules/doi/minters.py @@ -0,0 +1,39 @@ +"""Local DOI minter.""" + +from flask import current_app +from invenio_pidstore.models import PersistentIdentifier, PIDStatus + + +def mint_record_doi(record_uuid, data): + """Mint doi PersistentIdentifier in an initial New state. + + Because DOIs are minted by an external service, we create a PID for + tracking purposes, but do not mark it as Registered NOR do we specify the + final DOI value upfront. A unique and temporary doi value is put in the DB + until the real (final) DOI value is minted and provided to us by the + external service. For that reason, we don't pass that DOI value back in + `data`. + + An asynchronous task will update this PID with results from the external + service. + + A doi PersistentIdentifier can only be minted if the Record has an + associated recid PersistentIdentifier and it has not been doi minted + before. + + :param record_uuid: Record object uuid + :param data: Record object as dict (or dict-like). + :returns: doi PersistentIdentifier + """ + recid_field = current_app.config['PIDSTORE_RECID_FIELD'] + assert recid_field in data and 'doi' not in data + pid = PersistentIdentifier.create( + 'doi', + data['id'], # This is a purposefully unique but temporary value + pid_provider='datacite', + object_type='rec', + object_uuid=record_uuid, + status=PIDStatus.NEW, + ) + data['doi'] = '' + return pid diff --git a/cd2h_repo_project/modules/doi/schemas.py b/cd2h_repo_project/modules/doi/schemas.py new file mode 100644 index 0000000..1d19de5 --- /dev/null +++ b/cd2h_repo_project/modules/doi/schemas.py @@ -0,0 +1,103 @@ +"""JSON Schemas.""" +from datetime import date + +from flask import current_app +from marshmallow import Schema, fields + + +class DataCiteResourceTypeSchemaV4(Schema): + """ResourceType schema.""" + + resourceTypeGeneral = fields.Method('get_general_resource_type') + resourceType = fields.Method('get_specific_resource_type') + + def get_general_resource_type(self, resource_type): + """Extract general_resource_type. + + TODO: Settle on general resource types and use those. + We just provide a default for now. + """ + return resource_type.get('general', 'Dataset') + + def get_specific_resource_type(self, resource_type): + """Extract specific resource type. + + TODO: Settle on specific resource types (if any) and use those. + We just provide a default for now. + """ + return resource_type.get('specific', 'Dataset') + + +class DataCiteTitleSchemaV4(Schema): + """Title schema.""" + + title = fields.Str() + + +class DataCiteCreatorSchemaV4(Schema): + """Creator schema.""" + + # Note: Marshmallow doesn't try to automatically extract a field + # corresponding to a fields.Method. + creatorName = fields.Method('get_creator_name') + # TODO optional: + # givenName + # familyName + + def get_creator_name(self, author): + """Extract creator name.""" + name_parts = author.strip().split() + if len(name_parts) >= 2: + return "{last_name}, {first_name}".format( + last_name=name_parts[-1], first_name=name_parts[0]) + else: + return '' + + +class DataCiteSchemaV4(Schema): + """Schema for DataCite Metadata. + + For now, only the minimum required fields are implemented. In the future, + we may want to include optional fields as well. + + Fields and subfields are based on + schema.datacite.org/meta/kernel-4.1/doc/DataCite-MetadataKernel_v4.1.pdf + """ + + identifier = fields.Method( + 'get_identifier', + attribute='metadata.doi', + dump_only=True) + creators = fields.List( + fields.Nested(DataCiteCreatorSchemaV4), + attribute='metadata.author', + dump_only=True) + titles = fields.List( + fields.Nested(DataCiteTitleSchemaV4), + attribute='metadata', + dump_only=True) + publisher = fields.Method('get_publisher', dump_only=True) + publicationYear = fields.Method('get_year', dump_only=True) + resourceType = fields.Nested( + DataCiteResourceTypeSchemaV4, + attribute='metadata', # TODO: 'metadata.resource_type' when added + dump_only=True) + + def get_identifier(self, obj): + """Get record main identifier.""" + return { + 'identifier': obj['metadata'].get('doi', ''), + 'identifierType': 'DOI' + } + + def get_publisher(self, data): + """Extract publisher.""" + return current_app.config['DOI_PUBLISHER'] + + def get_year(self, data): + """Extract year. + + Current year for now. + TODO: Revisit when dealing with embargo. + """ + return date.today().year diff --git a/cd2h_repo_project/modules/doi/serializers.py b/cd2h_repo_project/modules/doi/serializers.py new file mode 100644 index 0000000..5bbe134 --- /dev/null +++ b/cd2h_repo_project/modules/doi/serializers.py @@ -0,0 +1,8 @@ +"""DOI serializer for external service.""" + +from invenio_records_rest.serializers.datacite import DataCite41Serializer + +from .schemas import DataCiteSchemaV4 + +# Datacite format serializer +datacite_v41 = DataCite41Serializer(DataCiteSchemaV4, replace_refs=True) diff --git a/cd2h_repo_project/modules/doi/tasks.py b/cd2h_repo_project/modules/doi/tasks.py new file mode 100644 index 0000000..03819bf --- /dev/null +++ b/cd2h_repo_project/modules/doi/tasks.py @@ -0,0 +1,87 @@ +"""CD2H Celery tasks.""" +import re + +from celery import shared_task +from datacite.client import DataCiteMDSClient +from datacite.errors import DataCiteError, HttpError +from elasticsearch.exceptions import RequestError +from flask import current_app, url_for +from invenio_db import db +from invenio_indexer.api import RecordIndexer +from invenio_pidstore.models import PersistentIdentifier, PIDStatus +from invenio_records_files.api import Record + +from cd2h_repo_project.modules.doi.serializers import datacite_v41 +from cd2h_repo_project.modules.records.links import ( + url_for_record_ui_recid_external +) +from cd2h_repo_project.modules.records.resolvers import record_resolver + + +def extract_doi(status_str): + """Extract minted DOI from response. + + Exceptions percolate. + """ + return re.search("\(([-.a-zA-Z0-9\/]+)\)$", status_str).group(1) + + +@shared_task(ignore_result=True, max_retries=6, default_retry_delay=10 * 60, + rate_limit='100/m') +def register_doi(recid_pid_value): + """External DOI registration task. + + This asynchronous task mints a DOI with the external service and + stores it in the local doi PID. It will retry a `max_retries` number of + times if the service is down. It will not retry for other errors + (internal ones). + + `default_retry_delay` is in seconds. + + :param recid_pid_value: pid_value of recid PID for the target record. + Note that this pid_value is also the pid_value of + the doi PID associated with the target record if + it has not been DOI minted yet. + """ + try: + pid, record = record_resolver.resolve(str(recid_pid_value)) + + doi_pid_value = record.get('doi') or recid_pid_value + doi_pid = PersistentIdentifier.get( + pid_type='doi', pid_value=doi_pid_value) + + client = DataCiteMDSClient( + username=current_app.config['PIDSTORE_DATACITE_USERNAME'], + password=current_app.config['PIDSTORE_DATACITE_PASSWORD'], + prefix=current_app.config['PIDSTORE_DATACITE_DOI_PREFIX'], + test_mode=current_app.config['PIDSTORE_DATACITE_TESTMODE'], + url=current_app.config['PIDSTORE_DATACITE_URL'] + ) + + # Mint DOI + serialized_record = datacite_v41.serialize(doi_pid, record) + result = client.metadata_post(serialized_record) + + # Associate landing page to DOI on DataCite if new + if doi_pid.status == PIDStatus.NEW: + minted_doi = extract_doi(result) + landing_url = url_for_record_ui_recid_external(recid_pid_value) + result = client.doi_post(minted_doi, landing_url) + + # Update doi_pid + doi_pid.pid_value = minted_doi + doi_pid.register() + record['doi'] = minted_doi + record.commit() + # Necessary but unclear why: (TODO: Investigate) + # - call to record.commit() is done above and + # - tests don't need it + db.session.commit() + + # Re-index record + RecordIndexer().index(record) + + except (HttpError, DataCiteError) as e: + register_doi.retry(exc=e) + except RequestError: + current_app.logger.exception('Could not index {}.'.format(record)) diff --git a/cd2h_repo_project/modules/doi/views.py b/cd2h_repo_project/modules/doi/views.py new file mode 100644 index 0000000..f226280 --- /dev/null +++ b/cd2h_repo_project/modules/doi/views.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2018 NU,FSM,GHSL. +# +# CD2H Data Model is free software; you can redistribute it and/or modify it +# under the terms of the MIT License; see LICENSE file for more details. + +"""Blueprint definitions.""" +from datetime import datetime, timedelta + +from flask import Blueprint, url_for + +from .links import doi_url_for + +blueprint = Blueprint( + 'cd2hrepo_doi', + __name__, + template_folder='templates', + static_folder='static', +) +"""Blueprint used for loading templates and static assets + +The sole purpose of this blueprint is to ensure that Invenio can find the +templates and static files located in the folders of the same names next to +this file. +""" + + +@blueprint.app_template_filter('to_doi_field') +def to_doi_field(record): + """Generate the DOI field.""" + doi_value = record.get('doi') + if doi_value: + doi_url = doi_url_for(doi_value) + return '{0}'.format(doi_url) + elif datetime.utcnow() < record.model.created + timedelta(hours=24): + return 'Minting the DOI...' + else: + return ( + 'There was an issue minting the DOI. ' + 'Contact us.'.format( + url_for('contact_us.contact_us')) + ) diff --git a/cd2h_repo_project/modules/records/api.py b/cd2h_repo_project/modules/records/api.py index 88bd087..6149692 100644 --- a/cd2h_repo_project/modules/records/api.py +++ b/cd2h_repo_project/modules/records/api.py @@ -2,6 +2,7 @@ from enum import Enum +from elasticsearch.exceptions import RequestError from flask import current_app from invenio_deposit.api import Deposit as _Deposit from invenio_deposit.api import has_status, preserve @@ -13,6 +14,8 @@ from invenio_records_files.models import RecordsBuckets from werkzeug.local import LocalProxy +from cd2h_repo_project.modules.doi.tasks import register_doi + current_jsonschemas = LocalProxy( lambda: current_app.extensions['invenio-jsonschemas'] ) @@ -185,6 +188,12 @@ def publish(self, pid=None, id_=None): 'Could not index {0}.'.format(published_record) ) + # NOTE: If we can figure out how to not raise + # sqlalchemy.orm.exc.DetachedInstanceError in client.post tests, + # we could use signal and receivers from client.post. + if current_app.config['DOI_REGISTER_SIGNALS']: + register_doi.delay(published_record['id']) + return self def _prepare_edit(self, published_record): diff --git a/cd2h_repo_project/modules/records/links.py b/cd2h_repo_project/modules/records/links.py index 0c6ede5..31f947c 100644 --- a/cd2h_repo_project/modules/records/links.py +++ b/cd2h_repo_project/modules/records/links.py @@ -1,14 +1,56 @@ """Deposit links factory.""" -from __future__ import absolute_import, print_function - import os -from flask import current_app, request, url_for +from flask import current_app, url_for from invenio_deposit.links import \ deposit_links_factory as _deposit_links_factory +def url_for_deposit_ui_recid_external(pid_value): + """Return the invenio_deposit_ui.recid endpoint from any app. + + Inside a Flask app other than the ui app (api or celery app), ui urls are + inaccessible (and vice versa). This function reconstructs the appropriate + URL to get the equivalent of + + url_for( + 'invenio_deposit_ui.', + pid_value=pid_value, + _external=True) + + from within any app. + Note: No request context needed. + """ + return current_app.config['DEPOSIT_UI_ENDPOINT'].format( + scheme=current_app.config['PREFERRED_URL_SCHEME'], + host=current_app.config['SERVER_NAME'], + pid_value=pid_value, + ) + + +def url_for_record_ui_recid_external(pid_value): + """Return the invenio_records_ui.recid endpoint from app other than ui. + + Inside a Flask app other than the ui app (api or celery app), ui urls are + inaccessible (and vice versa). This function reconstructs the appropriate + URL to get the equivalent of + + url_for( + 'invenio_records_ui.recid', + pid_value=pid_value, + _external=True) + + from within any app. + Note: No request context needed. + """ + return '{scheme}://{host}/records/{pid_value}'.format( + scheme=current_app.config['PREFERRED_URL_SCHEME'], + host=current_app.config['SERVER_NAME'], + pid_value=pid_value, + ) + + def deposit_links_api_factory(pid, **kwargs): """ Return, from the API applicaton, the useful URLs related to this record. @@ -27,11 +69,7 @@ def deposit_links_api_factory(pid, **kwargs): record = kwargs.get('record') links = _deposit_links_factory(pid) - links['html'] = current_app.config['DEPOSIT_UI_ENDPOINT'].format( - host=request.host, - scheme=request.scheme, - pid_value=pid.pid_value, - ) + links['html'] = url_for_deposit_ui_recid_external(pid.pid_value) bucket_id = record.get('_buckets', {}).get('deposit') if record else None diff --git a/cd2h_repo_project/modules/records/minters.py b/cd2h_repo_project/modules/records/minters.py index 663e712..da3cd8e 100644 --- a/cd2h_repo_project/modules/records/minters.py +++ b/cd2h_repo_project/modules/records/minters.py @@ -4,6 +4,8 @@ from invenio_pidstore.models import PersistentIdentifier, PIDStatus from invenio_pidstore.providers.recordid import RecordIdProvider +from cd2h_repo_project.modules.doi.minters import mint_record_doi + def mint_record(record_uuid, data): """Record PersistentIdentifiers minter. @@ -37,38 +39,3 @@ def mint_record_recid(record_uuid, data): object_type='rec', object_uuid=record_uuid).pid data[recid_field] = int(pid.pid_value) return pid - - -def mint_record_doi(record_uuid, data): - """Mint doi PersistentIdentifier in an initial New state. - - Because DOIs are minted by an external service, we create a PID for - tracking purposes, but do not mark it as Registered NOR do we specify the - final DOI value upfront. A unique and temporary doi value is put in the DB - until the real (final) DOI value is minted and provided to us by the - external service. For that reason, we don't pass that DOI value back in - `data`. - - An asynchronous task will update this PID with results from the external - service. - - A doi PersistentIdentifier can only be minted if the Record has an - associated recid PersistentIdentifier and it has not been doi minted - before. - - :param record_uuid: Record object uuid - :param data: Record object as dict (or dict-like). - :returns: doi PersistentIdentifier - """ - recid_field = current_app.config['PIDSTORE_RECID_FIELD'] - assert recid_field in data and 'doi' not in data - pid = PersistentIdentifier.create( - 'doi', - data['id'], # This is a purposefully unique but temporary value - pid_provider='datacite', - object_type='rec', - object_uuid=record_uuid, - status=PIDStatus.NEW, - ) - data['doi'] = '' - return pid diff --git a/cd2h_repo_project/modules/records/resolvers.py b/cd2h_repo_project/modules/records/resolvers.py new file mode 100644 index 0000000..04d242d --- /dev/null +++ b/cd2h_repo_project/modules/records/resolvers.py @@ -0,0 +1,9 @@ +"""CD2H Record resolver.""" + +from invenio_pidstore.resolver import Resolver +from invenio_records_files.api import Record + +record_resolver = Resolver( + pid_type='recid', object_type='rec', getter=Record.get_record +) +"""'recid'-PID resolver for published Records.""" diff --git a/cd2h_repo_project/modules/records/serializers/__init__.py b/cd2h_repo_project/modules/records/serializers/__init__.py index 8780f35..b5ca031 100644 --- a/cd2h_repo_project/modules/records/serializers/__init__.py +++ b/cd2h_repo_project/modules/records/serializers/__init__.py @@ -2,11 +2,10 @@ """Record serializers.""" -from __future__ import absolute_import, print_function - from invenio_records_rest.serializers.json import JSONSerializer -from invenio_records_rest.serializers.response import record_responsify, \ - search_responsify +from invenio_records_rest.serializers.response import ( + record_responsify, search_responsify +) from ..marshmallow import RecordSchemaV1 diff --git a/cd2h_repo_project/modules/records/templates/records/view.html b/cd2h_repo_project/modules/records/templates/records/view.html index 0617842..21afff7 100644 --- a/cd2h_repo_project/modules/records/templates/records/view.html +++ b/cd2h_repo_project/modules/records/templates/records/view.html @@ -57,6 +57,13 @@ {% set title = record.title + " | " + config.THEME_SITENAME %} +{%- block head_meta %} + {{ super() }} + {# HighWire meta tag compatible with Altmetric and Google Scholar... #} + {# TODO: Investigate what format we want to use to embed meta tag #} + {% if record.doi %}{% endif %} +{%- endblock %} + {%- block page_body %}
{%- block title %} @@ -79,26 +86,28 @@

{{ record.title }}

Edit {% endif %} -
+

{{ record.author }}

+

{{record.description}}

-
- -
- {{ record.license | license_value_to_name }} -
+ +
+
+
Digital Object Identifier (DOI)
+
{{ record | to_doi_field | safe }}
+
License
+
{{ record.license | license_value_to_name }}
+
+
diff --git a/cd2h_repo_project/modules/theme/static/scss/recordpage.scss b/cd2h_repo_project/modules/theme/static/scss/recordpage.scss new file mode 100644 index 0000000..c14c15c --- /dev/null +++ b/cd2h_repo_project/modules/theme/static/scss/recordpage.scss @@ -0,0 +1,12 @@ +@import 'common'; + + +#metadata-section .dl-horizontal { + dt { + width: 215px; + } + + dd { + margin-left: 235px; + } +} diff --git a/cd2h_repo_project/modules/theme/static/scss/styles.scss b/cd2h_repo_project/modules/theme/static/scss/styles.scss index 58ca80e..3588153 100644 --- a/cd2h_repo_project/modules/theme/static/scss/styles.scss +++ b/cd2h_repo_project/modules/theme/static/scss/styles.scss @@ -21,6 +21,7 @@ html { @import "footer"; // @import "input-icon"; @import "frontpage"; +@import "recordpage"; @import "search"; // CSS code to allow `ng-csp="no-inline-css"` diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 1ffae66..de7c1c9 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -16,7 +16,9 @@ services: service: app build: context: ./ - command: pipenv run ./run-ci-tests.sh + # Temporarily replace ./run-ci-tests.sh + # TODO: Fix E2E as separate task + command: pipenv run ./run-tests.sh image: cd2h-repo-project-ci # TODO: 'links' are deprecated, use 'networks' links: diff --git a/pytest.ini b/pytest.ini index 3f1ebcf..fb7284f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -9,3 +9,6 @@ pep8ignore = docs/conf.py ALL addopts = --pep8 --doctest-glob="*.rst" --doctest-modules --cov=cd2h_repo_project --cov-report=term-missing --ignore=setup.py testpaths = docs tests cd2h_repo_project +env = + # Forcefully disable external DOI minting in automated tests + INVENIO_DOI_REGISTER_SIGNALS=False diff --git a/setup.py b/setup.py index 8557f6a..861dfd7 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ 'cd2hrepo_records = cd2h_repo_project.modules.records.views:blueprint', 'cd2hrepo_user_dashboard = cd2h_repo_project.modules.user_dashboard.views:blueprint', 'cd2hrepo_contact_us = cd2h_repo_project.modules.contact_us.views:blueprint', + 'cd2hrepo_doi = cd2h_repo_project.modules.doi.views:blueprint', ], 'invenio_assets.bundles': [ 'cd2hrepo_theme_css = cd2h_repo_project.modules.theme.bundles:css', diff --git a/tests/api/doi/test_tasks.py b/tests/api/doi/test_tasks.py new file mode 100644 index 0000000..89baac1 --- /dev/null +++ b/tests/api/doi/test_tasks.py @@ -0,0 +1,150 @@ +"""Test Celery tasks.""" + +import json + +import datacite +import pytest +from flask import url_for +from invenio_pidstore.models import PersistentIdentifier, PIDStatus +from invenio_records_files.api import Record + +from cd2h_repo_project.modules.doi.serializers import datacite_v41 +from cd2h_repo_project.modules.doi.tasks import register_doi +from cd2h_repo_project.modules.records.minters import mint_record +from cd2h_repo_project.modules.records.resolvers import record_resolver +from utils import login_request_and_session + + +def test_register_doi_task_is_triggered_on_publish( + config, create_record, mocker): + orig_signal_trigger = config['DOI_REGISTER_SIGNALS'] + config['DOI_REGISTER_SIGNALS'] = True + deposit = create_record(published=False) + patched_delay = mocker.patch( + 'cd2h_repo_project.modules.records.api.register_doi.delay') + + deposit.publish() + + assert patched_delay.called + + config['DOI_REGISTER_SIGNALS'] = orig_signal_trigger + + +def test_register_doi_task_calls_succeeds(config, create_record, mocker): + """Test successful doi task.""" + patched_client = mocker.patch( + 'cd2h_repo_project.modules.doi.tasks.DataCiteMDSClient')() + orig_pidstore_datacite_doi_prefix = config['PIDSTORE_DATACITE_DOI_PREFIX'] + config['PIDSTORE_DATACITE_DOI_PREFIX'] = '10.5072' + returned_doi = '10.5072/qwer-tyui' + patched_client.metadata_post.return_value = 'OK ({})'.format(returned_doi) + patched_indexer = mocker.patch( + 'cd2h_repo_project.modules.doi.tasks.RecordIndexer')() + # Because publish() triggers the task, we need to perform some of the steps + # of publish() without calling publish() + record = create_record(published=False) + mint_record(record.id, record) + doi_pid = PersistentIdentifier.get(pid_type='doi', pid_value=record['id']) + + register_doi(record['id']) + + # Validate proper usage of DataCite API + assert patched_client.metadata_post.called_with( + datacite_v41.serialize(doi_pid, record) + ) + assert patched_client.doi_post.called_with( + returned_doi, + 'https://localhost:5000/records/{}'.format(record['id']) + ) + + # Validate DOI update + doi_pid = PersistentIdentifier.get(pid_type='doi', pid_value=returned_doi) + assert doi_pid.pid_value == returned_doi + pid, record = record_resolver.resolve(str(record['id'])) + assert record['doi'] == returned_doi + assert doi_pid.status == PIDStatus.REGISTERED + + # Validate indexing + assert patched_indexer.index.called + + config['PIDSTORE_DATACITE_DOI_PREFIX'] = orig_pidstore_datacite_doi_prefix + + +def test_register_doi_task_retries_if_datacite_down(create_record, mocker): + """Test register_doi task failing because of DataCite. + + Note that retries are done on another thread so only the initial call to + retry can be tested. + """ + patched_client = mocker.patch( + 'cd2h_repo_project.modules.doi.tasks.DataCiteMDSClient')() + patched_retry = mocker.patch( + 'cd2h_repo_project.modules.doi.tasks.register_doi.retry') + + # Because publish() triggers the task, we need to perform some of the steps + # of publish() without calling publish() + record = create_record(published=False) + mint_record(record.id, record) + doi_pid = PersistentIdentifier.get(pid_type='doi', pid_value=record['id']) + + # Test HttpError + patched_client.metadata_post.side_effect = datacite.errors.HttpError() + + register_doi(record['id']) + + number_retries = len(patched_retry.mock_calls) + assert number_retries == 1 + + # Test DataCiteError + patched_client.metadata_post.side_effect = datacite.errors.DataCiteError() + + register_doi(record['id']) + + number_retries = len(patched_retry.mock_calls) + assert number_retries == 2 + + +def test_register_doi_task_doesnt_retry_if_other_error(create_record, mocker): + """Test failing register_doi task because of us.""" + patched_client = mocker.patch( + 'cd2h_repo_project.modules.doi.tasks.DataCiteMDSClient')() + patched_retry = mocker.patch( + 'cd2h_repo_project.modules.doi.tasks.register_doi.retry') + + # Because publish() triggers the task, we need to perform some of the steps + # of publish() without calling publish() + record = create_record(published=False) + mint_record(record.id, record) + doi_pid = PersistentIdentifier.get(pid_type='doi', pid_value=record['id']) + + patched_client.metadata_post.side_effect = Exception() + + with pytest.raises(Exception): + register_doi(record['id']) + + number_retries = len(patched_retry.mock_calls) + assert number_retries == 0 + assert not record['doi'] + + +def test_register_doi_task_second_time_succeeds(config, create_record, mocker): + patched_client = mocker.patch( + 'cd2h_repo_project.modules.doi.tasks.DataCiteMDSClient')() + orig_pidstore_datacite_doi_prefix = config['PIDSTORE_DATACITE_DOI_PREFIX'] + config['PIDSTORE_DATACITE_DOI_PREFIX'] = '10.5072' + patched_client.metadata_post.return_value = 'OK (10.5072/qwer-tyui)' + patched_indexer = mocker.patch( + 'cd2h_repo_project.modules.doi.tasks.RecordIndexer')() + # Because publish() triggers the task, we need to perform some of the steps + # of publish() without calling publish() + record = create_record(published=False) + mint_record(record.id, record) + doi_pid = PersistentIdentifier.get(pid_type='doi', pid_value=record['id']) + + register_doi(record['id']) + register_doi(record['id']) # Second time + + assert len(patched_client.metadata_post.mock_calls) == 2 + assert len(patched_client.doi_post.mock_calls) == 1 + + config['PIDSTORE_DATACITE_DOI_PREFIX'] = orig_pidstore_datacite_doi_prefix diff --git a/tests/api/records/test_links.py b/tests/api/records/test_links.py index 6695f96..0ac1b07 100644 --- a/tests/api/records/test_links.py +++ b/tests/api/records/test_links.py @@ -26,4 +26,5 @@ def test_deposit_links_api_factory_contains_html(app, create_record): ): links = deposit_links_api_factory(record.pid, record=record) - assert links['html'] == 'http://localhost/deposit/' + expected_pid_value + expected_link = 'https://localhost:5000/deposit/' + expected_pid_value + assert links['html'] == expected_link diff --git a/tests/api/records/test_serializers.py b/tests/api/records/test_serializers.py index abf7a4c..17370a3 100644 --- a/tests/api/records/test_serializers.py +++ b/tests/api/records/test_serializers.py @@ -1,48 +1,100 @@ -from datetime import datetime +from datetime import date +import pytest +from flask import current_app from invenio_pidstore.models import PersistentIdentifier -from cd2h_repo_project.modules.records.marshmallow.json import ( - MetadataSchemaV1, RecordSchemaV1 -) +from cd2h_repo_project.modules.doi.serializers import datacite_v41 +from cd2h_repo_project.modules.records.minters import mint_record from cd2h_repo_project.modules.records.serializers import json_v1 -def test_json_v1_serializes_persistent_identifier(create_record): - record = create_record() - pid = PersistentIdentifier.get( - record['_deposit']['pid']['type'], - record['_deposit']['pid']['value'], - ) +class TestJsonV1(object): + def test_serializes_persistent_identifier(self, create_record): + record = create_record() + pid = PersistentIdentifier.get( + record['_deposit']['pid']['type'], + record['_deposit']['pid']['value'], + ) - serialized_record = json_v1.transform_record(pid, record) + serialized_record = json_v1.transform_record(pid, record) - assert serialized_record['id'] == record['_deposit']['pid']['value'] + assert serialized_record['id'] == record['_deposit']['pid']['value'] + def test_serializes_dump_onlys(self, create_record): + record = create_record() + pid = PersistentIdentifier.get( + record['_deposit']['pid']['type'], + record['_deposit']['pid']['value'], + ) -def test_json_v1_serializes_dump_onlys(create_record): - record = create_record() - pid = PersistentIdentifier.get( - record['_deposit']['pid']['type'], - record['_deposit']['pid']['value'], - ) + serialized_record = json_v1.transform_record(pid, record) - serialized_record = json_v1.transform_record(pid, record) + assert 'created' in serialized_record + assert 'updated' in serialized_record + assert 'links' in serialized_record - assert 'created' in serialized_record - assert 'updated' in serialized_record - assert 'links' in serialized_record + def test_serializes_metadata(self, create_record): + record = create_record() + pid = PersistentIdentifier.get( + record['_deposit']['pid']['type'], + record['_deposit']['pid']['value'], + ) + serialized_record = json_v1.transform_record(pid, record) -def test_json_v1_serializes_metadata(create_record): - record = create_record() - pid = PersistentIdentifier.get( - record['_deposit']['pid']['type'], - record['_deposit']['pid']['value'], - ) + required_keys = ['title', 'description', 'author', 'license'] + for key in required_keys: + assert serialized_record['metadata'][key] - serialized_record = json_v1.transform_record(pid, record) - required_keys = ['title', 'description', 'author', 'license'] - for key in required_keys: - assert serialized_record['metadata'][key] +@pytest.fixture +def serialized_record(create_record): + record = create_record(published=False) + mint_record(record.id, record) + pid = PersistentIdentifier.get('doi', record['id']) + return datacite_v41.serialize(pid, record) + + +class TestDataCiteV4(object): + """Test DataCiteV4 serialization""" + + def test_serializes_empty_identifier(self, serialized_record): + # Expect empty identifier so that DataCite generates it + assert ( + '' in + serialized_record + ) + + def test_serializes_creators(self, serialized_record): + # TODO: Test for multiple authors when we provide multiple author + # input fields + assert "\n" in serialized_record + assert "\n" in serialized_record + assert "author, An\n" in serialized_record + assert "\n" in serialized_record + assert "" in serialized_record + + def test_serializes_titles(self, serialized_record): + assert "\n" in serialized_record + assert "A title" in serialized_record + assert "\n" in serialized_record + + def test_serializes_publisher(self, serialized_record): + assert ( + "{}".format( + current_app.config['DOI_PUBLISHER']) in serialized_record + ) + + def test_serializes_publicationYear(self, serialized_record): + assert ( + "{}".format(date.today().year) + in serialized_record + ) + + def test_serializes_resourceType(self, serialized_record): + # TODO: Adjust if when we provide resource type as an input field + assert ( + 'Dataset' + '' in serialized_record + ) diff --git a/tests/ui/doi/__init__.py b/tests/ui/doi/__init__.py new file mode 100644 index 0000000..ec8ec40 --- /dev/null +++ b/tests/ui/doi/__init__.py @@ -0,0 +1 @@ +"""Test DOI Module.""" diff --git a/tests/ui/doi/test_views.py b/tests/ui/doi/test_views.py new file mode 100644 index 0000000..5ee6fcf --- /dev/null +++ b/tests/ui/doi/test_views.py @@ -0,0 +1,43 @@ +from datetime import datetime, timedelta + +import pytest + +from cd2h_repo_project.modules.doi.views import to_doi_field + + +@pytest.mark.parametrize('doi, timed_out, expect', [ + ( + '10.5072/qwer-tyui', + False, + ('' + 'https://doi.org/10.5072/qwer-tyui') + ), + ('', False, 'Minting the DOI...'), + ( + '', + True, + ('There was an issue minting the DOI. ' + 'Contact us.'), + ), + ( + '10.5072/qwer-tyui', + True, + ('' + 'https://doi.org/10.5072/qwer-tyui') + ), +]) +def test_to_doi_field( + doi, timed_out, expect, create_record, mocker, request_ctx): + record = create_record() + record['doi'] = doi + if timed_out: + patched_datetime = mocker.patch( + 'cd2h_repo_project.modules.doi.views.datetime', + ) + patched_datetime.utcnow.return_value = ( + datetime.utcnow() + timedelta(hours=24) + ) + + result = to_doi_field(record) + + assert result == expect