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,444 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright © 2024-present Wacom. All rights reserved.
3
+ """
4
+ This module contains the session management.
5
+ There are three types of sessions:
6
+ - **TimedSession**: The session is only valid until the token expires.
7
+ There is no refresh token, thus the session cannot be refreshed.
8
+ - **RefreshableSession**: The session is valid until the token expires.
9
+ There is a refresh token, thus the session can be refreshed.
10
+ - **PermanentSession**: The session is valid until the token expires.
11
+ There is a refresh token, thus the session can be refreshed.
12
+ Moreover, the session is bound to then _tenant api key_ and the _external user id_, which can be used to
13
+ re-login when the refresh token expires.
14
+ """
15
+ import hashlib
16
+ import logging
17
+ import threading
18
+ from abc import ABC, abstractmethod
19
+ from datetime import datetime, timezone
20
+ from typing import Union, Optional, Dict, Any
21
+
22
+ import jwt
23
+
24
+ logger: logging.Logger = logging.getLogger(__name__)
25
+
26
+
27
+ class Session(ABC):
28
+ """
29
+ Session
30
+ -------
31
+ Abstract session class.
32
+ """
33
+
34
+ @property
35
+ @abstractmethod
36
+ def id(self) -> str:
37
+ """Unique session id, which will be the same for the same external user id, tenant,
38
+ and instance of the service."""
39
+ raise NotImplementedError
40
+
41
+ @property
42
+ @abstractmethod
43
+ def auth_token(self) -> str:
44
+ """Authentication key. The authentication key is used to identify an external user withing private knowledge."""
45
+ raise NotImplementedError
46
+
47
+ @property
48
+ @abstractmethod
49
+ def tenant_id(self) -> str:
50
+ """Tenant id."""
51
+ raise NotImplementedError
52
+
53
+ @property
54
+ def refresh_token(self) -> Optional[str]:
55
+ """Refresh token. The refresh token is used to refresh the session."""
56
+ return None
57
+
58
+ @property
59
+ @abstractmethod
60
+ def refreshable(self) -> bool:
61
+ """Is the session refreshable."""
62
+ raise NotImplementedError
63
+
64
+ @property
65
+ @abstractmethod
66
+ def expired(self) -> bool:
67
+ """Is the session expired."""
68
+ raise NotImplementedError
69
+
70
+ @property
71
+ @abstractmethod
72
+ def expires_in(self) -> float:
73
+ """Seconds until token is expired in seconds."""
74
+ raise NotImplementedError
75
+
76
+ @abstractmethod
77
+ def update_session(self, auth_token: str, refresh_token: str):
78
+ """
79
+ Update the session.
80
+
81
+ Parameters
82
+ ----------
83
+ auth_token: str
84
+ The refreshed authentication token.
85
+ refresh_token: str
86
+ The refreshed refresh token.
87
+ """
88
+ raise NotImplementedError
89
+
90
+
91
+ class TimedSession(Session):
92
+ """
93
+ TimedSession
94
+ ----------------
95
+ The timed session is only valid until the token expires. There is no refresh token, thus the session cannot be
96
+ refreshed.
97
+ """
98
+
99
+ def __init__(self, auth_token: str):
100
+ self.__auth_token: str = auth_token
101
+ self._auth_token_details_(auth_token)
102
+
103
+ def _auth_token_details_(self, auth_token: str):
104
+ """
105
+ Extract the details from the authentication token.
106
+ Parameters
107
+ ----------
108
+ auth_token: str
109
+ Authentication token
110
+ """
111
+ structures: Dict[str, Any] = jwt.decode(auth_token, options={"verify_signature": False})
112
+ if (
113
+ "tenant" not in structures
114
+ or "roles" not in structures
115
+ or "exp" not in structures
116
+ or "iss" not in structures
117
+ or "ext-sub" not in structures
118
+ ):
119
+ raise ValueError("Invalid authentication token.")
120
+ self.__tenant_id: str = structures["tenant"]
121
+ self.__roles: str = structures["roles"]
122
+ self.__timestamp: datetime = datetime.fromtimestamp(structures["exp"], tz=timezone.utc)
123
+ self.__service_url: str = structures["iss"]
124
+ self.__external_user_id: str = structures["ext-sub"]
125
+ self.__id: str = TimedSession._session_id_(self.__service_url, self.__tenant_id, self.__external_user_id)
126
+
127
+ @staticmethod
128
+ def _session_id_(service_url: str, tenant_id: str, external_user_id: str):
129
+ """
130
+ Create a session id.
131
+
132
+ Parameters
133
+ ----------
134
+ service_url: str
135
+ Service url.
136
+ tenant_id: str
137
+ Tenant id.
138
+ external_user_id: str
139
+ External user id.
140
+
141
+ Returns
142
+ -------
143
+ session_id: str
144
+ Session id.
145
+ """
146
+ unique: str = f"{service_url}{tenant_id}{external_user_id}"
147
+ return hashlib.sha256(unique.encode()).hexdigest()
148
+
149
+ @staticmethod
150
+ def extract_session_id(auth_key: str) -> str:
151
+ """
152
+ Extract the session id from the authentication key.
153
+ Parameters
154
+ ----------
155
+ auth_key: str
156
+ Authentication key.
157
+
158
+ Returns
159
+ -------
160
+ session_id: str
161
+ Session id.
162
+ """
163
+ structures: Dict[str, Any] = jwt.decode(auth_key, options={"verify_signature": False})
164
+ if "ext-sub" not in structures:
165
+ raise ValueError("Invalid authentication key.")
166
+ service_url: str = structures["iss"]
167
+ tenant_id: str = structures["tenant"]
168
+ external_user_id: str = structures["ext-sub"]
169
+ return TimedSession._session_id_(service_url, tenant_id, external_user_id)
170
+
171
+ @property
172
+ def tenant_id(self) -> str:
173
+ """Tenant id."""
174
+ return self.__tenant_id
175
+
176
+ @property
177
+ def roles(self) -> str:
178
+ """Roles."""
179
+ return self.__roles
180
+
181
+ @property
182
+ def service_url(self) -> str:
183
+ """Service url."""
184
+ return self.__service_url
185
+
186
+ @property
187
+ def external_user_id(self) -> str:
188
+ """External user id."""
189
+ return self.__external_user_id
190
+
191
+ @property
192
+ def expiration(self) -> datetime:
193
+ """Timestamp when the token expires."""
194
+ return self.__timestamp
195
+
196
+ @property
197
+ def auth_token(self) -> str:
198
+ """JWT token for the session encoding the user id."""
199
+ return self.__auth_token
200
+
201
+ @auth_token.setter
202
+ def auth_token(self, value: str):
203
+ self.__auth_token = value
204
+
205
+ @property
206
+ def id(self) -> str:
207
+ """Session id."""
208
+ return self.__id
209
+
210
+ @property
211
+ def expires_in(self) -> float:
212
+ """Seconds until token is expired in seconds."""
213
+ timestamp: datetime = datetime.now(tz=timezone.utc)
214
+ return self.expiration.timestamp() - timestamp.timestamp()
215
+
216
+ @property
217
+ def expired(self) -> bool:
218
+ """Is the session expired."""
219
+ return self.expires_in <= 0.0
220
+
221
+ @property
222
+ def refreshable(self) -> bool:
223
+ """Is the session refreshable."""
224
+ return False
225
+
226
+ def update_session(self, auth_token: str, refresh_token: str):
227
+ raise NotImplementedError
228
+
229
+ def __str__(self):
230
+ return f"TimedSession(auth_token={self.auth_token})"
231
+
232
+
233
+ class RefreshableSession(TimedSession):
234
+ """
235
+ RefreshableSession
236
+ ------------------
237
+ The session class holds the information about the session.
238
+ As there is refresh token, the session can be refreshed.
239
+ """
240
+
241
+ def __init__(self, auth_token: str, refresh_token: str):
242
+ super().__init__(auth_token)
243
+ self.__refresh_token: str = refresh_token
244
+
245
+ @property
246
+ def refresh_token(self) -> str:
247
+ """Refresh token for the session."""
248
+ return self.__refresh_token
249
+
250
+ @refresh_token.setter
251
+ def refresh_token(self, value: str):
252
+ self.__refresh_token = value
253
+
254
+ def update_session(self, auth_token: str, refresh_token: str):
255
+ """
256
+ Refresh the session.
257
+ Parameters
258
+ ----------
259
+ auth_token: str
260
+ The refreshed authentication token.
261
+ refresh_token: str
262
+ The refreshed refresh token.
263
+ """
264
+ structures = jwt.decode(auth_token, options={"verify_signature": False})
265
+ if (
266
+ "tenant" not in structures
267
+ or "roles" not in structures
268
+ or "exp" not in structures
269
+ or "iss" not in structures
270
+ or "ext-sub" not in structures
271
+ ):
272
+ raise ValueError("Invalid authentication token.")
273
+ if (
274
+ self.tenant_id != structures["tenant"]
275
+ or self.external_user_id != structures["ext-sub"]
276
+ or self.service_url != structures["iss"]
277
+ ):
278
+ raise ValueError("The token is from a different user, tenant, or instance.")
279
+ self._auth_token_details_(auth_token)
280
+ self.auth_token = auth_token
281
+ self.refresh_token = refresh_token
282
+
283
+ @property
284
+ def refreshable(self) -> bool:
285
+ """Is the session refreshable."""
286
+ return self.refresh_token is not None
287
+
288
+ def __str__(self):
289
+ return f"RefreshableSession(auth_token={self.auth_token}, refresh_token={self.refresh_token})"
290
+
291
+
292
+ class PermanentSession(RefreshableSession):
293
+ """
294
+ RefreshableSession
295
+ ------------------
296
+ The session class holds the information about the session.
297
+
298
+ """
299
+
300
+ def __init__(self, tenant_api_key: str, external_user_id: str, auth_token: str, refresh_token: str):
301
+ super().__init__(auth_token, refresh_token)
302
+ self.__tenant_api_key: str = tenant_api_key
303
+ self.__external_user_id: str = external_user_id
304
+
305
+ @property
306
+ def tenant_api_key(self) -> str:
307
+ """Tenant api key."""
308
+ return self.__tenant_api_key
309
+
310
+ @property
311
+ def external_user_id(self) -> str:
312
+ """External user id."""
313
+ return self.__external_user_id
314
+
315
+ def __str__(self):
316
+ return (
317
+ f"PermanentSession(tenant_api_key={self.tenant_api_key}, external_user_id={self.external_user_id}, "
318
+ f"auth_token={self.auth_token}, refresh_token={self.refresh_token})"
319
+ )
320
+
321
+
322
+ class TokenManager:
323
+ """
324
+ TokenManager
325
+ ------------
326
+ The token manager is a singleton that holds all the sessions for the users.
327
+ """
328
+
329
+ __instance: "TokenManager" = None
330
+ __lock: threading.Lock = threading.Lock() # Asynchronous lock for thread safety
331
+
332
+ def __new__(cls):
333
+ """Create a new singleton instance of the token manager."""
334
+ with cls.__lock:
335
+ if cls.__instance is None:
336
+ cls.__instance = super(TokenManager, cls).__new__(cls)
337
+ cls.__instance.__initialize__()
338
+ return cls.__instance
339
+
340
+ def __initialize__(self):
341
+ self.sessions: Dict[str, Union[TimedSession, RefreshableSession, PermanentSession]] = {}
342
+
343
+ def add_session(
344
+ self,
345
+ auth_token: str,
346
+ refresh_token: Optional[str] = None,
347
+ tenant_api_key: Optional[str] = None,
348
+ external_user_id: Optional[str] = None,
349
+ ) -> Union[PermanentSession, RefreshableSession, TimedSession]:
350
+ """
351
+ Add a session.
352
+ Parameters
353
+ ----------
354
+ auth_token: str
355
+ The authentication token.
356
+ refresh_token: Optional[str] [default := None]
357
+ The refresh token.
358
+ tenant_api_key: Optional[str] [default := None]
359
+ The tenant api key.
360
+ external_user_id: Optional[str] [default := None]
361
+ The external user id.
362
+
363
+ Returns
364
+ -------
365
+ session: Union[PermanentSession, RefreshableSession, TimedSession]
366
+ The logged-in session.
367
+ """
368
+ with self.__lock:
369
+ if tenant_api_key is not None and external_user_id is not None:
370
+ session = PermanentSession(
371
+ tenant_api_key=tenant_api_key,
372
+ external_user_id=external_user_id,
373
+ auth_token=auth_token,
374
+ refresh_token=refresh_token,
375
+ )
376
+ # If there is a tenant api key and an external user id, then the session is permanent
377
+ elif refresh_token is not None:
378
+ session = RefreshableSession(auth_token=auth_token, refresh_token=refresh_token)
379
+ # If there is a refresh token, then the session is refreshable
380
+ else:
381
+ session = TimedSession(auth_token=auth_token)
382
+ # If there is no refresh token, then the session is timed
383
+ if session.id in self.sessions:
384
+ if type(session) is not type(self.sessions[session.id]):
385
+ logger.warning(
386
+ f"Session {session.id} already exists. "
387
+ f"Overwriting with new type of session {type(session)}, "
388
+ f"before {type(self.sessions[session.id])}."
389
+ )
390
+ if not isinstance(self.sessions[session.id], type(session)):
391
+ logger.warning(
392
+ f"The session {session.id} is of a different type. "
393
+ f"Cached version is a {type(self.sessions[session.id])} "
394
+ f"and the new session is a {type(session)}."
395
+ )
396
+ self.sessions[session.id] = session
397
+ return session
398
+
399
+ def get_session(self, session_id: str) -> Union[RefreshableSession, TimedSession, PermanentSession, None]:
400
+ """
401
+ Get a session by its id.
402
+
403
+ Parameters
404
+ ----------
405
+ session_id: str
406
+ Session id.
407
+
408
+ Returns
409
+ -------
410
+ session: Union[RefreshableSession, TimedSession, PermanentSession]
411
+ Depending on the session type, the session is returned.
412
+ """
413
+ with self.__lock:
414
+ return self.sessions.get(session_id)
415
+
416
+ def remove_session(self, session_id: str):
417
+ """
418
+ Remove a session by its id.
419
+
420
+ Parameters
421
+ ----------
422
+ session_id: str
423
+ Session id.
424
+ """
425
+ with self.__lock:
426
+ if session_id in self.sessions:
427
+ del self.sessions[session_id]
428
+
429
+ def has_session(self, session_id: str) -> bool:
430
+ """
431
+ Check if a session exists.
432
+
433
+ Parameters
434
+ ----------
435
+ session_id: str
436
+ Session id.
437
+
438
+ Returns
439
+ -------
440
+ available: bool
441
+ True if the session exists, otherwise False.
442
+ """
443
+ with self.__lock:
444
+ return session_id in self.sessions