NatLibFi/Annif

View on GitHub
annif/backend/http.py

Summary

Maintainability
A
35 mins
Test Coverage
"""HTTP/REST client backend that makes calls to a web service
and returns the results"""

from __future__ import annotations

import importlib
from typing import TYPE_CHECKING, Any

import dateutil.parser
import requests
import requests.exceptions

from annif.exception import OperationFailedException
from annif.suggestion import SubjectSuggestion

from . import backend

if TYPE_CHECKING:
    from datetime import datetime


class HTTPBackend(backend.AnnifBackend):
    name = "http"
    _headers = None

    @property
    def headers(self) -> dict[str, str]:
        if self._headers is None:
            version = importlib.metadata.version("annif")
            self._headers = {
                "User-Agent": f"Annif/{version}",
            }
        return self._headers

    @property
    def is_trained(self) -> bool | None:
        return self._get_project_info("is_trained")

    @property
    def modification_time(self) -> datetime | None:
        mtime = self._get_project_info("modification_time")
        if mtime is None:
            return None
        return dateutil.parser.parse(mtime)

    def _get_project_info(self, key: str) -> bool | str | None:
        params = self._get_backend_params(None)
        try:
            req = requests.get(
                params["endpoint"].replace("/suggest", ""), headers=self.headers
            )
            req.raise_for_status()
        except requests.exceptions.RequestException as err:
            msg = f"HTTP request failed: {err}"
            raise OperationFailedException(msg) from err
        try:
            response = req.json()
        except ValueError as err:
            msg = f"JSON decode failed: {err}"
            raise OperationFailedException(msg) from err

        if key in response:
            return response[key]
        else:
            return None

    def _suggest(self, text: str, params: dict[str, Any]) -> list[SubjectSuggestion]:
        data = {"text": text}
        if "project" in params:
            data["project"] = params["project"]
        if "limit" in params:
            data["limit"] = params["limit"]

        try:
            req = requests.post(params["endpoint"], data=data, headers=self.headers)
            req.raise_for_status()
        except requests.exceptions.RequestException as err:
            self.warning("HTTP request failed: {}".format(err))
            return []

        try:
            response = req.json()
        except ValueError as err:
            self.warning("JSON decode failed: {}".format(err))
            return []

        if "results" in response:
            results = response["results"]
        else:
            results = response

        try:
            subject_suggestions = [
                SubjectSuggestion(
                    subject_id=self.project.subjects.by_uri(hit["uri"]),
                    score=hit["score"],
                )
                for hit in results
                if hit["score"] > 0.0
            ]
        except (TypeError, ValueError) as err:
            self.warning("Problem interpreting JSON data: {}".format(err))
            return []

        return subject_suggestions