pycupra 0.1.5__py3-none-any.whl → 0.1.6__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.
pycupra/firebase.py CHANGED
@@ -16,29 +16,46 @@ from .const import (
16
16
  _LOGGER = logging.getLogger(__name__)
17
17
 
18
18
  class Firebase():
19
+ def __init__(self):
20
+ self._pushClient = None
21
+
19
22
  async def firebaseStart(self, onNotificationFunc, firebaseCredentialsFileName, brand='cupra'):
20
23
  """ Starts the firebase cloud messaging receiver """
21
- loop = asyncio.get_running_loop()
22
- credentials = await loop.run_in_executor(None, readFCMCredsFile, firebaseCredentialsFileName)
23
- #credentials = readFCMCredsFile(firebaseCredentialsFileName)
24
- if credentials == {}:
25
- credentials =''
24
+ try:
25
+ loop = asyncio.get_running_loop()
26
+ credentials = await loop.run_in_executor(None, readFCMCredsFile, firebaseCredentialsFileName)
27
+ #credentials = readFCMCredsFile(firebaseCredentialsFileName)
28
+ if credentials == {}:
29
+ credentials =''
26
30
 
27
- fcm_project_id=FCM_PROJECT_ID
28
- fcm_app_id=FCM_APP_ID[brand]
29
- fcm_api_key=FCM_API_KEY
30
- chars = string.ascii_letters + string.digits
31
- fcmMessageSenderId = ''.join(secrets.choice(chars) for i in range(16))
32
- fcmMessageSenderId= 'fxpWQ_'+fcmMessageSenderId
31
+ fcm_project_id=FCM_PROJECT_ID
32
+ fcm_app_id=FCM_APP_ID[brand]
33
+ fcm_api_key=FCM_API_KEY
34
+ chars = string.ascii_letters + string.digits
35
+ fcmMessageSenderId = ''.join(secrets.choice(chars) for i in range(16))
36
+ fcmMessageSenderId= 'fxpWQ_'+fcmMessageSenderId
33
37
 
38
+ fcm_config = FcmRegisterConfig(fcm_project_id, fcm_app_id, fcm_api_key, fcmMessageSenderId)
39
+ self._pushClient = FcmPushClient(onNotificationFunc, fcm_config, credentials, onFCMCredentialsUpdated)
40
+ fcm_token = await self._pushClient.checkin_or_register(firebaseCredentialsFileName)
41
+ _LOGGER.debug(f'Firebase.checkin_or_register() returned a token:{fcm_token}')
42
+ await self._pushClient.start()
43
+ await asyncio.sleep(5)
44
+ return self._pushClient.is_started()
45
+ except Exception as e:
46
+ _LOGGER.error('Error in firebaseStart. Error: {e}')
47
+ return False
34
48
 
35
- fcm_config = FcmRegisterConfig(fcm_project_id, fcm_app_id, fcm_api_key, fcmMessageSenderId)
36
- pc = FcmPushClient(onNotificationFunc, fcm_config, credentials, onFCMCredentialsUpdated)
37
- fcm_token = await pc.checkin_or_register(firebaseCredentialsFileName)
38
- _LOGGER.debug(f'Firebase.checkin_or_register() returned a token:{fcm_token}')
39
- await pc.start()
40
- await asyncio.sleep(5)
41
- return pc.is_started()
49
+ async def firebaseStop(self):
50
+ """ Stops the firebase cloud messaging receiver """
51
+ try:
52
+ await self._pushClient.stop()
53
+ #await asyncio.sleep(5)
54
+ self._pushClient = None
55
+ return True
56
+ except Exception as e:
57
+ _LOGGER.error('Error in firebaseStop. Error: {e}')
58
+ return False
42
59
 
43
60
  def readFCMCredsFile(credsFile):
44
61
  """ Reads the firebase cloud messaging credentials from file"""
@@ -52,8 +69,8 @@ def readFCMCredsFile(credsFile):
52
69
  else:
53
70
  _LOGGER.debug(f'{credsFile} not found.')
54
71
  return {}
55
- except:
56
- _LOGGER.warning('readFCMCredsFile() not successful.')
72
+ except Exception as e:
73
+ _LOGGER.warning('readFCMCredsFile() not successful. Error: {e}')
57
74
  return ''
58
75
 
59
76
  def writeFCMCredsFile(creds, firebaseCredentialsFileName):
pycupra/utilities.py CHANGED
@@ -1,116 +1,116 @@
1
- from datetime import date, datetime
2
- from base64 import b64encode
3
- from string import ascii_letters as letters, digits
4
- from sys import argv
5
- from os import environ as env
6
- from os.path import join, dirname, expanduser
7
- from itertools import product
8
- import json
9
- import logging
10
- import re
11
-
12
- _LOGGER = logging.getLogger(__name__)
13
-
14
-
15
- def read_config():
16
- """Read config from file."""
17
- for directory, filename in product(
18
- [
19
- dirname(argv[0]),
20
- expanduser("~"),
21
- env.get("XDG_CONFIG_HOME", join(expanduser("~"), ".config")),
22
- ],
23
- ["seat.conf", ".seat.conf"],
24
- ):
25
- try:
26
- config = join(directory, filename)
27
- _LOGGER.debug("checking for config file %s", config)
28
- with open(config) as config:
29
- return dict(
30
- x.split(": ")
31
- for x in config.read().strip().splitlines()
32
- if not x.startswith("#")
33
- )
34
- except (IOError, OSError):
35
- continue
36
- return {}
37
-
38
-
39
- def json_loads(s):
40
- return json.loads(s, object_hook=obj_parser)
41
-
42
-
43
- def obj_parser(obj):
44
- """Parse datetime."""
45
- for key, val in obj.items():
46
- try:
47
- obj[key] = datetime.strptime(val, "%Y-%m-%dT%H:%M:%S%z")
48
- except (TypeError, ValueError):
49
- pass
50
- return obj
51
-
52
-
53
- def find_path(src, path):
54
- """Simple navigation of a hierarchical dict structure using XPATH-like syntax.
55
-
56
- >>> find_path(dict(a=1), 'a')
57
- 1
58
-
59
- >>> find_path(dict(a=1), '')
60
- {'a': 1}
61
-
62
- >>> find_path(dict(a=None), 'a')
63
-
64
-
65
- >>> find_path(dict(a=1), 'b')
66
- Traceback (most recent call last):
67
- ...
68
- KeyError: 'b'
69
-
70
- >>> find_path(dict(a=dict(b=1)), 'a.b')
71
- 1
72
-
73
- >>> find_path(dict(a=dict(b=1)), 'a')
74
- {'b': 1}
75
-
76
- >>> find_path(dict(a=dict(b=1)), 'a.c')
77
- Traceback (most recent call last):
78
- ...
79
- KeyError: 'c'
80
-
81
- """
82
- if not path:
83
- return src
84
- if isinstance(path, str):
85
- path = path.split(".")
86
- return find_path(src[path[0]], path[1:])
87
-
88
-
89
- def is_valid_path(src, path):
90
- """
91
- >>> is_valid_path(dict(a=1), 'a')
92
- True
93
-
94
- >>> is_valid_path(dict(a=1), '')
95
- True
96
-
97
- >>> is_valid_path(dict(a=1), None)
98
- True
99
-
100
- >>> is_valid_path(dict(a=1), 'b')
101
- False
102
- """
103
- try:
104
- find_path(src, path)
105
- return True
106
- except KeyError:
107
- return False
108
-
109
-
110
- def camel2slug(s):
111
- """Convert camelCase to camel_case.
112
-
113
- >>> camel2slug('fooBar')
114
- 'foo_bar'
115
- """
116
- return re.sub("([A-Z])", "_\\1", s).lower().lstrip("_")
1
+ from datetime import date, datetime
2
+ from base64 import b64encode
3
+ from string import ascii_letters as letters, digits
4
+ from sys import argv
5
+ from os import environ as env
6
+ from os.path import join, dirname, expanduser
7
+ from itertools import product
8
+ import json
9
+ import logging
10
+ import re
11
+
12
+ _LOGGER = logging.getLogger(__name__)
13
+
14
+
15
+ def read_config():
16
+ """Read config from file."""
17
+ for directory, filename in product(
18
+ [
19
+ dirname(argv[0]),
20
+ expanduser("~"),
21
+ env.get("XDG_CONFIG_HOME", join(expanduser("~"), ".config")),
22
+ ],
23
+ ["seat.conf", ".seat.conf"],
24
+ ):
25
+ try:
26
+ config = join(directory, filename)
27
+ _LOGGER.debug("checking for config file %s", config)
28
+ with open(config) as config:
29
+ return dict(
30
+ x.split(": ")
31
+ for x in config.read().strip().splitlines()
32
+ if not x.startswith("#")
33
+ )
34
+ except (IOError, OSError):
35
+ continue
36
+ return {}
37
+
38
+
39
+ def json_loads(s):
40
+ return json.loads(s, object_hook=obj_parser)
41
+
42
+
43
+ def obj_parser(obj):
44
+ """Parse datetime."""
45
+ for key, val in obj.items():
46
+ try:
47
+ obj[key] = datetime.strptime(val, "%Y-%m-%dT%H:%M:%S%z")
48
+ except (TypeError, ValueError):
49
+ pass
50
+ return obj
51
+
52
+
53
+ def find_path(src, path):
54
+ """Simple navigation of a hierarchical dict structure using XPATH-like syntax.
55
+
56
+ >>> find_path(dict(a=1), 'a')
57
+ 1
58
+
59
+ >>> find_path(dict(a=1), '')
60
+ {'a': 1}
61
+
62
+ >>> find_path(dict(a=None), 'a')
63
+
64
+
65
+ >>> find_path(dict(a=1), 'b')
66
+ Traceback (most recent call last):
67
+ ...
68
+ KeyError: 'b'
69
+
70
+ >>> find_path(dict(a=dict(b=1)), 'a.b')
71
+ 1
72
+
73
+ >>> find_path(dict(a=dict(b=1)), 'a')
74
+ {'b': 1}
75
+
76
+ >>> find_path(dict(a=dict(b=1)), 'a.c')
77
+ Traceback (most recent call last):
78
+ ...
79
+ KeyError: 'c'
80
+
81
+ """
82
+ if not path:
83
+ return src
84
+ if isinstance(path, str):
85
+ path = path.split(".")
86
+ return find_path(src[path[0]], path[1:])
87
+
88
+
89
+ def is_valid_path(src, path):
90
+ """
91
+ >>> is_valid_path(dict(a=1), 'a')
92
+ True
93
+
94
+ >>> is_valid_path(dict(a=1), '')
95
+ True
96
+
97
+ >>> is_valid_path(dict(a=1), None)
98
+ True
99
+
100
+ >>> is_valid_path(dict(a=1), 'b')
101
+ False
102
+ """
103
+ try:
104
+ find_path(src, path)
105
+ return True
106
+ except KeyError:
107
+ return False
108
+
109
+
110
+ def camel2slug(s):
111
+ """Convert camelCase to camel_case.
112
+
113
+ >>> camel2slug('fooBar')
114
+ 'foo_bar'
115
+ """
116
+ return re.sub("([A-Z])", "_\\1", s).lower().lstrip("_")
pycupra/vehicle.py CHANGED
@@ -34,7 +34,7 @@ _LOGGER = logging.getLogger(__name__)
34
34
  DATEZERO = datetime(1970,1,1)
35
35
  class Vehicle:
36
36
  def __init__(self, conn, data):
37
- _LOGGER.debug(f'Creating Vehicle class object with data {data}')
37
+ _LOGGER.debug(conn.anonymise(f'Creating Vehicle class object with data {data}'))
38
38
  self._connection = conn
39
39
  self._url = data.get('vin', '')
40
40
  self._connectivities = data.get('connectivities', '')
@@ -50,6 +50,7 @@ class Vehicle:
50
50
  self._firebaseCredentialsFileName = None
51
51
  self._firebaseLastMessageId = ''
52
52
  self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
53
+ self.firebase = None
53
54
  self.updateCallback = None
54
55
 
55
56
  self._requests = {
@@ -184,7 +185,7 @@ class Vehicle:
184
185
  if hasattr(self, '_last_full_update'):
185
186
  _LOGGER.debug(f'last_full_update= {self._last_full_update}, fullUpdateExpired= {fullUpdateExpired}.')
186
187
  if updateType!=1 and (hasattr(self, '_last_full_update') and self._last_full_update>fullUpdateExpired):
187
- _LOGGER.debug(f'Just performed small update for vehicle with VIN {self.vin}.')
188
+ _LOGGER.debug(f'Just performed small update for vehicle with VIN {self._connection.anonymise(self.vin)}.')
188
189
  return True
189
190
 
190
191
  # Data to be updated less often
@@ -206,13 +207,13 @@ class Vehicle:
206
207
  return_exceptions=True
207
208
  )
208
209
  self._last_full_update = datetime.now(tz=None)
209
- _LOGGER.debug(f'Performed full update for vehicle with VIN {self.vin}.')
210
+ _LOGGER.debug(f'Performed full update for vehicle with VIN {self._connection.anonymise(self.vin)}.')
210
211
  _LOGGER.debug(f'So far about {self._connection._sessionRequestCounter} API calls since {self._connection._sessionRequestTimestamp}.')
211
212
  except:
212
213
  raise SeatException("Update failed")
213
214
  return True
214
215
  else:
215
- _LOGGER.info(f'Vehicle with VIN {self.vin} is deactivated.')
216
+ _LOGGER.info(f'Vehicle with VIN {self._connection.anonymise(self.vin)} is deactivated.')
216
217
  return False
217
218
  return True
218
219
 
@@ -290,6 +291,14 @@ class Vehicle:
290
291
  if self._relevantCapabilties.get('vehicleHealthWarnings', {}).get('active', False):
291
292
  data = await self._connection.getVehicleHealthWarnings(self.vin, self._apibase)
292
293
  if data:
294
+ #warningsList = data.get('warninglights',{}).get('statuses',[])
295
+ #for i in range(len(warningsList)):
296
+ # _LOGGER.debug(f'Element {i} in warninglights: {warningsList[i]}')
297
+ # if isinstance(warningsList[i], dict):
298
+ # if warningsList[i].get('icon',''):
299
+ # #Value of icon is very long and can lead to problems
300
+ # _LOGGER.debug(f'Substituting value of icon by \'DELETED\'')
301
+ # data['warninglights']['statuses'][i]['icon']='DELETED'
293
302
  self._states.update(data)
294
303
  else:
295
304
  _LOGGER.debug('Could not fetch vehicle health warnings')
@@ -1394,6 +1403,17 @@ class Vehicle:
1394
1403
  # if car.get('deactivated', False):
1395
1404
  # return True
1396
1405
 
1406
+ @property
1407
+ def brand(self):
1408
+ """Return brand"""
1409
+ return self._specification.get('factoryModel', False).get('vehicleBrand', 'Unknown')
1410
+
1411
+ @property
1412
+ def is_brand_supported(self):
1413
+ """Return true if brand is supported."""
1414
+ if self._specification.get('factoryModel', False).get('vehicleBrand', False):
1415
+ return True
1416
+
1397
1417
  @property
1398
1418
  def model(self):
1399
1419
  """Return model"""
@@ -2217,7 +2237,12 @@ class Vehicle:
2217
2237
  def warnings(self):
2218
2238
  """Return warnings."""
2219
2239
  if self.attrs.get('warninglights', {}).get('statuses',[]):
2220
- return self.attrs.get('warninglights', {}).get('statuses',[])
2240
+ warningTextList = []
2241
+ for elem in self.attrs['warninglights']['statuses']:
2242
+ if isinstance(elem, dict):
2243
+ if elem.get('text',''):
2244
+ warningTextList.append(elem.get('text',''))
2245
+ return warningTextList
2221
2246
  return 'No warnings'
2222
2247
 
2223
2248
  @property
@@ -3120,6 +3145,27 @@ class Vehicle:
3120
3145
  )
