object str can't be used in 'await' expression in PyScript Async Functions
Closed this issue · 7 comments
I am encountering a persistent error in PyScript when trying to perform an asynchronous HTTP request using aiohttp. Despite adhering to the asyncio guidelines and leveraging Home Assistant’s async_get_clientsession, the following error occurs consistently. This error arises regardless of the specific HTTP client method (response.text(), response.json(), or others), suggesting a deeper issue with how PyScript interprets awaitable objects.
1. The same code works flawlessly in a standalone Python environment.
2. Using task.executor to offload synchronous requests calls also fails due to PyScript restrictions.
3. The issue persists across various attempts to simplify the logic.
The issue seems to stem from PyScript misinterpreting certain objects (e.g., str or dict) as awaitable. This is not typical behavior for asyncio or aiohttp and may indicate a PyScript-specific bug.
Please include your code. Try removing the awaits in your pyscript code.
import aiohttp
Base URLs
AUTH_URL = "https://auth.quandify.com/"
BASE_URL = "https://api.quandify.com"
Replace with your actual credentials
ACCOUNT_ID = # Your account ID
PASSWORD = # Your password
ORGANIZATION_ID = # Your organization ID
Time range (example: Unix timestamps)
FROM_TIMESTAMP = 1700000000
TO_TIMESTAMP = 1700600000
async def authenticate(account_id, password):
"""Authenticate with the Quandify API and retrieve an ID token."""
log.info("Starting authentication...")
payload = {"account_id": account_id, "password": password}
headers = {"Content-Type": "application/json"}
async with aiohttp.ClientSession() as session:
try:
log.info("Sending authentication request...")
response = await session.post(AUTH_URL, json=payload, headers=headers)
log.info(f"Authentication response status: {response.status}")
# Validate HTTP status
if response.status != 200:
error_message = await response.text()
log.error(f"Authentication failed with error: {error_message}")
return None
# Parse the response JSON
data = await response.json()
log.info(f"Authentication response data: {data}")
# Validate response structure
if isinstance(data, dict) and "id_token" in data:
return data["id_token"]
else:
log.error(f"Invalid authentication response structure: {data}")
return None
except Exception as e:
log.error(f"Exception during authentication: {e}")
return None
@service
async def authenticate_quandify(account_id=None, password=None):
"""PyScript service to perform authentication and save the token."""
log.info("Starting authenticate_quandify service...")
# Use provided credentials or defaults
account_id = account_id or ACCOUNT_ID
password = password or PASSWORD
# Call the authentication function
id_token = await authenticate(account_id, password)
if id_token:
log.info(f"Authentication successful. Token: {id_token}")
state.set("sensor.quandify_auth_token", id_token, {"friendly_name": "Quandify Auth Token"})
else:
log.error("Authentication service failed. No token received.")
async def get_aggregated_data(id_token, organization_id, from_ts, to_ts):
"""Fetch aggregated consumption data (totalVolume)."""
log.info("Fetching aggregated data...")
url = f"{BASE_URL}/organization/{organization_id}/nodes/detailed-consumption"
params = {"from": from_ts, "to": to_ts, "truncate": "day"}
headers = {"Authorization": f"Bearer {id_token}", "Content-Type": "application/json"}
async with aiohttp.ClientSession() as session:
try:
response = await session.get(url, headers=headers, params=params)
log.info(f"Aggregated data response status: {response.status}")
# Validate HTTP status
if response.status != 200:
error_message = await response.text()
log.error(f"Failed to fetch aggregated data: {error_message}")
return None
# Parse the response JSON
data = await response.json()
log.info(f"Aggregated data response: {data}")
# Validate response structure
if isinstance(data, dict) and "aggregate" in data:
return data["aggregate"]["total"]["totalVolume"]
else:
log.error(f"Unexpected data structure: {data}")
return None
except Exception as e:
log.error(f"Exception during data fetch: {e}")
return None
@service
async def fetch_quandify_data():
"""Fetch and log the total consumption volume."""
log.info("Starting fetch_quandify_data service...")
try:
# Step 1: Retrieve the authentication token from the state
token_entity = state.get("sensor.quandify_auth_token")
if not token_entity:
log.error("No auth token found. Run authenticate_quandify first.")
return
id_token = token_entity
# Step 2: Fetch the aggregated data
total_volume = await get_aggregated_data(id_token, ORGANIZATION_ID, FROM_TIMESTAMP, TO_TIMESTAMP)
if total_volume is not None:
log.info(f"Total Volume: {total_volume}")
state.set("sensor.total_volume", total_volume, {"unit_of_measurement": "m³", "friendly_name": "Total Volume"})
else:
log.error("Failed to fetch total volume.")
except Exception as e:
log.error(f"Unhandled exception in fetch_quandify_data: {e}")
Gives error log:
This error originated from a custom integration.
Logger: custom_components.pyscript.file.Quandify.authenticate_quandify
Source: custom_components/pyscript/eval.py:1982
integration: Pyscript Python scripting (documentation, issues)
First occurred: 8:34:46 PM (15 occurrences)
Last logged: 8:52:31 PM
Error during authentication: object dict can't be used in 'await' expression
Authentication service failed.
Authentication service failed. No token received.
Unhandled exception during authentication: object dict can't be used in 'await' expression
Exception during authentication: object dict can't be used in 'await' expression
import aiohttp
Base URL for authentication
AUTH_URL = "https://auth.quandify.com/"
Replace with your actual credentials
ACCOUNT_ID = # Your account ID
PASSWORD = # Your password
@service
async def authenticate_quandify_debug():
"""Debug authentication service to pinpoint 'await' misuse."""
log.info("Starting debug authentication service...")
# Prepare payload and headers
payload = {"account_id": ACCOUNT_ID, "password": PASSWORD}
headers = {"Content-Type": "application/json"}
try:
# Log the payload and headers
log.info(f"Payload: {payload}")
log.info(f"Headers: {headers}")
# Send the POST request
async with aiohttp.ClientSession() as session:
log.info("Sending POST request to authenticate...")
response = await session.post(AUTH_URL, json=payload, headers=headers)
# Log response status
log.info(f"Response status: {response.status}")
# Check response status
if response.status != 200:
error_message = await response.text()
log.error(f"Authentication failed: {error_message}")
return
# Attempt to parse JSON response
log.info("Attempting to parse JSON response...")
raw_data = await response.text()
log.info(f"Raw response text: {raw_data}")
data = await response.json()
# Log the parsed data and its type
log.info(f"Parsed response data: {data} (type: {type(data)})")
# Validate response structure
if not isinstance(data, dict):
log.error(f"Unexpected response type: {type(data)}")
return
if "id_token" not in data:
log.error(f"Missing 'id_token' in response: {data}")
return
# Log and save the token
id_token = data["id_token"]
log.info(f"Authentication successful. Token: {id_token}")
state.set("sensor.quandify_auth_token", id_token, {"friendly_name": "Quandify Auth Token"})
except Exception as e:
# Log the full exception
log.error(f"Exception during authentication: {e}")
This error originated from a custom integration.
Logger: custom_components.pyscript.file.Quandify.authenticate_quandify_debug
Source: custom_components/pyscript/eval.py:1982
integration: Pyscript Python scripting (documentation, issues)
First occurred: 8:58:59 PM (1 occurrences)
Last logged: 8:58:59 PM
Exception during authentication: object str can't be used in 'await' expression
Simple code to reproduce:
def aio_test():
async with aiohttp.ClientSession() as session:
async with session.get("https://google.com") as response:
if response.status == 200:
return await response.text()Removing await resolves the problem.
The issue is in AstEval.ast_await:
async def ast_await(self, arg):
"""Evaluate await expr."""
coro = await self.aeval(arg.value)
if coro:
return await coro
return NoneIn my local setup, I fixed it like this, and everything works perfectly:
async def ast_await(self, arg):
"""Evaluate await expr."""
return await self.aeval(arg.value)@craigbarratt, can AstEval.aeval actually return a coroutine?
If it can, this might be correct:
async def ast_await(self, arg):
"""Evaluate await expr."""
val = await self.aeval(arg.value)
if asyncio.iscoroutinefunction(val):
return await val
else:
return valNo PR since I’m unsure which is correct.