Exemple #1
0
    def __init__(self, pyrebase_app, root_path, ttl=None):
        self._app = pyrebase_app
        self._root_path = root_path
        self._ttl = ttl
        self._db = self._app.database()
        self._streams = {}
        self._gc_streams = queue.Queue()
        self._gc_thread = None
        self._cache = None
        self.events = Namespace()

        self._handlers = {
            'put': self._put_handler,
            'patch': self._patch_handler,
        }
    def __init__(self, pyrebase_app, root_path, ttl=None, retry_interval=None):
        self._app = pyrebase_app
        self._root_path = root_path
        self._ttl = ttl
        self._retry_interval = (RETRY_INTERVAL
                                if retry_interval is None else retry_interval)
        self._db = self._app.database()
        self._streams = {}
        self._gc_streams = queue.Queue()
        self._gc_thread = None
        self._cache = None
        self.events = Namespace()

        self._handlers = {
            'put': self._put_handler,
            'patch': self._patch_handler,
        }
class LiveData(object):
    def __init__(self, pyrebase_app, root_path, ttl=None, retry_interval=None):
        self._app = pyrebase_app
        self._root_path = root_path
        self._ttl = ttl
        self._retry_interval = (RETRY_INTERVAL
                                if retry_interval is None else retry_interval)
        self._db = self._app.database()
        self._streams = {}
        self._gc_streams = queue.Queue()
        self._gc_thread = None
        self._cache = None
        self.events = Namespace()

        self._handlers = {
            'put': self._put_handler,
            'patch': self._patch_handler,
        }

    def get_data(self):
        if self._cache is None:
            # Fetch data now
            value = self._db.child(self._root_path).get().val()
            self._cache = data.FirebaseData(value)
            # Listen for updates
            self.listen()

        return self._cache

    def get_data_silent(self):
        try:
            return self.get_data()
        except Exception:
            logger.exception('Error getting data')

    def set_data(self, path, value):
        path_list = data.get_path_list(path)
        child = self._db.child(self._root_path)

        for path_part in path_list:
            child = child.child(path_part)
        child.set(value)

    def is_stale(self):
        if self._ttl is None:
            return False

        data = self.get_data()
        if data is None or data.last_updated_at is None:
            logger.debug('Data is invalid: %s', data)
            return True

        stale = datetime.datetime.utcnow() - data.last_updated_at > self._ttl
        if stale:
            logger.debug('Data is stale: %s', data)
        else:
            logger.debug('Data is fresh: %s', data)
        return stale

    def signal(self, path, doc=None):
        norm_path = data.normalize_path(path)
        return self.events.signal(norm_path, doc=doc)

    def listen(self):
        stream = self._db.child(self._root_path).stream(self._stream_handler)
        self._streams[id(stream)] = stream
        self._start_stream_gc()
        watcher.watch(id(self),
                      self.is_stale,
                      self.restart,
                      interval=self._ttl)
        # If the stream and stale watcher are established,
        # the metawatcher is no longer needed.
        self.cancel_metawatcher()

    def get_metawatcher_name(self):
        return 'meta_{}'.format(id(self))

    def start_metawatcher(self):
        watcher.watch(self.get_metawatcher_name(),
                      lambda: self._cache is None,
                      self.get_data_silent,
                      interval=self._retry_interval)

    def cancel_metawatcher(self):
        watcher.cancel(self.get_metawatcher_name())

    def restart(self):
        self.reset()
        self.start_metawatcher()
        self.get_data_silent()

    def reset(self):
        logger.debug('Resetting all data')
        self.hangup(block=False)
        self._cache = None

    def hangup(self, block=True):
        logger.debug('Marking all streams for shut down')

        watcher.cancel(id(self))

        for stream in self._streams.values():
            self._gc_streams.put(stream)

        if block:
            self._gc_streams.join()

    def _set_path_value(self, path, value):
        data = self.get_data()
        data.set(path, value)
        self._recurse_signal(path)

    def _recurse_signal(self, path):
        path_list = data.get_path_list(path)
        partial_path = ''
        value = self.get_data()

        self.signal('/').send(value, value=value.get(), path=path)
        for part in path_list:
            partial_path = '/'.join((partial_path, part))
            self.signal(partial_path).send(value,
                                           value=value.get(partial_path),
                                           path=path)

    def _put_handler(self, path, value):
        logger.debug('PUT: path=%s data=%s', path, value)
        self._set_path_value(path, value)

    def _patch_handler(self, path, all_values):
        logger.debug('PATCH: path=%s data=%s', path, all_values)

        for rel_path, value in all_values.items():
            full_path = data.normalize_path('{}/{}'.format(path, rel_path))
            self._set_path_value(full_path, value)

    def _valid_message(self, message):
        required_keys = [
            'event',
            'path',
            'data',
        ]

        valid_keys = all(k in message for k in required_keys)
        if not valid_keys:
            return False

        if message['event'] not in self._handlers:
            return False

        return True

    def _stream_handler(self, message):
        logger.debug('STREAM received: %s', message)
        if not self._valid_message(message):
            logger.warn('Invalid message: %s', message)
            return

        handler = self._handlers[message['event']]
        handler(message['path'], message['data'])

    def _gc_stream_worker(self):
        while True:
            stream = self._gc_streams.get()
            logger.debug('Closing stream: %s', stream)

            try:
                stream.close()
                del self._streams[id(stream)]
            except Exception as e:
                logger.warning('Error closing stream %s: %s', stream, e)
            else:
                logger.debug('Stream closed: %s', stream)

            self._gc_streams.task_done()

    def _start_stream_gc(self):
        if self._gc_thread is None:
            self._gc_thread = threading.Thread(target=self._gc_stream_worker,
                                               daemon=True)
            self._gc_thread.start()
