dimo-python-sdk 0.0.1__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.
dimo/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .dimo import DIMO
2
+
3
+ __all__ = ["DIMO"]
dimo/auth.py ADDED
@@ -0,0 +1,85 @@
1
+ from web3 import Web3
2
+ from eth_account.messages import encode_defunct
3
+ from dimo.constants import dimo_constants
4
+ from urllib.parse import urlencode
5
+
6
+ class Auth:
7
+
8
+ def __init__(self, request_method, get_auth_headers):
9
+ self._request = request_method
10
+ self._get_auth_headers = get_auth_headers
11
+
12
+ async def generate_challenge(self,
13
+ client_id,
14
+ domain,
15
+ address,
16
+ headers,
17
+ scope='openid email',
18
+ response_type='code'):
19
+ body = {
20
+ 'client_id': client_id,
21
+ 'domain': domain,
22
+ 'scope': scope,
23
+ 'response_type': response_type,
24
+ 'address': address
25
+ }
26
+ headers['Content-Type'] = 'application/x-www-form-urlencoded'
27
+ return self._request(
28
+ 'POST',
29
+ 'Auth',
30
+ '/auth/web3/generate_challenge',
31
+ data=urlencode(body),
32
+ headers=headers
33
+ )
34
+
35
+ async def sign_challenge(self, message, private_key, env="Production"):
36
+ web3 = Web3(Web3.HTTPProvider(dimo_constants[env]['RPC_provider']))
37
+ signed_message = web3.eth.account.sign_message(encode_defunct(text=message), private_key=private_key)
38
+ return signed_message.signature.hex()
39
+
40
+ async def submit_challenge(self, form_data, headers):
41
+ return self._request('POST', 'Auth', '/auth/web3/submit_challenge', data=form_data, headers=headers)
42
+
43
+ # Requires client_id, domain, and private_key. Address defaults to client_id.
44
+ async def get_token(self,
45
+ client_id,
46
+ domain,
47
+ private_key,
48
+ address=None,
49
+ scope='openid email',
50
+ response_type='code',
51
+ grant_type="authorization_code",
52
+ env="Production"):
53
+
54
+ if address is None:
55
+ address = client_id
56
+
57
+ headers = {
58
+ 'Content-Type': 'application/x-www-form-urlencoded'
59
+ }
60
+
61
+ challenge = await self.generate_challenge(
62
+ headers=headers,
63
+ client_id=client_id,
64
+ domain=domain,
65
+ scope=scope,
66
+ response_type=response_type,
67
+ address=address
68
+ )
69
+
70
+ sign = await self.sign_challenge(
71
+ message=challenge['challenge'],
72
+ private_key=private_key,
73
+ env=env
74
+ )
75
+
76
+ body = {
77
+ 'client_id': client_id,
78
+ 'domain': domain,
79
+ 'state': challenge['state'],
80
+ 'signature': sign,
81
+ 'grant_type': grant_type
82
+ }
83
+
84
+ submit = await self.submit_challenge(body, headers)
85
+ return submit
dimo/constants.py ADDED
@@ -0,0 +1,10 @@
1
+ dimo_constants = {
2
+ "Production": {
3
+ "NFT_address": "0xbA5738a18d83D41847dfFbDC6101d37C69c9B0cF",
4
+ "RPC_provider": "https://eth.llamarpc.com"
5
+ },
6
+ "Dev": {
7
+ "NFT_address": "0x45fbCD3ef7361d156e8b16F5538AE36DEdf61Da8",
8
+ "RPC_provider": "https://eth.llamarpc.com"
9
+ }
10
+ }
dimo/device_data.py ADDED
@@ -0,0 +1,118 @@
1
+ class DeviceData:
2
+
3
+ def __init__(self, request_method, get_auth_headers):
4
+ self._request = request_method
5
+ self._get_auth_headers = get_auth_headers
6
+
7
+ async def get_vehicle_history(self, privileged_token, token_id, start_time=None, end_time=None, buckets=None):
8
+ params = {}
9
+ if start_time is not None:
10
+ params['startTime'] = start_time
11
+ if end_time is not None:
12
+ params['endTime'] = end_time
13
+ if buckets is not None:
14
+ params['buckets'] = buckets
15
+ url = f'/v2/vehicle/{token_id}/history'
16
+ return self._request(
17
+ 'GET',
18
+ 'DeviceData',
19
+ url,
20
+ params=params,
21
+ headers=self._get_auth_headers(privileged_token)
22
+ )
23
+
24
+ async def get_vehicle_status(self, privileged_token, token_id):
25
+ url = f'/v2/vehicle/{token_id}/status'
26
+ return self._request(
27
+ 'GET',
28
+ 'DeviceData',
29
+ url,
30
+ headers=self._get_auth_headers(privileged_token)
31
+ )
32
+
33
+ async def get_v1_vehicle_history(self, privileged_token, token_id, start_date=None, end_date=None):
34
+ params = {}
35
+ if start_date is not None:
36
+ params['startDate'] = start_date
37
+ if end_date is not None:
38
+ params['endDate'] = end_date
39
+ url = f'/v1/vehicle/{token_id}/history'
40
+ return self._request(
41
+ 'GET',
42
+ 'DeviceData',
43
+ url,
44
+ params=params,
45
+ headers=self._get_auth_headers(privileged_token)
46
+ )
47
+
48
+ async def get_v1_vehicle_status(self, privileged_token, token_id):
49
+ url = f'/v1/vehicle/{token_id}/status'
50
+ return self._request(
51
+ 'GET',
52
+ 'DeviceData',
53
+ url,
54
+ headers=self._get_auth_headers(privileged_token)
55
+ )
56
+
57
+ async def get_v1_vehicle_status_raw(self, privileged_token, token_id):
58
+ url = f'/v1/vehicle/{token_id}/status-raw'
59
+ return self._request(
60
+ 'GET',
61
+ 'DeviceData',
62
+ url,
63
+ headers=self._get_auth_headers(privileged_token)
64
+ )
65
+
66
+ async def get_user_device_status(self, access_token, user_device_id):
67
+ url = f'/v1/user/device-data/{user_device_id}/status'
68
+ return self._request(
69
+ 'GET',
70
+ 'DeviceData',
71
+ url,
72
+ headers=self._get_auth_headers(access_token)
73
+ )
74
+
75
+ async def get_user_device_history(self, access_token, user_device_id, start_date=None, end_date=None):
76
+ params = {}
77
+ if start_date is not None:
78
+ params['startDate'] = start_date
79
+ if end_date is not None:
80
+ params['endDate'] = end_date
81
+ url = f'/v1/user/device-data/{user_device_id}/historical'
82
+ return self._request(
83
+ 'GET',
84
+ 'DeviceData',
85
+ url,
86
+ params=params,
87
+ headers=self._get_auth_headers(access_token),
88
+ )
89
+
90
+ async def get_daily_distance(self, access_token, user_device_id, time_zone):
91
+ params = {
92
+ 'timeZone': time_zone
93
+ }
94
+ url = f'/v1/user/device-data/{user_device_id}/daily-distance'
95
+ return self._request(
96
+ 'GET',
97
+ 'DeviceData',
98
+ url,
99
+ headers=self._get_auth_headers(access_token),
100
+ params=params)
101
+
102
+ async def get_total_distance(self, access_token, user_device_id):
103
+ url = f'/v1/user/device-data/{user_device_id}/distance-driven'
104
+ return self._request(
105
+ 'GET',
106
+ 'DeviceData',
107
+ url,
108
+ headers=self._get_auth_headers(access_token)
109
+ )
110
+
111
+ async def send_json_export_email(self, access_token, user_device_id):
112
+ url = f'/v1/user/device-data/{user_device_id}/export/json/email'
113
+ return self._request(
114
+ 'POST',
115
+ 'DeviceData',
116
+ url,
117
+ headers=self._get_auth_headers(access_token)
118
+ )
@@ -0,0 +1,39 @@
1
+ class DeviceDefinitions:
2
+
3
+ def __init__(self, request_method, get_auth_headers):
4
+ self._request = request_method
5
+ self._get_auth_headers = get_auth_headers
6
+
7
+ async def get_by_mmy(self, make, model, year):
8
+ params = {
9
+ 'make': make,
10
+ 'model': model,
11
+ 'year': year
12
+ }
13
+ return self._request(
14
+ 'GET',
15
+ 'DeviceDefinitions',
16
+ '/device-definitions',
17
+ params=params
18
+ )
19
+
20
+ async def get_by_id(self, id):
21
+ url = f'/device-definitions/{id}'
22
+ return self._request(
23
+ 'GET',
24
+ 'DeviceDefinitions',
25
+ url)
26
+
27
+ async def list_device_makes(self):
28
+ return self._request(
29
+ 'GET',
30
+ 'DeviceDefinitions',
31
+ '/device-makes'
32
+ )
33
+
34
+ async def get_device_type_by_id(self, id):
35
+ url = f'/device-types/{id}'
36
+ return self._request(
37
+ 'GET',
38
+ 'DeviceDefinitions',
39
+ url)
dimo/devices.py ADDED
@@ -0,0 +1,252 @@
1
+ class Devices:
2
+
3
+ def __init__(self, request_method, get_auth_headers):
4
+ self._request = request_method
5
+ self._get_auth_headers = get_auth_headers
6
+
7
+ async def create_vehicle(self, access_token, country_code, device_definition_id):
8
+ body = {
9
+ 'countryCode': country_code,
10
+ 'deviceDefinitionId': device_definition_id
11
+ }
12
+ return self._request(
13
+ 'POST',
14
+ 'Devices',
15
+ '/v1/user/devices',
16
+ headers=self._get_auth_headers(access_token),
17
+ data=body
18
+ )
19
+
20
+ async def create_vehicle_from_smartcar(self, access_token, code, country_code, redirect_uri):
21
+ body = {
22
+ 'code': code,
23
+ 'countryCode': country_code,
24
+ 'redirectURI': redirect_uri
25
+ }
26
+ return self._request(
27
+ 'POST',
28
+ 'Devices',
29
+ '/v1/user/devices/fromsmartcar',
30
+ headers=self._get_auth_headers(access_token),
31
+ data=body
32
+ )
33
+
34
+ async def create_vehicle_from_vin(self, access_token, can_protocol, country_code, vin):
35
+ body = {
36
+ 'canProtocol': can_protocol,
37
+ 'countryCode': country_code,
38
+ 'vin': vin
39
+ }
40
+ return self._request(
41
+ 'POST',
42
+ 'Devices',
43
+ '/v1/user/devices/fromvin',
44
+ headers=self._get_auth_headers(access_token),
45
+ data=body
46
+ )
47
+
48
+ async def update_vehicle_vin(self, access_token, user_device_id):
49
+ url = f'/v1/user/devices/{user_device_id}/vin'
50
+ return self._request(
51
+ 'PATCH',
52
+ 'Devices',
53
+ url,
54
+ headers=self._get_auth_headers(access_token)
55
+ )
56
+
57
+ async def get_claiming_payload(self, access_token, serial):
58
+ url = f'/v1/aftermarket/device/by-serial/{serial}/commands/claim'
59
+ return self._request(
60
+ 'POST',
61
+ 'Devices',
62
+ url,
63
+ headers=self._get_auth_headers(access_token)
64
+ )
65
+
66
+ async def sign_claiming_payload(self, access_token, serial, claim_request):
67
+ body = {
68
+ 'claimRequest': claim_request
69
+ }
70
+ url = f'/v1/aftermarket/device/by-serial/{serial}/commands/claim'
71
+ return self._request(
72
+ 'POST',
73
+ 'Devices',
74
+ url,
75
+ headers=self._get_auth_headers(access_token),
76
+ data=body
77
+ )
78
+
79
+ async def get_minting_payload(self, access_token, user_device_id):
80
+ url = f'/v1/user/devices/{user_device_id}/commands/mint'
81
+ return self._request(
82
+ 'POST',
83
+ 'Devices',
84
+ url,
85
+ headers=self._get_auth_headers(access_token)
86
+ )
87
+
88
+ async def sign_minting_payload(self, access_token, user_device_id, mint_request):
89
+ body = {
90
+ 'mintRequest': mint_request
91
+ }
92
+ url = f'/v1/user/devices/{user_device_id}/commands/mint'
93
+ return self._request(
94
+ 'POST',
95
+ 'Devices',
96
+ url,
97
+ headers=self._get_auth_headers(access_token),
98
+ data=body
99
+ )
100
+
101
+ async def opt_in_share_data(self, access_token, user_device_id):
102
+ url = f'/v1/user/devices/{user_device_id}/commands/opt-in'
103
+ return self._request(
104
+ 'POST',
105
+ 'Devices',
106
+ url,
107
+ headers=self._get_auth_headers(access_token)
108
+ )
109
+
110
+ async def refresh_smartcar_data(self, access_token, user_device_id):
111
+ url = f'/v1/user/devices/{user_device_id}/commands/refresh'
112
+ return self._request(
113
+ 'POST',
114
+ 'Devices',
115
+ url,
116
+ headers=self._get_auth_headers(access_token)
117
+ )
118
+
119
+ async def get_pairing_payload(self, access_token, user_device_id):
120
+ url = f'/v1/user/devices/{user_device_id}/aftermarket/commands/pair'
121
+ return self._request(
122
+ 'GET',
123
+ 'Devices',
124
+ url,
125
+ headers=self._get_auth_headers(access_token)
126
+ )
127
+
128
+ async def sign_pairing_payload(self, access_token, user_device_id, user_signature):
129
+ body = {
130
+ 'userSignature': user_signature
131
+ }
132
+ url = f'/v1/user/devices/{user_device_id}/aftermarket/commands/pair'
133
+ return self._request(
134
+ 'POST',
135
+ 'Devices',
136
+ url,
137
+ headers=self._get_auth_headers(access_token),
138
+ data=body
139
+ )
140
+
141
+ async def get_unpairing_payload(self, access_token, user_device_id):
142
+ url = f'/v1/user/devices/{user_device_id}/aftermarket/commands/unpair'
143
+ return self._request(
144
+ 'GET',
145
+ 'Devices',
146
+ url,
147
+ headers=self._get_auth_headers(access_token)
148
+ )
149
+
150
+ async def sign_unpairing_payload(self, access_token, user_device_id, user_signature):
151
+ body = {
152
+ 'userSignature': user_signature
153
+ }
154
+ url = f'/v1/user/devices/{user_device_id}/aftermarket/commands/unpair'
155
+ return self._request(
156
+ 'POST',
157
+ 'Devices',
158
+ url,
159
+ headers=self._get_auth_headers(access_token),
160
+ data=body
161
+ )
162
+
163
+ async def lock_doors(self, privilege_token, token_id):
164
+ url = f'/v1/vehicle/{token_id}/commands/doors/lock'
165
+ return self._request(
166
+ 'POST',
167
+ 'Devices',
168
+ url,
169
+ headers=self._get_auth_headers(privilege_token)
170
+ )
171
+
172
+ async def unlock_doors(self, privilege_token, token_id):
173
+ url = f'/v1/vehicle/{token_id}/commands/doors/unlock'
174
+ return self._request(
175
+ 'POST',
176
+ 'Devices',
177
+ url,
178
+ headers=self._get_auth_headers(privilege_token)
179
+ )
180
+
181
+ async def open_frunk(self, privilege_token, token_id):
182
+ url = f'/v1/vehicle/{token_id}/commands/frunk/open'
183
+ return self._request(
184
+ 'POST',
185
+ 'Devices',
186
+ url,
187
+ headers=self._get_auth_headers(privilege_token)
188
+ )
189
+
190
+ async def open_trunk(self, privilege_token, token_id):
191
+ url = f'/v1/vehicle/{token_id}/commands/trunk/open'
192
+ return self._request(
193
+ 'POST',
194
+ 'Devices',
195
+ url,
196
+ headers=self._get_auth_headers(privilege_token)
197
+ )
198
+
199
+ async def list_error_codes(self, access_token, user_device_id):
200
+ url = f'/v1/user/devices/{user_device_id}/error-codes'
201
+ return self._request(
202
+ 'GET',
203
+ 'Devices',
204
+ url,
205
+ headers=self._get_auth_headers(access_token)
206
+ )
207
+
208
+ async def submit_error_codes(self, access_token, user_device_id, query_device_error_codes):
209
+ body = {
210
+ 'queryDeviceErrorCodes': query_device_error_codes
211
+ }
212
+ url = f'/v1/user/devices/{user_device_id}/error-codes'
213
+ return self._request(
214
+ 'POST',
215
+ 'Devices',
216
+ url,
217
+ headers=self._get_auth_headers(access_token),
218
+ data=body
219
+ )
220
+
221
+ async def clear_error_codes(self, access_token, user_device_id):
222
+ url = f'/v1/user/devices/{user_device_id}/error-codes/clear'
223
+ return self._request(
224
+ 'POST',
225
+ 'Devices',
226
+ url,
227
+ headers=self._get_auth_headers(access_token)
228
+ )
229
+
230
+ async def get_aftermarket_device(self, token_id):
231
+ url = f'/v1/aftermarket/device/{token_id}'
232
+ self._request(
233
+ 'GET',
234
+ 'Devices',
235
+ url
236
+ )
237
+
238
+ async def get_aftermarket_device_image(self, token_id):
239
+ url = f'/v1/aftermarket/device/{token_id}/image'
240
+ self._request(
241
+ 'GET',
242
+ 'Devices',
243
+ url
244
+ )
245
+
246
+ async def get_aftermarket_device_metadata_by_address(self, address):
247
+ url = f'/v1/aftermarket/device/by-address/{address}'
248
+ self._request(
249
+ 'GET',
250
+ 'Devices',
251
+ url
252
+ )
dimo/dimo.py ADDED
@@ -0,0 +1,79 @@
1
+ from dimo.auth import Auth
2
+ from dimo.device_data import DeviceData
3
+ from dimo.device_definitions import DeviceDefinitions
4
+ from dimo.devices import Devices
5
+ from dimo.events import Events
6
+ from dimo.token_exchange import TokenExchange
7
+ from dimo.trips import Trips
8
+ from dimo.user import User
9
+ from dimo.valuations import Valuations
10
+ from dimo.vehicle_signal_decoding import VehicleSignalDecoding
11
+
12
+ from dimo.identity import Identity
13
+ from dimo.telemetry import Telemetry
14
+
15
+ from dimo.request import Request
16
+ from dimo.environments import dimo_environment
17
+ import re
18
+
19
+ class DIMO:
20
+
21
+ def __init__(self, env="Production"):
22
+ self.env = env
23
+ self.urls = dimo_environment[env]
24
+ self.auth = Auth(self.request, self._get_auth_headers)
25
+ self.device_data = DeviceData(self.request, self._get_auth_headers)
26
+ self.device_definitions = DeviceDefinitions(self.request, self._get_auth_headers)
27
+ self.devices = Devices(self.request, self._get_auth_headers)
28
+ self.events = Events(self.request, self._get_auth_headers)
29
+ self.token_exchange = TokenExchange(self.request, self._get_auth_headers)
30
+ self.trips = Trips(self.request, self._get_auth_headers)
31
+ self.user = User(self.request, self._get_auth_headers)
32
+ self.valuations = Valuations(self.request, self._get_auth_headers)
33
+ self.vehicle_signal_decoding = VehicleSignalDecoding(self.request, self._get_auth_headers)
34
+ self.identity = Identity(self)
35
+ self.telemetry = Telemetry(self)
36
+ self._session = Request.session
37
+
38
+ # Creates a full path for endpoints combining DIMO service, specific endpoint, and optional params
39
+ def _get_full_path(self, service, path, params=None):
40
+ base_path = self.urls[service]
41
+ full_path = f"{base_path}{path}"
42
+
43
+ if params:
44
+ for key, value in params.items():
45
+ pattern = f":{key}"
46
+ full_path = re.sub(pattern, str(value), full_path)
47
+ return full_path
48
+
49
+ # Sets headers based on access_token or privileged_token
50
+ def _get_auth_headers(self, token):
51
+ return {
52
+ 'Authorization': f'Bearer {token}',
53
+ 'Content-Type': 'application/json'
54
+ }
55
+
56
+ # request method for HTTP requests for the REST API
57
+ def request(self, http_method, service, path, **kwargs):
58
+ full_path = self._get_full_path(service, path)
59
+ return Request(http_method, full_path)(**kwargs)
60
+
61
+ # query method for graphQL queries, identity and telemetry
62
+ async def query(self, service, query, variables=None, token=None):
63
+ headers = self._get_auth_headers(token) if token else {}
64
+ headers['Content-Type'] = 'application/json'
65
+ headers['User-Agent'] = 'dimo-python-sdk'
66
+
67
+ data = {
68
+ 'query': query,
69
+ 'variables': variables or {}
70
+ }
71
+
72
+ response = self.request(
73
+ 'POST',
74
+ service,
75
+ '',
76
+ headers=headers,
77
+ data=data
78
+ )
79
+ return response
dimo/environments.py ADDED
@@ -0,0 +1,30 @@
1
+ dimo_environment = {
2
+ "Production": {
3
+ 'Auth': 'https://auth.dimo.zone',
4
+ 'Identity': 'https://identity-api.dimo.zone/query',
5
+ 'Devices': 'https://devices-api.dimo.zone',
6
+ 'DeviceData': 'https://device-data-api.dimo.zone',
7
+ 'DeviceDefinitions': 'https://device-definitions-api.dimo.zone',
8
+ 'Events': 'https://events-api.dimo.zone',
9
+ 'Telemetry': 'https://telemetry-api.dimo.zone/query',
10
+ 'TokenExchange': 'https://token-exchange-api.dimo.zone',
11
+ 'Trips': 'https://trips-api.dimo.zone',
12
+ 'User': 'https://users-api.dimo.zone',
13
+ 'Valuations': 'https://valuations-api.dimo.zone',
14
+ 'VehicleSignalDecoding': 'https://vehicle-signal-decoding.dimo.zone'
15
+ },
16
+ "Dev": {
17
+ 'Auth': 'https://auth.dev.dimo.zone',
18
+ 'Identity': 'https://identity-api.dev.dimo.zone/query',
19
+ 'Devices': 'https://devices-api.dev.dimo.zone',
20
+ 'DeviceData': 'https://device-data-api.dev.dimo.zone',
21
+ 'DeviceDefinitions': 'https://device-definitions-api.dev.dimo.zone',
22
+ 'Events': 'https://events-api.dev.dimo.zone',
23
+ 'Telemetry': 'https://telemetry-api.dev.dimo.zone/query',
24
+ 'TokenExchange': 'https://token-exchange-api.dev.dimo.zone',
25
+ 'Trips': 'https://trips-api.dev.dimo.zone',
26
+ 'User': 'https://users-api.dev.dimo.zone',
27
+ 'Valuations': 'https://valuations-api.dev.dimo.zone',
28
+ 'VehicleSignalDecoding': 'https://vehicle-signal-decoding.dev.dimo.zone'
29
+ }
30
+ }
dimo/events.py ADDED
@@ -0,0 +1,13 @@
1
+ class Events:
2
+
3
+ def __init__(self, request_method, get_auth_headers):
4
+ self._request = request_method
5
+ self._get_auth_headers = get_auth_headers
6
+
7
+ async def get_events(self, access_token):
8
+ return self._request(
9
+ 'GET',
10
+ 'Events',
11
+ '/v1/events',
12
+ headers=self._get_auth_headers(access_token)
13
+ )