atramhasis.views.crud   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 326
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 225
dl 0
loc 326
rs 9.52
c 0
b 0
f 0
wmc 36

15 Methods

Rating   Name   Duplication   Size   Complexity  
A AtramhasisCrud._get_json_body() 0 5 3
A AtramhasisCrud.__init__() 0 14 5
B AtramhasisCrud.add_concept() 0 66 7
A AtramhasisCrud.get_conceptscheme() 0 7 1
A AtramhasisCrud.edit_concept() 0 23 2
A AtramhasisCrud.update_provider() 0 21 2
A AtramhasisCrud.edit_conceptscheme() 0 17 1
A AtramhasisCrud.get_providers() 0 12 2
A AtramhasisCrud.get_provider() 0 7 1
A AtramhasisCrud.add_provider() 0 13 1
A AtramhasisCrud.delete_concept() 0 20 2
A AtramhasisCrud._validate_conceptscheme() 0 14 2
A AtramhasisCrud.get_concept() 0 23 4
A AtramhasisCrud._validate_concept() 0 15 2
A AtramhasisCrud.delete_provider() 0 11 1
1
"""
2
Module containing views related to the REST service.
3
"""
4
5
import logging
6
import time
7
8
import colander
9
import transaction
10
from pyramid.httpexceptions import HTTPNoContent
11
from pyramid.view import view_config
12
from pyramid.view import view_defaults
13
from pyramid_skosprovider.views import ProviderView
14
from skosprovider_sqlalchemy.models import Collection
15
from skosprovider_sqlalchemy.models import Concept
16
from skosprovider_sqlalchemy.providers import SQLAlchemyProvider
17
from sqlalchemy.exc import IntegrityError
18
from sqlalchemy.exc import NoResultFound
19
20
from atramhasis import utils
21
from atramhasis.audit import audit
22
from atramhasis.cache import invalidate_scheme_cache
23
from atramhasis.data.models import IDGenerationStrategy
24
from atramhasis.errors import ConceptNotFoundException
25
from atramhasis.errors import ConceptSchemeNotFoundException
26
from atramhasis.errors import SkosRegistryNotFoundException
27
from atramhasis.errors import ValidationError
28
from atramhasis.json_processors import provider
29
from atramhasis.mappers import map_concept
30
from atramhasis.mappers import map_conceptscheme
31
from atramhasis.protected_resources import protected_operation
32
from atramhasis.utils import from_thing
33
from atramhasis.utils import internal_providers_only
34
35
LOG = logging.getLogger(__name__)
36
37
38
@view_defaults(accept='application/json', renderer='skosrenderer_verbose', openapi=True)
39
class AtramhasisCrud:
40
    """
41
    This object groups CRUD REST views part of the private user interface.
42
    """
43
44
    def __init__(self, request):
45
        self.request = request
46
        self.skos_manager = self.request.data_managers['skos_manager']
47
        self.conceptscheme_manager = self.request.data_managers['conceptscheme_manager']
48
        self.logged_in = request.authenticated_userid
49
        if 'scheme_id' in self.request.matchdict:
50
            self.scheme_id = self.request.matchdict['scheme_id']
51
            if hasattr(request, 'skos_registry') and request.skos_registry is not None:
52
                self.skos_registry = self.request.skos_registry
53
            else:
54
                raise SkosRegistryNotFoundException()
55
            self.provider = self.skos_registry.get_provider(self.scheme_id)
56
            if not self.provider:
57
                raise ConceptSchemeNotFoundException(self.scheme_id)
58
59
    def _get_json_body(self):
60
        json_body = self.request.json_body
61
        if 'c_id' in self.request.matchdict and 'id' not in json_body:
62
            json_body['id'] = self.request.matchdict['c_id']
63
        return json_body
64
65
    def _validate_concept(self, json_concept, provider, validate_id_generation):
66
        from atramhasis.validators import (
67
            Concept as ConceptSchema,
68
            concept_schema_validator,
69
        )
70
71
        concept_schema = ConceptSchema(validator=concept_schema_validator).bind(
72
            request=self.request,
73
            provider=provider,
74
            validate_id_generation=validate_id_generation,
75
        )
76
        try:
77
            return concept_schema.deserialize(json_concept)
78
        except colander.Invalid as e:
79
            raise ValidationError('Concept could not be validated', e.asdict())
80
81
    def _validate_conceptscheme(self, json_conceptscheme):
82
        from atramhasis.validators import (
83
            ConceptScheme as ConceptSchemeSchema,
84
            conceptscheme_schema_validator,
85
        )