3121
3146
 
3122
3147
 
3148
+ async def stopFirebase(self):
3149
+ # Check if firebase is activated
3150
+ if self.firebaseStatus!= FIREBASE_STATUS_ACTIVATED:
3151
+ _LOGGER.info(f'No need to stop firebase. Firebase status={self.firebaseStatus}')
3152
+ return self.firebaseStatus
3153
+
3154
+ if self.firebase == None:
3155
+ _LOGGER.error(f'Internal error: Firebase status={self.firebaseStatus} but firebase variable not set. Setting firebase status back to not initialised.')
3156
+ self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
3157
+ return self.firebaseStatus
3158
+
3159
+ success = await self.firebase.firebaseStop()
3160
+ if not success:
3161
+ _LOGGER.warning('Stopping of firebase messaging failed.')
3162
+ return self.firebaseStatus
3163
+
3164
+ #await asyncio.sleep(5) # Wait to ignore the first notifications
3165
+ self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
3166
+ _LOGGER.info('Stopping of firebase messaging was successful.')
3167
+ return self.firebaseStatus
3168
+
3123
3169
  async def initialiseFirebase(self, firebaseCredentialsFileName=None, updateCallback=None):
3124
3170
  # Check if firebase shall be used
3125
3171
  if firebaseCredentialsFileName == None:
