import re import json import socket import requests import threading from decorators import validate_payload, parse_results from sseclient import SSEClient class FirebaseEvents(object): CHILD_CHANGED = 0 CHILD_ADDED = 2 CHILD_DELETED = 1 @staticmethod def id(event_name): ev = None mapping = { 'child_changed': FirebaseEvents.CHILD_CHANGED, 'child_added': FirebaseEvents.CHILD_ADDED, 'child_deleted': FirebaseEvents.CHILD_DELETED } try: ev = mapping.get(event_name) finally: return ev class ClosableSSEClient(SSEClient): def __init__(self, *args, **kwargs): self.should_connect = True super(ClosableSSEClient, self).__init__(*args, **kwargs) def _connect(self): if self.should_connect: super(ClosableSSEClient, self)._connect() else: raise StopIteration() def close(self): self.should_connect = False self.retry = 0 self.resp.raw._fp.fp._sock.shutdown(socket.SHUT_RDWR) self.resp.raw._fp.fp._sock.close() class EventSourceClient(threading.Thread): def __init__(self, url, event_name, callback): self.url = url self.event_name = event_name self.callback = callback super(EventSourceClient, self).__init__() def run(self): try: self.sse = ClosableSSEClient(self.url) for msg in self.sse: event = msg.event if event is not None and event in ('put', 'patch'): response = json.loads(msg.data) if response is not None: # Default to CHILD_CHANGED event occurred_event = FirebaseEvents.CHILD_CHANGED if response['data'] is None: occurred_event = FirebaseEvents.CHILD_DELETED # Get the event I'm trying to listen to ev = FirebaseEvents.id(self.event_name) if occurred_event == ev or ev == FirebaseEvents.CHILD_CHANGED: self.callback(event, response) except socket.error: pass class FirebaseReference(object): def __new__(cls, *args): if len(args) == 2: connector = args[0] if isinstance(connector, Firebase): if args[1] is None or FirebaseReference.is_valid(args[1]): return super(FirebaseReference, cls).__new__(cls) return None def __init__(self, connector, reference=None): self.connector = connector self.current = reference or '' def child(self, reference): if not FirebaseReference.is_valid(reference): raise ValueError("Invalid reference value") self.current = "{}/{}".format(self.current, reference) return self @parse_results @validate_payload def push(self, payload): return requests.post(self.current_url, json=payload) @parse_results def get(self): return requests.get(self.current_url) @parse_results @validate_payload def set(self, payload): return requests.put(self.current_url, json=payload) @parse_results @validate_payload def update(self, payload): return requests.patch(self.current_url, json=payload) @parse_results def delete(self): return requests.delete(self.current_url) @staticmethod def is_valid(reference): pattern = re.compile('^[a-zA-Z0-9_-]+(\/[a-zA-Z0-9_-]+)*$') matches = pattern.match(reference) if matches: return True return False @property def current_url(self): base = self.connector.FIREBASE_URL return "{}/{}.json".format(base, self.current) def patch_url(self): if self.current == '': return self.current_url base = self.connector.FIREBASE_URL return "{}/{}/.json".format(base, self.current) def on(self, event_name, **kwargs): url = self.patch_url() callback = kwargs.get('callback', None) if event_name is None or callback is None: raise AttributeError( 'No callback parameter provided' ) if FirebaseEvents.id(event_name) is None: raise AttributeError( 'Unsupported event' ) # Start Event Source Listener on this ref on a new thread self.client = EventSourceClient(url, event_name, callback) self.client.start() return True def off(self): try: # Close Event Source Listener self.client.sse.close() self.client.join() return True except Exception: print "Error while trying to end the thread. Try again!" class Firebase(object): FIREBASE_URL = None def __new__(cls, *args): if len(args) == 1: if Firebase.is_valid_firebase_url(args[0]): return super(Firebase, cls).__new__(cls) return None def __init__(self, url): self.FIREBASE_URL = url.strip('/') @staticmethod def is_valid_firebase_url(url): pattern = re.compile( r'^https://[a-zA-Z0-9_\-]+\.firebaseio(-demo)?\.com/?$' ) matches = pattern.match(url) if matches: return True return False def ref(self, reference=None): ref = FirebaseReference(self, reference) if ref is None: raise Exception( "Something went wrong when trying to create your ref" ) return ref