easy-utils-dev 2.138__py3-none-any.whl → 2.139__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 easy-utils-dev might be problematic. Click here for more details.

easy_utils_dev/EasySsh.py CHANGED
@@ -69,7 +69,7 @@ class CREATESSH :
69
69
  ch.disconnect = disconnect
70
70
  return ch
71
71
 
72
- def ssh_execute(self ,command , merge_output=False , hide_output=False) :
72
+ def ssh_execute(self ,command , merge_output=False , hide_output=True) :
73
73
  self.logger.info(f"executing {command}")
74
74
  try :
75
75
  stdin_ , stdout_ , stderr_ = self.ssh.exec_command(command)
@@ -35,6 +35,7 @@ class KafkaConfig :
35
35
  self.kafka_subscription_deleted = False
36
36
  self.base_url = None
37
37
  self.kafka_thread=None
38
+ self.enable_auto_refresh = False
38
39
 
39
40
 
40
41
 
@@ -69,7 +70,8 @@ class WSNOCLIB :
69
70
  self.onGoingRequests=0
70
71
  self.fastQueue = FastQueue(request_max_count)
71
72
  self.queue = []
72
- self.tokenRefreshPeriod = 2700
73
+ self.token = {}
74
+ self.tokenRefreshPeriod = None
73
75
  self.final_results = []
74
76
  self.killed=False
75
77
  self.nes=[]
@@ -124,33 +126,23 @@ class WSNOCLIB :
124
126
  return
125
127
  if not pingAddress(self.address) :
126
128
  raise Exception(f'Address {self.address} is not pingable.')
127
- self.logger.info(f'Connecting to {self.address} using username: {self.username} BAuth={BAuth(self.username, self.password)}')
129
+ # self.logger.info(f'Connecting to {self.address} using username: {self.username}')
128
130
  self.logger.debug(f'Connecting to {self.address} using username: {self.username}')
129
131
  URL = f"https://{self.address}/rest-gateway/rest/api/v1/auth/token"
130
- self.logger.debug(f'Login URL is {URL}')
131
132
  headers = {
132
133
  "Content-Type": "application/json"
133
134
  }
134
135
  data = {
135
136
  "grant_type": "client_credentials"
136
137
  }
137
- r = requests.post(url = URL , headers=headers , auth=BAuth(self.username, self.password), verify=False, json=data)
138
- self.logger.debug(f"""
139
- response_header={r.headers}
140
- response_encoding={r.encoding}
141
- response_reason={r.reason}
142
- body={r.request.body}
143
- headers={r.request.headers}
144
- URL={URL}
145
- content={r.text}
146
- """)
147
- self.logger.info(f'Request return status code : {r.status_code}')
148
- self.logger.debug(f"Login Request response body is {r.text}")
149
- if r.status_code != 200 :
138
+ r = self.session.post(url = URL , headers=headers , auth=BAuth(self.username, self.password), json=data)
139
+ if not r.ok :
150
140
  self.logger.debug(f'fail message {r.text}')
151
141
  raise Exception(f'Failed to authenticate WSNOC. Return status code : {r.status_code}')
152
142
  self.access_token = r.json()["access_token"]
153
143
  self.refresh_token = r.json()["refresh_token"]
144
+ if not self.tokenRefreshPeriod :
145
+ self.tokenRefreshPeriod = int(r.json()["expires_in"]) - 100
154
146
  self.bearer_token = f'Bearer {self.access_token}'
155
147
  self.token = r.json()
156
148
  self.token.update({'bearer_token' : self.bearer_token })
@@ -202,12 +194,13 @@ class WSNOCLIB :
202
194
  sleep(self.tokenRefreshPeriod)
203
195
  self.logger.info(f"Waiting period completed. Starting Revoking/Login process ...")
204
196
  self.refresh_inprogress = True
205
- self.logout(logout=False)
206
- sleep(30)
207
- self.logger.info(f"Logout process completed. Starting Reconnecting ...")
208
- self.connect(self.auto_refresh_token)
197
+ self.renew_by_refresh_token()
209
198
  self.refresh_inprogress = False