86
87
        conceptscheme_schema = ConceptSchemeSchema(
88
            validator=conceptscheme_schema_validator
89
        ).bind(request=self.request)
90
        try:
91
            return conceptscheme_schema.deserialize(json_conceptscheme)
92
        except colander.Invalid as e:  # pragma no cover
93
            # I doubt this code will ever be reached, keeping it here just in case
94
            raise ValidationError('Conceptscheme could not be validated', e.asdict())
95
96
    @audit
97
    @view_config(
98
        route_name='skosprovider.conceptscheme', permission='view', request_method='GET'
99
    )
100
    def get_conceptscheme(self):
101
        # is the same as pyramid_skosprovider but wrapped with the audit decorator
102
        return ProviderView(self.request).get_conceptscheme()
103
104
    @view_config(
105
        route_name='skosprovider.conceptscheme', permission='edit', request_method='PUT'
106
    )
107
    def edit_conceptscheme(self):
108
        """
109
        Edit an existing concept
110
111
        :raises atramhasis.errors.ValidationError: If the provided json can't be validated
112
        """
113
        validated_json_conceptscheme = self._validate_conceptscheme(
114
            self._get_json_body()
115
        )
116
        conceptscheme = self.conceptscheme_manager.get(self.provider.conceptscheme_id)
117
        conceptscheme = map_conceptscheme(conceptscheme, validated_json_conceptscheme)
118
        conceptscheme = self.conceptscheme_manager.save(conceptscheme)
119
        self.request.response.status = '200'
120
        return conceptscheme
121
122
    @audit
123
    @view_config(route_name='skosprovider.c', permission='view', request_method='GET')
124
    def get_concept(self):
125
        """
126
        Get an existing concept
127
128
        :raises atramhasis.errors.ConceptNotFoundException: If the concept can't be found
129
        """
130
        c_id = self.request.matchdict['c_id']
131
        if isinstance(self.provider, SQLAlchemyProvider):
132
            try:
133
                concept = self.skos_manager.get_thing(
134
                    c_id, self.provider.conceptscheme_id
135
                )
136
            except NoResultFound:
137
                raise ConceptNotFoundException(c_id)
138
        else:
139
            concept = self.provider.get_by_id(c_id)
140
            if not concept:
141
                raise ConceptNotFoundException(c_id)
142
143
        self.request.response.status = '200'
144
        return concept
145
146
    @internal_providers_only
147
    @view_config(
148
        route_name='skosprovider.conceptscheme.cs',
149
        permission='edit',
150
        request_method='POST',
151
    )
152
    def add_concept(self):
153
        """
154
        Add a new concept to a conceptscheme
155
156
        :raises atramhasis.errors.ValidationError: If the provided json can't be validated
157
        """
158
        validated_json_concept = self._validate_concept(
159
            self._get_json_body(), self.provider, validate_id_generation=True
160
        )
161
        exc = None
162
        id_generation_strategy = IDGenerationStrategy.NUMERIC
163
        for _ in range(5):
164
            try:
165
                if validated_json_concept['type'] == 'concept':
166
                    concept = Concept()
167
                else:
168
                    concept = Collection()
169
170
                id_generation_strategy = self.provider.metadata.get(
171
                    'atramhasis.id_generation_strategy', IDGenerationStrategy.NUMERIC
172
                )
173
                if id_generation_strategy == IDGenerationStrategy.MANUAL:
174
                    concept.concept_id = validated_json_concept['id']
175
                else:
176
                    concept.concept_id = self.skos_manager.get_next_cid(
177
                        self.provider.conceptscheme_id, id_generation_strategy
178
                    )
179
180
                concept.conceptscheme_id = self.provider.conceptscheme_id
181
                concept.uri = self.provider.uri_generator.generate(
182
                    id=concept.concept_id
183
                )
184
                map_concept(concept, validated_json_concept, self.skos_manager)
185
                concept = self.skos_manager.save(concept)
186
                break
187
            except IntegrityError as exc:
188
                if id_generation_strategy == IDGenerationStrategy.MANUAL:
189
                    # Technically the concept_id is not 100% guaranteed to be the cause
190
                    # of an IntegrityError, so log trace just in case.
191
                    LOG.exception('Integrity error')
192
193
                    concept_id = validated_json_concept['concept_id']
194
                    raise ValidationError(
195
                        'Integrity error',
196
                        [{'concept_id': f'{concept_id} already exists.'}],
197
                    )
198
                # There is a small chance that another concept gets added at the same
