personal_knowledge_library 3.0.0__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.

Potentially problematic release.


This version of personal_knowledge_library might be problematic. Click here for more details.

Files changed (42) hide show
  1. knowledge/__init__.py +91 -0
  2. knowledge/base/__init__.py +22 -0
  3. knowledge/base/access.py +167 -0
  4. knowledge/base/entity.py +267 -0
  5. knowledge/base/language.py +27 -0
  6. knowledge/base/ontology.py +2734 -0
  7. knowledge/base/search.py +473 -0
  8. knowledge/base/tenant.py +192 -0
  9. knowledge/nel/__init__.py +11 -0
  10. knowledge/nel/base.py +495 -0
  11. knowledge/nel/engine.py +123 -0
  12. knowledge/ontomapping/__init__.py +667 -0
  13. knowledge/ontomapping/manager.py +320 -0
  14. knowledge/public/__init__.py +27 -0
  15. knowledge/public/cache.py +115 -0
  16. knowledge/public/helper.py +373 -0
  17. knowledge/public/relations.py +128 -0
  18. knowledge/public/wikidata.py +1324 -0
  19. knowledge/services/__init__.py +128 -0
  20. knowledge/services/asyncio/__init__.py +7 -0
  21. knowledge/services/asyncio/base.py +458 -0
  22. knowledge/services/asyncio/graph.py +1420 -0
  23. knowledge/services/asyncio/group.py +450 -0
  24. knowledge/services/asyncio/search.py +439 -0
  25. knowledge/services/asyncio/users.py +270 -0
  26. knowledge/services/base.py +533 -0
  27. knowledge/services/graph.py +1897 -0
  28. knowledge/services/group.py +819 -0
  29. knowledge/services/helper.py +142 -0
  30. knowledge/services/ontology.py +1234 -0
  31. knowledge/services/search.py +488 -0
  32. knowledge/services/session.py +444 -0
  33. knowledge/services/tenant.py +281 -0
  34. knowledge/services/users.py +445 -0
  35. knowledge/utils/__init__.py +10 -0
  36. knowledge/utils/graph.py +417 -0
  37. knowledge/utils/wikidata.py +197 -0
  38. knowledge/utils/wikipedia.py +175 -0
  39. personal_knowledge_library-3.0.0.dist-info/LICENSE +201 -0
  40. personal_knowledge_library-3.0.0.dist-info/METADATA +1163 -0
  41. personal_knowledge_library-3.0.0.dist-info/RECORD +42 -0
  42. personal_knowledge_library-3.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,1420 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright © 2024-present Wacom. All rights reserved.
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import urllib
7
+ from pathlib import Path
8
+ from typing import Any, Optional, List, Dict, Tuple
9
+ from urllib.parse import urlparse
10
+
11
+ import aiohttp
12
+ import orjson
13
+
14
+ from knowledge.base.entity import (
15
+ DATA_PROPERTIES_TAG,
16
+ TYPE_TAG,
17
+ URI_TAG,
18
+ LABELS_TAG,
19
+ IS_MAIN_TAG,
20
+ RELATIONS_TAG,
21
+ LOCALE_TAG,
22
+ EntityStatus,
23
+ Label,
24
+ URIS_TAG,
25
+ FORCE_TAG,
26
+ VISIBILITY_TAG,
27
+ RELATION_TAG,
28
+ TEXT_TAG,
29
+ )
30
+ from knowledge.base.language import LocaleCode, EN_US
31
+ from knowledge.base.ontology import (
32
+ DataProperty,
33
+ OntologyPropertyReference,
34
+ ThingObject,
35
+ OntologyClassReference,
36
+ ObjectProperty,
37
+ )
38
+ from knowledge.nel.base import KnowledgeGraphEntity, EntityType, KnowledgeSource, EntitySource
39
+ from knowledge.services import AUTHORIZATION_HEADER_FLAG, IS_OWNER_PARAM
40
+ from knowledge.services import (
41
+ SUBJECT_URI,
42
+ RELATION_URI,
43
+ OBJECT_URI,
44
+ LANGUAGE_PARAMETER,
45
+ LIMIT,
46
+ LISTING,
47
+ TOTAL_COUNT,
48
+ SEARCH_TERM,
49
+ TYPES_PARAMETER,
50
+ APPLICATION_JSON_HEADER,
51
+ SUBJECT,
52
+ OBJECT,
53
+ PREDICATE,
54
+ LIMIT_PARAMETER,
55
+ ESTIMATE_COUNT,
56
+ TARGET,
57
+ ACTIVATION_TAG,
58
+ SEARCH_PATTERN_PARAMETER,
59
+ LITERAL_PARAMETER,
60
+ VALUE,
61
+ NEXT_PAGE_ID_TAG,
62
+ ENTITIES_TAG,
63
+ RESULT_TAG,
64
+ EXACT_MATCH,
65
+ )
66
+ from knowledge.services.asyncio.base import AsyncServiceAPIClient, handle_error
67
+ from knowledge.services.base import (
68
+ WacomServiceAPIClient,
69
+ WacomServiceException,
70
+ USER_AGENT_HEADER_FLAG,
71
+ CONTENT_TYPE_HEADER_FLAG,
72
+ DEFAULT_TIMEOUT,
73
+ format_exception,
74
+ )
75
+ from knowledge.services.graph import Visibility, SearchPattern, MIME_TYPE
76
+ from knowledge.services.helper import split_updates, entity_payload
77
+
78
+
79
+ # -------------------------------------------- Service API Client ------------------------------------------------------
80
+ class AsyncWacomKnowledgeService(AsyncServiceAPIClient):
81
+ """
82
+ AsyncWacomKnowledgeService
83
+ ---------------------
84
+ Client for the Semantic Ink Private knowledge system.
85
+
86
+ Operations for entities:
87
+ - Creation of entities
88
+ - Update of entities
89
+ - Deletion of entities
90
+ - Listing of entities
91
+
92
+ Parameters
93
+ ----------
94
+ application_name: str
95
+ Name of the application
96
+ service_url: str
97
+ URL of the service
98
+ service_endpoint: str
99
+ Base endpoint
100
+ """
101
+
102
+ USER_ENDPOINT: str = "user"
103
+ ENTITY_ENDPOINT: str = "entity"
104
+ ENTITY_BULK_ENDPOINT: str = "entity/bulk"
105
+ ENTITY_IMAGE_ENDPOINT: str = "entity/image/"
106
+ ACTIVATIONS_ENDPOINT: str = "entity/activations"
107
+ LISTING_ENDPOINT: str = "entity/types"
108
+ NAMED_ENTITY_LINKING_ENDPOINT: str = "nel/text"
109
+ RELATION_ENDPOINT: str = "entity/{}/relation"
110
+ RELATIONS_ENDPOINT: str = "entity/{}/relations"
111
+ SEARCH_LABELS_ENDPOINT: str = "semantic-search/labels"
112
+ SEARCH_TYPES_ENDPOINT: str = "semantic-search/types"
113
+ SEARCH_LITERALS_ENDPOINT: str = "semantic-search/literal"
114
+ SEARCH_DESCRIPTION_ENDPOINT: str = "semantic-search/description"
115
+ SEARCH_RELATION_ENDPOINT: str = "semantic-search/relation"
116
+ ONTOLOGY_UPDATE_ENDPOINT: str = "ontology-update"
117
+
118
+ def __init__(
119
+ self,
120
+ application_name: str,
121
+ service_url: str = WacomServiceAPIClient.SERVICE_URL,
122
+ service_endpoint: str = "graph/v1",
123
+ graceful_shutdown: bool = False,
124
+ ):
125
+ super().__init__(
126
+ application_name=application_name,
127
+ service_url=service_url,
128
+ service_endpoint=service_endpoint,
129
+ graceful_shutdown=graceful_shutdown,
130
+ )
131
+
132
+ async def entity(self, uri: str, auth_key: Optional[str] = None) -> ThingObject:
133
+ """
134
+ Retrieve entity information from personal knowledge, using the URI as identifier.
135
+
136
+ **Remark:** Object properties (relations) must be requested separately.
137
+
138
+ Parameters
139
+ ----------
140
+ uri: str
141
+ URI of entity
142
+ auth_key: Optional[str]
143
+ Use a different auth key than the one from the client
144
+
145
+ Returns
146
+ -------
147
+ thing: ThingObject
148
+ Entities with is type URI, description, an image/icon, and tags (labels).
149
+
150
+ Raises
151
+ ------
152
+ WacomServiceException
153
+ If the graph service returns an error code or the entity is not found in the knowledge graph
154
+ """
155
+ if auth_key is None:
156
+ auth_key, _ = await self.handle_token()
157
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/{uri}"
158
+ headers: Dict[str, str] = {
159
+ USER_AGENT_HEADER_FLAG: self.user_agent,
160
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
161
+ }
162
+ async with AsyncServiceAPIClient.__async_session__() as session:
163
+ async with session.get(url, headers=headers, verify_ssl=self.verify_calls) as response:
164
+ if response.ok:
165
+ e: Dict[str, Any] = await response.json()
166
+ pref_label: List[Label] = []
167
+ aliases: List[Label] = []
168
+ # Extract labels and alias
169
+ for label in e[LABELS_TAG]:
170
+ if label[IS_MAIN_TAG]: # Labels
171
+ pref_label.append(Label.create_from_dict(label))
172
+ else: # Alias
173
+ aliases.append(Label.create_from_dict(label))
174
+ else:
175
+ raise await handle_error(
176
+ f"Retrieving of entity content failed. URI:={uri}.", response, headers=headers
177
+ )
178
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
179
+ # Create ThingObject
180
+ thing: ThingObject = ThingObject.from_dict(e)
181
+ return thing
182
+
183
+ async def set_entity_image_local(self, entity_uri: str, path: Path, auth_key: Optional[str] = None) -> str:
184
+ """Setting the image of the entity.
185
+ The image is stored locally.
186
+
187
+ Parameters
188
+ ----------
189
+ entity_uri: str
190
+ URI of the entity.
191
+ path: Path
192
+ The path of image.
193
+ auth_key: Optional[str] [default:=None]
194
+ Auth key from user if not set, the client auth key will be used
195
+
196
+ Returns
197
+ -------
198
+ image_id: str
199
+ ID of uploaded image
200
+
201
+ Raises
202
+ ------
203
+ WacomServiceException
204
+ If the graph service returns an error code.
205
+ """
206
+ if auth_key is None:
207
+ auth_key, _ = await self.handle_token()
208
+ with path.open("rb") as fp:
209
+ image_bytes: bytes = fp.read()
210
+ file_name: str = str(path.absolute())
211
+ _, file_extension = os.path.splitext(file_name.lower())
212
+ mime_type = MIME_TYPE[file_extension]
213
+ return await self.set_entity_image(entity_uri, image_bytes, file_name, mime_type, auth_key=auth_key)
214
+
215
+ async def set_entity_image_url(
216
+ self,
217
+ entity_uri: str,
218
+ image_url: str,
219
+ file_name: Optional[str] = None,
220
+ mime_type: Optional[str] = None,
221
+ auth_key: Optional[str] = None,
222
+ ) -> str:
223
+ """Setting the image of the entity.
224
+ The image for the URL is downloaded and then pushed to the backend.
225
+
226
+ Parameters
227
+ ----------
228
+ auth_key: str
229
+ Auth key from user
230
+ entity_uri: str
231
+ URI of the entity.
232
+ image_url: str
233
+ URL of the image.
234
+ file_name: str [default:=None]
235
+ Name of the file. If None the name is extracted from URL.
236
+ mime_type: str [default:=None]
237
+ Mime type.
238
+ auth_key: Optional[str] [default:=None]
239
+ Auth key from user if not set, the client auth key will be used
240
+
241
+ Returns
242
+ -------
243
+ image_id: str
244
+ ID of uploaded image
245
+
246
+ Raises
247
+ ------
248
+ WacomServiceException
249
+ If the graph service returns an error code.
250
+ """
251
+ if auth_key is None:
252
+ auth_key, _ = await self.handle_token()
253
+ async with AsyncServiceAPIClient.__async_session__() as session:
254
+ headers: Dict[str, str] = {USER_AGENT_HEADER_FLAG: self.user_agent}
255
+ async with session.get(image_url, headers=headers, verify_ssl=self.verify_calls) as response:
256
+ if response.ok:
257
+ image_bytes: bytes = await response.content.read()
258
+ file_name: str = image_url if file_name is None else file_name
259
+ if mime_type is None:
260
+ _, file_extension = os.path.splitext(file_name.lower())
261
+ if file_extension not in MIME_TYPE:
262
+ raise WacomServiceException(
263
+ "Creation of entity image failed. Mime-type cannot be "
264
+ "identified or is not supported."
265
+ )
266
+ mime_type = MIME_TYPE[file_extension]
267
+
268
+ return await self.set_entity_image(entity_uri, image_bytes, file_name, mime_type, auth_key=auth_key)
269
+ raise await handle_error(f"Creation of entity image failed. URI:={entity_uri}.", response, headers=headers)
270
+
271
+ async def set_entity_image(
272
+ self,
273
+ entity_uri: str,
274
+ image_byte: bytes,
275
+ file_name: str = "icon.jpg",
276
+ mime_type: str = "image/jpeg",
277
+ auth_key: Optional[str] = None,
278
+ ) -> str:
279
+ """Setting the image of the entity.
280
+ The image for the URL is downloaded and then pushed to the backend.
281
+
282
+ Parameters
283
+ ----------
284
+ entity_uri: str
285
+ URI of the entity.
286
+ image_byte: bytes
287
+ Binary encoded image.
288
+ file_name: str [default:=None]
289
+ Name of the file. If None the name is extracted from URL.
290
+ mime_type: str [default:=None]
291
+ Mime type.
292
+ auth_key: Optional[str] [default:=None]
293
+ Auth key from user if not set, the client auth key will be used
294
+ Returns
295
+ -------
296
+ image_id: str
297
+ ID of uploaded image
298
+
299
+ Raises
300
+ ------
301
+ WacomServiceException
302
+ If the graph service returns an error code.
303
+ """
304
+ if auth_key is None:
305
+ auth_key, _ = await self.handle_token()
306
+ headers: dict = {AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
307
+ data: aiohttp.FormData = aiohttp.FormData()
308
+ data.add_field("file", image_byte, filename=file_name, content_type=mime_type)
309
+ url: str = f"{self.service_base_url}{self.ENTITY_IMAGE_ENDPOINT}{urllib.parse.quote(entity_uri)}"
310
+ async with AsyncServiceAPIClient.__async_session__() as session:
311
+ async with session.patch(
312
+ url, headers=headers, data=data, timeout=DEFAULT_TIMEOUT, verify_ssl=self.verify_calls
313
+ ) as response:
314
+ if response.ok:
315
+ image_id: str = (await response.json(loads=orjson.loads))["imageId"]
316
+ else:
317
+ raise await handle_error(
318
+ f"Creation of entity image failed. URI:={entity_uri}.", response, headers=headers
319
+ )
320
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
321
+ return image_id
322
+
323
+ async def delete_entities(self, uris: List[str], force: bool = False, auth_key: Optional[str] = None):
324
+ """
325
+ Delete a list of entities.
326
+
327
+ Parameters
328
+ ----------
329
+ uris: List[str]
330
+ List of URI of entities. **Remark:** More than 100 entities are not possible in one request
331
+ force: bool
332
+ Force deletion process
333
+ auth_key: Optional[str]
334
+ Use a different auth key than the one from the client
335
+
336
+ Raises
337
+ ------
338
+ WacomServiceException
339
+ If the graph service returns an error code
340
+ ValueError
341
+ If more than 100 entities are given
342
+ """
343
+ if len(uris) > 100:
344
+ raise ValueError("Please delete less than 100 entities.")
345
+ if auth_key is None:
346
+ auth_key, _ = await self.handle_token()
347
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}"
348
+ headers: Dict[str, str] = {
349
+ USER_AGENT_HEADER_FLAG: self.user_agent,
350
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
351
+ }
352
+ params: Dict[str, Any] = {URIS_TAG: uris, FORCE_TAG: str(force)}
353
+ async with aiohttp.ClientSession() as session:
354
+ async with session.delete(url, headers=headers, params=params, verify_ssl=self.verify_calls) as response:
355
+ if not response.ok:
356
+ raise await handle_error(
357
+ "Deletion of entities failed.", response, parameters=params, headers=headers
358
+ )
359
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
360
+
361
+ async def delete_entity(self, uri: str, force: bool = False, auth_key: Optional[str] = None):
362
+ """
363
+ Deletes an entity.
364
+
365
+ Parameters
366
+ ----------
367
+ uri: str
368
+ URI of entity
369
+ force: bool
370
+ Force deletion process
371
+ auth_key: Optional[str]
372
+ Use a different auth key than the one from the client
373
+
374
+ Raises
375
+ ------
376
+ WacomServiceException
377
+ If the graph service returns an error code
378
+ """
379
+ if auth_key is None:
380
+ auth_key, _ = await self.handle_token()
381
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/{uri}"
382
+ headers: Dict[str, str] = {
383
+ USER_AGENT_HEADER_FLAG: self.user_agent,
384
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
385
+ }
386
+ async with AsyncServiceAPIClient.__async_session__() as session:
387
+ async with session.delete(
388
+ url, headers=headers, params={FORCE_TAG: str(force)}, verify_ssl=self.verify_calls
389
+ ) as response:
390
+ if not response.ok:
391
+ raise await handle_error(f"Deletion of entity failed. URI:={uri}.", response, headers=headers)
392
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
393
+
394
+ async def exists(self, uri: str) -> bool:
395
+ """
396
+ Check if entity exists in knowledge graph.
397
+
398
+ Parameters
399
+ ----------
400
+ uri: str -
401
+ URI for entity
402
+
403
+ Returns
404
+ -------
405
+ flag: bool
406
+ Flag if entity does exist
407
+ """
408
+ try:
409
+ obj: ThingObject = await self.entity(uri)
410
+ return obj is not None
411
+ except WacomServiceException:
412
+ return False
413
+
414
+ @staticmethod
415
+ async def __entity__(entity: ThingObject):
416
+ return entity_payload(entity)
417
+
418
+ async def create_entity_bulk(
419
+ self,
420
+ entities: List[ThingObject],
421
+ batch_size: int = 10,
422
+ ignore_images: bool = False,
423
+ auth_key: Optional[str] = None,
424
+ ) -> List[ThingObject]:
425
+ """
426
+ Creates entity in graph.
427
+
428
+ Parameters
429
+ ----------
430
+ entities: List[ThingObject]
431
+ Entities
432
+ batch_size: int
433
+ Batch size
434
+ ignore_images: bool
435
+ Do not automatically upload images
436
+ auth_key: Optional[str]
437
+ If auth key is not set, the client auth key will be used.
438
+
439
+ Returns
440
+ -------
441
+ uris: List[ThingObject]
442
+ List of ThingObjects with URI
443
+
444
+ Raises
445
+ ------
446
+ WacomServiceException
447
+ If the graph service returns an error code
448
+ """
449
+ if auth_key is None:
450
+ auth_key, _ = await self.handle_token()
451
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_BULK_ENDPOINT}"
452
+ # Header info
453
+ headers: Dict[str, str] = {
454
+ USER_AGENT_HEADER_FLAG: self.user_agent,
455
+ CONTENT_TYPE_HEADER_FLAG: APPLICATION_JSON_HEADER,
456
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
457
+ }
458
+ payload: List[Dict[str, Any]] = [await AsyncWacomKnowledgeService.__entity__(e) for e in entities]
459
+ async with AsyncServiceAPIClient.__async_session__() as session:
460
+ for bulk_idx in range(0, len(entities), batch_size):
461
+ bulk = payload[bulk_idx : bulk_idx + batch_size]
462
+
463
+ async with session.post(url, json=bulk, headers=headers, verify_ssl=self.verify_calls) as response:
464
+ if response.ok:
465
+ response_dict: Dict[str, Any] = await response.json(loads=orjson.loads)
466
+ for idx, uri in enumerate(response_dict[URIS_TAG]):
467
+ entities[bulk_idx + idx].uri = uri
468
+ if (
469
+ entities[bulk_idx + idx].image is not None
470
+ and entities[bulk_idx + idx].image != ""
471
+ and not ignore_images
472
+ ):
473
+ try:
474
+ await self.set_entity_image_url(
475
+ uri, entities[bulk_idx + idx].image, auth_key=auth_key
476
+ )
477
+ except WacomServiceException as we:
478
+ logging.error(
479
+ f"Failed to upload image for entity {uri}. " f"{format_exception(we)}"
480
+ )
481
+ entities[bulk_idx + idx].uri = response_dict[URIS_TAG][idx]
482
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
483
+ return entities
484
+
485
+ async def create_entity(
486
+ self, entity: ThingObject, auth_key: Optional[str] = None, ignore_image: bool = False
487
+ ) -> str:
488
+ """
489
+ Creates entity in graph.
490
+
491
+ Parameters
492
+ ----------
493
+ entity: ThingObject
494
+ Entities object that needs to be created
495
+ auth_key: Optional[str]
496
+ Use a different auth key than the one from the client
497
+ ignore_image: bool
498
+ Ignore image.
499
+
500
+ Returns
501
+ -------
502
+ uri: str
503
+ URI of entity
504
+
505
+ Raises
506
+ ------
507
+ WacomServiceException
508
+ If the graph service returns an error code
509
+ """
510
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}"
511
+ if auth_key is None:
512
+ auth_key, _ = await self.handle_token()
513
+ # Header info
514
+ headers: Dict[str, str] = {
515
+ USER_AGENT_HEADER_FLAG: self.user_agent,
516
+ CONTENT_TYPE_HEADER_FLAG: APPLICATION_JSON_HEADER,
517
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
518
+ }
519
+ payload: Dict[str, Any] = await AsyncWacomKnowledgeService.__entity__(entity)
520
+ async with AsyncServiceAPIClient.__async_session__() as session:
521
+ async with session.post(url, json=payload, headers=headers, verify_ssl=self.verify_calls) as response:
522
+ if response.ok and not ignore_image:
523
+ uri: str = (await response.json(loads=orjson.loads))[URI_TAG]
524
+ # Set image
525
+ if entity.image is not None and entity.image.startswith("file:"):
526
+ p = urlparse(entity.image)
527
+ await self.set_entity_image_local(uri, Path(p.path), auth_key=auth_key)
528
+ elif entity.image is not None and entity.image != "":
529
+ await self.set_entity_image_url(uri, entity.image, auth_key=auth_key)
530
+ if not response.ok:
531
+ # Handle error
532
+ raise await handle_error("Creation of entity failed.", response, payload=payload, headers=headers)
533
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
534
+ return uri
535
+
536
+ async def update_entity(self, entity: ThingObject, auth_key: Optional[str] = None):
537
+ """
538
+ Updates entity in graph.
539
+
540
+ Parameters
541
+ ----------
542
+ entity: ThingObject
543
+ entity object
544
+ auth_key: Optional[str]
545
+ Use a different auth key than the one from the client
546
+
547
+ Raises
548
+ ------
549
+ WacomServiceException
550
+ If the graph service returns an error code
551
+ """
552
+ if auth_key is None:
553
+ auth_key, _ = await self.handle_token()
554
+ uri: str = entity.uri
555
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/{uri}"
556
+ # Header info
557
+ headers: dict = {
558
+ USER_AGENT_HEADER_FLAG: self.user_agent,
559
+ CONTENT_TYPE_HEADER_FLAG: APPLICATION_JSON_HEADER,
560
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
561
+ }
562
+ payload: Dict[str, Any] = await AsyncWacomKnowledgeService.__entity__(entity)
563
+ async with AsyncServiceAPIClient.__async_session__() as session:
564
+ async with session.patch(
565
+ url, json=payload, headers=headers, timeout=DEFAULT_TIMEOUT, verify_ssl=self.verify_calls
566
+ ) as response:
567
+ if not response.ok:
568
+ raise await handle_error(
569
+ f"Update of entity failed. URI:={uri}.", response, payload=payload, headers=headers
570
+ )
571
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
572
+
573
+ async def relations(
574
+ self, uri: str, auth_key: Optional[str] = None
575
+ ) -> Dict[OntologyPropertyReference, ObjectProperty]:
576
+ """
577
+ Retrieve the relations (object properties) of an entity.
578
+
579
+ Parameters
580
+ ----------
581
+ uri: str
582
+ Entities URI of the source
583
+
584
+ auth_key: Optional[str]
585
+ Use a different auth key than the one from the client
586
+
587
+ Returns
588
+ -------
589
+ relations: Dict[OntologyPropertyReference, ObjectProperty]
590
+ All relations a dict
591
+
592
+ Raises
593
+ ------
594
+ WacomServiceException
595
+ If the graph service returns an error code
596
+ """
597
+ if auth_key is None:
598
+ auth_key, _ = await self.handle_token()
599
+ url: str = (
600
+ f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/{urllib.parse.quote(uri)}"
601
+ f"/relations"
602
+ )
603
+ headers: Dict[str, str] = {
604
+ USER_AGENT_HEADER_FLAG: self.user_agent,
605
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
606
+ }
607
+ async with AsyncServiceAPIClient.__async_session__() as session:
608
+ async with session.get(url, headers=headers, verify_ssl=self.verify_calls) as response:
609
+ if response.ok:
610
+ rel: list = (await response.json(loads=orjson.loads)).get(RELATIONS_TAG)
611
+ else:
612
+ raise await handle_error(f"Retrieving of relations failed. URI:={uri}.", response, headers=headers)
613
+ # Graceful shutdown to close the session and file descriptor
614
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
615
+ return ObjectProperty.create_from_list(rel)
616
+
617
+ async def labels(self, uri: str, locale: LocaleCode = EN_US, auth_key: Optional[str] = None) -> List[Label]:
618
+ """
619
+ Extract list labels of entity.
620
+
621
+ Parameters
622
+ ----------
623
+ uri: str
624
+ Entities URI of the source
625
+ locale: str
626
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format <language_code>_<country>, e.g., en_US.
627
+ auth_key: Optional[str] = None
628
+ Use a different auth key than the one from the client
629
+
630
+ Returns
631
+ -------
632
+ labels: List[Label]
633
+ List of labels of an entity.
634
+
635
+ Raises
636
+ ------
637
+ WacomServiceException
638
+ If the graph service returns an error code
639
+ """
640
+ if auth_key is None:
641
+ auth_key, _ = await self.handle_token()
642
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/{uri}/labels"
643
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
644
+ async with AsyncServiceAPIClient.__async_session__() as session:
645
+ async with session.get(
646
+ url,
647
+ headers=headers,
648
+ params={
649
+ LOCALE_TAG: locale,
650
+ },
651
+ verify_ssl=self.verify_calls,
652
+ ) as response:
653
+ if response.ok:
654
+ labels: list = (await response.json(loads=orjson.loads)).get(LABELS_TAG)
655
+ else:
656
+ raise await handle_error(f"Failed to pull labels. URI:={uri}.", response, headers=headers)
657
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
658
+ return [Label.create_from_dict(label) for label in labels]
659
+
660
+ async def literals(
661
+ self, uri: str, locale: LocaleCode = EN_US, auth_key: Optional[str] = None
662
+ ) -> List[DataProperty]:
663
+ """
664
+ Collect all literals of entity.
665
+
666
+ Parameters
667
+ ----------
668
+ uri: str
669
+ Entities URI of the source
670
+ locale: LocaleCode [default:=EN_US]
671
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format <language_code>_<country>, e.g., en_US.
672
+ auth_key: Optional[str] [default:=None]
673
+ Use a different auth key than the one from the client
674
+ Returns
675
+ -------
676
+ literals: List[DataProperty]
677
+ List of data properties of an entity.
678
+
679
+ Raises
680
+ ------
681
+ WacomServiceException
682
+ If the graph service returns an error code
683
+ """
684
+ if auth_key is None:
685
+ auth_key, _ = await self.handle_token()
686
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/{uri}/literals"
687
+ headers: Dict[str, str] = {
688
+ USER_AGENT_HEADER_FLAG: self.user_agent,
689
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
690
+ }
691
+ async with AsyncServiceAPIClient.__async_session__() as session:
692
+ async with session.get(
693
+ url,
694
+ headers=headers,
695
+ params={
696
+ LOCALE_TAG: locale,
697
+ },
698
+ verify_ssl=self.verify_calls,
699
+ ) as response:
700
+ if response.ok:
701
+ literals: list = (await response.json(loads=orjson.loads)).get(DATA_PROPERTIES_TAG)
702
+ else:
703
+ raise await handle_error(f"Failed to pull literals. URI:={uri}.", response, headers=headers)
704
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
705
+ return DataProperty.create_from_list(literals)
706
+
707
+ async def create_relation(
708
+ self, source: str, relation: OntologyPropertyReference, target: str, auth_key: Optional[str] = None
709
+ ):
710
+ """
711
+ Creates a relation for an entity to a source entity.
712
+
713
+ Parameters
714
+ ----------
715
+ source: str
716
+ Entities URI of the source
717
+ relation: OntologyPropertyReference
718
+ ObjectProperty property
719
+ target: str
720
+ Entities URI of the target
721
+ auth_key: Optional[str] [default:=None]
722
+ Use a different auth key than the one from the client
723
+
724
+ Raises
725
+ ------
726
+ WacomServiceException
727
+ If the graph service returns an error code
728
+ """
729
+ if auth_key is None:
730
+ auth_key, _ = await self.handle_token()
731
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/{source}/relation"
732
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
733
+ params: dict = {RELATION_TAG: relation.iri, TARGET: target}
734
+ async with AsyncServiceAPIClient.__async_session__() as session:
735
+ async with session.post(url, params=params, headers=headers, verify_ssl=self.verify_calls) as response:
736
+ if not response.ok:
737
+ raise await handle_error(
738
+ f"Creation of relation failed. URI:={source}.", response, headers=headers, parameters=params
739
+ )
740
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
741
+
742
+ async def create_relations_bulk(
743
+ self, source: str, relations: Dict[OntologyPropertyReference, List[str]], auth_key: Optional[str] = None
744
+ ):
745
+ """
746
+ Creates all the relations for an entity to a source entity.
747
+
748
+ Parameters
749
+ ----------
750
+ source: str
751
+ Entities URI of the source
752
+
753
+ relations: Dict[OntologyPropertyReference, List[str]]
754
+ ObjectProperty property and targets mapping.
755
+
756
+ auth_key: Optional[str] = None
757
+ If the auth key is set the logged-in user (if any) will be ignored and the auth key will be used.
758
+
759
+ Raises
760
+ ------
761
+ WacomServiceException
762
+ If the graph service returns an error code
763
+ """
764
+ if auth_key is None:
765
+ auth_key, _ = await self.handle_token()
766
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/{source}/relations"
767
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
768
+ async with AsyncServiceAPIClient.__async_session__() as session:
769
+ for update_bulk in split_updates(relations):
770
+ async with session.post(
771
+ url, json=update_bulk, headers=headers, verify_ssl=self.verify_calls
772
+ ) as response:
773
+ if not response.ok:
774
+ raise await handle_error(
775
+ f"Creation of relation failed. URI:={source}.",
776
+ response,
777
+ headers=headers,
778
+ parameters=update_bulk,
779
+ )
780
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
781
+
782
+ async def remove_relation(
783
+ self, source: str, relation: OntologyPropertyReference, target: str, auth_key: Optional[str] = None
784
+ ):
785
+ """
786
+ Removes a relation.
787
+
788
+ Parameters
789
+ ----------
790
+ source: str
791
+ Entities uri of the source
792
+ relation: OntologyPropertyReference
793
+ ObjectProperty property
794
+ target: str
795
+ Entities uri of the target
796
+ auth_key: Optional[str] [default:=None]
797
+ Use a different auth key than the one from the client
798
+
799
+ Raises
800
+ ------
801
+ WacomServiceException
802
+ If the graph service returns an error code
803
+ """
804
+ if auth_key is None:
805
+ auth_key, _ = await self.handle_token()
806
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/{source}/relation"
807
+ params: Dict[str, str] = {RELATION_TAG: relation.iri, TARGET: target}
808
+ headers: Dict[str, str] = {
809
+ USER_AGENT_HEADER_FLAG: self.user_agent,
810
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
811
+ }
812
+ async with AsyncServiceAPIClient.__async_session__() as session:
813
+ async with session.delete(
814
+ url, params=params, headers=headers, timeout=DEFAULT_TIMEOUT, verify_ssl=self.verify_calls
815
+ ) as response:
816
+ if not response.ok:
817
+ raise await handle_error(
818
+ f"Removal of relation failed. URI:={source}.", response, headers=headers, parameters=params
819
+ )
820
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
821
+
822
+ async def activations(
823
+ self, uris: List[str], depth: int, auth_key: Optional[str] = None
824
+ ) -> Tuple[Dict[str, ThingObject], List[Tuple[str, OntologyPropertyReference, str]]]:
825
+ """
826
+ Spreading activation, retrieving the entities related to an entity.
827
+
828
+ Parameters
829
+ ----------
830
+ uris: List[str]
831
+ List of URIS for entity.
832
+ depth: int
833
+ Depth of activations
834
+ auth_key: Optional[str] [default:=None]
835
+ Use a different auth key than the one from the client
836
+
837
+ Returns
838
+ -------
839
+ entity_map: Dict[str, ThingObject]
840
+ Map with entity and its URI as key.
841
+ relations: List[Tuple[str, OntologyPropertyReference, str]]
842
+ List of relations with subject predicate, (Property), and subject
843
+
844
+ Raises
845
+ ------
846
+ WacomServiceException
847
+ If the graph service returns an error code, and activation failed.
848
+ """
849
+ if auth_key is None:
850
+ auth_key, _ = await self.handle_token()
851
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ACTIVATIONS_ENDPOINT}"
852
+ headers: Dict[str, str] = {
853
+ USER_AGENT_HEADER_FLAG: self.user_agent,
854
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
855
+ }
856
+ params: dict = {URIS_TAG: uris, ACTIVATION_TAG: depth}
857
+
858
+ async with AsyncServiceAPIClient.__async_session__() as session:
859
+ async with session.get(url, headers=headers, params=params, verify_ssl=self.verify_calls) as response:
860
+ if response.ok:
861
+ entities: Dict[str, Any] = await response.json(loads=orjson.loads)
862
+ things: Dict[str, ThingObject] = {
863
+ e[URI_TAG]: ThingObject.from_dict(e) for e in entities[ENTITIES_TAG]
864
+ }
865
+ relations: List[Tuple[str, OntologyPropertyReference, str]] = []
866
+ for r in entities[RELATIONS_TAG]:
867
+ relation: OntologyPropertyReference = OntologyPropertyReference.parse(r[PREDICATE])
868
+ relations.append((r[SUBJECT], relation, r[OBJECT]))
869
+ if r[SUBJECT] in things:
870
+ things[r[SUBJECT]].add_relation(ObjectProperty(relation, outgoing=[r[OBJECT]]))
871
+ else:
872
+ raise await handle_error(
873
+ f"Activation failed. URIS:={uris}.", response, headers=headers, parameters=params
874
+ )
875
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
876
+ return things, relations
877
+
878
+ async def listing(
879
+ self,
880
+ filter_type: OntologyClassReference,
881
+ page_id: Optional[str] = None,
882
+ limit: int = 30,
883
+ locale: Optional[LocaleCode] = None,
884
+ visibility: Optional[Visibility] = None,
885
+ is_owner: Optional[bool] = None,
886
+ estimate_count: bool = False,
887
+ auth_key: Optional[str] = None,
888
+ ) -> Tuple[List[ThingObject], int, str]:
889
+ """
890
+ List all entities visible to users.
891
+
892
+ Parameters
893
+ ----------
894
+ filter_type: OntologyClassReference
895
+ Filtering with entity
896
+ page_id: Optional[str]
897
+ Page id. Start from this page id
898
+ limit: int
899
+ Limit of the returned entities.
900
+ locale: Optional[LocaleCode] [default:=None]
901
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format '<language_code>_<country>', e.g., en_US.
902
+ visibility: Optional[Visibility] [default:=None]
903
+ Filter the entities based on its visibilities
904
+ is_owner: Optional[bool] [default:=None]
905
+ Filter the entities based on its owner
906
+ estimate_count: bool [default:=False]
907
+ Request an estimate of the entities in a tenant.
908
+ auth_key: Optional[str]
909
+ Auth key from user if not set, the client auth key will be used
910
+
911
+ Returns
912
+ -------
913
+ entities: List[ThingObject]
914
+ List of entities
915
+ estimated_total_number: int
916
+ Number of all entities
917
+ next_page_id: str
918
+ Identifier of the next page
919
+
920
+ Raises
921
+ ------
922
+ WacomServiceException
923
+ If the graph service returns an error code
924
+ """
925
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.LISTING_ENDPOINT}"
926
+ if auth_key is None:
927
+ auth_key, _ = await self.handle_token()
928
+ # Header with auth token
929
+ headers: Dict[str, str] = {
930
+ USER_AGENT_HEADER_FLAG: self.user_agent,
931
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
932
+ }
933
+ # Parameter with filtering and limit
934
+ parameters: Dict[str, str] = {
935
+ TYPE_TAG: filter_type.iri,
936
+ LIMIT_PARAMETER: str(limit),
937
+ ESTIMATE_COUNT: str(estimate_count),
938
+ }
939
+ if is_owner is not None:
940
+ parameters[IS_OWNER_PARAM] = str(is_owner)
941
+ if locale:
942
+ parameters[LOCALE_TAG] = locale
943
+ if visibility:
944
+ parameters[VISIBILITY_TAG] = str(visibility.value)
945
+ # If filtering is configured
946
+ if page_id is not None:
947
+ parameters[NEXT_PAGE_ID_TAG] = page_id
948
+ async with AsyncServiceAPIClient.__async_session__() as session:
949
+ # Send request
950
+ async with session.get(url, params=parameters, headers=headers, verify_ssl=self.verify_calls) as response:
951
+ # If response is successful
952
+ if response.ok:
953
+ entities_resp: Dict[str, Any] = await response.json(loads=orjson.loads)
954
+ next_page_id: str = entities_resp[NEXT_PAGE_ID_TAG]
955
+ estimated_total_number: int = entities_resp.get(TOTAL_COUNT, 0)
956
+ entities: List[ThingObject] = []
957
+ if LISTING in entities_resp:
958
+ for e in entities_resp[LISTING]:
959
+ thing: ThingObject = ThingObject.from_dict(e)
960
+ thing.status_flag = EntityStatus.SYNCED
961
+ entities.append(thing)
962
+ else:
963
+ raise await handle_error(
964
+ f"Failed to list the entities (since:= {page_id}, limit:={limit}). ", response
965
+ )
966
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
967
+ return entities, estimated_total_number, next_page_id
968
+
969
+ async def ontology_update(self, fix: bool = False, auth_key: Optional[str] = None):
970
+ """
971
+ Update the ontology.
972
+
973
+ **Remark:**
974
+ Works for users with role 'TenantAdmin'.
975
+
976
+ Parameters
977
+ ----------
978
+ fix: bool [default:=False]
979
+ Fix the ontology if tenant is in inconsistent state.
980
+ auth_key: Optional[str] [default:=None]
981
+ Auth key from user if not set, the client auth key will be used
982
+
983
+ Raises
984
+ ------
985
+ WacomServiceException
986
+ If the graph service returns an error code and commit failed.
987
+ """
988
+ if auth_key is None:
989
+ auth_key, _ = await self.handle_token()
990
+ url: str = (
991
+ f"{self.service_base_url}{AsyncWacomKnowledgeService.ONTOLOGY_UPDATE_ENDPOINT}" f'{"/fix" if fix else ""}'
992
+ )
993
+ # Header with auth token
994
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
995
+ async with AsyncServiceAPIClient.__async_session__() as session:
996
+ async with session.patch(
997
+ url, headers=headers, timeout=DEFAULT_TIMEOUT, verify_ssl=self.verify_calls
998
+ ) as response:
999
+ if not response.ok:
1000
+ raise await handle_error("Ontology update failed. ", response, headers=headers)
1001
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
1002
+
1003
+ async def search_all(
1004
+ self,
1005
+ search_term: str,
1006
+ language_code: LocaleCode,
1007
+ types: List[OntologyClassReference],
1008
+ limit: int = 30,
1009
+ next_page_id: str = None,
1010
+ auth_key: Optional[str] = None,
1011
+ ) -> Tuple[List[ThingObject], str]:
1012
+ """Search term in labels, literals and description.
1013
+
1014
+ Parameters
1015
+ ----------
1016
+ search_term: str
1017
+ Search term.
1018
+ language_code: LocaleCode
1019
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format '<language_code>_<country>', e.g., en_US.
1020
+ types: List[OntologyClassReference]
1021
+ Limits the types for search.
1022
+ limit: int (default:= 30)
1023
+ Size of the page for pagination.
1024
+ next_page_id: str [default:=None]
1025
+ ID of the next page within pagination.
1026
+ auth_key: Optional[str] [default:=None]
1027
+ Auth key from user if not set, the client auth key will be used
1028
+
1029
+ Returns
1030
+ -------
1031
+ results: List[ThingObject]
1032
+ List of things matching the search term
1033
+ next_page_id: str
1034
+ ID of the next page.
1035
+
1036
+ Raises
1037
+ ------
1038
+ WacomServiceException
1039
+ If the graph service returns an error code.
1040
+ """
1041
+ if auth_key is None:
1042
+ auth_key, _ = await self.handle_token()
1043
+ headers: Dict[str, str] = {
1044
+ USER_AGENT_HEADER_FLAG: self.user_agent,
1045
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
1046
+ }
1047
+ parameters: Dict[str, Any] = {
1048
+ SEARCH_TERM: search_term,
1049
+ LANGUAGE_PARAMETER: language_code,
1050
+ TYPES_PARAMETER: [ot.iri for ot in types],
1051
+ LIMIT: limit,
1052
+ }
1053
+ # Only add next page id if it is not None
1054
+ if next_page_id is not None:
1055
+ parameters[NEXT_PAGE_ID_TAG] = next_page_id
1056
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.SEARCH_TYPES_ENDPOINT}"
1057
+ async with AsyncServiceAPIClient.__async_session__() as session:
1058
+ async with session.get(
1059
+ url, headers=headers, params=parameters, timeout=DEFAULT_TIMEOUT, verify_ssl=self.verify_calls
1060
+ ) as response:
1061
+ if not response.ok:
1062
+ raise await handle_error(
1063
+ f"Search on labels {search_term} failed. ", response, headers=headers, parameters=parameters
1064
+ )
1065
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
1066
+ return await AsyncWacomKnowledgeService.__search_results__(await response.json(loads=orjson.loads))
1067
+
1068
+ async def search_labels(
1069
+ self,
1070
+ search_term: str,
1071
+ language_code: LocaleCode,
1072
+ exact_match: bool = False,
1073
+ limit: int = 30,
1074
+ next_page_id: str = None,
1075
+ auth_key: Optional[str] = None,
1076
+ ) -> Tuple[List[ThingObject], str]:
1077
+ """Search for matches in labels.
1078
+
1079
+ Parameters
1080
+ ----------
1081
+ search_term: str
1082
+ Search term.
1083
+ language_code: LocaleCode
1084
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format '<language_code>_<country>', e.g., en_US.
1085
+ exact_match: bool [default:=False]
1086
+ Exact match of the search term.
1087
+ limit: int (default:= 30)
1088
+ Size of the page for pagination.
1089
+ next_page_id: str [default:=None]
1090
+ ID of the next page within pagination.
1091
+ auth_key: Optional[str] [default:=None]
1092
+ Auth key from user if not set, the client auth key will be used
1093
+
1094
+ Returns
1095
+ -------
1096
+ results: List[ThingObject]
1097
+ List of things matching the search term
1098
+ next_page_id: str
1099
+ ID of the next page.
1100
+
1101
+ Raises
1102
+ ------
1103
+ WacomServiceException
1104
+ If the graph service returns an error code.
1105
+ """
1106
+ if auth_key is None:
1107
+ auth_key, _ = await self.handle_token()
1108
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.SEARCH_LABELS_ENDPOINT}"
1109
+ headers: Dict[str, str] = {
1110
+ USER_AGENT_HEADER_FLAG: self.user_agent,
1111
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
1112
+ }
1113
+ parameters: Dict[str, Any] = {
1114
+ SEARCH_TERM: search_term,
1115
+ LOCALE_TAG: language_code,
1116
+ EXACT_MATCH: str(exact_match),
1117
+ LIMIT: str(limit),
1118
+ }
1119
+ # Only add next page id if it is not None
1120
+ if next_page_id is not None:
1121
+ parameters[NEXT_PAGE_ID_TAG] = next_page_id
1122
+
1123
+ async with AsyncServiceAPIClient.__async_session__() as session:
1124
+ async with session.get(
1125
+ url, headers=headers, params=parameters, timeout=DEFAULT_TIMEOUT, verify_ssl=self.verify_calls
1126
+ ) as response:
1127
+ if not response.ok:
1128
+ raise await handle_error(
1129
+ f"Search on labels {search_term} failed. ", response, headers=headers, parameters=parameters
1130
+ )
1131
+ entities, next_page_id = await AsyncWacomKnowledgeService.__search_results__(
1132
+ await response.json(loads=orjson.loads)
1133
+ )
1134
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
1135
+ return entities, next_page_id
1136
+
1137
+ async def search_literal(
1138
+ self,
1139
+ search_term: str,
1140
+ literal: OntologyPropertyReference,
1141
+ pattern: SearchPattern = SearchPattern.REGEX,
1142
+ language_code: LocaleCode = EN_US,
1143
+ limit: int = 30,
1144
+ next_page_id: str = None,
1145
+ auth_key: Optional[str] = None,
1146
+ ) -> Tuple[List[ThingObject], str]:
1147
+ """
1148
+ Search for matches in literals.
1149
+
1150
+ Parameters
1151
+ ----------
1152
+ search_term: str
1153
+ Search term.
1154
+ literal: OntologyPropertyReference
1155
+ Literal used for the search
1156
+ pattern: SearchPattern (default:= SearchPattern.REGEX)
1157
+ Search pattern. The chosen search pattern must fit the type of the entity.
1158
+ language_code: LocaleCode
1159
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format '<language_code>_<country>', e.g., en_US.
1160
+ limit: int (default:= 30)
1161
+ Size of the page for pagination.
1162
+ next_page_id: str [default:=None]
1163
+ ID of the next page within pagination.
1164
+ auth_key: Optional[str] [default:=None]
1165
+ Auth key from user if not set, the client auth key will be used
1166
+ Returns
1167
+ -------
1168
+ results: List[ThingObject]
1169
+ List of things matching the search term
1170
+ next_page_id: str
1171
+ ID of the next page.
1172
+
1173
+ Raises
1174
+ ------
1175
+ WacomServiceException
1176
+ If the graph service returns an error code.
1177
+ """
1178
+ if auth_key is None:
1179
+ auth_key, _ = await self.handle_token()
1180
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.SEARCH_LITERALS_ENDPOINT}"
1181
+ parameters: Dict[str, Any] = {
1182
+ VALUE: search_term,
1183
+ LITERAL_PARAMETER: literal.iri,
1184
+ LANGUAGE_PARAMETER: language_code,
1185
+ LIMIT: str(limit),
1186
+ SEARCH_PATTERN_PARAMETER: pattern.value,
1187
+ }
1188
+ # Only add next page id if it is not None
1189
+ if next_page_id is not None:
1190
+ parameters[NEXT_PAGE_ID_TAG] = next_page_id
1191
+ headers: Dict[str, str] = {AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
1192
+ async with AsyncServiceAPIClient.__async_session__() as session:
1193
+ async with session.get(
1194
+ url, headers=headers, params=parameters, timeout=DEFAULT_TIMEOUT, verify_ssl=self.verify_calls
1195
+ ) as response:
1196
+ if not response.ok:
1197
+ raise await handle_error(
1198
+ f"Search on literals {search_term} failed. ", response, headers=headers, parameters=parameters
1199
+ )
1200
+ entities, n_p = await AsyncWacomKnowledgeService.__search_results__(
1201
+ await response.json(loads=orjson.loads)
1202
+ )
1203
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
1204
+ return entities, n_p
1205
+
1206
+ async def search_relation(
1207
+ self,
1208
+ relation: OntologyPropertyReference,
1209
+ language_code: LocaleCode,
1210
+ subject_uri: str = None,
1211
+ object_uri: str = None,
1212
+ limit: int = 30,
1213
+ next_page_id: str = None,
1214
+ auth_key: Optional[str] = None,
1215
+ ) -> Tuple[List[ThingObject], str]:
1216
+ """
1217
+ Search for matches in literals.
1218
+
1219
+ Parameters
1220
+ ----------
1221
+ relation: OntologyPropertyReference
1222
+ Search term.
1223
+ language_code: LocaleCode
1224
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format '<language_code>_<country>', e.g., en_US.
1225
+ subject_uri: str [default:=None]
1226
+ URI of the subject
1227
+ object_uri: str [default:=None]
1228
+ URI of the object
1229
+ limit: int (default:= 30)
1230
+ Size of the page for pagination.
1231
+ next_page_id: str [default:=None]
1232
+ ID of the next page within pagination.
1233
+ auth_key: Optional[str] [default:=None]
1234
+ Auth key from user if not set, the client auth key will be used
1235
+
1236
+ Returns
1237
+ -------
1238
+ results: List[ThingObject]
1239
+ List of things matching the search term
1240
+ next_page_id: str
1241
+ ID of the next page.
1242
+
1243
+ Raises
1244
+ ------
1245
+ WacomServiceException
1246
+ If the graph service returns an error code.
1247
+ """
1248
+ if auth_key is None:
1249
+ auth_key, _ = await self.handle_token()
1250
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.SEARCH_RELATION_ENDPOINT}"
1251
+ parameters: Dict[str, Any] = {RELATION_URI: relation.iri, LANGUAGE_PARAMETER: language_code, LIMIT: str(limit)}
1252
+ if subject_uri is not None and object_uri is not None:
1253
+ raise WacomServiceException("Only one parameter is allowed: either subject_uri or object_uri!")
1254
+ if subject_uri is None and object_uri is None:
1255
+ raise WacomServiceException("At least one parameters is must be defined: either subject_uri or object_uri!")
1256
+ if subject_uri is not None:
1257
+ parameters[SUBJECT_URI] = subject_uri
1258
+ if object_uri is not None:
1259
+ parameters[OBJECT_URI] = object_uri
1260
+ # Only add next page id if it is not None
1261
+ if next_page_id is not None:
1262
+ parameters[NEXT_PAGE_ID_TAG] = next_page_id
1263
+ headers: dict = {AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
1264
+ async with AsyncServiceAPIClient.__async_session__() as session:
1265
+ async with session.get(url, headers=headers, params=parameters, verify_ssl=self.verify_calls) as response:
1266
+ if not response.ok:
1267
+ raise await handle_error(
1268
+ f"Search on: subject:={subject_uri}, relation {relation.iri}, "
1269
+ f"object:= {object_uri} failed. ",
1270
+ response,
1271
+ headers=headers,
1272
+ parameters=parameters,
1273
+ )
1274
+ entities, n_p = await AsyncWacomKnowledgeService.__search_results__(
1275
+ await response.json(loads=orjson.loads)
1276
+ )
1277
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
1278
+ return entities, n_p
1279
+
1280
+ async def search_description(
1281
+ self,
1282
+ search_term: str,
1283
+ language_code: LocaleCode,
1284
+ limit: int = 30,
1285
+ auth_key: Optional[str] = None,
1286
+ next_page_id: str = None,
1287
+ ) -> Tuple[List[ThingObject], str]:
1288
+ """Search for matches in description.
1289
+
1290
+ Parameters
1291
+ ----------
1292
+ search_term: str
1293
+ Search term.
1294
+ language_code: LocaleCode
1295
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format '<language_code>_<country>', e.g., en_US.
1296
+ limit: int (default:= 30)
1297
+ Size of the page for pagination.
1298
+ auth_key: Optional[str] [default:=None]
1299
+ Auth key from user if not set, the client auth key will be used
1300
+ next_page_id: str [default:=None]
1301
+ ID of the next page within pagination.
1302
+
1303
+ Returns
1304
+ -------
1305
+ results: List[ThingObject]
1306
+ List of things matching the search term
1307
+ next_page_id: str
1308
+ ID of the next page.
1309
+
1310
+ Raises
1311
+ ------
1312
+ WacomServiceException
1313
+ If the graph service returns an error code.
1314
+ """
1315
+ if auth_key is None:
1316
+ auth_key, _ = await self.handle_token()
1317
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.SEARCH_DESCRIPTION_ENDPOINT}"
1318
+ parameters: Dict[str, Any] = {SEARCH_TERM: search_term, LOCALE_TAG: language_code, LIMIT: str(limit)}
1319
+ # Only add next page id if it is not None
1320
+ if next_page_id is not None:
1321
+ parameters[NEXT_PAGE_ID_TAG] = next_page_id
1322
+ headers: dict = {AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
1323
+ async with AsyncServiceAPIClient.__async_session__() as session:
1324
+ async with session.get(url, headers=headers, params=parameters, verify_ssl=self.verify_calls) as response:
1325
+ if not response.ok:
1326
+ raise await handle_error(
1327
+ f"Search on descriptions {search_term} failed. ",
1328
+ response,
1329
+ headers=headers,
1330
+ parameters=parameters,
1331
+ )
1332
+ entities, n_p = await AsyncWacomKnowledgeService.__search_results__(
1333
+ await response.json(loads=orjson.loads)
1334
+ )
1335
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
1336
+ return entities, n_p
1337
+
1338
+ @staticmethod
1339
+ async def __search_results__(response: Dict[str, Any]) -> Tuple[List[ThingObject], str]:
1340
+ results: List[ThingObject] = []
1341
+ for elem in response[RESULT_TAG]:
1342
+ results.append(ThingObject.from_dict(elem))
1343
+ return results, response[NEXT_PAGE_ID_TAG]
1344
+
1345
+ async def link_personal_entities(
1346
+ self, text: str, language_code: LocaleCode = EN_US, auth_key: Optional[str] = None
1347
+ ) -> List[KnowledgeGraphEntity]:
1348
+ """
1349
+ Performs Named Entities Linking on a text. It only finds entities which are accessible by the user identified by
1350
+ the auth key.
1351
+
1352
+ Parameters
1353
+ ----------
1354
+ auth_key: str
1355
+ Auth key identifying a user within the Wacom personal knowledge service.
1356
+ text: str
1357
+ Text where the entities shall be tagged in.
1358
+ language_code: LocaleCode
1359
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format '<language_code>_<country>', e.g., 'en_US'.
1360
+
1361
+ Returns
1362
+ -------
1363
+ entities: List[KnowledgeGraphEntity]
1364
+ List of knowledge graph entities.
1365
+
1366
+ Raises
1367
+ ------
1368
+ WacomServiceException
1369
+ If the Named Entities Linking service returns an error code.
1370
+ """
1371
+ if auth_key is None:
1372
+ auth_key, _ = await self.handle_token()
1373
+ named_entities: List[KnowledgeGraphEntity] = []
1374
+ url: str = f"{self.service_base_url}{self.NAMED_ENTITY_LINKING_ENDPOINT}"
1375
+ headers: Dict[str, str] = {
1376
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
1377
+ USER_AGENT_HEADER_FLAG: self.user_agent,
1378
+ CONTENT_TYPE_HEADER_FLAG: APPLICATION_JSON_HEADER,
1379
+ }
1380
+ payload: Dict[str, str] = {LOCALE_TAG: language_code, TEXT_TAG: text}
1381
+
1382
+ # Create a session and mount the retry adapter
1383
+ async with AsyncServiceAPIClient.__async_session__() as session:
1384
+ async with session.post(url, headers=headers, json=payload, verify_ssl=self.verify_calls) as response:
1385
+ if response.ok:
1386
+ results: dict = await response.json(loads=orjson.loads)
1387
+ for e in results:
1388
+ entity_types: List[str] = []
1389
+ # --------------------------- Entities content ---------------------------------------------------
1390
+ source: Optional[EntitySource] = None
1391
+ if "uri" in e:
1392
+ source = EntitySource(e["uri"], KnowledgeSource.WACOM_KNOWLEDGE)
1393
+ # --------------------------- Ontology types ---------------------------------------------------
1394
+ if "type" in e:
1395
+ entity_types.append(e["type"])
1396
+ # ----------------------------------------------------------------------------------------------
1397
+ start: int = e["startPosition"]
1398
+ end: int = e["endPosition"]
1399
+ ne: KnowledgeGraphEntity = KnowledgeGraphEntity(
1400
+ ref_text=text[start : end + 1],
1401
+ start_idx=start,
1402
+ end_idx=end,
1403
+ label=e["value"],
1404
+ confidence=0.0,
1405
+ source=source,
1406
+ content_link="",
1407
+ ontology_types=entity_types,
1408
+ entity_type=EntityType.PERSONAL_ENTITY,
1409
+ )
1410
+ ne.relevant_type = OntologyClassReference.parse(e["type"])
1411
+ named_entities.append(ne)
1412
+ else:
1413
+ raise await handle_error(
1414
+ f"Named entity linking for text:={text}@{language_code} failed. ",
1415
+ response,
1416
+ headers=headers,
1417
+ parameters=payload,
1418
+ )
1419
+ await asyncio.sleep(0.250 if self.use_graceful_shutdown else 0)
1420
+ return named_entities