Merge branch 'release/0.17.2'

This commit is contained in:
Tyson Clugg 2015-10-29 10:06:30 +11:00
commit cc0d437e9d
9 changed files with 68 additions and 46 deletions

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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': [