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,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,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}"
|