nucliadb-sdk 0.0.0.post2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nucliadb_sdk/__init__.py +24 -0
- nucliadb_sdk/client.py +420 -0
- nucliadb_sdk/entities.py +33 -0
- nucliadb_sdk/file.py +27 -0
- nucliadb_sdk/knowledgebox.py +368 -0
- nucliadb_sdk/labels.py +43 -0
- nucliadb_sdk/py.typed +0 -0
- nucliadb_sdk/resource.py +369 -0
- nucliadb_sdk/search.py +104 -0
- nucliadb_sdk/tests/__init__.py +18 -0
- nucliadb_sdk/tests/conftest.py +22 -0
- nucliadb_sdk/tests/fixtures.py +93 -0
- nucliadb_sdk/tests/test_create.py +29 -0
- nucliadb_sdk/tests/test_dict_resource.py +51 -0
- nucliadb_sdk/tests/test_search_resource.py +346 -0
- nucliadb_sdk/tests/test_set_resource.py +126 -0
- nucliadb_sdk/utils.py +133 -0
- nucliadb_sdk/vectors.py +51 -0
- nucliadb_sdk-0.0.0.post2.dist-info/METADATA +25 -0
- nucliadb_sdk-0.0.0.post2.dist-info/RECORD +23 -0
- nucliadb_sdk-0.0.0.post2.dist-info/WHEEL +5 -0
- nucliadb_sdk-0.0.0.post2.dist-info/top_level.txt +1 -0
- nucliadb_sdk-0.0.0.post2.dist-info/zip-safe +1 -0
nucliadb_sdk/resource.py
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# Copyright (C) 2021 Bosutech XXI S.L.
|
|
2
|
+
#
|
|
3
|
+
# nucliadb is offered under the AGPL v3.0 and as commercial software.
|
|
4
|
+
# For commercial licensing, contact us at info@nuclia.com.
|
|
5
|
+
#
|
|
6
|
+
# AGPL:
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU Affero General Public License as
|
|
9
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
10
|
+
# License, or (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU Affero General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
|
|
20
|
+
import base64
|
|
21
|
+
from typing import Callable, Dict, List, Optional, Union, cast
|
|
22
|
+
from uuid import uuid4
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
from nucliadb_models.common import Classification, FieldID
|
|
27
|
+
from nucliadb_models.common import File as NDBModelsFile
|
|
28
|
+
from nucliadb_models.file import FileField
|
|
29
|
+
from nucliadb_models.link import LinkField
|
|
30
|
+
from nucliadb_models.metadata import Origin, TokenSplit, UserFieldMetadata, UserMetadata
|
|
31
|
+
from nucliadb_models.resource import Resource
|
|
32
|
+
from nucliadb_models.text import TextField
|
|
33
|
+
from nucliadb_models.utils import FieldIdString, SlugString
|
|
34
|
+
from nucliadb_models.vectors import UserVector, UserVectorWrapper, VectorSet, VectorSets
|
|
35
|
+
from nucliadb_models.writer import (
|
|
36
|
+
GENERIC_MIME_TYPE,
|
|
37
|
+
CreateResourcePayload,
|
|
38
|
+
UpdateResourcePayload,
|
|
39
|
+
)
|
|
40
|
+
from nucliadb_sdk import DEFAULT_LABELSET, logger
|
|
41
|
+
from nucliadb_sdk.entities import Entities
|
|
42
|
+
from nucliadb_sdk.file import File
|
|
43
|
+
from nucliadb_sdk.labels import Label, Labels
|
|
44
|
+
from nucliadb_sdk.vectors import Vector, Vectors, convert_vector
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def create_resource(
|
|
48
|
+
key: Optional[str] = None,
|
|
49
|
+
text: Optional[str] = None,
|
|
50
|
+
binary: Optional[Union[File, str]] = None,
|
|
51
|
+
labels: Optional[Labels] = None,
|
|
52
|
+
entities: Optional[Entities] = None,
|
|
53
|
+
vectors: Optional[Union[Vectors, Dict[str, Union[np.ndarray, List[float]]]]] = None,
|
|
54
|
+
vectorsets: Optional[VectorSets] = None,
|
|
55
|
+
icon: Optional[str] = None,
|
|
56
|
+
) -> CreateResourcePayload:
|
|
57
|
+
create_payload = CreateResourcePayload()
|
|
58
|
+
create_payload.origin = Origin(source=Origin.Source.PYSDK)
|
|
59
|
+
if key is not None:
|
|
60
|
+
create_payload.slug = SlugString(key)
|
|
61
|
+
if icon is not None:
|
|
62
|
+
create_payload.icon = icon
|
|
63
|
+
else:
|
|
64
|
+
create_payload.icon = GENERIC_MIME_TYPE
|
|
65
|
+
main_field = None
|
|
66
|
+
if text is not None:
|
|
67
|
+
create_payload.texts[FieldIdString("text")] = TextField(body=text)
|
|
68
|
+
main_field = FieldID(field_type=FieldID.FieldType.TEXT, field="text")
|
|
69
|
+
if binary is not None:
|
|
70
|
+
if isinstance(binary, str):
|
|
71
|
+
with open(binary, "rb") as binary_file:
|
|
72
|
+
data = binary_file.read()
|
|
73
|
+
binary = File(data=data, filename=binary.split("/")[-1])
|
|
74
|
+
assert isinstance(binary, File)
|
|
75
|
+
|
|
76
|
+
create_payload.files[FieldIdString("file")] = FileField(
|
|
77
|
+
file=NDBModelsFile(
|
|
78
|
+
filename=binary.filename,
|
|
79
|
+
content_type=binary.content_type,
|
|
80
|
+
payload=base64.b64encode(binary.data),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
if main_field is None:
|
|
84
|
+
main_field = FieldID(field_type=FieldID.FieldType.FILE, field="file")
|
|
85
|
+
|
|
86
|
+
if main_field is None:
|
|
87
|
+
raise AttributeError("Missing field")
|
|
88
|
+
|
|
89
|
+
if labels is not None:
|
|
90
|
+
classifications = []
|
|
91
|
+
for label in labels:
|
|
92
|
+
if isinstance(label, Label):
|
|
93
|
+
classifications.append(
|
|
94
|
+
Classification(
|
|
95
|
+
labelset=label.labelset
|
|
96
|
+
if label.labelset is not None
|
|
97
|
+
else DEFAULT_LABELSET,
|
|
98
|
+
label=label.label,
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
elif isinstance(label, str):
|
|
102
|
+
if label.count("/") != 1:
|
|
103
|
+
labelset = DEFAULT_LABELSET
|
|
104
|
+
label_str = label
|
|
105
|
+
else:
|
|
106
|
+
labelset, label_str = label.split("/")
|
|
107
|
+
classifications.append(
|
|
108
|
+
Classification(labelset=labelset, label=label_str)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
create_payload.usermetadata = UserMetadata(classifications=classifications)
|
|
112
|
+
|
|
113
|
+
if entities is not None and text is not None:
|
|
114
|
+
tokens = []
|
|
115
|
+
for entity in entities:
|
|
116
|
+
for position in entity.positions:
|
|
117
|
+
tokens.append(
|
|
118
|
+
TokenSplit(
|
|
119
|
+
token=entity.value,
|
|
120
|
+
klass=entity.type,
|
|
121
|
+
start=position[0],
|
|
122
|
+
end=position[1],
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
create_payload.fieldmetadata = []
|
|
127
|
+
create_payload.fieldmetadata.append(
|
|
128
|
+
UserFieldMetadata(
|
|
129
|
+
token=tokens,
|
|
130
|
+
field=main_field,
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if vectors is not None:
|
|
135
|
+
if isinstance(vectors, dict):
|
|
136
|
+
new_vectors = []
|
|
137
|
+
for key, value in vectors.items():
|
|
138
|
+
list_value = convert_vector(value)
|
|
139
|
+
new_vectors.append(Vector(value=list_value, vectorset=key))
|
|
140
|
+
vectors = new_vectors
|
|
141
|
+
elif isinstance(vectors, list):
|
|
142
|
+
for vector_element in vectors:
|
|
143
|
+
vector_element.value = convert_vector(vector_element.value)
|
|
144
|
+
|
|
145
|
+
uvsw = []
|
|
146
|
+
uvw = UserVectorWrapper(field=main_field)
|
|
147
|
+
uvw.vectors = {}
|
|
148
|
+
generic_positions = (0, len(text) if text is not None else 0)
|
|
149
|
+
for vector in vectors:
|
|
150
|
+
vector_id = vector.key if vector.key is not None else uuid4().hex
|
|
151
|
+
if vectorsets is not None and vector.vectorset not in vectorsets.vectorsets:
|
|
152
|
+
logger.warn("Vectorset is not created, we will create it for you")
|
|
153
|
+
vectorsets.vectorsets[vector.vectorset] = VectorSet(
|
|
154
|
+
dimension=len(vector.value)
|
|
155
|
+
)
|
|
156
|
+
uvw.vectors[vector.vectorset] = {
|
|
157
|
+
vector_id: UserVector(
|
|
158
|
+
vector=vector.value,
|
|
159
|
+
positions=generic_positions
|
|
160
|
+
if vector.positions is None
|
|
161
|
+
else vector.positions,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
uvsw.append(uvw)
|
|
165
|
+
create_payload.uservectors = uvsw
|
|
166
|
+
|
|
167
|
+
return create_payload
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def update_resource(
|
|
171
|
+
resource: Resource,
|
|
172
|
+
text: Optional[str] = None,
|
|
173
|
+
binary: Optional[Union[File, str]] = None,
|
|
174
|
+
labels: Optional[Labels] = None,
|
|
175
|
+
entities: Optional[Entities] = None,
|
|
176
|
+
vectors: Optional[Union[Vectors, Dict[str, Union[np.ndarray, List[float]]]]] = None,
|
|
177
|
+
vectorsets: Optional[VectorSets] = None,
|
|
178
|
+
) -> UpdateResourcePayload:
|
|
179
|
+
upload_payload = UpdateResourcePayload()
|
|
180
|
+
|
|
181
|
+
main_field = None
|
|
182
|
+
if text is not None:
|
|
183
|
+
upload_payload.texts[FieldIdString("text")] = TextField(body=text)
|
|
184
|
+
main_field = FieldID(field_type=FieldID.FieldType.TEXT, field="text")
|
|
185
|
+
if binary is not None:
|
|
186
|
+
if isinstance(binary, str):
|
|
187
|
+
with open(binary, "rb") as binary_file:
|
|
188
|
+
data = binary_file.read()
|
|
189
|
+
binary = File(data=data, filename=binary.split("/")[-1])
|
|
190
|
+
|
|
191
|
+
assert isinstance(binary, File)
|
|
192
|
+
upload_payload.files[FieldIdString("file")] = FileField(
|
|
193
|
+
file=NDBModelsFile(
|
|
194
|
+
filename=binary.filename,
|
|
195
|
+
content_type=binary.content_type,
|
|
196
|
+
payload=base64.b64encode(binary.data),
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
main_field = FieldID(field_type=FieldID.FieldType.FILE, field="file")
|
|
200
|
+
|
|
201
|
+
if main_field is None and resource.data is not None:
|
|
202
|
+
if (
|
|
203
|
+
resource.data.texts is not None
|
|
204
|
+
and FieldIdString("text") in resource.data.texts
|
|
205
|
+
):
|
|
206
|
+
main_field = FieldID(field_type=FieldID.FieldType.TEXT, field="text")
|
|
207
|
+
|
|
208
|
+
elif (
|
|
209
|
+
resource.data.files is not None
|
|
210
|
+
and FieldIdString("file") in resource.data.files
|
|
211
|
+
):
|
|
212
|
+
main_field = FieldID(field_type=FieldID.FieldType.FILE, field="file")
|
|
213
|
+
|
|
214
|
+
if labels is not None:
|
|
215
|
+
classifications = []
|
|
216
|
+
for label in labels:
|
|
217
|
+
if isinstance(label, Label):
|
|
218
|
+
classifications.append(
|
|
219
|
+
Classification(
|
|
220
|
+
labelset=label.labelset
|
|
221
|
+
if label.labelset is not None
|
|
222
|
+
else DEFAULT_LABELSET,
|
|
223
|
+
label=label.label,
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
elif isinstance(label, str):
|
|
227
|
+
if label.count("/") != 1:
|
|
228
|
+
logger.warn(f"Labelset default linked to label {label}")
|
|
229
|
+
labelset = DEFAULT_LABELSET
|
|
230
|
+
label_str = label
|
|
231
|
+
else:
|
|
232
|
+
labelset, label_str = label.split("/")
|
|
233
|
+
|
|
234
|
+
classifications.append(
|
|
235
|
+
Classification(labelset=labelset, label=label_str)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
upload_payload.usermetadata = UserMetadata(classifications=classifications)
|
|
239
|
+
|
|
240
|
+
if entities is not None and text is not None:
|
|
241
|
+
tokens = []
|
|
242
|
+
for entity in entities:
|
|
243
|
+
for position in entity.positions:
|
|
244
|
+
tokens.append(
|
|
245
|
+
TokenSplit(
|
|
246
|
+
token=entity.value,
|
|
247
|
+
klass=entity.type,
|
|
248
|
+
start=position[0],
|
|
249
|
+
end=position[1],
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
upload_payload.fieldmetadata = []
|
|
254
|
+
upload_payload.fieldmetadata.append(
|
|
255
|
+
UserFieldMetadata(
|
|
256
|
+
token=tokens,
|
|
257
|
+
field=FieldID(field_type=FieldID.FieldType.TEXT, field="text"),
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
if vectors is not None:
|
|
262
|
+
if isinstance(vectors, dict):
|
|
263
|
+
new_vectors = []
|
|
264
|
+
for key, value in vectors.items():
|
|
265
|
+
list_value = convert_vector(value)
|
|
266
|
+
new_vectors.append(Vector(value=list_value, vectorset=key))
|
|
267
|
+
vectors = new_vectors
|
|
268
|
+
|
|
269
|
+
uvsw = []
|
|
270
|
+
uvw = UserVectorWrapper(field=main_field)
|
|
271
|
+
uvw.vectors = {}
|
|
272
|
+
generic_positions = (0, len(text) if text is not None else 0)
|
|
273
|
+
for vector in vectors:
|
|
274
|
+
vector_id = vector.key if vector.key is not None else uuid4().hex
|
|
275
|
+
if vectorsets is not None and vector.vectorset not in vectorsets.vectorsets:
|
|
276
|
+
logger.warn("Vectorset is not created, we will create it for you")
|
|
277
|
+
vectorsets.vectorsets[vector.vectorset] = VectorSet(
|
|
278
|
+
dimension=len(vector.value)
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
uvw.vectors[vector.vectorset] = {
|
|
282
|
+
vector_id: UserVector(
|
|
283
|
+
vector=vector.value,
|
|
284
|
+
positions=generic_positions
|
|
285
|
+
if vector.positions is None
|
|
286
|
+
else vector.positions,
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
uvsw.append(uvw)
|
|
290
|
+
upload_payload.uservectors = uvsw
|
|
291
|
+
|
|
292
|
+
return upload_payload
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def from_resource_to_payload(
|
|
296
|
+
item: Resource,
|
|
297
|
+
download: Callable[[str], bytes],
|
|
298
|
+
update: bool = False,
|
|
299
|
+
):
|
|
300
|
+
if update:
|
|
301
|
+
payload: Union[
|
|
302
|
+
UpdateResourcePayload, CreateResourcePayload
|
|
303
|
+
] = UpdateResourcePayload()
|
|
304
|
+
else:
|
|
305
|
+
payload = CreateResourcePayload()
|
|
306
|
+
|
|
307
|
+
payload.slug = item.slug # type: ignore
|
|
308
|
+
payload.title = item.title
|
|
309
|
+
payload.summary = item.summary
|
|
310
|
+
if item.icon is not None:
|
|
311
|
+
payload.icon = item.icon
|
|
312
|
+
payload.thumbnail = item.thumbnail
|
|
313
|
+
payload.layout = item.layout
|
|
314
|
+
|
|
315
|
+
payload.usermetadata = item.usermetadata
|
|
316
|
+
payload.fieldmetadata = item.fieldmetadata
|
|
317
|
+
|
|
318
|
+
payload.origin = item.origin
|
|
319
|
+
|
|
320
|
+
if item.data is not None and item.data.texts is not None:
|
|
321
|
+
for field, field_payload in item.data.texts.items():
|
|
322
|
+
if field_payload is not None and field_payload.value is not None:
|
|
323
|
+
payload.texts[cast(FieldIdString, field)] = TextField(
|
|
324
|
+
body=field_payload.value.body, format=field_payload.value.format
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
if item.data is not None and item.data.links is not None:
|
|
328
|
+
for field, link_payload in item.data.links.items():
|
|
329
|
+
if link_payload is not None and link_payload.value is not None:
|
|
330
|
+
payload.links[cast(FieldIdString, field)] = LinkField(
|
|
331
|
+
uri=link_payload.value.uri,
|
|
332
|
+
headers=link_payload.value.headers,
|
|
333
|
+
cookies=link_payload.value.cookies,
|
|
334
|
+
language=link_payload.value.language,
|
|
335
|
+
localstorage=link_payload.value.localstorage,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if item.data is not None and item.data.files is not None:
|
|
339
|
+
for field, file_payload in item.data.files.items():
|
|
340
|
+
if (
|
|
341
|
+
file_payload.value is not None
|
|
342
|
+
and file_payload.value is not None
|
|
343
|
+
and file_payload.value.file is not None
|
|
344
|
+
and file_payload.value.file.uri is not None
|
|
345
|
+
):
|
|
346
|
+
data = download(file_payload.value.file.uri)
|
|
347
|
+
payload.files[cast(FieldIdString, field)] = FileField(
|
|
348
|
+
language=file_payload.value.language,
|
|
349
|
+
password=file_payload.value.password,
|
|
350
|
+
file=NDBModelsFile(
|
|
351
|
+
payload=base64.b64encode(data),
|
|
352
|
+
filename=file_payload.value.file.filename,
|
|
353
|
+
content_type=file_payload.value.file.content_type,
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if item.data is not None and item.data.layouts is not None:
|
|
358
|
+
raise NotImplementedError()
|
|
359
|
+
|
|
360
|
+
if item.data is not None and item.data.conversations is not None:
|
|
361
|
+
raise NotImplementedError()
|
|
362
|
+
|
|
363
|
+
if item.data is not None and item.data.keywordsets is not None:
|
|
364
|
+
raise NotImplementedError()
|
|
365
|
+
|
|
366
|
+
if item.data is not None and item.data.datetimes is not None:
|
|
367
|
+
raise NotImplementedError()
|
|
368
|
+
|
|
369
|
+
return payload
|
nucliadb_sdk/search.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Copyright (C) 2021 Bosutech XXI S.L.
|
|
2
|
+
#
|
|
3
|
+
# nucliadb is offered under the AGPL v3.0 and as commercial software.
|
|
4
|
+
# For commercial licensing, contact us at info@nuclia.com.
|
|
5
|
+
#
|
|
6
|
+
# AGPL:
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU Affero General Public License as
|
|
9
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
10
|
+
# License, or (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU Affero General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from typing import Iterator, List
|
|
23
|
+
|
|
24
|
+
from nucliadb_models.search import KnowledgeboxSearchResults
|
|
25
|
+
from nucliadb_sdk.client import NucliaDBClient
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ScoreType(str, Enum):
|
|
29
|
+
BM25 = "BM25"
|
|
30
|
+
COSINE = "COSINE"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class SearchResource:
|
|
35
|
+
key: str
|
|
36
|
+
text: str
|
|
37
|
+
labels: List[str]
|
|
38
|
+
score: float
|
|
39
|
+
score_type: ScoreType
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SearchResult:
|
|
43
|
+
inner_search_results: KnowledgeboxSearchResults
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self, inner_search_results: KnowledgeboxSearchResults, client: NucliaDBClient
|
|
47
|
+
):
|
|
48
|
+
self.inner_search_results = inner_search_results
|
|
49
|
+
self.client = client
|
|
50
|
+
|
|
51
|
+
def __iter__(self) -> Iterator[SearchResource]:
|
|
52
|
+
if self.inner_search_results.fulltext is not None:
|
|
53
|
+
for fts in self.inner_search_results.fulltext.results:
|
|
54
|
+
resource = self.client.get_resource(fts.rid)
|
|
55
|
+
if fts.field_type == "t":
|
|
56
|
+
text = resource.data.texts[fts.field].value.body
|
|
57
|
+
classifications = [
|
|
58
|
+
classification.label
|
|
59
|
+
for classification in resource.usermetadata.classifications
|
|
60
|
+
]
|
|
61
|
+
yield SearchResource(
|
|
62
|
+
text=text,
|
|
63
|
+
labels=classifications,
|
|
64
|
+
score=fts.score,
|
|
65
|
+
key=fts.rid,
|
|
66
|
+
score_type=ScoreType.BM25,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if self.inner_search_results.sentences is not None:
|
|
70
|
+
for sentence in self.inner_search_results.sentences.results:
|
|
71
|
+
resource = self.client.get_resource(sentence.rid)
|
|
72
|
+
if sentence.field_type == "t":
|
|
73
|
+
text = resource.data.texts[sentence.field].value.body
|
|
74
|
+
classifications = [
|
|
75
|
+
classification.label
|
|
76
|
+
for classification in resource.usermetadata.classifications
|
|
77
|
+
]
|
|
78
|
+
yield SearchResource(
|
|
79
|
+
text=text,
|
|
80
|
+
labels=classifications,
|
|
81
|
+
score=sentence.score,
|
|
82
|
+
key=sentence.rid,
|
|
83
|
+
score_type=ScoreType.COSINE,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def fulltext(self):
|
|
88
|
+
return self.inner_search_results.fulltext
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def sentences(self):
|
|
92
|
+
return self.inner_search_results.sentences
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def paragraphs(self):
|
|
96
|
+
return self.inner_search_results.paragraphs
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def resources(self):
|
|
100
|
+
return self.inner_search_results.resources
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def relations(self):
|
|
104
|
+
return self.inner_search_results.relations
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright (C) 2021 Bosutech XXI S.L.
|
|
2
|
+
#
|
|
3
|
+
# nucliadb is offered under the AGPL v3.0 and as commercial software.
|
|
4
|
+
# For commercial licensing, contact us at info@nuclia.com.
|
|
5
|
+
#
|
|
6
|
+
# AGPL:
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU Affero General Public License as
|
|
9
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
10
|
+
# License, or (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU Affero General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (C) 2021 Bosutech XXI S.L.
|
|
2
|
+
#
|
|
3
|
+
# nucliadb is offered under the AGPL v3.0 and as commercial software.
|
|
4
|
+
# For commercial licensing, contact us at info@nuclia.com.
|
|
5
|
+
#
|
|
6
|
+
# AGPL:
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU Affero General Public License as
|
|
9
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
10
|
+
# License, or (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU Affero General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
#
|
|
20
|
+
pytest_plugins = [
|
|
21
|
+
"nucliadb_sdk.tests.fixtures",
|
|
22
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Copyright (C) 2021 Bosutech XXI S.L.
|
|
2
|
+
#
|
|
3
|
+
# nucliadb is offered under the AGPL v3.0 and as commercial software.
|
|
4
|
+
# For commercial licensing, contact us at info@nuclia.com.
|
|
5
|
+
#
|
|
6
|
+
# AGPL:
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU Affero General Public License as
|
|
9
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
10
|
+
# License, or (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU Affero General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
#
|
|
20
|
+
import os
|
|
21
|
+
from uuid import uuid4
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
import requests
|
|
25
|
+
from pytest_docker_fixtures import images # type: ignore
|
|
26
|
+
from pytest_docker_fixtures.containers._base import BaseImage # type: ignore
|
|
27
|
+
|
|
28
|
+
from nucliadb_models.resource import KnowledgeBoxObj
|
|
29
|
+
from nucliadb_sdk.client import Environment, NucliaDBClient
|
|
30
|
+
from nucliadb_sdk.knowledgebox import KnowledgeBox
|
|
31
|
+
|
|
32
|
+
images.settings["nucliadb"] = {
|
|
33
|
+
"image": "nuclia/nucliadb",
|
|
34
|
+
"version": "latest",
|
|
35
|
+
"env": {
|
|
36
|
+
"NUCLIADB_DISABLE_TELEMETRY": "True",
|
|
37
|
+
"max_receive_message_length": "40",
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NucliaDB(BaseImage):
|
|
43
|
+
name = "nucliadb"
|
|
44
|
+
port = 8080
|
|
45
|
+
|
|
46
|
+
def check(self):
|
|
47
|
+
try:
|
|
48
|
+
response = requests.get(f"http://{self.host}:{self.get_port()}")
|
|
49
|
+
return response.status_code == 200
|
|
50
|
+
except Exception:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.fixture(scope="session")
|
|
55
|
+
def nucliadb():
|
|
56
|
+
if os.environ.get("TEST_LOCAL_NUCLIADB"):
|
|
57
|
+
yield os.environ.get("TEST_LOCAL_NUCLIADB")
|
|
58
|
+
else:
|
|
59
|
+
container = NucliaDB()
|
|
60
|
+
host, port = container.run()
|
|
61
|
+
public_api_url = f"http://{host}:{port}"
|
|
62
|
+
yield public_api_url
|
|
63
|
+
container.stop()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.fixture(scope="function")
|
|
67
|
+
def knowledgebox(nucliadb):
|
|
68
|
+
kbslug = uuid4().hex
|
|
69
|
+
api_path = f"{nucliadb}/api/v1"
|
|
70
|
+
response = requests.post(
|
|
71
|
+
f"{api_path}/kbs",
|
|
72
|
+
json={"slug": kbslug},
|
|
73
|
+
headers={"X-NUCLIADB-ROLES": "MANAGER"},
|
|
74
|
+
)
|
|
75
|
+
assert response.status_code == 201
|
|
76
|
+
|
|
77
|
+
kb = KnowledgeBoxObj.parse_raw(response.content)
|
|
78
|
+
kbid = kb.uuid
|
|
79
|
+
|
|
80
|
+
url = f"{api_path}/kb/{kbid}"
|
|
81
|
+
client = NucliaDBClient(
|
|
82
|
+
environment=Environment.OSS,
|
|
83
|
+
writer_host=url,
|
|
84
|
+
reader_host=url,
|
|
85
|
+
search_host=url,
|
|
86
|
+
train_host=url,
|
|
87
|
+
)
|
|
88
|
+
yield KnowledgeBox(client)
|
|
89
|
+
|
|
90
|
+
response = requests.delete(
|
|
91
|
+
f"{api_path}/kb/{kbid}", headers={"X-NUCLIADB-ROLES": f"MANAGER"}
|
|
92
|
+
)
|
|
93
|
+
assert response.status_code == 200
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright (C) 2021 Bosutech XXI S.L.
|
|
2
|
+
#
|
|
3
|
+
# nucliadb is offered under the AGPL v3.0 and as commercial software.
|
|
4
|
+
# For commercial licensing, contact us at info@nuclia.com.
|
|
5
|
+
#
|
|
6
|
+
# AGPL:
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU Affero General Public License as
|
|
9
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
10
|
+
# License, or (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU Affero General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
|
|
20
|
+
import pytest
|
|
21
|
+
|
|
22
|
+
from nucliadb_sdk.utils import KnowledgeBoxAlreadyExists, create_knowledge_box
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_create_kb(nucliadb: str):
|
|
26
|
+
kbid = create_knowledge_box(slug="hola", nucliadb_base_url=nucliadb)
|
|
27
|
+
assert kbid is not None
|
|
28
|
+
with pytest.raises(KnowledgeBoxAlreadyExists):
|
|
29
|
+
kbid = create_knowledge_box(slug="hola", nucliadb_base_url=nucliadb)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright (C) 2021 Bosutech XXI S.L.
|
|
2
|
+
#
|
|
3
|
+
# nucliadb is offered under the AGPL v3.0 and as commercial software.
|
|
4
|
+
# For commercial licensing, contact us at info@nuclia.com.
|
|
5
|
+
#
|
|
6
|
+
# AGPL:
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU Affero General Public License as
|
|
9
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
10
|
+
# License, or (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU Affero General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
|
|
20
|
+
from nucliadb_sdk.entities import Entity
|
|
21
|
+
from nucliadb_sdk.file import File
|
|
22
|
+
from nucliadb_sdk.knowledgebox import KnowledgeBox
|
|
23
|
+
from nucliadb_sdk.vectors import Vector
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_dict_resource(knowledgebox: KnowledgeBox):
|
|
27
|
+
assert knowledgebox.get("mykey1") is None
|
|
28
|
+
|
|
29
|
+
knowledgebox.new_vectorset("base", 2)
|
|
30
|
+
|
|
31
|
+
resource_id = knowledgebox.upload(
|
|
32
|
+
key="mykey1",
|
|
33
|
+
binary=File(data=b"asd", filename="data"),
|
|
34
|
+
text="I'm Ramon",
|
|
35
|
+
labels=["labelset/positive"],
|
|
36
|
+
entities=[Entity(type="NAME", value="Ramon", positions=[(5, 9)])],
|
|
37
|
+
vectors=[Vector(value=[1.0, 0.2], vectorset="base")],
|
|
38
|
+
)
|
|
39
|
+
resource2 = knowledgebox[resource_id]
|
|
40
|
+
|
|
41
|
+
knowledgebox["mykey2"] = resource2
|
|
42
|
+
|
|
43
|
+
assert len(knowledgebox) == 2
|
|
44
|
+
|
|
45
|
+
del knowledgebox["mykey1"]
|
|
46
|
+
|
|
47
|
+
assert len(knowledgebox) == 1
|
|
48
|
+
|
|
49
|
+
del knowledgebox["mykey2"]
|
|
50
|
+
|
|
51
|
+
assert len(knowledgebox) == 0
|