Exemple #4
0
# coding=utf-8
""""""
from blinker.base import Namespace

# pylint: disable=C0103
#         invalid constant name

ns = Namespace()

#: sent when membership is set. Sender is community, arguments are:
#: :class:`.models.Membership` instance, :bool:`is_new`
membership_set = ns.signal("membership_set")

#: sent just before membership is removed. Sender is community, arguments:
# :class:`.models.Membership` instance
membership_removed = ns.signal("membership_removed")
Exemple #5
0
from blinker.base import Namespace
SIG_ROLES_EDITED = 'roles-edited'
SIG_ROLES_ADDED = 'roles-added'
SIG_ACC_CREATED = 'acc-created'
SIG_ACC_STATUS_CHANGE = 'acc-status-change'

waitlist_bps = Namespace()

roles_changed_sig = waitlist_bps.signal(
    SIG_ROLES_EDITED, "Called when roles are changed on an account")
account_created_sig = waitlist_bps.signal(
    SIG_ACC_CREATED, 'Called when a new Waitlist Account is created')
account_status_change_sig = waitlist_bps.signal(
    SIG_ACC_STATUS_CHANGE, 'Called when an account is enabled or disabled')
roles_added_sig = waitlist_bps.signal(SIG_ROLES_ADDED,
                                      'Called when a new role is added')


def send_roles_changed(sender, to_id, by_id, added_roles, removed_roles, note):
    roles_changed_sig.send(sender,
                           to_id=to_id,
                           by_id=by_id,
                           added_roles=added_roles,
                           removed_roles=removed_roles,
                           note=note)


