mirror of
https://github.com/jazzband/django-ddp.git
synced 2026-03-16 22:40:24 +00:00
Merge branch 'release/0.17.2'
This commit is contained in:
commit
cc0d437e9d
9 changed files with 68 additions and 46 deletions
|
|
@ -1,6 +1,10 @@
|
|||
Change Log
|
||||
==========
|
||||
|
||||
0.17.2
|
||||
------
|
||||
* Python 3 fixes using `six` compatibility library (#16, #17).
|
||||
|
||||
0.17.1
|
||||
------
|
||||
* Fix minor issue where some subscription queries still used slow queries.
|
||||
|
|
|
|||
17
README.rst
17
README.rst
|
|
@ -18,12 +18,21 @@ Peer servers subscribe to aggregate broadcast events which are de-multiplexed an
|
|||
|
||||
Limitations
|
||||
-----------
|
||||
* No support for the SockJS protocol to support browsers that
|
||||
don't have WebSockets_ (see http://caniuse.com/websockets for
|
||||
supported browsers).
|
||||
* No support for the SockJS XHR fallback protocol to support browsers
|
||||
that don't have WebSockets_ (see http://caniuse.com/websockets for
|
||||
supported browsers). It is noteworthy that the only current browser
|
||||
listed that doesn't support WebSockets_ is Opera Mini, which doesn't
|
||||
support pages that use EcmaScript (JavaScript) for interactivity
|
||||
anyway. Offering SockJS XHR fallback wouldn't help to substantially
|
||||
increase browser support: if Opera Mini is excluded then all current
|
||||
browser versions including IE, Edge, Firefox, Chrome, Safari, Opera,
|
||||
iOS Safari, Android Browser Android and Chrome for Android are
|
||||
supported. Having said all that, pull requests are welcome.
|
||||
|
||||
* Changes must be made via the Django ORM as django-ddp uses `Django
|
||||
signals`_ to receive model save/update signals.
|
||||
signals`_ to receive model save/update signals. There are no
|
||||
technical reasons why database triggers couldn't be used - pull
|
||||
requests are welcome.
|
||||
|
||||
|
||||
Installation
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ NEW="$( git rev-parse --abbrev-ref HEAD | cut -d / -f 2 )"
|
|||
echo "Bumping version ${OLD} -> ${NEW}...\n"
|
||||
sed -e "s/^ version='${OLD}'/ version='${NEW}'/" setup.py > .setup.py
|
||||
mv .setup.py setup.py
|
||||
sed -e "s/^__version__ = '${OLD}'$/__version__ = '${NEW}'/" dddp/__init__.py > .__init__.py
|
||||
mv .__init__.py dddp/__init__.py
|
||||
sed -e "s/^version = '${OLD%.*}'/version = '${NEW%.*}'/" -e "s/^release = '${OLD}'/release = '${NEW}'/" docs/conf.py > docs/.conf.py
|
||||
mv docs/.conf.py docs/conf.py
|
||||
git diff setup.py docs/conf.py
|
||||
git diff setup.py dddp/__init__.py docs/conf.py
|
||||
|
|
|
|||
|
|
@ -2,19 +2,10 @@
|
|||
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
|
||||
__version__ = '0.17.2'
|
||||
|
||||
default_app_config = 'dddp.apps.DjangoDDPConfig'
|
||||
|
||||
|
|
@ -27,9 +18,10 @@ _GREEN = {}
|
|||
|
||||
def greenify():
|
||||
"""Patch threading and psycopg2 modules for green threads."""
|
||||
# no need to greenify twice.
|
||||
# don't greenify twice.
|
||||
if _GREEN:
|
||||
return
|
||||
_GREEN[True] = True
|
||||
|
||||
from gevent.monkey import patch_all, saved
|
||||
if ('threading' in sys.modules) and ('threading' not in saved):
|
||||
|
|
@ -39,12 +31,10 @@ def greenify():
|
|||
from psycogreen.gevent import patch_psycopg
|
||||
patch_psycopg()
|
||||
|
||||
# ensure we don't greenify again
|
||||
_GREEN[True] = True
|
||||
|
||||
try:
|
||||
# Use psycopg2 by default
|
||||
import psycopg2
|
||||
del psycopg2
|
||||
except ImportError:
|
||||
# Fallback to psycopg2cffi if required (eg: pypy)
|
||||
from psycopg2cffi import compat
|
||||
|
|
@ -81,28 +71,40 @@ class ThreadLocal(local):
|
|||
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)):
|
||||
if (not update_thread_local) or (name not in self.__dict__):
|
||||
obj = factory(*factory_args, **factory_kwargs)
|
||||
if update_thread_local:
|
||||
setattr(self, name, obj)
|
||||
return obj
|
||||
return getattr(self, name)
|
||||
|
||||
@staticmethod
|
||||
def get_factory(name):
|
||||
"""Get factory for given name."""
|
||||
return THREAD_LOCAL_FACTORIES[name]
|
||||
|
||||
|
||||
class RandomStreams(object):
|
||||
|
||||
"""Namespaced PRNG generator."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize with random PRNG state."""
|
||||
self._streams = {}
|
||||
self._seed = THREAD_LOCAL.alea_random.hex_string(20)
|
||||
|
||||
def get_seed(self):
|
||||
"""Retrieve current PRNG seed."""
|
||||
return self._seed
|
||||
|
||||
def set_seed(self, val):
|
||||
"""Set current PRNG seed."""
|
||||
self._streams = {}
|
||||
self._seed = val
|
||||
random_seed = property(get_seed, set_seed)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get namespaced PRNG stream."""
|
||||
if key not in self._streams:
|
||||
return self._streams.setdefault(key, alea.Alea(self._seed, key))
|
||||
return self._streams[key]
|
||||
|
|
@ -124,6 +126,7 @@ METEOR_ID_CHARS = u'23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz'
|
|||
|
||||
|
||||
def meteor_random_id(name=None, length=17):
|
||||
"""Generate a new ID, optionally using namespace of given `name`."""
|
||||
if name is None:
|
||||
stream = THREAD_LOCAL.alea_random
|
||||
else:
|
||||
|
|
@ -132,6 +135,7 @@ def meteor_random_id(name=None, length=17):
|
|||
|
||||
|
||||
def autodiscover():
|
||||
"""Import all `ddp` submodules from `settings.INSTALLED_APPS`."""
|
||||
from django.utils.module_loading import autodiscover_modules
|
||||
from dddp.api import API
|
||||
autodiscover_modules('ddp', register_to=API)
|
||||
|
|
|
|||
32
dddp/alea.py
32
dddp/alea.py
|
|
@ -26,28 +26,30 @@ This implementation of Alea defaults to a more secure initial internal state.
|
|||
|
||||
>>> random = Alea("my", 3, "seeds")
|
||||
|
||||
>>> random.random_string(17, UNMISTAKABLE)
|
||||
'JYRduBwQtjpeCkqP7'
|
||||
>>> random.random_string(17, UNMISTAKABLE) == 'JYRduBwQtjpeCkqP7'
|
||||
True
|
||||
|
||||
>>> random.random_string(17, UNMISTAKABLE)
|
||||
'HLxYtpZBtSain84zj'
|
||||
>>> random.random_string(17, UNMISTAKABLE) == 'HLxYtpZBtSain84zj'
|
||||
True
|
||||
|
||||
>>> random.random_string(17, UNMISTAKABLE)
|
||||
's9XrbWaDC4yCL5NCW'
|
||||
>>> random.random_string(17, UNMISTAKABLE) == 's9XrbWaDC4yCL5NCW'
|
||||
True
|
||||
|
||||
>>> random.random_string(17, UNMISTAKABLE)
|
||||
'SCiymgNnZpwda9vSH'
|
||||
>>> random.random_string(17, UNMISTAKABLE) == 'SCiymgNnZpwda9vSH'
|
||||
True
|
||||
|
||||
>>> random.random_string(17, UNMISTAKABLE)
|
||||
'hui3ThSoZrFrdFDTT'
|
||||
>>> random.random_string(17, UNMISTAKABLE) == 'hui3ThSoZrFrdFDTT'
|
||||
True
|
||||
|
||||
|
||||
>>> random = Alea("my", 3, "seeds")
|
||||
|
||||
>>> random.random_string(43, BASE64)
|
||||
'tHBM5k8z4TZOmU0zgsv9H4ZIl4CJSXic_T3iF2KFJnm'
|
||||
>>> random.random_string(43, BASE64) == \
|
||||
'tHBM5k8z4TZOmU0zgsv9H4ZIl4CJSXic_T3iF2KFJnm'
|
||||
True
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from math import floor
|
||||
import os
|
||||
|
|
@ -78,8 +80,8 @@ class Mash(object):
|
|||
|
||||
def __call__(self, data):
|
||||
"""Return mash, updating internal state."""
|
||||
data = bytes(data)
|
||||
for byte in bytes(data):
|
||||
data = str(data)
|
||||
for byte in data:
|
||||
self.n += ord(byte)
|
||||
h = 0.02519603282416938 * self.n
|
||||
self.n = floor(h)
|
||||
|
|
@ -112,7 +114,7 @@ class Alea(object):
|
|||
# a much more secure seed by default to avoid hash collisions.
|
||||
seed_ids = [int, str, random, self, values, self.__class__]
|
||||
random.shuffle(seed_ids)
|
||||
values = map(id, seed_ids) + [time.time(), os.urandom(512)]
|
||||
values = list(map(id, seed_ids)) + [time.time(), os.urandom(512)]
|
||||
|
||||
mash = Mash()
|
||||
self.c = 1
|
||||
|
|
|
|||
17
dddp/api.py
17
dddp/api.py
|
|
@ -25,6 +25,7 @@ from django.utils.module_loading import import_string
|
|||
from django.db import DatabaseError
|
||||
from django.db.models import signals
|
||||
import ejson
|
||||
import six
|
||||
|
||||
# django-ddp
|
||||
from dddp import (
|
||||
|
|
@ -69,12 +70,11 @@ class Array(aggregates.Aggregate):
|
|||
|
||||
def add_to_query(self, query, alias, col, source, is_summary):
|
||||
"""Override source field internal type so the raw array is returned."""
|
||||
@six.add_metaclass(dbarray.ArrayFieldMetaclass)
|
||||
class ArrayField(dbarray.ArrayFieldBase, source.__class__):
|
||||
|
||||
"""ArrayField for override."""
|
||||
|
||||
__metaclass__ = dbarray.ArrayFieldMetaclass
|
||||
|
||||
@staticmethod
|
||||
def get_internal_type():
|
||||
"""Return ficticious type so Django doesn't cast as int."""
|
||||
|
|
@ -203,7 +203,9 @@ class APIMixin(object):
|
|||
"""Clear out cache for api_path_map."""
|
||||
self._api_path_cache = None
|
||||
for api_provider in self.api_providers:
|
||||
if api_provider.clear_api_path_map_cache.im_self is not None:
|
||||
if six.get_method_self(
|
||||
api_provider.clear_api_path_map_cache,
|
||||
) is not None:
|
||||
api_provider.clear_api_path_map_cache()
|
||||
|
||||
def api_endpoint(self, api_path):
|
||||
|
|
@ -241,12 +243,11 @@ class CollectionMeta(APIMeta):
|
|||
return super(CollectionMeta, mcs).__new__(mcs, name, bases, attrs)
|
||||
|
||||
|
||||
@six.add_metaclass(CollectionMeta)
|
||||
class Collection(APIMixin):
|
||||
|
||||
"""DDP Model Collection."""
|
||||
|
||||
__metaclass__ = CollectionMeta
|
||||
|
||||
name = None
|
||||
model = None
|
||||
qs_filter = None
|
||||
|
|
@ -522,12 +523,11 @@ class PublicationMeta(APIMeta):
|
|||
return super(PublicationMeta, mcs).__new__(mcs, name, bases, attrs)
|
||||
|
||||
|
||||
@six.add_metaclass(PublicationMeta)
|
||||
class Publication(APIMixin):
|
||||
|
||||
"""DDP Publication (a set of queries)."""
|
||||
|
||||
__metaclass__ = PublicationMeta
|
||||
|
||||
name = None
|
||||
queries = None
|
||||
|
||||
|
|
@ -553,12 +553,11 @@ class Publication(APIMixin):
|
|||
)
|
||||
|
||||
|
||||
@six.add_metaclass(APIMeta)
|
||||
class DDP(APIMixin):
|
||||
|
||||
"""Django DDP API."""
|
||||
|
||||
__metaclass__ = APIMeta
|
||||
|
||||
pgworker = None
|
||||
_in_migration = False
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ copyright = u'2015, Tyson Clugg'
|
|||
# The short X.Y version.
|
||||
version = '0.17'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.17.1'
|
||||
release = '0.17.2'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@ Django==1.8.5
|
|||
gevent==1.0.2
|
||||
gevent-websocket==0.9.5
|
||||
psycopg2==2.6.1
|
||||
six==1.10.0
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -44,7 +44,7 @@ CLASSIFIERS = [
|
|||
|
||||
setup(
|
||||
name='django-ddp',
|
||||
version='0.17.1',
|
||||
version='0.17.2',
|
||||
description=__doc__,
|
||||
long_description=open('README.rst').read(),
|
||||
author='Tyson Clugg',
|
||||
|
|
@ -62,6 +62,7 @@ setup(
|
|||
'psycogreen>=1.0',
|
||||
'django-dbarray>=0.2',
|
||||
'pybars3>=0.9.1',
|
||||
'six>=1.10.0',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
|
|
|||
Loading…
Reference in a new issue