diff --git a/.editorconfig b/.editorconfig index 020cf98..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 @@ -34,8 +23,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/.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/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 0000000..2657cbc --- /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 babel + + - 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..9d389b8 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# +# 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. + +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.7, 3.8, 3.9] + requirements-level: [pypi] + cache-service: [redis] + db-service: [postgresql10, postgresql13, mysql5, mysql8] + include: + - db-service: postgresql10 + DB_EXTRAS: "postgresql" + + - db-service: postgresql13 + 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: tests + 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: | + 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 + 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 0f8b381..0000000 --- a/.travis.yml +++ /dev/null @@ -1,78 +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 - -sudo: false - -language: python - -dist: trusty - -cache: - - pip - -services: - - mysql - - postgresql - - redis - -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" - - 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" - -python: - - "2.7" - - "3.5" - -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" - -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 .[${EXTRAS}]" - -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: "2.7" - condition: $REQUIREMENTS = release diff --git a/CHANGES.rst b/CHANGES.rst index 18066d2..c91e062 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,28 @@ 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 + 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. +- 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 +- 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/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..388cd39 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -29,3 +29,5 @@ recursive-include invenio_pidstore *.po recursive-include invenio_pidstore *.pot recursive-include invenio_pidstore *.py recursive-include tests *.py +recursive-include .github/workflows *.yml +include .git-blame-ignore-revs 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/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/docs/conf.py b/docs/conf.py index f616fe7..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. @@ -10,51 +11,52 @@ from __future__ import print_function -import os 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 ------------------------------------------------ # 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'] +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' +# 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 @@ -62,28 +64,21 @@ # # 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. # # 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: -#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,46 +86,46 @@ # 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' +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 # -- 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 @@ -139,146 +134,148 @@ # 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 = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', - 'searchbox.html', - 'donate.html', + "**": [ + "about.html", + "navigation.html", + "relations.html", + "searchbox.html", + "donate.html", ] } # 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' +htmlhelp_basename = "invenio-pidstore_namedoc" # -- 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", + "invenio-pidstore Documentation", + "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 +283,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", "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,29 +296,35 @@ # (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", + "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. 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 6d97a57..64315a1 100644 --- a/examples/app.py +++ b/examples/app.py @@ -70,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 348a2fc..50b5d52 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__ -__all__ = ('__version__', 'InvenioPIDStore', 'current_pidstore') +__version__ = "1.2.3" + +__all__ = ("__version__", "InvenioPIDStore", "current_pidstore") diff --git a/invenio_pidstore/admin.py b/invenio_pidstore/admin.py index b503d0f..c4f6efa 100644 --- a/invenio_pidstore/admin.py +++ b/invenio_pidstore/admin.py @@ -25,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): @@ -52,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 @@ -78,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 4700cef..a26897a 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 @@ -35,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 = {} @@ -52,8 +55,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 +64,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,16 +80,19 @@ 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()) 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. @@ -96,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: @@ -130,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: - pkg_resources.get_distribution('invenio-records') - app.config.setdefault('PIDSTORE_OBJECT_ENDPOINTS', dict( - rec='recordmetadata.details_view', - )) - except pkg_resources.DistributionNotFound: - app.config.setdefault('PIDSTORE_OBJECT_ENDPOINTS', {}) + 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", {}) # Register template filter - app.jinja_env.filters['pid_exists'] = pid_exists + app.jinja_env.filters["pid_exists"] = pid_exists # Initialize extension state. state = _PIDStoreState( @@ -153,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 7795367..782930f 100644 --- a/invenio_pidstore/fetchers.py +++ b/invenio_pidstore/fetchers.py @@ -34,7 +34,7 @@ def my_fetcher(record_uuid, data): 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.""" @@ -45,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]), ) @@ -60,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 b6703be..1c5c757 100644 --- a/invenio_pidstore/models.py +++ b/invenio_pidstore/models.py @@ -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( + u"Created PID {0}:{1}".format(pid_type, pid_value), 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, @@ -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: {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, @@ -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: @@ -281,11 +297,14 @@ 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)) + logger.info( + "Assigned object {0}:{1}".format(object_type, object_uuid), + extra=dict(pid=self), + ) return True def unassign(self): @@ -311,11 +330,9 @@ def unassign(self): self.object_uuid = None db.session.add(self) except SQLAlchemyError: - logger.exception("Failed to unassign object.".format(self), - 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(u"Unassigned object from {0}.".format(self), extra=dict(pid=self)) return True def get_redirect(self): @@ -360,10 +377,9 @@ 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)) + logger.info(u"Redirected PID to {0}".format(pid), extra=dict(pid=self)) return True def reserve(self): @@ -377,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(): @@ -400,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(): @@ -460,11 +475,9 @@ 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(u"Synced PID status to {0}.".format(status), extra=dict(pid=self)) return True def is_redirected(self): @@ -486,14 +499,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. """ @@ -502,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 "", ) @@ -523,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.""" @@ -549,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): @@ -585,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): @@ -604,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 17308e4..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,11 +74,13 @@ 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): + 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..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,10 +52,9 @@ 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): + def __init__(self, pid, client=None, **kwargs): """Initialize provider. To use the default client, just configure the following variables: @@ -75,12 +79,12 @@ def __init__(self, pid, client=None): 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 355ca4f..594a527 100644 --- a/invenio_pidstore/providers/recordid_v2.py +++ b/invenio_pidstore/providers/recordid_v2.py @@ -29,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 @@ -39,9 +39,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` """ @@ -50,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 @@ -86,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'] = PIDStatus.REGISTERED + 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 6b8e44f..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,7 +28,9 @@ 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 +41,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 +52,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/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/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/invenio_pidstore/version.py b/invenio_pidstore/version.py deleted file mode 100644 index 2cf5880..0000000 --- a/invenio_pidstore/version.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Invenio. -# Copyright (C) 2015-2019 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.1.0' 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/pytest.ini b/pytest.ini deleted file mode 100644 index 0161707..0000000 --- a/pytest.ini +++ /dev/null @@ -1,13 +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 -pep8ignore = docs/conf.py ALL -addopts = --pep8 --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 c6885e4..5384520 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -2,14 +2,29 @@ # -*- coding: utf-8 -*- # # This file is part of Invenio. -# Copyright (C) 2015-2019 CERN. -# Copyright (C) 2019 Northwestern University. +# 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. -pydocstyle invenio_pidstore tests docs && \ -isort -rc -c -df && \ -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 +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.cfg b/setup.cfg index 9eac762..e7be796 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,12 +2,69 @@ # # 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.7 +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 = + pytest-black>=0.3.0,<0.3.10 + 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.5.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/ @@ -22,6 +79,7 @@ add_ignore = D401 [compile_catalog] directory = invenio_pidstore/translations/ +use-fuzzy = True [extract_messages] copyright_holder = CERN @@ -37,3 +95,15 @@ output-dir = invenio_pidstore/translations/ [update_catalog] input-file = invenio_pidstore/translations/messages.pot 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 +testpaths = docs tests invenio_pidstore diff --git a/setup.py b/setup.py index d9ee8b5..7ce4002 100644 --- a/setup.py +++ b/setup.py @@ -3,142 +3,14 @@ # This file is part of Invenio. # 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 setup -from setuptools import find_packages, setup - -readme = open('README.rst').read() -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.0.0', - 'Flask-Menu>=0.5.1', - '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', -] - -extras_require = { - ':python_version<"3.4"': ['enum34>=1.1.6'], - 'admin': [ - 'Flask-Admin>=1.3.0', - ], - 'datacite': [ - 'datacite>=0.1.0' - ], - 'mysql': [ - 'invenio-db[mysql]>=1.0.0', - ], - 'postgresql': [ - 'invenio-db[postgresql]>=1.0.0', - ], - 'sqlite': [ - 'invenio-db>=1.0.0', - ], - 'docs': [ - 'Sphinx>=1.8.5', - ], - '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 = [ - 'Flask-BabelEx>=0.9.3', - 'Flask>=0.11.1', - 'six>=1.12.0', - 'base32-lib>=1.0.1' -] - -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() diff --git a/tests/conftest.py b/tests/conftest.py index 4219e79..1a9ad4b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,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, ) @@ -48,6 +49,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_admin.py b/tests/test_admin.py index a155857..dd0cbb1 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -24,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): @@ -52,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 dd3fd00..ba437ec 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -23,22 +23,20 @@ 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(): 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 @@ -47,179 +45,279 @@ 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 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 - 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): """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() # 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 8df24f8..695a7de 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. @@ -21,49 +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.pkg_resources'): - InvenioPIDStore(app) - assert app.config['PIDSTORE_OBJECT_ENDPOINTS'] + 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"] 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 8548506..740ffa8 100644 --- a/tests/test_invenio_pidstore.py +++ b/tests/test_invenio_pidstore.py @@ -17,20 +17,24 @@ 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 @@ -39,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() @@ -76,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() @@ -84,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) - i += 1 - pytest.raises(PIDInvalidAction, pid.redirect, pid1) + 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 @@ -345,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) @@ -367,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 fe2e160..63ae85a 100644 --- a/tests/test_minters.py +++ b/tests/test_minters.py @@ -27,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 @@ -37,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 515a737..c791c5d 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -14,8 +14,13 @@ import uuid import pytest -from datacite.errors import DataCiteError, DataCiteGoneError, \ - DataCiteNoContentError, DataCiteNotFoundError, HttpError +from datacite.errors import ( + DataCiteError, + DataCiteGoneError, + DataCiteNoContentError, + DataCiteNotFoundError, + HttpError, +) from mock import MagicMock, patch from invenio_pidstore.models import PIDStatus @@ -28,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 @@ -50,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): @@ -70,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 @@ -79,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): @@ -97,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 @@ -109,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 @@ -138,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) @@ -160,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 @@ -284,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 337e4be..4534568 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -14,8 +14,13 @@ import pytest -from invenio_pidstore.errors import PIDDeletedError, PIDDoesNotExistError, \ - PIDMissingObjectError, PIDRedirectedError, PIDUnregistered +from invenio_pidstore.errors import ( + PIDDeletedError, + PIDDoesNotExistError, + PIDMissingObjectError, + PIDRedirectedError, + PIDUnregistered, +) from invenio_pidstore.models import PersistentIdentifier, PIDStatus from invenio_pidstore.resolver import Resolver @@ -35,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 @@ -112,17 +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) + + 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=records.get) + 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 - assert pytest.raises(PIDDeletedError, resolver.resolve, '1') + # 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