@@ -3141,12 +3187,13 @@ class Vehicle:
3141
3187
  subscribedBrand = credentials.get('subscription',{}).get('brand','')
3142
3188
  if subscribedVin != '' and subscribedUserId != '':
3143
3189
  if subscribedVin != self.vin or subscribedUserId != self._connection._user_id or subscribedBrand != self._connection._session_auth_brand:
3144
- _LOGGER.debug(f'Change of vin, userId or brand. Deleting subscription for vin={subscribedVin} and userId={subscribedUserId}.')
3190
+ _LOGGER.debug(self._connection.anonymise(f'Change of vin, userId or brand. Deleting subscription for vin={subscribedVin} and userId={subscribedUserId}.'))
3145
3191
  result = await self._connection.deleteSubscription(credentials)
3146
3192
 
3147
3193
  # Start firebase
3148
- fb = Firebase()
3149
- success = await fb.firebaseStart(self.onNotification, firebaseCredentialsFileName, brand=self._connection._session_auth_brand)
3194
+ if self.firebase == None:
3195
+ self.firebase = Firebase()
3196
+ success = await self.firebase.firebaseStart(self.onNotification, firebaseCredentialsFileName, brand=self._connection._session_auth_brand)
3150
3197
  if not success:
3151
3198
  self.firebaseStatus = FIREBASE_STATUS_ACTIVATION_FAILED