210
- self.event.dispatchEvent('onTokenRefresh', self.token)
199
+ self.kafka.refresh_inprogress = True
200
+ if self.kafka.enable_auto_refresh :
201
+ self.renewSubscription()
202
+ self.kafka.refresh_inprogress = False
203
+ self.runAutoRefreshThread()
211
204
 
212
205
  def kafka_connect( self ,
213
206
  user ,
@@ -253,7 +246,6 @@ class WSNOCLIB :
253
246
  self.logger.debug(f"nsp_address: {nsp_address}")
254
247
  self.logger.debug(f"NewRelease: {new_release}")
255
248
  self.kafka.kafka_subscription_deleted= False
256
-
257
249
  if not self.external_nsp :
258
250
  if new_release :
259
251
  self.kafka.kafka_nsp_os_name = 'nspos-kafka'
@@ -265,8 +257,6 @@ class WSNOCLIB :
265
257
  self.kafka.kafka_address = nsp_address
266
258
  self.kafka.kafka_port = 9192
267
259
  self.kafka.kafka_nsp_os_name = 'nspos-tomcat'
268
-
269
- self.logger.info('requesting kafka subscription ...')
270
260
  if self.loggedOut or self.killed:
271
261
  self.logger.error(f"WSNOC API Authentication process loggedout or killed. exit")
272
262
  raise Exception('WSNOC API Authentication process loggedout or killed. exit')
@@ -283,21 +273,38 @@ class WSNOCLIB :
283
273
  self.nsp_key = f'{self.temp_dir}/nsp.key'
284
274
  self.nsp_cert = f'{self.temp_dir}/nsp.pem'
285
275
  if not self.external_nsp :
276
+ ####################
277
+ ####################
278
+ #
279
+ # IN CASE OF INTERNAL NSP
280
+ #
281
+ ####################
282
+ ####################
283
+ checkContainer = self.ssh.ssh_execute(f"docker ps | grep -i 'nspos-kafka' | wc -l")
284
+ if checkContainer != '0' :
285
+ self.kafka.kafka_nsp_os_name = 'nspos-kafka'
286
+ self.kafka.kafka_port = None
286
287
  self.logger.debug(f"Working on internal NSP to copy the files ...")
287
288
  self.ssh.ssh_execute(f"docker cp {self.kafka.kafka_nsp_os_name}:/opt/nsp/os/ssl/certs/nsp/nsp.pem /tmp/nsp.pem")
288
289
  self.ssh.ssh_execute(f"docker cp {self.kafka.kafka_nsp_os_name}:/opt/nsp/os/ssl/nsp.key /tmp/nsp.key")
289
290
  sftp.get('/tmp/nsp.pem' , f'{self.temp_dir}/nsp.pem')
290
291
  sftp.get('/tmp/nsp.key' , f'{self.temp_dir}/nsp.key')
291
292
  self.kafka.ca_cert = f'{self.temp_dir}/nsp.pem'
292
- baseUrl = self.kafka.base_url = None
293
- port = self.kafka.kafka_api_port
293
+ self.kafka.base_url = f'https://{self.address}'
294
294
  else :
295
+ ####################
296
+ ####################
297
+ #
298
+ # IN CASE OF EXTERNAL NSP
299
+ #
300
+ ####################
301
+ ####################
295
302
  self.logger.debug(f"Working on external NSP to copy the files ...")
296
303
  CertLoc = f"""find /var/lib/kubelet/pods/ -type d -path "*/volumes/kubernetes.io~empty-dir/shared-tls-volume" | head -n1"""
297
304
  CertLoc = self.ssh.ssh_execute(CertLoc).replace('\n','')
298
305
  self.logger.debug(f"CertLoc Host: {CertLoc}")
299
- baseUrl = self.kafka.base_url = f'https://{nsp_address}'
300
- port = self.kafka.kafka_port = None
306
+ self.kafka.base_url = f'https://{nsp_address}'
307
+ self.kafka.kafka_port = None
301
308
  if len(CertLoc) > 15 :
302
309
  self.logger.debug(f"Copying cert files from nsp host machine ....")
303
310
  copies = [
@@ -334,7 +341,6 @@ class WSNOCLIB :
334
341
  self.logger.debug(f'affectedObjectType Filter : filter updated with "{filter}"')
335
342
  if custom_filter_expression :
336
343
  filter += f" {custom_filter_expression}"
337
- URL = '/nbi-notification/api/v1/notifications/subscriptions'
338
344
  kafkaForm = {
339
345
  "categories": [
340
346
  {
@@ -344,19 +350,18 @@ class WSNOCLIB :
344
350
  ]
345
351
  }
346
352
  self.logger.debug(f"Kafka Filter Form : {kafkaForm}")
347
- response = self.post(
348
- url=URL,
349
- baseUrl=baseUrl,
350
- return_json=True,
351
- port=port,
352
- body=json.dumps(kafkaForm)
353
- )
354
- if response :
353
+ if self.kafka.kafka_port is not None :
354
+ URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions"
355
+ else :
356
+ URL = f"{self.kafka.base_url}/nbi-notification/api/v1/notifications/subscriptions"
357
+ response = self.session.post(URL , json=kafkaForm , retries=3)
358
+ if response.ok :
359
+ response = response.json()
355
360
  self.kafka.subscriptionId = response['response']['data']['subscriptionId']
356
361
  self.kafka.response = response
357
362
  self.kafka.topicId = response['response']['data']['topicId']
358
363
  if auto_refresh :
359
- self.kafka.kafka_thread = start_thread(target=self.renewSubscription)
364
+ self.kafka.enable_auto_refresh = True
360
365
  self.killed=False
361
366
  else :
362
367
  self.logger.error(f"Failed to create kafka subscription.")
@@ -365,81 +370,84 @@ class WSNOCLIB :
365
370
  return self.kafka
366
371
 
367
372
  def change_kafka_refresh_period(self , period : int =3000) :
368
- self.kafka.kafka_refresh_period = period
373
+ print('Deprecated function change_kafka_refresh_period. Kafka refresh period is now managed by WSNOC API SLEEP PERIOD')
369
374
 
370
375
  def renewSubscription(self) :
371
- while True :
372
- try :
373
- sleep(self.kafka.kafka_refresh_period)
374
- if self.loggedOut or self.killed:
375
- break
376
- self.logger.info('Renewing subscription ...')
377
- self.kafka.refresh_inprogress = True
378
- URL = f"/nbi-notification/api/v1/notifications/subscriptions/{self.kafka.subscriptionId}/renewals"
379
- response = self.post(
380
- url=URL,
381
- baseUrl=self.kafka.base_url,
382
- port=self.kafka.kafka_port,
383
- )
384
- self.logger.info(f'Renewing subscription [{response.status_code}]')
385
- self.logger.info(f'Renewing subscription Response: [{response.text}]')
386
- except Exception as error :
387
- self.logger.error(f'failed to renew subscription. {error}')
388
- self.logger.debug(traceback.format_exc())
389
- self.kafka.refresh_inprogress = False
390
-
391
- def deleteKafkaSubscription(self , subscriptionId=None) :
392
- self.logger.info(f'Deleting subscription subscriptionId:{subscriptionId}')
393
- if not subscriptionId :
394
- self.logger.info(f'Deleting subscription subscriptionId:{self.kafka.subscriptionId}')
395
- subscriptionId=self.kafka.subscriptionId
396
- URL = f"/nbi-notification/api/v1/notifications/subscriptions/{subscriptionId}"
397
- self.kafka.kafka_subscription_deleted= True
398
- self.delete(
399
- url=URL,
400
- return_json=True,
401
- port=self.kafka.kafka_api_port,
402
- )
403
- self.killed=True
404
- return True
376
+ self.logger.info('Renewing subscription ...')
377
+ if self.kafka.kafka_port is not None :
378
+ URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions/{self.kafka.subscriptionId}/renewals"
379
+ else :
380
+ URL = f"{self.kafka.base_url}/nbi-notification/api/v1/notifications/subscriptions/{self.kafka.subscriptionId}/renewals"
381
+ response = self.session.post(URL , retries=3)
382
+ if not response.ok :
383
+ self.logger.error(f'failed to renew subscription. {response.text}')
384
+
385
+ # def renewSubscription(self) :
386
+ # while True :
387
+ # try :
388
+ # sleep(self.kafka.kafka_refresh_period)
389
+ # if self.loggedOut or self.killed:
390
+ # break
391
+ # self.logger.info('Renewing subscription ...')
392
+ # self.kafka.refresh_inprogress = True
393
+ # URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions/{self.kafka.subscriptionId}/renewals"
394
+ # response = self.session.post(URL , retries=3)
395
+ # self.logger.debug(f'Renewing subscription Response: [{response.text}]')
396
+ # except Exception as error :
397
+ # self.logger.error(f'failed to renew subscription. {error}')
398
+ # self.logger.debug(traceback.format_exc())
399
+ # self.kafka.refresh_inprogress = False
400
+
401
+ # def deleteKafkaSubscription(self , subscriptionId=None) :
402
+ # self.logger.info(f'Deleting subscription subscriptionId:{subscriptionId}')
403
+ # if not subscriptionId :
404
+ # self.logger.info(f'Deleting subscription subscriptionId:{self.kafka.subscriptionId}')
405
+ # subscriptionId=self.kafka.subscriptionId
406
+ # self.kafka.kafka_subscription_deleted= True
407
+ # URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions/{subscriptionId}"
408
+ # response = self.session.delete(URL , retries=3)
409
+ # return response
405
410
 
406
- def handle_beautify_alarm(self , alarm ) :
407
- oalarm = alarm
408
- alarm = alarm['data']['ietf-restconf:notification']
409
- if 'create' in str(list(alarm.keys())) :
410
- alarmData = alarm['nsp-fault:alarm-create']
411
- oalarm['dataEnh'] = {
412
- 'newAlarm' : True,
413
- 'alarmChange' : False,
414
- 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
415
- 'neId' : alarmData['neId'],
416
- 'neName ' : alarmData['neName'],
417
- 'alarmName' : alarmData['alarmName'],
418
- 'cleared' : False,
419
- 'aknowledged' : False,
420
- **alarmData ,
421
- }
422
- elif 'change' in str(list(alarm.keys())) :
423
- alarmData = alarm['nsp-fault:alarm-change']
424
- cleared = False
425
- aknowledged = False
426
- if 'severity' in list(alarmData.keys()) :
427
- if alarmData['severity']['new-value'] == 'cleared' :
428
- cleared = True
429
- if 'acknowledged' in list(alarmData.keys()) :
430
- aknowledged = alarmData['acknowledged']['new-value']
431
- oalarm['dataEnh'] = {
432
- 'newAlarm' : False,
433
- 'alarmChange' : True,
434
- 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
435
- 'cleared' : cleared,
436
- 'aknowledged' : aknowledged,
437
- **alarmData ,
438
- }
439
- return oalarm
440
-
441
-
442
- def kafka_listen(self) :
411
+ # def handle_beautify_alarm(self , alarm ) :
412
+ # oalarm = alarm
413
+ # alarm = alarm['data']['ietf-restconf:notification']
414
+ # if 'create' in str(list(alarm.keys())) :
415
+ # alarmData = alarm['nsp-fault:alarm-create']
416
+ # oalarm['dataEnh'] = {
417
+ # 'newAlarm' : True,
418
+ # 'alarmChange' : False,
419
+ # 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
420
+ # 'neId' : alarmData['neId'],
421
+ # 'neName ' : alarmData['neName'],
422
+ # 'alarmName' : alarmData['alarmName'],
423
+ # 'cleared' : False,
424
+ # 'aknowledged' : False,
425
+ # **alarmData ,
426
+ # }
427
+ # elif 'change' in str(list(alarm.keys())) :
428
+ # alarmData = alarm['nsp-fault:alarm-change']
429
+ # cleared = False
430
+ # aknowledged = False
431
+ # if 'severity' in list(alarmData.keys()) :
432
+ # if alarmData['severity']['new-value'] == 'cleared' :
433
+ # cleared = True
434
+ # if 'acknowledged' in list(alarmData.keys()) :
435
+ # aknowledged = alarmData['acknowledged']['new-value']
436
+ # oalarm['dataEnh'] = {
437
+ # 'newAlarm' : False,
438
+ # 'alarmChange' : True,
439
+ # 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
440
+ # 'cleared' : cleared,
441
+ # 'aknowledged' : aknowledged,
442
+ # **alarmData ,
443
+ # }
444
+ # return oalarm
445
+
446
+
447
+ def kafka_listen(self) :
448
+ def hold_if_kafka_refresh_inprogress() :
449
+ while self.kafka.refresh_inprogress :
450
+ sleep(.1)
443
451
  self.logger.info('Listening to Kafka Notifications ...')
444
452
  if not self.kafka.topicId :
445
453
  self.logger.error(f'kafka is not established. exit.')
@@ -464,9 +472,7 @@ class WSNOCLIB :
464
472
  )
465
473
  try:
466
474
  while True:
467
- if self.kafka.refresh_inprogress :
468
- self.logger.info(f"Waiting for kafka subscription to be ready. In refresh process...")
469
- sleep(1)
475
+ hold_if_kafka_refresh_inprogress()
470
476
  if self.kafka.kafka_subscription_deleted :
471
477
  self.logger.info(f"Kafka subscription is deleted. exit.")
472
478
  break
@@ -583,6 +589,27 @@ class WSNOCLIB :
583
589
  return r.json()
584
590
  return r
585
591
 
592
+ def renew_by_refresh_token(self) :
593
+ URL = f"https://{self.address}/rest-gateway/rest/api/v1/auth/token"
594
+ headers = {
595
+ "Content-Type": "application/json"
596
+ }
597
+ data = {
598
+ "grant_type": "refresh_token",
599
+ "refresh_token": f"{self.refresh_token}"
600
+
601
+ }
602
+ r = self.session.post(URL , headers=headers , json=data , auth=BAuth(self.username, self.password) , skip_hold_for_token_refresh=True)
603
+ if r.ok :
604
+ if not self.tokenRefreshPeriod :
605
+ self.tokenRefreshPeriod = int(r.json()["expires_in"]) - 100
606
+ self.access_token = r.json()["access_token"]
607
+ self.refresh_token = r.json()["refresh_token"]
608
+ self.bearer_token = f'Bearer {self.access_token}'
609
+ self.token = r.json()
610
+ self.token.update({'bearer_token' : self.bearer_token })
611
+ return r
612
+
586
613
  def session_info(self) :
587
614
  self.logger.debug('Getting Version ...')
588
615
  response = self.get( url='/oms1350/data/common/sessionInfo')
@@ -676,22 +703,27 @@ class WSNOCSession(requests.Session):
676
703
  self.headers.update({"Content-Type": "application/json"}) # base defaults
677
704
  self._wsnoc = wsnoc
678
705
  self.verify = False
706
+ self.retries = 0
707
+ self.skip_hold_for_token_refresh = False
679
708
 
680
709
  def rebuild_auth(self, prepared_request, response):
681
710
  return
682
711
 
683
- def hold_for_token_refresh(self) :
712
+ def hold_for_token_refresh(self, url) :
684
713
  while self._wsnoc.refresh_inprogress :
714
+ self._wsnoc.logger.debug(f'Waiting for token refresh. {url}')
685
715
  sleep(.1)
686
716
 
687
- def request(self, method, url , retries=0 , **kwargs):
717
+ def request(self, method, url , retries=0 , skip_hold_for_token_refresh=False , **kwargs):
688
718
  self._wsnoc.logger.debug(f'[{method}] : {url}')
689
- self.hold_for_token_refresh()
719
+ if not skip_hold_for_token_refresh :
720
+ self.hold_for_token_refresh(url)
690
721
  self._wsnoc.api_count += 1
691
722
  token = self._wsnoc.getLatestToken().get('bearer_token')
692
723
  request_headers = kwargs.get('headers' , {})
693
- if not request_headers.get('Authorization') :
694
- request_headers['Authorization'] = token
724
+ if token :
725
+ if not request_headers.get('Authorization') :
726
+ request_headers['Authorization'] = token
695
727
  kwargs['headers'] = request_headers
696
728
  request = super().request(method, url, **kwargs )
697
729
  for i in range(retries) :
@@ -702,7 +734,7 @@ class WSNOCSession(requests.Session):
702
734
  self.hold_for_token_refresh()
703
735
  request = super().request(method, url, **kwargs )
704
736
  self._wsnoc.logger.debug(f'[Try-{i}] [{method}] : {url}- {request.status_code}')
705
- self._wsnoc.logger.info(f'[{method}] : {url}- {request.status_code}')
737
+ self._wsnoc.logger.info(f'[{method}] : {url} - [{request.status_code}]')
706
738
  return request
707
739
 
708
740
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: easy-utils-dev
3
- Version: 2.138
3
+ Version: 2.139
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
@@ -1,4 +1,4 @@
1
- easy_utils_dev/EasySsh.py,sha256=PbFK1vSBcnt_SfkZXZGh5wg6Sg9R2WlXeGdvjZluJ9E,3592
1
+ easy_utils_dev/EasySsh.py,sha256=9pQaHHQGng8X3fsy4Vvto5RIEObm-iOt52VLiO6m1g0,3591
2
2
  easy_utils_dev/Events.py,sha256=MdI53gAyXX_2jmChpayayQM0ZitgjtkyUNrQYGEEZnw,2978
3
3
  easy_utils_dev/FastQueue.py,sha256=Drt8B_hEdmg9eAt7OWSgTyoJ3rUHkeJHk9xdaehtEsY,5622
4
4
  easy_utils_dev/NameObject.py,sha256=Z4Qp3qfMcQeMPw35PV_xOu8kigRtfSRZ4h7woR0t3Gg,270
@@ -27,11 +27,11 @@ easy_utils_dev/temp_memory.py,sha256=gfC-izYw8Sg1DD5iOdL8R5-fdB5RK0qkXZie9WmFjPA
27
27
  easy_utils_dev/uiserver.py,sha256=d9jImwgSURMH4MIU4Zkro2b20cAbuV8rO_If42FVyZ4,6826
28
28
  easy_utils_dev/utils.py,sha256=BmVnbxc336c6WTeDFcEHN6Mavt7fJrIEyK4GXODV3gI,13345
29
29
  easy_utils_dev/winserviceapi.py,sha256=2ZP6jaSt1-5vEJYXqwBhwX-1-eQ3V3YzntsoOoko2cw,18804
30
- easy_utils_dev/wsnoclib.py,sha256=z18kdPJJeNYAaomqH1t6KfTWm77BNPx6aPAW9P0PSOc,30813
30
+ easy_utils_dev/wsnoclib.py,sha256=25sjxutCAOHHGaK7-t1owNq9fV29BHFK1Dox08SZdFg,32876
31
31
  easy_utils_dev/wsselib.py,sha256=YweScnoAAH_t29EeIjBpkQ6HtX0Rp9mQudRsRce2SE8,7920
32
32
  easy_utils_dev/ept_sql/create_dirs.sql,sha256=KWfX-Nc6lvr_BC-P6O97NE0idoPW4GNKUKUCgonJhto,3508
33
33
  easy_utils_dev/ept_sql/create_ept_tables.sql,sha256=WDHyIyeReV8_QaYBPIpSy-lto3OKvZtex1tWs-FPURQ,67737
34
- easy_utils_dev-2.138.dist-info/METADATA,sha256=uN_YPuZajKHryq24B8n7zfC3qVYpw9oXaDabo_Vfo1E,510
35
- easy_utils_dev-2.138.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
36
- easy_utils_dev-2.138.dist-info/top_level.txt,sha256=7vBsrpq7NmilkdU3YUvfd5iVDNBaT07u_-ut4F7zc7A,15
37
- easy_utils_dev-2.138.dist-info/RECORD,,
34
+ easy_utils_dev-2.139.dist-info/METADATA,sha256=-C0ualpFQyudh54KYQSws8PVDWoPXSme1Bd8FZNA3-E,510
35
+ easy_utils_dev-2.139.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
36
+ easy_utils_dev-2.139.dist-info/top_level.txt,sha256=7vBsrpq7NmilkdU3YUvfd5iVDNBaT07u_-ut4F7zc7A,15
37
+ easy_utils_dev-2.139.dist-info/RECORD,,