Passed
Pull Request — master (#659)
by
unknown
05:50
created

annif.rest.project_not_found_error()   A

Complexity

Conditions 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
"""Definitions for REST API operations. These are wired via Connexion to
2
methods defined in the Swagger specification."""
3
4
import importlib
5
6
import connexion
7
from simplemma.langdetect import lang_detector
8
9
import annif.registry
10
from annif.corpus import Document, DocumentList, SubjectSet
11
from annif.exception import AnnifException
12
from annif.project import Access
13
from annif.suggestion import SuggestionFilter
14
15
16
def project_not_found_error(project_id):
17
    """return a Connexion error object when a project is not found"""
18
19
    return connexion.problem(
20
        status=404,
21
        title="Project not found",
22
        detail="Project '{}' not found".format(project_id),
23
    )
24
25
26
def server_error(err):
27
    """return a Connexion error object when there is a server error (project
28
    or backend problem)"""
29
30
    return connexion.problem(
31
        status=503, title="Service unavailable", detail=err.format_message()
32
    )
33
34
35
def show_info():
36
    """return version of annif and a title for the api according to Swagger spec"""
37
38
    return {"title": "Annif REST API", "version": importlib.metadata.version("annif")}
39
40
41
def list_projects():
42
    """return a dict with projects formatted according to Swagger spec"""
43
44
    return {
45
        "projects": [
46
            proj.dump()
47
            for proj in annif.registry.get_projects(min_access=Access.public).values()
48
        ]
49
    }
50
51
52
def show_project(project_id):
53
    """return a single project formatted according to Swagger spec"""
54
55
    try:
56
        project = annif.registry.get_project(project_id, min_access=Access.hidden)
57
    except ValueError:
58
        return project_not_found_error(project_id)
59
    return project.dump()
60
61
62
def detect_language(body):
63
    """return scores for detected languages formatted according to Swagger spec"""
64
65
    text = body.get("text")
66
    candidates = body.get("candidates")
67
68
    if not candidates:
69
        return connexion.problem(
70
            status=400,
71
            title="Bad Request",
72
            detail="no candidate languages given",
73
        )
74
75
    scores = lang_detector(text, tuple(candidates))
76
77
    if not scores:
78
        return connexion.problem(
79
            status=400,
80
            title="Bad Request",
81
            detail="unsupported candidate languages",
82
        )
83
84
    return {
85
        "results": [
86
            {"language": lang if lang != "unk" else None, "score": score}
87
            for lang, score in scores
88
        ]
89
    }
90
91
92
def _suggestion_to_dict(suggestion, subject_index, language):
93
    subject = subject_index[suggestion.subject_id]
94
    return {
95
        "uri": subject.uri,
96
        "label": subject.labels[language],
97
        "notation": subject.notation,
98
        "score": suggestion.score,
99
    }
100
101
102
def suggest(project_id, body):
103
    """suggest subjects for the given text and return a dict with results
104
    formatted according to Swagger spec"""
105
106
    try:
107
        project = annif.registry.get_project(project_id, min_access=Access.hidden)
108
    except ValueError:
109
        return project_not_found_error(project_id)
110
111
    try:
112
        lang = body.get("language") or project.vocab_lang
113
    except AnnifException as err:
114
        return server_error(err)
115
116
    if lang not in project.vocab.languages:
117
        return connexion.problem(
118
            status=400,
119
            title="Bad Request",
120
            detail=f'language "{lang}" not supported by vocabulary',
121
        )
122
123
    limit = body.get("limit", 10)
124
    threshold = body.get("threshold", 0.0)
125
126
    try:
127
        hit_filter = SuggestionFilter(project.subjects, limit, threshold)
128
        result = project.suggest(body["text"])
129
    except AnnifException as err:
130
        return server_error(err)
131
132
    hits = hit_filter(result).as_list()
133
    return {
134
        "results": [_suggestion_to_dict(hit, project.subjects, lang) for hit in hits]
135
    }
136
137
138
def _documents_to_corpus(documents, subject_index):
139
    corpus = [
140
        Document(
141
            text=d["text"],
142
            subject_set=SubjectSet(
143
                [subject_index.by_uri(subj["uri"]) for subj in d["subjects"]]
144
            ),
145
        )
146
        for d in documents
147
        if "text" in d and "subjects" in d
148
    ]
149
    return DocumentList(corpus)
150
151
152
def learn(project_id, body):
153
    """learn from documents and return an empty 204 response if succesful"""
154
155
    try:
156
        project = annif.registry.get_project(project_id, min_access=Access.hidden)
157
    except ValueError:
158
        return project_not_found_error(project_id)
159
160
    try:
161
        corpus = _documents_to_corpus(body, project.subjects)
162
        project.learn(corpus)
163
    except AnnifException as err:
164
        return server_error(err)
165
166
    return None, 204
167