sumo-wrapper-python 1.0.25__py3-none-any.whl → 1.0.27__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.

@@ -1,10 +1,13 @@
1
1
  import errno
2
2
  import json
3
3
  import os
4
+ import platform
4
5
  import stat
5
6
  import sys
6
7
  import time
7
8
  from datetime import datetime, timedelta, timezone
9
+ from pathlib import Path
10
+ from typing import Dict
8
11
  from urllib.parse import parse_qs
9
12
 
10
13
  import jwt
@@ -74,10 +77,10 @@ class AuthProvider:
74
77
  # ELSE
75
78
  return result["access_token"]
76
79
 
77
- def get_authorization(self):
80
+ def get_authorization(self) -> Dict:
78
81
  token = self.get_token()
79
82
  if token is None:
80
- return ""
83
+ return {}
81
84
 
82
85
  return {"Authorization": "Bearer " + token}
83
86
 
@@ -99,6 +102,13 @@ class AuthProvider:
99
102
  pass
100
103
 
101
104
 
105
+ class AuthProviderNone(AuthProvider):
106
+ def get_token(self):
107
+ raise Exception("No valid authorization provider found.")
108
+
109
+ pass
110
+
111
+
102
112
  class AuthProviderSilent(AuthProvider):
103
113
  def __init__(self, client_id, authority, resource_id):
104
114
  super().__init__(resource_id)
@@ -421,7 +431,7 @@ def get_auth_provider(
421
431
  refresh_token=None,
422
432
  devicecode=False,
423
433
  case_uuid=None,
424
- ):
434
+ ) -> AuthProvider:
425
435
  if refresh_token:
