Skip to content

Bug: RESTResponse.data is None after ApiClient.call_api despite successful aiohttp response and body read #6

@yeule0

Description

@yeule0

Environment:

  • OS: Linux
  • Python Version: 3.11.9
  • lighter-python SDK Source: Installed via pip install git+https://github.com/elliottech/lighter-python.git

Problem Description:

When using the generic ApiClient.call_api method to make a GET request to an API endpoint known to return valid JSON data, the returned lighter.rest.RESTResponse object incorrectly has its data attribute set to None. This occurs even when the underlying aiohttp request receives a successful HTTP 200 status code and contains a valid response body, which can be confirmed by reading the aiohttp.ClientResponse directly.

This prevents the successful processing and deserialization of responses obtained through ApiClient.call_api, forcing the use of workarounds like direct aiohttp calls for endpoints that may not have dedicated high-level SDK methods (e.g., GET /api/v1/positionFunding).

Steps to Reproduce:

The following minimal script demonstrates the issue using the public /api/v1/orderBookDetails endpoint (which is known to work via the dedicated OrderApi.order_book_details method):

import asyncio
import json
from typing import Optional
import lighter
from lighter import rest 
from lighter.configuration import Configuration
from lighter.api_client import ApiClient

TESTNET_URL = "https://testnet.zklighter.elliot.ai"
ENDPOINT_PATH = "/api/v1/orderBookDetails" 
QUERY_PARAMS = "market_id=0" 

async def run_test():
    print(f"Testing generic call_api to: {ENDPOINT_PATH}?{QUERY_PARAMS}")
    config = Configuration(host=TESTNET_URL)
    api_client = ApiClient(configuration=config)

    response_obj: Optional[rest.RESTResponse] = None # Use Optional for clarity
    try:
        
        full_url = f"{config.host}{ENDPOINT_PATH}?{QUERY_PARAMS}"
        print(f"Calling api_client.call_api with URL: {full_url}")

        response_obj = await api_client.call_api(
            method="GET",
            url=full_url,
            header_params={"Accept": "application/json"}
        )
        

        print(f"call_api response status: {response_obj.status if response_obj else 'N/A'}")
        print(f"call_api response object type: {type(response_obj)}")

     
        if response_obj:
            print(f"call_api response.data is None: {response_obj.data is None}") # <-- EXPECTED: False, ACTUAL: True

            if response_obj.data is not None:
                 print("SUCCESS: response.data is populated.")
                 
            else:
                 print("FAILURE: response.data is None, cannot decode/process.")
                 # This branch is incorrectly hit

    except Exception as e:
        print(f"An error occurred: {e}")
        import traceback
        traceback.print_exc()
    finally:
        if api_client:
            await api_client.close()

if __name__ == "__main__":
    asyncio.run(run_test())

Expected Behavior:

When the script is run:

  1. The api_client.call_api request should succeed with HTTP status 200.
  2. The output line call_api response.data is None: should print False.
  3. The script should print "SUCCESS: response.data is populated."

Actual Behavior:

When the script is run:

  1. The api_client.call_api request succeeds with HTTP status 200.
  2. The output line call_api response.data is None: prints True.
  3. The script prints "FAILURE: response.data is None, cannot decode/process.".
  4. Any subsequent attempt to use response_obj.data (e.g., response_obj.data.decode()) would raise an AttributeError: 'NoneType' object has no attribute 'decode'.

Debugging Information

Further debugging by modifying the SDK's lighter/rest.py file (specifically the RESTClientObject.request method) confirmed that:

  1. The internal aiohttp.ClientResponse object (r) successfully receives the HTTP 200 status and headers.
  2. Calling await r.read() on the internal aiohttp.ClientResponse object successfully returns the expected JSON response body as bytes.
  3. However, the lighter.rest.RESTResponse object created immediately after (via RESTResponse(r)) has its data attribute set to None.

My guess is that the issue lies within the lighter.rest.RESTResponse.__init__ method, where the response body from the aiohttp object is not being correctly read or assigned to the self.data attribute of the RESTResponse instance when the response is generated via the call_api path.

Standard SDK methods (like OrderApi.order_book_details) work correctly, suggesting they might use a different internal request/response handling path that avoids this specific issue in the RESTResponse object's initialization or usage via call_api.

Workaround:

For endpoints without dedicated SDK methods (like /api/v1/positionFunding), a successful workaround involves bypassing ApiClient.call_api and using aiohttp.ClientSession directly to make the request and process the response body via await response.read().

Suggestion:

Please investigate the lighter.rest.RESTResponse.__init__ method to ensure it correctly populates the data attribute from the provided aiohttp.ClientResponse object under all circumstances, including when invoked via the ApiClient.call_api pathway.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions