personal_knowledge_library 3.2.1__py3-none-any.whl → 3.3.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.

knowledge/__init__.py CHANGED
@@ -17,7 +17,7 @@ __license__ = "Wacom"
17
17
  __maintainer__ = ["Markus Weber"]
18
18
  __email__ = "markus.weber@wacom.com"
19
19
  __status__ = "beta"
20
- __version__ = "3.2.1"
20
+ __version__ = "3.3.0"
21
21
 
22
22
  import loguru
23
23
 
@@ -1494,7 +1494,7 @@ class Ontology:
1494
1494
  return f"<Ontology> : classes:= {self.classes}"
1495
1495
 
1496
1496
 
1497
- class ThingObject(abc.ABC):
1497
+ class ThingObject(object):
1498
1498
  """
1499
1499
  ThingObject
1500
1500
  -----------
knowledge/nel/base.py CHANGED
@@ -162,6 +162,10 @@ class KnowledgeGraphEntity(NamedEntity):
162
162
  List of ontology types (class names)
163
163
  entity_type: EntityType
164
164
  Type of the entity.
165
+ tokens: Optional[List[str]] (default:=None)
166
+ List of tokens used to identify the entity.
167
+ token_indexes: Optional[List[int]] (default:=None)
168
+ List of token indexes used to identify the entity.
165
169
  """
166
170
 
167
171
  def __init__(
@@ -175,6 +179,9 @@ class KnowledgeGraphEntity(NamedEntity):
175
179
  content_link: str,
176
180
  ontology_types: List[str],
177
181
  entity_type: EntityType = EntityType.PUBLIC_ENTITY,
182
+ tokens: Optional[List[str]] = None,
183
+ token_indexes: Optional[List[int]] = None,
184
+
178
185
  ):
179
186
  super().__init__(ref_text, start_idx, end_idx, entity_type)
180
187
  self.__source: EntitySource = source
@@ -185,6 +192,8 @@ class KnowledgeGraphEntity(NamedEntity):
185
192
  self.__thumbnail: Optional[str] = None
186
193
  self.__ontology_types: List[str] = ontology_types
187
194
  self.__relevant_type: OntologyClassReference = THING_CLASS
195
+ self.__tokens: Optional[List[str]] = tokens
196
+ self.__token_indexes: Optional[List[int]] = token_indexes
188
197
 
189
198
  @property
190
199
  def entity_source(self) -> EntitySource:
@@ -242,6 +251,16 @@ class KnowledgeGraphEntity(NamedEntity):
242
251
  def relevant_type(self, value: OntologyClassReference):
243
252
  self.__relevant_type = value
244
253
 
254
+ @property
255
+ def tokens(self) -> Optional[List[str]]:
256
+ """List of tokens used to identify the entity."""
257
+ return self.__tokens
258
+
259
+ @property
260
+ def token_indexes(self) -> Optional[List[int]]:
261
+ """List of token indexes used to identify the entity."""
262
+ return self.__token_indexes
263
+
245
264
  def __repr__(self):
246
265
  return f"{self.ref_text} [{self.start_idx}-{self.end_idx}] -> {self.entity_source} [{self.entity_type}]"
247
266
 
knowledge/nel/engine.py CHANGED
@@ -116,6 +116,8 @@ class WacomEntityLinkingEngine(PersonalEntityLinkingProcessor):
116
116
  content_link="",
117
117
  ontology_types=entity_types,
118
118
  entity_type=EntityType.PERSONAL_ENTITY,
119
+ tokens=e.get("tokens"),
120
+ token_indexes=e.get("tokenIndexes"),
119
121
  )
120
122
  ne.relevant_type = OntologyClassReference.parse(e["type"])
121
123
  named_entities.append(ne)
@@ -1,6 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # Copyright © 2024-present Wacom. All rights reserved.
3
3
  import asyncio
4
+ import gzip
5
+ import json
4
6
  import logging
5
7
  import os
6
8
  import urllib
@@ -35,6 +37,7 @@ from knowledge.base.ontology import (
35
37
  OntologyClassReference,
36
38
  ObjectProperty,
37
39
  )
40
+ from knowledge.base.response import JobStatus, ErrorLogResponse, NewEntityUrisResponse
38
41
  from knowledge.nel.base import KnowledgeGraphEntity, EntityType, KnowledgeSource, EntitySource
39
42
  from knowledge.services import AUTHORIZATION_HEADER_FLAG, IS_OWNER_PARAM, IndexType