3152
3199
  _LOGGER.warning('Activation of firebase messaging failed.')
@@ -3167,7 +3214,7 @@ class Vehicle:
3167
3214
  loop = asyncio.get_running_loop()
3168
3215
  await loop.run_in_executor(None, writeFCMCredsFile, credentials, firebaseCredentialsFileName)
3169
3216
 
3170
- await asyncio.sleep(5) # Wait to let the first notifications
3217
+ await asyncio.sleep(5) # Wait to ignore the first notifications
3171
3218
  self.firebaseStatus = FIREBASE_STATUS_ACTIVATED
3172
3219
  _LOGGER.info('Activation of firebase messaging was successful.')
3173
3220
  return self.firebaseStatus
@@ -3247,7 +3294,7 @@ class Vehicle:
3247
3294
  await self.updateCallback(2)
3248
3295
  else:
3249
3296
  _LOGGER.debug(f'It is now {datetime.now(tz=None)}. Last get_charger was at {self._last_get_charger}. So no need to update.')
3250
- elif type in ('climatisation-status-changed','climatisation-started', 'climatisation-stopped'):
3297
+ elif type in ('climatisation-status-changed','climatisation-started', 'climatisation-stopped', 'climatisation-settings-updated'):
3251
3298
  if self._requests.get('climatisation', {}).get('id', None):
