autoverse-cli 0.28.3__py3-none-any.whl → 0.29.1.dev409__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autoverse-cli
3
- Version: 0.28.3
3
+ Version: 0.29.1.dev409
4
4
  Summary: The Autoverse CLI
5
5
  Author-email: Dan Kamrath <dan.kamrath@autonomalabs.com>
6
6
  License: # End-User License Agreement (EULA) of the Autonoma AutoVerse CLI
@@ -52,10 +52,31 @@ The new implementation of the Autoverse CLI
52
52
 
53
53
  ## Installation
54
54
 
55
+ ### Stable Release (Recommended)
55
56
  ```bash
56
57
  pip install autoverse-cli
57
58
  ```
58
59
 
60
+ ### Development Versions
61
+
62
+ Development versions are published from feature branches and follow the format `X.Y.Z.devN`.
63
+
64
+ ```bash
65
+ # Install the latest development version
66
+ pip install autoverse-cli --pre
67
+
68
+ # Install a specific development version
69
+ pip install autoverse-cli==0.29.1.dev123
70
+
71
+ # Check available versions (including dev)
72
+ pip index versions autoverse-cli
73
+
74
+ # Upgrade to latest dev version
75
+ pip install --upgrade --pre autoverse-cli
76
+ ```
77
+
78
+ > **Note:** `pip install autoverse-cli` (without `--pre`) always installs the latest stable release from `main`. Development versions require the `--pre` flag explicitly.
79
+
59
80
  ## Shell Completion
60
81
 
61
82
  To enable tab completion for the `avrs` command, run:
@@ -1,6 +1,6 @@
1
- autoverse_cli-0.28.3.dist-info/licenses/LICENSE,sha256=d4eWXho-u18HkBsX4K21uHX_bBb2UXZSrJdsb7Z_JlM,2647
1
+ autoverse_cli-0.29.1.dev409.dist-info/licenses/LICENSE,sha256=d4eWXho-u18HkBsX4K21uHX_bBb2UXZSrJdsb7Z_JlM,2647
2
2
  avrs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- avrs/app_version.py,sha256=ullV714kk4FcU4YhcTqWvwnpaDh3TO1AMKAxlmgp2dM,857
3
+ avrs/app_version.py,sha256=Vb1DRkLyCKiKGLD9odhFnqePSW-VWpkduU5iQjFLvrw,864
4
4
  avrs/argparse_help.py,sha256=EoEaohGXZXqJvs1dFEzbo9vh47CYdHdSY2Im2Ps2iFo,945
5
5
  avrs/avrs.py,sha256=pQHc8KLAD0hCxpBoNbXOgj2Et3qiqxSMKyAXFLwWFjE,5787
6
6
  avrs/can_tool.py,sha256=IBoctKobBJ3wCq4ZdtuXuXH4AMEUxazCfYT6YP0Luw8,7161
@@ -8,11 +8,11 @@ avrs/can_tool_util.py,sha256=G3q21dTGMLc09SDq_RieoW7kSn-kXPkXxPDjavPh4hQ,7710
8
8
  avrs/cfg.py,sha256=kMn08Z4Ms1PAu2-U1QI8weEJeIVEppP4A4_tFfwnjtg,2924
9
9
  avrs/launcher.py,sha256=uk7xG2JTLJv7FJMDo1diwc-zmTvUxXJME9-0ctrZRuk,11352
10
10
  avrs/launcher_util.py,sha256=zXUbJByn9M-ErkNM6DvLCFtUkueYH-R0dehjQj0aYEs,6879
11
- avrs/race_cloud.py,sha256=jcAUPYUSQvO1P-DASkiLpYRzfK7ObHtjqaQ4p_Fg7y8,20143
11
+ avrs/race_cloud.py,sha256=Pl8YrefTFJzhax2qFEEQVrZtezANbqNX7pxOcsjkirA,23728
12
12
  avrs/race_cloud_bridge_can.py,sha256=K3is7GFzWo2KBojr6V65kz138UfkVFXXofVxSAziArY,2888
13
- avrs/race_cloud_cfg_util.py,sha256=kxeVqCAs1UaZ8FxAIScJcIIn5kR3qtC026os-HpdV5A,13706
13
+ avrs/race_cloud_cfg_util.py,sha256=TF_gsP7c_39MKs1CyzeXkTH-Fwyd8wLkFkMbqQVmdeo,12066
14
14
  avrs/race_cloud_fwd_api.py,sha256=jB4kt6Shu9Pj96WPVjK790PZjucYxmuHjjVKczQfyZI,1620
