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,533 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright © 2021-present Wacom. All rights reserved.
3
+ import sys
4
+ from abc import ABC
5
+ from datetime import datetime
6
+ from typing import Any, Tuple, Dict, Optional, Union
7
+
8
+ import requests
9
+ from requests import Response
10
+
11
+ from knowledge import __version__, logger
12
+ from knowledge.services import DEFAULT_TIMEOUT
13
+ from knowledge.services import (
14
+ USER_AGENT_HEADER_FLAG,
15
+ TENANT_API_KEY,
16
+ CONTENT_TYPE_HEADER_FLAG,
17
+ REFRESH_TOKEN_TAG,
18
+ EXPIRATION_DATE_TAG,
19
+ ACCESS_TOKEN_TAG,
20
+ APPLICATION_JSON_HEADER,
21
+ EXTERNAL_USER_ID,
22
+ )
23
+ from knowledge.services.session import TokenManager, RefreshableSession, TimedSession, PermanentSession
24
+
25
+
26
+ class WacomServiceException(Exception):
27
+ """Exception thrown if Wacom service fails.
28
+
29
+ Parameters
30
+ ----------
31
+ message: str
32
+ Error message
33
+ payload: Optional[Dict[str, Any]] (Default:= None)
34
+ Payload
35
+ params: Optional[Dict[str, Any]] (Default:= None)
36
+ Parameters
37
+ method: Optional[str] (Default:= None)
38
+ Method
39
+ url: Optional[str] (Default:= None)
40
+ URL
41
+ service_response: Optional[str] (Default:= None)
42
+ Service response
43
+ status_code: int (Default:= 500)
44
+ Status code
45
+
46
+ Attributes
47
+ ----------
48
+ headers: Optional[Dict[str, Any]]
49
+ Headers of the exception
50
+ method: Optional[str]
51
+ Method of the exception
52
+ params: Optional[Dict[str, Any]]
53
+ Parameters of the exception
54
+ payload: Optional[Dict[str, Any]]
55
+ Payload of the exception
56
+ url: Optional[str]
57
+ URL of the exception
58
+ message: str
59
+ Message of the exception
60
+ status_code: int
61
+ Status code of the exception
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ message: str,
67
+ headers: Optional[Dict[str, Any]] = None,
68
+ payload: Optional[Dict[str, Any]] = None,
69
+ params: Optional[Dict[str, Any]] = None,
70
+ method: Optional[str] = None,
71
+ url: Optional[str] = None,
72
+ service_response: Optional[str] = None,
73
+ status_code: int = 500,
74
+ ):
75
+ super().__init__(message)
76
+ self.__status_code: int = status_code
77
+ self.__service_response: Optional[str] = service_response
78
+ self.__message: str = message
79
+ self.__headers: Optional[Dict[str, Any]] = headers
80
+ self.__payload: Optional[Dict[str, Any]] = payload
81
+ self.__params: Optional[Dict[str, Any]] = params
82
+ self.__method: Optional[str] = method
83
+ self.__url: Optional[str] = url
84
+
85
+ @property
86
+ def headers(self) -> Optional[Dict[str, Any]]:
87
+ """Headers of the exception."""
88
+ return self.__headers
89
+
90
+ @property
91
+ def method(self) -> Optional[str]:
92
+ """Method of the exception."""
93
+ return self.__method
94
+
95
+ @property
96
+ def params(self) -> Optional[Dict[str, Any]]:
97
+ """Parameters of the exception."""
98
+ return self.__params
99
+
100
+ @property
101
+ def payload(self) -> Optional[Dict[str, Any]]:
102
+ """Payload of the exception."""
103
+ return self.__payload
104
+
105
+ @property
106
+ def url(self) -> Optional[str]:
107
+ """URL of the exception."""
108
+ return self.__url
109
+
110
+ @property
111
+ def message(self) -> str:
112
+ """Message of the exception."""
113
+ return self.__message
114
+
115
+ @property
116
+ def service_response(self) -> Optional[Response]:
117
+ """Service response."""
118
+ return self.__service_response
119
+
120
+ @property
121
+ def status_code(self) -> int:
122
+ """Status code of the exception."""
123
+ return self.__status_code
124
+
125
+
126
+ def format_exception(exception: WacomServiceException) -> str:
127
+ """
128
+ Formats the exception.
129
+
130
+ Parameters
131
+ ----------
132
+ exception: WacomServiceException
133
+ Exception
134
+
135
+ Returns
136
+ -------
137
+ formatted_exception: str
138
+ Formatted exception
139
+ """
140
+ return (
141
+ f"WacomServiceException: {exception.message}\n"
142
+ "--------------------------------------------------\n"
143
+ f"URL:= {exception.url}\n,"
144
+ f"method:= {exception.method}\n,"
145
+ f"parameters:= {exception.params}\n,"
146
+ f"payload:= {exception.payload}\n,"
147
+ f"headers:= {exception.headers}\n,"
148
+ f"status code=: {exception.status_code}\n,"
149
+ f"service response:= {exception.service_response}"
150
+ )
151
+
152
+
153
+ def handle_error(
154
+ message: str,
155
+ response: Response,
156
+ parameters: Optional[Dict[str, Any]] = None,
157
+ payload: Optional[Dict[str, Any]] = None,
158
+ headers: Optional[Dict[str, str]] = None,
159
+ ) -> WacomServiceException:
160
+ """
161
+ Handles an error response.
162
+
163
+ Parameters
164
+ ----------
165
+ message: str
166
+ Error message
167
+ response: aiohttp.ClientResponse
168
+ Response
169
+ parameters: Optional[Dict[str, Any]] (Default:= None)
170
+ Parameters
171
+ payload: Optional[Dict[str, Any]] (Default:= None)
172
+ Payload
173
+ headers: Optional[Dict[str, str]] (Default:= None)
174
+ Headers
175
+
176
+ Returns
177
+ -------
178
+ WacomServiceException
179
+ Returns the generated exception.
180
+ """
181
+ return WacomServiceException(
182
+ message,
183
+ url=response.url,
184
+ method=response.request.method,
185
+ params=parameters,
186
+ payload=payload,
187
+ headers=headers,
188
+ status_code=response.status_code,
189
+ service_response=response.text,
190
+ )
191
+
192
+
193
+ class RESTAPIClient(ABC):
194
+ """
195
+ Abstract REST API client
196
+ ------------------------
197
+ REST API client handling the service url.
198
+
199
+ Arguments
200
+ ---------
201
+ service_url: str
202
+ Service URL for service
203
+ verify_calls: bool (default:= False)
204
+ Flag if the service calls should be verified
205
+ """
206
+
207
+ def __init__(self, service_url: str, verify_calls: bool = False):
208
+ self.__service_url: str = service_url.rstrip("/")
209
+ self.__verify_calls: bool = verify_calls
210
+
211
+ @property
212
+ def service_url(self) -> str:
213
+ """Service URL."""
214
+ return self.__service_url
215
+
216
+ @property
217
+ def verify_calls(self):
218
+ """Certificate verification activated."""
219
+ return self.__verify_calls
220
+
221
+ @verify_calls.setter
222
+ def verify_calls(self, value: bool):
223
+ self.__verify_calls = value
224
+
225
+
226
+ class WacomServiceAPIClient(RESTAPIClient):
227
+ """
228
+ Wacom Service API Client
229
+ ------------------------
230
+ Abstract class for Wacom service APIs.
231
+
232
+ Parameters
233
+ ----------
234
+ application_name: str
235
+ Name of the application using the service
236
+ service_url: str
237
+ URL of the service
238
+ service_endpoint: str
239
+ Base endpoint
240
+ auth_service_endpoint: str (Default:= 'graph/v1')
241
+ Authentication service endpoint
242
+ verify_calls: bool (Default:= True)
243
+ Flag if API calls should be verified.
244
+ """
245
+
246
+ USER_ENDPOINT: str = "user"
247
+ USER_LOGIN_ENDPOINT: str = f"{USER_ENDPOINT}/login"
248
+ USER_REFRESH_ENDPOINT: str = f"{USER_ENDPOINT}/refresh"
249
+ SERVICE_URL: str = "https://private-knowledge.wacom.com"
250
+ """Production service URL"""
251
+ STAGING_SERVICE_URL: str = "https://stage-private-knowledge.wacom.com"
252
+ """Staging service URL"""
253
+
254
+ def __init__(
255
+ self,
256
+ application_name: str,
257
+ service_url: str,
258
+ service_endpoint: str,
259
+ auth_service_endpoint: str = "graph/v1",
260
+ verify_calls: bool = True,
261
+ ):
262
+ self.__application_name: str = application_name
263
+ self.__service_endpoint: str = service_endpoint
264
+ self.__auth_service_endpoint: str = auth_service_endpoint
265
+ self.__token_manager: TokenManager = TokenManager()
266
+ self.__current_session_id: Optional[str] = None
267
+ super().__init__(service_url, verify_calls)
268
+
269
+ @property
270
+ def token_manager(self) -> TokenManager:
271
+ """Token manager."""
272
+ return self.__token_manager
273
+
274
+ @property
275
+ def auth_endpoint(self) -> str:
276
+ """Authentication endpoint."""
277
+ # This is in graph service REST API
278
+ return f"{self.service_url}/{self.__auth_service_endpoint}/{self.USER_LOGIN_ENDPOINT}"
279
+
280
+ @property
281
+ def current_session(self) -> Union[RefreshableSession, TimedSession, PermanentSession, None]:
282
+ """Current session.
283
+
284
+ Returns
285
+ -------
286
+ session: Union[TimedSession, RefreshableSession, PermanentSession]
287
+ Current session
288
+
289
+ Raises
290
+ ------
291
+ WacomServiceException
292
+ Exception if no session is available.
293
+ """
294
+ if self.__current_session_id is None:
295
+ raise WacomServiceException("No session set. Please login first.")
296
+ session: Union[RefreshableSession, TimedSession, PermanentSession, None] = self.__token_manager.get_session(
297
+ self.__current_session_id
298
+ )
299
+ if session is None:
300
+ raise WacomServiceException(f"Unknown session id:= {self.__current_session_id}. Please login first.")
301
+ return session
302
+
303
+ def request_user_token(self, tenant_api_key: str, external_id: str) -> Tuple[str, str, datetime]:
304
+ """
305
+ Login as user by using the tenant key and its external user id.
306
+
307
+ Parameters
308
+ ----------
309
+ tenant_api_key: str
310
+ Tenant API key
311
+ external_id: str
312
+ External id.
313
+
314
+ Returns
315
+ -------
316
+ auth_key: str
317
+ Authentication key for identifying the user for the service calls.
318
+ refresh_key: str
319
+ Refresh token
320
+ expiration_time: datatime
321
+ Expiration time
322
+
323
+ Raises
324
+ ------
325
+ WacomServiceException
326
+ Exception if service returns HTTP error code.
327
+ """
328
+ url: str = f"{self.auth_endpoint}"
329
+ headers: dict = {
330
+ USER_AGENT_HEADER_FLAG: self.user_agent,
331
+ TENANT_API_KEY: tenant_api_key,
332
+ CONTENT_TYPE_HEADER_FLAG: APPLICATION_JSON_HEADER,
333
+ }
334
+ payload: dict = {EXTERNAL_USER_ID: external_id}
335
+ response: Response = requests.post(
336
+ url, headers=headers, json=payload, timeout=DEFAULT_TIMEOUT, verify=self.verify_calls
337
+ )
338
+ if response.ok:
339
+ try:
340
+ response_token: Dict[str, str] = response.json()
341
+ timestamp_str_truncated: str = ""
342
+ try:
343
+ if sys.version_info <= (3, 10):
344
+ timestamp_str_truncated = response_token[EXPIRATION_DATE_TAG][:19] + "+00:00"
345
+ else:
346
+ timestamp_str_truncated = response_token[EXPIRATION_DATE_TAG]
347
+ date_object: datetime = datetime.fromisoformat(timestamp_str_truncated)
348
+ except (TypeError, ValueError) as _:
349
+ date_object: datetime = datetime.now()
350
+ logger.warning(
351
+ f"Parsing of expiration date failed. {response_token[EXPIRATION_DATE_TAG]} "
352
+ f"-> {timestamp_str_truncated}"
353
+ )
354
+ return response_token["accessToken"], response_token["refreshToken"], date_object
355
+ except Exception as e:
356
+ raise handle_error(f"Parsing of response failed. {e}", response) from e
357
+ raise handle_error("User login failed.", response)
358
+
359
+ def login(self, tenant_api_key: str, external_user_id: str) -> PermanentSession:
360
+ """Login as user by using the tenant id and its external user id.
361
+ Parameters
362
+ ----------
363
+ tenant_api_key: str
364
+ Tenant id
365
+ external_user_id: str
366
+ External user id
367
+ Returns
368
+ -------
369
+ session: PermanentSession
370
+ Session. The session is stored in the token manager and the client is using the session id for further
371
+ calls.
372
+ """
373
+ auth_key, refresh_token, _ = self.request_user_token(tenant_api_key, external_user_id)
374
+ session: PermanentSession = self.__token_manager.add_session(
375
+ auth_token=auth_key,
376
+ refresh_token=refresh_token,
377
+ tenant_api_key=tenant_api_key,
378
+ external_user_id=external_user_id,
379
+ )
380
+ self.__current_session_id = session.id
381
+ return session
382
+
383
+ def logout(self):
384
+ """Logout user."""
385
+ self.__current_session_id = None
386
+
387
+ def register_token(
388
+ self, auth_key: str, refresh_token: Optional[str] = None
389
+ ) -> Union[RefreshableSession, TimedSession]:
390
+ """Register token.
391
+ Parameters
392
+ ----------
393
+ auth_key: str
394
+ Authentication key for identifying the user for the service calls.
395
+ refresh_token: str
396
+ Refresh token
397
+
398
+ Returns
399
+ -------
400
+ session: Union[RefreshableSession, TimedSession]
401
+ Session. The session is stored in the token manager and the client is using the session id for further
402
+ calls.
403
+ """
404
+ session = self.__token_manager.add_session(auth_token=auth_key, refresh_token=refresh_token)
405
+ self.__current_session_id = session.id
406
+ if isinstance(session, (RefreshableSession, TimedSession)):
407
+ return session
408
+ raise WacomServiceException(f"Wrong session type:= {type(session)}.")
409
+
410
+ def use_session(self, session_id: str):
411
+ """Use session.
412
+ Parameters
413
+ ----------
414
+ session_id: str
415
+ Session id
416
+ """
417
+ if self.__token_manager.has_session(session_id):
418
+ self.__current_session_id = session_id
419
+ else:
420
+ raise WacomServiceException(f"Unknown session id:= {session_id}.")
421
+
422
+ def refresh_token(self, refresh_token: str) -> Tuple[str, str, datetime]:
423
+ """
424
+ Refreshing a token.
425
+
426
+ Parameters
427
+ ----------
428
+ refresh_token: str
429
+ Refresh token
430
+
431
+ Returns
432
+ -------
433
+ auth_key: str
434
+ Authentication key for identifying the user for the service calls.
435
+ refresh_key: str
436
+ Refresh token
437
+ expiration_time: str
438
+ Expiration time
439
+
440
+ Raises
441
+ ------
442
+ WacomServiceException
443
+ Exception if service returns HTTP error code.
444
+ """
445
+ url: str = f"{self.service_base_url}{WacomServiceAPIClient.USER_REFRESH_ENDPOINT}/"
446
+ headers: Dict[str, str] = {
447
+ USER_AGENT_HEADER_FLAG: self.user_agent,
448
+ CONTENT_TYPE_HEADER_FLAG: APPLICATION_JSON_HEADER,
449
+ }
450
+ payload: Dict[str, str] = {REFRESH_TOKEN_TAG: refresh_token}
451
+ response: Response = requests.post(
452
+ url, headers=headers, json=payload, timeout=DEFAULT_TIMEOUT, verify=self.verify_calls
453
+ )
454
+ if response.ok:
455
+ response_token: Dict[str, str] = response.json()
456
+ try:
457
+ date_object: datetime = datetime.fromisoformat(response_token[EXPIRATION_DATE_TAG])
458
+ except (TypeError, ValueError) as _:
459
+ date_object: datetime = datetime.now()
460
+ logger.warning(f"Parsing of expiration date failed. {response_token[EXPIRATION_DATE_TAG]}")
461
+ return response_token[ACCESS_TOKEN_TAG], response_token[REFRESH_TOKEN_TAG], date_object
462
+ raise handle_error("Refreshing token failed.", response)
463
+
464
+ def handle_token(self, force_refresh: bool = False, force_refresh_timeout: float = 120) -> Tuple[str, str]:
465
+ """
466
+ Handles the token and refreshes it if needed.
467
+
468
+ Parameters
469
+ ----------
470
+ force_refresh: bool
471
+ Force refresh token
472
+ force_refresh_timeout: int
473
+ Force refresh timeout
474
+ Returns
475
+ -------
476
+ user_token: str
477
+ The user token
478
+ refresh_token: str
479
+ The refresh token
480
+ """
481
+ # The session is not set
482
+ if self.current_session is None:
483
+ raise WacomServiceException("Authentication key is not set. Please login first.")
484
+
485
+ # The token expired and is not refreshable
486
+ if not self.current_session.refreshable and self.current_session.expired:
487
+ raise WacomServiceException("Authentication key is expired and cannot be refreshed. Please login again.")
488
+
489
+ # The token is not refreshable and the force refresh flag is set
490
+ if not self.current_session.refreshable and force_refresh:
491
+ raise WacomServiceException("Authentication key is not refreshable. Please login again.")
492
+
493
+ # Refresh token if needed
494
+ if self.current_session.refreshable and (
495
+ self.current_session.expires_in < force_refresh_timeout or force_refresh
496
+ ):
497
+ try:
498
+ auth_key, refresh_token, _ = self.refresh_token(self.current_session.refresh_token)
499
+ except WacomServiceException as e:
500
+ if isinstance(self.current_session, PermanentSession):
501
+ permanent_session: PermanentSession = self.current_session
502
+ auth_key, refresh_token, _ = self.request_user_token(
503
+ permanent_session.tenant_api_key, permanent_session.external_user_id
504
+ )
505
+ else:
506
+ logger.error(f"Error refreshing token: {e}")
507
+ raise e
508
+ self.current_session.update_session(auth_key, refresh_token)
509
+ return auth_key, refresh_token
510
+ return self.current_session.auth_token, self.current_session.refresh_token
511
+
512
+ @property
513
+ def user_agent(self) -> str:
514
+ """User agent."""
515
+ return (
516
+ f"Personal Knowledge Library({self.application_name})/{__version__}"
517
+ f"(+https://github.com/Wacom-Developer/personal-knowledge-library)"
518
+ )
519
+
520
+ @property
521
+ def service_endpoint(self):
522
+ """Service endpoint."""
523
+ return "" if len(self.__service_endpoint) == 0 else f"{self.__service_endpoint}/"
524
+
525
+ @property
526
+ def service_base_url(self):
527
+ """Service endpoint."""
528
+ return f"{self.service_url}/{self.service_endpoint}"
529
+
530
+ @property
531
+ def application_name(self):
532
+ """Application name."""
533
+ return self.__application_name