def send_roles_added(sender, by_id, role_name, role_display_name):
    roles_added_sig.send(sender,
                         by_id=by_id,
                         role_name=role_name,
Exemple #6
0
"""All signals used by Abilian Core.

Signals are the main tools used for decoupling applications components by
sending notifications. In short, signals allow certain senders to notify
subscribers that something happened.

Cf. http://flask.pocoo.org/docs/signals/ for detailed documentation.

The main signal is currently :obj:`activity`.
"""

from blinker.base import Namespace

signals = Namespace()

#: Triggered at application initialization when all extensions and plugins have
#: been loaded
components_registered = signals.signal("app:components:registered")

#: Trigger when JS api must be registered. At this time :func:`flask.url_for` is
#: usable
register_js_api = signals.signal("app:register-js-api")

#: This signal is used by the activity streams service and its clients.
activity = signals.signal("activity")

#: This signal is sent when user object has been loaded. g.user and current_user
#: are available.
user_loaded = signals.signal("user_loaded")

auth_failed = signals.signal("auth_failed")
Exemple #7
0
SIG_ROLES_EDITED = 'roles-edited'
SIG_ROLES_ADDED = 'roles-added'
SIG_ROLES_REMOVED = 'roles-removed'
SIG_ACC_CREATED = 'acc-created'
SIG_ACC_STATUS_CHANGE = 'acc-status-change'

SIG_ALT_LINK_REMOVED = 'alt-link-removed'
SIG_ALT_LINK_ADDED = 'alt-link-added'

SIG_ACCOUNT_NAME_CHANGE = 'acc-namec-change'

SIG_FLEET_REMOVED = 'fleet-removed'
SIG_FLEET_ADDED_FIRST = 'fleet-added-first'
SIG_FLEET_REMOVED_LAST = 'fleet-removed-last'

waitlist_bps = Namespace()


roles_changed_sig = waitlist_bps.\
    signal(SIG_ROLES_EDITED,
           "Called when roles are changed on an account")
account_created_sig = waitlist_bps.\
    signal(SIG_ACC_CREATED,
           'Called when a new Waitlist Account is created')
account_status_change_sig = waitlist_bps.\
    signal(SIG_ACC_STATUS_CHANGE,
           'Called when an account is enabled or disabled')
role_created_sig = waitlist_bps.\
    signal(SIG_ROLES_ADDED,
           'Called when a new role is created')
role_removed_sig = waitlist_bps.\
Exemple #8
0
# coding=utf-8
""""""
from __future__ import absolute_import, print_function, unicode_literals

from blinker.base import Namespace

# pylint: disable=C0103
#         invalid constant name

ns = Namespace()

#: sent when membership is set. Sender is community, arguments are:
#: :class:`.models.Membership` instance, :bool:`is_new`
membership_set = ns.signal("membership_set")

#: sent just before membership is removed. Sender is community, arguments:
# :class:`.models.Membership` instance
membership_removed = ns.signal("membership_removed")
Exemple #9
0
#!/usr/bin/env python
# coding=utf8

__version__ = '0.5.dev1'

from blinker.base import Namespace, Signal

namespace = Namespace()

user_id_changed = namespace.signal('user_id_changed')
user_id_reset = namespace.signal('user_id_reset')
Exemple #10
0
All signals used by Abilian Core.

Signals are the main tools used for decoupling applications components by
sending notifications. In short, signals allow certain senders to notify
subscribers that something happened.

Cf. http://flask.pocoo.org/docs/signals/ for detailed documentation.

The main signal is currently :obj:`activity`.
"""

from __future__ import absolute_import

from blinker.base import Namespace

signals = Namespace()

#: Triggered at application initialization when all extensions and plugins have
#: been loaded
components_registered = signals.signal("app:components:registered")

#: This signal is used by the activity streams service and its clients.
activity = signals.signal("activity")


#: Currently not used and subject to change.
entity_created = signals.signal("entity:created")

#: Currently not used and subject to change.
entity_updated = signals.signal("entity:updated")
Exemple #11
0
"""
All signals used by Abilian Core.

Cf. http://flask.pocoo.org/docs/signals/ for detailed documentation.

These signals are currently not used, and for this reason subject to change.
"""

from blinker.base import Namespace

signals = Namespace()

entity_created = signals.signal("entity:created")
entity_updated = signals.signal("entity:updated")
entity_deleted = signals.signal("entity:deleted")

#user_created = signals.signal("user:created")
#user_deleted = signals.signal("user:deleted")

activity = signals.signal("activity")
Exemple #12
0
# coding=utf-8
"""All signals used by Abilian Core.

Signals are the main tools used for decoupling applications components by
sending notifications. In short, signals allow certain senders to notify
subscribers that something happened.

Cf. http://flask.pocoo.org/docs/signals/ for detailed documentation.

The main signal is currently :obj:`activity`.
"""

from blinker.base import Namespace

signals = Namespace()

#: Triggered at application initialization when all extensions and plugins have
#: been loaded
components_registered = signals.signal("app:components:registered")

#: Trigger when JS api must be registered. At this time :func:`flask.url_for` is
#: usable
register_js_api = signals.signal("app:register-js-api")

#: This signal is used by the activity streams service and its clients.
activity = signals.signal("activity")

#: This signal is sent when user object has been loaded. g.user and current_user
#: are available.
user_loaded = signals.signal("user_loaded")
Exemple #13
0
All signals used by Abilian Core.

Signals are the main tools used for decoupling applications components by
sending notifications. In short, signals allow certain senders to notify
subscribers that something happened.

Cf. http://flask.pocoo.org/docs/signals/ for detailed documentation.

The main signal is currently :obj:`activity`.
"""

from __future__ import absolute_import, print_function, division

from blinker.base import Namespace

signals = Namespace()

#: Triggered at application initialization when all extensions and plugins have
#: been loaded
components_registered = signals.signal("app:components:registered")

#: Trigger when JS api must be registered. At this time :func:`flask.url_for` is
#: usable
register_js_api = signals.signal('app:register-js-api')

#: This signal is used by the activity streams service and its clients.
activity = signals.signal("activity")

#: This signal is sent when user object has been loaded. g.user and current_user
#: are available.
user_loaded = signals.signal('user_loaded')
Exemple #14
0
# coding=utf-8
"""
"""
from __future__ import absolute_import

from blinker.base import Namespace

# pylint: disable=C0103
#         invalid constant name

ns = Namespace()

#: sent when membership is set. Sender is community, arguments are:
#: :class:`.models.Membership` instance, :bool:`is_new`
membership_set = ns.signal('membership_set')

#: sent just before membership is removed. Sender is community, arguments:
# :class:`.models.Membership` instance
membership_removed = ns.signal('membership_removed')
Exemple #15
0
Signals are the main tools used for decoupling applications components by
sending notifications. In short, signals allow certain senders to notify
subscribers that something happened.

Cf. http://flask.pocoo.org/docs/signals/ for detailed documentation.

The main signal is currently :obj:`activity`.
"""

from __future__ import absolute_import, division, print_function, \
    unicode_literals

from blinker.base import Namespace

signals = Namespace()

#: Triggered at application initialization when all extensions and plugins have
#: been loaded
components_registered = signals.signal("app:components:registered")

#: Trigger when JS api must be registered. At this time :func:`flask.url_for` is
#: usable
register_js_api = signals.signal('app:register-js-api')

#: This signal is used by the activity streams service and its clients.
activity = signals.signal("activity")

#: This signal is sent when user object has been loaded. g.user and current_user
#: are available.
user_loaded = signals.signal('user_loaded')