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,128 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright © 2023-24 Wacom. All rights reserved.
3
+ """
4
+ This package contains the services for the knowledge graph functionality.
5
+ """
6
+ from typing import List
7
+
8
+ # ------------------------------------------------- Constants ----------------------------------------------------------
9
+ USER_AGENT_HEADER_FLAG: str = "User-Agent"
10
+ AUTHORIZATION_HEADER_FLAG: str = "Authorization"
11
+ CONTENT_TYPE_HEADER_FLAG: str = "Content-Type"
12
+ TENANT_API_KEY: str = "x-tenant-api-key"
13
+ REFRESH_TOKEN_TAG: str = "refreshToken"
14
+ EXPIRATION_DATE_TAG: str = "expirationDate"
15
+ ACCESS_TOKEN_TAG: str = "accessToken"
16
+ ACTIVATION_TAG: str = "activation"
17
+ SEARCH_TERM: str = "searchTerm"
18
+ EXACT_MATCH: str = "exactMatch"
19
+ LANGUAGE_PARAMETER: str = "language"
20
+ TYPES_PARAMETER: str = "types"
21
+ LIMIT_PARAMETER: str = "limit"
22
+ LITERAL_PARAMETER: str = "Literal"
23
+ VALUE: str = "Value"
24
+ SEARCH_PATTERN_PARAMETER: str = "SearchPattern"
25
+ LISTING: str = "listing"
26
+ TOTAL_COUNT: str = "estimatedCount"
27
+ TARGET: str = "target"
28
+ OBJECT: str = "object"
29
+ PREDICATE: str = "predicate"
30
+ SUBJECT: str = "subject"
31
+ LIMIT: str = "limit"
32
+ OBJECT_URI: str = "objectUri"
33
+ RELATION_URI: str = "relationUri"
34
+ SUBJECT_URI: str = "subjectUri"
35
+ NEXT_PAGE_ID_TAG: str = "nextPageId"
36
+ TENANT_RIGHTS_TAG: str = "tenantRights"
37
+ GROUP_IDS_TAG: str = "groupIds"
38
+ OWNER_ID_TAG: str = "ownerId"
39
+ VISIBILITY_TAG: str = "visibility"
40
+ ESTIMATE_COUNT: str = "estimateCount"
41
+ GROUP_USER_RIGHTS_TAG: str = "groupUserRights"
42
+ JOIN_KEY_PARAM: str = "joinKey"
43
+ USER_TO_ADD_PARAM: str = "userToAddId"
44
+ USER_TO_REMOVE_PARAM: str = "userToRemoveId"
45
+ FORCE_PARAM: str = "force"
46
+ IS_OWNER_PARAM: str = "isOwner"
47
+ RELATION_TAG: str = "relation"
48
+ ENTITIES_TAG: str = "entities"
49
+ RESULT_TAG: str = "result"
50
+ EXTERNAL_USER_ID: str = "externalUserId"
51
+ PRUNE_PARAM: str = "prune"
52
+ NEL_PARAM: str = "nelType"
53
+
54
+ APPLICATION_JSON_HEADER: str = "application/json"
55
+
56
+ DEFAULT_TIMEOUT: int = 60
57
+ DEFAULT_TOKEN_REFRESH_TIME: int = 360
58
+ STATUS_FORCE_LIST: List[int] = [502, 503, 504]
59
+ DEFAULT_BACKOFF_FACTOR: float = 0.1
60
+ DEFAULT_MAX_RETRIES: int = 3
61
+
62
+ """
63
+ Refresh token time in seconds. 360 seconds = 6 minutes
64
+ """
65
+
66
+ __all__ = [
67
+ "base",
68
+ "graph",
69
+ "ontology",
70
+ "tenant",
71
+ "users",
72
+ "search",
73
+ "USER_AGENT_HEADER_FLAG",
74
+ "AUTHORIZATION_HEADER_FLAG",
75
+ "CONTENT_TYPE_HEADER_FLAG",
76
+ "TENANT_API_KEY",
77
+ "REFRESH_TOKEN_TAG",
78
+ "EXPIRATION_DATE_TAG",
79
+ "ACCESS_TOKEN_TAG",
80
+ "ACTIVATION_TAG",
81
+ "SEARCH_TERM",
82
+ "LANGUAGE_PARAMETER",
83
+ "TYPES_PARAMETER",
84
+ "LIMIT_PARAMETER",
85
+ "LITERAL_PARAMETER",
86
+ "VALUE",
87
+ "SEARCH_PATTERN_PARAMETER",
88
+ "LISTING",
89
+ "TOTAL_COUNT",
90
+ "TARGET",
91
+ "OBJECT",
92
+ "PREDICATE",
93
+ "SUBJECT",
94
+ "LIMIT",
95
+ "OBJECT_URI",
96
+ "RELATION_URI",
97
+ "SUBJECT_URI",
98
+ "NEXT_PAGE_ID_TAG",
99
+ "TENANT_RIGHTS_TAG",
100
+ "GROUP_IDS_TAG",
101
+ "OWNER_ID_TAG",
102
+ "VISIBILITY_TAG",
103
+ "ESTIMATE_COUNT",
104
+ "GROUP_USER_RIGHTS_TAG",
105
+ "JOIN_KEY_PARAM",
106
+ "USER_TO_ADD_PARAM",
107
+ "USER_TO_REMOVE_PARAM",
108
+ "FORCE_PARAM",
109
+ "RELATION_TAG",
110
+ "APPLICATION_JSON_HEADER",
111
+ "DEFAULT_TIMEOUT",
112
+ "ENTITIES_TAG",
113
+ "RESULT_TAG",
114
+ "EXACT_MATCH",
115
+ "DEFAULT_TOKEN_REFRESH_TIME",
116
+ "EXTERNAL_USER_ID",
117
+ "IS_OWNER_PARAM",
118
+ "PRUNE_PARAM",
119
+ "STATUS_FORCE_LIST",
120
+ "DEFAULT_BACKOFF_FACTOR",
121
+ "DEFAULT_MAX_RETRIES",
122
+ ]
123
+
124
+ from knowledge.services import base
125
+ from knowledge.services import graph
126
+ from knowledge.services import ontology
127
+ from knowledge.services import tenant
128
+ from knowledge.services import users
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright © 2021-present Wacom. All rights reserved.
3
+ """
4
+ This package contains the asyncio client for the knowledge graph functionality.
5
+ """
6
+
7
+ __all__ = ["base", "graph", "group", "users", "search"]
@@ -0,0 +1,458 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright © 2021-present Wacom. All rights reserved.
3
+ import asyncio
4
+ import json
5
+ import socket
6
+ import ssl
7
+ import sys
8
+ from datetime import datetime
9
+ from typing import Any, Tuple, Dict, Optional, Union
10
+
11
+ import aiohttp
12
+ import certifi
13
+ import orjson
14
+ from aiohttp import ClientTimeout
15
+ from cachetools import TTLCache
16
+
17
+ from knowledge import __version__, logger
18
+ from knowledge.services import (
19
+ USER_AGENT_HEADER_FLAG,
20
+ TENANT_API_KEY,
21
+ CONTENT_TYPE_HEADER_FLAG,
22
+ REFRESH_TOKEN_TAG,
23
+ DEFAULT_TIMEOUT,
24
+ EXPIRATION_DATE_TAG,
25
+ ACCESS_TOKEN_TAG,
26
+ APPLICATION_JSON_HEADER,
27
+ EXTERNAL_USER_ID,
28
+ )
29
+ from knowledge.services.base import WacomServiceException, RESTAPIClient
30
+ from knowledge.services.session import TokenManager, PermanentSession, RefreshableSession, TimedSession
31
+
32
+ # A cache for storing DNS resolutions
33
+ dns_cache: TTLCache = TTLCache(maxsize=100, ttl=300) # Adjust size and ttl as needed
34
+
35
+
36
+ async def cached_getaddrinfo(host: str, *args, **kwargs) -> Any:
37
+ """
38
+ Cached address information.
39
+
40
+ Parameters
41
+ ----------
42
+ host: str
43
+ Hostname
44
+ args: Any
45
+ Additional arguments
46
+ kwargs: Any
47
+ Additional keyword arguments
48
+
49
+ Returns
50
+ -------
51
+ addr_info: Any
52
+ Address information
53
+ """
54
+ if host in dns_cache:
55
+ return dns_cache[host]
56
+ addr_info = await asyncio.get_running_loop().getaddrinfo(host, port=None, *args, **kwargs)
57
+ dns_cache[host] = addr_info
58
+ return addr_info
59
+
60
+
61
+ class CachedResolver(aiohttp.resolver.AbstractResolver):
62
+ """
63
+ CachedResolver
64
+ ==============
65
+ Cached resolver for aiohttp.
66
+ """
67
+
68
+ async def close(self) -> None:
69
+ pass
70
+
71
+ async def resolve(self, host: str, port: int = 0, family: int = socket.AF_INET):
72
+ infos = await cached_getaddrinfo(host)
73
+ return [
74
+ {
75
+ "hostname": host,
76
+ "host": info[4][0],
77
+ "port": port,
78
+ "family": family,
79
+ "proto": 0,
80
+ "flags": socket.AI_NUMERICHOST,
81
+ }
82
+ for info in infos
83
+ ]
84
+
85
+
86
+ cached_resolver: CachedResolver = CachedResolver()
87
+ """ Cached resolver for aiohttp."""
88
+
89
+
90
+ async def handle_error(
91
+ message: str,
92
+ response: aiohttp.ClientResponse,
93
+ parameters: Optional[Dict[str, Any]] = None,
94
+ payload: Optional[Dict[str, Any]] = None,
95
+ headers: Optional[Dict[str, str]] = None,
96
+ ) -> WacomServiceException:
97
+ """
98
+ Handles an error response.
99
+
100
+ Parameters
101
+ ----------
102
+ message: str
103
+ Error message
104
+ response: aiohttp.ClientResponse
105
+ Response
106
+ parameters: Optional[Dict[str, Any]] (Default:= None)
107
+ Parameters
108
+ payload: Optional[Dict[str, Any]] (Default:= None)
109
+ Payload
110
+ headers: Optional[Dict[str, str]] (Default:= None)
111
+ Headers
112
+
113
+ Returns
114
+ -------
115
+ WacomServiceException
116
+ Create exception.
117
+ """
118
+ try:
119
+ response_text: str = await response.text()
120
+ except Exception as _:
121
+ response_text: str = ""
122
+ return WacomServiceException(
123
+ message,
124
+ method=response.method,
125
+ url=response.url.human_repr(),
126
+ params=parameters,
127
+ payload=payload,
128
+ headers=headers,
129
+ status_code=response.status,
130
+ service_response=response_text,
131
+ )
132
+
133
+
134
+ class AsyncServiceAPIClient(RESTAPIClient):
135
+ """
136
+ Async Wacom Service API Client
137
+ ------------------------------
138
+ Abstract class for Wacom service APIs.
139
+
140
+ Parameters
141
+ ----------
142
+ service_url: str
143
+ URL of the service
144
+ service_endpoint: str
145
+ Base endpoint
146
+ auth_service_endpoint: str (Default:= 'graph/v1')
147
+ Authentication service endpoint
148
+ verify_calls: bool (Default:= True)
149
+ Flag if API calls should be verified.
150
+
151
+ """
152
+
153
+ USER_ENDPOINT: str = "user"
154
+ USER_LOGIN_ENDPOINT: str = f"{USER_ENDPOINT}/login"
155
+ USER_REFRESH_ENDPOINT: str = f"{USER_ENDPOINT}/refresh"
156
+ SERVICE_URL: str = "https://private-knowledge.wacom.com"
157
+ """Production service URL"""
158
+ STAGING_SERVICE_URL: str = "https://stage-private-knowledge.wacom.com"
159
+ """Staging service URL"""
160
+
161
+ def __init__(
162
+ self,
163
+ application_name: str = "Async Knowledge Client",
164
+ service_url: str = SERVICE_URL,
165
+ service_endpoint: str = "graph/v1",
166
+ auth_service_endpoint: str = "graph/v1",
167
+ verify_calls: bool = True,
168
+ graceful_shutdown: bool = False,
169
+ ):
170
+ self.__service_endpoint: str = service_endpoint
171
+ self.__auth_service_endpoint: str = auth_service_endpoint
172
+ self.__application_name: str = application_name
173
+ self.__token_manager: TokenManager = TokenManager()
174
+ self.__current_session_id: Optional[str] = None
175
+ self.__graceful_shutdown: bool = graceful_shutdown
176
+ super().__init__(service_url, verify_calls)
177
+
178
+ @property
179
+ def application_name(self) -> str:
180
+ """Application name."""
181
+ return self.__application_name
182
+
183
+ @property
184
+ def user_agent(self) -> str:
185
+ """User agent."""
186
+ return (
187
+ f"Personal Knowledge Library({self.application_name})/{__version__}"
188
+ f"(+https://github.com/Wacom-Developer/personal-knowledge-library)"
189
+ )
190
+
191
+ @property
192
+ def use_graceful_shutdown(self) -> bool:
193
+ """Use graceful shutdown."""
194
+ return self.__graceful_shutdown
195
+
196
+ @property
197
+ def current_session(self) -> Union[RefreshableSession, TimedSession, PermanentSession, None]:
198
+ """Current session.
199
+
200
+ Returns
201
+ -------
202
+ session: Union[TimedSession, RefreshableSession, PermanentSession]
203
+ Current session
204
+
205
+ Raises
206
+ ------
207
+ WacomServiceException
208
+ Exception if no session is available.
209
+ """
210
+ if self.__current_session_id is None:
211
+ raise WacomServiceException("No session set. Please login first.")
212
+ session: Union[RefreshableSession, TimedSession, PermanentSession, None] = self.__token_manager.get_session(
213
+ self.__current_session_id
214
+ )
215
+ if session is None:
216
+ raise WacomServiceException(f"Unknown session id:= {self.__current_session_id}. Please login first.")
217
+ return session
218
+
219
+ async def use_session(self, session_id: str):
220
+ """Use session.
221
+ Parameters
222
+ ----------
223
+ session_id: str
224
+ Session id
225
+ """
226
+ if self.__token_manager.has_session(session_id):
227
+ self.__current_session_id = session_id
228
+ else:
229
+ raise WacomServiceException(f"Unknown session id:= {session_id}.")
230
+
231
+ async def handle_token(self, force_refresh: bool = False, force_refresh_timeout: float = 120) -> Tuple[str, str]:
232
+ """
233
+ Handles the token and refreshes it if needed.
234
+
235
+ Parameters
236
+ ----------
237
+ force_refresh: bool
238
+ Force refresh token
239
+ force_refresh_timeout: int
240
+ Force refresh timeout
241
+ Returns
242
+ -------
243
+ user_token: str
244
+ The user token
245
+ refresh_token: str
246
+ The refresh token
247
+ """
248
+ # The session is not set
249
+ if self.current_session is None:
250
+ raise WacomServiceException("Authentication key is not set. Please login first.")
251
+
252
+ # The token expired and is not refreshable
253
+ if not self.current_session.refreshable and self.current_session.expired:
254
+ raise WacomServiceException("Authentication key is expired and cannot be refreshed. Please login again.")
255
+
256
+ # The token is not refreshable and the force refresh flag is set
257
+ if not self.current_session.refreshable and force_refresh:
258
+ raise WacomServiceException("Authentication key is not refreshable. Please login again.")
259
+
260
+ # Refresh token if needed
261
+ if self.current_session.refreshable and (
262
+ self.current_session.expires_in < force_refresh_timeout or force_refresh
263
+ ):
264
+ try:
265
+ auth_key, refresh_token, _ = await self.refresh_token(self.current_session.refresh_token)
266
+ except WacomServiceException as e:
267
+ if isinstance(self.current_session, PermanentSession):
268
+ permanent_session: PermanentSession = self.current_session
269
+ auth_key, refresh_token, _ = await self.request_user_token(
270
+ permanent_session.tenant_api_key, permanent_session.external_user_id
271
+ )
272
+ else:
273
+ logger.error(f"Error refreshing token: {e}")
274
+ raise e
275
+ self.current_session.update_session(auth_key, refresh_token)
276
+ return auth_key, refresh_token
277
+ return self.current_session.auth_token, self.current_session.refresh_token
278
+
279
+ @staticmethod
280
+ def __async_session__() -> aiohttp.ClientSession:
281
+ """
282
+ Returns an asynchronous session.
283
+
284
+ Returns
285
+ -------
286
+ session: aiohttp.ClientSession
287
+ Asynchronous session
288
+ """
289
+ timeout: ClientTimeout = ClientTimeout(total=60)
290
+ ssl_context: ssl.SSLContext = ssl.create_default_context(cafile=certifi.where())
291
+ connector: aiohttp.TCPConnector = aiohttp.TCPConnector(ssl=ssl_context, resolver=cached_resolver)
292
+ return aiohttp.ClientSession(
293
+ json_serialize=lambda x: orjson.dumps(x).decode(), timeout=timeout, connector=connector
294
+ )
295
+
296
+ async def request_user_token(self, tenant_api_key: str, external_id: str) -> Tuple[str, str, datetime]:
297
+ """
298
+ Login as user by using the tenant key and its external user id.
299
+
300
+ Parameters
301
+ ----------
302
+ tenant_api_key: str
303
+ Tenant api key
304
+ external_id: str
305
+ External id.
306
+
307
+ Returns
308
+ -------
309
+ auth_key: str
310
+ Authentication key for identifying the user for the service calls.
311
+ refresh_key: str
312
+ Refresh token
313
+ expiration_time: datatime
314
+ Expiration time
315
+
316
+ Raises
317
+ ------
318
+ WacomServiceException
319
+ Exception if service returns HTTP error code.
320
+ """
321
+ headers: Dict[str, str] = {
322
+ USER_AGENT_HEADER_FLAG: self.user_agent,
323
+ TENANT_API_KEY: tenant_api_key,
324
+ CONTENT_TYPE_HEADER_FLAG: APPLICATION_JSON_HEADER,
325
+ }
326
+ payload: dict = {EXTERNAL_USER_ID: external_id}
327
+ async with AsyncServiceAPIClient.__async_session__() as session:
328
+ async with session.post(self.auth_endpoint, data=json.dumps(payload), headers=headers) as response:
329
+ if response.ok:
330
+ response_token: Dict[str, str] = await response.json(loads=orjson.loads)
331
+ try:
332
+ date_object: datetime = datetime.fromisoformat(response_token[EXPIRATION_DATE_TAG])
333
+ except (TypeError, ValueError) as _:
334
+ date_object: datetime = datetime.now()
335
+ logger.warning(f"Parsing of expiration date failed. {response_token[EXPIRATION_DATE_TAG]}")
336
+ else:
337
+ raise await handle_error("Login failed.", response, payload=payload, headers=headers)
338
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
339
+ return response_token["accessToken"], response_token["refreshToken"], date_object
340
+
341
+ async def refresh_token(self, refresh_token: str) -> Tuple[str, str, datetime]:
342
+ """
343
+ Refreshing a token.
344
+
345
+ Parameters
346
+ ----------
347
+ refresh_token: str
348
+ Refresh token
349
+
350
+ Returns
351
+ -------
352
+ auth_key: str
353
+ Authentication key for identifying the user for the service calls.
354
+ refresh_key: str
355
+ Refresh token
356
+ expiration_time: str
357
+ Expiration time
358
+
359
+ Raises
360
+ ------
361
+ WacomServiceException
362
+ Exception if service returns HTTP error code.
363
+ """
364
+ url: str = f"{self.service_base_url}{AsyncServiceAPIClient.USER_REFRESH_ENDPOINT}/"
365
+ headers: Dict[str, str] = {
366
+ USER_AGENT_HEADER_FLAG: self.user_agent,
367
+ CONTENT_TYPE_HEADER_FLAG: "application/json",
368
+ }
369
+ payload: Dict[str, str] = {REFRESH_TOKEN_TAG: refresh_token}
370
+ async with self.__async_session__() as session:
371
+ async with session.post(
372
+ url, headers=headers, json=payload, timeout=DEFAULT_TIMEOUT, verify_ssl=self.verify_calls
373
+ ) as response:
374
+ if response.ok:
375
+ response_token: Dict[str, str] = await response.json()
376
+ timestamp_str_truncated: str = ""
377
+ try:
378
+ if sys.version_info <= (3, 10):
379
+ timestamp_str_truncated = response_token[EXPIRATION_DATE_TAG][:19] + "+00:00"
380
+ else:
381
+ timestamp_str_truncated = response_token[EXPIRATION_DATE_TAG]
382
+ date_object: datetime = datetime.fromisoformat(timestamp_str_truncated)
383
+ except (TypeError, ValueError) as _:
384
+ date_object: datetime = datetime.now()
385
+ logger.warning(f"Parsing of expiration date failed. {timestamp_str_truncated}")
386
+ else:
387
+ raise await handle_error("Refresh of token failed.", response, payload=payload, headers=headers)
388
+ await asyncio.sleep(0.25 if self.use_graceful_shutdown else 0.0)
389
+ return response_token[ACCESS_TOKEN_TAG], response_token[REFRESH_TOKEN_TAG], date_object
390
+
391
+ async def login(self, tenant_api_key: str, external_user_id: str) -> PermanentSession:
392
+ """Login as user by using the tenant id and its external user id.
393
+ Parameters
394
+ ----------
395
+ tenant_api_key: str
396
+ Tenant id
397
+ external_user_id: str
398
+ External user id
399
+ Returns
400
+ -------
401
+ session: PermanentSession
402
+ Session. The session is stored in the token manager and the client is using the session id for further
403
+ calls.
404
+ """
405
+ auth_key, refresh_token, _ = await self.request_user_token(tenant_api_key, external_user_id)
406
+ session: PermanentSession = self.__token_manager.add_session(
407
+ auth_token=auth_key,
408
+ refresh_token=refresh_token,
409
+ tenant_api_key=tenant_api_key,
410
+ external_user_id=external_user_id,
411
+ )
412
+ self.__current_session_id = session.id
413
+ return session
414
+
415
+ async def logout(self):
416
+ """Logout user."""
417
+ if self.__current_session_id:
418
+ self.__token_manager.remove_session(self.__current_session_id)
419
+ self.__current_session_id = None
420
+
421
+ async def register_token(
422
+ self, auth_key: str, refresh_token: Optional[str] = None
423
+ ) -> Union[RefreshableSession, TimedSession]:
424
+ """Register token.
425
+ Parameters
426
+ ----------
427
+ auth_key: str
428
+ Authentication key for identifying the user for the service calls.
429
+ refresh_token: str
430
+ Refresh token
431
+
432
+ Returns
433
+ -------
434
+ session: Union[RefreshableSession, TimedSession]
435
+ Session. The session is stored in the token manager and the client is using the session id for further
436
+ calls.
437
+ """
438
+ session = self.__token_manager.add_session(auth_token=auth_key, refresh_token=refresh_token)
439
+ self.__current_session_id = session.id
440
+ if isinstance(session, (RefreshableSession, TimedSession)):
441
+ return session
442
+ raise WacomServiceException(f"Wrong session type:= {type(session)}.")
443
+
444
+ @property
445
+ def service_endpoint(self):
446
+ """Service endpoint."""
447
+ return "" if len(self.__service_endpoint) == 0 else f"{self.__service_endpoint}/"
448
+
449
+ @property
450
+ def service_base_url(self):
451
+ """Service endpoint."""
452
+ return f"{self.service_url}/{self.service_endpoint}"
453
+
454
+ @property
455
+ def auth_endpoint(self) -> str:
456
+ """Authentication endpoint."""
457
+ # This is in graph service REST API
458
+ return f"{self.service_url}/{self.__auth_service_endpoint}/{self.USER_LOGIN_ENDPOINT}"