426
436
  return AuthProviderRefreshToken(
427
437
  refresh_token, client_id, authority, resource_id
@@ -441,6 +451,18 @@ def get_auth_provider(
441
451
  pass
442
452
  # ELSE
443
453
  if interactive:
454
+ lockfile_path = Path.home() / ".config/chromium/SingletonLock"
455
+
456
+ if Path(lockfile_path).is_symlink() and not str(
457
+ Path(lockfile_path).resolve()
458
+ ).__contains__(platform.node()):
459
+ # https://github.com/equinor/sumo-wrapper-python/issues/193
460
+ print(
461
+ "\n\n\033[1mDetected chromium lockfile for different node; using firefox to authenticate.\033[0m"
462
+ )
463
+ os.environ["BROWSER"] = "firefox"
464
+ pass
465
+
444
466
  return AuthProviderInteractive(client_id, authority, resource_id)
445
467
  # ELSE
446
468
  if devicecode:
@@ -458,6 +480,8 @@ def get_auth_provider(
458
480
  ]
459
481
  ):
460
482
  return AuthProviderManaged(resource_id)
483
+ # ELSE
484
+ return AuthProviderNone(resource_id)
461
485
 
462
486
 
463
487
  def cleanup_shared_keys():
@@ -467,7 +491,7 @@ def cleanup_shared_keys():
467
491
  for f in os.listdir(tokendir):
468
492
  ff = os.path.join(tokendir, f)
469
493
  if os.path.isfile(ff):
470
- (name, ext) = os.path.splitext(ff)
494
+ (_, ext) = os.path.splitext(ff)
471
495
  if ext.lower() == ".sharedkey":
472
496
  try:
473
497
  with open(ff, "r") as file:
sumo/wrapper/_logging.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from datetime import datetime
2
+ from datetime import datetime, timezone
3
3
 
4
4
 
5
5
  class LogHandlerSumo(logging.Handler):
@@ -11,8 +11,8 @@ class LogHandlerSumo(logging.Handler):
11
11
  def emit(self, record):
12
12
  try:
13
13
  dt = (
14
- datetime.now(datetime.timezone.utc)
15
- .replace(microsecond=0)
14
+ datetime.now(timezone.utc)
15
+ .replace(microsecond=0, tzinfo=None)
16
16
  .isoformat()
17
17
  + "Z"
18
18
  )
@@ -44,7 +44,7 @@ class RetryStrategy:
44
44
  self._exp_base = exp_base
45
45
  return
46
46
 
47
- def make_retryer(self):
47
+ def make_retryer(self) -> tn.Retrying:
48
48
  return tn.Retrying(
49
49
  stop=tn.stop_after_attempt(self._stop_after),
50
50
  retry=(
@@ -63,7 +63,7 @@ class RetryStrategy:
63
63
  before_sleep=_log_retry_info,
64
64
  )
65
65
 
66
- def make_retryer_async(self):
66
+ def make_retryer_async(self) -> tn.AsyncRetrying:
67
67
  return tn.AsyncRetrying(
68
68
  stop=tn.stop_after_attempt(self._stop_after),
69
69
  retry=(
sumo/wrapper/_version.py CHANGED
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '1.0.25'
16
- __version_tuple__ = version_tuple = (1, 0, 25)
20
+ __version__ = version = '1.0.27'
21
+ __version_tuple__ = version_tuple = (1, 0, 27)
sumo/wrapper/login.py CHANGED
@@ -1,7 +1,5 @@
1
1
  import logging
2
- import platform
3
2
  from argparse import ArgumentParser
4
- from pathlib import Path
5
3
 
6
4
  from sumo.wrapper import SumoClient
7
5
 
@@ -69,16 +67,6 @@ def main():
69
67
  if mode != "silent":
70
68
  print("Login to Sumo environment: " + env)
71
69
 
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
81
-
82
70
  sumo = SumoClient(
83
71
  env,
84
72
  interactive=is_interactive,
@@ -2,6 +2,8 @@ import asyncio
2
2
  import contextlib
3
3
  import logging
4
4
  import re
5
+ import time
6
+ from typing import Dict, Optional, Tuple
5
7
 
6
8
  import httpx
7
9
  import jwt
@@ -24,10 +26,13 @@ DEFAULT_TIMEOUT = httpx.Timeout(30.0)
24
26
  class SumoClient:
25
27
  """Authenticate and perform requests to the Sumo API."""
26
28
 
29
+ _client: httpx.Client
30
+ _async_client: httpx.AsyncClient
31
+
27
32
  def __init__(
28
33
  self,
29
34
  env: str,
30
- token: str = None,
35
+ token: Optional[str] = None,
31
36
  interactive: bool = False,
32
37
  devicecode: bool = False,
33
38
  verbosity: str = "CRITICAL",
@@ -118,26 +123,23 @@ class SumoClient:
118
123
  def __enter__(self):
119
124
  return self
120
125
 
121
- def __exit__(self, exc_type, exc_value, traceback):
126
+ def __exit__(self, *_):
122
127
  if not self._borrowed_client:
123
128
  self._client.close()
124
- self._client = None
125
129
  return False
126
130
 
127
131
  async def __aenter__(self):
128
132
  return self
129
133
 
130
- async def __aexit__(self, exc_type, exc_value, traceback):
134
+ async def __aexit__(self, *_):
131
135
  if not self._borrowed_async_client:
132
136
  await self._async_client.aclose()
133
- self._async_client = None
134
137
  return False
135
138
 
136
139
  def __del__(self):
137
140
  if self._client is not None and not self._borrowed_client:
138
141
  self._client.close()
139
142
  pass
140
- self._client = None
141
143
  if self._async_client is not None and not self._borrowed_async_client:
142
144
 
143
145
  async def closeit(client):
@@ -150,7 +152,6 @@ class SumoClient:
150
152
  except RuntimeError:
151
153
  pass
152
154
  pass
153
- self._async_client = None
154
155
 
155
156
  def authenticate(self):
156
157
  if self.auth is None:
@@ -185,7 +186,7 @@ class SumoClient:
185
186
  )
186
187
 
187
188
  @raise_for_status
188
- def get(self, path: str, params: dict = None) -> dict:
189
+ def get(self, path: str, params: Optional[Dict] = None) -> httpx.Response:
189
190
  """Performs a GET-request to the Sumo API.
190
191
 
191
192
  Args:
@@ -246,9 +247,9 @@ class SumoClient:
246
247
  def post(
247
248
  self,
248
249
  path: str,
249
- blob: bytes = None,
250
- json: dict = None,
251
- params: dict = None,
250
+ blob: Optional[bytes] = None,
251
+ json: Optional[dict] = None,
252
+ params: Optional[dict] = None,
252
253
  ) -> httpx.Response:
253
254
  """Performs a POST-request to the Sumo API.
254
255
 
@@ -319,7 +320,10 @@ class SumoClient:
319
320
 
320
321
  @raise_for_status
321
322
  def put(
322
- self, path: str, blob: bytes = None, json: dict = None
323
+ self,
324
+ path: str,
325
+ blob: Optional[bytes] = None,
326
+ json: Optional[dict] = None,
323
327
  ) -> httpx.Response:
324
328
  """Performs a PUT-request to the Sumo API.
325
329
 
@@ -364,7 +368,9 @@ class SumoClient:
364
368
  return retryer(_put)
365
369
 
366
370
  @raise_for_status
367
- def delete(self, path: str, params: dict = None) -> dict:
371
+ def delete(
372
+ self, path: str, params: Optional[dict] = None
373
+ ) -> httpx.Response:
368
374
  """Performs a DELETE-request to the Sumo API.
369
375
 
370
376
  Args:
@@ -372,7 +378,7 @@ class SumoClient:
372
378
  params: query parameters, as dictionary
373
379
 
374
380
  Returns:
375
- Sumo JSON resposne as a dictionary
381
+ Sumo JSON response as a dictionary
376
382
 
377
383
  Examples:
378
384
  Deleting object::
@@ -401,6 +407,45 @@ class SumoClient:
401
407
 
402
408
  return retryer(_delete)
403
409
 
410
+ def _get_retry_details(self, response_in) -> Tuple[str, int]:
411
+ assert response_in.status_code == 202, (
412
+ "Incorrect status code; expcted 202"
413
+ )
414
+ headers = response_in.headers
415
+ location: str = headers.get("location")
416
+ assert location is not None, "Missing header: Location"
417
+ assert location.startswith(self.base_url)
418
+ retry_after = headers.get("retry-after")
419
+ assert retry_after is not None, "Missing header: Retry-After"
420
+ location = location[len(self.base_url) :]
421
+ retry_after = int(retry_after)
422
+ return location, retry_after
423
+
424
+ def poll(
425
+ self, response_in: httpx.Response, timeout=None
426
+ ) -> httpx.Response:
427
+ """Poll a specific endpoint until a result is obtained.
428
+
429
+ Args:
430
+ response_in: httpx.Response from a previous request, with 'location' and 'retry-after' headers.
431
+
432
+ Returns:
433
+ A new httpx.response object.
434
+ """
435
+ location, retry_after = self._get_retry_details(response_in)
436
+ expiry = time.time() + timeout if timeout is not None else None
437
+ while True:
438
+ time.sleep(retry_after)
439
+ response = self.get(location)
440
+ if response.status_code != 202:
441
+ return response
442
+ if expiry is not None and time.time() > expiry:
443
+ raise httpx.TimeoutException(
444
+ "No response within specified timeout."
445
+ )
446
+ location, retry_after = self._get_retry_details(response)
447
+ pass
448
+
404
449
  def getLogger(self, name):
405
450
  """Gets a logger object that sends log objects into the message_log
406
451
  index for the Sumo instance.
@@ -454,7 +499,9 @@ class SumoClient:
454
499
  return self
455
500
 
456
501
  @raise_for_status_async
457
- async def get_async(self, path: str, params: dict = None):
502
+ async def get_async(
503
+ self, path: str, params: Optional[dict] = None
504
+ ) -> httpx.Response:
458
505
  """Performs an async GET-request to the Sumo API.
459
506
 
460
507
  Args:
@@ -515,9 +562,9 @@ class SumoClient:
515
562
  async def post_async(
516
563
  self,
517
564
  path: str,
518
- blob: bytes = None,
519
- json: dict = None,
520
- params: dict = None,
565
+ blob: Optional[bytes] = None,
566
+ json: Optional[dict] = None,
567
+ params: Optional[dict] = None,
521
568
  ) -> httpx.Response:
522
569
  """Performs an async POST-request to the Sumo API.
523
570
 
@@ -589,7 +636,10 @@ class SumoClient:
589
636
 
590
637
  @raise_for_status_async
591
638
  async def put_async(
592
- self, path: str, blob: bytes = None, json: dict = None
639
+ self,
640
+ path: str,
641
+ blob: Optional[bytes] = None,
642
+ json: Optional[dict] = None,
593
643
  ) -> httpx.Response:
594
644
  """Performs an async PUT-request to the Sumo API.
595
645
 
@@ -634,7 +684,9 @@ class SumoClient:
634
684
  return await retryer(_put)
635
685
 
636
686
  @raise_for_status_async
637
- async def delete_async(self, path: str, params: dict = None) -> dict:
687
+ async def delete_async(
688
+ self, path: str, params: Optional[dict] = None
689
+ ) -> httpx.Response:
638
690
  """Performs an async DELETE-request to the Sumo API.
639
691
 
640
692
  Args:
@@ -642,7 +694,7 @@ class SumoClient:
642
694
  params: query parameters, as dictionary
643
695
 
644
696
  Returns:
645
- Sumo JSON resposne as a dictionary
697
+ Sumo JSON response as a dictionary
646
698
 
647
699
  Examples:
648
700
  Deleting object::
@@ -670,3 +722,28 @@ class SumoClient:
670
722
  retryer = self._retry_strategy.make_retryer_async()
671
723
 
672
724
  return await retryer(_delete)
725
+
726
+ async def poll_async(
727
+ self, response_in: httpx.Response, timeout=None
728
+ ) -> httpx.Response:
729
+ """Poll a specific endpoint until a result is obtained.
730
+
731
+ Args:
732
+ response_in: httpx.Response from a previous request, with 'location' and 'retry-after' headers.
733
+
734
+ Returns:
735
+ A new httpx.response object.
736
+ """
737
+ location, retry_after = self._get_retry_details(response_in)
738
+ expiry = time.time() + timeout if timeout is not None else None
739
+ while True:
740
+ await asyncio.sleep(retry_after)
741
+ response = await self.get_async(location)
742
+ if response.status_code != 202:
743
+ return response
744
+ if expiry is not None and time.time() > expiry:
745
+ raise httpx.TimeoutException(
746
+ "No response within specified timeout."
747
+ )
748
+ location, retry_after = self._get_retry_details(response)
749
+ pass
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: sumo-wrapper-python
3
- Version: 1.0.25
3
+ Version: 1.0.27
4
4
  Summary: Python wrapper for the Sumo API
5
5
  Author: Equinor
6
6
  License: Apache License
@@ -229,6 +229,7 @@ Requires-Dist: sphinxcontrib-apidoc; extra == "docs"
229
229
  Provides-Extra: dev
230
230
  Requires-Dist: ruff; extra == "dev"
231
231
  Requires-Dist: pre-commit; extra == "dev"
232
+ Dynamic: license-file
232
233
 
233
234
  # sumo-wrapper-python
234
235
 
@@ -0,0 +1,17 @@
1
+ sumo/__init__.py,sha256=ftS-xRPSH-vU7fIHlnZQaCTWbNvs4owJivNW65kzsIM,85
2
+ sumo/wrapper/__init__.py,sha256=JK6iMAy2Fdrc7iaIHTn60fHPdRvsgNjZwWD6DJtGUYQ,235
3
+ sumo/wrapper/_auth_provider.py,sha256=et6rTh24u0-I5qnT0pKubVEfE4h4gNOSSLXzd0EMHfE,16345
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=WAGrjPmK2_JC9w1IxT5onq1iEAreEsyzmm95pXBe0dA,1074
7
+ sumo/wrapper/_retry_strategy.py,sha256=JSXy-ki5vWI_N4D52LpK9EoAmPrnT3H6OG0aBLsJRMk,2525
8
+ sumo/wrapper/_version.py,sha256=CjktiUF4FYmIi8u7-B82u38sKQpbDIKiVFrHn5do4dc,513
9
+ sumo/wrapper/config.py,sha256=6t7qqjrrmd11m4VMlRryiMYw2JDU_R51305woAP1TAs,865
10
+ sumo/wrapper/login.py,sha256=Bp5IgSrsgjWcgOQIQIQ5aJrCBWdK3khS-CnTmB63Rjk,1951
11
+ sumo/wrapper/sumo_client.py,sha256=nwi6hScFzkO1DAqExMDI6LBkPylFEQak9QyzDseNl70,21102
12
+ sumo_wrapper_python-1.0.27.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
13
+ sumo_wrapper_python-1.0.27.dist-info/METADATA,sha256=3vp54Rrgo5h6m0bY88HcoDKx1YajIRJYA_nP5uq-SAo,14615
14
+ sumo_wrapper_python-1.0.27.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
15
+ sumo_wrapper_python-1.0.27.dist-info/entry_points.txt,sha256=V_vGky2C3He5vohJAxnBdvpt_fqfUDFj5irUm9HtoFc,55
16
+ sumo_wrapper_python-1.0.27.dist-info/top_level.txt,sha256=rLbKyH9rWgCj3PoLeR7fvC5X8vCaUc5LF8-Y_GBWZL0,5
17
+ sumo_wrapper_python-1.0.27.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,17 +0,0 @@
1
- sumo/__init__.py,sha256=ftS-xRPSH-vU7fIHlnZQaCTWbNvs4owJivNW65kzsIM,85
2
- sumo/wrapper/__init__.py,sha256=JK6iMAy2Fdrc7iaIHTn60fHPdRvsgNjZwWD6DJtGUYQ,235
3
- sumo/wrapper/_auth_provider.py,sha256=kzX83bFB0LSZGmeOk7X25n3BZRSy2feuTvZRBQHaga0,15571
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=jOtMpiVJcF38QBM4ER1VDdHom777L5MkfomtOVHXgu8,1060
7
- sumo/wrapper/_retry_strategy.py,sha256=m4mK-ZQDr0CSOZGN9ZleZyRSR07PjUjAis3WXXR4jwk,2490
8
- sumo/wrapper/_version.py,sha256=AeStOcCZfaV4GG2vuN6yIbdKgnY7ci5PD3hzovEL604,413
9
- sumo/wrapper/config.py,sha256=6t7qqjrrmd11m4VMlRryiMYw2JDU_R51305woAP1TAs,865
10
- sumo/wrapper/login.py,sha256=PGYhKnrwV4NorNbwnWj-5LDvCCIKgn-pnvCTeCDvqXA,2375
11
- sumo/wrapper/sumo_client.py,sha256=oeyYUB-sEDXrYcHkoQGGqlJYaB3y9GZ-zuO4-_iiP_U,18342
12
- sumo_wrapper_python-1.0.25.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
13
- sumo_wrapper_python-1.0.25.dist-info/METADATA,sha256=HQD1jlMEdK2tZvIXg6PSztf8W7QhMHCeAmN4DxEPo2c,14593
14
- sumo_wrapper_python-1.0.25.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
15
- sumo_wrapper_python-1.0.25.dist-info/entry_points.txt,sha256=V_vGky2C3He5vohJAxnBdvpt_fqfUDFj5irUm9HtoFc,55
16
- sumo_wrapper_python-1.0.25.dist-info/top_level.txt,sha256=rLbKyH9rWgCj3PoLeR7fvC5X8vCaUc5LF8-Y_GBWZL0,5
17
- sumo_wrapper_python-1.0.25.dist-info/RECORD,,