199
                # time. There is nothing wrong with the request, so we try again.
200
                transaction.abort()
201
                time.sleep(0.05)
202
        else:
203
            raise Exception(f'Could not save new concept due to IntegrityErrors. {exc}')
204
205
        invalidate_scheme_cache(self.scheme_id)
206
207
        self.request.response.status = '201'
208
        self.request.response.location = self.request.route_path(
209
            'skosprovider.c', scheme_id=self.scheme_id, c_id=concept.concept_id
210
        )
211
        return from_thing(concept)
212
213
    @internal_providers_only
214
    @view_config(route_name='skosprovider.c', permission='edit', request_method='PUT')
215
    def edit_concept(self):
216
        """
217
        Edit an existing concept
218
219
        :raises atramhasis.errors.ConceptNotFoundException: If the concept can't be found
220
        :raises atramhasis.errors.ValidationError: If the provided json can't be validated
221
        """
222
        c_id = self.request.matchdict['c_id']
223
        validated_json_concept = self._validate_concept(
224
            self._get_json_body(), self.provider, validate_id_generation=False
225
        )
226
        try:
227
            concept = self.skos_manager.get_thing(c_id, self.provider.conceptscheme_id)
228
        except NoResultFound:
229
            raise ConceptNotFoundException(c_id)
230
        concept = map_concept(concept, validated_json_concept, self.skos_manager)
231
232
        invalidate_scheme_cache(self.scheme_id)
233
234
        self.request.response.status = '200'
235
        return from_thing(concept)
236
237
    @internal_providers_only
238
    @protected_operation
239
    @view_config(
240
        route_name='skosprovider.c', permission='delete', request_method='DELETE'
241
    )
242
    def delete_concept(self):
243
        """
244
        Delete an existing concept
245
246
        :raises atramhasis.errors.ConceptNotFoundException: If the concept can't be found
247
        """
248
        c_id = self.request.matchdict['c_id']
249
        try:
250
            concept = self.skos_manager.get_thing(c_id, self.provider.conceptscheme_id)
251
        except NoResultFound:
252
            raise ConceptNotFoundException(c_id)
253
        self.skos_manager.delete_thing(concept)
254
255
        invalidate_scheme_cache(self.scheme_id)
256
        return HTTPNoContent()
257
258
    @view_config(
259
        route_name='atramhasis.providers',
260
        permission='view',
261
        request_method='GET',
262
    )
263
    def get_providers(self):
264
        query_params = self.request.openapi_validated.parameters.query
265
        if 'subject' in query_params:
266
            filters = {'subject': query_params['subject']}
267
        else:
268
            filters = {}
269
        return self.request.skos_registry.get_providers(**filters)
270
271
    @view_config(
272
        route_name='atramhasis.provider',
273
        permission='view',
274
        request_method='GET',
275
    )
276
    def get_provider(self):
277
        return self.request.skos_registry.get_provider(self.request.matchdict['id'])
278
279
    @view_config(
280
        route_name='atramhasis.providers',
281
        permission='add-provider',
282
        request_method='POST',
283
    )
284
    def add_provider(self):
285
        db_provider = provider.create_provider(
286
            json_data=self.request.openapi_validated.body,
287
            session=self.request.db,
288
            skos_registry=self.request.skos_registry,
289
        )
290
        self.request.response.status_code = 201
291
        return utils.db_provider_to_skosprovider(db_provider)
292
293
    @view_config(
294
        route_name='atramhasis.provider',
295
        permission='edit-provider',
296
        request_method='PUT',
297
    )
298
    def update_provider(self):
299
        try:
300
            db_provider = provider.update_provider(
301
                provider_id=self.request.matchdict['id'],
302
                json_data=self.request.openapi_validated.body,
303
                session=self.request.db,
304
            )
305
            return utils.db_provider_to_skosprovider(db_provider)
306
        except NoResultFound:
307
            db_provider = provider.create_provider(
308
                json_data=self.request.openapi_validated.body,
309
                session=self.request.db,
310
                skos_registry=self.request.skos_registry,
311
            )
312
            self.request.response.status_code = 201
313
            return utils.db_provider_to_skosprovider(db_provider)
314
315
    @view_config(
316
        route_name='atramhasis.provider',
317
        permission='delete-provider',
318
        request_method='DELETE',
319
    )
320
    def delete_provider(self):
321
        provider.delete_provider(
322
            provider_id=self.request.matchdict['id'],
323
            session=self.request.db,
324
        )
325
        return HTTPNoContent()
326