15
- avrs/race_cloud_util.py,sha256=yV7NC7-zGpCvXxzCq2nzRQMu3YdbwNFzQyT59k2V00I,15175
15
+ avrs/race_cloud_util.py,sha256=0wGVEwnJi5ndZyiBoIp-4QdojhAshr082cUJVjE8a8s,16805
16
16
  avrs/shell_completion.py,sha256=_IM85ADKQHYNMlTa1_nhnYWsgdOzRQ7tIFHxAT6EWWA,4298
17
17
  avrs/simconfig.py,sha256=UZc4FrxVHUkk2e9IglSpuojXkaL_nPrJW7trbEGgOOo,2907
18
18
  avrs/simconfig_util.py,sha256=ronvXg9Mraz60K99ABBvo_YDI1_rWphUWUn_s_voTKw,6044
@@ -44,8 +44,8 @@ avrs/requests/toggle_hud.py,sha256=sV5t5QZc4uvRihPVk8jEKZiQNsyF9tsUwq5b4jjZplc,3
44
44
  avrs/requests/vd.py,sha256=Rd0mCc89m2yKjftVf0cMHMFbJG4iFd09KzrI-DsCLgI,2606
45
45
  avrs/requests/vehicle_input.py,sha256=R1b1xkRtOBkwyU6OSN4bWVlYsIUroZG0WKbYbc4pEH8,729
46
46
  avrs/requests/vehicle_replay.py,sha256=7HPfVLjSLNbOyzU77Bz29qpHqRsMUAIWvnwvnNiw2nk,15026
47
- autoverse_cli-0.28.3.dist-info/METADATA,sha256=EXbQmiNN-6TDB1n9TPkZESMdFANMOU726lVwhCV5s7c,3935
48
- autoverse_cli-0.28.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
49
- autoverse_cli-0.28.3.dist-info/entry_points.txt,sha256=Cb9qsUyU5AKkklehCcvtfT0-N3SXbUEqvjze4iEU5kE,40
50
- autoverse_cli-0.28.3.dist-info/top_level.txt,sha256=-AJO2e4MCVej6hY0U84pu5NfMeMW5qaAPSMisDT5rmA,5
51
- autoverse_cli-0.28.3.dist-info/RECORD,,
47
+ autoverse_cli-0.29.1.dev409.dist-info/METADATA,sha256=63780yqs6dL_q95_hsfp4xHIMcWfFkT5-cBtZ55kNGU,4588
48
+ autoverse_cli-0.29.1.dev409.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
49
+ autoverse_cli-0.29.1.dev409.dist-info/entry_points.txt,sha256=Cb9qsUyU5AKkklehCcvtfT0-N3SXbUEqvjze4iEU5kE,40
50
+ autoverse_cli-0.29.1.dev409.dist-info/top_level.txt,sha256=-AJO2e4MCVej6hY0U84pu5NfMeMW5qaAPSMisDT5rmA,5
51
+ autoverse_cli-0.29.1.dev409.dist-info/RECORD,,
avrs/app_version.py CHANGED
@@ -2,7 +2,7 @@ import http.client
2
2
  import json
3
3
 
4
4
  def get_app_version():
5
- return '0.28.2'
5
+ return '0.29.1.dev409'
6
6
 
7
7
  def check_app_is_latest():
8
8
  pass
avrs/race_cloud.py CHANGED
@@ -2,6 +2,7 @@ import subprocess
2
2
  import base64
3
3
  import logging
4
4
  import time
5
+ import ipaddress
5
6
  from avrs.cfg import *
6
7
  from avrs.race_cloud_util import *
7
8
  from avrs.race_cloud_cfg_util import *
@@ -11,6 +12,47 @@ from avrs.util import *
11
12
  from avrs.requests.request import AvrsApiRequest
12
13
  from argparse import RawTextHelpFormatter
13
14
 
