def stats(self, stream, field, start=None, end=None): """Get stats for a given field in a stream. Arguments: stream -- A stream object corresponding to a stream stored in Sentenai. field -- A dotted field name for a numeric field in the stream. start -- Optional argument indicating start time in stream for calculations. end -- Optional argument indicating end time in stream for calculations. """ args = {} if start: args['start'] = start.isoformat() + ("Z" if not start.tzinfo else "") if end: args['end'] = end.isoformat() + ("Z" if not end.tzinfo else "") url = "/".join( [self.host, "streams", stream()['name'], "fields", field, "stats"]) resp = self.session.get(url, params=args) if resp.status_code == 404: raise NotFound( 'The field at "/streams/{}/fields/{}" does not exist'.format( stream()['name'], field)) else: status_codes(resp) return resp.json()
def get(self, stream, eid=None): """Get event or stream as JSON. Arguments: stream -- A stream object corresponding to a stream stored in Sentenai. eid -- A unique ID corresponding to an event stored within the stream. """ if eid: url = "/".join( [self.host, "streams", stream()['name'], "events", eid]) else: url = "/".join([self.host, "streams", stream()['name']]) resp = self.session.get(url) if resp.status_code == 404 and eid is not None: raise NotFound('The event at "/streams/{}/events/{}" ' 'does not exist'.format(stream()['name'], eid)) elif resp.status_code == 404: raise NotFound('The stream at "/streams/{}" ' 'does not exist'.format(stream()['name'], eid)) else: status_codes(resp) if eid is not None: return { 'id': resp.headers['location'], 'ts': resp.headers['timestamp'], 'event': resp.json() } else: return resp.json()
def streams(self, name=None, meta={}): """Get list of available streams. Optionally, parameters may be supplied to enable searching for stream subsets. Arguments: name -- A regular expression pattern to search names for meta -- A dictionary of key/value pairs to match from stream metadata """ url = "/".join([self.host, "streams"]) resp = self.session.get(url) status_codes(resp) def filtered(s): f = True if name: f = bool(re.search(name, s['name'])) for k, v in meta.items(): f = f and s.get('meta', {}).get(k) == v return f try: return [stream(**v) for v in resp.json() if filtered(v)] except: raise SentenaiException("Something went wrong")
def validate(self, data): ts = data.get('ts') try: if isinstance(ts, string_types): ts = cts(ts) if not ts.tzinfo: ts = pytz.utc.localize(ts) except: return (data, "invalid timestamp") sid = data.get('stream') if isinstance(sid, Stream): strm = sid elif sid: strm = stream(str(sid)) else: return (data, "missing stream") try: evt = data['event'] except KeyError: return (data, "missing event data") except Exception: return (data, "invalid event data") else: return { "stream": strm, "timestamp": ts, "id": data.get('id'), "event": evt }
def destroy(self, stream): """Delete stream. Argument: stream -- A stream object corresponding to a stream stored in Sentenai. """ url = "/".join([self.host, "streams", stream()['name']]) headers = {'auth-key': self.auth_key} resp = requests.delete(url, headers=headers) status_codes(resp) return None
def validate(self, data): ts = data.get('ts') try: if not ts.tzinfo: ts = pytz.localize(ts) except: return (data, "invalid timestamp") sid = data.get('id') if sid: sid = str(sid) try: evt = data['event'] except KeyError: return (data, "missing event data") except Exception: return (data, "invalid event data") else: return {"stream": stream(sid), "timestamp": ts, "id": sid}
def build_url(host, stream, eid=None): """Build a url for the Sentenai API. Arguments: stream -- a stream object. eid -- an optional event id. Returns: url -- a URL for the Sentenai API endpoint to query a stream or event """ if not isinstance(stream, Stream): raise TypeError("stream argument must be of type sentenai.Stream") def with_quoter(s): try: return quote(s) except: return quote(s.encode('utf-8', 'ignore')) url = [host, "streams", with_quoter(stream()['name'])] events = [] if eid is None else ["events", with_quoter(eid)] return "/".join(url + events)
def range(self, stream, start, end): """Get all stream events between start (inclusive) and end (exclusive). Arguments: stream -- A stream object corresponding to a stream stored in Sentenai. start -- A datetime object representing the start of the requested time range. end -- A datetime object representing the end of the requested time range. Result: A time ordered list of all events in a stream from `start` to `end` """ url = "/".join([ self.host, "streams", stream()['name'], "start", iso8601(start), "end", iso8601(end) ]) resp = self.session.get(url) status_codes(resp) return [json.loads(line) for line in resp.text.splitlines()]
def put(self, stream, event, id=None, timestamp=None): """Put a new event into a stream. Arguments: stream -- A stream object corresponding to a stream stored in Sentenai. event -- A JSON-serializable dictionary containing an event's data id -- A user-specified id for the event that is unique to this stream (optional) timestamp -- A user-specified datetime object representing the time of the event. (optional) """ headers = {'content-type': 'application/json'} jd = event if timestamp: headers['timestamp'] = iso8601(timestamp) if id: url = '{host}/streams/{sid}/events/{eid}'.format( sid=stream()['name'], host=self.host, eid=id) resp = self.session.put(url, json=jd, headers=headers) if resp.status_code not in [200, 201]: status_codes(resp) else: return id else: url = '{host}/streams/{sid}/events'.format(sid=stream._name, host=self.host) resp = self.session.post(url, json=jd, headers=headers) if resp.status_code in [200, 201]: return resp.headers['location'] else: status_codes(resp) raise APIError(resp)