sumo-wrapper-python 1.0.9__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.

@@ -1,5 +1,3 @@
1
- import platform
2
- from pathlib import Path
3
1
  import msal
4
2
  import os
5
3
  from datetime import datetime, timedelta
@@ -71,6 +69,18 @@ class AuthProvider:
71
69
  pass
72
70
 
73
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
+
74
84
  class AuthProviderAccessToken(AuthProvider):
75
85
  def __init__(self, access_token):
76
86
  self._access_token = access_token
@@ -396,6 +406,11 @@ def get_auth_provider(
396
406
  if os.path.exists(get_token_path(resource_id, ".sharedkey")):
397
407
  return AuthProviderSumoToken(resource_id)
398
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
399
414
  if interactive:
400
415
  return AuthProviderInteractive(client_id, authority, resource_id)
401
416
  # ELSE
@@ -416,12 +431,3 @@ def get_auth_provider(
416
431
  ]
417
432
  ):
418
433
  return AuthProviderManaged(resource_id)
419
- # ELSE
420
- lockfile_path = Path.home() / ".config/chromium/SingletonLock"
421
- if Path(lockfile_path).is_symlink() and not str(
422
- Path(lockfile_path).resolve()
423
- ).__contains__(platform.node()):
424
- # https://github.com/equinor/sumo-wrapper-python/issues/193
425
- return AuthProviderDeviceCode(client_id, authority, resource_id)
426
- # ELSE
427
- return AuthProviderInteractive(client_id, authority, resource_id)
@@ -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=RetryStrategy()):
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 httpx.put(url, content=blob, headers=headers)
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
- async with httpx.AsyncClient() as client:
55
- return await client.put(url=url, content=blob, headers=headers)
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/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.0.9'
16
- __version_tuple__ = version_tuple = (1, 0, 9)
15
+ __version__ = version = '1.0.10'
16
+ __version_tuple__ = version_tuple = (1, 0, 10)
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
- "-i",
33
- "--interactive",
34
- dest="interactive",
35
- action="store_true",
36
- default=False,
37
- help="Login interactively",
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,22 +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
- print("Login to Sumo environment: " + env)
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
- args.env, interactive=args.interactive, devicecode=args.devicecode
83
+ env,
84
+ interactive=is_interactive,
85
+ devicecode=is_devicecode,
71
86
  )
72
87
  token = sumo.authenticate()
73
88
 
74
- if args.print_token:
75
- print(f"TOKEN: {token}")
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)
76
97
 
77
- if token is not None:
78
- print("Successfully logged in to Sumo environment: " + env)
79
- else:
80
- print("Failed login to Sumo environment: " + env)
98
+ if token is None:
99
+ return 1
100
+ return 0
81
101
 
82
102
 
83
103
  if __name__ == "__main__":
@@ -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.0)
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._blob_client = BlobClient(retry_strategy)
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
@@ -84,27 +87,6 @@ class SumoClient:
84
87
  access_token=access_token,
85
88
  devicecode=devicecode,
86
89
  )
87
- if (
88
- self.auth.get_token() is None
89
- and refresh_token is None
90
- and access_token is None
91
- ):
92
- print("\n \033[31m !!! Falling back to device-code login:\033[0m")
93
- self.auth = get_auth_provider(
94
- client_id=APP_REGISTRATION[env]["CLIENT_ID"],
95
- authority=f"{AUTHORITY_HOST_URI}/{TENANT_ID}",
96
- resource_id=APP_REGISTRATION[env]["RESOURCE_ID"],
97
- interactive=False,
98
- refresh_token=None,
99
- access_token=None,
100
- devicecode=True,
101
- )
102
- if self.auth.get_token() is None:
103
- print(
104
- "\n\n \033[31m "
105
- + "NOTE! Login failed/timed out. Giving up."
106
- + "\033[0m"
107
- )
108
90
 
109
91
  if env == "localhost":
110
92
  self.base_url = "http://localhost:8084/api/v1"
@@ -113,7 +95,44 @@ class SumoClient:
113
95
  pass
114
96
  return
115
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
+
116
133
  def authenticate(self):
