sumo-wrapper-python 1.0.8__py3-none-any.whl → 1.0.10__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 sumo-wrapper-python might be problematic. Click here for more details.
- sumo/wrapper/_auth_provider.py +68 -22
- sumo/wrapper/_blob_client.py +10 -8
- sumo/wrapper/_decorators.py +6 -0
- sumo/wrapper/_logging.py +3 -0
- sumo/wrapper/_version.py +2 -2
- sumo/wrapper/login.py +45 -20
- sumo/wrapper/sumo_client.py +85 -44
- {sumo_wrapper_python-1.0.8.dist-info → sumo_wrapper_python-1.0.10.dist-info}/METADATA +20 -164
- sumo_wrapper_python-1.0.10.dist-info/RECORD +17 -0
- {sumo_wrapper_python-1.0.8.dist-info → sumo_wrapper_python-1.0.10.dist-info}/WHEEL +1 -1
- sumo_wrapper_python-1.0.8.dist-info/RECORD +0 -17
- {sumo_wrapper_python-1.0.8.dist-info → sumo_wrapper_python-1.0.10.dist-info}/LICENSE +0 -0
- {sumo_wrapper_python-1.0.8.dist-info → sumo_wrapper_python-1.0.10.dist-info}/entry_points.txt +0 -0
- {sumo_wrapper_python-1.0.8.dist-info → sumo_wrapper_python-1.0.10.dist-info}/top_level.txt +0 -0
sumo/wrapper/_auth_provider.py
CHANGED
|
@@ -34,6 +34,9 @@ class AuthProvider:
|
|
|
34
34
|
self._resource_id = resource_id
|
|
35
35
|
self._scope = scope_for_resource(resource_id)
|
|
36
36
|
self._app = None
|
|
37
|
+
self._login_timeout_minutes = 5
|
|
38
|
+
os.system("") # Ensure color init on all platforms (win10)
|
|
39
|
+
|
|
37
40
|
return
|
|
38
41
|
|
|
39
42
|
@tn.retry(
|
|
@@ -57,11 +60,27 @@ class AuthProvider:
|
|
|
57
60
|
return result["access_token"]
|
|
58
61
|
|
|
59
62
|
def get_authorization(self):
|
|
60
|
-
|
|
63
|
+
token = self.get_token()
|
|
64
|
+
if token is None:
|
|
65
|
+
return ""
|
|
66
|
+
|
|
67
|
+
return {"Authorization": "Bearer " + token}
|
|
61
68
|
|
|
62
69
|
pass
|
|
63
70
|
|
|
64
71
|
|
|
72
|
+
class AuthProviderSilent(AuthProvider):
|
|
73
|
+
def __init__(self, client_id, authority, resource_id):
|
|
74
|
+
super().__init__(resource_id)
|
|
75
|
+
cache = get_token_cache(resource_id, ".token")
|
|
76
|
+
self._app = msal.PublicClientApplication(
|
|
77
|
+
client_id=client_id, authority=authority, token_cache=cache
|
|
78
|
+
)
|
|
79
|
+
self._resource_id = resource_id
|
|
80
|
+
|
|
81
|
+
self._scope = scope_for_resource(resource_id)
|
|
82
|
+
|
|
83
|
+
|
|
65
84
|
class AuthProviderAccessToken(AuthProvider):
|
|
66
85
|
def __init__(self, access_token):
|
|
67
86
|
self._access_token = access_token
|
|
@@ -195,25 +214,24 @@ class AuthProviderInteractive(AuthProvider):
|
|
|
195
214
|
)
|
|
196
215
|
def login(self):
|
|
197
216
|
scopes = [self._scope + " offline_access"]
|
|
198
|
-
login_timeout_minutes = 7
|
|
199
|
-
os.system("") # Ensure color init on all platforms (win10)
|
|
200
217
|
print(
|
|
201
218
|
"\n\n \033[31m NOTE! \033[0m"
|
|
202
219
|
+ " Please login to Equinor Azure to enable Sumo access: "
|
|
203
|
-
+ "we
|
|
220
|
+
+ "we are opening a login web-page for you in your browser."
|
|
204
221
|
+ "\nYou should complete your login within "
|
|
205
|
-
+ str(
|
|
222
|
+
+ str(self._login_timeout_minutes)
|
|
206
223
|
+ " minutes, "
|
|
207
224
|
+ "that is before "
|
|
208
225
|
+ str(
|
|
209
226
|
(
|
|
210
|
-
datetime.now()
|
|
227
|
+
datetime.now()
|
|
228
|
+
+ timedelta(minutes=self._login_timeout_minutes)
|
|
211
229
|
).strftime("%H:%M:%S")
|
|
212
230
|
)
|
|
213
231
|
)
|
|
214
232
|
try:
|
|
215
233
|
result = self._app.acquire_token_interactive(
|
|
216
|
-
scopes, timeout=(
|
|
234
|
+
scopes, timeout=(self._login_timeout_minutes * 60)
|
|
217
235
|
)
|
|
218
236
|
if "error" in result:
|
|
219
237
|
print(
|
|
@@ -230,7 +248,9 @@ class AuthProviderInteractive(AuthProvider):
|
|
|
230
248
|
return
|
|
231
249
|
|
|
232
250
|
protect_token_cache(self._resource_id, ".token")
|
|
233
|
-
print(
|
|
251
|
+
print(
|
|
252
|
+
"Equinor Azure login for Sumo access was successful (interactive)"
|
|
253
|
+
)
|
|
234
254
|
return
|
|
235
255
|
|
|
236
256
|
pass
|
|
@@ -261,24 +281,47 @@ class AuthProviderDeviceCode(AuthProvider):
|
|
|
261
281
|
before_sleep=_log_retry_info,
|
|
262
282
|
)
|
|
263
283
|
def login(self):
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
284
|
+
try:
|
|
285
|
+
scopes = [self._scope + " offline_access"]
|
|
286
|
+
flow = self._app.initiate_device_flow(scopes)
|
|
287
|
+
if "error" in flow:
|
|
288
|
+
print(
|
|
289
|
+
"\n\n \033[31m"
|
|
290
|
+
+ "Failed to initiate device-code login. Err: %s"
|
|
291
|
+
+ "\033[0m" % json.dumps(flow, indent=4)
|
|
292
|
+
)
|
|
293
|
+
return
|
|
294
|
+
flow["expires_at"] = (
|
|
295
|
+
int(time.time()) + self._login_timeout_minutes * 60
|
|
270
296
|
)
|
|
271
297
|
|
|
272
|
-
|
|
273
|
-
|
|
298
|
+
print(
|
|
299
|
+
"\033[31m"
|
|
300
|
+
+ " NOTE! Please login to Equinor Azure to enable Sumo access:"
|
|
301
|
+
+ flow["message"]
|
|
302
|
+
+ " \033[0m"
|
|
303
|
+
+ "\nYou should complete your login within a few minutes"
|
|
304
|
+
)
|
|
305
|
+
result = self._app.acquire_token_by_device_flow(flow)
|
|
274
306
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
307
|
+
if "error" in result:
|
|
308
|
+
print(
|
|
309
|
+
"\n\n \033[31m Error during Equinor Azure login "
|
|
310
|
+
"for Sumo access: \033[0m"
|
|
311
|
+
)
|
|
312
|
+
print("Err: ", json.dumps(result, indent=4))
|
|
313
|
+
return
|
|
314
|
+
except Exception:
|
|
315
|
+
print(
|
|
316
|
+
"\n\n \033[31m Failed Equinor Azure login for Sumo access, "
|
|
317
|
+
"one possible reason is timeout \033[0m"
|
|
279
318
|
)
|
|
319
|
+
return
|
|
280
320
|
|
|
281
321
|
protect_token_cache(self._resource_id, ".token")
|
|
322
|
+
print(
|
|
323
|
+
"Equinor Azure login for Sumo access was successful (device-code)"
|
|
324
|
+
)
|
|
282
325
|
|
|
283
326
|
return
|
|
284
327
|
|
|
@@ -363,6 +406,11 @@ def get_auth_provider(
|
|
|
363
406
|
if os.path.exists(get_token_path(resource_id, ".sharedkey")):
|
|
364
407
|
return AuthProviderSumoToken(resource_id)
|
|
365
408
|
# ELSE
|
|
409
|
+
auth_silent = AuthProviderSilent(client_id, authority, resource_id)
|
|
410
|
+
token = auth_silent.get_token()
|
|
411
|
+
if token is not None:
|
|
412
|
+
return auth_silent
|
|
413
|
+
# ELSE
|
|
366
414
|
if interactive:
|
|
367
415
|
return AuthProviderInteractive(client_id, authority, resource_id)
|
|
368
416
|
# ELSE
|
|
@@ -383,5 +431,3 @@ def get_auth_provider(
|
|
|
383
431
|
]
|
|
384
432
|
):
|
|
385
433
|
return AuthProviderManaged(resource_id)
|
|
386
|
-
# ELSE
|
|
387
|
-
return AuthProviderInteractive(client_id, authority, resource_id)
|
sumo/wrapper/_blob_client.py
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import httpx
|
|
2
|
-
|
|
3
1
|
from ._decorators import (
|
|
4
2
|
raise_for_status,
|
|
5
3
|
raise_for_status_async,
|
|
6
4
|
)
|
|
7
5
|
|
|
8
|
-
from ._retry_strategy import RetryStrategy
|
|
9
|
-
|
|
10
6
|
|
|
11
7
|
class BlobClient:
|
|
12
8
|
"""Upload blobs to blob store using pre-authorized URLs"""
|
|
13
9
|
|
|
14
|
-
def __init__(self, retry_strategy
|
|
10
|
+
def __init__(self, client, async_client, timeout, retry_strategy):
|
|
11
|
+
self._client = client
|
|
12
|
+
self._async_client = async_client
|
|
13
|
+
self._timeout = timeout
|
|
15
14
|
self._retry_strategy = retry_strategy
|
|
16
15
|
return
|
|
17
16
|
|
|
@@ -30,7 +29,9 @@ class BlobClient:
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
def _put():
|
|
33
|
-
return
|
|
32
|
+
return self._client.put(
|
|
33
|
+
url, content=blob, headers=headers, timeout=self._timeout
|
|
34
|
+
)
|
|
34
35
|
|
|
35
36
|
retryer = self._retry_strategy.make_retryer()
|
|
36
37
|
|
|
@@ -51,8 +52,9 @@ class BlobClient:
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
async def _put():
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
return await self._async_client.put(
|
|
56
|
+
url=url, content=blob, headers=headers, timeout=self._timeout
|
|
57
|
+
)
|
|
56
58
|
|
|
57
59
|
retryer = self._retry_strategy.make_retryer_async()
|
|
58
60
|
|
sumo/wrapper/_decorators.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
# For sphinx:
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
|
|
1
5
|
def raise_for_status(func):
|
|
6
|
+
@wraps(func)
|
|
2
7
|
def wrapper(*args, **kwargs):
|
|
3
8
|
# FIXME: in newer versions of httpx, raise_for_status() is chainable,
|
|
4
9
|
# so we could simply write
|
|
@@ -11,6 +16,7 @@ def raise_for_status(func):
|
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
def raise_for_status_async(func):
|
|
19
|
+
@wraps(func)
|
|
14
20
|
async def wrapper(*args, **kwargs):
|
|
15
21
|
# FIXME: in newer versions of httpx, raise_for_status() is chainable,
|
|
16
22
|
# so we could simply write
|
sumo/wrapper/_logging.py
CHANGED
|
@@ -20,6 +20,9 @@ class LogHandlerSumo(logging.Handler):
|
|
|
20
20
|
"funcname": record.funcName,
|
|
21
21
|
"linenumber": record.lineno,
|
|
22
22
|
}
|
|
23
|
+
if "objectUuid" in record.__dict__.keys():
|
|
24
|
+
json["objectUuid"] = record.__dict__.get("objectUuid")
|
|
25
|
+
|
|
23
26
|
self._sumoClient.post("/message-log/new", json=json)
|
|
24
27
|
except Exception:
|
|
25
28
|
# Never fail on logging
|
sumo/wrapper/_version.py
CHANGED
sumo/wrapper/login.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
2
|
+
import platform
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from argparse import ArgumentParser
|
|
4
5
|
from sumo.wrapper import SumoClient
|
|
5
6
|
|
|
@@ -7,6 +8,8 @@ from sumo.wrapper import SumoClient
|
|
|
7
8
|
logger = logging.getLogger("sumo.wrapper")
|
|
8
9
|
logger.setLevel(level="CRITICAL")
|
|
9
10
|
|
|
11
|
+
modes = ["interactive", "devicecode", "silent"]
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
def get_parser() -> ArgumentParser:
|
|
12
15
|
parser = ArgumentParser(description="Login to Sumo on azure")
|
|
@@ -29,21 +32,12 @@ def get_parser() -> ArgumentParser:
|
|
|
29
32
|
)
|
|
30
33
|
|
|
31
34
|
parser.add_argument(
|
|
32
|
-
"-
|
|
33
|
-
"--
|
|
34
|
-
dest="
|
|
35
|
-
action="
|
|
36
|
-
default=
|
|
37
|
-
help="
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
parser.add_argument(
|
|
41
|
-
"-d",
|
|
42
|
-
"--devicecode",
|
|
43
|
-
dest="devicecode",
|
|
44
|
-
action="store_true",
|
|
45
|
-
default=False,
|
|
46
|
-
help="Login with device-code",
|
|
35
|
+
"-m",
|
|
36
|
+
"--mode",
|
|
37
|
+
dest="mode",
|
|
38
|
+
action="store",
|
|
39
|
+
default="interactive",
|
|
40
|
+
help=f"Valid modes: {', '.join(modes)}",
|
|
47
41
|
)
|
|
48
42
|
|
|
49
43
|
parser.add_argument(
|
|
@@ -62,17 +56,48 @@ def main():
|
|
|
62
56
|
args = get_parser().parse_args()
|
|
63
57
|
logger.setLevel(level=args.verbosity)
|
|
64
58
|
env = args.env
|
|
59
|
+
mode = args.mode
|
|
60
|
+
is_interactive = mode == "interactive"
|
|
61
|
+
is_devicecode = mode == "devicecode"
|
|
62
|
+
|
|
65
63
|
logger.debug("env is %s", env)
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
if mode not in modes:
|
|
66
|
+
print(f"Invalid mode: {mode}")
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
if mode != "silent":
|
|
70
|
+
print("Login to Sumo environment: " + env)
|
|
71
|
+
|
|
72
|
+
if mode == "interactive":
|
|
73
|
+
lockfile_path = Path.home() / ".config/chromium/SingletonLock"
|
|
74
|
+
|
|
75
|
+
if Path(lockfile_path).is_symlink() and not str(
|
|
76
|
+
Path(lockfile_path).resolve()
|
|
77
|
+
).__contains__(platform.node()):
|
|
78
|
+
# https://github.com/equinor/sumo-wrapper-python/issues/193
|
|
79
|
+
is_interactive = False
|
|
80
|
+
is_devicecode = True
|
|
68
81
|
|
|
69
82
|
sumo = SumoClient(
|
|
70
|
-
|
|
83
|
+
env,
|
|
84
|
+
interactive=is_interactive,
|
|
85
|
+
devicecode=is_devicecode,
|
|
71
86
|
)
|
|
72
87
|
token = sumo.authenticate()
|
|
73
88
|
|
|
74
|
-
if
|
|
75
|
-
|
|
89
|
+
if mode != "silent":
|
|
90
|
+
if args.print_token:
|
|
91
|
+
print(token)
|
|
92
|
+
|
|
93
|
+
if token is not None:
|
|
94
|
+
print("Successfully logged in to Sumo environment: " + env)
|
|
95
|
+
else:
|
|
96
|
+
print("Failed login to Sumo environment: " + env)
|
|
97
|
+
|
|
98
|
+
if token is None:
|
|
99
|
+
return 1
|
|
100
|
+
return 0
|
|
76
101
|
|
|
77
102
|
|
|
78
103
|
if __name__ == "__main__":
|
sumo/wrapper/sumo_client.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
2
|
+
import asyncio
|
|
3
3
|
import httpx
|
|
4
|
-
|
|
5
4
|
import jwt
|
|
6
5
|
|
|
7
6
|
from ._blob_client import BlobClient
|
|
@@ -18,7 +17,7 @@ from ._retry_strategy import RetryStrategy
|
|
|
18
17
|
|
|
19
18
|
logger = logging.getLogger("sumo.wrapper")
|
|
20
19
|
|
|
21
|
-
DEFAULT_TIMEOUT = httpx.Timeout(
|
|
20
|
+
DEFAULT_TIMEOUT = httpx.Timeout(30.0)
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
class SumoClient:
|
|
@@ -32,6 +31,7 @@ class SumoClient:
|
|
|
32
31
|
devicecode: bool = False,
|
|
33
32
|
verbosity: str = "CRITICAL",
|
|
34
33
|
retry_strategy=RetryStrategy(),
|
|
34
|
+
timeout=DEFAULT_TIMEOUT,
|
|
35
35
|
):
|
|
36
36
|
"""Initialize a new Sumo object
|
|
37
37
|
|
|
@@ -49,7 +49,10 @@ class SumoClient:
|
|
|
49
49
|
raise ValueError(f"Invalid environment: {env}")
|
|
50
50
|
|
|
51
51
|
self._retry_strategy = retry_strategy
|
|
52
|
-
self.
|
|
52
|
+
self._client = httpx.Client(follow_redirects=True)
|
|
53
|
+
self._async_client = httpx.AsyncClient(follow_redirects=True)
|
|
54
|
+
|
|
55
|
+
self._timeout = timeout
|
|
53
56
|
|
|
54
57
|
access_token = None
|
|
55
58
|
refresh_token = None
|
|
@@ -92,7 +95,44 @@ class SumoClient:
|
|
|
92
95
|
pass
|
|
93
96
|
return
|
|
94
97
|
|
|
98
|
+
def __enter__(self):
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
102
|
+
self._client.close()
|
|
103
|
+
self._client = None
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
async def __aenter__(self):
|
|
107
|
+
return self
|
|
108
|
+
|
|
109
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
110
|
+
await self._async_client.aclose()
|
|
111
|
+
self._async_client = None
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
def __del__(self):
|
|
115
|
+
if self._client is not None:
|
|
116
|
+
self._client.close()
|
|
117
|
+
self._client = None
|
|
118
|
+
pass
|
|
119
|
+
if self._async_client is not None:
|
|
120
|
+
|
|
121
|
+
async def closeit(client):
|
|
122
|
+
await client.aclose()
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
loop = asyncio.get_running_loop()
|
|
127
|
+
loop.create_task(closeit(self._async_client))
|
|
128
|
+
except RuntimeError:
|
|
129
|
+
pass
|
|
130
|
+
self._async_client = None
|
|
131
|
+
pass
|
|
132
|
+
|
|
95
133
|
def authenticate(self):
|
|
134
|
+
if self.auth is None:
|
|
135
|
+
return None
|
|
96
136
|
return self.auth.get_token()
|
|
97
137
|
|
|
98
138
|
@property
|
|
@@ -115,7 +155,12 @@ class SumoClient:
|
|
|
115
155
|
await sumo.blob_client.upload_blob_async(blob, blob_url)
|
|
116
156
|
"""
|
|
117
157
|
|
|
118
|
-
return
|
|
158
|
+
return BlobClient(
|
|
159
|
+
self._client,
|
|
160
|
+
self._async_client,
|
|
161
|
+
self._timeout,
|
|
162
|
+
self._retry_strategy,
|
|
163
|
+
)
|
|
119
164
|
|
|
120
165
|
@raise_for_status
|
|
121
166
|
def get(self, path: str, params: dict = None) -> dict:
|
|
@@ -153,12 +198,12 @@ class SumoClient:
|
|
|
153
198
|
headers.update(self.auth.get_authorization())
|
|
154
199
|
|
|
155
200
|
def _get():
|
|
156
|
-
return
|
|
201
|
+
return self._client.get(
|
|
157
202
|
f"{self.base_url}{path}",
|
|
158
203
|
params=params,
|
|
159
204
|
headers=headers,
|
|
160
205
|
follow_redirects=True,
|
|
161
|
-
timeout=
|
|
206
|
+
timeout=self._timeout,
|
|
162
207
|
)
|
|
163
208
|
|
|
164
209
|
retryer = self._retry_strategy.make_retryer()
|
|
@@ -227,13 +272,13 @@ class SumoClient:
|
|
|
227
272
|
headers.update(self.auth.get_authorization())
|
|
228
273
|
|
|
229
274
|
def _post():
|
|
230
|
-
return
|
|
275
|
+
return self._client.post(
|
|
231
276
|
f"{self.base_url}{path}",
|
|
232
277
|
content=blob,
|
|
233
278
|
json=json,
|
|
234
279
|
headers=headers,
|
|
235
280
|
params=params,
|
|
236
|
-
timeout=
|
|
281
|
+
timeout=self._timeout,
|
|
237
282
|
)
|
|
238
283
|
|
|
239
284
|
retryer = self._retry_strategy.make_retryer()
|
|
@@ -274,12 +319,12 @@ class SumoClient:
|
|
|
274
319
|
headers.update(self.auth.get_authorization())
|
|
275
320
|
|
|
276
321
|
def _put():
|
|
277
|
-
return
|
|
322
|
+
return self._client.put(
|
|
278
323
|
f"{self.base_url}{path}",
|
|
279
324
|
content=blob,
|
|
280
325
|
json=json,
|
|
281
326
|
headers=headers,
|
|
282
|
-
timeout=
|
|
327
|
+
timeout=self._timeout,
|
|
283
328
|
)
|
|
284
329
|
|
|
285
330
|
retryer = self._retry_strategy.make_retryer()
|
|
@@ -313,11 +358,11 @@ class SumoClient:
|
|
|
313
358
|
headers.update(self.auth.get_authorization())
|
|
314
359
|
|
|
315
360
|
def _delete():
|
|
316
|
-
return
|
|
361
|
+
return self._client.delete(
|
|
317
362
|
f"{self.base_url}{path}",
|
|
318
363
|
headers=headers,
|
|
319
364
|
params=params,
|
|
320
|
-
timeout=
|
|
365
|
+
timeout=self._timeout,
|
|
321
366
|
)
|
|
322
367
|
|
|
323
368
|
retryer = self._retry_strategy.make_retryer()
|
|
@@ -378,13 +423,12 @@ class SumoClient:
|
|
|
378
423
|
headers.update(self.auth.get_authorization())
|
|
379
424
|
|
|
380
425
|
async def _get():
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
)
|
|
426
|
+
return await self._async_client.get(
|
|
427
|
+
f"{self.base_url}{path}",
|
|
428
|
+
params=params,
|
|
429
|
+
headers=headers,
|
|
430
|
+
timeout=self._timeout,
|
|
431
|
+
)
|
|
388
432
|
|
|
389
433
|
retryer = self._retry_strategy.make_retryer_async()
|
|
390
434
|
|
|
@@ -453,15 +497,14 @@ class SumoClient:
|
|
|
453
497
|
headers.update(self.auth.get_authorization())
|
|
454
498
|
|
|
455
499
|
async def _post():
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
)
|
|
500
|
+
return await self._async_client.post(
|
|
501
|
+
url=f"{self.base_url}{path}",
|
|
502
|
+
content=blob,
|
|
503
|
+
json=json,
|
|
504
|
+
headers=headers,
|
|
505
|
+
params=params,
|
|
506
|
+
timeout=self._timeout,
|
|
507
|
+
)
|
|
465
508
|
|
|
466
509
|
retryer = self._retry_strategy.make_retryer_async()
|
|
467
510
|
|
|
@@ -501,14 +544,13 @@ class SumoClient:
|
|
|
501
544
|
headers.update(self.auth.get_authorization())
|
|
502
545
|
|
|
503
546
|
async def _put():
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
)
|
|
547
|
+
return await self._async_client.put(
|
|
548
|
+
url=f"{self.base_url}{path}",
|
|
549
|
+
content=blob,
|
|
550
|
+
json=json,
|
|
551
|
+
headers=headers,
|
|
552
|
+
timeout=self._timeout,
|
|
553
|
+
)
|
|
512
554
|
|
|
513
555
|
retryer = self._retry_strategy.make_retryer_async()
|
|
514
556
|
|
|
@@ -541,13 +583,12 @@ class SumoClient:
|
|
|
541
583
|
headers.update(self.auth.get_authorization())
|
|
542
584
|
|
|
543
585
|
async def _delete():
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
)
|
|
586
|
+
return await self._async_client.delete(
|
|
587
|
+
url=f"{self.base_url}{path}",
|
|
588
|
+
headers=headers,
|
|
589
|
+
params=params,
|
|
590
|
+
timeout=self._timeout,
|
|
591
|
+
)
|
|
551
592
|
|
|
552
593
|
retryer = self._retry_strategy.make_retryer_async()
|
|
553
594
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sumo-wrapper-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.10
|
|
4
4
|
Summary: Python wrapper for the Sumo API
|
|
5
5
|
Author: Equinor
|
|
6
6
|
License: Apache License
|
|
@@ -211,174 +211,30 @@ Classifier: Programming Language :: Python
|
|
|
211
211
|
Requires-Python: >=3.8
|
|
212
212
|
Description-Content-Type: text/markdown
|
|
213
213
|
License-File: LICENSE
|
|
214
|
-
Requires-Dist: msal
|
|
215
|
-
Requires-Dist: msal-extensions
|
|
216
|
-
Requires-Dist: pyjwt
|
|
217
|
-
Requires-Dist: httpx
|
|
218
|
-
Requires-Dist: tenacity
|
|
219
|
-
Requires-Dist: azure-identity
|
|
214
|
+
Requires-Dist: msal>=1.20.0
|
|
215
|
+
Requires-Dist: msal-extensions>=1.0.0
|
|
216
|
+
Requires-Dist: pyjwt>=2.4.0
|
|
217
|
+
Requires-Dist: httpx>=0.24.1
|
|
218
|
+
Requires-Dist: tenacity!=8.4.0,>=8.2.2
|
|
219
|
+
Requires-Dist: azure-identity>=1.13.0
|
|
220
220
|
Provides-Extra: docs
|
|
221
|
-
Requires-Dist: sphinx
|
|
222
|
-
Requires-Dist: sphinx-rtd-theme
|
|
221
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
222
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
223
|
+
Requires-Dist: autoapi; extra == "docs"
|
|
224
|
+
Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
|
|
225
|
+
Requires-Dist: sphinxcontrib-apidoc; extra == "docs"
|
|
223
226
|
Provides-Extra: test
|
|
224
|
-
Requires-Dist: pytest
|
|
225
|
-
Requires-Dist: PyYAML
|
|
227
|
+
Requires-Dist: pytest; extra == "test"
|
|
228
|
+
Requires-Dist: PyYAML; extra == "test"
|
|
226
229
|
|
|
227
230
|
# sumo-wrapper-python
|
|
228
231
|
|
|
229
|
-
|
|
232
|
+
[](https://sumo-wrapper-python.readthedocs.io/en/latest/?badge=latest)
|
|
233
|
+
[](https://scm-compliance-api.radix.equinor.com/repos/equinor/sumo-wrapper-python/badge)
|
|
230
234
|
|
|
231
|
-
|
|
235
|
+
## Documentation and guidelines
|
|
236
|
+
[sumo-wrapper-python documentation](https://sumo-wrapper-python.readthedocs.io/en/latest/)
|
|
232
237
|
|
|
233
|
-
##
|
|
238
|
+
## Contribute
|
|
239
|
+
[Contribution guidelines](./CONTRIBUTING.md)
|
|
234
240
|
|
|
235
|
-
pip install sumo-wrapper-python
|
|
236
|
-
|
|
237
|
-
For internal Equinor users, this package is available through the Komodo
|
|
238
|
-
distribution.
|
|
239
|
-
|
|
240
|
-
# Table of contents
|
|
241
|
-
|
|
242
|
-
- [sumo-wrapper-python](#sumo-wrapper-python)
|
|
243
|
-
- [Install:](#install)
|
|
244
|
-
- [Table of contents](#table-of-contents)
|
|
245
|
-
- [SumoClient](#sumoclient)
|
|
246
|
-
- [Initialization](#initialization)
|
|
247
|
-
- [Parameters](#parameters)
|
|
248
|
-
- [`token` logic](#token-logic)
|
|
249
|
-
- [Methods](#methods)
|
|
250
|
-
- [get(path, \*\*params)](#getpath-params)
|
|
251
|
-
- [post(path, json, blob, params)](#postpath-json-blob-params)
|
|
252
|
-
- [put(path, json, blob)](#putpath-json-blob)
|
|
253
|
-
- [delete(path)](#deletepath)
|
|
254
|
-
- [Async methods](#async-methods)
|
|
255
|
-
|
|
256
|
-
# SumoClient
|
|
257
|
-
|
|
258
|
-
A thin wrapper class for the Sumo API.
|
|
259
|
-
|
|
260
|
-
### Initialization
|
|
261
|
-
|
|
262
|
-
```python
|
|
263
|
-
from sumo.wrapper import SumoClient
|
|
264
|
-
|
|
265
|
-
sumo = SumoClient(env="dev")
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### Parameters
|
|
269
|
-
|
|
270
|
-
```python
|
|
271
|
-
class SumoClient:
|
|
272
|
-
def __init__(
|
|
273
|
-
self,
|
|
274
|
-
env:str,
|
|
275
|
-
token:str=None,
|
|
276
|
-
interactive:bool=False,
|
|
277
|
-
verbosity:str="CRITICAL"
|
|
278
|
-
):
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
- `env`: sumo environment
|
|
282
|
-
- `token`: bearer token or refresh token
|
|
283
|
-
- `interactive`: use interactive flow when authenticating
|
|
284
|
-
- `verbosity`: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
|
|
285
|
-
|
|
286
|
-
###### `token` logic
|
|
287
|
-
|
|
288
|
-
If an access token is provided in the `token` parameter, it will be used as long
|
|
289
|
-
as it's valid. An error will be raised when it expires.
|
|
290
|
-
|
|
291
|
-
If we are unable to decode the provided `token` as a JWT, we treat it as a
|
|
292
|
-
refresh token and attempt to use it to retrieve an access token.
|
|
293
|
-
|
|
294
|
-
If no `token` is provided, an authentication code flow/interactive flow is
|
|
295
|
-
triggered to retrieve a token.
|
|
296
|
-
|
|
297
|
-
## Methods
|
|
298
|
-
|
|
299
|
-
`SumoClient` has one method for each HTTP-method that is used in the sumo-core
|
|
300
|
-
API. See examples of how to use these methods below.
|
|
301
|
-
|
|
302
|
-
All methods accepts a path argument. Path parameters can be interpolated into
|
|
303
|
-
the path string. Example:
|
|
304
|
-
|
|
305
|
-
```python
|
|
306
|
-
object_id = "1234"
|
|
307
|
-
|
|
308
|
-
# GET/objects('{obejctid}')
|
|
309
|
-
sumo.get(f"/objects('{object_id}')")
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### get(path, \*\*params)
|
|
313
|
-
|
|
314
|
-
Performs a GET-request to sumo-core. Accepts query parameters as keyword
|
|
315
|
-
arguments.
|
|
316
|
-
|
|
317
|
-
```python
|
|
318
|
-
# Retrieve userdata
|
|
319
|
-
user_data = sumo.get("/userdata")
|
|
320
|
-
|
|
321
|
-
# Search for objects
|
|
322
|
-
results = sumo.get("/search",
|
|
323
|
-
query="class:surface",
|
|
324
|
-
size:3,
|
|
325
|
-
select=["_id"]
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
# Get object by id
|
|
329
|
-
object_id = "159405ba-0046-b321-55ce-542f383ba5c7"
|
|
330
|
-
|
|
331
|
-
obj = sumo.get(f"/objects('{object_id}')")
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### post(path, json, blob, params)
|
|
335
|
-
|
|
336
|
-
Performs a POST-request to sumo-core. Accepts json and blob, but not both at the
|
|
337
|
-
same time.
|
|
338
|
-
|
|
339
|
-
```python
|
|
340
|
-
# Upload new parent object
|
|
341
|
-
parent_object = sumo.post("/objects", json=parent_meta_data)
|
|
342
|
-
|
|
343
|
-
# Upload child object
|
|
344
|
-
parent_id = parent_object["_id"]
|
|
345
|
-
|
|
346
|
-
child_object = sumo.post(f"/objects('{parent_id}')", json=child_meta_data)
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### put(path, json, blob)
|
|
350
|
-
|
|
351
|
-
Performs a PUT-request to sumo-core. Accepts json and blob, but not both at the
|
|
352
|
-
same time.
|
|
353
|
-
|
|
354
|
-
```python
|
|
355
|
-
# Upload blob to child object
|
|
356
|
-
child_id = child_object["_id"]
|
|
357
|
-
|
|
358
|
-
sumo.put(f"/objects('{child_id}')/blob", blob=blob)
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### delete(path)
|
|
362
|
-
|
|
363
|
-
Performs a DELETE-request to sumo-core.
|
|
364
|
-
|
|
365
|
-
```python
|
|
366
|
-
# Delete blob
|
|
367
|
-
sumo.delete(f"/objects('{child_id}')/blob")
|
|
368
|
-
|
|
369
|
-
# Delete child object
|
|
370
|
-
sumo.delete(f"/objects('{child_id}')")
|
|
371
|
-
|
|
372
|
-
# Delete parent object
|
|
373
|
-
sumo.delete(f"/objects('{parent_id}')")
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
## Async methods
|
|
377
|
-
|
|
378
|
-
`SumoClient` also has *async* alternatives `get_async`, `post_async`, `put_async` and `delete_async`.
|
|
379
|
-
These accept the same parameters as their synchronous counterparts, but have to be *awaited*.
|
|
380
|
-
|
|
381
|
-
```python
|
|
382
|
-
# Retrieve userdata
|
|
383
|
-
user_data = await sumo.get_async("/userdata")
|
|
384
|
-
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
sumo/__init__.py,sha256=ftS-xRPSH-vU7fIHlnZQaCTWbNvs4owJivNW65kzsIM,85
|
|
2
|
+
sumo/wrapper/__init__.py,sha256=wW7Bjl0btCgK9_exGVrfJsnFmTOybIdyeCudz0LueQM,235
|
|
3
|
+
sumo/wrapper/_auth_provider.py,sha256=PGLOnQmOC77caKfEZaSNLmETrN48PNBZtatiJnzrzOI,13618
|
|
4
|
+
sumo/wrapper/_blob_client.py,sha256=SyfyFZ1hHVWKU4lmgUqSjl5TaK_OJNQ__0wDETrp-ag,1623
|
|
5
|
+
sumo/wrapper/_decorators.py,sha256=3IEi6GXVkkDACHoo8dOeDoBtZh5TlJ6Tw0qlpOVHqLQ,806
|
|
6
|
+
sumo/wrapper/_logging.py,sha256=lnhjn6oQna33jZpzeZ7IeBya2uKNfrzXr_C3nw7txo0,965
|
|
7
|
+
sumo/wrapper/_retry_strategy.py,sha256=9PjOT0hOhLbCHrJu02thUkGdY39NpB2rqa_XUODoguw,2415
|
|
8
|
+
sumo/wrapper/_version.py,sha256=6mwHFYvRmuR_NDSMZ-tZ0hVgwAgYaLihPiAGrb850ks,413
|
|
9
|
+
sumo/wrapper/config.py,sha256=6t7qqjrrmd11m4VMlRryiMYw2JDU_R51305woAP1TAs,865
|
|
10
|
+
sumo/wrapper/login.py,sha256=IlENRNdSc2UPmGdrcxjziovMVYpV40qQSnAuDy5LKh4,2375
|
|
11
|
+
sumo/wrapper/sumo_client.py,sha256=Td2K1MkSP_ZDD2AIWcMmvFrwugtBWdDdme8h3VWSGJk,15707
|
|
12
|
+
sumo_wrapper_python-1.0.10.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
13
|
+
sumo_wrapper_python-1.0.10.dist-info/METADATA,sha256=PR41RcBbNUOYGFvT1KuhgTCqqRpdHVi2hJlOxxsINDI,14455
|
|
14
|
+
sumo_wrapper_python-1.0.10.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
15
|
+
sumo_wrapper_python-1.0.10.dist-info/entry_points.txt,sha256=V_vGky2C3He5vohJAxnBdvpt_fqfUDFj5irUm9HtoFc,55
|
|
16
|
+
sumo_wrapper_python-1.0.10.dist-info/top_level.txt,sha256=rLbKyH9rWgCj3PoLeR7fvC5X8vCaUc5LF8-Y_GBWZL0,5
|
|
17
|
+
sumo_wrapper_python-1.0.10.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
sumo/__init__.py,sha256=ftS-xRPSH-vU7fIHlnZQaCTWbNvs4owJivNW65kzsIM,85
|
|
2
|
-
sumo/wrapper/__init__.py,sha256=wW7Bjl0btCgK9_exGVrfJsnFmTOybIdyeCudz0LueQM,235
|
|
3
|
-
sumo/wrapper/_auth_provider.py,sha256=jxhLxaImD0PMKZDy6YNcBOn-ogF7G5F5Fvh4oDwW40g,12023
|
|
4
|
-
sumo/wrapper/_blob_client.py,sha256=y15_TThJ4ENFtEAF37mgju6Htc8D3ZyzcYCqsYQZo7Y,1495
|
|
5
|
-
sumo/wrapper/_decorators.py,sha256=FSorPaTEBpq2N0qrqfdTt0jMToe_0PpOYlsL6vZSbIg,728
|
|
6
|
-
sumo/wrapper/_logging.py,sha256=XgXcOAEoVw0aqij7V7EPTiP3ALqa8xNDgJgKyWOxeok,838
|
|
7
|
-
sumo/wrapper/_retry_strategy.py,sha256=9PjOT0hOhLbCHrJu02thUkGdY39NpB2rqa_XUODoguw,2415
|
|
8
|
-
sumo/wrapper/_version.py,sha256=vy0P95Si-KzUl6NsixjrWEHfr5xcmkA_oO_S3bQRmvE,411
|
|
9
|
-
sumo/wrapper/config.py,sha256=6t7qqjrrmd11m4VMlRryiMYw2JDU_R51305woAP1TAs,865
|
|
10
|
-
sumo/wrapper/login.py,sha256=HB_USKw6pR4j5EyNevaFVJGQjQ3t_8F__ZBaR29XLf4,1610
|
|
11
|
-
sumo/wrapper/sumo_client.py,sha256=q5k6qP_oSgzmfFf1Bc2zw8L2KK85GFXg99Ro8GT3GZA,14741
|
|
12
|
-
sumo_wrapper_python-1.0.8.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
13
|
-
sumo_wrapper_python-1.0.8.dist-info/METADATA,sha256=CVL2k02sgRkHOJk1EvXcED6XHZbcxS75ZVrXejy9mB8,17407
|
|
14
|
-
sumo_wrapper_python-1.0.8.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
15
|
-
sumo_wrapper_python-1.0.8.dist-info/entry_points.txt,sha256=V_vGky2C3He5vohJAxnBdvpt_fqfUDFj5irUm9HtoFc,55
|
|
16
|
-
sumo_wrapper_python-1.0.8.dist-info/top_level.txt,sha256=rLbKyH9rWgCj3PoLeR7fvC5X8vCaUc5LF8-Y_GBWZL0,5
|
|
17
|
-
sumo_wrapper_python-1.0.8.dist-info/RECORD,,
|
|
File without changes
|
{sumo_wrapper_python-1.0.8.dist-info → sumo_wrapper_python-1.0.10.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|