3252
3299
  openRequest= self._requests.get('climatisation', {}).get('id', None)
3253
3300
  if openRequest == requestId:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycupra
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A library to read and send vehicle data via Cupra/Seat portal using the same API calls as the MyCupra/MySeat mobile app.
5
5
  Home-page: https://github.com/WulfgarW/pycupra
6
6
  Author: WulfgarW
@@ -1,12 +1,12 @@
1
1
  pycupra/__init__.py,sha256=p0880jPkLqOErX3u3qaLboBLOsEHFpe44axApdaGeqI,231
2
- pycupra/__version__.py,sha256=y2dos0x1TDMyh80ZOW3YvhHvw1DWdxYSgYNyRSzioPY,207
3
- pycupra/connection.py,sha256=VPyLBfcmWPm4L8cL5HXvFT179PhNmiDn3yvWiNtu3Pw,86366
2
+ pycupra/__version__.py,sha256=orABFX8h4AFdsffPt4uVXqXeC0Wz3ZJOeNAzyeNw7O8,207
3
+ pycupra/connection.py,sha256=cZIqsjEwA1rTkhVyjowrTdbWnviOXFSso-WR8FnSrmg,90610
4
4
  pycupra/const.py,sha256=cJJ9xrof6HZ7ZE7nXnB6tU4qMbSadlN8mgs43Igy7Mo,10591
