Can I get a tip for how to do an http request via a VOLTTRON agent using grequests? From what I know about VOLTTRON I think grequests are required for an asynchronous methods.
Small snip from my VOLTTRON agent code, line 140 on git gist:
_log.info(f'*** [grequests INFO] *** - onstart sucess!')
self.core.schedule(cron('*/1 * * * *'), self.cron_function)
def cron_function(self):
def simple_request(url):
page = requests.get(url)
return page
_log.info(f'*** [grequests INFO] *** - starting CRON Processes')
request = grequests.get(simple_request(self.url1))
_log.info(f'*** [grequests INFO] *** - sending out {request}')
response = grequests.send(request)
_log.info(f'*** [grequests INFO] *** - response code {response.status_code}')
_log.info(f'*** [grequests INFO] *** - response text {response.content}')
The traceback when I install the agent has to do with grequests not having the attributes response.status_code
. Should I be wrapping the requests
library with a grequests
to make my script asynchronous? Sorry not a lot wisdom here but I only need to do one http request and I think I realize my agent code needs to asynchronous to accommodate what all else is happening on the VOLTTRON platform.
2021-05-02 10:28:10,875 (grequesteragent-0.1 39693) <stderr> ERROR: Traceback (most recent call last):
2021-05-02 10:28:10,875 (grequesteragent-0.1 39693) <stderr> ERROR: File "src/gevent/greenlet.py", line 854, in gevent._gevent_cgreenlet.Greenlet.run
2021-05-02 10:28:10,875 (grequesteragent-0.1 39693) <stderr> ERROR: File "/home/dan/Desktop/volttron/volttron/platform/vip/agent/core.py", line 455, in wrapper
2021-05-02 10:28:10,875 (grequesteragent-0.1 39693) <stderr> ERROR: event.function(*event.args, **event.kwargs)
2021-05-02 10:28:10,876 (grequesteragent-0.1 39693) <stderr> ERROR: File "/home/dan/.volttron/agents/5b1bc704-ff6e-4d3d-a186-d5ec6b348686/grequesteragent-0.1/grequester/agent.py", line 153, in cron_function
2021-05-02 10:28:10,877 (grequesteragent-0.1 39693) <stderr> ERROR: _log.info(f'*** [grequests INFO] *** - response code {response.status_code}')
2021-05-02 10:28:10,877 (grequesteragent-0.1 39693) <stderr> ERROR: AttributeError: 'gevent._gevent_cgreenlet.Greenlet' object has no attribute 'status_code'
2021-05-02 10:28:10,877 (grequesteragent-0.1 39693) <stderr> ERROR: 2021-05-02T15:20:00Z <Greenlet at 0x7f44a146c9d0: wrapper> failed with AttributeError
2021-05-02 10:28:10,877 (grequesteragent-0.1 39693) <stderr> ERROR:
Example code from the Ecobee driver that I've annotated:
def call_grequest(method_name, url, **kwargs):
"""
Make grequest calls to remote api
:param method_name: method type - put/get/delete
:param url: http URL suffix
:param kwargs: Additional arguments for http request
:return: grequest response
"""
try:
# Get the correct grequests method for the HTTP request type
fn = getattr(grequests, method_name)
# use that method with the url and any args to send the request
request = fn(url, **kwargs)
# use map to pull out the response
response = grequests.map([request])[0]
if response and isinstance(response, list):
response = response[0]
# handle cases were we receive a non-200 response
response.raise_for_status()
return response
# handle specific error cases by logging a message and re-raising so we can handle it elsewhere
except (ConnectionError, NewConnectionError) as e:
_log.error(f"Error connecting to {url} with args {kwargs}: {e}")
raise e
def make_ecobee_request(request_type, url, **kwargs):
"""
Wrapper around making arbitrary GET and POST requests to remote Ecobee API
:return: Ecobee API response using provided request content
"""
# Generate appropriate grequests object
# HTTP request method
if request_type.lower() in ["get", "post"]:
# we use verify so we can work with SSL
response = call_grequest(request_type.lower(), url, verify=requests.certs.where(), timeout=30, **kwargs)
else:
raise ValueError(f"Unsupported request type {request_type} for Ecobee driver.")
# Send request and extract data from response
headers = response.headers
# parse JSON content
if "json" in headers.get("Content-Type"):
return response.json()
else:
# parse Text content
content = response.content
if isinstance(content, bytes):
content = jsonapi.loads(response.decode("UTF-8"))
return content