From a929b021e1c2fa6f43f142621addd5679f5cbe31 Mon Sep 17 00:00:00 2001 From: Nicola Tarocco Date: Tue, 19 Nov 2019 09:04:51 +0100 Subject: [PATCH 01/25] travis: fix release --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0f8b381..9bf24cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ env: - REQUIREMENTS=lowest EXTRAS=all,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" - REQUIREMENTS=release EXTRAS=all,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - REQUIREMENTS=release EXTRAS=all,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - - REQUIREMENTS=release EXTRAS=all,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" + - REQUIREMENTS=release EXTRAS=all,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" DEPLOY=true - REQUIREMENTS=devel EXTRAS=all,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - REQUIREMENTS=devel EXTRAS=all,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - REQUIREMENTS=devel EXTRAS=all,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" @@ -74,5 +74,6 @@ deploy: distributions: "compile_catalog sdist bdist_wheel" on: tags: true - python: "2.7" - condition: $REQUIREMENTS = release + python: "3.5" + repo: inveniosoftware/invenio-pidstore + condition: $DEPLOY = true From acd00be15d0297af53be2b737570e6f49ca106a4 Mon Sep 17 00:00:00 2001 From: Harris Tzovanakis Date: Mon, 13 Jan 2020 17:20:59 +0100 Subject: [PATCH 02/25] models: fix exception interpolation for better aggreagation Signed-off-by: Harris Tzovanakis --- invenio_pidstore/models.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/invenio_pidstore/models.py b/invenio_pidstore/models.py index b6703be..8c2cf5b 100644 --- a/invenio_pidstore/models.py +++ b/invenio_pidstore/models.py @@ -151,7 +151,7 @@ def create(cls, pid_type, pid_value, pid_provider=None, extra={'pid': obj}) except IntegrityError: logger.exception( - "PID already exists: {0}:{1}".format(pid_type, pid_value), + "PID already exists: %s:%s", pid_type, pid_value, extra=dict( pid_type=pid_type, pid_value=pid_value, @@ -163,7 +163,7 @@ def create(cls, pid_type, pid_value, pid_provider=None, raise PIDAlreadyExists(pid_type=pid_type, pid_value=pid_value) except SQLAlchemyError: logger.exception( - "Failed to create PID: {0}:{1}".format(pid_type, pid_value), + "Failed to create PID: %s:%s", pid_type, pid_value, extra=dict( pid_type=pid_type, pid_value=pid_value, @@ -281,8 +281,12 @@ def assign(self, object_type, object_uuid, overwrite=False): self.object_uuid = object_uuid db.session.add(self) except SQLAlchemyError: - logger.exception("Failed to assign {0}:{1}".format( - object_type, object_uuid), extra=dict(pid=self)) + logger.exception( + "Failed to assign %s:%s", + object_type, + object_uuid, + extra=dict(pid=self) + ) raise logger.info("Assigned object {0}:{1}".format( object_type, object_uuid), extra=dict(pid=self)) @@ -311,7 +315,7 @@ def unassign(self): self.object_uuid = None db.session.add(self) except SQLAlchemyError: - logger.exception("Failed to unassign object.".format(self), + logger.exception("Failed to unassign object.", extra=dict(pid=self)) raise logger.info("Unassigned object from {0}.".format(self), @@ -360,8 +364,8 @@ def redirect(self, pid): except IntegrityError: raise PIDDoesNotExistError(pid.pid_type, pid.pid_value) except SQLAlchemyError: - logger.exception("Failed to redirect to {0}".format( - pid), extra=dict(pid=self)) + logger.exception( + "Failed to redirect to %s", pid, extra=dict(pid=self)) raise logger.info("Redirected PID to {0}".format(pid), extra=dict(pid=self)) return True @@ -460,11 +464,11 @@ def sync_status(self, status): self.status = status db.session.add(self) except SQLAlchemyError: - logger.exception("Failed to sync status {0}.".format(status), - extra=dict(pid=self)) + logger.exception( + "Failed to sync status %s.", status, extra=dict(pid=self)) raise - logger.info("Synced PID status to {0}.".format(status), - extra=dict(pid=self)) + logger.info( + "Synced PID status to {0}.".format(status), extra=dict(pid=self)) return True def is_redirected(self): From 6531259705d027663b97d19d0211b56d06ea2e4b Mon Sep 17 00:00:00 2001 From: Alexander Ioannidis Date: Mon, 2 Mar 2020 14:32:06 +0100 Subject: [PATCH 03/25] global: centralize dependencies * Replaces Flask and six with Invenio-Base, Flask-Admin with Invenio-Admin, Flask-BabelEx with Invenio-I18N (closes #130). * Cleans up Python versions from .travis.yml --- .travis.yml | 36 ++++++++++++++++-------------------- setup.py | 13 ++++++------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9bf24cb..1e7e005 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,8 @@ notifications: email: false -sudo: false - language: python -dist: trusty - cache: - pip @@ -27,26 +23,26 @@ addons: postgresql: "9.4" env: - - REQUIREMENTS=lowest EXTRAS=all,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - - REQUIREMENTS=lowest EXTRAS=all,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - - REQUIREMENTS=lowest EXTRAS=all,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" - - REQUIREMENTS=release EXTRAS=all,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - - REQUIREMENTS=release EXTRAS=all,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - - REQUIREMENTS=release EXTRAS=all,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" DEPLOY=true - - REQUIREMENTS=devel EXTRAS=all,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - - REQUIREMENTS=devel EXTRAS=all,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - - REQUIREMENTS=devel EXTRAS=all,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" + - REQUIREMENTS=lowest EXTRAS=admin,datacite,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" + - REQUIREMENTS=lowest EXTRAS=admin,datacite,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" + - REQUIREMENTS=lowest EXTRAS=admin,datacite,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" + - REQUIREMENTS=release EXTRAS=admin,datacite,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" + - REQUIREMENTS=release EXTRAS=admin,datacite,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" + - REQUIREMENTS=release EXTRAS=admin,datacite,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" DEPLOY=true + - REQUIREMENTS=devel EXTRAS=admin,datacite,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" + - REQUIREMENTS=devel EXTRAS=admin,datacite,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" + - REQUIREMENTS=devel EXTRAS=admin,datacite,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" python: - - "2.7" - - "3.5" + - "3.6" + - "3.7" matrix: fast_finish: true allow_failures: - - env: REQUIREMENTS=devel EXTRAS=all,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - - env: REQUIREMENTS=devel EXTRAS=all,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - - env: REQUIREMENTS=devel EXTRAS=all,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" + - env: REQUIREMENTS=devel EXTRAS=admin,datacite,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" + - env: REQUIREMENTS=devel EXTRAS=admin,datacite,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" + - env: REQUIREMENTS=devel EXTRAS=admin,datacite,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" before_install: - "travis_retry pip install --upgrade pip setuptools py" @@ -57,7 +53,7 @@ before_install: install: - "travis_retry pip install -r .travis-${REQUIREMENTS}-requirements.txt" - - "travis_retry pip install -e .[${EXTRAS}]" + - "travis_retry pip install -e .[all]" script: - pip freeze @@ -74,6 +70,6 @@ deploy: distributions: "compile_catalog sdist bdist_wheel" on: tags: true - python: "3.5" + python: "3.6" repo: inveniosoftware/invenio-pidstore condition: $DEPLOY = true diff --git a/setup.py b/setup.py index d9ee8b5..af8d40a 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ 'check-manifest>=0.25', 'coverage>=4.0', 'isort>=4.3.0', - 'invenio-admin>=1.0.0', + 'invenio-admin>=1.2.0', 'Flask-Menu>=0.5.1', 'invenio-access>=1.0.0', 'invenio-accounts>=1.0.0', @@ -34,9 +34,8 @@ ] extras_require = { - ':python_version<"3.4"': ['enum34>=1.1.6'], 'admin': [ - 'Flask-Admin>=1.3.0', + 'invenio-admin>=1.2.0', ], 'datacite': [ 'datacite>=0.1.0' @@ -69,10 +68,10 @@ ] install_requires = [ - 'Flask-BabelEx>=0.9.3', - 'Flask>=0.11.1', - 'six>=1.12.0', - 'base32-lib>=1.0.1' + 'base32-lib>=1.0.1', + 'enum34>=1.1.6;python_version<"3.4"', + 'invenio-base>=1.2.2', + 'invenio-i18n>=1.2.0', ] packages = find_packages() From 49f22cdb3efa78f9b784ffc63394a2945f6a3079 Mon Sep 17 00:00:00 2001 From: Harris Tzovanakis Date: Tue, 10 Mar 2020 10:53:31 +0100 Subject: [PATCH 04/25] release: v1.2.0 --- CHANGES.rst | 8 +++++++- invenio_pidstore/version.py | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 18066d2..ab1c388 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,6 @@ .. This file is part of Invenio. - Copyright (C) 2015-2019 CERN. + Copyright (C) 2015-2020 CERN. Invenio is free software; you can redistribute it and/or modify it under the terms of the MIT License; see LICENSE file for more details. @@ -8,6 +8,12 @@ Changes ======= +Version 1.2.0 (released 2020-03-09) + +- Change exception interpolation for better aggregation +- Depend on Invenio-Base, Invenio-Admin, and Invenio-I18N to centralize + 3rd-party module dependencies. + Version 1.1.0 (released 2019-11-18) - New record id provider v2 to generate random, base32, URI-friendly diff --git a/invenio_pidstore/version.py b/invenio_pidstore/version.py index 2cf5880..2f6e2bd 100644 --- a/invenio_pidstore/version.py +++ b/invenio_pidstore/version.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # This file is part of Invenio. -# Copyright (C) 2015-2019 CERN. +# Copyright (C) 2015-2020 CERN. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -14,4 +14,4 @@ from __future__ import absolute_import, print_function -__version__ = '1.1.0' +__version__ = '1.2.0' From 21702687bbbc780964939fe798cb8e592a45b191 Mon Sep 17 00:00:00 2001 From: Pablo Panero Date: Mon, 20 Jul 2020 16:15:34 +0200 Subject: [PATCH 05/25] models: fix docstring typos --- invenio_pidstore/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invenio_pidstore/models.py b/invenio_pidstore/models.py index 8c2cf5b..664ed9e 100644 --- a/invenio_pidstore/models.py +++ b/invenio_pidstore/models.py @@ -490,14 +490,14 @@ def is_deleted(self): return self.status == PIDStatus.DELETED def is_new(self): - """Return true if the PIDhas not yet been registered or reserved. + """Return true if the PID is new. :returns: A boolean value. """ return self.status == PIDStatus.NEW def is_reserved(self): - """Return true if the PID has not yet been reserved. + """Return true if the PID has been reserved. :returns: A boolean value. """ From 1313dc9b26e9ff0b51e18ae7dfb146a1ed0c3679 Mon Sep 17 00:00:00 2001 From: Pablo Panero Date: Mon, 20 Jul 2020 16:47:34 +0200 Subject: [PATCH 06/25] dependencies, tests: upgrade deps and fix isort issues --- invenio_pidstore/admin.py | 1 - invenio_pidstore/fetchers.py | 1 - invenio_pidstore/models.py | 4 ++-- invenio_pidstore/providers/recordid_v2.py | 1 - run-tests.sh | 2 +- setup.py | 13 +++---------- tests/conftest.py | 3 +-- tests/test_admin.py | 1 - tests/test_cli.py | 1 - tests/test_invenio_pidstore.py | 3 +-- tests/test_minters.py | 3 +-- tests/test_providers.py | 3 +-- tests/test_resolver.py | 3 +-- 13 files changed, 11 insertions(+), 28 deletions(-) diff --git a/invenio_pidstore/admin.py b/invenio_pidstore/admin.py index b503d0f..f6fbf7d 100644 --- a/invenio_pidstore/admin.py +++ b/invenio_pidstore/admin.py @@ -9,7 +9,6 @@ """Admin model views for PersistentIdentifier.""" import uuid - from flask import current_app, url_for from flask_admin.contrib.sqla import ModelView from flask_admin.contrib.sqla.filters import FilterEqual diff --git a/invenio_pidstore/fetchers.py b/invenio_pidstore/fetchers.py index 7795367..cec3d8e 100644 --- a/invenio_pidstore/fetchers.py +++ b/invenio_pidstore/fetchers.py @@ -28,7 +28,6 @@ def my_fetcher(record_uuid, data): from __future__ import absolute_import, print_function from collections import namedtuple - from flask import current_app from .providers.recordid import RecordIdProvider diff --git a/invenio_pidstore/models.py b/invenio_pidstore/models.py index 664ed9e..4aa4127 100644 --- a/invenio_pidstore/models.py +++ b/invenio_pidstore/models.py @@ -10,11 +10,11 @@ from __future__ import absolute_import, print_function -import logging -import uuid from enum import Enum +import logging import six +import uuid from flask_babelex import gettext from invenio_db import db from speaklater import make_lazy_gettext diff --git a/invenio_pidstore/providers/recordid_v2.py b/invenio_pidstore/providers/recordid_v2.py index 355ca4f..77f6397 100644 --- a/invenio_pidstore/providers/recordid_v2.py +++ b/invenio_pidstore/providers/recordid_v2.py @@ -12,7 +12,6 @@ from __future__ import absolute_import import copy - from base32_lib import base32 from flask import current_app diff --git a/run-tests.sh b/run-tests.sh index c6885e4..820c122 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -9,7 +9,7 @@ # under the terms of the MIT License; see LICENSE file for more details. pydocstyle invenio_pidstore tests docs && \ -isort -rc -c -df && \ +isort invenio_pidstore tests --check-only --diff && \ check-manifest --ignore ".travis-*" && \ sphinx-build -qnNW docs docs/_build/html && \ python setup.py test diff --git a/setup.py b/setup.py index af8d40a..0c82835 100644 --- a/setup.py +++ b/setup.py @@ -17,20 +17,13 @@ history = open('CHANGES.rst').read() tests_require = [ - 'attrs>=17.4.0', # once pytest is upgraded this can be removed - 'SQLAlchemy-Continuum>=1.2.1', - 'check-manifest>=0.25', - 'coverage>=4.0', - 'isort>=4.3.0', - 'invenio-admin>=1.2.0', 'Flask-Menu>=0.5.1', + 'invenio-admin>=1.2.0', 'invenio-access>=1.0.0', 'invenio-accounts>=1.0.0', 'mock>=3.0.0', - 'pydocstyle>=1.0.0', - 'pytest-cov>=1.8.0', - 'pytest-pep8>=1.0.6', - 'pytest>=3.8.0,<5.0.0', + 'pytest-invenio<=1.3.2', + 'SQLAlchemy-Continuum>=1.2.1', ] extras_require = { diff --git a/tests/conftest.py b/tests/conftest.py index 4219e79..f51f1ca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,10 +11,9 @@ from __future__ import absolute_import, print_function import os +import pytest import shutil import tempfile - -import pytest from flask import Flask from invenio_db import InvenioDB from sqlalchemy_utils.functions import create_database, database_exists diff --git a/tests/test_admin.py b/tests/test_admin.py index a155857..87c92dc 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -10,7 +10,6 @@ from __future__ import absolute_import, print_function import uuid - from flask_admin import Admin, menu from invenio_db import db diff --git a/tests/test_cli.py b/tests/test_cli.py index dd3fd00..074565b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -12,7 +12,6 @@ from __future__ import absolute_import, print_function import uuid - from click.testing import CliRunner from flask.cli import ScriptInfo diff --git a/tests/test_invenio_pidstore.py b/tests/test_invenio_pidstore.py index 8548506..a0ff69a 100644 --- a/tests/test_invenio_pidstore.py +++ b/tests/test_invenio_pidstore.py @@ -11,9 +11,8 @@ from __future__ import absolute_import, print_function -import uuid - import pytest +import uuid from mock import patch from sqlalchemy.exc import SQLAlchemyError diff --git a/tests/test_minters.py b/tests/test_minters.py index fe2e160..7b99305 100644 --- a/tests/test_minters.py +++ b/tests/test_minters.py @@ -10,9 +10,8 @@ from __future__ import absolute_import, print_function -import uuid - import pytest +import uuid from invenio_pidstore import current_pidstore from invenio_pidstore.minters import recid_minter, recid_minter_v2 diff --git a/tests/test_providers.py b/tests/test_providers.py index 515a737..18920cf 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -11,9 +11,8 @@ from __future__ import absolute_import, print_function -import uuid - import pytest +import uuid from datacite.errors import DataCiteError, DataCiteGoneError, \ DataCiteNoContentError, DataCiteNotFoundError, HttpError from mock import MagicMock, patch diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 337e4be..3f567bd 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -10,9 +10,8 @@ from __future__ import absolute_import, print_function -import uuid - import pytest +import uuid from invenio_pidstore.errors import PIDDeletedError, PIDDoesNotExistError, \ PIDMissingObjectError, PIDRedirectedError, PIDUnregistered From dec0fd12f8dd62f80221c5d457ad708fe7be3be7 Mon Sep 17 00:00:00 2001 From: Pablo Panero Date: Mon, 20 Jul 2020 17:02:02 +0200 Subject: [PATCH 07/25] resolver: support returning new and reseved pids --- invenio_pidstore/resolver.py | 9 ++++++-- tests/test_resolver.py | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/invenio_pidstore/resolver.py b/invenio_pidstore/resolver.py index 6b8e44f..57970de 100644 --- a/invenio_pidstore/resolver.py +++ b/invenio_pidstore/resolver.py @@ -24,7 +24,8 @@ class Resolver(object): identifier. """ - def __init__(self, pid_type=None, object_type=None, getter=None): + def __init__(self, pid_type=None, object_type=None, getter=None, + registered_only=True): """Initialize resolver. :param pid_type: Persistent identifier type. @@ -35,6 +36,7 @@ def __init__(self, pid_type=None, object_type=None, getter=None): self.pid_type = pid_type self.object_type = object_type self.object_getter = getter + self.registered_only = registered_only def resolve(self, pid_value): """Resolve a persistent identifier to an internal object. @@ -45,7 +47,10 @@ def resolve(self, pid_value): pid = PersistentIdentifier.get(self.pid_type, pid_value) if pid.is_new() or pid.is_reserved(): - raise PIDUnregistered(pid) + if self.registered_only: + raise PIDUnregistered(pid) + else: + obj_id = pid.get_assigned_object(object_type=self.object_type) if pid.is_deleted(): obj_id = pid.get_assigned_object(object_type=self.object_type) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 3f567bd..6ed848c 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -125,3 +125,45 @@ def test_resolver_deleted_object(app, db): pid_type='recid', object_type='rec', getter=records.get) assert pytest.raises(PIDDeletedError, resolver.resolve, '1') + + +def test_resolver_not_registered_only(app, db): + """Test the resolver returns a new and reserved PID when specified.""" + + status = [ + PIDStatus.NEW, + PIDStatus.RESERVED, + PIDStatus.REGISTERED + ] + + with app.app_context(): + rec_a = uuid.uuid4() + # Create pids for each status with and without object + for idx, s in enumerate(status, 1): + PersistentIdentifier.create('recid', idx*2 - 1, status=s) + PersistentIdentifier.create('recid', idx*2, status=s, + object_type='rec', object_uuid=rec_a) + + db.session.commit() + + # Start tests + resolver = Resolver( + pid_type='recid', + object_type='rec', + getter=lambda x: x, + registered_only=False) + + # Resolve status new + pytest.raises(PIDMissingObjectError, resolver.resolve, '1') + pid, obj = resolver.resolve('2') + assert pid and obj == rec_a + + # Resolve status reserved + pytest.raises(PIDMissingObjectError, resolver.resolve, '3') + pid, obj = resolver.resolve('4') + assert pid and obj == rec_a + + # Resolve status registered + pytest.raises(PIDMissingObjectError, resolver.resolve, '5') + pid, obj = resolver.resolve('6') + assert pid and obj == rec_a From 8399be394b6497b512725ac59ef045b18f1954a0 Mon Sep 17 00:00:00 2001 From: Pablo Panero Date: Tue, 21 Jul 2020 17:19:16 +0200 Subject: [PATCH 08/25] recid_v2: support setting default status for obj_type and obj_uuid --- invenio_pidstore/providers/recordid_v2.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/invenio_pidstore/providers/recordid_v2.py b/invenio_pidstore/providers/recordid_v2.py index 77f6397..c02de73 100644 --- a/invenio_pidstore/providers/recordid_v2.py +++ b/invenio_pidstore/providers/recordid_v2.py @@ -38,9 +38,15 @@ class RecordIdProviderV2(BaseProvider): provide any additional features besides creation of record ids. """ - default_status = PIDStatus.RESERVED + default_status_with_obj = PIDStatus.REGISTERED """Record IDs are by default registered immediately. + Default: :attr:`invenio_pidstore.models.PIDStatus.REGISTERED` + """ + + default_status = PIDStatus.RESERVED + """Record IDs with an object are by default reserved. + Default: :attr:`invenio_pidstore.models.PIDStatus.RESERVED` """ @@ -91,7 +97,7 @@ def create(cls, object_type=None, object_uuid=None, options=None, kwargs.setdefault('status', cls.default_status) if object_type and object_uuid: - kwargs['status'] = PIDStatus.REGISTERED + kwargs['status'] = cls.default_status_with_obj return super(RecordIdProviderV2, cls).create( object_type=object_type, object_uuid=object_uuid, **kwargs) From 172f68edd9525232154489d2134c658570b2275d Mon Sep 17 00:00:00 2001 From: Pablo Panero Date: Wed, 22 Jul 2020 09:00:34 +0200 Subject: [PATCH 09/25] release: v1.2.1 --- CHANGES.rst | 5 +++++ invenio_pidstore/version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index ab1c388..6689c77 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,11 @@ Changes ======= +Version 1.2.1 (released 2020-07-22) + +- Support returning NEW and RESERVED PIDs by setting the `registered_only` flag. +- Support setting default status for PIDs with object type and uuid. + Version 1.2.0 (released 2020-03-09) - Change exception interpolation for better aggregation diff --git a/invenio_pidstore/version.py b/invenio_pidstore/version.py index 2f6e2bd..f4e43c5 100644 --- a/invenio_pidstore/version.py +++ b/invenio_pidstore/version.py @@ -14,4 +14,4 @@ from __future__ import absolute_import, print_function -__version__ = '1.2.0' +__version__ = '1.2.1' From eaf6c826f324cb69f28d0b859ab1add59d60dfd2 Mon Sep 17 00:00:00 2001 From: Parth Shandilya Date: Wed, 9 Dec 2020 17:05:00 +0100 Subject: [PATCH 10/25] global: migrate CI to gh-actions closes #134 --- .editorconfig | 4 +- .github/workflows/pypi-publish.yml | 35 +++++++++++ .github/workflows/tests.yml | 95 ++++++++++++++++++++++++++++++ .travis.yml | 75 ----------------------- CONTRIBUTING.rst | 4 +- MANIFEST.in | 1 + README.rst | 4 +- pytest.ini | 3 +- run-tests.sh | 28 ++++++--- setup.py | 9 ++- 10 files changed, 163 insertions(+), 95 deletions(-) create mode 100644 .github/workflows/pypi-publish.yml create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml diff --git a/.editorconfig b/.editorconfig index 020cf98..96f691f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -34,8 +34,8 @@ indent_size = 4 [*.{css,html,js,json,yml}] indent_size = 2 -# Matches the exact files either package.json or .travis.yml -[{package.json,.travis.yml}] +# Matches the exact files either package.json or .github/workflows/*.yml +[{package.json, .github/workflows/*.yml}] indent_size = 2 # Dockerfile diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 0000000..556367a --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,35 @@ +name: Publish + +on: + push: + tags: + - v* + +jobs: + Publish: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.7 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel + + - name: Build package + # Remove `compile_catalog` if the package has no translations. + run: | + python setup.py compile_catalog sdist bdist_wheel + + - name: Publish on PyPI + uses: pypa/gh-action-pypi-publish@v1.3.1 + with: + user: __token__ + # The token is provided by the inveniosoftware organization + password: ${{ secrets.pypi_token }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..1a0ae62 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Invenio. +# Copyright (C) 2020 CERN. +# +# Invenio is free software; you can redistribute it and/or modify it +# under the terms of the MIT License; see LICENSE file for more details. + +name: CI + +on: + push: + branches: master + pull_request: + branches: master + schedule: + # * is a special character in YAML so you have to quote this string + - cron: '0 3 * * 6' + workflow_dispatch: + inputs: + reason: + description: 'Reason' + required: false + default: 'Manual trigger' + +jobs: + Tests: + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + requirements-level: [min, pypi] + cache-service: [redis] + db-service: [postgresql9, postgresql11, mysql5, mysql8] + exclude: + - python-version: 3.8 + requirements-level: min + + - python-version: 3.9 + requirements-level: min + + - db-service: postgresql11 + python-version: 3.6 + + - db-service: mysql8 + python-version: 3.6 + + include: + - db-service: postgresql9 + DB_EXTRAS: "postgresql" + + - db-service: postgresql11 + DB_EXTRAS: "postgresql" + + - db-service: mysql5 + DB_EXTRAS: "mysql" + + - db-service: mysql8 + DB_EXTRAS: "mysql" + + env: + CACHE: ${{ matrix.cache-service }} + DB: ${{ matrix.db-service }} + EXTRAS: all,${{ matrix.DB_EXTRAS }} + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Generate dependencies + run: | + python -m pip install --upgrade pip setuptools py wheel requirements-builder + requirements-builder -e "$EXTRAS" --level=${{ matrix.requirements-level }} setup.py > .${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('.${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt') }} + + - name: Install dependencies + run: | + pip install -r .${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt + pip install ".[$EXTRAS]" + pip freeze + docker --version + docker-compose --version + + - name: Run tests + run: | + ./run-tests.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1e7e005..0000000 --- a/.travis.yml +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Invenio. -# Copyright (C) 2015-2018 CERN. -# -# Invenio is free software; you can redistribute it and/or modify it -# under the terms of the MIT License; see LICENSE file for more details. - -notifications: - email: false - -language: python - -cache: - - pip - -services: - - mysql - - postgresql - - redis - -addons: - postgresql: "9.4" - -env: - - REQUIREMENTS=lowest EXTRAS=admin,datacite,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - - REQUIREMENTS=lowest EXTRAS=admin,datacite,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - - REQUIREMENTS=lowest EXTRAS=admin,datacite,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" - - REQUIREMENTS=release EXTRAS=admin,datacite,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - - REQUIREMENTS=release EXTRAS=admin,datacite,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - - REQUIREMENTS=release EXTRAS=admin,datacite,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" DEPLOY=true - - REQUIREMENTS=devel EXTRAS=admin,datacite,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - - REQUIREMENTS=devel EXTRAS=admin,datacite,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - - REQUIREMENTS=devel EXTRAS=admin,datacite,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" - -python: - - "3.6" - - "3.7" - -matrix: - fast_finish: true - allow_failures: - - env: REQUIREMENTS=devel EXTRAS=admin,datacite,sqlite SQLALCHEMY_DATABASE_URI="sqlite:///test.db" - - env: REQUIREMENTS=devel EXTRAS=admin,datacite,mysql SQLALCHEMY_DATABASE_URI="mysql+pymysql://travis@localhost:3306/invenio" - - env: REQUIREMENTS=devel EXTRAS=admin,datacite,postgresql SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres@localhost:5432/invenio" - -before_install: - - "travis_retry pip install --upgrade pip setuptools py" - - "travis_retry pip install twine wheel coveralls requirements-builder" - - "requirements-builder -e ${EXTRAS} --level=min setup.py > .travis-lowest-requirements.txt" - - "requirements-builder -e ${EXTRAS} --level=pypi setup.py > .travis-release-requirements.txt" - - "requirements-builder -e ${EXTRAS} --level=dev --req requirements-devel.txt setup.py > .travis-devel-requirements.txt" - -install: - - "travis_retry pip install -r .travis-${REQUIREMENTS}-requirements.txt" - - "travis_retry pip install -e .[all]" - -script: - - pip freeze - - "./run-tests.sh" - -after_success: - - coveralls - -deploy: - provider: pypi - user: inveniosoftware - password: - secure: "IJJYsQoKk4FAgZkrUjpVFyg8jQc1Z3WdST4w1nRUZAsZCvFUKfdHFb+coz4E635mY9aZRLOz1v4mRmX2nV3X6339nLaqs1yEGxyVsPLQt80zxyH81TBBAvvG9aFQ/ZtEs1p4rLDqUQjz8UnzjudJ1UuZqTNPHBiwg/cQWo5ZTVDj851XJEmACBvUFlupxLk6b/JoHp0xFcE3fmJlNcYic/gRkG2GiTchIgjw095dhd1Kx6j9XP/rFBdbsxS5GGoxhEjELPfwq3mzdscqQ5NS9v/3delMrnjCLfOdac8mI5Q1jGYOyqrZW2/tZquK9CNZ/wd9FVCcz+/zIV34OVHdriAWiF/G66VFh90v23SKnjyWthCukyYuXjPI/GaViwYWDczCGqB6mHOpuI9YU207W2Uaq2A/NFK12hCTR6/y55d570HHoR2K7f4u4ZgwOcMMYNU7Ii/MqKv7MtyYtKCBnHzq1nI9BWQUUN9z6wlvFHArX9Y3ygGw/jNMqSuUykbxOq9dfjnv6eFl3ZyF5y8XfkywdygIYTBEJBUZZxQznkPpssnnJbyHTBitkJTffwtHwgZZwBZgxR4X8rde8y1OPk8nNHHOtnkwDKuRoLCD5vnye5vEhg6xmMujsNlE2PwNZbYQ7gcd4SH9WwWljEV9W6F5Lc9+vOdJoD/3MGGXYgY=" - distributions: "compile_catalog sdist bdist_wheel" - on: - tags: true - python: "3.6" - repo: inveniosoftware/invenio-pidstore - condition: $DEPLOY = true diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 8c4fce8..8af7d7a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -120,6 +120,6 @@ Before you submit a pull request, check that it meets these guidelines: 1. The pull request should include tests and must not decrease test coverage. 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring. -3. The pull request should work for Python 2.7, 3.3, 3.4 and 3.5. Check - https://travis-ci.org/inveniosoftware/invenio-pidstore/pull_requests +3. The pull request should work for Python 3.6, 3.7, 3.8 and 3.9. Check + https://github.com/inveniosoftware/invenio-pidstore/actions?query=event%3Apull_request and make sure that the tests pass for all supported Python versions. diff --git a/MANIFEST.in b/MANIFEST.in index ad1f3af..c55a7d6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -29,3 +29,4 @@ recursive-include invenio_pidstore *.po recursive-include invenio_pidstore *.pot recursive-include invenio_pidstore *.py recursive-include tests *.py +recursive-include .github/workflows *.yml diff --git a/README.rst b/README.rst index 669e5d0..9c3ce2a 100644 --- a/README.rst +++ b/README.rst @@ -12,8 +12,8 @@ .. image:: https://img.shields.io/github/license/inveniosoftware/invenio-pidstore.svg :target: https://github.com/inveniosoftware/invenio-pidstore/blob/master/LICENSE -.. image:: https://img.shields.io/travis/inveniosoftware/invenio-pidstore.svg - :target: https://travis-ci.org/inveniosoftware/invenio-pidstore +.. image:: https://github.com/inveniosoftware/invenio-pidstore/workflows/CI/badge.svg + :target: https://github.com/inveniosoftware/invenio-pidstore/actions?query=workflow%3ACI .. image:: https://img.shields.io/coveralls/inveniosoftware/invenio-pidstore.svg :target: https://coveralls.io/r/inveniosoftware/invenio-pidstore diff --git a/pytest.ini b/pytest.ini index 0161707..d2d0895 100644 --- a/pytest.ini +++ b/pytest.ini @@ -8,6 +8,5 @@ [pytest] doctest_optionflags = DONT_ACCEPT_TRUE_FOR_1 ELLIPSIS IGNORE_EXCEPTION_DETAIL -pep8ignore = docs/conf.py ALL -addopts = --pep8 --doctest-glob="*.rst" --doctest-modules --cov=invenio_pidstore --cov-report=term-missing +addopts = --isort --pydocstyle --pycodestyle --doctest-glob="*.rst" --doctest-modules --cov=invenio_pidstore --cov-report=term-missing testpaths = docs tests invenio_pidstore diff --git a/run-tests.sh b/run-tests.sh index 820c122..952c879 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -2,14 +2,28 @@ # -*- coding: utf-8 -*- # # This file is part of Invenio. -# Copyright (C) 2015-2019 CERN. -# Copyright (C) 2019 Northwestern University. +# Copyright (C) 2020 CERN. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. -pydocstyle invenio_pidstore tests docs && \ -isort invenio_pidstore tests --check-only --diff && \ -check-manifest --ignore ".travis-*" && \ -sphinx-build -qnNW docs docs/_build/html && \ -python setup.py test +# Quit on errors +set -o errexit + +# Quit on unbound symbols +set -o nounset + +# Always bring down docker services +function cleanup() { + eval "$(docker-services-cli down --env)" +} +trap cleanup EXIT + + +python -m check_manifest --ignore ".*-requirements.txt" +python -m sphinx.cmd.build -qnNW docs docs/_build/html +eval "$(docker-services-cli up --db ${DB:-postgresql} --cache ${CACHE:-redis} --env)" +python -m pytest +tests_exit_code=$? +python -m sphinx.cmd.build -qnNW -b doctest docs docs/_build/doctest +exit "$tests_exit_code" diff --git a/setup.py b/setup.py index 0c82835..28a64ee 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,6 @@ """Invenio module that stores and registers persistent identifiers.""" import os - from setuptools import find_packages, setup readme = open('README.rst').read() @@ -20,9 +19,9 @@ 'Flask-Menu>=0.5.1', 'invenio-admin>=1.2.0', 'invenio-access>=1.0.0', - 'invenio-accounts>=1.0.0', + 'invenio-accounts>=1.4.0', 'mock>=3.0.0', - 'pytest-invenio<=1.3.2', + 'pytest-invenio>=1.4.0', 'SQLAlchemy-Continuum>=1.2.1', ] @@ -43,7 +42,7 @@ 'invenio-db>=1.0.0', ], 'docs': [ - 'Sphinx>=1.8.5', + 'Sphinx>=3', ], 'tests': tests_require, } @@ -63,7 +62,7 @@ install_requires = [ 'base32-lib>=1.0.1', 'enum34>=1.1.6;python_version<"3.4"', - 'invenio-base>=1.2.2', + 'invenio-base>=1.2.3', 'invenio-i18n>=1.2.0', ] From 2dca151837740445d15919135b99bbb5d42c2953 Mon Sep 17 00:00:00 2001 From: Parth Shandilya Date: Wed, 9 Dec 2020 17:05:48 +0100 Subject: [PATCH 11/25] tests: fixed Pycodestyle and isort errors --- docs/conf.py | 132 +++++++++++++++++---------------- examples/app.py | 1 - setup.cfg | 3 + tests/conftest.py | 1 + tests/test_invenio_pidstore.py | 8 +- tests/test_resolver.py | 4 +- 6 files changed, 79 insertions(+), 70 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index f616fe7..1daf154 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,9 +11,8 @@ from __future__ import print_function import os -import sys - import sphinx.environment +import sys # Plug example application into module path sys.path.append('examples') @@ -21,7 +20,7 @@ # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Do not warn on external images. suppress_warnings = ['image.nonlocal_uri'] @@ -46,7 +45,7 @@ source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' @@ -81,9 +80,9 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -91,27 +90,27 @@ # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -139,44 +138,44 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] +# html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { @@ -191,47 +190,47 @@ # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'invenio-pidstore_namedoc' @@ -239,46 +238,48 @@ # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', + # Latex figure (float) alignment + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'invenio-pidstore.tex', u'invenio-pidstore Documentation', - u'CERN', 'manual'), + ( + master_doc, + 'invenio-pidstore.tex', + u'invenio-pidstore Documentation', + u'CERN', + 'manual', + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- @@ -286,12 +287,11 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'invenio-pidstore', u'invenio-pidstore Documentation', - [author], 1) + (master_doc, 'invenio-pidstore', u'invenio-pidstore Documentation', [author], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -300,22 +300,28 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'invenio-pidstore', u'Invenio-PIDStore Documentation', - author, 'invenio-pidstore', 'Invenio module that stores and registers persistent identifiers.', - 'Miscellaneous'), + ( + master_doc, + 'invenio-pidstore', + u'Invenio-PIDStore Documentation', + author, + 'invenio-pidstore', + 'Invenio module that stores and registers persistent identifiers.', + 'Miscellaneous', + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. diff --git a/examples/app.py b/examples/app.py index 6d97a57..f1cbb81 100644 --- a/examples/app.py +++ b/examples/app.py @@ -53,7 +53,6 @@ from __future__ import absolute_import, print_function import os - from flask import Flask from flask_babelex import Babel from flask_menu import Menu diff --git a/setup.cfg b/setup.cfg index 9eac762..93e3ddd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,3 +37,6 @@ output-dir = invenio_pidstore/translations/ [update_catalog] input-file = invenio_pidstore/translations/messages.pot output-dir = invenio_pidstore/translations/ + +[pycodestyle] +ignore = E501 diff --git a/tests/conftest.py b/tests/conftest.py index f51f1ca..e0ee52a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,6 +47,7 @@ def app(request): def db(app): """Database fixture.""" from invenio_db import db as db_ + if not database_exists(str(db_.engine.url)): create_database(str(db_.engine.url)) db_.create_all() diff --git a/tests/test_invenio_pidstore.py b/tests/test_invenio_pidstore.py index a0ff69a..105c810 100644 --- a/tests/test_invenio_pidstore.py +++ b/tests/test_invenio_pidstore.py @@ -267,7 +267,7 @@ def test_delete(logger, app, db): assert PersistentIdentifier.query.count() == count assert logger.info.call_args[0][0] == "Deleted PID (removed)." - pid = PersistentIdentifier.create('rec', str(i+1)) + pid = PersistentIdentifier.create('rec', str(i + 1)) with patch('invenio_pidstore.models.db.session.begin_nested') as mock: mock.side_effect = SQLAlchemyError() pytest.raises(SQLAlchemyError, pid.delete) @@ -290,9 +290,9 @@ def test_redirect(logger, app, db): # Can't redirect these statuses i = 10 for s in [PIDStatus.NEW, PIDStatus.RESERVED, PIDStatus.DELETED, ]: - pid = PersistentIdentifier.create('rec', str(i), status=s) - i += 1 - pytest.raises(PIDInvalidAction, pid.redirect, pid1) + pid = PersistentIdentifier.create('rec', str(i), status=s) + i += 1 + pytest.raises(PIDInvalidAction, pid.redirect, pid1) pid = PersistentIdentifier.create( 'rec', str(i), status=PIDStatus.REGISTERED) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 6ed848c..559a8dc 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -140,8 +140,8 @@ def test_resolver_not_registered_only(app, db): rec_a = uuid.uuid4() # Create pids for each status with and without object for idx, s in enumerate(status, 1): - PersistentIdentifier.create('recid', idx*2 - 1, status=s) - PersistentIdentifier.create('recid', idx*2, status=s, + PersistentIdentifier.create('recid', idx * 2 - 1, status=s) + PersistentIdentifier.create('recid', idx * 2, status=s, object_type='rec', object_uuid=rec_a) db.session.commit() From a3d4adb2f0b7aee250cca680990b9b52cab58bea Mon Sep 17 00:00:00 2001 From: Lars Holm Nielsen Date: Mon, 18 Jan 2021 17:22:18 +0100 Subject: [PATCH 12/25] providers: api consistency * The create() method takes kwargs and passes them to __init__, but __init__ doesn't take kwargs by default, meaning it's hard to allow providers to be exchanged. --- invenio_pidstore/providers/base.py | 2 +- invenio_pidstore/providers/datacite.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/invenio_pidstore/providers/base.py b/invenio_pidstore/providers/base.py index 17308e4..7b3e488 100644 --- a/invenio_pidstore/providers/base.py +++ b/invenio_pidstore/providers/base.py @@ -71,7 +71,7 @@ def get(cls, pid_value, pid_type=None, **kwargs): pid_provider=cls.pid_provider), **kwargs) - def __init__(self, pid): + def __init__(self, pid, **kwargs): """Initialize provider using persistent identifier. :param pid: A :class:`invenio_pidstore.models.PersistentIdentifier` diff --git a/invenio_pidstore/providers/datacite.py b/invenio_pidstore/providers/datacite.py index 271d562..122bfe5 100644 --- a/invenio_pidstore/providers/datacite.py +++ b/invenio_pidstore/providers/datacite.py @@ -50,7 +50,7 @@ def create(cls, pid_value, **kwargs): return super(DataCiteProvider, cls).create( pid_value=pid_value, **kwargs) - def __init__(self, pid, client=None): + def __init__(self, pid, client=None, **kwargs): """Initialize provider. To use the default client, just configure the following variables: From 8f6e99787da5a07d38712f7eb16146608304cdb9 Mon Sep 17 00:00:00 2001 From: Lars Holm Nielsen Date: Tue, 19 Jan 2021 16:02:42 +0100 Subject: [PATCH 13/25] release: v1.2.2 --- CHANGES.rst | 7 +++++++ invenio_pidstore/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6689c77..abd1c1a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,13 @@ Changes ======= +Version 1.2.2 (released 2021-01-19) + +- Fix a consistency issue in the providers API where the create() method takes + kwargs and passes them to __init__, but __init__ doesn't take kwargs by + default. This made it difficult to exchange providers. Now __init__ takes + kwargs by default. + Version 1.2.1 (released 2020-07-22) - Support returning NEW and RESERVED PIDs by setting the `registered_only` flag. diff --git a/invenio_pidstore/version.py b/invenio_pidstore/version.py index f4e43c5..476a9bd 100644 --- a/invenio_pidstore/version.py +++ b/invenio_pidstore/version.py @@ -14,4 +14,4 @@ from __future__ import absolute_import, print_function -__version__ = '1.2.1' +__version__ = '1.2.2' From 15536273da92e9fdc3d34406b4ff797971ff5b6c Mon Sep 17 00:00:00 2001 From: Peter Weber Date: Thu, 13 Jan 2022 11:56:40 +0100 Subject: [PATCH 14/25] ext: replace pkg_resources * Replaces `pkg_resources` with `importlib_metadata` and `importlib_resources`. --- invenio_pidstore/ext.py | 20 +++++++++++--------- setup.py | 16 +++++++++------- tests/test_ext.py | 6 ++++-- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/invenio_pidstore/ext.py b/invenio_pidstore/ext.py index 4700cef..6398ed7 100644 --- a/invenio_pidstore/ext.py +++ b/invenio_pidstore/ext.py @@ -2,6 +2,7 @@ # # This file is part of Invenio. # Copyright (C) 2015-2018 CERN. +# Copyright (C) 2022 RERO. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -10,7 +11,8 @@ from __future__ import absolute_import, print_function -import pkg_resources +import importlib_metadata +import importlib_resources from . import config from .cli import pid as cmd @@ -52,8 +54,8 @@ def register_minter(self, name, minter): :param name: Minter name. :param minter: The new minter. """ - assert name not in self.minters - self.minters[name] = minter + if name not in self.minters: + self.minters[name] = minter def register_fetcher(self, name, fetcher): """Register a fetcher. @@ -61,15 +63,15 @@ def register_fetcher(self, name, fetcher): :param name: Fetcher name. :param fetcher: The new fetcher. """ - assert name not in self.fetchers - self.fetchers[name] = fetcher + if name not in self.fetchers: + self.fetchers[name] = fetcher def load_minters_entry_point_group(self, entry_point_group): """Load minters from an entry point group. :param entry_point_group: The entrypoint group. """ - for ep in pkg_resources.iter_entry_points(group=entry_point_group): + for ep in importlib_metadata.entry_points(group=entry_point_group): self.register_minter(ep.name, ep.load()) def load_fetchers_entry_point_group(self, entry_point_group): @@ -77,7 +79,7 @@ def load_fetchers_entry_point_group(self, entry_point_group): :param entry_point_group: The entrypoint group. """ - for ep in pkg_resources.iter_entry_points(group=entry_point_group): + for ep in importlib_metadata.entry_points(group=entry_point_group): self.register_fetcher(ep.name, ep.load()) @@ -137,11 +139,11 @@ def init_app(self, app, minters_entry_point_group=None, # Initialize admin object link endpoints. try: - pkg_resources.get_distribution('invenio-records') + importlib_metadata.version('invenio-records') app.config.setdefault('PIDSTORE_OBJECT_ENDPOINTS', dict( rec='recordmetadata.details_view', )) - except pkg_resources.DistributionNotFound: + except importlib_metadata.PackageNotFoundError: app.config.setdefault('PIDSTORE_OBJECT_ENDPOINTS', {}) # Register template filter diff --git a/setup.py b/setup.py index 28a64ee..0dfd52f 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ # This file is part of Invenio. # Copyright (C) 2015-2019 CERN. # Copyright (C) 2019 Northwestern University. +# Copyright (C) 2022 RERO. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -22,7 +23,7 @@ 'invenio-accounts>=1.4.0', 'mock>=3.0.0', 'pytest-invenio>=1.4.0', - 'SQLAlchemy-Continuum>=1.2.1', + 'SQLAlchemy-Continuum>=1.3.11', ] extras_require = { @@ -33,16 +34,16 @@ 'datacite>=0.1.0' ], 'mysql': [ - 'invenio-db[mysql]>=1.0.0', + 'invenio-db[mysql]>=1.0.9', ], 'postgresql': [ - 'invenio-db[postgresql]>=1.0.0', + 'invenio-db[postgresql]>=1.0.9', ], 'sqlite': [ - 'invenio-db>=1.0.0', + 'invenio-db>=1.0.9', ], 'docs': [ - 'Sphinx>=3', + 'Sphinx>=4.2.0', ], 'tests': tests_require, } @@ -61,8 +62,9 @@ install_requires = [ 'base32-lib>=1.0.1', - 'enum34>=1.1.6;python_version<"3.4"', - 'invenio-base>=1.2.3', + 'importlib_metadata>=4.4', + 'importlib_resources>=5.0', + 'invenio-base>=1.2.5', 'invenio-i18n>=1.2.0', ] diff --git a/tests/test_ext.py b/tests/test_ext.py index 8df24f8..8440188 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -2,6 +2,7 @@ # # This file is part of Invenio. # Copyright (C) 2015-2018 CERN. +# Copyright (C) 2022 RERO. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -51,8 +52,9 @@ def test_logger(): def test_invenio_records(): """Test extension initialization.""" app = Flask('testapp') - with patch('invenio_pidstore.ext.pkg_resources'): - InvenioPIDStore(app) + with patch('invenio_pidstore.ext.importlib_metadata'): + with patch('invenio_pidstore.ext.importlib_resources'): + InvenioPIDStore(app) assert app.config['PIDSTORE_OBJECT_ENDPOINTS'] From e58e0987e7bcc8d8b021f44a88b401b89b8ae8c8 Mon Sep 17 00:00:00 2001 From: Lars Holm Nielsen Date: Mon, 28 Feb 2022 17:10:12 +0100 Subject: [PATCH 15/25] release: v1.2.3 --- CHANGES.rst | 4 ++++ invenio_pidstore/version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index abd1c1a..c91e062 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,10 @@ Changes ======= +Version 1.2.3 (released 2022-02-28) + +- Replaces pkg_resources with importlib for entry points iteration. + Version 1.2.2 (released 2021-01-19) - Fix a consistency issue in the providers API where the create() method takes diff --git a/invenio_pidstore/version.py b/invenio_pidstore/version.py index 476a9bd..93b25f5 100644 --- a/invenio_pidstore/version.py +++ b/invenio_pidstore/version.py @@ -14,4 +14,4 @@ from __future__ import absolute_import, print_function -__version__ = '1.2.2' +__version__ = '1.2.3' From 5cfc0a6fc093b6fd37356b6308592ba3719d2472 Mon Sep 17 00:00:00 2001 From: Mojib Wali <44528277+mb-wali@users.noreply.github.com> Date: Fri, 27 May 2022 10:37:31 +0200 Subject: [PATCH 16/25] i18n: extract messages --- babel.ini | 1 - invenio_pidstore/translations/messages.pot | 36 +++++++++++----------- setup.cfg | 1 + 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/babel.ini b/babel.ini index e021ca1..57923d9 100644 --- a/babel.ini +++ b/babel.ini @@ -15,7 +15,6 @@ encoding = utf-8 [jinja2: **/templates/**.html] encoding = utf-8 -extensions = jinja2.ext.autoescape, jinja2.ext.with_ # Extraction from JavaScript files diff --git a/invenio_pidstore/translations/messages.pot b/invenio_pidstore/translations/messages.pot index 820f63f..5fa7e79 100644 --- a/invenio_pidstore/translations/messages.pot +++ b/invenio_pidstore/translations/messages.pot @@ -1,72 +1,72 @@ # Translations template for invenio-pidstore. -# Copyright (C) 2016 CERN +# Copyright (C) 2022 CERN # This file is distributed under the same license as the invenio-pidstore # project. -# FIRST AUTHOR , 2016. +# FIRST AUTHOR , 2022. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: invenio-pidstore 1.0.0b2\n" +"Project-Id-Version: invenio-pidstore 1.2.3\n" "Report-Msgid-Bugs-To: info@inveniosoftware.org\n" -"POT-Creation-Date: 2016-07-26 08:52+0000\n" +"POT-Creation-Date: 2022-05-27 09:48+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.3.4\n" +"Generated-By: Babel 2.10.1\n" -#: invenio_pidstore/admin.py:41 +#: invenio_pidstore/admin.py:33 msgid "View" msgstr "" -#: invenio_pidstore/admin.py:66 +#: invenio_pidstore/admin.py:58 msgid "PID Type" msgstr "" -#: invenio_pidstore/admin.py:67 +#: invenio_pidstore/admin.py:59 msgid "PID" msgstr "" -#: invenio_pidstore/admin.py:68 +#: invenio_pidstore/admin.py:60 msgid "Provider" msgstr "" -#: invenio_pidstore/admin.py:69 invenio_pidstore/admin.py:76 +#: invenio_pidstore/admin.py:61 invenio_pidstore/admin.py:68 msgid "Status" msgstr "" -#: invenio_pidstore/admin.py:70 +#: invenio_pidstore/admin.py:62 msgid "Object Type" msgstr "" -#: invenio_pidstore/admin.py:71 invenio_pidstore/admin.py:75 +#: invenio_pidstore/admin.py:63 invenio_pidstore/admin.py:67 msgid "Object UUID" msgstr "" -#: invenio_pidstore/admin.py:88 +#: invenio_pidstore/admin.py:80 msgid "Records" msgstr "" -#: invenio_pidstore/models.py:52 +#: invenio_pidstore/models.py:36 msgid "New" msgstr "" -#: invenio_pidstore/models.py:53 +#: invenio_pidstore/models.py:37 msgid "Reserved" msgstr "" -#: invenio_pidstore/models.py:54 +#: invenio_pidstore/models.py:38 msgid "Registered" msgstr "" -#: invenio_pidstore/models.py:55 +#: invenio_pidstore/models.py:39 msgid "Redirected" msgstr "" -#: invenio_pidstore/models.py:56 +#: invenio_pidstore/models.py:40 msgid "Deleted" msgstr "" diff --git a/setup.cfg b/setup.cfg index 93e3ddd..43031ec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ add_ignore = D401 [compile_catalog] directory = invenio_pidstore/translations/ +use-fuzzy = True [extract_messages] copyright_holder = CERN From 7bc5d6776f44366592068f7670ca4ccae7bf4e54 Mon Sep 17 00:00:00 2001 From: Mojib Wali <44528277+mb-wali@users.noreply.github.com> Date: Fri, 3 Jun 2022 11:58:35 +0200 Subject: [PATCH 17/25] i18n: adds german translations (#140) * i18n: adds german translations * sphinx: pin version --- .../translations/de/LC_MESSAGES/messages.po | 73 ++++++++++--------- setup.py | 2 +- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/invenio_pidstore/translations/de/LC_MESSAGES/messages.po b/invenio_pidstore/translations/de/LC_MESSAGES/messages.po index 4fe03f6..625d086 100644 --- a/invenio_pidstore/translations/de/LC_MESSAGES/messages.po +++ b/invenio_pidstore/translations/de/LC_MESSAGES/messages.po @@ -1,72 +1,79 @@ # Translations template for invenio-pidstore. -# Copyright (C) 2016, 2017 CERN +# Copyright (C) 2022 CERN # This file is distributed under the same license as the invenio-pidstore # project. -# FIRST AUTHOR , 2016. +# FIRST AUTHOR , 2022. +# +# Translators: +# Alizee Pace , 2016 +# Tibor Simko , 2016 +# Hermann Schranzhofer , 2021 +# chriz_uniba , 2022 # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: invenio-pidstore 1.0.0b2\n" +"Project-Id-Version: invenio-pidstore 1.2.3\n" "Report-Msgid-Bugs-To: info@inveniosoftware.org\n" -"POT-Creation-Date: 2016-08-17 11:42+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"POT-Creation-Date: 2022-05-27 09:48+0200\n" +"PO-Revision-Date: 2016-08-17 11:41+0000\n" +"Last-Translator: chriz_uniba , 2022\n" "Language-Team: German (https://www.transifex.com/inveniosoftware/teams/23537/de/)\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.3.4\n" +"Generated-By: Babel 2.10.1\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: invenio_pidstore/admin.py:45 +#: invenio_pidstore/admin.py:33 msgid "View" -msgstr "" +msgstr "Ansicht" -#: invenio_pidstore/admin.py:70 +#: invenio_pidstore/admin.py:58 msgid "PID Type" -msgstr "" +msgstr "PID Typ" -#: invenio_pidstore/admin.py:71 +#: invenio_pidstore/admin.py:59 msgid "PID" -msgstr "" +msgstr "PID" -#: invenio_pidstore/admin.py:72 +#: invenio_pidstore/admin.py:60 msgid "Provider" -msgstr "" +msgstr "AnbieterIn" -#: invenio_pidstore/admin.py:73 invenio_pidstore/admin.py:80 +#: invenio_pidstore/admin.py:61 invenio_pidstore/admin.py:68 msgid "Status" -msgstr "" +msgstr "Status" -#: invenio_pidstore/admin.py:74 +#: invenio_pidstore/admin.py:62 msgid "Object Type" -msgstr "" +msgstr "Objekttyp" -#: invenio_pidstore/admin.py:75 invenio_pidstore/admin.py:79 +#: invenio_pidstore/admin.py:63 invenio_pidstore/admin.py:67 msgid "Object UUID" -msgstr "" +msgstr "Objekt UUID" -#: invenio_pidstore/admin.py:92 +#: invenio_pidstore/admin.py:80 msgid "Records" -msgstr "" +msgstr "Einträge" -#: invenio_pidstore/models.py:52 +#: invenio_pidstore/models.py:36 msgid "New" -msgstr "" +msgstr "Neu" -#: invenio_pidstore/models.py:53 +#: invenio_pidstore/models.py:37 msgid "Reserved" -msgstr "" +msgstr "Reserviert" -#: invenio_pidstore/models.py:54 +#: invenio_pidstore/models.py:38 msgid "Registered" -msgstr "" +msgstr "Registriert" -#: invenio_pidstore/models.py:55 +#: invenio_pidstore/models.py:39 msgid "Redirected" -msgstr "" +msgstr "Umgeleitet" -#: invenio_pidstore/models.py:56 +#: invenio_pidstore/models.py:40 msgid "Deleted" -msgstr "" +msgstr "Gelöscht" diff --git a/setup.py b/setup.py index 0dfd52f..dd601dc 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ 'invenio-db>=1.0.9', ], 'docs': [ - 'Sphinx>=4.2.0', + 'Sphinx>=4.2.0,<5.0.0', ], 'tests': tests_require, } From 97b475218076c6503ec48b9ea5b674f7b6f3d564 Mon Sep 17 00:00:00 2001 From: Christoph Ladurner Date: Wed, 8 Jun 2022 23:17:40 +0200 Subject: [PATCH 18/25] migrate setup.py to setup.cfg --- .github/workflows/pypi-publish.yml | 2 +- .github/workflows/tests.yml | 10 +-- docs/conf.py | 11 +-- invenio_pidstore/__init__.py | 3 +- invenio_pidstore/version.py | 17 ---- pyproject.toml | 4 + setup.cfg | 60 +++++++++++++- setup.py | 127 +---------------------------- 8 files changed, 75 insertions(+), 159 deletions(-) delete mode 100644 invenio_pidstore/version.py create mode 100644 pyproject.toml diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 556367a..2657cbc 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -20,7 +20,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel + pip install setuptools wheel babel - name: Build package # Remove `compile_catalog` if the package has no translations. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a0ae62..7951cf8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ jobs: python-version: [3.6, 3.7, 3.8, 3.9] requirements-level: [min, pypi] cache-service: [redis] - db-service: [postgresql9, postgresql11, mysql5, mysql8] + db-service: [postgresql9, postgresql13, mysql5, mysql8] exclude: - python-version: 3.8 requirements-level: min @@ -39,7 +39,7 @@ jobs: - python-version: 3.9 requirements-level: min - - db-service: postgresql11 + - db-service: postgresql13 python-version: 3.6 - db-service: mysql8 @@ -49,7 +49,7 @@ jobs: - db-service: postgresql9 DB_EXTRAS: "postgresql" - - db-service: postgresql11 + - db-service: postgresql13 DB_EXTRAS: "postgresql" - db-service: mysql5 @@ -61,7 +61,7 @@ jobs: env: CACHE: ${{ matrix.cache-service }} DB: ${{ matrix.db-service }} - EXTRAS: all,${{ matrix.DB_EXTRAS }} + EXTRAS: tests steps: - name: Checkout uses: actions/checkout@v2 @@ -73,7 +73,7 @@ jobs: - name: Generate dependencies run: | - python -m pip install --upgrade pip setuptools py wheel requirements-builder + pip install wheel requirements-builder requirements-builder -e "$EXTRAS" --level=${{ matrix.requirements-level }} setup.py > .${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt - name: Cache pip diff --git a/docs/conf.py b/docs/conf.py index 1daf154..7fd90b7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,7 +10,7 @@ from __future__ import print_function -import os +from invenio_pidstore import __version__ import sphinx.environment import sys @@ -61,15 +61,8 @@ # # The short X.Y version. -# Get the version string. Cannot be done with import! -g = {} -with open(os.path.join(os.path.dirname(__file__), '..', 'invenio_pidstore', - 'version.py'), 'rt') as fp: - exec(fp.read(), g) - version = g['__version__'] - # The full version, including alpha/beta/rc tags. -release = version +release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/invenio_pidstore/__init__.py b/invenio_pidstore/__init__.py index 348a2fc..72cf053 100644 --- a/invenio_pidstore/__init__.py +++ b/invenio_pidstore/__init__.py @@ -367,6 +367,7 @@ from .ext import InvenioPIDStore from .proxies import current_pidstore -from .version import __version__ + +__version__ = '1.2.3' __all__ = ('__version__', 'InvenioPIDStore', 'current_pidstore') diff --git a/invenio_pidstore/version.py b/invenio_pidstore/version.py deleted file mode 100644 index 93b25f5..0000000 --- a/invenio_pidstore/version.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Invenio. -# Copyright (C) 2015-2020 CERN. -# -# Invenio is free software; you can redistribute it and/or modify it -# under the terms of the MIT License; see LICENSE file for more details. - -"""Version information for Invenio-PIDStore. - -This file is imported by ``invenio_pidstore.__init__``, -and parsed by ``setup.py``. -""" - -from __future__ import absolute_import, print_function - -__version__ = '1.2.3' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f00e778 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,4 @@ +[build-system] +requires = ["setuptools", "wheel", "babel>2.8"] +build-backend = "setuptools.build_meta" + diff --git a/setup.cfg b/setup.cfg index 43031ec..0cbd92b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,12 +2,68 @@ # # This file is part of Invenio. # Copyright (C) 2015-2018 CERN. +# Copyright (C) 2022 Graz University of Technology. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. -[aliases] -test = pytest +[metadata] +name = invenio-pidstore +version = attr: invenio_pidstore.__version__ +description = "Invenio module that stores and registers persistent identifiers." +long_description = file: README.rst, CHANGES.rst +keywords = invenio identifier DOI +license = MIT +author = CERN +author_email = info@inveniosoftware.org +platforms = any +url = https://github.com/inveniosoftware/invenio-pidstore +classifiers = + Development Status :: 5 - Production/Stable + +[options] +include_package_data = True +packages = find: +python_requires = >=3.6 +zip_safe = False +install_requires = + base32-lib>=1.0.1 + importlib_metadata>=4.4 + importlib_resources>=5.0 + invenio-base>=1.2.5 + invenio-i18n>=1.2.0 + +[options.extras_require] +tests = + Flask-Menu>=0.5.1 + invenio-admin>=1.2.0 + invenio-access>=1.0.0 + invenio-accounts>=1.4.0 + mock>=3.0.0 + pytest-invenio>=1.4.0 + SQLAlchemy-Continuum>=1.3.11 + invenio-admin>=1.2.0 + datacite>=0.1.0 + Sphinx>=4.2.0,<5.0.0 + invenio-db[mysql,postgresql,versioning]>=1.0.9,<2.0.0 + +[options.entry_points] +invenio_db.alembic = + invenio_pidstore = invenio_pidstore:alembic +invenio_db.models = + invenio_pidstore = invenio_pidstore.models +invenio_base.apps = + invenio_pidstore = invenio_pidstore:InvenioPIDStore +invenio_base.api_apps = + invenio_pidstore = invenio_pidstore:InvenioPIDStore +invenio_pidstore.minters = + recid = invenio_pidstore.minters:recid_minter + recid_v2 = invenio_pidstore.minters:recid_minter_v2 +invenio_pidstore.fetchers = + recid = invenio_pidstore.fetchers:recid_fetcher + recid_v2 = invenio_pidstore.fetchers:recid_fetcher_v2 +invenio_admin.views = + invenio_pidstore_pid = invenio_pidstore.admin:pid_adminview [build_sphinx] source-dir = docs/ diff --git a/setup.py b/setup.py index dd601dc..7ce4002 100644 --- a/setup.py +++ b/setup.py @@ -4,134 +4,13 @@ # Copyright (C) 2015-2019 CERN. # Copyright (C) 2019 Northwestern University. # Copyright (C) 2022 RERO. +# Copyright (C) 2022 Graz University of Technology. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. """Invenio module that stores and registers persistent identifiers.""" -import os -from setuptools import find_packages, setup +from setuptools import setup -readme = open('README.rst').read() -history = open('CHANGES.rst').read() - -tests_require = [ - 'Flask-Menu>=0.5.1', - 'invenio-admin>=1.2.0', - 'invenio-access>=1.0.0', - 'invenio-accounts>=1.4.0', - 'mock>=3.0.0', - 'pytest-invenio>=1.4.0', - 'SQLAlchemy-Continuum>=1.3.11', -] - -extras_require = { - 'admin': [ - 'invenio-admin>=1.2.0', - ], - 'datacite': [ - 'datacite>=0.1.0' - ], - 'mysql': [ - 'invenio-db[mysql]>=1.0.9', - ], - 'postgresql': [ - 'invenio-db[postgresql]>=1.0.9', - ], - 'sqlite': [ - 'invenio-db>=1.0.9', - ], - 'docs': [ - 'Sphinx>=4.2.0,<5.0.0', - ], - 'tests': tests_require, -} - -extras_require['all'] = [] -for name, reqs in extras_require.items(): - if name in ('mysql', 'postgresql', 'sqlite') \ - or name.startswith(':'): - continue - extras_require['all'].extend(reqs) - -setup_requires = [ - 'Babel>=1.3', - 'pytest-runner>=2.7.1', -] - -install_requires = [ - 'base32-lib>=1.0.1', - 'importlib_metadata>=4.4', - 'importlib_resources>=5.0', - 'invenio-base>=1.2.5', - 'invenio-i18n>=1.2.0', -] - -packages = find_packages() - -# Get the version string. Cannot be done with import! -g = {} -with open(os.path.join('invenio_pidstore', 'version.py'), 'rt') as fp: - exec(fp.read(), g) - version = g['__version__'] - -setup( - name='invenio-pidstore', - version=version, - description=__doc__, - long_description=readme + '\n\n' + history, - keywords='invenio identifier DOI', - license='MIT', - author='CERN', - author_email='info@inveniosoftware.org', - url='https://github.com/inveniosoftware/invenio-pidstore', - packages=packages, - zip_safe=False, - include_package_data=True, - platforms='any', - entry_points={ - 'invenio_db.alembic': [ - 'invenio_pidstore = invenio_pidstore:alembic', - ], - 'invenio_db.models': [ - 'invenio_pidstore = invenio_pidstore.models', - ], - 'invenio_base.apps': [ - 'invenio_pidstore = invenio_pidstore:InvenioPIDStore', - ], - 'invenio_base.api_apps': [ - 'invenio_pidstore = invenio_pidstore:InvenioPIDStore', - ], - 'invenio_pidstore.minters': [ - 'recid = invenio_pidstore.minters:recid_minter', - 'recid_v2 = invenio_pidstore.minters:recid_minter_v2', - ], - 'invenio_pidstore.fetchers': [ - 'recid = invenio_pidstore.fetchers:recid_fetcher', - 'recid_v2 = invenio_pidstore.fetchers:recid_fetcher_v2', - ], - 'invenio_admin.views': [ - 'invenio_pidstore_pid = invenio_pidstore.admin:pid_adminview', - ] - }, - extras_require=extras_require, - install_requires=install_requires, - setup_requires=setup_requires, - tests_require=tests_require, - classifiers=[ - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: Implementation :: CPython', - 'Development Status :: 5 - Production/Stable', - ], -) +setup() From 6df72b9bd9c1572234d645ae3719e9d83e3811a9 Mon Sep 17 00:00:00 2001 From: Christoph Ladurner Date: Wed, 8 Jun 2022 23:17:48 +0200 Subject: [PATCH 19/25] migrate to use black as opinionated auto formater --- .editorconfig | 11 - docs/conf.py | 96 +++--- examples/app.py | 11 +- invenio_pidstore/__init__.py | 4 +- invenio_pidstore/admin.py | 56 ++-- .../999c62899c20_create_pidstore_tables.py | 77 ++--- .../f615cee99600_create_pidstore_branch.py | 6 +- invenio_pidstore/cli.py | 62 ++-- invenio_pidstore/config.py | 10 +- invenio_pidstore/ext.py | 52 +-- invenio_pidstore/fetchers.py | 9 +- invenio_pidstore/minters.py | 10 +- invenio_pidstore/models.py | 169 +++++----- invenio_pidstore/providers/base.py | 19 +- invenio_pidstore/providers/datacite.py | 65 ++-- invenio_pidstore/providers/recordid.py | 13 +- invenio_pidstore/providers/recordid_v2.py | 31 +- invenio_pidstore/proxies.py | 3 +- invenio_pidstore/resolver.py | 13 +- pytest.ini | 12 - setup.cfg | 10 +- tests/conftest.py | 8 +- tests/test_admin.py | 48 +-- tests/test_cli.py | 287 ++++++++++------ tests/test_ext.py | 47 +-- tests/test_fetchers.py | 6 +- tests/test_invenio_pidstore.py | 313 ++++++++++-------- tests/test_minters.py | 15 +- tests/test_providers.py | 162 +++++---- tests/test_resolver.py | 126 +++---- 30 files changed, 952 insertions(+), 799 deletions(-) delete mode 100644 pytest.ini diff --git a/.editorconfig b/.editorconfig index 96f691f..c9ecc42 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,17 +15,6 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -# Python files -[*.py] -indent_size = 4 -# isort plugin configuration -known_standard_library = enum -known_first_party = invenio_pidstore -known_third_party = invenio_db,datacite -multi_line_output = 2 -default_section = THIRDPARTY -skip = .eggs - # RST files (used by sphinx) [*.rst] indent_size = 4 diff --git a/docs/conf.py b/docs/conf.py index 7fd90b7..3b330b0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,12 +10,14 @@ from __future__ import print_function -from invenio_pidstore import __version__ -import sphinx.environment import sys +import sphinx.environment + +from invenio_pidstore import __version__ + # Plug example application into module path -sys.path.append('examples') +sys.path.append("examples") # -- General configuration ------------------------------------------------ @@ -23,37 +25,37 @@ # needs_sphinx = '1.0' # Do not warn on external images. -suppress_warnings = ['image.nonlocal_uri'] +suppress_warnings = ["image.nonlocal_uri"] # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Invenio-PIDStore' -copyright = u'2017, CERN' -author = u'CERN' +project = "Invenio-PIDStore" +copyright = "2017, CERN" +author = "CERN" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -97,7 +99,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -110,19 +112,19 @@ # -- Options for HTML output ---------------------------------------------- -html_theme = 'alabaster' +html_theme = "alabaster" html_theme_options = { - 'description': 'Invenio module that stores and registers persistent identifiers.', - 'github_user': 'inveniosoftware', - 'github_repo': 'invenio-pidstore', - 'github_button': False, - 'github_banner': True, - 'show_powered_by': False, - 'extra_nav_links': { - 'invenio-pidstore@GitHub': 'https://github.com/inveniosoftware/invenio-pidstore', - 'invenio-pidstore@PyPI': 'https://pypi.python.org/pypi/invenio-pidstore/', - } + "description": "Invenio module that stores and registers persistent identifiers.", + "github_user": "inveniosoftware", + "github_repo": "invenio-pidstore", + "github_button": False, + "github_banner": True, + "show_powered_by": False, + "extra_nav_links": { + "invenio-pidstore@GitHub": "https://github.com/inveniosoftware/invenio-pidstore", + "invenio-pidstore@PyPI": "https://pypi.python.org/pypi/invenio-pidstore/", + }, } # The theme to use for HTML and HTML Help pages. See the documentation for @@ -172,12 +174,12 @@ # Custom sidebar templates, maps document names to template names. html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', - 'searchbox.html', - 'donate.html', + "**": [ + "about.html", + "navigation.html", + "relations.html", + "searchbox.html", + "donate.html", ] } @@ -226,7 +228,7 @@ # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'invenio-pidstore_namedoc' +htmlhelp_basename = "invenio-pidstore_namedoc" # -- Options for LaTeX output --------------------------------------------- @@ -247,10 +249,10 @@ latex_documents = [ ( master_doc, - 'invenio-pidstore.tex', - u'invenio-pidstore Documentation', - u'CERN', - 'manual', + "invenio-pidstore.tex", + "invenio-pidstore Documentation", + "CERN", + "manual", ), ] @@ -280,7 +282,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'invenio-pidstore', u'invenio-pidstore Documentation', [author], 1) + (master_doc, "invenio-pidstore", "invenio-pidstore Documentation", [author], 1) ] # If true, show URL addresses after external links. @@ -295,12 +297,12 @@ texinfo_documents = [ ( master_doc, - 'invenio-pidstore', - u'Invenio-PIDStore Documentation', + "invenio-pidstore", + "Invenio-PIDStore Documentation", author, - 'invenio-pidstore', - 'Invenio module that stores and registers persistent identifiers.', - 'Miscellaneous', + "invenio-pidstore", + "Invenio module that stores and registers persistent identifiers.", + "Miscellaneous", ), ] @@ -319,9 +321,9 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'https://docs.python.org/': None, - 'https://datacite.readthedocs.io/en/latest/': None + "https://docs.python.org/": None, + "https://datacite.readthedocs.io/en/latest/": None, } # Autodoc configuraton. -autoclass_content = 'both' +autoclass_content = "both" diff --git a/examples/app.py b/examples/app.py index f1cbb81..64315a1 100644 --- a/examples/app.py +++ b/examples/app.py @@ -53,6 +53,7 @@ from __future__ import absolute_import, print_function import os + from flask import Flask from flask_babelex import Babel from flask_menu import Menu @@ -69,14 +70,16 @@ app = Flask(__name__) app.config.update( DB_VERSIONING_USER_MODEL=None, - SECRET_KEY='test_key', - SECURITY_PASSWORD_HASH='pbkdf2_sha512', + SECRET_KEY="test_key", + SECURITY_PASSWORD_HASH="pbkdf2_sha512", SECURITY_PASSWORD_SALT="CHANGE_ME_ALSO", SECURITY_PASSWORD_SCHEMES=[ - 'pbkdf2_sha512', 'sha512_crypt', 'invenio_aes_encrypted_email' + "pbkdf2_sha512", + "sha512_crypt", + "invenio_aes_encrypted_email", ], SQLALCHEMY_DATABASE_URI=os.environ.get( - 'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db' + "SQLALCHEMY_DATABASE_URI", "sqlite:///test.db" ), WTF_CSRF_ENABLED=False, ) diff --git a/invenio_pidstore/__init__.py b/invenio_pidstore/__init__.py index 72cf053..50b5d52 100644 --- a/invenio_pidstore/__init__.py +++ b/invenio_pidstore/__init__.py @@ -368,6 +368,6 @@ from .ext import InvenioPIDStore from .proxies import current_pidstore -__version__ = '1.2.3' +__version__ = "1.2.3" -__all__ = ('__version__', 'InvenioPIDStore', 'current_pidstore') +__all__ = ("__version__", "InvenioPIDStore", "current_pidstore") diff --git a/invenio_pidstore/admin.py b/invenio_pidstore/admin.py index f6fbf7d..c4f6efa 100644 --- a/invenio_pidstore/admin.py +++ b/invenio_pidstore/admin.py @@ -9,6 +9,7 @@ """Admin model views for PersistentIdentifier.""" import uuid + from flask import current_app, url_for from flask_admin.contrib.sqla import ModelView from flask_admin.contrib.sqla.filters import FilterEqual @@ -24,14 +25,15 @@ def _(x): def object_formatter(v, c, m, p): """Format object view link.""" - endpoint = current_app.config['PIDSTORE_OBJECT_ENDPOINTS'].get( - m.object_type) + endpoint = current_app.config["PIDSTORE_OBJECT_ENDPOINTS"].get(m.object_type) if endpoint and m.object_uuid: - return Markup('{1}'.format( - url_for(endpoint, id=m.object_uuid), - _('View'))) - return '' + return Markup( + '{1}'.format( + url_for(endpoint, id=m.object_uuid), _("View") + ) + ) + return "" class FilterUUID(FilterEqual): @@ -51,25 +53,36 @@ class PersistentIdentifierModelView(ModelView): can_view_details = True column_display_all_relations = True column_list = ( - 'pid_type', 'pid_value', 'status', 'object_type', - 'object_uuid', 'created', 'updated', 'object', + "pid_type", + "pid_value", + "status", + "object_type", + "object_uuid", + "created", + "updated", + "object", ) column_labels = dict( - pid_type=_('PID Type'), - pid_value=_('PID'), - pid_provider=_('Provider'), - status=_('Status'), - object_type=_('Object Type'), - object_uuid=_('Object UUID'), + pid_type=_("PID Type"), + pid_value=_("PID"), + pid_provider=_("Provider"), + status=_("Status"), + object_type=_("Object Type"), + object_uuid=_("Object UUID"), ) column_filters = ( - 'pid_type', 'pid_value', 'object_type', - FilterUUID(PersistentIdentifier.object_uuid, _('Object UUID')), - FilterEqual(PersistentIdentifier.status, _('Status'), - options=[(s.value, s.title) for s in PIDStatus]), + "pid_type", + "pid_value", + "object_type", + FilterUUID(PersistentIdentifier.object_uuid, _("Object UUID")), + FilterEqual( + PersistentIdentifier.status, + _("Status"), + options=[(s.value, s.title) for s in PIDStatus], + ), ) - column_searchable_list = ('pid_value', ) - column_default_sort = ('updated', True) + column_searchable_list = ("pid_value",) + column_default_sort = ("updated", True) column_formatters = dict(object=object_formatter) page_size = 25 @@ -77,4 +90,5 @@ class PersistentIdentifierModelView(ModelView): pid_adminview = dict( modelview=PersistentIdentifierModelView, model=PersistentIdentifier, - category=_('Records')) + category=_("Records"), +) diff --git a/invenio_pidstore/alembic/999c62899c20_create_pidstore_tables.py b/invenio_pidstore/alembic/999c62899c20_create_pidstore_tables.py index 3d822da..a95d1a0 100644 --- a/invenio_pidstore/alembic/999c62899c20_create_pidstore_tables.py +++ b/invenio_pidstore/alembic/999c62899c20_create_pidstore_tables.py @@ -13,8 +13,8 @@ from alembic import op # revision identifiers, used by Alembic. -revision = '999c62899c20' -down_revision = 'f615cee99600' +revision = "999c62899c20" +down_revision = "f615cee99600" branch_labels = () depends_on = None @@ -22,61 +22,48 @@ def upgrade(): """Upgrade database.""" op.create_table( - 'pidstore_pid', - sa.Column('created', sa.DateTime(), nullable=False), - sa.Column('updated', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('pid_type', sa.String(length=6), nullable=False), - sa.Column('pid_value', sa.String(length=255), nullable=False), - sa.Column('pid_provider', sa.String(length=8), nullable=True), - sa.Column('status', sa.CHAR(1), nullable=False), - sa.Column('object_type', sa.String(length=3), nullable=True), - sa.Column( - 'object_uuid', - sqlalchemy_utils.types.uuid.UUIDType(), - nullable=True - ), - sa.PrimaryKeyConstraint('id') + "pidstore_pid", + sa.Column("created", sa.DateTime(), nullable=False), + sa.Column("updated", sa.DateTime(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("pid_type", sa.String(length=6), nullable=False), + sa.Column("pid_value", sa.String(length=255), nullable=False), + sa.Column("pid_provider", sa.String(length=8), nullable=True), + sa.Column("status", sa.CHAR(1), nullable=False), + sa.Column("object_type", sa.String(length=3), nullable=True), + sa.Column("object_uuid", sqlalchemy_utils.types.uuid.UUIDType(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) op.create_index( - 'idx_object', 'pidstore_pid', ['object_type', 'object_uuid'], - unique=False + "idx_object", "pidstore_pid", ["object_type", "object_uuid"], unique=False ) - op.create_index('idx_status', 'pidstore_pid', ['status'], unique=False) + op.create_index("idx_status", "pidstore_pid", ["status"], unique=False) op.create_index( - 'uidx_type_pid', 'pidstore_pid', ['pid_type', 'pid_value'], - unique=True + "uidx_type_pid", "pidstore_pid", ["pid_type", "pid_value"], unique=True ) op.create_table( - 'pidstore_recid', - sa.Column('recid', sa.BigInteger(), nullable=False), - sa.PrimaryKeyConstraint('recid') + "pidstore_recid", + sa.Column("recid", sa.BigInteger(), nullable=False), + sa.PrimaryKeyConstraint("recid"), ) op.create_table( - 'pidstore_redirect', - sa.Column('created', sa.DateTime(), nullable=False), - sa.Column('updated', sa.DateTime(), nullable=False), - sa.Column( - 'id', - sqlalchemy_utils.types.uuid.UUIDType(), - nullable=False - ), - sa.Column('pid_id', sa.Integer(), nullable=False), + "pidstore_redirect", + sa.Column("created", sa.DateTime(), nullable=False), + sa.Column("updated", sa.DateTime(), nullable=False), + sa.Column("id", sqlalchemy_utils.types.uuid.UUIDType(), nullable=False), + sa.Column("pid_id", sa.Integer(), nullable=False), sa.ForeignKeyConstraint( - ['pid_id'], - [u'pidstore_pid.id'], - onupdate='CASCADE', - ondelete='RESTRICT' + ["pid_id"], ["pidstore_pid.id"], onupdate="CASCADE", ondelete="RESTRICT" ), - sa.PrimaryKeyConstraint('id') + sa.PrimaryKeyConstraint("id"), ) def downgrade(): """Downgrade database.""" - op.drop_table('pidstore_redirect') - op.drop_table('pidstore_recid') - op.drop_index('uidx_type_pid', table_name='pidstore_pid') - op.drop_index('idx_status', table_name='pidstore_pid') - op.drop_index('idx_object', table_name='pidstore_pid') - op.drop_table('pidstore_pid') + op.drop_table("pidstore_redirect") + op.drop_table("pidstore_recid") + op.drop_index("uidx_type_pid", table_name="pidstore_pid") + op.drop_index("idx_status", table_name="pidstore_pid") + op.drop_index("idx_object", table_name="pidstore_pid") + op.drop_table("pidstore_pid") diff --git a/invenio_pidstore/alembic/f615cee99600_create_pidstore_branch.py b/invenio_pidstore/alembic/f615cee99600_create_pidstore_branch.py index 76b5b91..500943a 100644 --- a/invenio_pidstore/alembic/f615cee99600_create_pidstore_branch.py +++ b/invenio_pidstore/alembic/f615cee99600_create_pidstore_branch.py @@ -12,10 +12,10 @@ from alembic import op # revision identifiers, used by Alembic. -revision = 'f615cee99600' +revision = "f615cee99600" down_revision = None -branch_labels = (u'invenio_pidstore',) -depends_on = 'dbdbc1b19cf2' +branch_labels = ("invenio_pidstore",) +depends_on = "dbdbc1b19cf2" def upgrade(): diff --git a/invenio_pidstore/cli.py b/invenio_pidstore/cli.py index bb5559d..4ea6394 100644 --- a/invenio_pidstore/cli.py +++ b/invenio_pidstore/cli.py @@ -26,9 +26,11 @@ def process_status(ctx, param, value): return None if not hasattr(PIDStatus, value): - raise click.BadParameter('Status needs to be one of {0}.'.format( - ', '.join([s.name for s in PIDStatus]) - )) + raise click.BadParameter( + "Status needs to be one of {0}.".format( + ", ".join([s.name for s in PIDStatus]) + ) + ) return getattr(PIDStatus, value) @@ -36,24 +38,25 @@ def process_status(ctx, param, value): # PIDStore management commands # + @click.group() def pid(): """PID-Store management commands.""" @pid.command() -@click.argument('pid_type') -@click.argument('pid_value') -@click.option('-s', '--status', default='NEW', callback=process_status) -@click.option('-t', '--type', 'object_type', default=None) -@click.option('-i', '--uuid', 'object_uuid', default=None) +@click.argument("pid_type") +@click.argument("pid_value") +@click.option("-s", "--status", default="NEW", callback=process_status) +@click.option("-t", "--type", "object_type", default=None) +@click.option("-i", "--uuid", "object_uuid", default=None) @with_appcontext def create(pid_type, pid_value, status, object_type, object_uuid): """Create new persistent identifier.""" from .models import PersistentIdentifier if bool(object_type) ^ bool(object_uuid): - raise click.BadParameter('Speficy both or any of --type and --uuid.') + raise click.BadParameter("Speficy both or any of --type and --uuid.") new_pid = PersistentIdentifier.create( pid_type, @@ -63,22 +66,21 @@ def create(pid_type, pid_value, status, object_type, object_uuid): object_uuid=object_uuid, ) db.session.commit() - click.echo( - '{0.pid_type} {0.pid_value} {0.pid_provider}'.format(new_pid) - ) + click.echo("{0.pid_type} {0.pid_value} {0.pid_provider}".format(new_pid)) @pid.command() -@click.argument('pid_type') -@click.argument('pid_value') -@click.option('-s', '--status', default=None, callback=process_status) -@click.option('-t', '--type', 'object_type', required=True) -@click.option('-i', '--uuid', 'object_uuid', required=True) -@click.option('--overwrite', is_flag=True, default=False) +@click.argument("pid_type") +@click.argument("pid_value") +@click.option("-s", "--status", default=None, callback=process_status) +@click.option("-t", "--type", "object_type", required=True) +@click.option("-i", "--uuid", "object_uuid", required=True) +@click.option("--overwrite", is_flag=True, default=False) @with_appcontext def assign(pid_type, pid_value, status, object_type, object_uuid, overwrite): """Assign persistent identifier.""" from .models import PersistentIdentifier + obj = PersistentIdentifier.get(pid_type, pid_value) if status is not None: obj.status = status @@ -88,8 +90,8 @@ def assign(pid_type, pid_value, status, object_type, object_uuid, overwrite): @pid.command() -@click.argument('pid_type') -@click.argument('pid_value') +@click.argument("pid_type") +@click.argument("pid_value") @with_appcontext def unassign(pid_type, pid_value): """Unassign persistent identifier.""" @@ -101,9 +103,9 @@ def unassign(pid_type, pid_value): click.echo(obj.status) -@pid.command('get') -@click.argument('pid_type') -@click.argument('pid_value') +@pid.command("get") +@click.argument("pid_type") +@click.argument("pid_value") @with_appcontext def get_object(pid_type, pid_value): """Get an object behind persistent identifier.""" @@ -111,13 +113,13 @@ def get_object(pid_type, pid_value): obj = PersistentIdentifier.get(pid_type, pid_value) if obj.has_object(): - click.echo('{0.object_type} {0.object_uuid} {0.status}'.format(obj)) + click.echo("{0.object_type} {0.object_uuid} {0.status}".format(obj)) -@pid.command('dereference') -@click.argument('object_type') -@click.argument('object_uuid') -@click.option('-s', '--status', default=None, callback=process_status) +@pid.command("dereference") +@click.argument("object_type") +@click.argument("object_uuid") +@click.option("-s", "--status", default=None, callback=process_status) @with_appcontext def dereference_object(object_type, object_uuid, status): """Show linked persistent identifier(s).""" @@ -130,6 +132,4 @@ def dereference_object(object_type, object_uuid, status): pids = pids.filter_by(status=status) for found_pid in pids.all(): - click.echo( - '{0.pid_type} {0.pid_value} {0.pid_provider}'.format(found_pid) - ) + click.echo("{0.pid_type} {0.pid_value} {0.pid_provider}".format(found_pid)) diff --git a/invenio_pidstore/config.py b/invenio_pidstore/config.py index b543d5f..b0b5bfe 100644 --- a/invenio_pidstore/config.py +++ b/invenio_pidstore/config.py @@ -8,7 +8,7 @@ """Invenio-PIDStore configuration.""" -PIDSTORE_RECID_FIELD = 'control_number' +PIDSTORE_RECID_FIELD = "control_number" """Default record id field inside the json data. This name will be used by the fetcher, to retrieve the record ID value from the @@ -16,11 +16,7 @@ """ -PIDSTORE_DATACITE_DOI_PREFIX = '' +PIDSTORE_DATACITE_DOI_PREFIX = "" """Provide a DOI prefix here.""" -PIDSTORE_RECORDID_OPTIONS = { - 'length': 10, - 'split_every': 5, - 'checksum': True -} +PIDSTORE_RECORDID_OPTIONS = {"length": 10, "split_every": 5, "checksum": True} diff --git a/invenio_pidstore/ext.py b/invenio_pidstore/ext.py index 6398ed7..a26897a 100644 --- a/invenio_pidstore/ext.py +++ b/invenio_pidstore/ext.py @@ -37,8 +37,9 @@ def pid_exists(value, pidtype=None): class _PIDStoreState(object): """Persistent identifier store state.""" - def __init__(self, app, minters_entry_point_group=None, - fetchers_entry_point_group=None): + def __init__( + self, app, minters_entry_point_group=None, fetchers_entry_point_group=None + ): """Initialize state.""" self.app = app self.minters = {} @@ -86,9 +87,12 @@ def load_fetchers_entry_point_group(self, entry_point_group): class InvenioPIDStore(object): """Invenio-PIDStore extension.""" - def __init__(self, app=None, - minters_entry_point_group='invenio_pidstore.minters', - fetchers_entry_point_group='invenio_pidstore.fetchers'): + def __init__( + self, + app=None, + minters_entry_point_group="invenio_pidstore.minters", + fetchers_entry_point_group="invenio_pidstore.fetchers", + ): """Extension initialization. :param minters_entry_point_group: The entrypoint for minters. @@ -98,12 +102,14 @@ def __init__(self, app=None, """ if app: self._state = self.init_app( - app, minters_entry_point_group=minters_entry_point_group, - fetchers_entry_point_group=fetchers_entry_point_group + app, + minters_entry_point_group=minters_entry_point_group, + fetchers_entry_point_group=fetchers_entry_point_group, ) - def init_app(self, app, minters_entry_point_group=None, - fetchers_entry_point_group=None): + def init_app( + self, app, minters_entry_point_group=None, fetchers_entry_point_group=None + ): """Flask application initialization. Initialize: @@ -132,22 +138,25 @@ def init_app(self, app, minters_entry_point_group=None, app.cli.add_command(cmd) # Initialize logger - app.config.setdefault('PIDSTORE_APP_LOGGER_HANDLERS', app.debug) - if app.config['PIDSTORE_APP_LOGGER_HANDLERS']: + app.config.setdefault("PIDSTORE_APP_LOGGER_HANDLERS", app.debug) + if app.config["PIDSTORE_APP_LOGGER_HANDLERS"]: for handler in app.logger.handlers: logger.addHandler(handler) # Initialize admin object link endpoints. try: - importlib_metadata.version('invenio-records') - app.config.setdefault('PIDSTORE_OBJECT_ENDPOINTS', dict( - rec='recordmetadata.details_view', - )) + importlib_metadata.version("invenio-records") + app.config.setdefault( + "PIDSTORE_OBJECT_ENDPOINTS", + dict( + rec="recordmetadata.details_view", + ), + ) except importlib_metadata.PackageNotFoundError: - app.config.setdefault('PIDSTORE_OBJECT_ENDPOINTS', {}) + app.config.setdefault("PIDSTORE_OBJECT_ENDPOINTS", {}) # Register template filter - app.jinja_env.filters['pid_exists'] = pid_exists + app.jinja_env.filters["pid_exists"] = pid_exists # Initialize extension state. state = _PIDStoreState( @@ -155,15 +164,16 @@ def init_app(self, app, minters_entry_point_group=None, minters_entry_point_group=minters_entry_point_group, fetchers_entry_point_group=fetchers_entry_point_group, ) - app.extensions['invenio-pidstore'] = state + app.extensions["invenio-pidstore"] = state return state def init_config(self, app): """Initialize configuration.""" for k in dir(config): - if k.startswith('PIDSTORE_') and k not in ( - 'PIDSTORE_OBJECT_ENDPOINTS', - 'PIDSTORE_APP_LOGGER_HANDLERS'): + if k.startswith("PIDSTORE_") and k not in ( + "PIDSTORE_OBJECT_ENDPOINTS", + "PIDSTORE_APP_LOGGER_HANDLERS", + ): app.config.setdefault(k, getattr(config, k)) def __getattr__(self, name): diff --git a/invenio_pidstore/fetchers.py b/invenio_pidstore/fetchers.py index cec3d8e..782930f 100644 --- a/invenio_pidstore/fetchers.py +++ b/invenio_pidstore/fetchers.py @@ -28,12 +28,13 @@ def my_fetcher(record_uuid, data): from __future__ import absolute_import, print_function from collections import namedtuple + from flask import current_app from .providers.recordid import RecordIdProvider from .providers.recordid_v2 import RecordIdProviderV2 -FetchedPID = namedtuple('FetchedPID', ['provider', 'pid_type', 'pid_value']) +FetchedPID = namedtuple("FetchedPID", ["provider", "pid_type", "pid_value"]) """A pid fetcher.""" @@ -44,11 +45,11 @@ def recid_fetcher_v2(record_uuid, data): :param data: The record metadata. :returns: A :data:`invenio_pidstore.fetchers.FetchedPID` instance. """ - pid_field = current_app.config['PIDSTORE_RECID_FIELD'] + pid_field = current_app.config["PIDSTORE_RECID_FIELD"] return FetchedPID( provider=RecordIdProviderV2, pid_type=RecordIdProviderV2.pid_type, - pid_value=str(data[pid_field]) + pid_value=str(data[pid_field]), ) @@ -59,7 +60,7 @@ def recid_fetcher(record_uuid, data): :param data: The record metadata. :returns: A :data:`invenio_pidstore.fetchers.FetchedPID` instance. """ - pid_field = current_app.config['PIDSTORE_RECID_FIELD'] + pid_field = current_app.config["PIDSTORE_RECID_FIELD"] return FetchedPID( provider=RecordIdProvider, pid_type=RecordIdProvider.pid_type, diff --git a/invenio_pidstore/minters.py b/invenio_pidstore/minters.py index 92eb7e5..116b247 100644 --- a/invenio_pidstore/minters.py +++ b/invenio_pidstore/minters.py @@ -30,10 +30,9 @@ def recid_minter_v2(record_uuid, data): :param data: The record metadata. :returns: A fresh `invenio_pidstore.models.PersistentIdentifier` instance. """ - pid_field = current_app.config['PIDSTORE_RECID_FIELD'] + pid_field = current_app.config["PIDSTORE_RECID_FIELD"] assert pid_field not in data - provider = RecordIdProviderV2.create( - object_type='rec', object_uuid=record_uuid) + provider = RecordIdProviderV2.create(object_type="rec", object_uuid=record_uuid) data[pid_field] = provider.pid.pid_value return provider.pid @@ -63,9 +62,8 @@ def recid_minter(record_uuid, data): :param data: The record metadata. :returns: A fresh `invenio_pidstore.models.PersistentIdentifier` instance. """ - pid_field = current_app.config['PIDSTORE_RECID_FIELD'] + pid_field = current_app.config["PIDSTORE_RECID_FIELD"] assert pid_field not in data - provider = RecordIdProvider.create( - object_type='rec', object_uuid=record_uuid) + provider = RecordIdProvider.create(object_type="rec", object_uuid=record_uuid) data[pid_field] = provider.pid.pid_value return provider.pid diff --git a/invenio_pidstore/models.py b/invenio_pidstore/models.py index 4aa4127..2714422 100644 --- a/invenio_pidstore/models.py +++ b/invenio_pidstore/models.py @@ -10,11 +10,11 @@ from __future__ import absolute_import, print_function +import logging +import uuid from enum import Enum -import logging import six -import uuid from flask_babelex import gettext from invenio_db import db from speaklater import make_lazy_gettext @@ -24,41 +24,45 @@ from sqlalchemy_utils.models import Timestamp from sqlalchemy_utils.types import ChoiceType, UUIDType -from .errors import PIDAlreadyExists, PIDDoesNotExistError, PIDInvalidAction, \ - PIDObjectAlreadyAssigned +from .errors import ( + PIDAlreadyExists, + PIDDoesNotExistError, + PIDInvalidAction, + PIDObjectAlreadyAssigned, +) _ = make_lazy_gettext(lambda: gettext) -logger = logging.getLogger('invenio-pidstore') +logger = logging.getLogger("invenio-pidstore") PID_STATUS_TITLES = { - 'NEW': _('New'), - 'RESERVED': _('Reserved'), - 'REGISTERED': _('Registered'), - 'REDIRECTED': _('Redirected'), - 'DELETED': _('Deleted'), + "NEW": _("New"), + "RESERVED": _("Reserved"), + "REGISTERED": _("Registered"), + "REDIRECTED": _("Redirected"), + "DELETED": _("Deleted"), } class PIDStatus(Enum): """Constants for possible status of any given PID.""" - __order__ = 'NEW RESERVED REGISTERED REDIRECTED DELETED' + __order__ = "NEW RESERVED REGISTERED REDIRECTED DELETED" - NEW = 'N' + NEW = "N" """PID has *not* yet been registered with the service provider.""" - RESERVED = 'K' + RESERVED = "K" """PID reserved in the service provider but not yet fully registered.""" - REGISTERED = 'R' + REGISTERED = "R" """PID has been registered with the service provider.""" - REDIRECTED = 'M' + REDIRECTED = "M" """PID has been redirected to another persistent identifier.""" - DELETED = 'D' + DELETED = "D" """PID has been deleted/inactivated with the service provider. This should happen very rarely, and must be kept track of, as the PID @@ -91,11 +95,11 @@ class PersistentIdentifier(db.Model, Timestamp): * A persistent identifier has one and only one object. """ - __tablename__ = 'pidstore_pid' + __tablename__ = "pidstore_pid" __table_args__ = ( - db.Index('uidx_type_pid', 'pid_type', 'pid_value', unique=True), - db.Index('idx_status', 'status'), - db.Index('idx_object', 'object_type', 'object_uuid'), + db.Index("uidx_type_pid", "pid_type", "pid_value", unique=True), + db.Index("idx_status", "status"), + db.Index("idx_object", "object_type", "object_uuid"), ) id = db.Column(db.Integer, primary_key=True) @@ -123,8 +127,15 @@ class PersistentIdentifier(db.Model, Timestamp): # Class methods # @classmethod - def create(cls, pid_type, pid_value, pid_provider=None, - status=PIDStatus.NEW, object_type=None, object_uuid=None,): + def create( + cls, + pid_type, + pid_value, + pid_provider=None, + status=PIDStatus.NEW, + object_type=None, + object_uuid=None, + ): """Create a new persistent identifier with specific type and value. :param pid_type: Persistent identifier type. @@ -140,18 +151,23 @@ def create(cls, pid_type, pid_value, pid_provider=None, """ try: with db.session.begin_nested(): - obj = cls(pid_type=pid_type, - pid_value=pid_value, - pid_provider=pid_provider, - status=status) + obj = cls( + pid_type=pid_type, + pid_value=pid_value, + pid_provider=pid_provider, + status=status, + ) if object_type and object_uuid: obj.assign(object_type, object_uuid) db.session.add(obj) - logger.info("Created PID {0}:{1}".format(pid_type, pid_value), - extra={'pid': obj}) + logger.info( + "Created PID {0}:{1}".format(pid_type, pid_value), extra={"pid": obj} + ) except IntegrityError: logger.exception( - "PID already exists: %s:%s", pid_type, pid_value, + "PID already exists: %s:%s", + pid_type, + pid_value, extra=dict( pid_type=pid_type, pid_value=pid_value, @@ -159,11 +175,14 @@ def create(cls, pid_type, pid_value, pid_provider=None, status=status, object_type=object_type, object_uuid=object_uuid, - )) + ), + ) raise PIDAlreadyExists(pid_type=pid_type, pid_value=pid_value) except SQLAlchemyError: logger.exception( - "Failed to create PID: %s:%s", pid_type, pid_value, + "Failed to create PID: %s:%s", + pid_type, + pid_value, extra=dict( pid_type=pid_type, pid_value=pid_value, @@ -171,7 +190,8 @@ def create(cls, pid_type, pid_value, pid_provider=None, status=status, object_type=object_type, object_uuid=object_uuid, - )) + ), + ) raise return obj @@ -190,7 +210,7 @@ def get(cls, pid_type, pid_value, pid_provider=None): try: args = dict(pid_type=pid_type, pid_value=six.text_type(pid_value)) if pid_provider: - args['pid_provider'] = pid_provider + args["pid_provider"] = pid_provider return cls.query.filter_by(**args).one() except NoResultFound: raise PIDDoesNotExistError(pid_type, pid_value) @@ -209,9 +229,7 @@ def get_by_object(cls, pid_type, object_type, object_uuid): """ try: return cls.query.filter_by( - pid_type=pid_type, - object_type=object_type, - object_uuid=object_uuid + pid_type=pid_type, object_type=object_type, object_uuid=object_uuid ).one() except NoResultFound: raise PIDDoesNotExistError(pid_type, None) @@ -267,12 +285,10 @@ def assign(self, object_type, object_uuid, overwrite=False): if self.object_type or self.object_uuid: # The object is already assigned to this pid. - if object_type == self.object_type and \ - object_uuid == self.object_uuid: + if object_type == self.object_type and object_uuid == self.object_uuid: return True if not overwrite: - raise PIDObjectAlreadyAssigned(object_type, - object_uuid) + raise PIDObjectAlreadyAssigned(object_type, object_uuid) self.unassign() try: @@ -282,14 +298,13 @@ def assign(self, object_type, object_uuid, overwrite=False): db.session.add(self) except SQLAlchemyError: logger.exception( - "Failed to assign %s:%s", - object_type, - object_uuid, - extra=dict(pid=self) + "Failed to assign %s:%s", object_type, object_uuid, extra=dict(pid=self) ) raise - logger.info("Assigned object {0}:{1}".format( - object_type, object_uuid), extra=dict(pid=self)) + logger.info( + "Assigned object {0}:{1}".format(object_type, object_uuid), + extra=dict(pid=self), + ) return True def unassign(self): @@ -315,11 +330,9 @@ def unassign(self): self.object_uuid = None db.session.add(self) except SQLAlchemyError: - logger.exception("Failed to unassign object.", - extra=dict(pid=self)) + logger.exception("Failed to unassign object.", extra=dict(pid=self)) raise - logger.info("Unassigned object from {0}.".format(self), - extra=dict(pid=self)) + logger.info("Unassigned object from {0}.".format(self), extra=dict(pid=self)) return True def get_redirect(self): @@ -364,8 +377,7 @@ def redirect(self, pid): except IntegrityError: raise PIDDoesNotExistError(pid.pid_type, pid.pid_value) except SQLAlchemyError: - logger.exception( - "Failed to redirect to %s", pid, extra=dict(pid=self)) + logger.exception("Failed to redirect to %s", pid, extra=dict(pid=self)) raise logger.info("Redirected PID to {0}".format(pid), extra=dict(pid=self)) return True @@ -381,8 +393,7 @@ def reserve(self): :returns: `True` if the PID is successfully reserved. """ if not (self.is_new() or self.is_reserved()): - raise PIDInvalidAction( - "Persistent identifier is not new or reserved.") + raise PIDInvalidAction("Persistent identifier is not new or reserved.") try: with db.session.begin_nested(): @@ -404,8 +415,8 @@ def register(self): """ if self.is_registered() or self.is_deleted() or self.is_redirected(): raise PIDInvalidAction( - "Persistent identifier has already been registered" - " or is deleted.") + "Persistent identifier has already been registered" " or is deleted." + ) try: with db.session.begin_nested(): @@ -464,11 +475,9 @@ def sync_status(self, status): self.status = status db.session.add(self) except SQLAlchemyError: - logger.exception( - "Failed to sync status %s.", status, extra=dict(pid=self)) + logger.exception("Failed to sync status %s.", status, extra=dict(pid=self)) raise - logger.info( - "Synced PID status to {0}.".format(status), extra=dict(pid=self)) + logger.info("Synced PID status to {0}.".format(status), extra=dict(pid=self)) return True def is_redirected(self): @@ -506,9 +515,12 @@ def is_reserved(self): def __repr__(self): """Get representation of object.""" return "".format( - self.pid_type, self.pid_value, self.status, - " / {0}:{1}".format(self.object_type, self.object_uuid) if - self.object_type else "" + self.pid_type, + self.pid_value, + self.status, + " / {0}:{1}".format(self.object_type, self.object_uuid) + if self.object_type + else "", ) @@ -527,18 +539,18 @@ class Redirect(db.Model, Timestamp): assert pid2.pid_value == pid.get_redirect().pid_value """ - __tablename__ = 'pidstore_redirect' + __tablename__ = "pidstore_redirect" id = db.Column(UUIDType, default=uuid.uuid4, primary_key=True) """Id of redirect entry.""" pid_id = db.Column( db.Integer, - db.ForeignKey(PersistentIdentifier.id, onupdate="CASCADE", - ondelete="RESTRICT"), - nullable=False) + db.ForeignKey(PersistentIdentifier.id, onupdate="CASCADE", ondelete="RESTRICT"), + nullable=False, + ) """Persistent identifier.""" - pid = db.relationship(PersistentIdentifier, backref='redirects') + pid = db.relationship(PersistentIdentifier, backref="redirects") """Relationship to persistent identifier.""" @@ -553,11 +565,13 @@ class RecordIdentifier(db.Model): record identifiers, but instead use e.g. UUIDs as record identifiers. """ - __tablename__ = 'pidstore_recid' + __tablename__ = "pidstore_recid" recid = db.Column( db.BigInteger().with_variant(db.Integer, "sqlite"), - primary_key=True, autoincrement=True) + primary_key=True, + autoincrement=True, + ) @classmethod def next(cls): @@ -589,11 +603,12 @@ def _set_sequence(cls, val): :param val: The value to be set. """ - if db.engine.dialect.name == 'postgresql': # pragma: no cover + if db.engine.dialect.name == "postgresql": # pragma: no cover db.session.execute( "SELECT setval(pg_get_serial_sequence(" - "'{0}', 'recid'), :newval)".format( - cls.__tablename__), dict(newval=val)) + "'{0}', 'recid'), :newval)".format(cls.__tablename__), + dict(newval=val), + ) @classmethod def insert(cls, val): @@ -608,8 +623,8 @@ def insert(cls, val): __all__ = ( - 'PersistentIdentifier', - 'PIDStatus', - 'RecordIdentifier', - 'Redirect', + "PersistentIdentifier", + "PIDStatus", + "RecordIdentifier", + "Redirect", ) diff --git a/invenio_pidstore/providers/base.py b/invenio_pidstore/providers/base.py index 7b3e488..c73856f 100644 --- a/invenio_pidstore/providers/base.py +++ b/invenio_pidstore/providers/base.py @@ -26,8 +26,15 @@ class BaseProvider(object): """Default status for newly created PIDs by this provider.""" @classmethod - def create(cls, pid_type=None, pid_value=None, object_type=None, - object_uuid=None, status=None, **kwargs): + def create( + cls, + pid_type=None, + pid_value=None, + object_type=None, + object_uuid=None, + status=None, + **kwargs + ): """Create a new instance for the given type and pid. :param pid_type: Persistent identifier type. (Default: None). @@ -67,9 +74,11 @@ def get(cls, pid_value, pid_type=None, **kwargs): instance. """ return cls( - PersistentIdentifier.get(pid_type or cls.pid_type, pid_value, - pid_provider=cls.pid_provider), - **kwargs) + PersistentIdentifier.get( + pid_type or cls.pid_type, pid_value, pid_provider=cls.pid_provider + ), + **kwargs + ) def __init__(self, pid, **kwargs): """Initialize provider using persistent identifier. diff --git a/invenio_pidstore/providers/datacite.py b/invenio_pidstore/providers/datacite.py index 122bfe5..e7d14d8 100644 --- a/invenio_pidstore/providers/datacite.py +++ b/invenio_pidstore/providers/datacite.py @@ -12,8 +12,13 @@ from __future__ import absolute_import from datacite import DataCiteMDSClient -from datacite.errors import DataCiteError, DataCiteGoneError, \ - DataCiteNoContentError, DataCiteNotFoundError, HttpError +from datacite.errors import ( + DataCiteError, + DataCiteGoneError, + DataCiteNoContentError, + DataCiteNotFoundError, + HttpError, +) from flask import current_app from ..models import PIDStatus, logger @@ -23,10 +28,10 @@ class DataCiteProvider(BaseProvider): """DOI provider using DataCite API.""" - pid_type = 'doi' + pid_type = "doi" """Default persistent identifier type.""" - pid_provider = 'datacite' + pid_provider = "datacite" """Persistent identifier provider name.""" default_status = PIDStatus.NEW @@ -47,8 +52,7 @@ def create(cls, pid_value, **kwargs): :class:`invenio_pidstore.providers.datacite.DataCiteProvider` instance. """ - return super(DataCiteProvider, cls).create( - pid_value=pid_value, **kwargs) + return super(DataCiteProvider, cls).create(pid_value=pid_value, **kwargs) def __init__(self, pid, client=None, **kwargs): """Initialize provider. @@ -75,12 +79,12 @@ def __init__(self, pid, client=None, **kwargs): self.api = client else: self.api = DataCiteMDSClient( - username=current_app.config.get('PIDSTORE_DATACITE_USERNAME'), - password=current_app.config.get('PIDSTORE_DATACITE_PASSWORD'), - prefix=current_app.config.get('PIDSTORE_DATACITE_DOI_PREFIX'), - test_mode=current_app.config.get( - 'PIDSTORE_DATACITE_TESTMODE', False), - url=current_app.config.get('PIDSTORE_DATACITE_URL')) + username=current_app.config.get("PIDSTORE_DATACITE_USERNAME"), + password=current_app.config.get("PIDSTORE_DATACITE_PASSWORD"), + prefix=current_app.config.get("PIDSTORE_DATACITE_DOI_PREFIX"), + test_mode=current_app.config.get("PIDSTORE_DATACITE_TESTMODE", False), + url=current_app.config.get("PIDSTORE_DATACITE_URL"), + ) def reserve(self, doc): """Reserve a DOI (amounts to upload metadata, but not to mint). @@ -93,11 +97,9 @@ def reserve(self, doc): self.pid.reserve() self.api.metadata_post(doc) except (DataCiteError, HttpError): - logger.exception("Failed to reserve in DataCite", - extra=dict(pid=self.pid)) + logger.exception("Failed to reserve in DataCite", extra=dict(pid=self.pid)) raise - logger.info("Successfully reserved in DataCite", - extra=dict(pid=self.pid)) + logger.info("Successfully reserved in DataCite", extra=dict(pid=self.pid)) return True def register(self, url, doc): @@ -114,11 +116,9 @@ def register(self, url, doc): # Mint DOI self.api.doi_post(self.pid.pid_value, url) except (DataCiteError, HttpError): - logger.exception("Failed to register in DataCite", - extra=dict(pid=self.pid)) + logger.exception("Failed to register in DataCite", extra=dict(pid=self.pid)) raise - logger.info("Successfully registered in DataCite", - extra=dict(pid=self.pid)) + logger.info("Successfully registered in DataCite", extra=dict(pid=self.pid)) return True def update(self, url, doc): @@ -130,22 +130,19 @@ def update(self, url, doc): :returns: `True` if is updated successfully. """ if self.pid.is_deleted(): - logger.info("Reactivate in DataCite", - extra=dict(pid=self.pid)) + logger.info("Reactivate in DataCite", extra=dict(pid=self.pid)) try: # Set metadata self.api.metadata_post(doc) self.api.doi_post(self.pid.pid_value, url) except (DataCiteError, HttpError): - logger.exception("Failed to update in DataCite", - extra=dict(pid=self.pid)) + logger.exception("Failed to update in DataCite", extra=dict(pid=self.pid)) raise if self.pid.is_deleted(): self.pid.sync_status(PIDStatus.REGISTERED) - logger.info("Successfully updated in DataCite", - extra=dict(pid=self.pid)) + logger.info("Successfully updated in DataCite", extra=dict(pid=self.pid)) return True def delete(self): @@ -163,11 +160,9 @@ def delete(self): self.pid.delete() self.api.metadata_delete(self.pid.pid_value) except (DataCiteError, HttpError): - logger.exception("Failed to delete in DataCite", - extra=dict(pid=self.pid)) + logger.exception("Failed to delete in DataCite", extra=dict(pid=self.pid)) raise - logger.info("Successfully deleted in DataCite", - extra=dict(pid=self.pid)) + logger.info("Successfully deleted in DataCite", extra=dict(pid=self.pid)) return True def sync_status(self): @@ -199,8 +194,9 @@ def sync_status(self): except DataCiteNotFoundError: pass except (DataCiteError, HttpError): - logger.exception("Failed to sync status from DataCite", - extra=dict(pid=self.pid)) + logger.exception( + "Failed to sync status from DataCite", extra=dict(pid=self.pid) + ) raise if status is None: @@ -208,6 +204,7 @@ def sync_status(self): self.pid.sync_status(status) - logger.info("Successfully synced status from DataCite", - extra=dict(pid=self.pid)) + logger.info( + "Successfully synced status from DataCite", extra=dict(pid=self.pid) + ) return True diff --git a/invenio_pidstore/providers/recordid.py b/invenio_pidstore/providers/recordid.py index 3c81382..2a40561 100644 --- a/invenio_pidstore/providers/recordid.py +++ b/invenio_pidstore/providers/recordid.py @@ -17,7 +17,7 @@ class RecordIdProvider(BaseProvider): """Record identifier provider.""" - pid_type = 'recid' + pid_type = "recid" """Type of persistent identifier.""" pid_provider = None @@ -46,10 +46,11 @@ def create(cls, object_type=None, object_uuid=None, **kwargs): :param kwargs: You specify the pid_value. """ # Request next integer in recid sequence. - assert 'pid_value' not in kwargs - kwargs['pid_value'] = str(RecordIdentifier.next()) - kwargs.setdefault('status', cls.default_status) + assert "pid_value" not in kwargs + kwargs["pid_value"] = str(RecordIdentifier.next()) + kwargs.setdefault("status", cls.default_status) if object_type and object_uuid: - kwargs['status'] = PIDStatus.REGISTERED + kwargs["status"] = PIDStatus.REGISTERED return super(RecordIdProvider, cls).create( - object_type=object_type, object_uuid=object_uuid, **kwargs) + object_type=object_type, object_uuid=object_uuid, **kwargs + ) diff --git a/invenio_pidstore/providers/recordid_v2.py b/invenio_pidstore/providers/recordid_v2.py index c02de73..594a527 100644 --- a/invenio_pidstore/providers/recordid_v2.py +++ b/invenio_pidstore/providers/recordid_v2.py @@ -12,6 +12,7 @@ from __future__ import absolute_import import copy + from base32_lib import base32 from flask import current_app @@ -28,7 +29,7 @@ class RecordIdProviderV2(BaseProvider): integer (:class:`invenio_pidstore.providers.recordid.RecordIdProvider`). """ - pid_type = 'recid' + pid_type = "recid" """Type of persistent identifier.""" pid_provider = None @@ -55,23 +56,18 @@ def generate_id(cls, options=None): """Generate record id.""" passed_options = options or {} # WHY: A new dict needs to be created to prevent side-effects - options = copy.deepcopy(current_app.config.get( - 'PIDSTORE_RECORDID_OPTIONS', {} - )) + options = copy.deepcopy(current_app.config.get("PIDSTORE_RECORDID_OPTIONS", {})) options.update(passed_options) - length = options.get('length', 10) - split_every = options.get('split_every', 0) - checksum = options.get('checksum', True) + length = options.get("length", 10) + split_every = options.get("split_every", 0) + checksum = options.get("checksum", True) return base32.generate( - length=length, - split_every=split_every, - checksum=checksum + length=length, split_every=split_every, checksum=checksum ) @classmethod - def create(cls, object_type=None, object_uuid=None, options=None, - **kwargs): + def create(cls, object_type=None, object_uuid=None, options=None, **kwargs): """Create a new record identifier. Note: if the object_type and object_uuid values are passed, then the @@ -91,13 +87,14 @@ def create(cls, object_type=None, object_uuid=None, options=None, parameters. :returns: A :class:`RecordIdProviderV2` instance. """ - assert 'pid_value' not in kwargs + assert "pid_value" not in kwargs - kwargs['pid_value'] = cls.generate_id(options) - kwargs.setdefault('status', cls.default_status) + kwargs["pid_value"] = cls.generate_id(options) + kwargs.setdefault("status", cls.default_status) if object_type and object_uuid: - kwargs['status'] = cls.default_status_with_obj + kwargs["status"] = cls.default_status_with_obj return super(RecordIdProviderV2, cls).create( - object_type=object_type, object_uuid=object_uuid, **kwargs) + object_type=object_type, object_uuid=object_uuid, **kwargs + ) diff --git a/invenio_pidstore/proxies.py b/invenio_pidstore/proxies.py index c7d1612..1b53a97 100644 --- a/invenio_pidstore/proxies.py +++ b/invenio_pidstore/proxies.py @@ -13,5 +13,4 @@ from flask import current_app from werkzeug.local import LocalProxy -current_pidstore = LocalProxy( - lambda: current_app.extensions['invenio-pidstore']) +current_pidstore = LocalProxy(lambda: current_app.extensions["invenio-pidstore"]) diff --git a/invenio_pidstore/resolver.py b/invenio_pidstore/resolver.py index 57970de..5fb28ae 100644 --- a/invenio_pidstore/resolver.py +++ b/invenio_pidstore/resolver.py @@ -12,8 +12,12 @@ from sqlalchemy.orm.exc import NoResultFound -from .errors import PIDDeletedError, PIDMissingObjectError, \ - PIDRedirectedError, PIDUnregistered +from .errors import ( + PIDDeletedError, + PIDMissingObjectError, + PIDRedirectedError, + PIDUnregistered, +) from .models import PersistentIdentifier @@ -24,8 +28,9 @@ class Resolver(object): identifier. """ - def __init__(self, pid_type=None, object_type=None, getter=None, - registered_only=True): + def __init__( + self, pid_type=None, object_type=None, getter=None, registered_only=True + ): """Initialize resolver. :param pid_type: Persistent identifier type. diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index d2d0895..0000000 --- a/pytest.ini +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Invenio. -# Copyright (C) 2015-2018 CERN. -# -# Invenio is free software; you can redistribute it and/or modify it -# under the terms of the MIT License; see LICENSE file for more details. - -[pytest] -doctest_optionflags = DONT_ACCEPT_TRUE_FOR_1 ELLIPSIS IGNORE_EXCEPTION_DETAIL -addopts = --isort --pydocstyle --pycodestyle --doctest-glob="*.rst" --doctest-modules --cov=invenio_pidstore --cov-report=term-missing -testpaths = docs tests invenio_pidstore diff --git a/setup.cfg b/setup.cfg index 0cbd92b..f7f7ab9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,6 +35,7 @@ install_requires = [options.extras_require] tests = + pytest-black>=0.3.0,<0.3.10 Flask-Menu>=0.5.1 invenio-admin>=1.2.0 invenio-access>=1.0.0 @@ -95,5 +96,10 @@ output-dir = invenio_pidstore/translations/ input-file = invenio_pidstore/translations/messages.pot output-dir = invenio_pidstore/translations/ -[pycodestyle] -ignore = E501 +[isort] +profile=black + +[tool:pytest] +doctest_optionflags = DONT_ACCEPT_TRUE_FOR_1 ELLIPSIS IGNORE_EXCEPTION_DETAIL +addopts = --black --isort --pydocstyle --doctest-glob="*.rst" --doctest-modules --cov=invenio_pidstore --cov-report=term-missing +testpaths = docs tests invenio_pidstore diff --git a/tests/conftest.py b/tests/conftest.py index e0ee52a..1a9ad4b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,9 +11,10 @@ from __future__ import absolute_import, print_function import os -import pytest import shutil import tempfile + +import pytest from flask import Flask from invenio_db import InvenioDB from sqlalchemy_utils.functions import create_database, database_exists @@ -26,13 +27,14 @@ def app(request): """Flask application fixture.""" # Set temporary instance path for sqlite instance_path = tempfile.mkdtemp() - app = Flask('testapp', instance_path=instance_path) + app = Flask("testapp", instance_path=instance_path) InvenioDB(app) InvenioPIDStore(app) app.config.update( SQLALCHEMY_DATABASE_URI=os.environ.get( - 'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db'), + "SQLALCHEMY_DATABASE_URI", "sqlite:///test.db" + ), TESTING=True, ) diff --git a/tests/test_admin.py b/tests/test_admin.py index 87c92dc..dd0cbb1 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -10,6 +10,7 @@ from __future__ import absolute_import, print_function import uuid + from flask_admin import Admin, menu from invenio_db import db @@ -23,27 +24,28 @@ def test_admin(app): pid_kwargs = dict(pid_adminview) - assert 'model' in pid_adminview - assert 'modelview' in pid_adminview + assert "model" in pid_adminview + assert "modelview" in pid_adminview # Register both models in admin - pid_model = pid_kwargs.pop('model') - pid_mv = pid_kwargs.pop('modelview') + pid_model = pid_kwargs.pop("model") + pid_mv = pid_kwargs.pop("modelview") admin.add_view(pid_mv(pid_model, db.session, **pid_kwargs)) # Check if generated admin menu contains the correct items menu_items = {str(item.name): item for item in admin.menu()} # PIDStore should be a category - assert 'Records' in menu_items - assert menu_items['Records'].is_category() - assert isinstance(menu_items['Records'], menu.MenuCategory) + assert "Records" in menu_items + assert menu_items["Records"].is_category() + assert isinstance(menu_items["Records"], menu.MenuCategory) # Items in PIDStore menu should be the modelviews - submenu_items = {str(item.name): item for item in - menu_items['Records'].get_children()} - assert 'Persistent Identifier' in submenu_items - assert isinstance(submenu_items['Persistent Identifier'], menu.MenuView) + submenu_items = { + str(item.name): item for item in menu_items["Records"].get_children() + } + assert "Persistent Identifier" in submenu_items + assert isinstance(submenu_items["Persistent Identifier"], menu.MenuView) def test_filter_uuid(app, db): @@ -51,25 +53,31 @@ def test_filter_uuid(app, db): with app.app_context(): myuuid = uuid.uuid4() PersistentIdentifier.create( - 'doi', '10.1234/a', object_type='tst', object_uuid=myuuid) + "doi", "10.1234/a", object_type="tst", object_uuid=myuuid + ) - query = FilterUUID(PersistentIdentifier.object_uuid, 'Test').apply( - PersistentIdentifier.query, str(myuuid), None) + query = FilterUUID(PersistentIdentifier.object_uuid, "Test").apply( + PersistentIdentifier.query, str(myuuid), None + ) assert query.count() == 1 def test_object_formatter(app, db): """Test FilterUUID.""" - @app.route('/') + + @app.route("/") def test_detail(id=None): return str(id) with app.test_request_context(): - app.config['PIDSTORE_OBJECT_ENDPOINTS']['tst'] = 'test_detail' + app.config["PIDSTORE_OBJECT_ENDPOINTS"]["tst"] = "test_detail" pid = PersistentIdentifier.create( - 'doi', '10.1234/a', object_type='tst', object_uuid=uuid.uuid4()) - assert 'View' in object_formatter(None, None, pid, None) + "doi", "10.1234/a", object_type="tst", object_uuid=uuid.uuid4() + ) + assert "View" in object_formatter(None, None, pid, None) pid = PersistentIdentifier.create( - 'doi', '10.1234/b', ) - assert object_formatter(None, None, pid, None) == '' + "doi", + "10.1234/b", + ) + assert object_formatter(None, None, pid, None) == "" diff --git a/tests/test_cli.py b/tests/test_cli.py index 074565b..119cfe2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -12,6 +12,7 @@ from __future__ import absolute_import, print_function import uuid + from click.testing import CliRunner from flask.cli import ScriptInfo @@ -28,16 +29,14 @@ def test_pid_creation(app, db): with app.app_context(): assert PersistentIdentifier.query.count() == 0 - result = runner.invoke(cmd, [ - 'create', 'doi', '10.1234/foo' - ], obj=script_info) + result = runner.invoke(cmd, ["create", "doi", "10.1234/foo"], obj=script_info) assert 0 == result.exit_code with app.app_context(): assert PersistentIdentifier.query.count() == 1 - pid = PersistentIdentifier.get('doi', '10.1234/foo') - assert pid.pid_type == 'doi' - assert pid.pid_value == '10.1234/foo' + pid = PersistentIdentifier.get("doi", "10.1234/foo") + assert pid.pid_type == "doi" + assert pid.pid_value == "10.1234/foo" assert pid.pid_provider is None assert pid.status == PIDStatus.NEW assert pid.object_type is None @@ -46,46 +45,88 @@ def test_pid_creation(app, db): rec_uuid = uuid.uuid4() # Bad parameter status: - result = runner.invoke(cmd, [ - 'create', 'recid', '2', '--status', 'BADPARAMETER', - '--type', 'rec', '--uuid', str(rec_uuid), - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "create", + "recid", + "2", + "--status", + "BADPARAMETER", + "--type", + "rec", + "--uuid", + str(rec_uuid), + ], + obj=script_info, + ) assert 2 == result.exit_code # Any or both type and uuid must be defined: - result = runner.invoke(cmd, [ - 'create', 'recid', '2', - '--type', 'rec', - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "create", + "recid", + "2", + "--type", + "rec", + ], + obj=script_info, + ) assert 2 == result.exit_code - result = runner.invoke(cmd, [ - 'create', 'recid', '2', - '--uuid', str(rec_uuid), - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "create", + "recid", + "2", + "--uuid", + str(rec_uuid), + ], + obj=script_info, + ) assert 2 == result.exit_code # Everything should be fine now: - result = runner.invoke(cmd, [ - 'create', 'recid', '2', '--status', 'REGISTERED', - '--type', 'rec', '--uuid', str(rec_uuid), - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "create", + "recid", + "2", + "--status", + "REGISTERED", + "--type", + "rec", + "--uuid", + str(rec_uuid), + ], + obj=script_info, + ) assert 0 == result.exit_code with app.app_context(): assert PersistentIdentifier.query.count() == 2 - pid = PersistentIdentifier.get('recid', '2') - assert pid.pid_type == 'recid' - assert pid.pid_value == '2' + pid = PersistentIdentifier.get("recid", "2") + assert pid.pid_type == "recid" + assert pid.pid_value == "2" assert pid.pid_provider is None assert pid.status == PIDStatus.REGISTERED - assert pid.object_type == 'rec' + assert pid.object_type == "rec" assert pid.object_uuid == rec_uuid # Can't duplicate existing persistent identifier - result = runner.invoke(cmd, [ - 'create', 'recid', '2', - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "create", + "recid", + "2", + ], + obj=script_info, + ) assert 1 == result.exit_code @@ -96,81 +137,112 @@ def test_pid_assign(app, db): with runner.isolated_filesystem(): # No assigned object - result = runner.invoke(cmd, [ - 'create', 'doi', '10.1234/foo' - ], obj=script_info) + result = runner.invoke(cmd, ["create", "doi", "10.1234/foo"], obj=script_info) assert 0 == result.exit_code with app.app_context(): - pid = PersistentIdentifier.get('doi', '10.1234/foo') + pid = PersistentIdentifier.get("doi", "10.1234/foo") assert not pid.has_object() assert pid.get_assigned_object() is None - assert pid.get_assigned_object('rec') is None + assert pid.get_assigned_object("rec") is None # Assign object rec_uuid = uuid.uuid4() - result = runner.invoke(cmd, [ - 'assign', 'doi', '10.1234/foo', - '-t', 'rec', '-i', str(rec_uuid) - ], obj=script_info) + result = runner.invoke( + cmd, + ["assign", "doi", "10.1234/foo", "-t", "rec", "-i", str(rec_uuid)], + obj=script_info, + ) assert 0 == result.exit_code with app.app_context(): - pid = PersistentIdentifier.get('doi', '10.1234/foo') + pid = PersistentIdentifier.get("doi", "10.1234/foo") assert pid.has_object() assert pid.get_assigned_object() == rec_uuid - assert pid.get_assigned_object('rec') == rec_uuid - assert pid.get_assigned_object('oth') is None + assert pid.get_assigned_object("rec") == rec_uuid + assert pid.get_assigned_object("oth") is None # Doesnt' raise - result = runner.invoke(cmd, [ - 'assign', 'doi', '10.1234/foo', - '-t', 'rec', '-i', str(rec_uuid) - ], obj=script_info) + result = runner.invoke( + cmd, + ["assign", "doi", "10.1234/foo", "-t", "rec", "-i", str(rec_uuid)], + obj=script_info, + ) assert 0 == result.exit_code # Missing type or uuid: - result = runner.invoke(cmd, [ - 'assign', 'doi', '10.1234/foo', - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "assign", + "doi", + "10.1234/foo", + ], + obj=script_info, + ) assert 2 == result.exit_code - result = runner.invoke(cmd, [ - 'assign', 'doi', '10.1234/foo', - '-t', 'rec', - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "assign", + "doi", + "10.1234/foo", + "-t", + "rec", + ], + obj=script_info, + ) assert 2 == result.exit_code - result = runner.invoke(cmd, [ - 'assign', 'doi', '10.1234/foo', - '-i', str(rec_uuid), - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "assign", + "doi", + "10.1234/foo", + "-i", + str(rec_uuid), + ], + obj=script_info, + ) assert 2 == result.exit_code # Assign without overwrite (uuid as str and uuid) new_uuid = uuid.uuid4() - result = runner.invoke(cmd, [ - 'assign', 'doi', '10.1234/foo', - '-t', 'rec', '-i', str(new_uuid) - ], obj=script_info) + result = runner.invoke( + cmd, + ["assign", "doi", "10.1234/foo", "-t", "rec", "-i", str(new_uuid)], + obj=script_info, + ) assert 1 == result.exit_code # Assign with overwrite - result = runner.invoke(cmd, [ - 'assign', 'doi', '10.1234/foo', - '-s', 'REGISTERED', - '-t', 'rec', '-i', str(new_uuid), - '--overwrite' - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "assign", + "doi", + "10.1234/foo", + "-s", + "REGISTERED", + "-t", + "rec", + "-i", + str(new_uuid), + "--overwrite", + ], + obj=script_info, + ) assert 0 == result.exit_code with app.app_context(): - pid = PersistentIdentifier.get('doi', '10.1234/foo') + pid = PersistentIdentifier.get("doi", "10.1234/foo") assert pid.has_object() assert pid.status == PIDStatus.REGISTERED assert pid.get_assigned_object() == new_uuid - assert pid.get_assigned_object('rec') == new_uuid - assert pid.get_assigned_object('oth') is None + assert pid.get_assigned_object("rec") == new_uuid + assert pid.get_assigned_object("oth") is None def test_pid_unassign(app, db): @@ -181,44 +253,71 @@ def test_pid_unassign(app, db): with runner.isolated_filesystem(): rec_uuid = uuid.uuid4() # Assigned object - result = runner.invoke(cmd, [ - 'create', 'recid', '101', - '-t', 'rec', '-i', str(rec_uuid) - ], obj=script_info) + result = runner.invoke( + cmd, + ["create", "recid", "101", "-t", "rec", "-i", str(rec_uuid)], + obj=script_info, + ) assert 0 == result.exit_code - result = runner.invoke(cmd, [ - 'get', 'recid', '101', - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "get", + "recid", + "101", + ], + obj=script_info, + ) assert 0 == result.exit_code - assert 'rec {0} N\n'.format(str(rec_uuid)) == result.output - - result = runner.invoke(cmd, [ - 'dereference', 'rec', str(rec_uuid), - ], obj=script_info) + assert "rec {0} N\n".format(str(rec_uuid)) == result.output + + result = runner.invoke( + cmd, + [ + "dereference", + "rec", + str(rec_uuid), + ], + obj=script_info, + ) assert 0 == result.exit_code - assert 'recid 101 None\n' == result.output - - result = runner.invoke(cmd, [ - 'dereference', 'rec', str(rec_uuid), '-s', 'NEW', - ], obj=script_info) + assert "recid 101 None\n" == result.output + + result = runner.invoke( + cmd, + [ + "dereference", + "rec", + str(rec_uuid), + "-s", + "NEW", + ], + obj=script_info, + ) assert 0 == result.exit_code - assert 'recid 101 None\n' == result.output + assert "recid 101 None\n" == result.output with app.app_context(): - pid = PersistentIdentifier.get('recid', '101') + pid = PersistentIdentifier.get("recid", "101") assert pid.has_object() assert pid.get_assigned_object() == rec_uuid - assert pid.get_assigned_object('rec') == rec_uuid + assert pid.get_assigned_object("rec") == rec_uuid # Unassign the object - result = runner.invoke(cmd, [ - 'unassign', 'recid', '101', - ], obj=script_info) + result = runner.invoke( + cmd, + [ + "unassign", + "recid", + "101", + ], + obj=script_info, + ) assert 0 == result.exit_code with app.app_context(): - pid = PersistentIdentifier.get('recid', '101') + pid = PersistentIdentifier.get("recid", "101") assert not pid.has_object() assert pid.get_assigned_object() is None - assert pid.get_assigned_object('rec') is None + assert pid.get_assigned_object("rec") is None diff --git a/tests/test_ext.py b/tests/test_ext.py index 8440188..695a7de 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -22,50 +22,53 @@ def test_version(): """Test version import.""" from invenio_pidstore import __version__ + assert __version__ def test_init(): """Test extension initialization.""" - app = Flask('testapp') + app = Flask("testapp") ext = InvenioPIDStore(app) - assert 'invenio-pidstore' in app.extensions - ext.register_minter('testminter', - lambda a, b: 'deadbeef-c0de-c0de-c0de-b100dc0ffee5') - ext.register_fetcher('testfetcher', - lambda a, b: 'deadbeef-c0de-c0de-c0de-b100dc0ffee5') - - app = Flask('testapp') + assert "invenio-pidstore" in app.extensions + ext.register_minter( + "testminter", lambda a, b: "deadbeef-c0de-c0de-c0de-b100dc0ffee5" + ) + ext.register_fetcher( + "testfetcher", lambda a, b: "deadbeef-c0de-c0de-c0de-b100dc0ffee5" + ) + + app = Flask("testapp") ext = InvenioPIDStore() - assert 'invenio-pidstore' not in app.extensions + assert "invenio-pidstore" not in app.extensions ext.init_app(app) - assert 'invenio-pidstore' in app.extensions + assert "invenio-pidstore" in app.extensions def test_logger(): """Test extension initialization.""" - app = Flask('testapp') - app.config['PIDSTORE_APP_LOGGER_HANDLERS'] = True + app = Flask("testapp") + app.config["PIDSTORE_APP_LOGGER_HANDLERS"] = True InvenioPIDStore(app) def test_invenio_records(): """Test extension initialization.""" - app = Flask('testapp') - with patch('invenio_pidstore.ext.importlib_metadata'): - with patch('invenio_pidstore.ext.importlib_resources'): + app = Flask("testapp") + with patch("invenio_pidstore.ext.importlib_metadata"): + with patch("invenio_pidstore.ext.importlib_resources"): InvenioPIDStore(app) - assert app.config['PIDSTORE_OBJECT_ENDPOINTS'] + assert app.config["PIDSTORE_OBJECT_ENDPOINTS"] def test_template_filters(app, db): """Test the template filters.""" with app.app_context(): # Test the 'pid_exists' template filter - pid_exists = app.jinja_env.filters['pid_exists'] - assert not pid_exists('pid_val0', pidtype='mock_t') - PersistentIdentifier.create('mock_t', 'pid_val0') + pid_exists = app.jinja_env.filters["pid_exists"] + assert not pid_exists("pid_val0", pidtype="mock_t") + PersistentIdentifier.create("mock_t", "pid_val0") db.session.commit() - assert pid_exists('pid_val0', pidtype='mock_t') - assert not pid_exists('foo', pidtype='mock_t') - assert not pid_exists('pid_val0', pidtype='foo') + assert pid_exists("pid_val0", pidtype="mock_t") + assert not pid_exists("foo", pidtype="mock_t") + assert not pid_exists("pid_val0", pidtype="foo") diff --git a/tests/test_fetchers.py b/tests/test_fetchers.py index 2eeac12..132e401 100644 --- a/tests/test_fetchers.py +++ b/tests/test_fetchers.py @@ -26,7 +26,7 @@ def test_recid_fetcher(app, db): fetched_pid = recid_fetcher(rec_uuid, data) assert minted_pid.pid_value == fetched_pid.pid_value assert fetched_pid.pid_type == fetched_pid.provider.pid_type - assert fetched_pid.pid_type == 'recid' + assert fetched_pid.pid_type == "recid" def test_recid_fetcher_v2(app, db): @@ -40,11 +40,11 @@ def test_recid_fetcher_v2(app, db): assert minted_pid.pid_value == fetched_pid.pid_value assert minted_pid.pid_type == fetched_pid.pid_type - assert fetched_pid.pid_type == 'recid' + assert fetched_pid.pid_type == "recid" assert fetched_pid.pid_value == minted_pid.pid_value def test_register_fetcher(app): """Test base provider.""" with app.app_context(): - current_pidstore.register_fetcher('anothername', recid_minter) + current_pidstore.register_fetcher("anothername", recid_minter) diff --git a/tests/test_invenio_pidstore.py b/tests/test_invenio_pidstore.py index 105c810..740ffa8 100644 --- a/tests/test_invenio_pidstore.py +++ b/tests/test_invenio_pidstore.py @@ -11,25 +11,30 @@ from __future__ import absolute_import, print_function -import pytest import uuid + +import pytest from mock import patch from sqlalchemy.exc import SQLAlchemyError -from invenio_pidstore.errors import PIDAlreadyExists, PIDDoesNotExistError, \ - PIDInvalidAction, PIDObjectAlreadyAssigned +from invenio_pidstore.errors import ( + PIDAlreadyExists, + PIDDoesNotExistError, + PIDInvalidAction, + PIDObjectAlreadyAssigned, +) from invenio_pidstore.models import PersistentIdentifier, PIDStatus, Redirect -@patch('invenio_pidstore.models.logger') +@patch("invenio_pidstore.models.logger") def test_pid_creation(logger, app, db): """Test pid creation.""" with app.app_context(): assert PersistentIdentifier.query.count() == 0 - pid = PersistentIdentifier.create('doi', '10.1234/foo') + pid = PersistentIdentifier.create("doi", "10.1234/foo") assert PersistentIdentifier.query.count() == 1 - assert pid.pid_type == 'doi' - assert pid.pid_value == '10.1234/foo' + assert pid.pid_type == "doi" + assert pid.pid_value == "10.1234/foo" assert pid.pid_provider is None assert pid.status == PIDStatus.NEW assert pid.object_type is None @@ -38,36 +43,37 @@ def test_pid_creation(logger, app, db): rec_uuid = uuid.uuid4() pid = PersistentIdentifier.create( - 'rec', '2', status=PIDStatus.REGISTERED, object_type='rec', - object_uuid=rec_uuid) + "rec", + "2", + status=PIDStatus.REGISTERED, + object_type="rec", + object_uuid=rec_uuid, + ) assert PersistentIdentifier.query.count() == 2 - assert pid.pid_type == 'rec' - assert pid.pid_value == '2' + assert pid.pid_type == "rec" + assert pid.pid_value == "2" assert pid.pid_provider is None assert pid.status == PIDStatus.REGISTERED - assert pid.object_type == 'rec' + assert pid.object_type == "rec" assert pid.object_uuid == rec_uuid # Can't duplicate existing persistent identifier assert not logger.exception.called - pytest.raises( - PIDAlreadyExists, PersistentIdentifier.create, 'rec', '2') + pytest.raises(PIDAlreadyExists, PersistentIdentifier.create, "rec", "2") assert logger.exception.called - with patch('invenio_pidstore.models.db.session.begin_nested') as mock: + with patch("invenio_pidstore.models.db.session.begin_nested") as mock: mock.side_effect = SQLAlchemyError() - pytest.raises(SQLAlchemyError, PersistentIdentifier.create, - 'rec', '2') - assert logger.exception.call_args[0][0].startswith( - "Failed to create") + pytest.raises(SQLAlchemyError, PersistentIdentifier.create, "rec", "2") + assert logger.exception.call_args[0][0].startswith("Failed to create") def test_alembic(app, db): """Test alembic recipes.""" - ext = app.extensions['invenio-db'] + ext = app.extensions["invenio-db"] - if db.engine.name == 'sqlite': - raise pytest.skip('Upgrades are not supported on SQLite.') + if db.engine.name == "sqlite": + raise pytest.skip("Upgrades are not supported on SQLite.") assert not ext.alembic.compare_metadata() db.drop_all() @@ -75,7 +81,7 @@ def test_alembic(app, db): assert not ext.alembic.compare_metadata() ext.alembic.stamp() - ext.alembic.downgrade(target='96e796392533') + ext.alembic.downgrade(target="96e796392533") ext.alembic.upgrade() assert not ext.alembic.compare_metadata() @@ -83,260 +89,271 @@ def test_alembic(app, db): def test_pidstatus_as(): """Test PID status.""" - assert PIDStatus.NEW.title == 'New' - assert PIDStatus.RESERVED.title == 'Reserved' - assert next(iter(PIDStatus)) == 'N' + assert PIDStatus.NEW.title == "New" + assert PIDStatus.RESERVED.title == "Reserved" + assert next(iter(PIDStatus)) == "N" def test_pid_get(app, db): """Test pid retrieval.""" with app.app_context(): - PersistentIdentifier.create('doi', '10.1234/foo') - assert PersistentIdentifier.get('doi', '10.1234/foo') + PersistentIdentifier.create("doi", "10.1234/foo") + assert PersistentIdentifier.get("doi", "10.1234/foo") pytest.raises( - PIDDoesNotExistError, - PersistentIdentifier.get, - 'doi', '10.1234/bar' + PIDDoesNotExistError, PersistentIdentifier.get, "doi", "10.1234/bar" ) # PID with provider - doi = '10.1234/a' - PersistentIdentifier.create('doi', doi, pid_provider='dcite') - assert PersistentIdentifier.get('doi', doi) - assert PersistentIdentifier.get( - 'doi', doi, pid_provider='dcite') + doi = "10.1234/a" + PersistentIdentifier.create("doi", doi, pid_provider="dcite") + assert PersistentIdentifier.get("doi", doi) + assert PersistentIdentifier.get("doi", doi, pid_provider="dcite") pytest.raises( PIDDoesNotExistError, PersistentIdentifier.get, - 'doi', doi, pid_provider='cref' + "doi", + doi, + pid_provider="cref", ) # Retrieve by object myuuid = uuid.uuid4() - doi = '10.1234/b' - PersistentIdentifier.create( - 'doi', doi, object_type='rec', object_uuid=myuuid) - pid = PersistentIdentifier.get_by_object('doi', 'rec', myuuid) + doi = "10.1234/b" + PersistentIdentifier.create("doi", doi, object_type="rec", object_uuid=myuuid) + pid = PersistentIdentifier.get_by_object("doi", "rec", myuuid) assert pid.pid_value == doi pytest.raises( PIDDoesNotExistError, PersistentIdentifier.get_by_object, - 'doi', 'rec', uuid.uuid4() + "doi", + "rec", + uuid.uuid4(), ) -@patch('invenio_pidstore.models.logger') +@patch("invenio_pidstore.models.logger") def test_pid_assign(logger, app, db): """Test pid object assignment.""" with app.app_context(): # No assigned object - pid = PersistentIdentifier.create('doi', '10.1234/foo') + pid = PersistentIdentifier.create("doi", "10.1234/foo") assert not pid.has_object() assert pid.get_assigned_object() is None - assert pid.get_assigned_object('rec') is None + assert pid.get_assigned_object("rec") is None # Assign object rec_uuid = uuid.uuid4() - pid.assign('rec', rec_uuid) + pid.assign("rec", rec_uuid) assert logger.info.call_args[0][0].startswith("Assigned") - assert 'pid' in logger.info.call_args[1]['extra'] + assert "pid" in logger.info.call_args[1]["extra"] assert pid.has_object() assert pid.get_assigned_object() == rec_uuid - assert pid.get_assigned_object('rec') == rec_uuid - assert pid.get_assigned_object('oth') is None + assert pid.get_assigned_object("rec") == rec_uuid + assert pid.get_assigned_object("oth") is None # Doesnt' raise - pid.assign('rec', rec_uuid) + pid.assign("rec", rec_uuid) # Assign without overwrite (uuid as str and uuid) new_uuid = uuid.uuid4() - pytest.raises(PIDObjectAlreadyAssigned, pid.assign, 'rec', new_uuid) - pytest.raises( - PIDObjectAlreadyAssigned, pid.assign, 'rec', str(new_uuid)) + pytest.raises(PIDObjectAlreadyAssigned, pid.assign, "rec", new_uuid) + pytest.raises(PIDObjectAlreadyAssigned, pid.assign, "rec", str(new_uuid)) # Assign with overwrite - pid.assign('rec', str(new_uuid), overwrite=True) + pid.assign("rec", str(new_uuid), overwrite=True) assert pid.has_object() assert pid.get_assigned_object() == new_uuid - assert pid.get_assigned_object('rec') == new_uuid - assert pid.get_assigned_object('oth') is None + assert pid.get_assigned_object("rec") == new_uuid + assert pid.get_assigned_object("oth") is None # Assign with SQLError - pid = PersistentIdentifier.create('recid', '101') - with patch('invenio_pidstore.models.db.session.begin_nested') as mock: + pid = PersistentIdentifier.create("recid", "101") + with patch("invenio_pidstore.models.db.session.begin_nested") as mock: mock.side_effect = SQLAlchemyError() - pytest.raises(SQLAlchemyError, pid.assign, 'rec', uuid.uuid4()) + pytest.raises(SQLAlchemyError, pid.assign, "rec", uuid.uuid4()) -@patch('invenio_pidstore.models.logger') +@patch("invenio_pidstore.models.logger") def test_pid_unassign_noobject(logger, app, db): """Test unassign.""" with app.app_context(): - pid = PersistentIdentifier.create('recid', '101') + pid = PersistentIdentifier.create("recid", "101") assert pid.unassign() - pid.assign('rec', uuid.uuid4()) - with patch('invenio_pidstore.models.db.session.begin_nested') as mock: + pid.assign("rec", uuid.uuid4()) + with patch("invenio_pidstore.models.db.session.begin_nested") as mock: mock.side_effect = SQLAlchemyError() pytest.raises(SQLAlchemyError, pid.unassign) - assert logger.exception.call_args[0][0].startswith( - "Failed to unassign") - assert 'pid' in logger.exception.call_args[1]['extra'] + assert logger.exception.call_args[0][0].startswith("Failed to unassign") + assert "pid" in logger.exception.call_args[1]["extra"] def test_pid_assign_deleted(app, db): """Test pid object assignment.""" with app.app_context(): pid = PersistentIdentifier.create( - 'doi', '10.1234/foo', status=PIDStatus.DELETED) - pytest.raises(PIDInvalidAction, pid.assign, 'rec', uuid.uuid4()) + "doi", "10.1234/foo", status=PIDStatus.DELETED + ) + pytest.raises(PIDInvalidAction, pid.assign, "rec", uuid.uuid4()) -@patch('invenio_pidstore.models.logger') +@patch("invenio_pidstore.models.logger") def test_reserve(logger, app, db): """Test pid reserve.""" with app.app_context(): i = 1 for s in [PIDStatus.NEW, PIDStatus.RESERVED]: - pid = PersistentIdentifier.create('rec', str(i), status=s) + pid = PersistentIdentifier.create("rec", str(i), status=s) i += 1 assert pid.reserve() - assert logger.info.call_args[0][0].startswith( - "Reserved PID") - for s in [PIDStatus.REGISTERED, PIDStatus.DELETED, - PIDStatus.REDIRECTED]: - pid = PersistentIdentifier.create('rec', str(i), status=s) + assert logger.info.call_args[0][0].startswith("Reserved PID") + for s in [PIDStatus.REGISTERED, PIDStatus.DELETED, PIDStatus.REDIRECTED]: + pid = PersistentIdentifier.create("rec", str(i), status=s) i += 1 pytest.raises(PIDInvalidAction, pid.reserve) # Test logging of bad errors. - pid = PersistentIdentifier.create('rec', str(i)) - with patch('invenio_pidstore.models.db.session.begin_nested') as mock: + pid = PersistentIdentifier.create("rec", str(i)) + with patch("invenio_pidstore.models.db.session.begin_nested") as mock: mock.side_effect = SQLAlchemyError() pytest.raises(SQLAlchemyError, pid.reserve) - assert logger.exception.call_args[0][0].startswith( - "Failed to reserve") - assert 'pid' in logger.exception.call_args[1]['extra'] + assert logger.exception.call_args[0][0].startswith("Failed to reserve") + assert "pid" in logger.exception.call_args[1]["extra"] -@patch('invenio_pidstore.models.logger') +@patch("invenio_pidstore.models.logger") def test_register(logger, app, db): """Test pid register.""" with app.app_context(): i = 1 for s in [PIDStatus.NEW, PIDStatus.RESERVED]: - pid = PersistentIdentifier.create('rec', str(i), status=s) + pid = PersistentIdentifier.create("rec", str(i), status=s) i += 1 assert pid.register() - assert logger.info.call_args[0][0].startswith( - "Registered PID") - for s in [PIDStatus.REGISTERED, PIDStatus.DELETED, - PIDStatus.REDIRECTED]: - pid = PersistentIdentifier.create('rec', str(i), status=s) + assert logger.info.call_args[0][0].startswith("Registered PID") + for s in [PIDStatus.REGISTERED, PIDStatus.DELETED, PIDStatus.REDIRECTED]: + pid = PersistentIdentifier.create("rec", str(i), status=s) i += 1 pytest.raises(PIDInvalidAction, pid.register) # Test logging of bad errors. - pid = PersistentIdentifier.create('rec', str(i), - status=PIDStatus.RESERVED) - with patch('invenio_pidstore.models.db.session.begin_nested') as mock: + pid = PersistentIdentifier.create("rec", str(i), status=PIDStatus.RESERVED) + with patch("invenio_pidstore.models.db.session.begin_nested") as mock: mock.side_effect = SQLAlchemyError() pytest.raises(SQLAlchemyError, pid.register) - assert logger.exception.call_args[0][0].startswith( - "Failed to register") - assert 'pid' in logger.exception.call_args[1]['extra'] + assert logger.exception.call_args[0][0].startswith("Failed to register") + assert "pid" in logger.exception.call_args[1]["extra"] -@patch('invenio_pidstore.models.logger') +@patch("invenio_pidstore.models.logger") def test_delete(logger, app, db): """Test pid delete.""" with app.app_context(): i = 1 - for s in [PIDStatus.RESERVED, PIDStatus.RESERVED, - PIDStatus.REDIRECTED, PIDStatus.DELETED]: - pid = PersistentIdentifier.create('rec', str(i), status=s) + for s in [ + PIDStatus.RESERVED, + PIDStatus.RESERVED, + PIDStatus.REDIRECTED, + PIDStatus.DELETED, + ]: + pid = PersistentIdentifier.create("rec", str(i), status=s) i += 1 assert pid.delete() assert logger.info.call_args[0][0] == "Deleted PID." # New persistent identifiers are removed completely count = PersistentIdentifier.query.count() - pid = PersistentIdentifier.create('rec', str(i), status=PIDStatus.NEW) + pid = PersistentIdentifier.create("rec", str(i), status=PIDStatus.NEW) db.session.commit() assert PersistentIdentifier.query.count() == count + 1 pid.delete() assert PersistentIdentifier.query.count() == count assert logger.info.call_args[0][0] == "Deleted PID (removed)." - pid = PersistentIdentifier.create('rec', str(i + 1)) - with patch('invenio_pidstore.models.db.session.begin_nested') as mock: + pid = PersistentIdentifier.create("rec", str(i + 1)) + with patch("invenio_pidstore.models.db.session.begin_nested") as mock: mock.side_effect = SQLAlchemyError() pytest.raises(SQLAlchemyError, pid.delete) - assert logger.exception.call_args[0][0].startswith( - "Failed to delete") - assert 'pid' in logger.exception.call_args[1]['extra'] + assert logger.exception.call_args[0][0].startswith("Failed to delete") + assert "pid" in logger.exception.call_args[1]["extra"] -@patch('invenio_pidstore.models.logger') +@patch("invenio_pidstore.models.logger") def test_redirect(logger, app, db): """Test redirection.""" with app.app_context(): pid1 = PersistentIdentifier.create( - 'rec', '1', status=PIDStatus.REGISTERED, object_type='rec', - object_uuid=uuid.uuid4()) + "rec", + "1", + status=PIDStatus.REGISTERED, + object_type="rec", + object_uuid=uuid.uuid4(), + ) pid2 = PersistentIdentifier.create( - 'doi', '2', status=PIDStatus.REGISTERED, object_type='rec', - object_uuid=uuid.uuid4()) + "doi", + "2", + status=PIDStatus.REGISTERED, + object_type="rec", + object_uuid=uuid.uuid4(), + ) # Can't redirect these statuses i = 10 - for s in [PIDStatus.NEW, PIDStatus.RESERVED, PIDStatus.DELETED, ]: - pid = PersistentIdentifier.create('rec', str(i), status=s) + for s in [ + PIDStatus.NEW, + PIDStatus.RESERVED, + PIDStatus.DELETED, + ]: + pid = PersistentIdentifier.create("rec", str(i), status=s) i += 1 pytest.raises(PIDInvalidAction, pid.redirect, pid1) - pid = PersistentIdentifier.create( - 'rec', str(i), status=PIDStatus.REGISTERED) + pid = PersistentIdentifier.create("rec", str(i), status=PIDStatus.REGISTERED) # Can't redirect to non-exsting pid. - pytest.raises(PIDDoesNotExistError, pid.redirect, - PersistentIdentifier()) + pytest.raises(PIDDoesNotExistError, pid.redirect, PersistentIdentifier()) pid.redirect(pid1) assert logger.info.call_args[0][0].startswith("Redirected") - assert 'pid' in logger.info.call_args[1]['extra'] + assert "pid" in logger.info.call_args[1]["extra"] assert pid.status == PIDStatus.REDIRECTED assert pid.object_type is None assert pid.object_uuid is not None new_pid = pid.get_redirect() - assert new_pid.pid_type == 'rec' - assert new_pid.pid_value == '1' + assert new_pid.pid_type == "rec" + assert new_pid.pid_value == "1" # You can redirect an already redirected pid pid.redirect(pid2) new_pid = pid.get_redirect() - assert new_pid.pid_type == 'doi' - assert new_pid.pid_value == '2' + assert new_pid.pid_type == "doi" + assert new_pid.pid_value == "2" # Assign with SQLError - with patch('invenio_pidstore.models.db.session.begin_nested') as mock: + with patch("invenio_pidstore.models.db.session.begin_nested") as mock: mock.side_effect = SQLAlchemyError() - pytest.raises(SQLAlchemyError, pid.redirect, '1') - assert logger.exception.call_args[0][0].startswith( - "Failed to redirect") - assert 'pid' in logger.exception.call_args[1]['extra'] + pytest.raises(SQLAlchemyError, pid.redirect, "1") + assert logger.exception.call_args[0][0].startswith("Failed to redirect") + assert "pid" in logger.exception.call_args[1]["extra"] def test_redirect_cleanup(app, db): """Test proper clean up from redirects.""" with app.app_context(): pid1 = PersistentIdentifier.create( - 'recid', '1', status=PIDStatus.REGISTERED, object_type='rec', - object_uuid=uuid.uuid4()) + "recid", + "1", + status=PIDStatus.REGISTERED, + object_type="rec", + object_uuid=uuid.uuid4(), + ) pid2 = PersistentIdentifier.create( - 'recid', '2', status=PIDStatus.REGISTERED, object_type='rec', - object_uuid=uuid.uuid4()) - pid3 = PersistentIdentifier.create( - 'recid', '3', status=PIDStatus.REGISTERED) + "recid", + "2", + status=PIDStatus.REGISTERED, + object_type="rec", + object_uuid=uuid.uuid4(), + ) + pid3 = PersistentIdentifier.create("recid", "3", status=PIDStatus.REGISTERED) db.session.commit() assert Redirect.query.count() == 0 @@ -344,19 +361,22 @@ def test_redirect_cleanup(app, db): assert Redirect.query.count() == 1 pid3.redirect(pid2) assert Redirect.query.count() == 1 - pytest.raises( - PIDObjectAlreadyAssigned, pid3.assign, 'rec', uuid.uuid4()) + pytest.raises(PIDObjectAlreadyAssigned, pid3.assign, "rec", uuid.uuid4()) pid3.unassign() assert Redirect.query.count() == 0 -@patch('invenio_pidstore.models.logger') +@patch("invenio_pidstore.models.logger") def test_sync_status(logger, app, db): """Test sync status.""" with app.app_context(): pid = PersistentIdentifier.create( - 'rec', '1', status=PIDStatus.REGISTERED, object_type='rec', - object_uuid=uuid.uuid4()) + "rec", + "1", + status=PIDStatus.REGISTERED, + object_type="rec", + object_uuid=uuid.uuid4(), + ) pytest.raises(PIDInvalidAction, pid.reserve) calls = logger.info.call_count assert pid.sync_status(PIDStatus.NEW) @@ -366,23 +386,26 @@ def test_sync_status(logger, app, db): assert pid.sync_status(PIDStatus.RESERVED) assert logger.info.call_count == calls - with patch('invenio_pidstore.models.db.session.begin_nested') as mock: + with patch("invenio_pidstore.models.db.session.begin_nested") as mock: mock.side_effect = SQLAlchemyError() pytest.raises(SQLAlchemyError, pid.sync_status, PIDStatus.NEW) - assert logger.exception.call_args[0][0].startswith( - "Failed to sync status") - assert 'pid' in logger.exception.call_args[1]['extra'] + assert logger.exception.call_args[0][0].startswith("Failed to sync status") + assert "pid" in logger.exception.call_args[1]["extra"] def test_repr(app, db): """Test representation.""" with app.app_context(): pid = PersistentIdentifier.create( - 'recid', '1', status=PIDStatus.REGISTERED, object_type='rec', - object_uuid='de3bb351-bc1a-4e51-8605-c6cd9589a560') - assert str(pid) == \ - "" - pid = PersistentIdentifier.create( - 'recid', '2', status=PIDStatus.REGISTERED) + ) + pid = PersistentIdentifier.create("recid", "2", status=PIDStatus.REGISTERED) assert str(pid) == "" diff --git a/tests/test_minters.py b/tests/test_minters.py index 7b99305..63ae85a 100644 --- a/tests/test_minters.py +++ b/tests/test_minters.py @@ -10,9 +10,10 @@ from __future__ import absolute_import, print_function -import pytest import uuid +import pytest + from invenio_pidstore import current_pidstore from invenio_pidstore.minters import recid_minter, recid_minter_v2 @@ -26,8 +27,8 @@ def test_recid_minter(app, db): pid = recid_minter(rec_uuid, data) assert pid - assert data[app.config['PIDSTORE_RECID_FIELD']] == pid.pid_value - assert pid.object_type == 'rec' + assert data[app.config["PIDSTORE_RECID_FIELD"]] == pid.pid_value + assert pid.object_type == "rec" assert pid.object_uuid == rec_uuid @@ -36,20 +37,20 @@ def test_recid_minter_v2(app, db): with app.app_context(): rec_uuid = uuid.uuid4() data = {} - recid_field = app.config['PIDSTORE_RECID_FIELD'] + recid_field = app.config["PIDSTORE_RECID_FIELD"] pid = recid_minter_v2(rec_uuid, data) assert pid assert data[recid_field] == pid.pid_value - assert pid.object_type == 'rec' + assert pid.object_type == "rec" assert pid.object_uuid == rec_uuid with pytest.raises(AssertionError): - recid_minter_v2(rec_uuid, {recid_field: '1'}) + recid_minter_v2(rec_uuid, {recid_field: "1"}) def test_register_minter(app): """Test base provider.""" with app.app_context(): - current_pidstore.register_minter('anothername', recid_minter) + current_pidstore.register_minter("anothername", recid_minter) diff --git a/tests/test_providers.py b/tests/test_providers.py index 18920cf..c791c5d 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -11,10 +11,16 @@ from __future__ import absolute_import, print_function -import pytest import uuid -from datacite.errors import DataCiteError, DataCiteGoneError, \ - DataCiteNoContentError, DataCiteNotFoundError, HttpError + +import pytest +from datacite.errors import ( + DataCiteError, + DataCiteGoneError, + DataCiteNoContentError, + DataCiteNotFoundError, + HttpError, +) from mock import MagicMock, patch from invenio_pidstore.models import PIDStatus @@ -27,10 +33,10 @@ def test_base_provider(app, db): """Test base provider.""" with app.app_context(): - provider = BaseProvider.create(pid_type='test', pid_value='test') + provider = BaseProvider.create(pid_type="test", pid_value="test") assert provider.pid - assert provider.pid.pid_type == 'test' - assert provider.pid.pid_value == 'test' + assert provider.pid.pid_type == "test" + assert provider.pid.pid_value == "test" assert provider.pid.pid_provider is None assert provider.pid.status == PIDStatus.NEW assert provider.pid.object_type is None @@ -49,19 +55,19 @@ def test_base_provider(app, db): provider.update() class TestProvider(BaseProvider): - pid_type = 't' - pid_provider = 'testpr' + pid_type = "t" + pid_provider = "testpr" default_status = PIDStatus.RESERVED with app.app_context(): - provider = TestProvider.create(pid_value='test') + provider = TestProvider.create(pid_value="test") assert provider.pid - assert provider.pid.pid_type == 't' - assert provider.pid.pid_provider == 'testpr' - assert provider.pid.pid_value == 'test' + assert provider.pid.pid_type == "t" + assert provider.pid.pid_provider == "testpr" + assert provider.pid.pid_value == "test" assert provider.pid.status == PIDStatus.RESERVED - assert TestProvider.get('test') + assert TestProvider.get("test") def test_recordid_provider(app, db): @@ -69,8 +75,8 @@ def test_recordid_provider(app, db): with app.app_context(): provider = RecordIdProvider.create() assert provider.pid - assert provider.pid.pid_type == 'recid' - assert provider.pid.pid_value == '1' + assert provider.pid.pid_type == "recid" + assert provider.pid.pid_value == "1" assert provider.pid.pid_provider is None assert provider.pid.status == PIDStatus.RESERVED assert provider.pid.object_type is None @@ -78,17 +84,16 @@ def test_recordid_provider(app, db): # Assign to object immediately rec_uuid = uuid.uuid4() - provider = RecordIdProvider.create( - object_type='rec', object_uuid=rec_uuid) + provider = RecordIdProvider.create(object_type="rec", object_uuid=rec_uuid) assert provider.pid - assert provider.pid.pid_type == 'recid' - assert provider.pid.pid_value == '2' + assert provider.pid.pid_type == "recid" + assert provider.pid.pid_value == "2" assert provider.pid.pid_provider is None assert provider.pid.status == PIDStatus.REGISTERED - assert provider.pid.object_type == 'rec' + assert provider.pid.object_type == "rec" assert provider.pid.object_uuid == rec_uuid - pytest.raises(AssertionError, RecordIdProvider.create, pid_value='3') + pytest.raises(AssertionError, RecordIdProvider.create, pid_value="3") def test_recordid_provider_v2(app, db): @@ -96,9 +101,9 @@ def test_recordid_provider_v2(app, db): with app.app_context(): provider = RecordIdProviderV2.create() assert provider.pid - assert provider.pid.pid_type == 'recid' + assert provider.pid.pid_type == "recid" pid_value = provider.pid.pid_value - part1, part2 = pid_value.split('-') + part1, part2 = pid_value.split("-") assert len(part1) == 5 assert len(part2) == 5 assert provider.pid.pid_provider is None @@ -108,27 +113,26 @@ def test_recordid_provider_v2(app, db): # Assign to object immediately rec_uuid = uuid.uuid4() - provider = RecordIdProviderV2.create( - object_type='rec', object_uuid=rec_uuid) + provider = RecordIdProviderV2.create(object_type="rec", object_uuid=rec_uuid) assert provider.pid - assert provider.pid.pid_type == 'recid' + assert provider.pid.pid_type == "recid" pid_value = provider.pid.pid_value - part1, part2 = pid_value.split('-') + part1, part2 = pid_value.split("-") assert len(part1) == 5 assert len(part2) == 5 assert provider.pid.pid_provider is None assert provider.pid.status == PIDStatus.REGISTERED - assert provider.pid.object_type == 'rec' + assert provider.pid.object_type == "rec" assert provider.pid.object_uuid == rec_uuid - pytest.raises(AssertionError, RecordIdProviderV2.create, pid_value='3') + pytest.raises(AssertionError, RecordIdProviderV2.create, pid_value="3") # Options provider = RecordIdProviderV2.create( - options={'length': 3, 'checksum': True, 'split_every': 1} + options={"length": 3, "checksum": True, "split_every": 1} ) pid_value = provider.pid.pid_value - part1, part2, part3 = pid_value.split('-') + part1, part2, part3 = pid_value.split("-") assert len(part1) == 1 assert len(part2) == 1 assert len(part3) == 1 @@ -137,21 +141,21 @@ def test_recordid_provider_v2(app, db): def test_datacite_create_get(app, db): """Test datacite provider create/get.""" with app.app_context(): - provider = DataCiteProvider.create('10.1234/a') + provider = DataCiteProvider.create("10.1234/a") assert provider.pid.status == PIDStatus.NEW - assert provider.pid.pid_provider == 'datacite' + assert provider.pid.pid_provider == "datacite" # Crete passing client kwarg to provider object creation - provider = DataCiteProvider.create('10.1234/b', client=MagicMock()) + provider = DataCiteProvider.create("10.1234/b", client=MagicMock()) assert provider.pid.status == PIDStatus.NEW - assert provider.pid.pid_provider == 'datacite' + assert provider.pid.pid_provider == "datacite" assert isinstance(provider.api, MagicMock) - provider = DataCiteProvider.get('10.1234/a') + provider = DataCiteProvider.get("10.1234/a") assert provider.pid.status == PIDStatus.NEW - assert provider.pid.pid_provider == 'datacite' + assert provider.pid.pid_provider == "datacite" - provider = DataCiteProvider.get('10.1234/a', client=MagicMock()) + provider = DataCiteProvider.get("10.1234/a", client=MagicMock()) assert isinstance(provider.api, MagicMock) @@ -159,89 +163,86 @@ def test_datacite_reserve_register_update_delete(app, db): """Test datacite provider reserve.""" with app.app_context(): api = MagicMock() - provider = DataCiteProvider.create('10.1234/a', client=api) - assert provider.reserve('mydoc') + provider = DataCiteProvider.create("10.1234/a", client=api) + assert provider.reserve("mydoc") assert provider.pid.status == PIDStatus.RESERVED - api.metadata_post.assert_called_with('mydoc') + api.metadata_post.assert_called_with("mydoc") - assert provider.register('myurl', 'anotherdoc') + assert provider.register("myurl", "anotherdoc") assert provider.pid.status == PIDStatus.REGISTERED - api.metadata_post.assert_called_with('anotherdoc') - api.doi_post.assert_called_with('10.1234/a', 'myurl') + api.metadata_post.assert_called_with("anotherdoc") + api.doi_post.assert_called_with("10.1234/a", "myurl") - assert provider.update('anotherurl', 'yetanother') + assert provider.update("anotherurl", "yetanother") assert provider.pid.status == PIDStatus.REGISTERED - api.metadata_post.assert_called_with('yetanother') - api.doi_post.assert_called_with('10.1234/a', 'anotherurl') + api.metadata_post.assert_called_with("yetanother") + api.doi_post.assert_called_with("10.1234/a", "anotherurl") assert provider.delete() assert provider.pid.status == PIDStatus.DELETED - api.metadata_delete.assert_called_with('10.1234/a') + api.metadata_delete.assert_called_with("10.1234/a") - assert provider.update('newurl', 'newdoc') + assert provider.update("newurl", "newdoc") assert provider.pid.status == PIDStatus.REGISTERED - api.metadata_post.assert_called_with('newdoc') - api.doi_post.assert_called_with('10.1234/a', 'newurl') + api.metadata_post.assert_called_with("newdoc") + api.doi_post.assert_called_with("10.1234/a", "newurl") -@patch('invenio_pidstore.providers.datacite.logger') +@patch("invenio_pidstore.providers.datacite.logger") def test_datacite_error_reserve(logger, app, db): """Test reserve errors.""" with app.app_context(): api = MagicMock() - provider = DataCiteProvider.create('10.1234/a', client=api) + provider = DataCiteProvider.create("10.1234/a", client=api) api.metadata_post.side_effect = DataCiteError pytest.raises(DataCiteError, provider.reserve, "testdoc") - assert logger.exception.call_args[0][0] == \ - "Failed to reserve in DataCite" + assert logger.exception.call_args[0][0] == "Failed to reserve in DataCite" -@patch('invenio_pidstore.providers.datacite.logger') +@patch("invenio_pidstore.providers.datacite.logger") def test_datacite_error_register_update(logger, app, db): """Test register errors.""" with app.app_context(): api = MagicMock() - provider = DataCiteProvider.create('10.1234/a', client=api) + provider = DataCiteProvider.create("10.1234/a", client=api) api.doi_post.side_effect = DataCiteError pytest.raises(DataCiteError, provider.register, "testurl", "testdoc") - assert logger.exception.call_args[0][0] == \ - "Failed to register in DataCite" + assert logger.exception.call_args[0][0] == "Failed to register in DataCite" pytest.raises(DataCiteError, provider.update, "testurl", "testdoc") - assert logger.exception.call_args[0][0] == \ - "Failed to update in DataCite" + assert logger.exception.call_args[0][0] == "Failed to update in DataCite" -@patch('invenio_pidstore.providers.datacite.logger') +@patch("invenio_pidstore.providers.datacite.logger") def test_datacite_error_delete(logger, app, db): """Test reserve errors.""" with app.app_context(): api = MagicMock() - provider = DataCiteProvider.create('10.1234/a', client=api) + provider = DataCiteProvider.create("10.1234/a", client=api) # DOIs in new state doesn't contact datacite api.metadata_delete.side_effect = DataCiteError assert provider.delete() # Already registered DOIs do contact datacite to delete - provider = DataCiteProvider.create('10.1234/b', client=api, - status=PIDStatus.REGISTERED) + provider = DataCiteProvider.create( + "10.1234/b", client=api, status=PIDStatus.REGISTERED + ) api.metadata_delete.side_effect = DataCiteError pytest.raises(DataCiteError, provider.delete) - assert logger.exception.call_args[0][0] == \ - "Failed to delete in DataCite" + assert logger.exception.call_args[0][0] == "Failed to delete in DataCite" -@patch('invenio_pidstore.providers.datacite.logger') +@patch("invenio_pidstore.providers.datacite.logger") def test_datacite_sync(logger, app, db): """Test sync.""" with app.app_context(): api = MagicMock() - provider = DataCiteProvider.create('10.1234/a', client=api) + provider = DataCiteProvider.create("10.1234/a", client=api) assert provider.pid.status == PIDStatus.NEW # Status can be set from api.doi_get reply @@ -283,25 +284,20 @@ def test_datacite_sync(logger, app, db): assert provider.pid.status == PIDStatus.NEW pytest.raises(HttpError, provider.sync_status) assert provider.pid.status == PIDStatus.NEW - assert logger.exception.call_args[0][0] == \ - "Failed to sync status from DataCite" + assert logger.exception.call_args[0][0] == "Failed to sync status from DataCite" -@patch('invenio_pidstore.providers.recordid_v2.base32') +@patch("invenio_pidstore.providers.recordid_v2.base32") def test_recordidv2_generate_id_calls_base32_generate(patched_base32, app): - original_options = app.config.get('PIDSTORE_RECORDID_OPTIONS') - app.config['PIDSTORE_RECORDID_OPTIONS'] = { - 'length': 8, - 'split_every': 4, - 'checksum': False + original_options = app.config.get("PIDSTORE_RECORDID_OPTIONS") + app.config["PIDSTORE_RECORDID_OPTIONS"] = { + "length": 8, + "split_every": 4, + "checksum": False, } RecordIdProviderV2.generate_id() - patched_base32.generate.assert_called_with( - length=8, - split_every=4, - checksum=False - ) + patched_base32.generate.assert_called_with(length=8, split_every=4, checksum=False) - app.config['PIDSTORE_RECORDID_OPTIONS'] = original_options + app.config["PIDSTORE_RECORDID_OPTIONS"] = original_options diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 559a8dc..4534568 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -10,11 +10,17 @@ from __future__ import absolute_import, print_function -import pytest import uuid -from invenio_pidstore.errors import PIDDeletedError, PIDDoesNotExistError, \ - PIDMissingObjectError, PIDRedirectedError, PIDUnregistered +import pytest + +from invenio_pidstore.errors import ( + PIDDeletedError, + PIDDoesNotExistError, + PIDMissingObjectError, + PIDRedirectedError, + PIDUnregistered, +) from invenio_pidstore.models import PersistentIdentifier, PIDStatus from invenio_pidstore.resolver import Resolver @@ -34,75 +40,72 @@ def test_resolver(app, db): # Create pids for each status with and without object for s in status: - PersistentIdentifier.create('recid', i, status=s) + PersistentIdentifier.create("recid", i, status=s) i += 1 if s != PIDStatus.DELETED: PersistentIdentifier.create( - 'recid', i, status=s, object_type='rec', object_uuid=rec_a) + "recid", i, status=s, object_type="rec", object_uuid=rec_a + ) i += 1 # Create a DOI pid_doi = PersistentIdentifier.create( - 'doi', '10.1234/foo', status=PIDStatus.REGISTERED, - object_type='rec', object_uuid=rec_a) + "doi", + "10.1234/foo", + status=PIDStatus.REGISTERED, + object_type="rec", + object_uuid=rec_a, + ) # Create redirects - pid = PersistentIdentifier.create( - 'recid', i, status=PIDStatus.REGISTERED) + pid = PersistentIdentifier.create("recid", i, status=PIDStatus.REGISTERED) i += 1 - pid.redirect(PersistentIdentifier.get('recid', '2')) - pid = PersistentIdentifier.create( - 'recid', i, status=PIDStatus.REGISTERED) + pid.redirect(PersistentIdentifier.get("recid", "2")) + pid = PersistentIdentifier.create("recid", i, status=PIDStatus.REGISTERED) pid.redirect(pid_doi) db.session.commit() # Start tests - resolver = Resolver( - pid_type='recid', - object_type='rec', - getter=lambda x: x) + resolver = Resolver(pid_type="recid", object_type="rec", getter=lambda x: x) # Resolve non-existing pid - pytest.raises(PIDDoesNotExistError, resolver.resolve, '100') - pytest.raises(PIDDoesNotExistError, resolver.resolve, '10.1234/foo') + pytest.raises(PIDDoesNotExistError, resolver.resolve, "100") + pytest.raises(PIDDoesNotExistError, resolver.resolve, "10.1234/foo") # Resolve status new - pytest.raises(PIDUnregistered, resolver.resolve, '1') - pytest.raises(PIDUnregistered, resolver.resolve, '2') + pytest.raises(PIDUnregistered, resolver.resolve, "1") + pytest.raises(PIDUnregistered, resolver.resolve, "2") # Resolve status reserved - pytest.raises(PIDUnregistered, resolver.resolve, '3') - pytest.raises(PIDUnregistered, resolver.resolve, '4') + pytest.raises(PIDUnregistered, resolver.resolve, "3") + pytest.raises(PIDUnregistered, resolver.resolve, "4") # Resolve status registered - pytest.raises(PIDMissingObjectError, resolver.resolve, '5') - pid, obj = resolver.resolve('6') + pytest.raises(PIDMissingObjectError, resolver.resolve, "5") + pid, obj = resolver.resolve("6") assert pid and obj == rec_a # Resolve status deleted - pytest.raises(PIDDeletedError, resolver.resolve, '7') + pytest.raises(PIDDeletedError, resolver.resolve, "7") # Resolve status redirected try: - resolver.resolve('8') + resolver.resolve("8") assert False except PIDRedirectedError as e: - assert e.destination_pid.pid_type == 'recid' - assert e.destination_pid.pid_value == '2' + assert e.destination_pid.pid_type == "recid" + assert e.destination_pid.pid_value == "2" try: - resolver.resolve('9') + resolver.resolve("9") assert False except PIDRedirectedError as e: - assert e.destination_pid.pid_type == 'doi' - assert e.destination_pid.pid_value == '10.1234/foo' - - doiresolver = Resolver( - pid_type='doi', - object_type='rec', - getter=lambda x: x) - pytest.raises(PIDDoesNotExistError, doiresolver.resolve, '1') - pid, obj = doiresolver.resolve('10.1234/foo') + assert e.destination_pid.pid_type == "doi" + assert e.destination_pid.pid_value == "10.1234/foo" + + doiresolver = Resolver(pid_type="doi", object_type="rec", getter=lambda x: x) + pytest.raises(PIDDoesNotExistError, doiresolver.resolve, "1") + pid, obj = doiresolver.resolve("10.1234/foo") assert pid and obj == rec_a @@ -111,59 +114,60 @@ def test_resolver_deleted_object(app, db): with app.app_context(): rec_uuid = uuid.uuid4() records = { - rec_uuid: {'title': 'test'}, + rec_uuid: {"title": "test"}, } with db.session.begin_nested(): pid = PersistentIdentifier.create( - 'recid', '1', status=PIDStatus.REGISTERED, - object_type='rec', object_uuid=rec_uuid) + "recid", + "1", + status=PIDStatus.REGISTERED, + object_type="rec", + object_uuid=rec_uuid, + ) with db.session.begin_nested(): pid.delete() - resolver = Resolver( - pid_type='recid', object_type='rec', getter=records.get) + resolver = Resolver(pid_type="recid", object_type="rec", getter=records.get) - assert pytest.raises(PIDDeletedError, resolver.resolve, '1') + assert pytest.raises(PIDDeletedError, resolver.resolve, "1") def test_resolver_not_registered_only(app, db): """Test the resolver returns a new and reserved PID when specified.""" - status = [ - PIDStatus.NEW, - PIDStatus.RESERVED, - PIDStatus.REGISTERED - ] + status = [PIDStatus.NEW, PIDStatus.RESERVED, PIDStatus.REGISTERED] with app.app_context(): rec_a = uuid.uuid4() # Create pids for each status with and without object for idx, s in enumerate(status, 1): - PersistentIdentifier.create('recid', idx * 2 - 1, status=s) - PersistentIdentifier.create('recid', idx * 2, status=s, - object_type='rec', object_uuid=rec_a) + PersistentIdentifier.create("recid", idx * 2 - 1, status=s) + PersistentIdentifier.create( + "recid", idx * 2, status=s, object_type="rec", object_uuid=rec_a + ) db.session.commit() # Start tests resolver = Resolver( - pid_type='recid', - object_type='rec', + pid_type="recid", + object_type="rec", getter=lambda x: x, - registered_only=False) + registered_only=False, + ) # Resolve status new - pytest.raises(PIDMissingObjectError, resolver.resolve, '1') - pid, obj = resolver.resolve('2') + pytest.raises(PIDMissingObjectError, resolver.resolve, "1") + pid, obj = resolver.resolve("2") assert pid and obj == rec_a # Resolve status reserved - pytest.raises(PIDMissingObjectError, resolver.resolve, '3') - pid, obj = resolver.resolve('4') + pytest.raises(PIDMissingObjectError, resolver.resolve, "3") + pid, obj = resolver.resolve("4") assert pid and obj == rec_a # Resolve status registered - pytest.raises(PIDMissingObjectError, resolver.resolve, '5') - pid, obj = resolver.resolve('6') + pytest.raises(PIDMissingObjectError, resolver.resolve, "5") + pid, obj = resolver.resolve("6") assert pid and obj == rec_a From 8ccc310ca3afb8be3093e5952ebe8cccddf46498 Mon Sep 17 00:00:00 2001 From: Christoph Ladurner Date: Wed, 8 Jun 2022 23:17:48 +0200 Subject: [PATCH 20/25] add .git-blame-ignore-revs --- .git-blame-ignore-revs | 1 + MANIFEST.in | 1 + 2 files changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..411a2a6 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +8849a1eabd505ddfecfcca09ca1744f8c96a6747 diff --git a/MANIFEST.in b/MANIFEST.in index c55a7d6..388cd39 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -30,3 +30,4 @@ recursive-include invenio_pidstore *.pot recursive-include invenio_pidstore *.py recursive-include tests *.py recursive-include .github/workflows *.yml +include .git-blame-ignore-revs From 5df2955992065ff19bd58c6abb73e8f641f0a0b5 Mon Sep 17 00:00:00 2001 From: Christoph Ladurner Date: Wed, 8 Jun 2022 23:17:50 +0200 Subject: [PATCH 21/25] fix docs compatibilty problem with Sphinx>=5.0.0 --- docs/conf.py | 3 ++- setup.cfg | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 3b330b0..319d43c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,6 +2,7 @@ # # This file is part of Invenio. # Copyright (C) 2015-2018 CERN. +# Copyright (C) 2022 Graz University of Technology. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -71,7 +72,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/setup.cfg b/setup.cfg index f7f7ab9..1a64037 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,7 @@ tests = SQLAlchemy-Continuum>=1.3.11 invenio-admin>=1.2.0 datacite>=0.1.0 - Sphinx>=4.2.0,<5.0.0 + Sphinx>=4.5.0 invenio-db[mysql,postgresql,versioning]>=1.0.9,<2.0.0 [options.entry_points] From 39efa28038dd7dab20321e120fb0c4826fdb9fcc Mon Sep 17 00:00:00 2001 From: Christoph Ladurner Date: Wed, 8 Jun 2022 23:17:50 +0200 Subject: [PATCH 22/25] move check_manifest configuration to setup.cfg. concentrate the configuration of all calls in one place --- run-tests.sh | 3 ++- setup.cfg | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index 952c879..5384520 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -3,6 +3,7 @@ # # This file is part of Invenio. # Copyright (C) 2020 CERN. +# Copyright (C) 2022 Graz University of Technology. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -20,7 +21,7 @@ function cleanup() { trap cleanup EXIT -python -m check_manifest --ignore ".*-requirements.txt" +python -m check_manifest python -m sphinx.cmd.build -qnNW docs docs/_build/html eval "$(docker-services-cli up --db ${DB:-postgresql} --cache ${CACHE:-redis} --env)" python -m pytest diff --git a/setup.cfg b/setup.cfg index 1a64037..f880fc8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -99,6 +99,10 @@ output-dir = invenio_pidstore/translations/ [isort] profile=black +[check-manifest] +ignore = + *-requirements.txt + [tool:pytest] doctest_optionflags = DONT_ACCEPT_TRUE_FOR_1 ELLIPSIS IGNORE_EXCEPTION_DETAIL addopts = --black --isort --pydocstyle --doctest-glob="*.rst" --doctest-modules --cov=invenio_pidstore --cov-report=term-missing From d436f0acbf32b2479b8e215ffb2a47a64b296b92 Mon Sep 17 00:00:00 2001 From: Christoph Ladurner Date: Wed, 8 Jun 2022 23:17:50 +0200 Subject: [PATCH 23/25] increase minimal python version to 3.7 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f880fc8..e7be796 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ classifiers = [options] include_package_data = True packages = find: -python_requires = >=3.6 +python_requires = >=3.7 zip_safe = False install_requires = base32-lib>=1.0.1 From d4f571aac2e687bb40fa3ca82511ac32ed479c0a Mon Sep 17 00:00:00 2001 From: Christoph Ladurner Date: Wed, 8 Jun 2022 23:21:17 +0200 Subject: [PATCH 24/25] global: clean test infrastructure --- .github/workflows/tests.yml | 22 +++++----------------- tests/test_cli.py | 6 +++--- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7951cf8..9d389b8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,6 +2,7 @@ # # This file is part of Invenio. # Copyright (C) 2020 CERN. +# Copyright (C) 2022 Graz University of Technology. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -28,25 +29,12 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] - requirements-level: [min, pypi] + python-version: [3.7, 3.8, 3.9] + requirements-level: [pypi] cache-service: [redis] - db-service: [postgresql9, postgresql13, mysql5, mysql8] - exclude: - - python-version: 3.8 - requirements-level: min - - - python-version: 3.9 - requirements-level: min - - - db-service: postgresql13 - python-version: 3.6 - - - db-service: mysql8 - python-version: 3.6 - + db-service: [postgresql10, postgresql13, mysql5, mysql8] include: - - db-service: postgresql9 + - db-service: postgresql10 DB_EXTRAS: "postgresql" - db-service: postgresql13 diff --git a/tests/test_cli.py b/tests/test_cli.py index 119cfe2..ba437ec 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -23,7 +23,7 @@ def test_pid_creation(app, db): """Test pid creation.""" runner = CliRunner() - script_info = ScriptInfo(create_app=lambda info: app) + script_info = ScriptInfo(create_app=lambda: app) with runner.isolated_filesystem(): with app.app_context(): @@ -133,7 +133,7 @@ def test_pid_creation(app, db): def test_pid_assign(app, db): """Test pid object assignment.""" runner = CliRunner() - script_info = ScriptInfo(create_app=lambda info: app) + script_info = ScriptInfo(create_app=lambda: app) with runner.isolated_filesystem(): # No assigned object @@ -248,7 +248,7 @@ def test_pid_assign(app, db): def test_pid_unassign(app, db): """Test pid object unassignment.""" runner = CliRunner() - script_info = ScriptInfo(create_app=lambda info: app) + script_info = ScriptInfo(create_app=lambda: app) with runner.isolated_filesystem(): rec_uuid = uuid.uuid4() From d46dae6b932ab375439c4e1168e9671ef9ed33ff Mon Sep 17 00:00:00 2001 From: ctrl-alt-soneca <79139616+ctrl-alt-soneca@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:11:05 +0100 Subject: [PATCH 25/25] models: logging strings unicode-friendly --- invenio_pidstore/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/invenio_pidstore/models.py b/invenio_pidstore/models.py index 2714422..1c5c757 100644 --- a/invenio_pidstore/models.py +++ b/invenio_pidstore/models.py @@ -161,7 +161,7 @@ def create( obj.assign(object_type, object_uuid) db.session.add(obj) logger.info( - "Created PID {0}:{1}".format(pid_type, pid_value), extra={"pid": obj} + u"Created PID {0}:{1}".format(pid_type, pid_value), extra={"pid": obj} ) except IntegrityError: logger.exception( @@ -332,7 +332,7 @@ def unassign(self): except SQLAlchemyError: logger.exception("Failed to unassign object.", extra=dict(pid=self)) raise - logger.info("Unassigned object from {0}.".format(self), extra=dict(pid=self)) + logger.info(u"Unassigned object from {0}.".format(self), extra=dict(pid=self)) return True def get_redirect(self): @@ -379,7 +379,7 @@ def redirect(self, pid): except SQLAlchemyError: logger.exception("Failed to redirect to %s", pid, extra=dict(pid=self)) raise - logger.info("Redirected PID to {0}".format(pid), extra=dict(pid=self)) + logger.info(u"Redirected PID to {0}".format(pid), extra=dict(pid=self)) return True def reserve(self): @@ -477,7 +477,7 @@ def sync_status(self, status): except SQLAlchemyError: logger.exception("Failed to sync status %s.", status, extra=dict(pid=self)) raise - logger.info("Synced PID status to {0}.".format(status), extra=dict(pid=self)) + logger.info(u"Synced PID status to {0}.".format(status), extra=dict(pid=self)) return True def is_redirected(self):