15
+
16
+ def is_valid_public_ip(value):
17
+ """Check if a string is a valid public IP address (not private, not loopback, not reserved)."""
18
+ try:
19
+ ip = ipaddress.ip_address(value)
20
+ # Reject private, loopback, reserved, multicast, and link-local addresses
21
+ return not (ip.is_private or ip.is_loopback or ip.is_reserved or
22
+ ip.is_multicast or ip.is_link_local)
23
+ except ValueError:
24
+ return False
25
+
26
+
27
+ def parse_sim_target(value):
28
+ """
29
+ Parse sim_target argument: can be a legacy index (0-5) or a public IP address.
30
+ Returns (is_valid, parsed_value, is_ip).
31
+ """
32
+ # First, check if it's a legacy integer index (0-5)
33
+ try:
34
+ index = int(value)
35
+ if 0 <= index <= 5:
36
+ return (True, index, False)
37
+ else:
38
+ return (False, "Legacy index must be between 0 and 5", False)
39
+ except ValueError:
40
+ pass
41
+
42
+ # Check if it's a valid public IP address
43
+ try:
44
+ ip = ipaddress.ip_address(value)
45
+ if is_valid_public_ip(value):
46
+ # Valid public IP address
47
+ return (True, value, True)
48
+ else:
49
+ # IP is private, loopback, or otherwise not public
50
+ return (False, "IP address must be public (not private, loopback, or reserved)", False)
51
+ except ValueError:
52
+ pass
53
+
54
+ return (False, "Must be a legacy index (0-5) or a valid public IP address", False)
55
+
14
56
  class AvrsRaceCloud(AvrsApiRequest):
15
57
  def __init__(self, parser, cfg):
16
58
  self.cfg = cfg
@@ -24,10 +66,9 @@ class AvrsRaceCloud(AvrsApiRequest):
24
66
  'connect',
25
67
  help='connect to an instance for cloud racing')
26
68
  connect_parser.add_argument(
27
- 'sim_index',
28
- type = int,
29
- choices = [0, 1, 2, 3, 4, 5],
30
- help='the index of the simulator instance to connect to')
69
+ 'sim_target',
70
+ type=str,
71
+ help='simulator target: legacy index (0-5) or PUBLIC IP address of the server instance (private IPs not allowed)')
31
72
  connect_parser.add_argument(
32
73
  'team_name',
33
74
  help='the name of the team to race under')
@@ -74,10 +115,9 @@ class AvrsRaceCloud(AvrsApiRequest):
74
115
  'sim-ctrl',
75
116
  help='control the sim program (start, stop, restart, reset)')
