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.
- knowledge/__init__.py +91 -0
- knowledge/base/__init__.py +22 -0
- knowledge/base/access.py +167 -0
- knowledge/base/entity.py +267 -0
- knowledge/base/language.py +27 -0
- knowledge/base/ontology.py +2734 -0
- knowledge/base/search.py +473 -0
- knowledge/base/tenant.py +192 -0
- knowledge/nel/__init__.py +11 -0
- knowledge/nel/base.py +495 -0
- knowledge/nel/engine.py +123 -0
- knowledge/ontomapping/__init__.py +667 -0
- knowledge/ontomapping/manager.py +320 -0
- knowledge/public/__init__.py +27 -0
- knowledge/public/cache.py +115 -0
- knowledge/public/helper.py +373 -0
- knowledge/public/relations.py +128 -0
- knowledge/public/wikidata.py +1324 -0
- knowledge/services/__init__.py +128 -0
- knowledge/services/asyncio/__init__.py +7 -0
- knowledge/services/asyncio/base.py +458 -0
- knowledge/services/asyncio/graph.py +1420 -0
- knowledge/services/asyncio/group.py +450 -0
- knowledge/services/asyncio/search.py +439 -0
- knowledge/services/asyncio/users.py +270 -0
- knowledge/services/base.py +533 -0
- knowledge/services/graph.py +1897 -0
- knowledge/services/group.py +819 -0
- knowledge/services/helper.py +142 -0
- knowledge/services/ontology.py +1234 -0
- knowledge/services/search.py +488 -0
- knowledge/services/session.py +444 -0
- knowledge/services/tenant.py +281 -0
- knowledge/services/users.py +445 -0
- knowledge/utils/__init__.py +10 -0
- knowledge/utils/graph.py +417 -0
- knowledge/utils/wikidata.py +197 -0
- knowledge/utils/wikipedia.py +175 -0
- personal_knowledge_library-3.0.0.dist-info/LICENSE +201 -0
- personal_knowledge_library-3.0.0.dist-info/METADATA +1163 -0
- personal_knowledge_library-3.0.0.dist-info/RECORD +42 -0
- 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
|