mirror of
https://github.com/jazzband/django-ddp.git
synced 2026-03-16 22:40:24 +00:00
138 lines
3.8 KiB
Python
138 lines
3.8 KiB
Python
"""Django/PostgreSQL implementation of the Meteor DDP service."""
|
|
from __future__ import unicode_literals
|
|
import os.path
|
|
import sys
|
|
from pkg_resources import get_distribution, DistributionNotFound
|
|
from gevent.local import local
|
|
from dddp import alea
|
|
|
|
try:
|
|
_dist = get_distribution('django-ddp')
|
|
if not __file__.startswith(os.path.join(_dist.location, 'django-ddp', '')):
|
|
# not installed, but there is another version that *is*
|
|
raise DistributionNotFound
|
|
except DistributionNotFound:
|
|
__version__ = 'development'
|
|
else:
|
|
__version__ = _dist.version
|
|
|
|
default_app_config = 'dddp.apps.DjangoDDPConfig'
|
|
|
|
ADDED = 'added'
|
|
CHANGED = 'changed'
|
|
REMOVED = 'removed'
|
|
|
|
_GREEN = {}
|
|
|
|
|
|
def greenify():
|
|
"""Patch threading and psycopg2 modules for green threads."""
|
|
# no need to greenify twice.
|
|
if _GREEN:
|
|
return
|
|
|
|
from gevent.monkey import patch_all, saved
|
|
if ('threading' in sys.modules) and ('threading' not in saved):
|
|
raise Exception('threading module loaded before patching!')
|
|
patch_all()
|
|
|
|
from psycogreen.gevent import patch_psycopg
|
|
patch_psycopg()
|
|
|
|
# ensure we don't greenify again
|
|
_GREEN[True] = True
|
|
|
|
try:
|
|
# Use psycopg2 by default
|
|
import psycopg2
|
|
except ImportError:
|
|
# Fallback to psycopg2cffi if required (eg: pypy)
|
|
from psycopg2cffi import compat
|
|
compat.register()
|
|
|
|
|
|
class AlreadyRegistered(Exception):
|
|
|
|
"""Raised when registering over the top of an existing registration."""
|
|
|
|
pass
|
|
|
|
|
|
class ThreadLocal(local):
|
|
|
|
"""Thread local storage for greenlet state."""
|
|
|
|
_init_done = False
|
|
|
|
def __init__(self):
|
|
"""Create new thread storage instance."""
|
|
if self._init_done:
|
|
raise SystemError('__init__ called too many times')
|
|
self._init_done = True
|
|
|
|
def __getattr__(self, name):
|
|
"""Create missing attributes using default factories."""
|
|
try:
|
|
factory = THREAD_LOCAL_FACTORIES[name]
|
|
except KeyError:
|
|
raise AttributeError(name)
|
|
return self.get(name, factory)
|
|
|
|
def get(self, name, factory, *factory_args, **factory_kwargs):
|
|
"""Get attribute, creating if required using specified factory."""
|
|
update_thread_local = getattr(factory, 'update_thread_local', True)
|
|
if (not update_thread_local) or (not hasattr(self, name)):
|
|
obj = factory(*factory_args, **factory_kwargs)
|
|
if update_thread_local:
|
|
setattr(self, name, obj)
|
|
return obj
|
|
return getattr(self, name)
|
|
|
|
|
|
class RandomStreams(object):
|
|
|
|
def __init__(self):
|
|
self._streams = {}
|
|
self._seed = THREAD_LOCAL.alea_random.hex_string(20)
|
|
|
|
def get_seed(self):
|
|
return self._seed
|
|
def set_seed(self, val):
|
|
self._streams = {}
|
|
self._seed = val
|
|
random_seed = property(get_seed, set_seed)
|
|
|
|
def __getitem__(self, key):
|
|
if key not in self._streams:
|
|
return self._streams.setdefault(key, alea.Alea(self._seed, key))
|
|
return self._streams[key]
|
|
|
|
|
|
def serializer_factory():
|
|
"""Make a new DDP serializer."""
|
|
from django.core.serializers import get_serializer
|
|
return get_serializer('python')()
|
|
|
|
|
|
THREAD_LOCAL_FACTORIES = {
|
|
'alea_random': alea.Alea,
|
|
'random_streams': RandomStreams,
|
|
'serializer': serializer_factory,
|
|
}
|
|
THREAD_LOCAL = ThreadLocal()
|
|
METEOR_ID_CHARS = u'23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz'
|
|
|
|
|
|
def meteor_random_id(name=None, length=17):
|
|
if name is None:
|
|
stream = THREAD_LOCAL.alea_random
|
|
else:
|
|
stream = THREAD_LOCAL.random_streams[name]
|
|
return stream.random_string(length, METEOR_ID_CHARS)
|
|
|
|
|
|
def autodiscover():
|
|
from django.utils.module_loading import autodiscover_modules
|
|
from dddp.api import API
|
|
autodiscover_modules('ddp', register_to=API)
|
|
return API
|