diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0056d36..a6efcc2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,10 +2,10 @@ name: "CodeQL" on: push: - branches: [ main, develop ] + branches: [main, develop] pull_request: # The branches below must be a subset of the branches above - branches: [ main, develop ] + branches: [main, develop] schedule: - cron: '34 21 * * 1' @@ -17,40 +17,40 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'python' ] + language: ['python'] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 620005d..aa3773a 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -12,9 +12,6 @@ on: # normal behavior: run when a new release is created release: types: [published] - # allow running manually on main - workflow_dispatch: - branches: [main] permissions: contents: read @@ -27,19 +24,19 @@ jobs: name: pypi url: https://pypi.org/project/viapy/ permissions: - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@release/v1 + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index a641167..33ef997 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -3,7 +3,7 @@ name: unit tests on: push: # run on all branches pull_request: - - branches: [develop, main] + branches: [develop, main] schedule: # run automatically on main branch each Tuesday at 11am - cron: "0 16 * * 2" @@ -20,25 +20,23 @@ jobs: uses: actions/checkout@v6 - name: Install uv and Python version - uses: astral-sh/setup@v7 + uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.python }} - name: Install package dependencies - run: | - { - if [ "${{ matrix.django }}" -gt "0" ] - then - uv add django==${{ matrix.django }} - uv sync --extra test --extra django_test - else - uv sync --extra test - } + run: >- + if [ "${{ matrix.django }}" -gt "0" ]; then + uv add django=="${{ matrix.django }}"; + uv sync --extra test --extra django_test; + else + uv sync --extra test; + fi - name: Setup test settings run: | cp ci/testsettings.py testsettings.py - python -c "import uuid; print('SECRET_KEY = \'%s\'' % uuid.uuid4())" >> testsettings.py + uv run python -c "import uuid; print('SECRET_KEY = \'%s\'' % uuid.uuid4())" >> testsettings.py - name: Run pytest run: uv run pytest --cov=viapy --cov=tests --cov-report=xml --cov-report=term diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..bf48d01 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,55 @@ +# This config file specifies which pre-commit hooks to run. +# +# To set up, run `pre-commit install` +# To run all hooks manually, run `pre-commit run --all-files` +repos: + # Ruff Python linter and formatter (configs in pyproject.toml) + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.7 + hooks: + # Run the linter + - id: ruff-check + args: [--fix, --show-fixes] # enable lint fixes + # Run the formatter + - id: ruff-format + # yamlfmt for formatting YAML files + - repo: https://github.com/google/yamlfmt + rev: v0.17.2 + hooks: + - id: yamlfmt + # Codespell for spell checking + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + additional_dependencies: + - tomli + exclude_types: ["css", "html", "javascript", "json"] + # Some out-of-the-box file checks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + # Check for files with merge conflict strings + - id: check-merge-conflict + # Verify syntax of TOML files + - id: check-toml + # Verify syntax of YAML files + - id: check-yaml + args: [--unsafe] + # Verifies that test files begin with test_ + - id: name-tests-test + args: [--pytest-test-first] + # Removes trailing whitespace from all files + - id: trailing-whitespace + exclude: "docs/" + # Check uv.lock file is up to date + - repo: https://github.com/astral-sh/uv-pre-commit + # uv version. + rev: 0.8.6 + hooks: + - id: uv-lock + # Validate GitHub Actions workflow files + - repo: https://github.com/mpalmer/action-validator + rev: v0.8.0 + hooks: + - id: action-validator diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b0a90ae..a304f8f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,7 +15,7 @@ CHANGELOG * Handle negative years when parsing birth and death dates * Now tested on python 3.9 through 3.12 * Now tested against Django 3.2 through 5.0 -* Migrate continous integration to GitHub Actions +* Migrate continuous integration to GitHub Actions 0.2 --- diff --git a/README.rst b/README.rst index 2f83b4b..5bac395 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ django-autocomplete-light lookup view and a VIAF url widget. .. image:: https://codecov.io/gh/Princeton-CDH/viapy/branch/master/graph/badge.svg :target: https://codecov.io/gh/Princeton-CDH/viapy/branch/master - :alt: Code coverage + :alt: Code coverage .. image:: https://www.codefactor.io/repository/github/princeton-cdh/viapy/badge :target: https://www.codefactor.io/repository/github/princeton-cdh/viapy @@ -27,7 +27,7 @@ django-autocomplete-light lookup view and a VIAF url widget. .. image:: https://img.shields.io/pypi/pyversions/viapy :alt: PyPI - Python Version - + .. image:: https://img.shields.io/pypi/djversions/viapy :alt: PyPI - Django Version @@ -80,7 +80,7 @@ and a tool of your choice for creating python virtual environments Initial setup and installation: -* Install ``uv`` if it's not installed. +* Install ``uv`` if it's not installed. It can be installed via PyPi, Homebrew, or a standalone installer. See uv's `installation documentation `_ for more details. diff --git a/ci/testsettings.py b/ci/testsettings.py index 96219f7..8edd698 100644 --- a/ci/testsettings.py +++ b/ci/testsettings.py @@ -8,15 +8,14 @@ INSTALLED_APPS = ( # 'django.contrib.contenttypes', - 'dal', - 'dal_select2', - 'viapy', + "dal", + "dal_select2", + "viapy", ) -ROOT_URLCONF = 'viapy.test_urls' +ROOT_URLCONF = "viapy.test_urls" -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" # must be added # SECRET_KEY = '' - diff --git a/pyproject.toml b/pyproject.toml index a616891..b5fcddd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,12 +57,19 @@ django = ["django>=3.2", "django-autocomplete-light"] django_test = ["pytest-django", "viapy[django]"] docs = ["sphinx"] test_all = ["viapy[test]", "viapy[django_test]"] -dev = ["pre-commit", "viapy[django]", "viapy[test_all]", "viapy[docs]"] +dev = [ + "ruff", + "pre-commit", + "viapy[django]", + "viapy[test_all]", + "viapy[docs]" +] [dependency-groups] dev = ["viapy[dev]"] -[tool.pytest] +[tool.pytest.ini_options] +# Need to use ini_options for Python 3.9 testpaths = ["tests"] pythonpath = ["."] DJANGO_SETTINGS_MODULE = "testsettings" @@ -80,3 +87,19 @@ exclude_lines = [ [tool.coverage.html] directory = "coverage_html_report" + +[tool.ruff.lint] +# Include these rules in addition to ruff's defaults +extend-select = [ + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "I", #isort + "NPY", # numpy-specific rules + "PERF", # perflint + "PTH", # flake8-use-pathlib + "RUF", # ruff-specific rules + "SIM", # flake8-simplify + "UP", # pyupgrade +] +# Can use to ignore specific rules within above selection +ignore = [] diff --git a/sphinx-docs/conf.py b/sphinx-docs/conf.py index 05de731..1565c4e 100644 --- a/sphinx-docs/conf.py +++ b/sphinx-docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # viapy documentation build configuration file, created by # sphinx-quickstart on Thu Oct 12 17:06:23 2017. @@ -15,23 +14,25 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. +# documentation root, use pathlib.Path.resolve to make it absolute, like shown here. # -# import os +# import pathlib # import sys -# sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, pathlib.Path().resolve()) import os +import pathlib import sys + import django -sys.path.insert(0, os.path.abspath(".")) +from viapy import __version__ + +sys.path.insert(0, pathlib.Path().resolve()) os.environ["DJANGO_SETTINGS_MODULE"] = "docsettings" django.setup() -from viapy import __version__ - # -- General configuration ------------------------------------------------ diff --git a/sphinx-docs/docsettings.py b/sphinx-docs/docsettings.py index fbc5e9c..41fab1e 100644 --- a/sphinx-docs/docsettings.py +++ b/sphinx-docs/docsettings.py @@ -8,14 +8,13 @@ INSTALLED_APPS = ( # 'django.contrib.contenttypes', - 'dal', - 'dal_select2', - 'viapy', + "dal", + "dal_select2", + "viapy", ) -ROOT_URLCONF = 'viapy.test_urls' +ROOT_URLCONF = "viapy.test_urls" -LANGUAGE_CODE = 'en-us' - -SECRET_KEY = 'f$J4FB3B=}1bFtJ$}b9s28Vsf?&otV}o0*V)g;#OD5%20uksel' +LANGUAGE_CODE = "en-us" +SECRET_KEY = "f$J4FB3B=}1bFtJ$}b9s28Vsf?&otV}o0*V)g;#OD5%20uksel" diff --git a/src/viapy/api.py b/src/viapy/api.py index 875abb6..0adeb77 100644 --- a/src/viapy/api.py +++ b/src/viapy/api.py @@ -2,20 +2,19 @@ import re import time +import rdflib +import requests from attrdict import AttrMap from cached_property import cached_property -import requests -import rdflib from rdflib.namespace import Namespace - logger = logging.getLogger(__name__) SCHEMA_NS = Namespace("http://schema.org/") -class ViafAPI(object): +class ViafAPI: """Wrapper for VIAF API. https://platform.worldcat.org/api-explorer/apis/VIAF @@ -31,14 +30,14 @@ class ViafAPI(object): @classmethod def uri_from_id(cls, viaf_id): """Generate a canonical VIAF URI for the specified id""" - return "%s/%s" % (cls.uri_base, viaf_id) + return f"{cls.uri_base}/{viaf_id}" def suggest(self, term): """Query autosuggest API. Returns a list of results, or an empty list if no suggestions are found or if something went wrong""" # 'viaf/AutoSuggest?query=[searchTerms]&callback[optionalCallbackName] - autosuggest_url = "%s/AutoSuggest" % self.api_base + autosuggest_url = f"{self.api_base}/AutoSuggest" response = requests.get( autosuggest_url, params={"query": term}, @@ -61,10 +60,10 @@ def suggest(self, term): return [] def search(self, query): - """Query VIAF seach interface. Returns a list of :class:`SRUItem` + """Query VIAF search interface. Returns a list of :class:`SRUItem` :param query: CQL query in viaf syntax (e.g., ``cql.any all "term"``) """ - search_url = "%s/search" % self.api_base + search_url = f"{self.api_base}/search" params = { "query": query, "maximumRecords": 10, # TODO: configurable ? @@ -73,7 +72,9 @@ def search(self, query): "sortKey": "holdingscount", } - response = requests.get(search_url, params=params, headers={"Accept": "application/json"}) + response = requests.get( + search_url, params=params, headers={"Accept": "application/json"} + ) logger.debug( "search '%s': %s %s, %0.2f", params["query"], @@ -90,7 +91,7 @@ def search(self, query): response.raise_for_status() def _find_type(self, fltr, value): - return self.search('%s all "%s"' % (fltr, value)) + return self.search(f'{fltr} all "{value}"') def find_person(self, name): """Search VIAF for local.personalNames""" @@ -105,7 +106,7 @@ def find_place(self, name): return self._find_type("local.geographicNames", name) -class ViafEntity(object): +class ViafEntity: """Object for working with a single VIAF entity. :param viaf_id: viaf identifier (either integer or uri) @@ -177,7 +178,7 @@ def year_from_isodate(cls, date): return value -class SRUResult(object): +class SRUResult: """SRU search result object, for use with :meth:`ViafAPI.search`.""" def __init__(self, data): @@ -213,10 +214,11 @@ def normalize_record(self, data): else: return data + class SRUItem(AttrMap): """Single item returned by a SRU search, for use with :meth:`ViafAPI.search` and :class:`SRUResult`. - + The `VIAFCluster` attribute was added to each property lookup in 2025 to match updates to the /search API's JSON response.""" diff --git a/src/viapy/test_urls.py b/src/viapy/test_urls.py index d5a3511..be494cb 100644 --- a/src/viapy/test_urls.py +++ b/src/viapy/test_urls.py @@ -1,7 +1,7 @@ -"""Test URL configuration for viapy -""" +"""Test URL configuration for viapy""" + try: - from django.urls import path, include + from django.urls import include, path from viapy import urls as viapy_urls diff --git a/src/viapy/urls.py b/src/viapy/urls.py index 8e35c1f..5f86b8b 100644 --- a/src/viapy/urls.py +++ b/src/viapy/urls.py @@ -2,7 +2,6 @@ from viapy.views import ViafLookup, ViafSearch - app_name = "viapy" urlpatterns = [ diff --git a/src/viapy/views.py b/src/viapy/views.py index a016e30..c648168 100644 --- a/src/viapy/views.py +++ b/src/viapy/views.py @@ -1,15 +1,15 @@ -from django.http import JsonResponse from dal import autocomplete +from django.http import JsonResponse from viapy.api import ViafAPI class ViafLookup(autocomplete.Select2ListView): - '''View to provide VIAF suggestions for autocomplete lookup. + """View to provide VIAF suggestions for autocomplete lookup. Based on :class:`dal.autocompleteSelect2ListView`. Expects search term as query string parameter `q`. Returns viaf URI as identifier and display form as text. - ''' + """ def get(self, request, *args, **kwargs): """Return JSON with suggested VIAF ids and display names.""" @@ -17,53 +17,64 @@ def get(self, request, *args, **kwargs): result = viaf.suggest(self.q) # optionally filter by nametype if set - if 'nametype' in self.kwargs: - result = [item for item in result - if item['nametype'] == self.kwargs['nametype']] + if "nametype" in self.kwargs: + result = [ + item for item in result if item["nametype"] == self.kwargs["nametype"] + ] - return JsonResponse({ - 'results': [dict( - id=viaf.uri_from_id(item['viafid']), - id_number=item['viafid'], - text=item['displayForm'], - nametype=item['nametype'] - # exclude any names that are not personal - ) for item in result] - }) + return JsonResponse( + { + "results": [ + { + "id": viaf.uri_from_id(item["viafid"]), + "id_number": item["viafid"], + "text": item["displayForm"], + "nametype": item["nametype"], + # exclude any names that are not personal + } + for item in result + ] + } + ) class ViafSearch(autocomplete.Select2ListView): - '''View to provide VIAF suggestions for autocomplete lookup. + """View to provide VIAF suggestions for autocomplete lookup. Based on :class:`dal.autocompleteSelect2ListView`. Expects search term as query string parameter `q`. Returns viaf URI as identifier and display form as text. - ''' + """ def get(self, request, *args, **kwargs): """Return JSON with suggested VIAF ids and display names.""" viaf = ViafAPI() # search for specific kind of name if set - nametype = self.kwargs.get('nametype', None) - if nametype == 'personal': + nametype = self.kwargs.get("nametype", None) + if nametype == "personal": result = viaf.find_person(self.q) else: result = viaf.search(self.q) # check for empty search result and return empty json response if result is None: - return JsonResponse({'results': []}) + return JsonResponse({"results": []}) - return JsonResponse({ - 'results': [dict( - # id=viaf.uri_from_id(item.recordData.viafID), - id=item.uri, - id_number=item.viaf_id, - text=item.label, - nametype=item.nametype, - # possibly useful to include, since we have them (for people) - birth=item.recordData.VIAFCluster.birthDate, - death=item.recordData.VIAFCluster.deathDate, - # exclude any names that are not personal - ) for item in result] - }) + return JsonResponse( + { + "results": [ + { + # 'id': viaf.uri_from_id(item.recordData.viafID), + "id": item.uri, + "id_number": item.viaf_id, + "text": item.label, + "nametype": item.nametype, + # possibly useful to include, since we have them (for people) + "birth": item.recordData.VIAFCluster.birthDate, + "death": item.recordData.VIAFCluster.deathDate, + # exclude any names that are not personal + } + for item in result + ] + } + ) diff --git a/src/viapy/widgets.py b/src/viapy/widgets.py index 885c6f3..b0bc808 100644 --- a/src/viapy/widgets.py +++ b/src/viapy/widgets.py @@ -11,8 +11,7 @@ def render(self, name, value, renderer=None, attrs=None): # so when a value is set, add it to the list of choices if value: self.choices = [(value, value)] - widget = super(ViafWidget, self).render(name, value, attrs) + widget = super().render(name, value, attrs) return mark_safe( - '%s


