|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
|
|
|
|
3
|
|
|
'''This module provides an abstraction of controlled vocabularies. |
|
4
|
|
|
|
|
5
|
|
|
This abstraction allows our application to work with both local and remote |
|
6
|
|
|
vocabs (be they SOAP, REST, XML-RPC or something else). |
|
7
|
|
|
|
|
8
|
|
|
The basic idea is that we have skos providers. Each provider is an instance |
|
9
|
|
|
of a :class:`VocabularyProvider`. The same class can thus be reused with |
|
10
|
|
|
different configurations to handle different vocabs. Generally speaking, every |
|
11
|
|
|
instance of a certain :class:`VocabularyProvider` will deal with concepts and |
|
12
|
|
|
collections from a single conceptscheme. |
|
13
|
|
|
''' |
|
14
|
|
|
|
|
15
|
|
|
from __future__ import unicode_literals |
|
16
|
|
|
|
|
17
|
|
|
import abc |
|
18
|
|
|
import copy |
|
19
|
|
|
import logging |
|
20
|
|
|
from operator import methodcaller |
|
21
|
|
|
|
|
22
|
|
|
from .skos import ( |
|
23
|
|
|
Concept, |
|
24
|
|
|
Collection, |
|
25
|
|
|
ConceptScheme |
|
26
|
|
|
) |
|
27
|
|
|
|
|
28
|
|
|
from .uri import ( |
|
29
|
|
|
DefaultUrnGenerator, |
|
30
|
|
|
DefaultConceptSchemeUrnGenerator |
|
31
|
|
|
) |
|
32
|
|
|
|
|
33
|
|
|
log = logging.getLogger(__name__) |
|
34
|
|
|
|
|
35
|
|
|
|
|
36
|
|
|
class VocabularyProvider: |
|
37
|
|
|
'''An interface that all vocabulary providers must follow. |
|
38
|
|
|
''' |
|
39
|
|
|
|
|
40
|
|
|
__metaclass__ = abc.ABCMeta |
|
41
|
|
|
|
|
42
|
|
|
concept_scheme = None |
|
43
|
|
|
'''The :class:`~skosprovider.skos.ConceptScheme` this provider serves.''' |
|
44
|
|
|
|
|
45
|
|
|
uri_generator = None |
|
46
|
|
|
'''The :class:`~skosprovider.uri.UriGenerator` responsible for generating |
|
47
|
|
|
uris for this provider.''' |
|
48
|
|
|
|
|
49
|
|
|
def __init__(self, metadata, **kwargs): |
|
50
|
|
|
'''Create a new provider and register some metadata. |
|
51
|
|
|
|
|
52
|
|
|
|
|
53
|
|
|
:param uri_generator: An object that implements the |
|
54
|
|
|
:class:`skosprovider.uri.UriGenerator` interface. |
|
55
|
|
|
:param concept_scheme: A :class:`~skosprovider.skos.ConceptScheme`. If |
|
56
|
|
|
not present, a default :class:`~skosprovider.skos.ConceptScheme` |
|
57
|
|
|
will be created with a uri generated by the |
|
58
|
|
|
:class:`~skosprovider.uri.DefaultConceptSchemeUrnGenerator` in |
|
59
|
|
|
combination with the provider `id`. |
|
60
|
|
|
:param dict metadata: Metadata essential to this provider. Expected |
|
61
|
|
|
metadata: |
|
62
|
|
|
|
|
63
|
|
|
* `id`: A unique identifier for the vocabulary. Required. |
|
64
|
|
|
* `default_language`: Used to determine what language to use when \ |
|
65
|
|
|
returning labels if no language is specified. Will default \ |
|
66
|
|
|
to `en` if not specified. |
|
67
|
|
|
* `subject`: A list of subjects or tags that define what the \ |
|
68
|
|
|
provider is about or what the provider can handle. This \ |
|
69
|
|
|
information can then be used when querying a \ |
|
70
|
|
|
:class:`~skosprovider.registry.Registry` for providers. |
|
71
|
|
|
''' |
|
72
|
|
|
if 'subject' not in metadata: |
|
73
|
|
|
metadata['subject'] = [] |
|
74
|
|
|
self.metadata = metadata |
|
75
|
|
|
if 'uri_generator' in kwargs: |
|
76
|
|
|
self.uri_generator = kwargs.get('uri_generator') |
|
77
|
|
|
else: |
|
78
|
|
|
self.uri_generator = DefaultUrnGenerator(self.metadata.get('id')) |
|
79
|
|
|
if 'concept_scheme' in kwargs: |
|
80
|
|
|
self.concept_scheme = kwargs.get('concept_scheme') |
|
81
|
|
|
else: |
|
82
|
|
|
self.concept_scheme = ConceptScheme( |
|
83
|
|
|
uri=DefaultConceptSchemeUrnGenerator().generate( |
|
84
|
|
|
id=self.metadata.get('id') |
|
85
|
|
|
) |
|
86
|
|
|
) |
|
87
|
|
|
|
|
88
|
|
|
def _get_language(self, **kwargs): |
|
89
|
|
|
'''Determine what language to render labels in. |
|
90
|
|
|
|
|
91
|
|
|
Will first check if there's a language keyword specified in **kwargs. |
|
92
|
|
|
If not, will check the default language of the provider. If there's no |
|
93
|
|
|
default language, will fall back to 'en'. |
|
94
|
|
|
|
|
95
|
|
|
:rtype: str |
|
96
|
|
|
''' |
|
97
|
|
|
return kwargs.get( |
|
98
|
|
|
'language', |
|
99
|
|
|
self.metadata.get('default_language', 'en') |
|
100
|
|
|
) |
|
101
|
|
|
|
|
102
|
|
|
def _get_sort(self, **kwargs): |
|
103
|
|
|
'''Determine on what attribute to sort. |
|
104
|
|
|
|
|
105
|
|
|
:rtype: str |
|
106
|
|
|
''' |
|
107
|
|
|
return kwargs.get('sort', None) |
|
108
|
|
|
|
|
109
|
|
|
def _get_sort_order(self, **kwargs): |
|
110
|
|
|
'''Determine the sort order. |
|
111
|
|
|
|
|
112
|
|
|
:rtype: str |
|
113
|
|
|
:returns: 'asc' or 'desc' |
|
114
|
|
|
''' |
|
115
|
|
|
return kwargs.get('sort_order', 'asc') |
|
116
|
|
|
|
|
117
|
|
|
def _sort(self, concepts, sort=None, language='any', reverse=False): |
|
118
|
|
|
''' |
|
119
|
|
|
Returns a sorted version of a list of concepts. Will leave the original |
|
120
|
|
|
list unsorted. |
|
121
|
|
|
|
|
122
|
|
|
:param list concepts: A list of concepts and collections. |
|
123
|
|
|
:param string sort: What to sort on: `id`, `label` or `sortlabel` |
|
124
|
|
|
:param string language: Language to use when sorting on `label` or |
|
125
|
|
|
`sortlabel`. |
|
126
|
|
|
:param boolean reverse: Reverse the sort order? |
|
127
|
|
|
:rtype: list |
|
128
|
|
|
''' |
|
129
|
|
|
sorted = copy.copy(concepts) |
|
130
|
|
|
if sort: |
|
131
|
|
|
sorted.sort(key=methodcaller('_sortkey', sort, language), reverse=reverse) |
|
132
|
|
|
return sorted |
|
133
|
|
|
|
|
134
|
|
|
def get_vocabulary_id(self): |
|
135
|
|
|
'''Get an identifier for the vocabulary. |
|
136
|
|
|
|
|
137
|
|
|
:rtype: String or number. |
|
138
|
|
|
''' |
|
139
|
|
|
return self.metadata.get('id') |
|
140
|
|
|
|
|
141
|
|
|
def get_metadata(self): |
|
142
|
|
|
'''Get some metadata on the provider or the vocab it represents. |
|
143
|
|
|
|
|
144
|
|
|
:rtype: Dict. |
|
145
|
|
|
''' |
|
146
|
|
|
return self.metadata |
|
147
|
|
|
|
|
148
|
|
|
@abc.abstractmethod |
|
149
|
|
|
def get_by_id(self, id): |
|
150
|
|
|
'''Get all information on a concept or collection, based on id. |
|
151
|
|
|
|
|
152
|
|
|
Providers should assume that all id's passed are strings. If a provider |
|
153
|
|
|
knows that internally it uses numeric identifiers, it's up to the |
|
154
|
|
|
provider to do the typecasting. Generally, this should not be done by |
|
155
|
|
|
changing the id's themselves (eg. from int to str), but by doing the |
|
156
|
|
|
id comparisons in a type agnostic way. |
|
157
|
|
|
|
|
158
|
|
|
Since this method could be used to find both concepts and collections, |
|
159
|
|
|
it's assumed that there are no id collisions between concepts and |
|
160
|
|
|
collections. |
|
161
|
|
|
|
|
162
|
|
|
:rtype: :class:`skosprovider.skos.Concept` or |
|
163
|
|
|
:class:`skosprovider.skos.Collection` or `False` if the concept or |
|
164
|
|
|
collection is unknown to the provider. |
|
165
|
|
|
''' |
|
166
|
|
|
|
|
167
|
|
|
@abc.abstractmethod |
|
168
|
|
|
def get_by_uri(self, uri): |
|
169
|
|
|
'''Get all information on a concept or collection, based on a |
|
170
|
|
|
:term:`URI`. |
|
171
|
|
|
|
|
172
|
|
|
:rtype: :class:`skosprovider.skos.Concept` or |
|
173
|
|
|
:class:`skosprovider.skos.Collection` or `False` if the concept or |
|
174
|
|
|
collection is unknown to the provider. |
|
175
|
|
|
''' |
|
176
|
|
|
|
|
177
|
|
|
@abc.abstractmethod |
|
178
|
|
|
def get_all(self, **kwargs): |
|
179
|
|
|
'''Returns all concepts and collections in this provider. |
|
180
|
|
|
|
|
181
|
|
|
:param string language: Optional. If present, it should be a |
|
182
|
|
|
:term:`language-tag`. This language-tag is passed on to the |
|
183
|
|
|
underlying providers and used when selecting the label to display |
|
184
|
|
|
for each concept. |
|
185
|
|
|
:param string sort: Optional. If present, it should either be `id`, |
|
186
|
|
|
`label` or `sortlabel`. The `sortlabel` option means the providers should |
|
187
|
|
|
take into account any `sortLabel` if present, if not it will |
|
188
|
|
|
fallback to a regular label to sort on. |
|
189
|
|
|
:param string sort_order: Optional. What order to sort in: `asc` or |
|
190
|
|
|
`desc`. Defaults to `asc` |
|
191
|
|
|
|
|
192
|
|
|
:returns: A :class:`lst` of concepts and collections. Each of these is a dict |
|
193
|
|
|
with the following keys: |
|
194
|
|
|
|
|
195
|
|
|
* id: id within the conceptscheme |
|
196
|
|
|
* uri: :term:`uri` of the concept or collection |
|
197
|
|
|
* type: concept or collection |
|
198
|
|
|
* label: A label to represent the concept or collection. It is \ |
|
199
|
|
|
determined by looking at the `language` parameter, the default \ |
|
200
|
|
|
language of the provider and finally falls back to `en`. |
|
201
|
|
|
|
|
202
|
|
|
''' |
|
203
|
|
|
|
|
204
|
|
|
@abc.abstractmethod |
|
205
|
|
|
def get_top_concepts(self, **kwargs): |
|
206
|
|
|
''' |
|
207
|
|
|
Returns all top-level concepts in this provider. |
|
208
|
|
|
|
|
209
|
|
|
Top-level concepts are concepts that have no broader concepts |
|
210
|
|
|
themselves. They might have narrower concepts, but this is not |
|
211
|
|
|
mandatory. |
|
212
|
|
|
|
|
213
|
|
|
:param string language: Optional. If present, it should be a |
|
214
|
|
|
:term:`language-tag`. This language-tag is passed on to the |
|
215
|
|
|
underlying providers and used when selecting the label to display |
|
216
|
|
|
for each concept. |
|
217
|
|
|
:param string sort: Optional. If present, it should either be `id`, |
|
218
|
|
|
`label` or `sortlabel`. The `sortlabel` option means the providers should |
|
219
|
|
|
take into account any `sortLabel` if present, if not it will |
|
220
|
|
|
fallback to a regular label to sort on. |
|
221
|
|
|
:param string sort_order: Optional. What order to sort in: `asc` or |
|
222
|
|
|
`desc`. Defaults to `asc` |
|
223
|
|
|
|
|
224
|
|
|
:returns: A :class:`lst` of concepts, NOT collections. Each of these |
|
225
|
|
|
is a dict with the following keys: |
|
226
|
|
|
|
|
227
|
|
|
* id: id within the conceptscheme |
|
228
|
|
|
* uri: :term:`uri` of the concept or collection |
|
229
|
|
|
* type: concept or collection |
|
230
|
|
|
* label: A label to represent the concept or collection. It is \ |
|
231
|
|
|
determined by looking at the `language` parameter, the default \ |
|
232
|
|
|
language of the provider and finally falls back to `en`. |
|
233
|
|
|
|
|
234
|
|
|
''' |
|
235
|
|
|
|
|
236
|
|
|
@abc.abstractmethod |
|
237
|
|
|
def find(self, query, **kwargs): |
|
238
|
|
|
'''Find concepts that match a certain query. |
|
239
|
|
|
|
|
240
|
|
|
Currently query is expected to be a dict, so that complex queries can |
|
241
|
|
|
be passed. You can use this dict to search for concepts or collections |
|
242
|
|
|
with a certain label, with a certain type and for concepts that belong |
|
243
|
|
|
to a certain collection. |
|
244
|
|
|
|
|
245
|
|
|
.. code-block:: python |
|
246
|
|
|
|
|
247
|
|
|
# Find anything that has a label of church. |
|
248
|
|
|
provider.find({'label': 'church'}) |
|
249
|
|
|
|
|
250
|
|
|
# Find all concepts that are a part of collection 5. |
|
251
|
|
|
provider.find({'type': 'concept', 'collection': {'id': 5}) |
|
252
|
|
|
|
|
253
|
|
|
# Find all concepts, collections or children of these |
|
254
|
|
|
# that belong to collection 5. |
|
255
|
|
|
provider.find({'collection': {'id': 5, 'depth': 'all'}) |
|
256
|
|
|
|
|
257
|
|
|
# Find anything that has a label of church. |
|
258
|
|
|
# Preferentially display a label in Dutch. |
|
259
|
|
|
provider.find({'label': 'church'}, language='nl') |
|
260
|
|
|
|
|
261
|
|
|
:param query: A dict that can be used to express a query. The following |
|
262
|
|
|
keys are permitted: |
|
263
|
|
|
|
|
264
|
|
|
* `label`: Search for something with this label value. An empty \ |
|
265
|
|
|
label is equal to searching for all concepts. |
|
266
|
|
|
* `type`: Limit the search to certain SKOS elements. If not \ |
|
267
|
|
|
present or `None`, `all` is assumed: |
|
268
|
|
|
|
|
269
|
|
|
* `concept`: Only return :class:`skosprovider.skos.Concept` \ |
|
270
|
|
|
instances. |
|
271
|
|
|
* `collection`: Only return \ |
|
272
|
|
|
:class:`skosprovider.skos.Collection` instances. |
|
273
|
|
|
* `all`: Return both :class:`skosprovider.skos.Concept` and \ |
|
274
|
|
|
:class:`skosprovider.skos.Collection` instances. |
|
275
|
|
|
* `collection`: Search only for concepts belonging to a certain \ |
|
276
|
|
|
collection. This argument should be a dict with two keys: |
|
277
|
|
|
|
|
278
|
|
|
* `id`: The id of a collection. Required. |
|
279
|
|
|
* `depth`: Can be `members` or `all`. Optional. If not \ |
|
280
|
|
|
present, `members` is assumed, meaning only concepts or \ |
|
281
|
|
|
collections that are a direct member of the collection \ |
|
282
|
|
|
should be considered. When set to `all`, this method \ |
|
283
|
|
|
should return concepts and collections that are a member \ |
|
284
|
|
|
of the collection or are a narrower concept of a member \ |
|
285
|
|
|
of the collection. |
|
286
|
|
|
|
|
287
|
|
|
:param string language: Optional. If present, it should be a |
|
288
|
|
|
:term:`language-tag`. This language-tag is passed on to the |
|
289
|
|
|
underlying providers and used when selecting the label to display |
|
290
|
|
|
for each concept. |
|
291
|
|
|
:param string sort: Optional. If present, it should either be `id`, |
|
292
|
|
|
`label` or `sortlabel`. The `sortlabel` option means the providers should |
|
293
|
|
|
take into account any `sortLabel` if present, if not it will |
|
294
|
|
|
fallback to a regular label to sort on. |
|
295
|
|
|
:param string sort_order: Optional. What order to sort in: `asc` or |
|
296
|
|
|
`desc`. Defaults to `asc` |
|
297
|
|
|
|
|
298
|
|
|
:returns: A :class:`lst` of concepts and collections. Each of these |
|
299
|
|
|
is a dict with the following keys: |
|
300
|
|
|
|
|
301
|
|
|
* id: id within the conceptscheme |
|
302
|
|
|
* uri: :term:`uri` of the concept or collection |
|
303
|
|
|
* type: concept or collection |
|
304
|
|
|
* label: A label to represent the concept or collection. It is \ |
|
305
|
|
|
determined by looking at the `language` parameter, the default \ |
|
306
|
|
|
language of the provider and finally falls back to `en`. |
|
307
|
|
|
|
|
308
|
|
|
''' |
|
309
|
|
|
|
|
310
|
|
|
@abc.abstractmethod |
|
311
|
|
|
def expand(self, id): |
|
312
|
|
|
'''Expand a concept or collection to all it's narrower |
|
313
|
|
|
concepts. |
|
314
|
|
|
|
|
315
|
|
|
This method should recurse and also return narrower concepts |
|
316
|
|
|
of narrower concepts. |
|
317
|
|
|
|
|
318
|
|
|
If the id passed belongs to a :class:`skosprovider.skos.Concept`, |
|
319
|
|
|
the id of the concept itself should be include in the return value. |
|
320
|
|
|
|
|
321
|
|
|
If the id passed belongs to a :class:`skosprovider.skos.Collection`, |
|
322
|
|
|
the id of the collection itself must not be present in the return value |
|
323
|
|
|
In this case the return value includes all the member concepts and |
|
324
|
|
|
their narrower concepts. |
|
325
|
|
|
|
|
326
|
|
|
:param id: A concept or collection id. |
|
327
|
|
|
:rtype: A list of id's or `False` if the concept or collection doesn't |
|
328
|
|
|
exist. |
|
329
|
|
|
''' |
|
330
|
|
|
|
|
331
|
|
|
def get_top_display(self, **kwargs): |
|
332
|
|
|
''' |
|
333
|
|
|
Returns all concepts or collections that form the top-level of a |
|
334
|
|
|
display hierarchy. |
|
335
|
|
|
|
|
336
|
|
|
As opposed to the :meth:`get_top_concepts`, this method can possibly |
|
337
|
|
|
return both concepts and collections. |
|
338
|
|
|
|
|
339
|
|
|
:param string language: Optional. If present, it should be a |
|
340
|
|
|
:term:`language-tag`. This language-tag is passed on to the |
|
341
|
|
|
underlying providers and used when selecting the label to display |
|
342
|
|
|
for each concept. |
|
343
|
|
|
:param string sort: Optional. If present, it should either be `id`, |
|
344
|
|
|
`label` or `sortlabel`. The `sortlabel` option means the providers should |
|
345
|
|
|
take into account any `sortLabel` if present, if not it will |
|
346
|
|
|
fallback to a regular label to sort on. |
|
347
|
|
|
:param string sort_order: Optional. What order to sort in: `asc` or |
|
348
|
|
|
`desc`. Defaults to `asc` |
|
349
|
|
|
|
|
350
|
|
|
:returns: A :class:`lst` of concepts and collections. Each of these |
|
351
|
|
|
is a dict with the following keys: |
|
352
|
|
|
|
|
353
|
|
|
* id: id within the conceptscheme |
|
354
|
|
|
* uri: :term:`uri` of the concept or collection |
|
355
|
|
|
* type: concept or collection |
|
356
|
|
|
* label: A label to represent the concept or collection. It is\ |
|
357
|
|
|
determined by looking at the `language` parameter, the default\ |
|
358
|
|
|
language of the provider and finally falls back to `en`. |
|
359
|
|
|
|
|
360
|
|
|
''' |
|
361
|
|
|
|
|
362
|
|
|
def get_children_display(self, id, **kwargs): |
|
363
|
|
|
''' |
|
364
|
|
|
Return a list of concepts or collections that should be displayed |
|
365
|
|
|
under this concept or collection. |
|
366
|
|
|
|
|
367
|
|
|
:param string language: Optional. If present, it should be a |
|
368
|
|
|
:term:`language-tag`. This language-tag is passed on to the |
|
369
|
|
|
underlying providers and used when selecting the label to display |
|
370
|
|
|
for each concept. |
|
371
|
|
|
:param string sort: Optional. If present, it should either be `id`, |
|
372
|
|
|
`label` or `sortlabel`. The `sortlabel` option means the providers should |
|
373
|
|
|
take into account any `sortLabel` if present, if not it will |
|
374
|
|
|
fallback to a regular label to sort on. |
|
375
|
|
|
:param string sort_order: Optional. What order to sort in: `asc` or |
|
376
|
|
|
`desc`. Defaults to `asc` |
|
377
|
|
|
|
|
378
|
|
|
:param str id: A concept or collection id. |
|
379
|
|
|
:returns: A :class:`lst` of concepts and collections. Each of these |
|
380
|
|
|
is a dict with the following keys: |
|
381
|
|
|
|
|
382
|
|
|
* id: id within the conceptscheme |
|
383
|
|
|
* uri: :term:`uri` of the concept or collection |
|
384
|
|
|
* type: concept or collection |
|
385
|
|
|
* label: A label to represent the concept or collection. It is \ |
|
386
|
|
|
determined by looking at the `language` parameter, the default \ |
|
387
|
|
|
language of the provider and finally falls back to `en`. |
|
388
|
|
|
|
|
389
|
|
|
''' |
|
390
|
|
|
|
|
391
|
|
|
|
|
392
|
|
|
class MemoryProvider(VocabularyProvider): |
|
393
|
|
|
''' |
|
394
|
|
|
A provider that keeps everything in memory. |
|
395
|
|
|
|
|
396
|
|
|
The data is passed in the constructor of this provider as a :class:`lst` of |
|
397
|
|
|
:class:`skosprovider.skos.Concept` and :class:`skosprovider.skos.Collection` |
|
398
|
|
|
instances. |
|
399
|
|
|
''' |
|
400
|
|
|
|
|
401
|
|
|
case_insensitive = True |
|
402
|
|
|
''' |
|
403
|
|
|
Is searching for labels case insensitive? |
|
404
|
|
|
|
|
405
|
|
|
By default a search for a label is done case insensitive. Older versions of |
|
406
|
|
|
this provider were case sensitive. If this behaviour is desired, this can |
|
407
|
|
|
be triggered by providing a `case_insensitive` keyword to the constructor. |
|
408
|
|
|
''' |
|
409
|
|
|
|
|
410
|
|
|
def __init__(self, metadata, list, **kwargs): |
|
411
|
|
|
''' |
|
412
|
|
|
:param dict metadata: A dictionary with keywords like language. |
|
413
|
|
|
:param list list: A list of :class:`skosprovider.skos.Concept` and |
|
414
|
|
|
:class:`skosprovider.skos.Collection` instances. |
|
415
|
|
|
:param Boolean case_insensitive: Should searching for labels be done |
|
416
|
|
|
case-insensitive? |
|
417
|
|
|
''' |
|
418
|
|
|
super(MemoryProvider, self).__init__(metadata, **kwargs) |
|
419
|
|
|
self.list = list |
|
420
|
|
|
if 'case_insensitive' in kwargs: |
|
421
|
|
|
self.case_insensitive = kwargs['case_insensitive'] |
|
422
|
|
|
|
|
423
|
|
|
def get_by_id(self, id): |
|
424
|
|
|
id = str(id) |
|
425
|
|
|
for c in self.list: |
|
426
|
|
|
if str(c.id) == id: |
|
427
|
|
|
return c |
|
428
|
|
|
return False |
|
429
|
|
|
|
|
430
|
|
|
def get_by_uri(self, uri): |
|
431
|
|
|
uri = str(uri) |
|
432
|
|
|
for c in self.list: |
|
433
|
|
|
if str(c.uri) == uri: |
|
434
|
|
|
return c |
|
435
|
|
|
return False |
|
436
|
|
|
|
|
437
|
|
|
def find(self, query, **kwargs): |
|
438
|
|
|
query = self._normalise_query(query) |
|
439
|
|
|
filtered = [c for c in self.list if self._include_in_find(c, query)] |
|
440
|
|
|
language = self._get_language(**kwargs) |
|
441
|
|
|
sort = self._get_sort(**kwargs) |
|
442
|
|
|
sort_order = self._get_sort_order(**kwargs) |
|
443
|
|
|
return [self._get_find_dict(c, **kwargs) for c in self._sort(filtered, sort, language, sort_order == 'desc')] |
|
444
|
|
|
|
|
445
|
|
|
def _normalise_query(self, query): |
|
446
|
|
|
''' |
|
447
|
|
|
:param query: A dict that can be used to express a query. |
|
448
|
|
|
:rtype: dict |
|
449
|
|
|
''' |
|
450
|
|
|
if 'type' in query and query['type'] not in ['concept', 'collection']: |
|
451
|
|
|
del query['type'] |
|
452
|
|
|
return query |
|
453
|
|
|
|
|
454
|
|
|
def _include_in_find(self, c, query): |
|
455
|
|
|
''' |
|
456
|
|
|
:param c: A :class:`skosprovider.skos.Concept` or |
|
457
|
|
|
:class:`skosprovider.skos.Collection`. |
|
458
|
|
|
:param query: A dict that can be used to express a query. |
|
459
|
|
|
:rtype: boolean |
|
460
|
|
|
''' |
|
461
|
|
|
include = True |
|
462
|
|
|
if include and 'type' in query: |
|
463
|
|
|
include = query['type'] == c.type |
|
464
|
|
|
if include and 'label' in query: |
|
465
|
|
|
def finder(l, query): |
|
466
|
|
|
if not self.case_insensitive: |
|
467
|
|
|
return l.label.find(query['label']) |
|
468
|
|
|
else: |
|
469
|
|
|
return l.label.upper().find(query['label'].upper()) |
|
470
|
|
|
include = any([finder(l, query) >= 0 for l in c.labels]) |
|
471
|
|
|
if include and 'collection' in query: |
|
472
|
|
|
coll = self.get_by_id(query['collection']['id']) |
|
473
|
|
|
if not coll or not isinstance(coll, Collection): |
|
474
|
|
|
raise ValueError( |
|
475
|
|
|
'You are searching for items in an unexisting collection.' |
|
476
|
|
|
) |
|
477
|
|
|
if 'depth' in query['collection'] and query['collection']['depth'] == 'all': |
|
478
|
|
|
members = self.expand(coll.id) |
|
479
|
|
|
else: |
|
480
|
|
|
members = coll.members |
|
481
|
|
|
include = any([True for id in members if str(id) == str(c.id)]) |
|
482
|
|
|
return include |
|
483
|
|
|
|
|
484
|
|
|
def _get_find_dict(self, c, **kwargs): |
|
485
|
|
|
''' |
|
486
|
|
|
Return a dict that can be used in the return list of the :meth:`find` |
|
487
|
|
|
method. |
|
488
|
|
|
|
|
489
|
|
|
:param c: A :class:`skosprovider.skos.Concept` or |
|
490
|
|
|
:class:`skosprovider.skos.Collection`. |
|
491
|
|
|
:rtype: dict |
|
492
|
|
|
''' |
|
493
|
|
|
language = self._get_language(**kwargs) |
|
494
|
|
|
return { |
|
495
|
|
|
'id': c.id, |
|
496
|
|
|
'uri': c.uri, |
|
497
|
|
|
'type': c.type, |
|
498
|
|
|
'label': None if c.label() is None else c.label(language).label |
|
499
|
|
|
} |
|
500
|
|
|
|
|
501
|
|
|
def get_all(self, **kwargs): |
|
502
|
|
|
language = self._get_language(**kwargs) |
|
503
|
|
|
sort = self._get_sort(**kwargs) |
|
504
|
|
|
sort_order = self._get_sort_order(**kwargs) |
|
505
|
|
|
return [self._get_find_dict(c, **kwargs) for c in self._sort(self.list, sort, language, sort_order == 'desc')] |
|
506
|
|
|
|
|
507
|
|
|
def get_top_concepts(self, **kwargs): |
|
508
|
|
|
language = self._get_language(**kwargs) |
|
509
|
|
|
sort = self._get_sort(**kwargs) |
|
510
|
|
|
sort_order = self._get_sort_order(**kwargs) |
|
511
|
|
|
tc = [c for c in self.list if isinstance(c, Concept) and len(c.broader) == 0] |
|
512
|
|
|
return [self._get_find_dict(c, **kwargs) for c in self._sort(tc, sort, language, sort_order == 'desc')] |
|
513
|
|
|
|
|
514
|
|
|
def expand(self, id): |
|
515
|
|
|
id = str(id) |
|
516
|
|
|
for c in self.list: |
|
517
|
|
|
if str(c.id) == id: |
|
518
|
|
|
if isinstance(c, Concept): |
|
519
|
|
|
ret = set([c.id]) |
|
520
|
|
|
for cid in c.narrower: |
|
521
|
|
|
ret |= set(self.expand(cid)) |
|
522
|
|
|
return list(ret) |
|
523
|
|
|
elif isinstance(c, Collection): |
|
524
|
|
|
ret = set([]) |
|
525
|
|
|
for m in c.members: |
|
526
|
|
|
ret |= set(self.expand(m)) |
|
527
|
|
|
return list(ret) |
|
528
|
|
|
return False |
|
529
|
|
|
|
|
530
|
|
|
def get_top_display(self, **kwargs): |
|
531
|
|
|
language = self._get_language(**kwargs) |
|
532
|
|
|
sort = self._get_sort(**kwargs) |
|
533
|
|
|
sort_order = self._get_sort_order(**kwargs) |
|
534
|
|
|
td = [c for c in self.list if |
|
535
|
|
|
(isinstance(c, Concept) and len(c.broader) == 0 and len(c.member_of) == 0) or |
|
536
|
|
|
(isinstance(c, Collection) and len(c.superordinates) == 0 and len(c.member_of) == 0)] |
|
537
|
|
|
return [ |
|
538
|
|
|
{ |
|
539
|
|
|
'id': c.id, |
|
540
|
|
|
'uri': c.uri, |
|
541
|
|
|
'type': c.type, |
|
542
|
|
|
'label': None if c.label() is None else c.label(language).label |
|
543
|
|
|
} for c in self._sort(td, sort, language, sort_order == 'desc')] |
|
544
|
|
|
|
|
545
|
|
|
def get_children_display(self, id, **kwargs): |
|
546
|
|
|
c = self.get_by_id(id) |
|
547
|
|
|
if not c: |
|
548
|
|
|
return False |
|
549
|
|
|
language = self._get_language(**kwargs) |
|
550
|
|
|
sort = self._get_sort(**kwargs) |
|
551
|
|
|
sort_order = self._get_sort_order(**kwargs) |
|
552
|
|
|
if isinstance(c, Concept): |
|
553
|
|
|
if len(c.subordinate_arrays) == 0: |
|
554
|
|
|
display_children = c.narrower |
|
555
|
|
|
else: |
|
556
|
|
|
display_children = c.subordinate_arrays |
|
557
|
|
|
else: |
|
558
|
|
|
display_children = c.members |
|
559
|
|
|
dc = [self.get_by_id(dcid) for dcid in display_children] |
|
560
|
|
|
return [ |
|
561
|
|
|
{ |
|
562
|
|
|
'id': co.id, |
|
563
|
|
|
'uri': co.uri, |
|
564
|
|
|
'type': co.type, |
|
565
|
|
|
'label': None if co.label() is None else co.label(language).label |
|
566
|
|
|
} for co in self._sort(dc, sort, language, sort_order == 'desc')] |
|
567
|
|
|
|
|
568
|
|
|
|
|
569
|
|
|
class DictionaryProvider(MemoryProvider): |
|
570
|
|
|
'''A simple vocab provider that use a python list of dicts. |
|
571
|
|
|
|
|
572
|
|
|
The provider expects a list with elements that are dicts that represent |
|
573
|
|
|
the concepts. |
|
574
|
|
|
''' |
|
575
|
|
|
|
|
576
|
|
|
def __init__(self, metadata, list, **kwargs): |
|
577
|
|
|
super(DictionaryProvider, self).__init__(metadata, [], **kwargs) |
|
578
|
|
|
self.list = [self._from_dict(c) for c in list] |
|
579
|
|
|
|
|
580
|
|
|
def _from_dict(self, data): |
|
581
|
|
|
if 'type' in data and data['type'] == 'collection': |
|
582
|
|
|
return Collection( |
|
583
|
|
|
id=data['id'], |
|
584
|
|
|
uri=data.get('uri') if data.get('uri') is not None else self.uri_generator.generate(type='collection', id=data['id']), |
|
585
|
|
|
concept_scheme=self.concept_scheme, |
|
586
|
|
|
labels=data.get('labels', []), |
|
587
|
|
|
notes=data.get('notes', []), |
|
588
|
|
|
sources=data.get('sources', []), |
|
589
|
|
|
members=data.get('members', []), |
|
590
|
|
|
member_of=data.get('member_of', []), |
|
591
|
|
|
superordinates=data.get('superordinates', []) |
|
592
|
|
|
) |
|
593
|
|
|
else: |
|
594
|
|
|
return Concept( |
|
595
|
|
|
id=data['id'], |
|
596
|
|
|
uri=data.get('uri') if data.get('uri') is not None else self.uri_generator.generate(type='collection', id=data['id']), |
|
597
|
|
|
concept_scheme=self.concept_scheme, |
|
598
|
|
|
labels=data.get('labels', []), |
|
599
|
|
|
notes=data.get('notes', []), |
|
600
|
|
|
sources=data.get('sources', []), |
|
601
|
|
|
broader=data.get('broader', []), |
|
602
|
|
|
narrower=data.get('narrower', []), |
|
603
|
|
|
related=data.get('related', []), |
|
604
|
|
|
member_of=data.get('member_of', []), |
|
605
|
|
|
subordinate_arrays=data.get('subordinate_arrays', []), |
|
606
|
|
|
matches=data.get('matches', {}) |
|
607
|
|
|
) |
|
608
|
|
|
|
|
609
|
|
|
|
|
610
|
|
|
class SimpleCsvProvider(MemoryProvider): |
|
611
|
|
|
''' |
|
612
|
|
|
A provider that reads a simple csv format into memory. |
|
613
|
|
|
|
|
614
|
|
|
The supported csv format looks like this: |
|
615
|
|
|
<id>,<preflabel>,<note>,<source> |
|
616
|
|
|
|
|
617
|
|
|
This provider essentialy provides a flat list of concepts. This is commonly |
|
618
|
|
|
associated with short lookup-lists. |
|
619
|
|
|
|
|
620
|
|
|
.. versionadded:: 0.2.0 |
|
621
|
|
|
''' |
|
622
|
|
|
|
|
623
|
|
|
def __init__(self, metadata, reader, **kwargs): |
|
624
|
|
|
''' |
|
625
|
|
|
:param metadata: A metadata dictionary. |
|
626
|
|
|
:param reader: A csv reader. |
|
627
|
|
|
''' |
|
628
|
|
|
super(SimpleCsvProvider, self).__init__(metadata, [], **kwargs) |
|
629
|
|
|
self.list = [self._from_row(row) for row in reader] |
|
630
|
|
|
|
|
631
|
|
|
def _from_row(self, row): |
|
632
|
|
|
id = row[0] |
|
633
|
|
|
labels = [{'label': row[1], 'type':'prefLabel'}] |
|
634
|
|
|
if len(row) > 2 and row[2]: |
|
635
|
|
|
notes = [{'note': row[2], 'type':'note'}] |
|
636
|
|
|
else: |
|
637
|
|
|
notes = [] |
|
638
|
|
|
if len(row) > 3 and row[3]: |
|
639
|
|
|
sources = [{'citation': 'My citation.'}] |
|
640
|
|
|
else: |
|
641
|
|
|
sources = [] |
|
642
|
|
|
return Concept( |
|
643
|
|
|
id=id, |
|
644
|
|
|
uri=self.uri_generator.generate(type='concept', id=id), |
|
645
|
|
|
labels=labels, |
|
646
|
|
|
notes=notes, |
|
647
|
|
|
sources=sources |
|
648
|
|
|
) |
|
649
|
|
|
|