40
43
  from knowledge.services import (
@@ -114,6 +117,8 @@ class AsyncWacomKnowledgeService(AsyncServiceAPIClient):
114
117
  SEARCH_DESCRIPTION_ENDPOINT: str = "semantic-search/description"
115
118
  SEARCH_RELATION_ENDPOINT: str = "semantic-search/relation"
116
119
  ONTOLOGY_UPDATE_ENDPOINT: str = "ontology-update"
120
+ IMPORT_ENTITIES_ENDPOINT: str = "import"
121
+ IMPORT_ERROR_LOG_ENDPOINT: str = "import/errorlog"
117
122
 
118
123
  def __init__(
119
124
  self,
@@ -180,6 +185,61 @@ class AsyncWacomKnowledgeService(AsyncServiceAPIClient):
180
185
  thing: ThingObject = ThingObject.from_dict(e)
181
186
  return thing
182
187
 
188
+ async def entities(self, uris: List[str], locale: Optional[LocaleCode] = None, auth_key: Optional[str] = None) \
189
+ -> List[ThingObject]:
190
+ """
191
+ Retrieve entities information from personal knowledge, using the URI as identifier.
192
+
193
+ **Remark:** Object properties (relations) must be requested separately.
194
+
195
+ Parameters
196
+ ----------
197
+ uris: List[str]
198
+ List of URIs of the entities
199
+ locale: LocaleCode
200
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format <language_code>_<country>, e.g., en_US.
201
+ auth_key: Optional[str]
202
+ Use a different auth key than the one from the client
203
+
204
+ Returns
205
+ -------
206
+ things: List[ThingObject]
207
+ Entities with is type URI, description, an image/icon, and tags (labels).
208
+
209
+ Raises
210
+ ------
211
+ WacomServiceException
212
+ If the graph service returns an error code or the entity is not found in the knowledge graph
213
+ """
214
+ if auth_key is None:
215
+ auth_key, _ = await self.handle_token()
216
+ url: str = f"{self.service_base_url}{AsyncWacomKnowledgeService.ENTITY_ENDPOINT}/"
217
+ headers: Dict[str, str] = {
218
+ USER_AGENT_HEADER_FLAG: self.user_agent,
219
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
220
+ }
221
+ things: List[ThingObject] = []
222
+ params: Dict[str, Any] = {
223
+ URIS_TAG: uris
224
+ }
225
+ if locale:
226
+ params[LOCALE_TAG] = locale
227
+ async with AsyncServiceAPIClient.__async_session__() as session:
228
+ async with session.get(url, headers=headers, params=params, verify_ssl=self.verify_calls) as response:
229
+ if response.ok:
230
+ entities: List[Dict[str, Any]] = await response.json()
231
+ for e in entities:
232
+ thing: ThingObject = ThingObject.from_dict(e)
233
+ things.append(thing)
234
+ else:
235
+ raise await handle_error(
236
+ f"Retrieving of entities content failed. List of URIs: {uris}.", response,
237
+ headers=headers
238
+ )
239
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
240
+ # Create ThingObject
241
+ return things
242
+
183
243
  async def set_entity_image_local(self, entity_uri: str, path: Path, auth_key: Optional[str] = None) -> str:
184
244
  """Setting the image of the entity.
185
245
  The image is stored locally.
@@ -533,6 +593,222 @@ class AsyncWacomKnowledgeService(AsyncServiceAPIClient):
533
593
  await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
534
594
  return uri
535
595
 
596
+ async def import_entities(
597
+ self,
598
+ entities: List[ThingObject],
599
+ auth_key: Optional[str] = None,
600
+ timeout: int = DEFAULT_TIMEOUT
601
+ ) -> str:
602
+ """Import entities to the graph.
603
+
604
+ Parameters
605
+ ----------
606
+ entities: List[ThingObject]
607
+ List of entities to import.
608
+ auth_key: Optional[str] = None
609
+ If the auth key is set the logged-in user (if any) will be ignored and the auth key will be used.
610
+ timeout: int
611
+ Timeout for the request (default: 60 seconds)
612
+
613
+ Returns
614
+ -------
615
+ job_id: str
616
+ ID of the job
617
+
618
+ Raises
619
+ ------
620
+ WacomServiceException
621
+ If the graph service returns an error code.
622
+ """
623
+ if auth_key is None:
624
+ auth_key, _ = await self.handle_token()
625
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
626
+ ndjson_lines: List[str] = []
627
+ for obj in entities:
628
+ data_dict = obj.__import_format_dict__()
629
+ ndjson_lines.append(json.dumps(data_dict)) # Convert each dict to a JSON string
630
+
631
+ ndjson_content = "\n".join(ndjson_lines) # Join JSON strings with newline
632
+
633
+ # Compress the NDJSON string to a gzip byte array
634
+ compressed_data: bytes = gzip.compress(ndjson_content.encode("utf-8"))
635
+ url: str = f"{self.service_base_url}{self.IMPORT_ENTITIES_ENDPOINT}"
636
+ data: aiohttp.FormData = aiohttp.FormData()
637
+ data.add_field("file", compressed_data, filename="import.njson.gz", content_type="application/x-gzip")
638
+ async with AsyncServiceAPIClient.__async_session__() as session:
639
+ async with session.post(
640
+ url, headers=headers, data=data, timeout=timeout, verify_ssl=self.verify_calls
641
+ ) as response:
642
+ if response.ok:
643
+ structure: Dict[str, Any] = await response.json(loads=orjson.loads)
644
+ return structure["jobId"]
645
+ raise await handle_error("Import endpoint returns an error.", response)
646
+
647
+ async def import_entities_from_file(
648
+ self,
649
+ file_path: Path,
650
+ auth_key: Optional[str] = None,
651
+ timeout: int = DEFAULT_TIMEOUT
652
+ ) -> str:
653
+ """Import entities from a file to the graph.
654
+
655
+ Parameters
656
+ ----------
657
+ file_path: Path
658
+ Path to the file containing entities in NDJSON format.
659
+ auth_key: Optional[str] = None
660
+ If the auth key is set the logged-in user (if any) will be ignored and the auth key will be used.
661
+ timeout: int
662
+ Timeout for the request (default: 60 seconds)
663
+
664
+ Returns
665
+ -------
666
+ job_id: str
667
+ ID of the job
668
+
669
+ Raises
670
+ ------
671
+ WacomServiceException
672
+ If the graph service returns an error code.
673
+ """
674
+ if not file_path.exists():
675
+ raise FileNotFoundError(f"The file {file_path} does not exist.")
676
+ if auth_key is None:
677
+ auth_key, _ = await self.handle_token()
678
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
679
+ with file_path.open("rb") as file:
680
+ # Compress the NDJSON string to a gzip byte array
681
+ compressed_data: bytes = file.read()
682
+ data: aiohttp.FormData = aiohttp.FormData()
683
+ data.add_field("file", compressed_data, filename="import.njson.gz",
684
+ content_type="application/x-gzip")
685
+ url: str = f"{self.service_base_url}{self.IMPORT_ENTITIES_ENDPOINT}"
686
+ async with AsyncServiceAPIClient.__async_session__() as session:
687
+ async with session.post(
688
+ url, headers=headers, data=data, timeout=timeout, verify_ssl=self.verify_calls
689
+ ) as response:
690
+ if response.ok:
691
+ structure: Dict[str, Any] = await response.json(loads=orjson.loads)
692
+ return structure["jobId"]
693
+ raise await handle_error("Import endpoint returns an error.", response)
694
+
695
+ async def job_status(
696
+ self,
697
+ job_id: str,
698
+ auth_key: Optional[str] = None,
699
+ timeout: int = DEFAULT_TIMEOUT
700
+ ) -> JobStatus:
701
+ """
702
+ Retrieve the status of the job.
703
+
704
+ Parameters
705
+ ----------
706
+ job_id: str
707
+ ID of the job
708
+ auth_key: Optional[str] = None
709
+ If the auth key is set the logged-in user (if any) will be ignored and the auth key will be used.
710
+ timeout: int
711
+ Timeout for the request (default: 60 seconds)
712
+
713
+ Returns
714
+ -------
715
+ job_status: JobStatus
716
+ Status of the job
717
+ """
718
+ if auth_key is None:
719
+ auth_key, _ = await self.handle_token()
720
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
721
+ url: str = f"{self.service_base_url}{self.IMPORT_ENTITIES_ENDPOINT}/{job_id}"
722
+ async with AsyncServiceAPIClient.__async_session__() as session:
723
+ async with session.get(
724
+ url, headers=headers, timeout=timeout, verify_ssl=self.verify_calls
725
+ ) as response:
726
+ if response.ok:
727
+ structure: Dict[str, Any] = await response.json(loads=orjson.loads)
728
+ return JobStatus.from_dict(structure)
729
+ raise await handle_error(f"Retrieving job status for {job_id} failed.", response, headers=headers)
730
+
731
+ async def import_error_log(
732
+ self,
733
+ job_id: str,
734
+ auth_key: Optional[str] = None,
735
+ next_page_id: Optional[str] = None,
736
+ timeout: int = DEFAULT_TIMEOUT
737
+ ) -> ErrorLogResponse:
738
+ """
739
+ Retrieve the error log of the job.
740
+
741
+ Parameters
742
+ ----------
743
+ job_id: str
744
+ ID of the job
745
+ next_page_id: Optional[str] = None
746
+ ID of the next page within pagination.
747
+ auth_key: Optional[str] = None
748
+ If the auth key is set the logged-in user (if any) will be ignored and the auth key will be used.
749
+ timeout: int
750
+ Timeout for the request (default: 60 seconds)
751
+
752
+ Returns
753
+ -------
754
+ error: ErrorLogResponse
755
+ Error log of the job
756
+ """
757
+ if auth_key is None:
758
+ auth_key, _ = await self.handle_token()
759
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
760
+ url: str = f"{self.service_base_url}{self.IMPORT_ERROR_LOG_ENDPOINT}/{job_id}"
761
+ params: Dict[str, str] = {NEXT_PAGE_ID_TAG: next_page_id} if next_page_id else {}
762
+ async with AsyncServiceAPIClient.__async_session__() as session:
763
+ async with session.get(
764
+ url, headers=headers, params=params, timeout=timeout, verify_ssl=self.verify_calls
765
+ ) as response:
766
+ if response.ok:
767
+ structure: Dict[str, Any] = await response.json(loads=orjson.loads)
768
+ return ErrorLogResponse.from_dict(structure)
769
+ raise await handle_error(f"Retrieving job status for {job_id} failed.", response)
770
+
771
+ async def import_new_uris(
772
+ self,
773
+ job_id: str,
774
+ auth_key: Optional[str] = None,
775
+ next_page_id: Optional[str] = None,
776
+ timeout: int = DEFAULT_TIMEOUT
777
+ ) -> NewEntityUrisResponse:
778
+ """
779
+ Retrieve the new entity uris from the job.
780
+
781
+ Parameters
782
+ ----------
783
+ job_id: str
784
+ ID of the job
785
+ next_page_id: Optional[str] = None
786
+ ID of the next page within pagination.
787
+ auth_key: Optional[str] = None
788
+ If the auth key is set the logged-in user (if any) will be ignored and the auth key will be used.
789
+ timeout: int
790
+ Timeout for the request (default: 60 seconds)
791
+
792
+ Returns
793
+ -------
794
+ response: NewEntityUrisResponse
795
+ New entity uris of the job.
796
+ """
797
+ if auth_key is None:
798
+ auth_key, _ = await self.handle_token()
799
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
800
+ url: str = f"{self.service_base_url}{self.IMPORT_ENTITIES_ENDPOINT}/{job_id}/new-entities"
801
+ params: Dict[str, str] = {NEXT_PAGE_ID_TAG: next_page_id} if next_page_id else {}
802
+ async with AsyncServiceAPIClient.__async_session__() as session:
803
+ async with session.get(
804
+ url, headers=headers, params=params, timeout=timeout, verify_ssl=self.verify_calls
805
+ ) as response:
806
+ if response.ok:
807
+ structure: Dict[str, Any] = await response.json(loads=orjson.loads)
808
+ return NewEntityUrisResponse.from_dict(structure)
809
+ raise await handle_error(f"Retrieving job status for {job_id} failed.", response)
810
+
811
+
536
812
  async def update_entity(self, entity: ThingObject, auth_key: Optional[str] = None):
537
813
  """
538
814
  Updates entity in graph.
@@ -1521,6 +1797,8 @@ class AsyncWacomKnowledgeService(AsyncServiceAPIClient):
1521
1797
  content_link="",
1522
1798
  ontology_types=entity_types,
1523
1799
  entity_type=EntityType.PERSONAL_ENTITY,
1800
+ tokens=e.get("token"),
1801
+ token_indexes=e.get("tokenIndexes"),
1524
1802
  )
1525
1803
  ne.relevant_type = OntologyClassReference.parse(e["type"])
1526
1804
  named_entities.append(ne)
@@ -300,7 +300,8 @@ class WacomServiceAPIClient(RESTAPIClient):
300
300
  raise WacomServiceException(f"Unknown session id:= {self.__current_session_id}. Please login first.")
301
301
  return session
302
302
 
303
- def request_user_token(self, tenant_api_key: str, external_id: str) -> Tuple[str, str, datetime]:
303
+ def request_user_token(self, tenant_api_key: str, external_id: str, timeout: int = DEFAULT_TIMEOUT) \
304
+ -> Tuple[str, str, datetime]:
304
305
  """
305
306
  Login as user by using the tenant key and its external user id.
306
307
 
@@ -310,6 +311,8 @@ class WacomServiceAPIClient(RESTAPIClient):
310
311
  Tenant API key
311
312
  external_id: str
312
313
  External id.
314
+ timeout: int (Default:= DEFAULT_TIMEOUT)
315
+ Timeout for the request in seconds.
313
316
 
314
317
  Returns
315
318
  -------
@@ -333,7 +336,8 @@ class WacomServiceAPIClient(RESTAPIClient):
333
336
  }
334
337
  payload: dict = {EXTERNAL_USER_ID: external_id}
335
338
  response: Response = requests.post(
336
- url, headers=headers, json=payload, timeout=DEFAULT_TIMEOUT, verify=self.verify_calls
339
+ url, headers=headers, json=payload, timeout=timeout, verify=self.verify_calls,
340
+ allow_redirects=True
337
341
  )
338
342
  if response.ok:
339
343
  try:
@@ -18,7 +18,6 @@ from knowledge.base.entity import (
18
18
  DATA_PROPERTIES_TAG,
19
19
  TYPE_TAG,
20
20
  LABELS_TAG,
21
- IS_MAIN_TAG,
22
21
  RELATIONS_TAG,
23
22
  LOCALE_TAG,
24
23
  EntityStatus,
@@ -226,18 +225,74 @@ class WacomKnowledgeService(WacomServiceAPIClient):
226
225
  response: Response = session.get(url, headers=headers, timeout=timeout, verify=self.verify_calls)
227
226
  if response.ok:
228
227
  e: Dict[str, Any] = response.json()
229
- pref_label: List[Label] = []
230
- aliases: List[Label] = []
231
- # Extract labels and alias
232
- for label in e[LABELS_TAG]:
233
- if label[IS_MAIN_TAG]: # Labels
234
- pref_label.append(Label.create_from_dict(label))
235
- else: # Alias
236
- aliases.append(Label.create_from_dict(label))
237
228
  thing: ThingObject = ThingObject.from_dict(e)
238
229
  return thing
239
230
  raise handle_error(f"Retrieving of entity content failed. URI:={uri}.", response)
240
231
 
232
+ def entities(self,
233
+ uris: List[str],
234
+ locale: Optional[LocaleCode] = None,
235
+ auth_key: Optional[str] = None,
236
+ timeout: int = DEFAULT_TIMEOUT,
237
+ max_retries: int = DEFAULT_MAX_RETRIES,
238
+ backoff_factor: float = DEFAULT_BACKOFF_FACTOR,
239
+ ) -> List[ThingObject]:
240
+ """
241
+ Retrieve entity information from personal knowledge, using the URI as identifier.
242
+
243
+ **Remark:** Object properties (relations) must be requested separately.
244
+
245
+ Parameters
246
+ ----------
247
+ uris: List[str]
248
+ List of URIs of entities
249
+ locale: Optional[LocaleCode]
250
+ ISO-3166 Country Codes and ISO-639 Language Codes in the format <language_code>_<country>, e.g., en_US.
251
+ auth_key: Optional[str]
252
+ If the auth key is set the logged-in user (if any) will be ignored and the auth key will be used.
253
+ timeout: int
254
+ Timeout for the request (default: 60 seconds)
255
+ max_retries: int
256
+ Maximum number of retries (default: 3)
257
+ backoff_factor: float
258
+ A backoff factor to apply between attempts after the second try (most errors are resolved immediately by a
259
+ second try without a delay) (default: 0.1)
260
+
261
+ Returns
262
+ -------
263
+ things: List[ThingObject]
264
+ Entities with is type URI, description, an image/icon, and tags (labels).
265
+
266
+ Raises
267
+ ------
268
+ WacomServiceException
269
+ If the graph service returns an error code or the entity is not found in the knowledge graph
270
+ """
271
+ if auth_key is None:
272
+ auth_key, _ = self.handle_token()
273
+ url: str = f"{self.service_base_url}{WacomKnowledgeService.ENTITY_ENDPOINT}/"
274
+ headers: Dict[str, str] = {
275
+ USER_AGENT_HEADER_FLAG: self.user_agent,
276
+ AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}",
277
+ }
278
+ params: Dict[str, Any] = {URIS_TAG: uris}
279
+ if locale is not None:
280
+ params[LOCALE_TAG] = locale
281
+ mount_point: str = "https://" if self.service_url.startswith("https") else "http://"
282
+ with requests.Session() as session:
283
+ retries: Retry = Retry(total=max_retries, backoff_factor=backoff_factor, status_forcelist=STATUS_FORCE_LIST)
284
+ session.mount(mount_point, HTTPAdapter(max_retries=retries))
285
+ response: Response = session.get(url, params=params, headers=headers, timeout=timeout,
286
+ verify=self.verify_calls)
287
+ if response.ok:
288
+ things: List[ThingObject] = []
289
+ entities: List[Dict[str, Any]] = response.json()
290
+ for e in entities:
291
+ thing: ThingObject = ThingObject.from_dict(e)
292
+ things.append(thing)
293
+ return things
294
+ raise handle_error(f"Retrieving of entity content failed. URIs:={uris}.", response)
295
+
241
296
  def delete_entities(
242
297
  self,
243
298
  uris: List[str],
@@ -520,6 +575,8 @@ class WacomKnowledgeService(WacomServiceAPIClient):
520
575
  if entity.image is not None and entity.image.startswith("file:"):
521
576
  p = urlparse(entity.image)
522
577
  self.set_entity_image_local(uri, Path(p.path), auth_key=auth_key)
578
+ elif entity.image is not None and entity.image.startswith("/"):
579
+ self.set_entity_image_local(uri, Path(entity.image), auth_key=auth_key)
523
580
  elif entity.image is not None and entity.image != "":
524
581
  self.set_entity_image_url(uri, entity.image, auth_key=auth_key)
525
582
  except WacomServiceException as _:
@@ -1824,6 +1881,63 @@ class WacomKnowledgeService(WacomServiceAPIClient):
1824
1881
  return response.json()["jobId"]
1825
1882
  raise handle_error("Import endpoint returns an error.", response)
1826
1883
 
1884
+ def import_entities_from_file(
1885
+ self,
1886
+ file_path: Path,
1887
+ auth_key: Optional[str] = None,
1888
+ timeout: int = DEFAULT_TIMEOUT,
1889
+ max_retries: int = DEFAULT_MAX_RETRIES,
1890
+ backoff_factor: float = DEFAULT_BACKOFF_FACTOR,
1891
+ ) -> str:
1892
+ """Import entities from a file to the graph.
1893
+
1894
+ Parameters
1895
+ ----------
1896
+ file_path: Path
1897
+ Path to the file containing entities in NDJSON format.
1898
+ auth_key: Optional[str] = None
1899
+ If the auth key is set the logged-in user (if any) will be ignored and the auth key will be used.
1900
+ timeout: int
1901
+ Timeout for the request (default: 60 seconds)
1902
+ max_retries: int
1903
+ Maximum number of retries
1904
+ backoff_factor: float
1905
+ A backoff factor to apply between attempts after the second try (most errors are resolved immediately by a
1906
+ second try without a delay)
1907
+
1908
+ Returns
1909
+ -------
1910
+ job_id: str
1911
+ ID of the job
1912
+
1913
+ Raises
1914
+ ------
1915
+ WacomServiceException
1916
+ If the graph service returns an error code.
1917
+ """
1918
+ if not file_path.exists():
1919
+ raise FileNotFoundError(f"The file {file_path} does not exist.")
1920
+ if auth_key is None:
1921
+ auth_key, _ = self.handle_token()
1922
+ headers: dict = {USER_AGENT_HEADER_FLAG: self.user_agent, AUTHORIZATION_HEADER_FLAG: f"Bearer {auth_key}"}
1923
+ with file_path.open("rb") as file:
1924
+ # Compress the NDJSON string to a gzip byte array
1925
+ compressed_data: bytes = file.read()
1926
+ files: List[Tuple[str, Tuple[str, bytes, str]]] = [
1927
+ ("file", ("import.njson.gz", compressed_data, "application/x-gzip"))
1928
+ ]
1929
+ url: str = f"{self.service_base_url}{self.IMPORT_ENTITIES_ENDPOINT}"
1930
+ mount_point: str = "https://" if self.service_url.startswith("https") else "http://"
1931
+ with requests.Session() as session:
1932
+ retries: Retry = Retry(total=max_retries, backoff_factor=backoff_factor, status_forcelist=STATUS_FORCE_LIST)
1933
+ session.mount(mount_point, HTTPAdapter(max_retries=retries))
1934
+ response: Response = session.post(
1935
+ url, headers=headers, files=files, timeout=timeout, verify=self.verify_calls
1936
+ )
1937
+ if response.ok:
1938
+ return response.json()["jobId"]
1939
+ raise handle_error("Import endpoint returns an error.", response)
1940
+
1827
1941
  def job_status(
1828
1942
  self,
1829
1943
  job_id: str,
@@ -2017,7 +2131,7 @@ class WacomKnowledgeService(WacomServiceAPIClient):
2017
2131
 
2018
2132
  def rebuild_nel_index(
2019
2133
  self,
2020
- nel_index: Literal["western", "japanese"],
2134
+ nel_index: Literal["Western", "Japanese"],
2021
2135
  prune: bool = False,
2022
2136
  auth_key: Optional[str] = None,
2023
2137
  timeout: int = DEFAULT_TIMEOUT,
@@ -263,6 +263,7 @@ class SemanticSearchClient(WacomServiceAPIClient):
263
263
  locale: LocaleCode,
264
264
  concept_type: Optional[str] = None,
265
265
  auth_key: Optional[str] = None,
266
+ timeout: float = DEFAULT_TIMEOUT,
266
267
  max_retries: int = 3,
267
268
  backoff_factor: float = 0.1,
268
269
  ) -> int:
@@ -275,6 +276,8 @@ class SemanticSearchClient(WacomServiceAPIClient):
275
276
  ISO-3166 Country Codes and ISO-639 Language Codes in the format '<language_code>_<country>', e.g., en_US.
276
277
  concept_type: Optional[str] (Default:= None)
277
278
  Concept type.
279
+ timeout: int (Default:= DEFAULT_TIMEOUT)
280
+ Timeout for the request in seconds.
278
281
  max_retries: int
279
282
  Maximum number of retries
280
283
  backoff_factor: float
@@ -306,7 +309,7 @@ class SemanticSearchClient(WacomServiceAPIClient):
306
309
  with requests.Session() as session:
307
310
  retries: Retry = Retry(total=max_retries, backoff_factor=backoff_factor, status_forcelist=STATUS_FORCE_LIST)
308
311
  session.mount(mount_point, HTTPAdapter(max_retries=retries))
309
- response = session.get(url, params=params, headers=headers)
312
+ response = session.get(url, params=params, headers=headers, timeout=timeout)
310
313
  if response.ok:
311
314
  return response.json().get("count", 0)
312
315
  raise handle_error("Counting labels failed.", response, headers=headers, parameters={"locale": locale})
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: personal_knowledge_library
3
- Version: 3.2.1
3
+ Version: 3.3.0
4
4
  Summary: Library to access Wacom's Personal Knowledge graph.
5
- License: Apache-2.0
5
+ License-Expression: Apache-2.0
6
+ License-File: LICENSE
6
7
  Keywords: semantic-knowledge,knowledge-graph
7
8
  Author: Markus Weber
8
9
  Author-email: markus.weber@wacom.com
@@ -1,15 +1,15 @@
1
- knowledge/__init__.py,sha256=jiyP4XnToZFHIp1qoT0J6LOaY2T8aX3nPXWJn1ogDD0,2680
1
+ knowledge/__init__.py,sha256=GRwtZsuLOUMj3OElzD5iDRR2A0q7EdiQ-Uu34AgELtw,2680
2
2
  knowledge/base/__init__.py,sha256=rgAcl0azO6eoVk1gK9z0UFNlTHj3AN88k6LG-GdATFk,1079
3
3
  knowledge/base/access.py,sha256=BSh-6QbeHOCu55XTxA-p3DwEyRzgtN8TSxtcn6UvmZo,4411
4
4
  knowledge/base/entity.py,sha256=b-Ana_H_WI2-AT_n-V_HzUL6W9Ri16DcZFS3q4ziI94,8445
5
5
  knowledge/base/language.py,sha256=L4VAKRuFRQxE0BfzxkQUeChyhvLKoZM7ooqxm0dACO4,1478
6
- knowledge/base/ontology.py,sha256=UMgJS2QVBskOiTFYbjNUxx1tY3jJZV-STF3UYf4ofKM,95435
6
+ knowledge/base/ontology.py,sha256=kPZAPBYuT-2LY33lldP66hXArqKO8WwZj8SMJhgTEjU,95434
7
7
  knowledge/base/response.py,sha256=dqdskdcjKnM142nX_8sDVs8v7xTQjiVY1MqJCI-Dfkc,10918
8
8
  knowledge/base/search.py,sha256=J1PSVpTU2JKF9xSZLZZvAbJfFy1HiMPJRzPjHhR7IQM,14722
9
9
  knowledge/base/tenant.py,sha256=f2Z_LjUcjIt5J2_Rse9aQyTzJ0sSyVvCzlm8PP3PqY8,6084
10
10
  knowledge/nel/__init__.py,sha256=eTT88LV_KQ-Ku-a4RnTv_TUCNogl357ui4KT1pEKMuQ,330
11
- knowledge/nel/base.py,sha256=bsUj9PwoZoJWW33RfoYmQHbFhArKH1kMOsDgU3yj0T8,15032
12
- knowledge/nel/engine.py,sha256=qvQkfsdeYPWJ_5m8mmGFw1tvd699vOQ3IKoBIwALRWk,5347
11
+ knowledge/nel/base.py,sha256=EloMv3utdYVaor7dFp0R79IxXd7oXq4kv7AwTHzbQhw,15775
12
+ knowledge/nel/engine.py,sha256=2fCGkrS3PVPVEfO6AmYyWDPUOpwIeLxDRmNQT3-eMvo,5456
13
13
  knowledge/ontomapping/__init__.py,sha256=u_t6i5a6pYYnf43Q_S6sG7NRLeGuKmahIHW6TFGHqOk,18848
14
14
  knowledge/ontomapping/manager.py,sha256=pXBwRGSTcS731df5vewNv6oMgP18HzvnjZqiUgyFIhI,13361
15
15
  knowledge/public/__init__.py,sha256=FrW5sqJGVcIfg5IVpt_g7qlWiIYNGA370jkE5mJDhoo,812
@@ -21,16 +21,16 @@ knowledge/public/wikidata.py,sha256=LwMP2kq2mH5Oi9JhPvG8lN9pZMDlQvxgaZgQKQO3kaM,
21
21
  knowledge/services/__init__.py,sha256=Pg4Pd9cCRiQ4-v3E0WCqldOhJFcMmkZrQHA7BY1Z1K8,3604
22
22
  knowledge/services/asyncio/__init__.py,sha256=dq9yGTxg_hoQb8E1kEtY4AeB8zrdJfw3-XlPoYn2j5U,225
23
23
  knowledge/services/asyncio/base.py,sha256=im6J1CWOp1N1tt6WdJ1uU0R-VFpvdYc6lZu8TyPrU5A,16222
24
- knowledge/services/asyncio/graph.py,sha256=BRU8kLgKt7KNuyzlgT2PEVwa7ig-S-sR0AWMTYkp5NE,62401
24
+ knowledge/services/asyncio/graph.py,sha256=9ZNNJvWKKYxyJBdTOxpx1Y3Iu1NzI6bdDDbKmgld4CU,74143
25
25
  knowledge/services/asyncio/group.py,sha256=UKbk8vnPqqAkuc5gAd6g3IVOKmUJNqG2lreZM0O-hRg,18356
26
26
  knowledge/services/asyncio/search.py,sha256=fm1j6fJDQx831B82bqmZ3czYrYH3qtPCYlisSF26M5g,16217
27
27
  knowledge/services/asyncio/users.py,sha256=NJ7xagjr4gF6yKJXdwa0bz0eaYlQDee5RCEpfQJEHWk,10274
28
- knowledge/services/base.py,sha256=gXBIkbk7wXGrm_hZGfLvft7-E8fcSMjl-HIsLxfRZ6c,17849
29
- knowledge/services/graph.py,sha256=XhbB2-3CUkQ9-NdVoW9pcQfnItaX6H07WVTsK3ynZ5I,86888
28
+ knowledge/services/base.py,sha256=PdHfObShUBpbAS71LBhe5jOA13Y1JXbUkhRi9op8wis,18018
29
+ knowledge/services/graph.py,sha256=hMGFomueC7lOr7-PjVZWeQn2S-D8FsU-0Ah7lCiAJH4,92095
30
30
  knowledge/services/group.py,sha256=b8dBBrHXqgwpJ7BtUqqweoR_WQH5MZ0aLh0fBBTyHUI,30698
31
31
  knowledge/services/helper.py,sha256=NYhRkYXtiFgovsXaQr6rBVN2mjBwOT7cSSuLTyjvzKA,4990
32
32
  knowledge/services/ontology.py,sha256=1QewHZoNTNrQe9cAgLMvWRPQ2YYn4IltscEOV9Dy2qM,51800
33
- knowledge/services/search.py,sha256=uOyBmPfzE1GG6EA2Uj64SD0Uda1EH6Nh8ZCJ0I0mpXs,19149
33
+ knowledge/services/search.py,sha256=9I4ZdCFnVSCCmYHeQralrw47VwKnMqA08UpAq_BMxQs,19305
34
34
  knowledge/services/session.py,sha256=y8uTETRMDOBbC30UAlrrtWTN7Zgs2klqHsoMjLO2tq0,14145
35
35
  knowledge/services/tenant.py,sha256=rQsUe8qNFTt_WZG4XBTg8QSLoyavuCWLWk3QhU1fBG8,11728
36
36
  knowledge/services/users.py,sha256=nOwyZhqERTNFPTN21c_q7-6XaEMb3SnHfnXr4Kf22qk,16594
@@ -40,7 +40,7 @@ knowledge/utils/graph.py,sha256=kuPZEGhexVN9McoT8JK2ViIrfXTh-nFPv_8XPfG0wlA,1231
40
40
  knowledge/utils/import_format.py,sha256=fjuWpd91eAs7MbqSpLrFgdRvFaHK0nzV1L83ByjTmV0,5277
41
41
  knowledge/utils/wikidata.py,sha256=vRH-4AMR-3xywyvmDqbjI4KSw4tF4TEYqUGCJmUMqK8,5512
42
42
  knowledge/utils/wikipedia.py,sha256=rBuFqYVM58JCj5ISLxuhYVVl2gOIlPLWx7_CghyQgvE,5052
43
- personal_knowledge_library-3.2.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
44
- personal_knowledge_library-3.2.1.dist-info/METADATA,sha256=SXl4lbRy0z86q5_Bk_44t3H3JivU_riN7Peexu2feEc,57087
45
- personal_knowledge_library-3.2.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
46
- personal_knowledge_library-3.2.1.dist-info/RECORD,,
43
+ personal_knowledge_library-3.3.0.dist-info/METADATA,sha256=xzv1A74fPWL7oB1DVg9tSVqnDyZ8fBxkmsVp_mzt4Vk,57120
44
+ personal_knowledge_library-3.3.0.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
45
+ personal_knowledge_library-3.3.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
46
+ personal_knowledge_library-3.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 2.2.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any