76
117
  sim_ctrl_parser.add_argument(
77
- 'sim_index',
78
- type=int,
79
- choices=[0, 1, 2, 3, 4, 5],
80
- help='the index of the simulator instance to apply the action')
118
+ 'sim_target',
119
+ type=str,
120
+ help='simulator target: legacy index (0-5) or PUBLIC IP address of the server instance (private IPs not allowed)')
81
121
  sim_ctrl_parser.add_argument(
82
122
  'action',
83
123
  choices=['start', 'stop', 'restart', 'reset-connection', 'get-log'],
@@ -169,15 +209,29 @@ class AvrsRaceCloud(AvrsApiRequest):
169
209
  def race_connect(self, args):
170
210
  logger = logging.getLogger('avrs')
171
211
 
212
+ print('[DEBUG] ===== RACE CONNECT START =====')
213
+ print('[DEBUG] sim_target argument: {}'.format(args.sim_target))
214
+
215
+ # Validate sim_target (can be legacy index 0-5 or public IP only)
216
+ is_valid, parsed_value, is_ip = parse_sim_target(args.sim_target)
217
+ print('[DEBUG] parse_sim_target result: is_valid={}, parsed_value={}, is_ip={}'.format(
218
+ is_valid, parsed_value, is_ip))
219
+
220
+ if not is_valid:
221
+ print('invalid sim_target: {}'.format(parsed_value))
222
+ return
223
+
172
224
  # make api call to begin connection
173
225
  our_ip = get_local_instance_ip()
226
+ print('[DEBUG] Local instance IP (our_ip): {}'.format(our_ip))
174
227
 
175
228
  if our_ip == '127.0.0.1':
176
229
  print('this machines IP was returned as localhost. was this run on the cloud instance?')
177
230
  return
178
231
 
179
- logger.info('starting race-cloud connect for team {} to instance {}'.format(
180
- args.team_name, args.sim_index))
232
+ target_display = args.sim_target if is_ip else 'index {}'.format(parsed_value)
233
+ logger.info('starting race-cloud connect for team {} to {}'.format(
234
+ args.team_name, target_display))
181
235
  print('connecting to race with team name: {}'.format(args.team_name))
182
236
 
183
237
  if args.no_restart_sim:
@@ -217,7 +271,7 @@ class AvrsRaceCloud(AvrsApiRequest):
217
271
 
218
272
  connection_request = {
219
273
  'action': 'connect',
220
- 'sim_index': args.sim_index,
274
+ 'sim_id': str(parsed_value), # Can be index (0-5) or public IP address only
221
275
  'ip_to_reserve': our_ip,
222
276
  'team_name': args.team_name,
223
277
  'sim_id_override': args.instance_id,
@@ -226,11 +280,18 @@ class AvrsRaceCloud(AvrsApiRequest):
226
280
  'config_data': vcfg_data
227
281
  }
228
282
 
283
+ print('[DEBUG] ===== CONNECTION REQUEST =====')
284
+ print('[DEBUG] sim_id (parsed): {}'.format(parsed_value))
285
+ print('[DEBUG] our_ip: {}'.format(our_ip))
286
+ print('[DEBUG] team_name: {}'.format(args.team_name))
287
+ print('[DEBUG] instance_id override: {}'.format(args.instance_id))
288
+
229
289
  ok, response = call_race_cloud_api(connection_request)
230
290
  if not ok:
231
291
  print('connect api error: {}'.format(response))
232
292
  return
233
293
 
294
+ print('[DEBUG] API call succeeded, parsing response...')
234
295
  ok, rbody, sim_ip = get_api_script_response(response)
235
296
  if not ok:
236
297
  print(rbody)
@@ -242,9 +303,27 @@ class AvrsRaceCloud(AvrsApiRequest):
242
303
 
243
304
  slot_info = {}
244
305
  for k, v in rbody.items():
245
- #print('out: {}'.format(v['stdout']))
246
- #print('err: {}'.format(v['stderr']))
247
- slot_info = json.loads(v['stdout'])
306
+ logger.info('Processing response for key: {}'.format(k))
307
+ logger.info('stdout: {}'.format(v.get('stdout', '')))
308
+ logger.info('stderr: {}'.format(v.get('stderr', '')))
309
+
310
+ stdout = v.get('stdout', '').strip()
311
+ stderr = v.get('stderr', '').strip()
312
+
313
+ if not stdout:
314
+ print('Error: No stdout from remote command')
315
+ if stderr:
316
+ print('stderr: {}'.format(stderr))
317
+ return
318
+
319
+ try:
320
+ slot_info = json.loads(stdout)
321
+ except json.JSONDecodeError as e:
322
+ print('Error parsing JSON from stdout: {}'.format(e))
323
+ print('stdout content: {}'.format(stdout[:500])) # Print first 500 chars
324
+ if stderr:
325
+ print('stderr: {}'.format(stderr))
326
+ return
248
327
 
249
328
  if not slot_info['ok']:
250
329
  print('issue reserving slot: {}'.format(slot_info['msg']))
@@ -406,6 +485,12 @@ class AvrsRaceCloud(AvrsApiRequest):
406
485
  def remote_sim_ctrl(self, args):
407
486
  logger = logging.getLogger('avrs')
408
487
 
488
+ # Validate sim_target (can be legacy index 0-5 or public IP only)
489
+ is_valid, parsed_value, is_ip = parse_sim_target(args.sim_target)
490
+ if not is_valid:
491
+ print('invalid sim_target: {}'.format(parsed_value))
492
+ return
493
+
409
494
  # go ahead and reset local connection as well
410
495
  if args.action == 'reset-connection':
411
496
  reset_race_cloud_connection()
@@ -414,7 +499,7 @@ class AvrsRaceCloud(AvrsApiRequest):
414
499
 
415
500
  reset_request = {
416
501
  'action': args.action,
417
- 'sim_index': args.sim_index,
502
+ 'sim_id': str(parsed_value), # Can be index (0-5) or public IP address only
418
503
  'ip_to_reserve': our_ip,
419
504
  'sim_id_override': args.instance_id,
420
505
  'ensure_instance_is_running': False
@@ -78,13 +78,13 @@ def register_received_vehicle(team_name, slot, cfg_data, bsu_vcan, kistler_vcan,
78
78
  if eav24 is None:
79
79
  return (False, 'no eav24 payload found')
80
80
 
81
- #logger.info('disabling perception for received vehicle config')
82
- # eav24['body']['bLidarEnabled'] = False
81
+ logger.info('disabling perception for received vehicle config')
82
+ eav24['body']['bLidarEnabled'] = False
83
83
  eav24['body']['bCameraEnabled'] = False
84
84
  eav24['body']['bRadarEnabled'] = False
85
- # eav24['body']['bPublishGroundTruth'] = False
85
+ eav24['body']['bPublishGroundTruth'] = False
86
86
  eav24['body']['bPublishInputs'] = False
87
- eav24['body']['bRenderHudInWorld'] = False
87
+ eav24['body']['bRenderHudInWorld'] = True
88
88
 
89
89
  # do not disable HUD
90
90
  #logger.info('disabling hud for received vehicle config')
@@ -208,32 +208,6 @@ def register_received_vehicle(team_name, slot, cfg_data, bsu_vcan, kistler_vcan,
208
208
  logger.info("opponentGroundTruthDsd rate was > 100. clamping")
209
209
  gtc["body"]["opponentGroundTruthDsd"]["rateHz"] = 100
210
210
 
211
- # clamp lidar rates
212
- for payload in cfg_object['payloads']:
213
- if payload['typeName'].lower() == 'genericlidaripd':
214
- lidar_rate = payload.get("body", {}).get("sensorDesc", {}).get("dataStream", {}).get("rateHz", 0)
215
- if lidar_rate > 20:
216
- instance_name = payload.get("body", {}).get("componentConfig", {}).get("instanceName", "unknown")
217
- logger.info("lidar {} rate was > 20. clamping".format(instance_name))
218
- payload["body"]["sensorDesc"]["dataStream"]["rateHz"] = 20
219
-
220
- # clamp camera rates
221
- for payload in cfg_object['payloads']:
222
- if payload['typeName'].lower() == 'camerasensor':
223
- camera_rate = payload.get("body", {}).get("sensorDesc", {}).get("dataStream", {}).get("rateHz", 0)
224
- if camera_rate > 20:
225
- instance_name = payload.get("body", {}).get("componentConfig", {}).get("instanceName", "unknown")
226
- logger.info("camera {} rate was > 20. clamping".format(instance_name))
227
- payload["body"]["sensorDesc"]["dataStream"]["rateHz"] = 20
228
-
229
- # clamp radar rates
230
- for payload in cfg_object['payloads']:
231
- if payload['typeName'].lower() == 'radarsensor':
232
- radar_rate = payload.get("body", {}).get("sensorDesc", {}).get("dataStream", {}).get("rateHz", 0)
233
- if radar_rate > 20:
234
- instance_name = payload.get("body", {}).get("componentConfig", {}).get("instanceName", "unknown")
235
- logger.info("radar {} rate was > 20. clamping".format(instance_name))
236
- payload["body"]["sensorDesc"]["dataStream"]["rateHz"] = 20
237
211
 
238
212
  # do not allow default object name (collision)
239
213
  if cfg_object['name'] == 'eav24':
avrs/race_cloud_util.py CHANGED
@@ -1,7 +1,10 @@
1
1
  import os
2
2
  import json
3
- import http.client
4
3
  import logging
4
+ import boto3
5
+ from botocore.auth import SigV4Auth
6
+ from botocore.awsrequest import AWSRequest
7
+ import urllib.request
5
8
  from avrs.util import *
6
9
 
7
10
  BASH_KILL_PROCESS_SCRIPT = '''
@@ -360,45 +363,83 @@ def call_race_cloud_api(body):
360
363
  logger = logging.getLogger('avrs')
361
364
  logger.info('calling race-cloud api with body: {}'.format(body))
362
365
 
363
- api_url = 'gitzels0l7.execute-api.us-east-1.amazonaws.com'
366
+ url = 'https://9gca6018p0.execute-api.us-east-1.amazonaws.com/backend/race-connect'
367
+ region = 'us-east-1'
364
368
 
365
- connection = http.client.HTTPSConnection(api_url)
366
- headers = {
367
- 'Content-type': 'application/json',
368
- 'x-api-key': '7aQ83sJ89Q2DZ8NdIi9aUTBuUS2uyix5QoDwrl1j'
369
- }
370
- body = json.dumps(body).encode('utf-8')
371
- connection.request('POST', '/beta/connect', body, headers)
372
- response = connection.getresponse()
373
- if response.status != 200:
374
- return (False, 'response had status code {}'.format(response))
375
- return (True, response.read().decode('utf-8'))
369
+ print('[DEBUG] ===== RACE CLOUD API CALL =====')
370
+ print('[DEBUG] URL: {}'.format(url))
371
+ print('[DEBUG] Request body: {}'.format(json.dumps(body, indent=2)))
376
372
 
377
- def get_api_script_response(raw):
378
- logger = logging.getLogger('avrs')
379
- try:
380
- parsed = json.loads(raw)
381
- decoded = parsed['body']
373
+ # Get credentials from boto3 session (uses IAM role on EC2)
374
+ session = boto3.Session()
375
+ credentials = session.get_credentials()
376
+
377
+ if credentials is None:
378
+ print('[DEBUG] ERROR: No AWS credentials found!')
379
+ return (False, 'No AWS credentials available')
382
380
 
383
- # Handle double-encoded JSON (body is a string instead of dict)
384
- if isinstance(decoded, str):
385
- decoded = json.loads(decoded)
381
+ print('[DEBUG] AWS credentials found: access_key={}...'.format(
382
+ credentials.access_key[:10] if credentials.access_key else 'None'))
386
383
 
387
- logger.info('race cloud api response: {}'.format(decoded))
384
+ # Prepare the request body
385
+ body_json = json.dumps(body)
388
386
 
389
- if 'script_response' not in decoded:
390
- return (False, 'missing script_response in response: {}'.format(decoded), None)
387
+ # Create AWS request for signing
388
+ request = AWSRequest(
389
+ method='POST',
390
+ url=url,
391
+ data=body_json,
392
+ headers={'Content-Type': 'application/json'}
393
+ )
391
394
 
392
- if decoded['script_response']['statusCode'] != 200:
393
- return (False, 'inner response had bad status code {}'.format(decoded), None)
395
+ # Sign the request with SigV4
396
+ SigV4Auth(credentials, 'execute-api', region).add_auth(request)
394
397
 
395
- return (True, json.loads(decoded['script_response']['body']), decoded['sim_private_ip'])
396
- except json.JSONDecodeError as e:
397
- logger.error('failed to parse API response as JSON: {}'.format(e))
398
- return (False, 'invalid JSON response: {}'.format(raw[:500]), None)
399
- except KeyError as e:
400
- logger.error('missing expected key in API response: {}'.format(e))
401
- return (False, 'missing key {} in response: {}'.format(e, raw[:500]), None)
398
+ print('[DEBUG] Request headers after signing:')
399
+ for k, v in request.headers.items():
400
+ # Don't print full auth header for security
401
+ if k.lower() == 'authorization':
402
+ print('[DEBUG] {}: {}...'.format(k, v[:50]))
403
+ else:
404
+ print('[DEBUG] {}: {}'.format(k, v))
405
+
406
+ # Make the request using urllib
407
+ try:
408
+ req = urllib.request.Request(
409
+ url,
410
+ data=body_json.encode('utf-8'),
411
+ headers=dict(request.headers),
412
+ method='POST'
413
+ )
414
+ print('[DEBUG] Sending request...')
415
+ with urllib.request.urlopen(req) as response:
416
+ response_body = response.read().decode('utf-8')
417
+ print('[DEBUG] Response status: {}'.format(response.status))
418
+ print('[DEBUG] Response body (first 500 chars): {}'.format(response_body[:500]))
419
+ return (True, response_body)
420
+ except urllib.error.HTTPError as e:
421
+ error_body = e.read().decode('utf-8')
422
+ print('[DEBUG] HTTP ERROR!')
423
+ print('[DEBUG] Status code: {}'.format(e.code))
424
+ print('[DEBUG] Reason: {}'.format(e.reason))
425
+ print('[DEBUG] Headers: {}'.format(dict(e.headers)))
426
+ print('[DEBUG] Body: {}'.format(error_body))
427
+ logger.error('API error - status: {}, body: {}'.format(e.code, error_body))
428
+ return (False, 'response had status code {} - {}'.format(e.code, error_body))
402
429
  except Exception as e:
403
- logger.error('unexpected error parsing API response: {}'.format(e))
404
- return (False, 'error parsing response: {}'.format(e), None)
430
+ print('[DEBUG] EXCEPTION: {} - {}'.format(type(e).__name__, str(e)))
431
+ logger.error('API request failed: {}'.format(str(e)))
432
+ return (False, 'request failed: {}'.format(str(e)))
433
+
434
+ def get_api_script_response(raw):
435
+ logger = logging.getLogger('avrs')
436
+ # Parse API Gateway response, then parse the body which is also JSON string
437
+ api_response = json.loads(raw)
438
+ body_str = api_response['body']
439
+ # Body is a JSON string from Lambda Proxy integration
440
+ decoded = json.loads(body_str) if isinstance(body_str, str) else body_str
441
+ logger.info('race cloud api response: {}'.format(decoded))
442
+ if decoded['script_response']['statusCode'] != 200:
443
+ return (False, 'inner response had bad status code {}'.format(decoded))
444
+ #print(decoded)
445
+ return (True, json.loads(decoded['script_response']['body']), decoded['sim_private_ip'])