diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..cc72b19 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,4 @@ +select = ["ALL"] +ignore = ["ANN101", "ANN102", "D203", "D211", "D212", "D213", "UP009", "S101", "S314", "INP001", "FLY002", "TD002", "TD003"] + +exclude = ["docs"] diff --git a/MANIFEST.in b/MANIFEST.in index 6fd9ca2..0baab13 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,19 +1,11 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2021 Graz University of Technology. +# Copyright (C) 2021-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more # details. -# TODO: Generate this manifest file by running the following commands: -# (please sort the lines in this file after running below commands) -# -# git init -# git add -A -# pip install -e .[all] -# check-manifest -u - include .dockerignore include .editorconfig include .tx/config @@ -26,6 +18,7 @@ include *.md include *.rst include *.sh include *.txt +include *.toml include babel.ini include pytest.ini recursive-include docs *.bat diff --git a/docs/conf.py b/docs/conf.py index 5b87394..89fbd91 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,6 +29,10 @@ "sphinx.ext.viewcode", ] +nitpick_ignore = [ + ("py:class", "flask.app.Flask"), +] + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/invenio_campusonline/__init__.py b/invenio_campusonline/__init__.py index 788d80b..15c523e 100644 --- a/invenio_campusonline/__init__.py +++ b/invenio_campusonline/__init__.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2021-2022 Graz University of Technology. +# Copyright (C) 2021-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more # details. -"""This module is used to import/export from/to the CampusOnline System.""" +"""The module is used to import/export from/to the CampusOnline System.""" from .api import fetch_all_ids, import_from_campusonline from .ext import InvenioCampusonline diff --git a/invenio_campusonline/api.py b/invenio_campusonline/api.py index 35c4bd5..ab1ad78 100644 --- a/invenio_campusonline/api.py +++ b/invenio_campusonline/api.py @@ -7,9 +7,10 @@ # file for more details. """API functions of the campusonline connector.""" -from typing import Callable +from collections.abc import Callable from xml.etree.ElementTree import fromstring +from invenio_records_resources.services.records.results import RecordItem from requests import post from .types import ( @@ -29,14 +30,18 @@ def import_from_campusonline( - import_func: Callable, cms_id: CampusOnlineID, configs: CampusOnlineConfigs -): + import_func: Callable, + cms_id: CampusOnlineID, + configs: CampusOnlineConfigs, +) -> RecordItem: """Import record from campusonline.""" return import_func(cms_id, configs, get_metadata, download_file) def fetch_all_ids( - endpoint: URL, token: CampusOnlineToken, theses_filters: ThesesFilter = None + endpoint: URL, + token: CampusOnlineToken, + theses_filters: ThesesFilter = None, ) -> list[tuple[CampusOnlineID, ThesesState]]: """Fetch to import ids.""" ids = [] @@ -44,7 +49,7 @@ def fetch_all_ids( body = create_request_body_ids(token, theses_filter) headers = create_request_header("getAllThesesMetadataRequest") - response = post(endpoint, data=body, headers=headers) + response = post(endpoint, data=body, headers=headers, timeout=10) root = fromstring(response.text) xpath = "{http://www.campusonline.at/thesisservice/basetypes}ID" diff --git a/invenio_campusonline/cli.py b/invenio_campusonline/cli.py index 3317f75..aed3207 100644 --- a/invenio_campusonline/cli.py +++ b/invenio_campusonline/cli.py @@ -18,7 +18,7 @@ @group() -def campusonline(): +def campusonline() -> None: """Campusonline CLI.""" @@ -28,8 +28,14 @@ def campusonline(): @option("--campusonline-id", type=STRING) @option("--token", type=STRING) @option("--user-email", type=STRING, default="cms@tugraz.at") -@option("--no-color", is_flag=True) -def import_thesis(endpoint, campusonline_id, token, user_email, no_color=False): +@option("--no-color", is_flag=True, default=False) +def import_thesis( + endpoint: str, + campusonline_id: str, + token: str, + user_email: str, + no_color: bool, # noqa: FBT001 +) -> None: """Import metadata and file (aka one thesis) from campusonline.""" import_func = current_app.config["CAMPUSONLINE_IMPORT_FUNC"] theses_filters = current_app.config["CAMPUSONLINE_THESES_FILTERS"] @@ -37,7 +43,12 @@ def import_thesis(endpoint, campusonline_id, token, user_email, no_color=False): sender = current_app.config["CAMPUSONLINE_ERROR_MAIL_SENDER"] configs = CampusOnlineConfigs( - endpoint, token, user_email, theses_filters, recipients, sender + endpoint, + token, + user_email, + theses_filters, + recipients, + sender, ) record = import_from_campusonline(import_func, campusonline_id, configs) @@ -50,7 +61,11 @@ def import_thesis(endpoint, campusonline_id, token, user_email, no_color=False): @option("--endpoint", type=URL) @option("--token", type=STRING) @option("--no-color", is_flag=True) -def fetch_ids(endpoint, token, no_color): +def fetch_ids( + endpoint: str, + token: str, + no_color: bool, # noqa: FBT001 +) -> None: """Fetch all to import ids.""" theses_filters = current_app.config["CAMPUSONLINE_THESES_FILTERS"] ids = fetch_all_ids(endpoint, token, theses_filters) diff --git a/invenio_campusonline/config.py b/invenio_campusonline/config.py index 675ac96..829838a 100644 --- a/invenio_campusonline/config.py +++ b/invenio_campusonline/config.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2021-2022 Graz University of Technology. +# Copyright (C) 2021-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more # details. -"""This is the configuration file.""" +"""The configuration file.""" CAMPUSONLINE_ENDPOINT = "" """This is the endpoint to fetch the records.""" diff --git a/invenio_campusonline/ext.py b/invenio_campusonline/ext.py index f19da53..47f4083 100644 --- a/invenio_campusonline/ext.py +++ b/invenio_campusonline/ext.py @@ -1,22 +1,24 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2021-2022 Graz University of Technology. +# Copyright (C) 2021-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more # details. -"""This module is used to import/export from/to the CampusOnline System.""" +"""The module is used to import/export from/to the CampusOnline System.""" + +from flask import Flask class InvenioCampusonline: """invenio-campusonline extension.""" - def __init__(self, app=None): + def __init__(self, app: Flask = None) -> None: """Extension initialization.""" if app: self.init_app(app) - def init_app(self, app): + def init_app(self, app: Flask) -> None: """Flask application initialization.""" app.extensions["invenio-campusonline"] = self diff --git a/invenio_campusonline/tasks.py b/invenio_campusonline/tasks.py index ae3afd9..f10c197 100644 --- a/invenio_campusonline/tasks.py +++ b/invenio_campusonline/tasks.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2022 Graz University of Technology. +# Copyright (C) 2022-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it # and/or modify it under the terms of the MIT License; see LICENSE @@ -8,7 +8,7 @@ """Celery tasks for `invenio-campusonline`.""" -from typing import Callable +from collections.abc import Callable from celery import shared_task from flask import current_app @@ -19,7 +19,7 @@ def config_variables() -> tuple[Callable, CampusOnlineConfigs]: - """Configuration variables.""" + """Configure variables.""" import_func = current_app.config["CAMPUSONLINE_IMPORT_FUNC"] endpoint = current_app.config["CAMPUSONLINE_ENDPOINT"] token = current_app.config["CAMPUSONLINE_TOKEN"] @@ -29,7 +29,12 @@ def config_variables() -> tuple[Callable, CampusOnlineConfigs]: sender = current_app.config["CAMPUSONLINE_ERROR_MAIL_SENDER"] return import_func, CampusOnlineConfigs( - endpoint, token, user_email, theses_filters, recipients, sender + endpoint, + token, + user_email, + theses_filters, + recipients, + sender, ) @@ -39,7 +44,7 @@ def import_theses_from_campusonline() -> None: import_func, configs = config_variables() ids = fetch_all_ids(configs.endpoint, configs.token, configs.theses_filters) - for cms_id, state in ids: + for cms_id, _ in ids: try: import_from_campusonline(import_func, cms_id, configs) except RuntimeError: diff --git a/invenio_campusonline/types.py b/invenio_campusonline/types.py index 617a678..0f27b08 100644 --- a/invenio_campusonline/types.py +++ b/invenio_campusonline/types.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2022 Graz University of Technology. +# Copyright (C) 2022-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it # and/or modify it under the terms of the MIT License; see LICENSE @@ -40,14 +40,14 @@ @dataclass(frozen=True) class Color: - """This class is for the output color management.""" + """The class is for the output color management.""" neutral = "black" error = "red" warning = "yellow" abort = "magenta" success = "green" - alternate = ["blue", "cyan"] + alternate = ("blue", "cyan") class ThesesState(Enum): @@ -64,8 +64,8 @@ class ThesesFilter: filter_: list[Element] state: ThesesState - def __iter__(self): - """This method makes the properties iterable.""" + def __iter__(self): # noqa: ANN204 + """Make the class iteratable.""" return iter(astuple(self)) diff --git a/invenio_campusonline/utils.py b/invenio_campusonline/utils.py index e6a33ee..1321066 100644 --- a/invenio_campusonline/utils.py +++ b/invenio_campusonline/utils.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2022 Graz University of Technology. +# Copyright (C) 2022-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it # and/or modify it under the terms of the MIT License; see LICENSE # file for more details. """Command line interface to interact with the CampusOnline-Connector module.""" +from pathlib import Path from shutil import copyfileobj from xml.etree.ElementTree import Element, fromstring @@ -16,7 +17,8 @@ def create_request_body_metadata( - token: CampusOnlineToken, campusonline_id: CampusOnlineID + token: CampusOnlineToken, + campusonline_id: CampusOnlineID, ) -> str: """Build Request.""" body = """ @@ -49,7 +51,8 @@ def create_request_body_metadata( def create_request_body_download( - token: CampusOnlineToken, campusonline_id: CampusOnlineID + token: CampusOnlineToken, + campusonline_id: CampusOnlineID, ) -> str: """Build Request.""" body = """ @@ -70,7 +73,8 @@ def create_request_body_download( def create_request_body_ids( - token: CampusOnlineToken, theses_filter: list[Element] + token: CampusOnlineToken, + theses_filter: list[Element], ) -> str: """Build request.""" body = """ @@ -91,35 +95,37 @@ def create_request_body_ids( def create_request_header(service: str) -> dict: """Create request header.""" - header = { + return { "Content-Type": "application/xml", "SOAPAction": f"urn:service#{service}", } - return header def get_metadata( - endpoint: URL, token: CampusOnlineToken, campusonline_id: CampusOnlineID + endpoint: URL, + token: CampusOnlineToken, + campusonline_id: CampusOnlineID, ) -> Element: """Get Metadata.""" body = create_request_body_metadata(token, campusonline_id) headers = create_request_header("getMetadataByThesisID") - response = post(endpoint, data=body, headers=headers) + response = post(endpoint, data=body, headers=headers, timeout=10) root = fromstring(response.text) xpath = "{http://www.campusonline.at/thesisservice/basetypes}thesis" - thesis = list(root.iter(xpath))[0] # TODO: fix it - return thesis + return list(root.iter(xpath))[0] # TODO: fix it def get_file_url( - endpoint: URL, token: CampusOnlineToken, campusonline_id: CampusOnlineID + endpoint: URL, + token: CampusOnlineToken, + campusonline_id: CampusOnlineID, ) -> str: """Get file URL.""" body = create_request_body_download(token, campusonline_id) headers = create_request_header("getDocumentByThesisID") - response = post(endpoint, data=body, headers=headers) + response = post(endpoint, data=body, headers=headers, timeout=10) root = fromstring(response.text) xpath = "{http://www.campusonline.at/thesisservice/basetypes}docUrl" @@ -127,19 +133,21 @@ def get_file_url( return file_url.text -def store_file_temporarily(file_url: URL, file_path: FilePath): - """This function stores files referenced by an url to the local file path.""" - with get(file_url, stream=True) as response: - with open(file_path, "wb") as fp: +def store_file_temporarily(file_url: URL, file_path: FilePath) -> None: + """Store the file referenced by url to the local file path.""" + with get(file_url, stream=True, timeout=10) as response: # noqa: SIM117 + with Path(file_path).open("wb") as fp: copyfileobj(response.raw, fp) def download_file( - endpoint: URL, token: CampusOnlineToken, campusonline_id: CampusOnlineID + endpoint: URL, + token: CampusOnlineToken, + campusonline_id: CampusOnlineID, ) -> FilePath: - """This function downloads files from campus online.""" + """Download files from campus online by campusonline_id.""" file_url = get_file_url(endpoint, token, campusonline_id) file_url = f"{file_url}{token}" - file_path = f"/tmp/{campusonline_id}.pdf" + file_path = f"/tmp/{campusonline_id}.pdf" # noqa: S108 store_file_temporarily(file_url, file_path) return file_path diff --git a/run-tests.sh b/run-tests.sh index cde5658..d9551db 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # -*- coding: utf-8 -*- # -# Copyright (C) 2021-2022 Graz University of Technology. +# Copyright (C) 2021-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more @@ -13,6 +13,8 @@ set -o errexit # Quit on unbound symbols set -o nounset +ruff . + python -m check_manifest python -m sphinx.cmd.build -qnNW docs docs/_build/html python -m pytest -s diff --git a/setup.cfg b/setup.cfg index 1aafba2..f6b893f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2021-2022 Graz University of Technology. +# Copyright (C) 2021-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more @@ -9,7 +9,7 @@ [metadata] name = invenio-campusonline version = attr: invenio_campusonline.__version__ -description = "This module is used to import/export from/to the CampusOnline System." +description = "The module is used to import/export from/to the CampusOnline System." long_description = file: README.rst, CHANGES.rst keywords = invenio TODO license = MIT @@ -31,7 +31,6 @@ zip_safe = False install_requires = click>=8.0.0 click-params>=0.4.0 - invenio-i18n>=1.2.0 invenio-celery>=1.2.5 invenio-mail>=1.0.2 requests>=2.0.0 @@ -40,7 +39,9 @@ install_requires = tests = pytest-black>=0.3.0 pytest-invenio>=1.4.0 + invenio-records-resources>=1.0.0 invenio-search[opensearch2]>=2.1.0 + ruff>=0.0.263 sphinx>=4.5 [options.entry_points] @@ -70,5 +71,5 @@ ignore = *-requirements.txt [tool:pytest] -addopts = --black --isort --pydocstyle --doctest-glob="*.rst" --doctest-modules --cov=invenio_campusonline --cov-report=term-missing +addopts = --black --doctest-glob="*.rst" --doctest-modules --cov=invenio_campusonline --cov-report=term-missing testpaths = docs tests invenio_campusonline diff --git a/setup.py b/setup.py index ed6057f..d093acf 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2021-2022 Graz University of Technology. +# Copyright (C) 2021-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more # details. -"""This module is used to import/export from/to the CampusOnline System.""" +"""The module is used to import/export from/to the CampusOnline System.""" from setuptools import setup diff --git a/tests/conftest.py b/tests/conftest.py index 3d21116..c01720b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2021 Graz University of Technology. +# Copyright (C) 2021-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more @@ -12,33 +12,20 @@ fixtures are available. """ -import shutil -import tempfile import pytest from flask import Flask -from flask_babelex import Babel from invenio_campusonline import InvenioCampusonline @pytest.fixture(scope="module") -def celery_config(): - """Override pytest-invenio fixture. - - TODO: Remove this fixture if you add Celery support. - """ - return {} - - -@pytest.fixture(scope="module") -def create_app(instance_path): +def create_app(instance_path: str) -> Flask: """Application factory fixture.""" - def factory(**config): + def factory(**config: dict) -> Flask: app = Flask("testapp", instance_path=instance_path) app.config.update(**config) - Babel(app) InvenioCampusonline(app) return app diff --git a/tests/test_invenio_campusonline.py b/tests/test_invenio_campusonline.py index 45356e1..a05b06b 100644 --- a/tests/test_invenio_campusonline.py +++ b/tests/test_invenio_campusonline.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2021 Graz University of Technology. +# Copyright (C) 2021-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more @@ -10,17 +10,15 @@ from flask import Flask -from invenio_campusonline import InvenioCampusonline +from invenio_campusonline import InvenioCampusonline, __version__ -def test_version(): +def test_version() -> None: """Test version import.""" - from invenio_campusonline import __version__ - assert __version__ -def test_init(): +def test_init() -> None: """Test extension initialization.""" app = Flask("testapp") ext = InvenioCampusonline(app) diff --git a/tests/test_utils.py b/tests/test_utils.py index 5758ea3..ded8cf5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2022 Graz University of Technology. +# Copyright (C) 2022-2023 Graz University of Technology. # # invenio-campusonline is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more @@ -16,7 +16,7 @@ ) -def test_create_request_body_metadata(): +def test_create_request_body_metadata() -> None: """Test the create_request_body_metadata function.""" body = create_request_body_metadata("abcd", "efgh") expected = """ @@ -48,7 +48,7 @@ def test_create_request_body_metadata(): assert body == expected -def test_create_request_body_download(): +def test_create_request_body_download() -> None: """Test the create_request_body_download function.""" body = create_request_body_download("abcd", "efgh") expected = """ @@ -67,7 +67,7 @@ def test_create_request_body_download(): assert body == expected -def test_create_request_body_ids(): +def test_create_request_body_ids() -> None: """Test the create_request_body_ids function.""" body = create_request_body_ids("abcd", []) expected = """ @@ -82,7 +82,8 @@ def test_create_request_body_ids(): """.replace( - "FILTER", "\n".join([]) + "FILTER", + "\n".join([]), ) assert body == expected @@ -108,7 +109,7 @@ def test_create_request_body_ids(): assert body == expected -def test_create_request_header(): +def test_create_request_header() -> None: """Test the create_request_header function.""" header = create_request_header("allThesesMetadataRequest") expected = {