134
+ if self.auth is None:
135
+ return None
117
136
  return self.auth.get_token()
118
137
 
119
138
  @property
@@ -136,7 +155,12 @@ class SumoClient:
136
155
  await sumo.blob_client.upload_blob_async(blob, blob_url)
137
156
  """
138
157
 
139
- return self._blob_client
158
+ return BlobClient(
159
+ self._client,
160
+ self._async_client,
161
+ self._timeout,
162
+ self._retry_strategy,
163
+ )
140
164
 
141
165
  @raise_for_status
142
166
  def get(self, path: str, params: dict = None) -> dict:
@@ -174,12 +198,12 @@ class SumoClient:
174
198
  headers.update(self.auth.get_authorization())
175
199
 
176
200
  def _get():
177
- return httpx.get(
201
+ return self._client.get(
178
202
  f"{self.base_url}{path}",
179
203
  params=params,
180
204
  headers=headers,
181
205
  follow_redirects=True,
182
- timeout=DEFAULT_TIMEOUT,
206
+ timeout=self._timeout,
183
207
  )
184
208
 
185
209
  retryer = self._retry_strategy.make_retryer()
@@ -248,13 +272,13 @@ class SumoClient:
248
272
  headers.update(self.auth.get_authorization())
249
273
 
250
274
  def _post():
251
- return httpx.post(
275
+ return self._client.post(
252
276
  f"{self.base_url}{path}",
253
277
  content=blob,
254
278
  json=json,
255
279
  headers=headers,
256
280
  params=params,
257
- timeout=DEFAULT_TIMEOUT,
281
+ timeout=self._timeout,
258
282
  )
259
283
 
260
284
  retryer = self._retry_strategy.make_retryer()
@@ -295,12 +319,12 @@ class SumoClient:
295
319
  headers.update(self.auth.get_authorization())
296
320
 
297
321
  def _put():
298
- return httpx.put(
322
+ return self._client.put(
299
323
  f"{self.base_url}{path}",
300
324
  content=blob,
301
325
  json=json,
302
326
  headers=headers,
303
- timeout=DEFAULT_TIMEOUT,
327
+ timeout=self._timeout,
304
328
  )
305
329
 
306
330
  retryer = self._retry_strategy.make_retryer()
@@ -334,11 +358,11 @@ class SumoClient:
334
358
  headers.update(self.auth.get_authorization())
335
359
 
336
360
  def _delete():
337
- return httpx.delete(
361
+ return self._client.delete(
338
362
  f"{self.base_url}{path}",
339
363
  headers=headers,
340
364
  params=params,
341
- timeout=DEFAULT_TIMEOUT,
365
+ timeout=self._timeout,
342
366
  )
343
367
 
344
368
  retryer = self._retry_strategy.make_retryer()
@@ -399,13 +423,12 @@ class SumoClient:
399
423
  headers.update(self.auth.get_authorization())
400
424
 
401
425
  async def _get():
402
- async with httpx.AsyncClient(follow_redirects=True) as client:
403
- return await client.get(
404
- f"{self.base_url}{path}",
405
- params=params,
406
- headers=headers,
407
- timeout=DEFAULT_TIMEOUT,
408
- )
426
+ return await self._async_client.get(
427
+ f"{self.base_url}{path}",
428
+ params=params,
429
+ headers=headers,
430
+ timeout=self._timeout,
431
+ )
409
432
 
410
433
  retryer = self._retry_strategy.make_retryer_async()
411
434
 
@@ -474,15 +497,14 @@ class SumoClient:
474
497
  headers.update(self.auth.get_authorization())
475
498
 
476
499
  async def _post():
477
- async with httpx.AsyncClient() as client:
478
- return await client.post(
479
- url=f"{self.base_url}{path}",
480
- content=blob,
481
- json=json,
482
- headers=headers,
483
- params=params,
484
- timeout=DEFAULT_TIMEOUT,
485
- )
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
+ )
486
508
 
487
509
  retryer = self._retry_strategy.make_retryer_async()
488
510
 
@@ -522,14 +544,13 @@ class SumoClient:
522
544
  headers.update(self.auth.get_authorization())
523
545
 
524
546
  async def _put():
525
- async with httpx.AsyncClient() as client:
526
- return await client.put(
527
- url=f"{self.base_url}{path}",
528
- content=blob,
529
- json=json,
530
- headers=headers,
531
- timeout=DEFAULT_TIMEOUT,
532
- )
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
+ )
533
554
 
534
555
  retryer = self._retry_strategy.make_retryer_async()
535
556
 
@@ -562,13 +583,12 @@ class SumoClient:
562
583
  headers.update(self.auth.get_authorization())
563
584
 
564
585
  async def _delete():
565
- async with httpx.AsyncClient() as client:
566
- return await client.delete(
567
- url=f"{self.base_url}{path}",
568
- headers=headers,
569
- params=params,
570
- timeout=DEFAULT_TIMEOUT,
571
- )
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
+ )
572
592
 
573
593
  retryer = self._retry_strategy.make_retryer_async()
574
594
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sumo-wrapper-python
3
- Version: 1.0.9
3
+ Version: 1.0.10
4
4
  Summary: Python wrapper for the Sumo API
5
5
  Author: Equinor
6
6
  License: Apache License
@@ -211,25 +211,26 @@ 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 >=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.2.2
219
- Requires-Dist: azure-identity >=1.13.0
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 ; 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'
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"
226
226
  Provides-Extra: test
227
- Requires-Dist: pytest ; extra == 'test'
228
- Requires-Dist: PyYAML ; extra == 'test'
227
+ Requires-Dist: pytest; extra == "test"
228
+ Requires-Dist: PyYAML; extra == "test"
229
229
 
230
230
  # sumo-wrapper-python
231
231
 
232
232
  [![Documentation Status](https://readthedocs.org/projects/sumo-wrapper-python/badge/?version=latest)](https://sumo-wrapper-python.readthedocs.io/en/latest/?badge=latest)
233
+ [![SCM Compliance](https://scm-compliance-api.radix.equinor.com/repos/equinor/sumo-wrapper-python/badge)](https://scm-compliance-api.radix.equinor.com/repos/equinor/sumo-wrapper-python/badge)
233
234
 
234
235
  ## Documentation and guidelines
235
236
  [sumo-wrapper-python documentation](https://sumo-wrapper-python.readthedocs.io/en/latest/)
@@ -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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (74.1.2)
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=wW7Bjl0btCgK9_exGVrfJsnFmTOybIdyeCudz0LueQM,235
3
- sumo/wrapper/_auth_provider.py,sha256=HieseMcqT9lMBqgyzwL5Pr6CCyk-eFjKyYhJQx8UKRw,13494
4
- sumo/wrapper/_blob_client.py,sha256=y15_TThJ4ENFtEAF37mgju6Htc8D3ZyzcYCqsYQZo7Y,1495
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=29gfaFnVGHlO4YwJu6vgvQjCQwPqDmuCvkEOYMZyflo,411
9
- sumo/wrapper/config.py,sha256=6t7qqjrrmd11m4VMlRryiMYw2JDU_R51305woAP1TAs,865
10
- sumo/wrapper/login.py,sha256=HY3CLu3u0RZtgSbitpdyk2T0P-M9ttZ6LRDsGO4BwRM,1773
11
- sumo/wrapper/sumo_client.py,sha256=Mjco-nFF28GgmjXgDmGmXmFXr7LJI50M54zL0zt3CJs,15545
12
- sumo_wrapper_python-1.0.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
13
- sumo_wrapper_python-1.0.9.dist-info/METADATA,sha256=pmewZzDIe8sALypH6d9EVNxBr5qiuoCFlW5MfBjytSg,14267
14
- sumo_wrapper_python-1.0.9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
15
- sumo_wrapper_python-1.0.9.dist-info/entry_points.txt,sha256=V_vGky2C3He5vohJAxnBdvpt_fqfUDFj5irUm9HtoFc,55
16
- sumo_wrapper_python-1.0.9.dist-info/top_level.txt,sha256=rLbKyH9rWgCj3PoLeR7fvC5X8vCaUc5LF8-Y_GBWZL0,5
17
- sumo_wrapper_python-1.0.9.dist-info/RECORD,,