%s

' - % (widget, value or "", value or "") + f'{widget}


{value or ""}

' ) diff --git a/tests/test_api.py b/tests/test_api.py index 4cd5eba..c916df2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,16 +1,16 @@ import json -import os +import pathlib from unittest.mock import Mock, patch -import requests import rdflib +import requests -from viapy.api import ViafAPI, ViafEntity, SRUResult, SRUItem +from viapy.api import SRUItem, SRUResult, ViafAPI, ViafEntity -FIXTURES_PATH = os.path.join(os.path.dirname(__file__), "fixtures") +FIXTURES_PATH = pathlib.Path(__file__).parent / "fixtures" -class TestViafAPI(object): +class TestViafAPI: def test_get_uri(self): assert ViafAPI.uri_from_id("1234") == "http://viaf.org/viaf/1234" # numeric id should also work @@ -51,8 +51,8 @@ def test_search(self, mockrequests): viaf = ViafAPI() mockrequests.codes = requests.codes - sru_fixture = os.path.join(FIXTURES_PATH, "sru_search.json") - with open(sru_fixture) as srufile: + sru_fixture = FIXTURES_PATH / "sru_search.json" + with sru_fixture.open() as srufile: mock_result = json.load(srufile) mockrequests.get.return_value.status_code = requests.codes.ok mockrequests.get.return_value.json.return_value = mock_result @@ -85,7 +85,7 @@ def test_find_person(self): with patch.object(viaf, "search") as mocksearch: viaf.find_person(term) - mocksearch.assert_called_with('local.personalNames all "%s"' % term) + mocksearch.assert_called_with(f'local.personalNames all "{term}"') def test_find_corporate(self): viaf = ViafAPI() @@ -93,7 +93,7 @@ def test_find_corporate(self): with patch.object(viaf, "search") as mocksearch: viaf.find_corporate(term) - mocksearch.assert_called_with('local.corporateNames all "%s"' % term) + mocksearch.assert_called_with(f'local.corporateNames all "{term}"') def test_find_place(self): viaf = ViafAPI() @@ -101,13 +101,13 @@ def test_find_place(self): with patch.object(viaf, "search") as mocksearch: viaf.find_place(term) - mocksearch.assert_called_with('local.geographicNames all "%s"' % term) + mocksearch.assert_called_with(f'local.geographicNames all "{term}"') -class TestViafEntity(object): +class TestViafEntity: test_id = 102333412 test_uri = "http://viaf.org/viaf/102333412" - rdf_fixture = os.path.join(FIXTURES_PATH, "102333412_rdf.xml") + rdf_fixture = FIXTURES_PATH / "102333412_rdf.xml" def test_init(self): # numeric id (either int or string should work) @@ -137,9 +137,13 @@ def test_rdf(self, mockrdflib, mockrequests): # should call requests.get on the uri, initialize a graph and parse data assert ent.rdf == mockrdflib.Graph.return_value mockrdflib.Graph.assert_called_with() - mockrequests.get.assert_called_with(self.test_uri, headers={"Accept": "application/rdf+xml"}) - mockrdflib.Graph.return_value.parse.assert_called_with(data=mock_response.text, format="xml") - mockrequests.raise_for_status.assert_called_once + mockrequests.get.assert_called_with( + self.test_uri, headers={"Accept": "application/rdf+xml"} + ) + mockrdflib.Graph.return_value.parse.assert_called_with( + data=mock_response.text, format="xml" + ) + mock_response.raise_for_status.assert_called_once() def test_properties(self): # use viaf id matching fixture rdf file @@ -165,8 +169,8 @@ def test_year_from_isodate(self): def test_sru_result(): # test SRUResult class properties - sru_fixture = os.path.join(FIXTURES_PATH, "sru_search.json") - with open(sru_fixture) as srufile: + sru_fixture = FIXTURES_PATH / "sru_search.json" + with sru_fixture.open() as srufile: sru_data = json.load(srufile) sru_res = SRUResult(sru_data) assert sru_res.total_results == 8 @@ -177,8 +181,8 @@ def test_sru_result(): def test_sru_item(): # test SRUItem class - sru_fixture = os.path.join(FIXTURES_PATH, "sru_search.json") - with open(sru_fixture) as srufile: + sru_fixture = FIXTURES_PATH / "sru_search.json" + with sru_fixture.open() as srufile: sru_data = json.load(srufile) sru_item = SRUResult(sru_data).records[0] assert sru_item.uri == "http://viaf.org/viaf/100260717/" diff --git a/tests/test_django.py b/tests/test_django.py index 95fde73..83206ec 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -2,8 +2,8 @@ import json from unittest.mock import patch -from attrdict import AttrDict import pytest +from attrdict import AttrDict try: import django @@ -19,120 +19,120 @@ from unittest import TestCase -@pytest.mark.skipif(django is None, reason='Requires Django') -class TestViafWidget(object): - +@pytest.mark.skipif(django is None, reason="Requires Django") +class TestViafWidget: def test_render(self): widget = ViafWidget() # no value set - should not error - rendered = widget.render('person', None, {'id': 123}) - assert '


' \ - in rendered + rendered = widget.render("person", None, {"id": 123}) + assert '


' in rendered # test marked as "safe"? # uri value set - should be included in generated link *and* # set as an option - uri = 'http://viaf.org/viaf/13103985/' - rendered = widget.render('person', uri, {'id': 1234}) - assert '%(uri)s' \ - % {'uri': uri} in rendered + uri = "http://viaf.org/viaf/13103985/" + rendered = widget.render("person", uri, {"id": 1234}) + assert f'{uri}' in rendered # value should be set as an option to preserve existing # value when the form is submitted - assert '