5
5
  pycupra/dashboard.py,sha256=RlJPdTvJV7Urog5gCz_4pYWNd5_ApQIVJUCwJIi_7L8,43822
6
6
  pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
7
- pycupra/firebase.py,sha256=V3Ico6FZzEn0-5-CnqaDP9Mg9LpVU-_qLyZQwiRBbD0,2725
8
- pycupra/utilities.py,sha256=cH4MiIzT2WlHgmnl_E7rR0R5LvCXfDNvirJolct50V8,2563
9
- pycupra/vehicle.py,sha256=yMaO-6lquak8821usCen6LWEM7rn0EZjiEOzK9jp6e4,154682
7
+ pycupra/firebase.py,sha256=tuN_W3OX3h3-yfdprWzmCn6z_T-BMx-OpL7Z6hOA8Lc,3451
8
+ pycupra/utilities.py,sha256=6sDxWP13-XtxmqhuBJBGdVbkj48BQ9AxFMrBPxH0J7g,2679
9
+ pycupra/vehicle.py,sha256=stxeyn2vd2j2yQ3dWEmT9XX1aZNZ7FdYvxbIraF6M0o,157175
10
10
  pycupra/firebase_messaging/__init__.py,sha256=oerLHWvEf4qRqu3GxSX6SLY_OYI430ydAiAhKtzyMEM,666
11
11
  pycupra/firebase_messaging/android_checkin_pb2.py,sha256=-U1oGroFt3KRuGDieae3iTcux6mAfx1TFkE1Q35ul2E,2849
12
12
  pycupra/firebase_messaging/android_checkin_pb2.pyi,sha256=7KL-zQIz2Zz7uftcLkv57Podzu-yk6trn50FN4X4A8E,9379
@@ -18,8 +18,8 @@ pycupra/firebase_messaging/fcmregister.py,sha256=yZngC-0ZfTygtjfdzg03OW_3xk2n_uS
18
18
  pycupra/firebase_messaging/mcs_pb2.py,sha256=nwXY7IDgLYPxgpSGs6wyTSyYDdomQsyGqH8R8EgODLg,7733
19
19
  pycupra/firebase_messaging/mcs_pb2.pyi,sha256=HfIhInC3wRg8_caKwUm-V3knE2jTdEQvBy6uXgQ5rHY,33959
20
20
  pycupra/firebase_messaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- pycupra-0.1.5.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
22
- pycupra-0.1.5.dist-info/METADATA,sha256=upt06crj4Naaogz_wPgfmLUDZ8VXdwDlZLzP9bthKpM,3757
23
- pycupra-0.1.5.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
24
- pycupra-0.1.5.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
25
- pycupra-0.1.5.dist-info/RECORD,,
21
+ pycupra-0.1.6.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
22
+ pycupra-0.1.6.dist-info/METADATA,sha256=u9ZdIDY607S5-5APn3VwI1nJgvWHPngzptNHedmY_k4,3757
23
+ pycupra-0.1.6.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
24
+ pycupra-0.1.6.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
25
+ pycupra-0.1.6.dist-info/RECORD,,