platypush/platypush/backend/http/request/__init__.py

153 lines
4.4 KiB
Python
Raw Normal View History

2018-06-08 16:55:06 +02:00
import logging
2018-01-09 18:44:45 +01:00
import re
from threading import Thread
2018-01-09 18:44:45 +01:00
import time
import requests
2018-01-09 18:44:45 +01:00
from frozendict import frozendict
2018-01-10 03:14:27 +01:00
from platypush.message.event.http import HttpEvent
2018-01-09 18:44:45 +01:00
2020-09-27 01:33:38 +02:00
class HttpRequest:
"""
Backend used for polling HTTP resources.
"""
2018-01-09 18:44:45 +01:00
poll_seconds = 60
timeout = 5
class HttpRequestArguments:
"""
Models the properties of an HTTP request.
"""
def __init__(self, url, *args, method='get', **kwargs):
2018-01-09 18:44:45 +01:00
self.method = method.lower()
self.url = url
self.args = args
self.kwargs = kwargs
def __init__(
self, args, bus=None, poll_seconds=None, timeout=None, skip_first_call=True, **_
):
2018-01-09 18:44:45 +01:00
super().__init__()
self.poll_seconds = poll_seconds or self.poll_seconds
self.timeout = timeout or self.timeout
2018-01-10 03:14:27 +01:00
self.bus = bus
self.skip_first_call = skip_first_call
2018-01-10 03:14:27 +01:00
self.last_request_timestamp = 0
2020-09-27 01:33:38 +02:00
self.logger = logging.getLogger('platypush')
2018-01-09 18:44:45 +01:00
if isinstance(args, self.HttpRequestArguments):
self.args = args
elif isinstance(args, dict):
self.args = self.HttpRequestArguments(**args)
else:
raise RuntimeError('{} is neither a dictionary nor an HttpRequest')
2018-01-11 19:31:44 +01:00
if 'timeout' not in self.args.kwargs:
self.args.kwargs['timeout'] = self.timeout
2018-01-10 03:14:27 +01:00
self.request_args = {
'method': self.args.method,
'url': self.args.url,
**self.args.kwargs,
2018-01-10 03:14:27 +01:00
}
2018-01-09 18:44:45 +01:00
def execute(self):
2018-01-10 03:14:27 +01:00
def _thread_func():
is_first_call = self.last_request_timestamp == 0
self.last_request_timestamp = time.time()
try:
method = getattr(requests, self.args.method.lower())
response = method(self.args.url, *self.args.args, **self.args.kwargs)
new_items = self.get_new_items(response)
if isinstance(new_items, HttpEvent):
event = new_items
new_items = event.args['response']
else:
event = HttpEvent(dict(self), new_items)
if (
new_items
and self.bus
and (
not self.skip_first_call
or (self.skip_first_call and not is_first_call)
)
):
self.bus.post(event)
response.raise_for_status()
except Exception as e:
self.logger.exception(e)
self.logger.warning(
'Encountered an error while retrieving %s: %s', self.args.url, e
)
2018-01-10 03:14:27 +01:00
Thread(target=_thread_func, name='HttpPoll').start()
2018-01-09 18:44:45 +01:00
2018-01-10 03:14:27 +01:00
def get_new_items(self, response):
"""Gets new items out of a response"""
raise NotImplementedError(
"get_new_items must be implemented in a derived class"
)
2018-01-10 03:14:27 +01:00
def __iter__(self):
"""
:return: The ``request_args`` as key-value pairs.
"""
for key, value in self.request_args.items():
2021-04-05 00:58:44 +02:00
yield key, value
2018-01-09 18:44:45 +01:00
class JsonHttpRequest(HttpRequest):
"""
Specialization of the HttpRequest class for JSON requests.
"""
def __init__(self, *args, path=None, **kwargs):
2018-01-09 18:44:45 +01:00
super().__init__(*args, **kwargs)
self.path = path
self.seen_entries = set()
2018-01-10 03:14:27 +01:00
def get_new_items(self, response):
response = response.json()
2018-01-09 18:44:45 +01:00
new_entries = []
if self.path:
2021-04-05 00:58:44 +02:00
m = re.match(r'\${\s*(.*)\s*}', self.path)
if m:
response = eval(m.group(1)) # pylint: disable=eval-used
2018-01-09 18:44:45 +01:00
for entry in response:
flattened_entry = deep_freeze(entry)
if flattened_entry not in self.seen_entries:
new_entries.append(entry)
self.seen_entries.add(flattened_entry)
return new_entries
def deep_freeze(x):
"""
Deep freezes a Python object - works for strings, dictionaries, sets and
iterables.
"""
2020-09-27 01:33:38 +02:00
if isinstance(x, str) or not hasattr(x, "__len__"):
2018-01-09 18:44:45 +01:00
return x
2020-09-27 01:33:38 +02:00
if hasattr(x, "keys") and hasattr(x, "values"):
return frozendict({deep_freeze(k): deep_freeze(v) for k, v in x.items()})
if hasattr(x, "__getitem__"):
2018-01-09 18:44:45 +01:00
return tuple(map(deep_freeze, x))
2020-09-27 01:33:38 +02:00
return frozenset(map(deep_freeze, x))
2018-01-09 18:44:45 +01:00
2018-01-09 18:44:45 +01:00
# vim:sw=4:ts=4:et: