From f1b4f0798db11cdd5cc0a72dbc5c2ab68bc39b79 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 23 Nov 2015 10:10:07 +1100 Subject: [PATCH 01/31] Don't require DJANGO_SETTINGS_MODULE to import API. --- dddp/api.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dddp/api.py b/dddp/api.py index fc0a745..c4f6610 100644 --- a/dddp/api.py +++ b/dddp/api.py @@ -10,7 +10,6 @@ import uuid # requirements import dbarray from django.conf import settings -from django.contrib.auth import get_user_model import django.contrib.postgres.fields from django.db import connections, router from django.db.models import aggregates, Q @@ -270,6 +269,13 @@ class Collection(APIMixin): queryset = property(get_queryset) + @property + def user_model(self): + """Cached property getter around `get_user_model`.""" + from django.contrib.auth import get_user_model + val = self.__dict__['user_model'] = get_user_model() + return val + def objects_for_user(self, user, qs=None, xmin__lte=None): """Find objects in queryset related to specified user.""" qs = self.get_queryset(qs) @@ -329,7 +335,7 @@ class Collection(APIMixin): if self.always_allow_superusers: user_ids.update( - get_user_model().objects.filter( + self.user_model.objects.filter( is_superuser=True, is_active=True, ).values_list('pk', flat=True) ) From a330746432f7e3ab9b8fe8a4941142f314b05fdd Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 23 Nov 2015 11:09:59 +1100 Subject: [PATCH 02/31] Send django.core.signals.request_finished when closing WebSocket. --- dddp/websocket.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dddp/websocket.py b/dddp/websocket.py index 6d098c5..8a2f4d8 100644 --- a/dddp/websocket.py +++ b/dddp/websocket.py @@ -13,6 +13,7 @@ from six.moves import range as irange import ejson import geventwebsocket +from django.core import signals from django.core.handlers.base import BaseHandler from django.core.handlers.wsgi import WSGIRequest from django.db import connection, transaction @@ -151,6 +152,7 @@ class DDPWebSocketApplication(geventwebsocket.WebSocketApplication): del self.pgworker.connections[self.connection.pk] self.connection.delete() self.connection = None + signals.request_finished.send(sender=self.__class__) self.logger.info('- %s %s', self, args or 'CLOSE') def on_message(self, message): From 0af3aa8c3f68565689da0ff6591da9068c0d1144 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 23 Nov 2015 11:10:57 +1100 Subject: [PATCH 03/31] Set `application_name` on PostgreSQL async connection. --- dddp/postgres.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dddp/postgres.py b/dddp/postgres.py index 603309f..88c993d 100644 --- a/dddp/postgres.py +++ b/dddp/postgres.py @@ -6,8 +6,10 @@ import ejson import gevent import gevent.queue import gevent.select +import os import psycopg2 # green import psycopg2.extensions +import socket class PostgresGreenlet(gevent.Greenlet): @@ -33,15 +35,23 @@ class PostgresGreenlet(gevent.Greenlet): def _run(self): # pylint: disable=method-hidden """Spawn sub tasks, wait for stop signal.""" conn_params = self.connection.get_connection_params() + # See http://initd.org/psycopg/docs/module.html#psycopg2.connect and + # http://www.postgresql.org/docs/current/static/libpq-connect.html + # section 31.1.2 (Parameter Key Words) for details on available params. conn_params.update( async=True, + application_name='{} pid={} django-ddp'.format( + socket.gethostname(), # hostname + os.getpid(), # PID + )[:64], # 64 characters for default PostgreSQL build config ) conn = psycopg2.connect(**conn_params) self.poll(conn) # wait for conneciton to start - cur = conn.cursor() import logging logging.getLogger('dddp').info('=> Started PostgresGreenlet.') + + cur = conn.cursor() cur.execute('LISTEN "ddp";') while not self._stop_event.is_set(): try: From 30516dd44ae3723faa78cbb63aced72986f0919a Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 23 Nov 2015 11:12:48 +1100 Subject: [PATCH 04/31] Wait for all threads to stop upon launcher stop. --- dddp/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dddp/main.py b/dddp/main.py index 4de20f2..577d86d 100644 --- a/dddp/main.py +++ b/dddp/main.py @@ -189,6 +189,9 @@ class DDPLauncher(object): for server in self.servers + [DDPLauncher.pgworker]: self.logger.debug('Stopping %s', server) server.stop() + # wait for all threads to stop. + gevent.joinall(self.threads + [DDPLauncher.pgworker]) + self.threads = [] def start(self): """Run PostgresGreenlet and web/debug servers.""" @@ -229,6 +232,7 @@ class DDPLauncher(object): self._stop_event.wait() # wait for all threads to stop. gevent.joinall(self.threads + [DDPLauncher.pgworker]) + self.threads = [] def addr(val, default_port=8000, defualt_host='localhost'): From 0b8b3c6ddf195e960d371c155e1052af74f96072 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 23 Nov 2015 11:15:17 +1100 Subject: [PATCH 05/31] Fixes #23 -- For loop instead of using . --- dddp/views.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dddp/views.py b/dddp/views.py index dbb72a0..bdc7295 100644 --- a/dddp/views.py +++ b/dddp/views.py @@ -96,16 +96,14 @@ class MeteorView(View): 4. MeteorView.as_view(meteor_public_envs=...) """ self.runtime_config = {} - self.meteor_settings = reduce( - dict_merge, - [ + self.meteor_settings = {} + for other in [ getattr(settings, 'METEOR_SETTINGS', {}), loads(os.environ.get('METEOR_SETTINGS', '{}')), self.meteor_settings or {}, kwargs.pop('meteor_settings', {}), - ], - {}, - ) + ]: + self.meteor_settings = dict_merge(self.meteor_settings, other) self.meteor_public_envs = set() self.meteor_public_envs.update( getattr(settings, 'METEOR_PUBLIC_ENVS', []), From ab33359b9bd0f4c07c990735af0cc876061ebc62 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Thu, 3 Dec 2015 09:10:55 +1100 Subject: [PATCH 06/31] Use PEP-0496 environment markers for universal wheels. --- setup.cfg | 2 +- setup.py | 68 +++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/setup.cfg b/setup.cfg index aa76bae..3c6e79c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [bdist_wheel] -universal=0 +universal=1 diff --git a/setup.py b/setup.py index cf5b95f..92958f1 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,22 @@ #!/usr/bin/env python -"""Django/PostgreSQL implementation of the Meteor DDP service.""" -import platform +"""Django/PostgreSQL implementation of the Meteor server.""" + +from distutils.version import StrictVersion +import setuptools import sys -from setuptools import setup, find_packages + +# setuptools 18.5 introduces support for the `platform_python_implementation` +# environment marker: https://github.com/jaraco/setuptools/pull/28 + +if not StrictVersion(setuptools.__version__) >= StrictVersion('18.5'): + # TODO: Is there an official way to upgrade setuptools in-place? + import subprocess + subprocess.check_call(['pip', 'install', '-U', 'setuptools>=18.5']) + sys.stderr.write( + 'Your setuptools has been upgraded, ' + 'please re-run setup to continue.' + ) + sys.exit(1) CLASSIFIERS = [ # Beta status until 1.0 is released @@ -44,21 +58,7 @@ CLASSIFIERS = [ "Framework :: Django :: 1.8", ] -# Ensure correct dependencies between different python implementations. -IMPLEMENTATION_INSTALL_REQUIRES = { - # extra requirements for CPython implementation - 'CPython': [ - 'psycopg2>=2.5.4', - 'gevent>=1.1b6' if sys.version_info >= (3, 0) else 'gevent>=1.0', - ], - # extra requirements for all other Python implementations - None: [ - 'psycopg2cffi>=2.7.2', - 'gevent>=1.1b6', - ], -} - -setup( +setuptools.setup( name='django-ddp', version='0.18.1', description=__doc__, @@ -67,20 +67,40 @@ setup( author_email='tyson@clugg.net', url='https://github.com/commoncode/django-ddp', license='MIT', - packages=find_packages(), + packages=setuptools.find_packages(), include_package_data=True, + setup_requires=[ + 'setuptools>=18.5', + ], install_requires=[ 'Django>=1.7', + 'django-dbarray>=0.2', 'gevent-websocket>=0.9,!=0.9.4', 'meteor-ejson>=1.0', 'psycogreen>=1.0', - 'django-dbarray>=0.2', 'pybars3>=0.9.1', 'six>=1.10.0', - ] + IMPLEMENTATION_INSTALL_REQUIRES.get( - platform.python_implementation(), - IMPLEMENTATION_INSTALL_REQUIRES[None], # default to non-CPython reqs - ), + ], + extras_require={ + # CPython < 3.0 can use gevent 1.0 + ':platform_python_implementation == "CPython" ' + 'and python_version < "3.0"': [ + 'gevent>=1.0', + ], + # everything else needs gevent 1.1 + ':platform_python_implementation != "CPython" ' + 'or python_version >= "3.0"': [ + 'gevent>=1.1rc1', + ], + # CPython can use plain old psycopg2 + ':platform_python_implementation == "CPython"': [ + 'psycopg2>=2.5.4', + ], + # everything else must use psycopg2cffi + ':platform_python_implementation != "CPython"': [ + 'psycopg2cffi>=2.7.2', + ], + }, entry_points={ 'console_scripts': [ 'dddp=dddp.main:main', From 0109f3372d20dd9cde3e7211876668b19bc5657b Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Thu, 3 Dec 2015 12:19:00 +1100 Subject: [PATCH 07/31] Add missing versions and dates to the change log, plus new changes. --- CHANGES.rst | 254 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 158 insertions(+), 96 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 452281e..487f523 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,34 +1,47 @@ Change Log ========== -0.18.1 ------- +All notable changes to this project will be documented in this file. +This project adheres to `Semantic Versioning `_. + +develop +------- +* Add missing versions and dates to the change log, and note on Semantic + Versioning. +* Back to universal wheels, thanks to PEP-0496 environment markers. +* Fix for #23 (Python 3 compatibility). +* Set `application_name` on PostgreSQL async connection. +* Send `django.core.signals.request_finished` when closing WebSocket. +* Don't require `DJANGO_SETTINGS_MODULE` to import API. + +0.18.1 (2015-11-06) +------------------- * Don't assume Django projects include a `wsgi.py`. -0.18.0 ------- -* Python implementaiton specific builds using tox so that Python2 and +0.18.0 (2015-11-05) +------------------- +* Python implementation specific builds using tox so that Python2 and Python3 can have different dependencies (eg: gevent>=1.1 for Pyton3). * Added support for `METEOR_SETTINGS` environment variable. -0.17.3 ------- +0.17.3 (2015-10-30) +------------------- * Depend on gevent>=1.1b6 if running anything other than CPython 2.7, otherwise allow gevent 1.0 (current stable). * Preliminary (but broken) support for PyPy/Jython/IronPython though platform specific install_requires on psycopg2cffi instead of psycopg2 for all platforms except CPython 2/3. -0.17.2 ------- +0.17.2 (2015-10-29) +------------------- * Python 3 fixes using `six` compatibility library (#16, #17). -0.17.1 ------- +0.17.1 (2015-10-14) +------------------- * Fix minor issue where some subscription queries still used slow queries. -0.17.0 ------- +0.17.0 (2015-10-14) +------------------- * Make the SQL for subscriptions much faster for PostgreSQL. * Repeatable builds using ye olde make. * Use tox test runner - no tests yet (#11). @@ -36,15 +49,15 @@ Change Log * Started documentation using Sphinx (#10). * Python 3 style exception handling. -0.16.0 ------- +0.16.0 (2015-10-13) +------------------- * New setting: `DDP_API_ENDPOINT_DECORATORS`. This setting takes a list of dotted import paths to decorators which are applied to API endpoints. For example, enable New Relic instrumentation by adding the line below to your Django `settings.py`: .. code:: python DDP_API_ENDPOINT_DECORATORS = ['newrelic.agent.background_task'] - + * Fixed #7 -- Warn if using DB engines other than psycopg2 - thanks @Matvey-Kuk. * Improvements to error/exception handling. * Warn if many TX chunks are queued in case WebSocket has stalled. @@ -53,15 +66,15 @@ Change Log * Work towards #16 -- Use `psycopg2cffi` compatibility if `psycopg2` not installed. -0.15.0 ------- +0.15.0 (2015-09-25) +------------------- * Renamed `Logs` collection and publication to `dddp.logs` to be consistent with naming conventions used elsewhere. * Pass all attributes from `logging.LogRecord` via `dddp.logs` collection. * Use select_related() and resultant cached relational fields to speed up Colleciton.serialize() by significantly reducing round-trips to the database. * Fix bug in `get_meteor_ids()` which caused many extra database hits. -0.14.0 ------- +0.14.0 (2015-09-22) +------------------- * Correctly handle serving app content from the root path of a domain. * Account security tokens are now calculated for each minute allowing for finer grained token expiry. * Fix bug in error handling where invalid arguments were being passed to `logging.error()`. @@ -74,8 +87,8 @@ Change Log * Honour `--verbosity` in `dddp` command, now showing API endpoints in more verbose modes. * Updated `dddp.test` to Meteor 1.2 and also showing example of URL config to serve Meteor files from Python. -0.13.0 ------- +0.13.0 (2015-09-18) +------------------- * Abstract DDPLauncher out from dddp.main.serve to permit use from other contexts. * Allow Ctrl-C (Break) handling at any time. * Only run async DB connection when PostgresGreenlet is running. @@ -87,13 +100,13 @@ Change Log * Use sane default options for `python setup.py bdist_wheel`. * Fixed README link to meteor - thanks @LegoStormtroopr. -0.12.2 ------- +0.12.2 (2015-08-27) +------------------- * Set blank=True on AleaIdField, allowing adding items without inventing IDs yourself. -0.12.1 ------- +0.12.1 (2015-08-13) +------------------- * Add `AleaIdMixin` which provides `aid = AleaIdField(unique=True)` to models. * Use `AleaIdField(unique=True)` wherever possible when translating @@ -101,102 +114,102 @@ Change Log round trips to the database and hence drastically improving performance when such fields are available. -0.12.0 ------- +0.12.0 (2015-08-11) +------------------- * Get path to `star.json` from view config (defined in your urls.py) instead of from settings. * Dropped `dddp.server.views`, use `dddp.views` instead. -0.11.0 ------- +0.11.0 (2015-08-10) +------------------- * Support more than 8KB of change data by splitting large payloads into multiple chunks. -0.10.2 ------- +0.10.2 (2015-08-10) +------------------- * Add `Logs` publication that can be configured to emit logs via DDP through the use of the `dddp.logging.DDPHandler` log handler. * Add option to dddp daemon to provide a BackdoorServer (telnet) for interactive debugging (REPL) at runtime. -0.10.1 ------- +0.10.1 (2015-07-28) +------------------- * Bugfix dddp.accounts forgot_password feature. -0.10.0 ------- +0.10.0 (2015-07-21) +------------------- * Stop processing request middleware upon connection - see https://github.com/commoncode/django-ddp/commit/e7b38b89db5c4e252ac37566f626b5e9e1651a29 for rationale. Access to `this.request.user` is gone. * Add `this.user` handling to dddp.accounts. -0.9.14 ------- +0.9.14 (2015-07-18) +------------------- * Fix ordering of user added vs login ready in dddp.accounts authentication methods. -0.9.13 ------- +0.9.13 (2015-07-17) +------------------- * Add dddp.models.get_object_ids helper function. * Add ObjectMappingMixini abstract model mixin providing GenericRelation back to ObjectMapping model. -0.9.12 ------- +0.9.12 (2015-07-16) +------------------- * Bugfix /app.model/schema helper method on collections to work with more model field types. -0.9.11 ------- +0.9.11 (2015-07-14) +------------------- * Fix bug in post login/logout subscription handling. -0.9.10 ------- +0.9.10 (2015-07-08) +------------------- * Fix bug in Accounts.forgotPassword implementation. -0.9.9 ------ +0.9.9 (2015-07-08) +------------------ * Match return values for Accounts.changePassword and Accounts.changePassword methods in dddp.accounts submodule. -0.9.8 ------ +0.9.8 (2015-07-08) +------------------ * Fix method signature for Accouts.changePassword. -0.9.7 ------ +0.9.7 (2015-07-08) +------------------ * Updated Accounts hashing to prevent cross-purposing auth tokens. -0.9.6 ------ +0.9.6 (2015-07-07) +------------------ * Correct method signature to match Meteor Accounts.resetPassword in dddp.accounts submodule. -0.9.5 ------ +0.9.5 (2015-07-03) +------------------ * Include array of `permissions` on User publication. -0.9.4 ------ +0.9.4 (2015-06-29) +------------------ * Use mimetypes module to correctly guess mime types for Meteor files being served. -0.9.3 ------ +0.9.3 (2015-06-29) +------------------ * Include ROOT_URL_PATH_PREFIX in ROOT_URL when serving Meteor build files. -0.9.2 ------ +0.9.2 (2015-06-22) +------------------ * Use HTTPS for DDP URL if settings.SECURE_SSL_REDIRECT is set. -0.9.1 ------ +0.9.1 (2015-06-16) +------------------ * Added support for django.contrib.postres.fields.ArrayField serialization. -0.9.0 ------ +0.9.0 (2015-06-14) +------------------ * Added Django 1.8 compatibility. The current implementation has a hackish (but functional) implementation to use PostgreSQL's `array_agg` function. Pull requests are welcome. @@ -204,31 +217,31 @@ Change Log `dbarray` package for this even though not strictly required with Django 1.8. Once again, pull requests are welcome. -0.8.1 ------ +0.8.1 (2015-06-10) +------------------ * Add missing dependency on `pybars3` used to render boilerplate HTML template when serving Meteor application files. -0.8.0 ------ +0.8.0 (2015-06-09) +------------------ * Add `dddp.server` Django app to serve Meteor application files. * Show input params after traceback if exception occurs in API methods. * Small pylint cleanups. -0.7.0 ------ +0.7.0 (2015-05-28) +------------------ * Refactor serialization to improve performance through reduced number of database queries, especially on sub/unsub. * Fix login/logout user subscription, now emitting user `added`/ `removed` upon `login`/`logout` respectively. -0.6.5 ------ +0.6.5 (2015-05-27) +------------------ * Use OrderedDict for geventwebsocket.Resource spec to support geventwebsockets 0.9.4 and above. -0.6.4 ------ +0.6.4 (2015-05-27) +------------------ * Send `removed` messages when client unsubscribes from publications. * Add support for SSL options and --settings=SETTINGS args in dddp tool. * Add `optional` and `label` attributes to ManyToManyField simple @@ -237,17 +250,17 @@ Change Log than when queuing messages. * Move test projects into path that can be imported post install. -0.6.3 ------ +0.6.3 (2015-05-21) +------------------ * Refactor pub/sub functionality to fix support for `removed` messages. -0.6.2 ------ +0.6.2 (2015-05-20) +------------------ * Bugfix issue where DDP connection thread stops sending messages after changing item that has subscribers for other connections but not self. -0.6.1 ------ +0.6.1 (2015-05-18) +------------------ * Fix `createUser` method to login new user after creation. * Dump stack trace to console on error for easier debugging DDP apps. * Fix handing of F expressions in object change handler. @@ -255,14 +268,14 @@ Change Log * Per connection tracking of sent objects so changed/added sent appropriately. -0.6.0 ------ +0.6.0 (2015-05-12) +------------------ * Add dddp.accounts module which provides password based auth mapping to django.contrib.auth module. * Fix ordering of change messages and result message in method calls. -0.5.0 ------ +0.5.0 (2015-05-07) +------------------ * Drop relations to sessions.Session as WebSocket requests don't have HTTP cookie support -- **you must `migrate` your database after upgrading**. @@ -275,8 +288,8 @@ Change Log * Cleanup transaction handling to apply once at the entry point for DDP API calls. -0.4.0 ------ +0.4.0 (2015-04-28) +------------------ * Make live updates honour user_rel restrictions, also allow superusers to see everything. * Support serializing objects that are saved with F expressions by @@ -288,8 +301,8 @@ Change Log have user_rel items defined). This change includes a schema change, remember to run migrations after updating. -0.3.0 ------ +0.3.0 (2015-04-23) +------------------ * New DB field: Connection.server_addr -- **you must `migrate` your database after upgrading**. * Cleanup connections on shutdown (and purge associated subscriptions). @@ -300,15 +313,64 @@ Change Log * Fix `unsubscribe` from publications. * Fix `/schema` method call. -0.2.5 ------ +0.2.5 (2015-04-25) +------------------ * Fix foreign key references in change messages to correctly reference related object rather than source object. -0.2.4 ------ +0.2.4 (2015-04-15) +------------------ * Fix unicode rendering bug in DDP admin for ObjectMapping model. -0.2.3 ------ +0.2.3 (2015-04-15) +------------------ * Add `dddp` console script to start DDP service in more robust manner than using the dddp Django mangement command. + +0.2.2 (2015-04-14) +------------------ +* Don't include null/None reply from method calls in message. +* Force creation of Alea/Meteor ID even if nobody seems to care -- they + do care if they're using the ID with latency compensated views. +* Support collections to models having non-integer primary key fields. +* Fix latency compensated Alea/Meteor ID generation to match Meteor + semantics of using a namespace to generate seeded Alea PRNGs. + +0.2.1 (2015-04-10) +------------------ +* Change validation so that we now pass the DDP test suite + . +* Add lots of useful info to the README. + +0.2.0 (2015-04-08) +------------------ +* Add `dddp.models.get_meteor_id` and `dddp.models.get_object_id` + methods. +* Add `Connection`, `Subscription` and `SubscriptionColleciton` models, + instances of which are managed during life cycle of connections and + subscriptions. +* Fixed incorrect use of `django.core.serializers` where different + threads used same the serializer instance. +* Add `Collection.user_rel` class attribute allowing user-specific + filtering of objects at the collection level. +* Add `dddp.test` test project with example meteor-todos/django-ddp + project. +* Change `dddp` management command default port from 3000 to 8000. +* Validate `django.conf.settings.DATABASES` configuration on start. +* React to `django.db.models.signals.m2m_changed` model changes for + ManyToManyField. +* Add dependency on `django-dbarray`. + +0.1.1 (2015-03-11) +------------------ +* Add missing dependencies on `gevent`, `gevent-websocket`, + `meteor-ejson` and `psycogreen`. +* Meteor compatible latency compensation using Alea PRNG. +* Add `dddp.THREAD_LOCAL` with factories. +* Register django signals handlers via `AppConfig.ready()` handler. +* Add `dddp` management command. +* Add `dddp.models.AleaIdField` and `dddp.models.ObjectMapping` model. +* Major internal refactoring. + +0.1.0 (2015-02-13) +------------------ +* Working proof-of-concept. From b521317c773b6e5e08a135e2a3fc4a658a981ea8 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 11:47:13 +1100 Subject: [PATCH 08/31] Add test runner for use with `python setup.py test` --- dddp/test/__init__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/dddp/test/__init__.py b/dddp/test/__init__.py index e69de29..d1fa05a 100644 --- a/dddp/test/__init__.py +++ b/dddp/test/__init__.py @@ -0,0 +1,17 @@ +# This file mainly exists to allow `python setup.py test` to work. +import os +import sys + +import dddp +import django +from django.test.utils import get_runner +from django.conf import settings + + +def run_tests(): + os.environ['DJANGO_SETTINGS_MODULE'] = 'dddp.test.test_project.settings' + dddp.greenify() + django.setup() + test_runner = get_runner(settings)() + failures = test_runner.run_tests(['dddp']) + sys.exit(bool(failures)) From bc1a122394395ebbfaf4cebfebbcc6b7ab5fefc8 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 11:50:19 +1100 Subject: [PATCH 09/31] Generate .travis.yml file from tox.ini --- .travis.yml | 29 +++++++++++++++++++++++++++++ .travis.yml.sh | 22 ++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .travis.yml create mode 100755 .travis.yml.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..50fe7e8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +# .travis.yml automatically generated by ".travis.yml.sh" + +# Container-based builds used if "sudo: false" --> fast boot (1-6s) +# https://docs.travis-ci.com/user/ci-environment/ +sudo: false + +language: python + +env: + - TOXENV="py27-django1.8" + - TOXENV="py27-django1.9" + - TOXENV="py33-django1.8" + - TOXENV="py34-django1.8" + - TOXENV="py34-django1.9" + - TOXENV="py35-django1.8" + - TOXENV="py35-django1.9" + - TOXENV="pypy3-django1.8" + - TOXENV="pypy3-django1.9" + - TOXENV="pypy-django1.8" + - TOXENV="pypy-django1.9" + +install: + - pip install tox coveralls + +script: + - tox + +after_success: + coveralls diff --git a/.travis.yml.sh b/.travis.yml.sh new file mode 100755 index 0000000..89fde3d --- /dev/null +++ b/.travis.yml.sh @@ -0,0 +1,22 @@ +#!/bin/bash +cat< fast boot (1-6s) +# https://docs.travis-ci.com/user/ci-environment/ +sudo: false + +language: python + +env: +$( tox -l | grep '^py' | sort -n | sed -e 's/^.*$/ - TOXENV="\0"/' ) + +install: + - pip install tox coveralls + +script: + - tox + +after_success: + coveralls +EOF From 927311958d46c77d6c6ffa134a1c8babe4150bb3 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 11:57:39 +1100 Subject: [PATCH 10/31] Add `dddp.tests` test suite, just running doctests for now. --- dddp/tests.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 dddp/tests.py diff --git a/dddp/tests.py b/dddp/tests.py new file mode 100644 index 0000000..10c656e --- /dev/null +++ b/dddp/tests.py @@ -0,0 +1,31 @@ +"""Django DDP test suite.""" + +import doctest +import os +import unittest +import dddp.alea + +os.environ['DJANGO_SETTINGS_MODULE'] = 'dddp.test.test_project.settings' + +DOCTEST_MODULES = [ + dddp.alea, +] + + +def load_tests(loader, tests, pattern): + """Specify which test cases to run.""" + del pattern + suite = unittest.TestSuite() + # add all TestCase classes from this (current) module + for attr in globals().values(): + try: + if not issubclass(attr, unittest.TestCase): + continue # not subclass of TestCase + except TypeError: + continue # not a class + tests = loader.loadTestsFromTestCase(attr) + suite.addTests(tests) + # add doctests defined in DOCTEST_MODULES + for doctest_module in DOCTEST_MODULES: + suite.addTest(doctest.DocTestSuite(doctest_module)) + return suite From 407c41c0ffb96ac7329ac519bb1c0a2b86f93789 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 12:20:23 +1100 Subject: [PATCH 11/31] Ignore built docs. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4d9c384..53be784 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ tests/report/ .tox/ dddp/test/build/ dddp/test/meteor_todos/.meteor/ + +docs/_build/ +docs/node_modules/ From 437313267fa87cfd0131453f82714c1e69a0eacf Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 12:21:27 +1100 Subject: [PATCH 12/31] Update MANIFEST.in for sdist. --- MANIFEST.in | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 8a0b3c1..046ad9d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,11 +2,12 @@ include LICENSE include *.rst include *.sh include *.txt +include requirements*.txt include .gitignore -include .coveragerc include Makefile -graft docs -prune docs/_build -include tox.ini +exclude tox.ini graft dddp/test/meteor_todos -prune dddp/test/meteor_todos/.meteor +graft dddp/test/build +prune docs +exclude .travis.yml.sh +exclude .travis.yml From 7bdd6baa8562e61cc05c0bab2f5eeb9ccc21f1c2 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 12:41:47 +1100 Subject: [PATCH 13/31] Use libpq environment variables for DB settings in test project. --- dddp/test/test_project/settings.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dddp/test/test_project/settings.py b/dddp/test/test_project/settings.py index 8e697a3..781c342 100644 --- a/dddp/test/test_project/settings.py +++ b/dddp/test/test_project/settings.py @@ -62,7 +62,11 @@ WSGI_APPLICATION = 'dddp.test.test_project.wsgi.application' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'django_ddp_test_project', + 'NAME': os.environ.get('PGDATABASE', 'django_ddp_test_project'), + 'USER': os.environ.get('PGUSER', os.environ['LOGNAME']), + 'PORT': int(os.environ.get('PGPORT', '0')) or None, + 'PASSWORD': os.environ.get('PGPASSWORD', '') or None, + 'HOST': os.environ.get('PGHOST', '') or None, } } From 99fddfcc23a9d8aa1e6aa6c10cf9c1811f9d05e3 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 12:43:49 +1100 Subject: [PATCH 14/31] Try to use dddp.test.django_todos.tests in test suite. --- dddp/test/__init__.py | 2 +- dddp/test/django_todos/tests.py | 35 +++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/dddp/test/__init__.py b/dddp/test/__init__.py index d1fa05a..8740a3b 100644 --- a/dddp/test/__init__.py +++ b/dddp/test/__init__.py @@ -13,5 +13,5 @@ def run_tests(): dddp.greenify() django.setup() test_runner = get_runner(settings)() - failures = test_runner.run_tests(['dddp']) + failures = test_runner.run_tests(['dddp', 'dddp.test.django_todos']) sys.exit(bool(failures)) diff --git a/dddp/test/django_todos/tests.py b/dddp/test/django_todos/tests.py index 7ce503c..70500dc 100644 --- a/dddp/test/django_todos/tests.py +++ b/dddp/test/django_todos/tests.py @@ -1,3 +1,34 @@ -from django.test import TestCase +"""Django Todos test suite.""" -# Create your tests here. +import doctest +import os +import unittest + +os.environ['DJANGO_SETTINGS_MODULE'] = 'dddp.test.test_project.settings' + +DOCTEST_MODULES = [ +] + + +class NoOpTest(unittest.TestCase): + def test_noop(self): + assert True + + +def load_tests(loader, tests, pattern): + """Specify which test cases to run.""" + del pattern + suite = unittest.TestSuite() + # add all TestCase classes from this (current) module + for attr in globals().values(): + try: + if not issubclass(attr, unittest.TestCase): + continue # not subclass of TestCase + except TypeError: + continue # not a class + tests = loader.loadTestsFromTestCase(attr) + suite.addTests(tests) + # add doctests defined in DOCTEST_MODULES + for doctest_module in DOCTEST_MODULES: + suite.addTest(doctest.DocTestSuite(doctest_module)) + return suite From c8e4600419f0b434b85370fd7a65007bb4c4ad71 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 12:44:33 +1100 Subject: [PATCH 15/31] Comments and ordering in gitignore. --- .gitignore | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 53be784..92c772f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,24 +2,30 @@ # $GIT_DIR/info/exclude or the core.excludesFile configuration variable as # described in https://git-scm.com/docs/gitignore -*.egg-info +# python *.pot *.py[co] __pycache__ + +# distutils/setuptools +.eggs/ +*.egg-info MANIFEST dist/ +build/ + +# docs docs/_build/ docs/locale/ -node_modules/ -tests/coverage_html/ -tests/.coverage -build/ -tests/report/ +docs/node_modules/ + +# test suite +.cache/ .coverage -.eggs/ .tox/ +htmlcov/ +tests/report/ + +# meteor dddp/test/build/ dddp/test/meteor_todos/.meteor/ - -docs/_build/ -docs/node_modules/ From d7dde53bdc64dbc26b34e9e5870782fc717afe74 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 12:45:20 +1100 Subject: [PATCH 16/31] Packaging updates in preparation for next release. - `make test` runs tox against Python 2.7/3.3/3.4/3.5 and Django 1.8/1.9. - Building universal wheels with PEP-0496 Environment Markers. - Build wheel from tox environment to ensure consistency. - Consistent layout for setup and requirements files, now using PEP-0409 Environment Markers. - Dropping support for Django 1.7 (didn't work anyway). - Moving repository to https://github.com/django-ddp/django-ddp (new Github organisation). - Update changelog. --- .coveragerc | 3 - CHANGES.rst | 8 ++ Makefile | 36 ++++--- README.rst | 2 + dddp/__init__.py | 4 +- dev-requirements.txt | 5 - docs/{ => reference}/changelog.rst | 0 requirements-dev.txt | 5 + requirements-test.txt | 2 + requirements.txt | 8 +- setup.py | 92 ++++++++++++---- test-requirements.txt | 1 - tox.ini | 165 ++++++++++++++++++++++++----- 13 files changed, 250 insertions(+), 81 deletions(-) delete mode 100644 .coveragerc delete mode 100644 dev-requirements.txt rename docs/{ => reference}/changelog.rst (100%) create mode 100644 requirements-dev.txt create mode 100644 requirements-test.txt delete mode 100644 test-requirements.txt diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 9ff19fc..0000000 --- a/.coveragerc +++ /dev/null @@ -1,3 +0,0 @@ -[run] -branch=True -source=dddp diff --git a/CHANGES.rst b/CHANGES.rst index 487f523..b110bb5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,11 @@ This project adheres to `Semantic Versioning `_. develop ------- +* Dropped support for Django 1.7 (didn't work anyway). +* Require `setuptools>=18.5` at install time due to use of + `python_platform_implementation` environment marker. +* Moved repository to https://github.com/django-ddp/django-ddp (new + Github organisation). * Add missing versions and dates to the change log, and note on Semantic Versioning. * Back to universal wheels, thanks to PEP-0496 environment markers. @@ -13,6 +18,9 @@ develop * Set `application_name` on PostgreSQL async connection. * Send `django.core.signals.request_finished` when closing WebSocket. * Don't require `DJANGO_SETTINGS_MODULE` to import API. +* Tox test suite updated to runs against Python 2.7/3.3/3.4/3.5 and + Django 1.8/1.9. +* Build wheels from tox environment to ensure consistency. 0.18.1 (2015-11-06) ------------------- diff --git a/Makefile b/Makefile index 8b2e277..6396fa7 100644 --- a/Makefile +++ b/Makefile @@ -2,49 +2,53 @@ NAME := $(shell python setup.py --name) VERSION := $(shell python setup.py --version) SDIST := dist/${NAME}-${VERSION}.tar.gz -WHEEL_PY2 := dist/$(subst -,_,${NAME})-${VERSION}-py2-none-any.whl -WHEEL_PY3 := dist/$(subst -,_,${NAME})-${VERSION}-py3-none-any.whl -WHEEL_PYPY := dist/$(subst -,_,${NAME})-${VERSION}-pypy-none-any.whl +WHEEL := dist/$(subst -,_,${NAME})-${VERSION}-py2.py3-none-any.whl .PHONY: all test clean clean-docs clean-dist upload-docs upload-pypi dist .INTERMEDIATE: dist.intermediate docs -all: docs dist +all: .travis.yml docs dist test: tox -vvv -clean: clean-docs clean-dist +clean: clean-docs clean-dist clean-pyc clean-docs: $(MAKE) -C docs/ clean clean-dist: - rm -f "${SDIST}" "${WHEEL_PY2}" "${WHEEL_PY3}" + rm -f "${SDIST}" "${WHEEL}" + +clean-pyc: + find . -type f -name \*.pyc -print0 | xargs -0 rm docs: $(shell find docs/ -type f -name \*.rst) docs/conf.py docs/Makefile $(shell find docs/_static/ -type f) $(shell find docs/_templates/ -type f) README.rst CHANGES.rst $(MAKE) -C docs/ clean html touch "$@" -dist: ${SDIST} ${WHEEL_PY2} ${WHEEL_PY3} +dist: ${SDIST} ${WHEEL} + @echo 'Build successful, `${MAKE} upload` when ready to release.' ${SDIST}: dist.intermediate + @echo "Testing ${SDIST}..." + tox --installpkg ${SDIST} -${WHEEL_PY2}: dist.intermediate - -${WHEEL_PY3}: dist.intermediate - -${WHEEL_PYPY}: - tox -e pypy-test-dist +${WHEEL}: dist.intermediate + @echo "Testing ${WHEEL}..." + tox --installpkg ${WHEEL} dist.intermediate: $(shell find dddp -type f) - tox -e py27-test-dist,py34-test-dist + tox -e dist upload: upload-pypi upload-docs -upload-pypi: ${SDIST} ${WHEEL_PY2} ${WHEEL_PY3} - twine upload "${WHEEL_PY2}" "${WHEEL_PY3}" "${SDIST}" +upload-pypi: ${SDIST} ${WHEEL} + twine upload "${WHEEL}" "${SDIST}" upload-docs: docs/_build/ python setup.py upload_sphinx --upload-dir="$ "$@" diff --git a/README.rst b/README.rst index da945eb..58e64ab 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,4 @@ +========== Django DDP ========== @@ -259,6 +260,7 @@ Contributors This project is forever grateful for the love, support and respect given by the awesome team at `Common Code`_. +.. _Django DDP: https://github.com/django-ddp/django-ddp .. _Django: https://www.djangoproject.com/ .. _Django signals: https://docs.djangoproject.com/en/stable/topics/signals/ .. _Common Code: https://commoncode.com.au/ diff --git a/dddp/__init__.py b/dddp/__init__.py index 5978f8c..834f6c7 100644 --- a/dddp/__init__.py +++ b/dddp/__init__.py @@ -1,11 +1,11 @@ -"""Django/PostgreSQL implementation of the Meteor DDP service.""" +"""Django/PostgreSQL implementation of the Meteor server.""" from __future__ import unicode_literals -import os.path import sys from gevent.local import local from dddp import alea __version__ = '0.18.1' +__url__ = 'https://github.com/django-ddp/django-ddp' default_app_config = 'dddp.apps.DjangoDDPConfig' diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index 101a9b6..0000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ --r requirements.txt -Sphinx==1.3.1 -Sphinx-PyPI-upload==0.2.1 -cloud_sptheme==1.7 -twine==1.6.4 diff --git a/docs/changelog.rst b/docs/reference/changelog.rst similarity index 100% rename from docs/changelog.rst rename to docs/reference/changelog.rst diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..40fe1a6 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,5 @@ +# things you need to build from source and distribute a release +Sphinx==1.3.3 +Sphinx-PyPI-upload==0.2.1 +twine==1.6.4 +sphinxcontrib-dashbuilder==0.1.0 diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..dc85271 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,2 @@ +# things required to run test suite +# (nothing required! we use unittest from stdlib...) diff --git a/requirements.txt b/requirements.txt index e0617b0..7a314e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ -Django==1.8.5 -gevent==1.0.2 +Django>=1.7 +gevent==1.0.2 ; platform_python_implementation == "CPython" and python_version < "3.0" +gevent==1.1rc2 ; platform_python_implementation != "CPython" or python_version >= "3.0" gevent-websocket==0.9.5 -psycopg2==2.6.1 +psycopg2==2.6.1 ; platform_python_implementation == "CPython" +psycopg2cffi>=2.7.2 ; platform_python_implementation != "CPython" six==1.10.0 diff --git a/setup.py b/setup.py index 92958f1..9b0ca55 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,43 @@ #!/usr/bin/env python """Django/PostgreSQL implementation of the Meteor server.""" -from distutils.version import StrictVersion +import os.path import setuptools -import sys +import subprocess +from distutils import log +from distutils.version import StrictVersion +from distutils.command.build import build # setuptools 18.5 introduces support for the `platform_python_implementation` # environment marker: https://github.com/jaraco/setuptools/pull/28 +__requires__ = 'setuptools>=18.5' + +assert StrictVersion(setuptools.__version__) >= StrictVersion('18.5'), \ + 'Installation from source requires setuptools>=18.5.' + + +class Build(build): + + """Build all files of a package.""" + + def run(self): + """Build our package.""" + cmdline = [ + 'meteor', + 'build', + '--directory', + '../build', + ] + meteor_dir = os.path.join( + os.path.dirname(__file__), + 'dddp', + 'test', + 'meteor_todos', + ) + log.info('Building meteor app %r (%s)', meteor_dir, ' '.join(cmdline)) + subprocess.check_call(cmdline, cwd=meteor_dir) + return build.run(self) -if not StrictVersion(setuptools.__version__) >= StrictVersion('18.5'): - # TODO: Is there an official way to upgrade setuptools in-place? - import subprocess - subprocess.check_call(['pip', 'install', '-U', 'setuptools>=18.5']) - sys.stderr.write( - 'Your setuptools has been upgraded, ' - 'please re-run setup to continue.' - ) - sys.exit(1) CLASSIFIERS = [ # Beta status until 1.0 is released @@ -54,8 +75,8 @@ CLASSIFIERS = [ "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", - "Framework :: Django :: 1.7", "Framework :: Django :: 1.8", + "Framework :: Django :: 1.9", ] setuptools.setup( @@ -65,41 +86,62 @@ setuptools.setup( long_description=open('README.rst').read(), author='Tyson Clugg', author_email='tyson@clugg.net', - url='https://github.com/commoncode/django-ddp', + url='https://github.com/django-ddp/django-ddp', + keywords=[ + 'django ddp meteor websocket websockets realtime real-time live ' + 'liveupdate live-update livequery live-query' + ], license='MIT', packages=setuptools.find_packages(), - include_package_data=True, + include_package_data=True, # install data files specified in MANIFEST.in + zip_safe=False, # TODO: Move dddp.test into it's own package. setup_requires=[ - 'setuptools>=18.5', + # packages required to run the setup script + __requires__, ], install_requires=[ - 'Django>=1.7', + 'Django>=1.8', 'django-dbarray>=0.2', - 'gevent-websocket>=0.9,!=0.9.4', 'meteor-ejson>=1.0', 'psycogreen>=1.0', 'pybars3>=0.9.1', 'six>=1.10.0', ], extras_require={ + # We need gevent version dependent upon environment markers, but the + # extras_require seem to be a separate phase from setup/install of + # install_requires. So we specify gevent-websocket (which depends on + # gevent) here in order to honour environment markers. + '': [ + 'gevent-websocket>=0.9,!=0.9.4', + ], + # Django 1.9 doesn't support Python 3.3 + ':python_version=="3.3"': [ + 'Django<1.9', + ], # CPython < 3.0 can use gevent 1.0 - ':platform_python_implementation == "CPython" ' - 'and python_version < "3.0"': [ + ':platform_python_implementation=="CPython" and python_version<"3.0"': [ 'gevent>=1.0', ], # everything else needs gevent 1.1 - ':platform_python_implementation != "CPython" ' - 'or python_version >= "3.0"': [ - 'gevent>=1.1rc1', + ':platform_python_implementation!="CPython" or python_version>="3.0"': [ + 'gevent>=1.1rc2', ], # CPython can use plain old psycopg2 - ':platform_python_implementation == "CPython"': [ + ':platform_python_implementation=="CPython"': [ 'psycopg2>=2.5.4', ], # everything else must use psycopg2cffi ':platform_python_implementation != "CPython"': [ 'psycopg2cffi>=2.7.2', ], + 'develop': [ + # things you need to distribute a wheel from source (`make dist`) + 'Sphinx>=1.3.3', + 'Sphinx-PyPI-upload>=0.2.1', + 'twine>=1.6.4', + 'sphinxcontrib-dashbuilder>=0.1.0', + ], }, entry_points={ 'console_scripts': [ @@ -107,4 +149,8 @@ setuptools.setup( ], }, classifiers=CLASSIFIERS, + test_suite='dddp.test.run_tests', + cmdclass={ + 'build': Build, + }, ) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index bc04b49..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1 +0,0 @@ --r requirements.txt diff --git a/tox.ini b/tox.ini index 1788129..19f9648 100644 --- a/tox.ini +++ b/tox.ini @@ -4,45 +4,154 @@ # and then run "tox" from this directory. [tox] -# require tox>=2.1.1 or refuse to run the tests. + +# require tox 2.1.1 or later minversion=2.1.1 -# return success even if some of the specified environments are missing +# don't fail if missing a python version specified in envlist skip_missing_interpreters=True -# "envlist" is a comma separated list of environments, each environment name -# contains factors separated by hyphens. For example, "py27-unittest" has 2 -# factors: "py27" and "unittest". Other settings such as "setenv" accept the -# factor names as a prefixes (eg: "unittest: ...") so that prefixed settings -# only apply if the environment being run contains that factor. - +# list of environments to run by default envlist = - py27-test, - py33-test, - py34-test, - py35-test, - pypy-test, + lint + clean + py33-django{1.8} + {py27,py34,py35,pypy,pypy3}-django{1.8,1.9} + report + [testenv] +# virtualenv only installs setuptools==0.18.2 but we need 0.18.5: +# - https://github.com/pypa/virtualenv/issues/807 +# - https://github.com/pypa/virtualenv/issues/801 +# - https://github.com/pypa/virtualenv/issues/717 +# - https://github.com/pypa/virtualenv/issues/781 +# - https://github.com/pypa/virtualenv/issues/580 +# - https://github.com/pypa/virtualenv/issues/563 +# - https://github.com/pypa/virtualenv/issues/491 +# wheel 0.25.0 needed for Python 3.5: +# - https://bitbucket.org/pypa/wheel/issues/146/wheel-building-fails-on-cpython-350b3 +install_command=sh -c 'pip install -U "setuptools>=18.5" "wheel>=0.25.0" "pip>=7.1.2" && pip install "$@" && sync' sh {opts} {packages} +whitelist_externals=sh + +# force clean environment each time recreate=True -usedevelop=True + +# build sdist from setup.py and install from that (validate setup.py) +usedevelop=False + +# list of environment variables passed through to commands passenv= - BUILD_NUMBER - BUILD_URL - XDG_CACHE_HOME + ; https://help.ubuntu.com/community/EnvironmentVariables#Other_environment_variables + USER + LOGNAME + HOME + TERM + TERMCAP -# stop running commands if previous commands fail -ignore_errors = False + ; https://help.ubuntu.com/community/EnvironmentVariables#Graphical_desktop-related_variables + DISPLAY + XDG_CACHE_HOME + C_INCLUDE_PATH + CFLAGS + ; https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project + BUILD_NUMBER + BUILD_ID + BUILD_URL + NODE_NAME + JOB_NAME + BUILD_TAG + JENKINS_URL + EXECUTOR_NUMBER + JAVA_HOME + WORKSPACE + GIT_COMMIT + GIT_URL + GIT_BRANCH + + ; http://www.postgresql.org/docs/current/static/libpq-envars.html + PGHOST + PGHOSTADDR + PGPORT + PGDATABASE + PGUSER + PGPASSWORD + PGPASSFILE + PGSERVICE + PGSERVICEFILE + PGREALM + PGOPTIONS + PGAPPNAME + PGSSLMODE + PGREQUIRESSL + PGSSLCOMPRESSION + PGSSLCERT + PGSSLKEY + PGSSLROOTCERT + PGSSLCRL + PGREQUIREPEER + PGKRBSRVNAME + PGSSLLIB + PGCONNECT_TIMEOUT + PGCLIENTENCODING + PGDATESTYLE + PGTZ + PGGEQO + PGSYSCONFDIR + PGLOCALEDIR + +# `pip install -rrequierements.txt` <-- tox doesn't understand PEP-0496 Environment Markers. +# pypy coverage fails with --concurrency set to `gevent` +# pypy install gevent fails building wheel commands = - dist: check-manifest - py{27,33,34,35}-test: coverage run --concurrency=gevent {toxinidir}/dddp/test/manage.py test -v3 --noinput - pypy-test: coverage run {toxinidir}/dddp/test/manage.py test -v3 --noinput - test: coverage report - dist: {envpython} setup.py --quiet --no-user-cfg sdist --dist-dir={toxinidir}/dist/ - dist: {envpython} setup.py --quiet --no-user-cfg bdist_wheel --dist-dir={toxinidir}/dist/ + {py27,py33,py34,py35}: pip install -rrequirements.txt + {pypy,pypy3}: pip install --no-binary gevent -rrequirements.txt + {py27,py33,py34,py35}: coverage run --append --concurrency=gevent --source=dddp setup.py test + {pypy,pypy3}: coverage run --append --source=dddp setup.py test deps = - test: coverage - dist: check-manifest - dist: wheel + #-rrequirements.txt + django1.8: Django>=1.8,<1.9 + django1.9: Django>=1.9,<1.10 + coverage + + +[testenv:dist] +commands = + check-manifest --ignore "dddp/test/build*,dddp/test/meteor_todos/.meteor/local*" + {envpython} setup.py --quiet --no-user-cfg sdist --dist-dir={toxinidir}/dist/ + {envpython} setup.py --quiet --no-user-cfg bdist_wheel --dist-dir={toxinidir}/dist/ + +# the `dist` environment doesn't need our package installed +skip_install=True + +deps = + -rrequirements-dev.txt + check-manifest + wheel + + +[testenv:clean] +skip_install=True +deps=coverage +commands= + coverage erase + + +[testenv:report] +skip_install=True +deps=coverage +commands= + coverage report + coverage html + + +[testenv:lint] +usedevelop=True +commands= + pip install -rrequirements.txt + prospector --doc-warnings --zero-exit {toxinidir}/dddp/ +deps = + prospector==0.10.2 + pylint==1.4.5 From 40d0bf27a6a629f83d83f13f5bd7c827ea93ffcc Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 13:07:25 +1100 Subject: [PATCH 17/31] Fail on start if any child threads can't start. --- CHANGES.rst | 1 + dddp/main.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b110bb5..d31637a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,7 @@ develop `python_platform_implementation` environment marker. * Moved repository to https://github.com/django-ddp/django-ddp (new Github organisation). +* Fail on start if any child threads can't start (eg: port in use). * Add missing versions and dates to the change log, and note on Semantic Versioning. * Back to universal wheels, thanks to PEP-0496 environment markers. diff --git a/dddp/main.py b/dddp/main.py index 577d86d..f4783a5 100644 --- a/dddp/main.py +++ b/dddp/main.py @@ -207,7 +207,12 @@ class DDPLauncher(object): self.print('=> Started PostgresGreenlet.') for server in self.servers: thread = gevent.spawn(server.serve_forever) + gevent.sleep() # yield to thread in case it can't start self.threads.append(thread) + if thread.dead: + # thread died, stop everything and re-raise the exception. + self.stop() + thread.get() if isinstance(server, geventwebsocket.WebSocketServer): self.print( '=> App running at: %s://%s:%d/' % ( From 81bedca9970a30f08650e25fe462c46144025c1a Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 14 Dec 2015 13:08:02 +1100 Subject: [PATCH 18/31] Remove unused import. --- dddp/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dddp/models.py b/dddp/models.py index 24357bc..28c52e1 100644 --- a/dddp/models.py +++ b/dddp/models.py @@ -4,7 +4,7 @@ from __future__ import absolute_import import collections import os -from django.db import models, transaction +from django.db import models from django.db.models.fields import NOT_PROVIDED from django.conf import settings from django.contrib.contenttypes.fields import ( From 96c53649b837a71e0da7478fe062ab9e007e05e1 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 15 Dec 2015 09:49:12 +1100 Subject: [PATCH 19/31] Remove `debug` argment from `PostgresGreenlet`. --- CHANGES.rst | 1 + dddp/main.py | 4 +--- dddp/postgres.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d31637a..be0531b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,6 +22,7 @@ develop * Tox test suite updated to runs against Python 2.7/3.3/3.4/3.5 and Django 1.8/1.9. * Build wheels from tox environment to ensure consistency. +* Remove `debug` argment from `PostgresGreenlet` class (unused). 0.18.1 (2015-11-06) ------------------- diff --git a/dddp/main.py b/dddp/main.py index f4783a5..dd8d12c 100644 --- a/dddp/main.py +++ b/dddp/main.py @@ -103,9 +103,7 @@ class DDPLauncher(object): # shutdown existing connections close_old_connections() - DDPLauncher.pgworker = PostgresGreenlet( - connection, debug=debug, - ) + DDPLauncher.pgworker = PostgresGreenlet(connection) # use settings.WSGI_APPLICATION or fallback to default Django WSGI app from django.conf import settings diff --git a/dddp/postgres.py b/dddp/postgres.py index 88c993d..a513028 100644 --- a/dddp/postgres.py +++ b/dddp/postgres.py @@ -16,7 +16,7 @@ class PostgresGreenlet(gevent.Greenlet): """Greenlet for multiplexing database operations.""" - def __init__(self, conn, debug=False): + def __init__(self, conn): """Prepare async connection.""" super(PostgresGreenlet, self).__init__() import logging From 77ac4a9689823296e775ec822ac1d939f8f592cb Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 15 Dec 2015 10:07:08 +1100 Subject: [PATCH 20/31] Support for alternate Django psycopg2 drivers. --- dddp/postgres.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/dddp/postgres.py b/dddp/postgres.py index a513028..18aa32b 100644 --- a/dddp/postgres.py +++ b/dddp/postgres.py @@ -45,7 +45,25 @@ class PostgresGreenlet(gevent.Greenlet): os.getpid(), # PID )[:64], # 64 characters for default PostgreSQL build config ) - conn = psycopg2.connect(**conn_params) + conn = None + while conn is None: + try: + conn = psycopg2.connect(**conn_params) + except psycopg2.OperationalError as err: + # Some variants of the psycopg2 driver for Django add extra + # params that aren't meant to be passed directly to + # `psycopg2.connect()` -- issue a warning and try again. + msg = ('%s' % err).strip() + msg_prefix = 'invalid connection option "' + if not msg.startswith(msg_prefix): + # *waves hand* this is not the errror you are looking for. + raise + key = msg[len(msg_prefix):-1] + self.logger.warning( + 'Ignoring unknown settings.DATABASES[%r] option: %s=%r', + self.connection.alias, + key, conn_params.pop(key), + ) self.poll(conn) # wait for conneciton to start import logging From f8539b9253dfc19641098718476cf162a2e6d17c Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 15 Dec 2015 10:15:07 +1100 Subject: [PATCH 21/31] Fix doctest in API module. --- dddp/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dddp/api.py b/dddp/api.py index c4f6610..b638c06 100644 --- a/dddp/api.py +++ b/dddp/api.py @@ -107,6 +107,7 @@ def api_endpoint(path_or_func=None, decorate=True): Examples: + >>> from dddp.api import APIMixin, api_endpoint >>> class Counter(APIMixin): ... value = 0 ... From 7184e633c9d40b9b666dba5d26be3c7f009d46ed Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 17:02:01 +1100 Subject: [PATCH 22/31] pylint cleanups on dict_merge. --- dddp/views.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/dddp/views.py b/dddp/views.py index bdc7295..0df5cf5 100644 --- a/dddp/views.py +++ b/dddp/views.py @@ -16,18 +16,22 @@ import pybars # from https://www.xormedia.com/recursively-merge-dictionaries-in-python/ -def dict_merge(a, b): - '''recursively merges dict's. not just simple a['key'] = b['key'], if - both a and bhave a key who's value is a dict then dict_merge is called - on both values and the result stored in the returned dictionary.''' - if not isinstance(b, dict): - return b - result = deepcopy(a) - for k, v in b.iteritems(): - if k in result and isinstance(result[k], dict): - result[k] = dict_merge(result[k], v) +def dict_merge(lft, rgt): + """ + Recursive dict merge. + + Recursively merges dict's. not just simple lft['key'] = rgt['key'], if + both lft and rgt have a key who's value is a dict then dict_merge is + called on both values and the result stored in the returned dictionary. + """ + if not isinstance(rgt, dict): + return rgt + result = deepcopy(lft) + for key, val in rgt.iteritems(): + if key in result and isinstance(result[key], dict): + result[key] = dict_merge(result[key], val) else: - result[k] = deepcopy(v) + result[key] = deepcopy(val) return result From e8651d3bd361c0b8eab31da2d005a57aa861740c Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 17:04:31 +1100 Subject: [PATCH 23/31] Pylint cleanups and remove logging from MeteorView. --- dddp/views.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/dddp/views.py b/dddp/views.py index 0df5cf5..648a7ca 100644 --- a/dddp/views.py +++ b/dddp/views.py @@ -3,7 +3,6 @@ from __future__ import absolute_import, unicode_literals from copy import deepcopy import io -import logging import mimetypes import os.path @@ -58,16 +57,11 @@ class MeteorView(View): """Django DDP Meteor server view.""" - logger = logging.getLogger(__name__) http_method_names = ['get', 'head'] json_path = None runtime_config = None - manifest = None - program_json = None - program_json_path = None - star_json = None # top level layout url_map = None @@ -86,7 +80,7 @@ class MeteorView(View): """ Initialisation for Django DDP server view. - `Meteor.settings` is sourced from the following (later take precedence): + The following items populate `Meteor.settings` (later take precedence): 1. django.conf.settings.METEOR_SETTINGS 2. os.environ['METEOR_SETTINGS'] 3. MeteorView.meteor_settings (class attribute) or empty dict @@ -253,18 +247,14 @@ class MeteorView(View): def get(self, request, path): """Return HTML (or other related content) for Meteor.""" - self.logger.debug( - '[%s:%s] %s %s %s', - request.META['REMOTE_ADDR'], request.META['REMOTE_PORT'], - request.method, request.path, - request.META['SERVER_PROTOCOL'], - ) if path == 'meteor_runtime_config.js': config = { 'DDP_DEFAULT_CONNECTION_URL': request.build_absolute_uri('/'), 'PUBLIC_SETTINGS': self.meteor_settings.get('public', {}), 'ROOT_URL': request.build_absolute_uri( - '%s/' % self.runtime_config.get('ROOT_URL_PATH_PREFIX', ''), + '%s/' % ( + self.runtime_config.get('ROOT_URL_PATH_PREFIX', ''), + ), ), 'ROOT_URL_PATH_PREFIX': '', } From cb4bf1afa5253412a25f8d972dccc3ac3ba0819f Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 17:12:44 +1100 Subject: [PATCH 24/31] Start on major documentation refresh. --- CHANGES.rst | 1 + docs/Makefile | 12 ++++- docs/_static/django-ddp-logo.png | Bin 0 -> 140466 bytes docs/admin/index.rst | 43 ++++++++++++++++++ docs/conf.py | 73 ++++++++++++++++++++++++++++--- docs/devel/api/dddp.api.rst | 5 +++ docs/devel/api/index.rst | 8 ++++ docs/devel/index.rst | 7 +++ docs/gulpfile.js | 22 ++++++++++ docs/index.rst | 54 +++++++---------------- docs/readme.rst | 3 ++ docs/reference/changelog.rst | 2 +- docs/reference/index.rst | 9 ++++ docs/reference/license.rst | 5 +++ docs/tutorial/index.rst | 12 +++++ tox.ini | 1 + 16 files changed, 211 insertions(+), 46 deletions(-) create mode 100644 docs/_static/django-ddp-logo.png create mode 100644 docs/admin/index.rst create mode 100644 docs/devel/api/dddp.api.rst create mode 100644 docs/devel/api/index.rst create mode 100644 docs/devel/index.rst create mode 100644 docs/gulpfile.js create mode 100644 docs/readme.rst create mode 100644 docs/reference/index.rst create mode 100644 docs/reference/license.rst create mode 100644 docs/tutorial/index.rst diff --git a/CHANGES.rst b/CHANGES.rst index be0531b..e089d9d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,7 @@ develop `python_platform_implementation` environment marker. * Moved repository to https://github.com/django-ddp/django-ddp (new Github organisation). +* Started work on documentation refresh. * Fail on start if any child threads can't start (eg: port in use). * Add missing versions and dates to the change log, and note on Semantic Versioning. diff --git a/docs/Makefile b/docs/Makefile index 1912130..9a8d4c6 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -4,7 +4,7 @@ # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build -PAPER = +PAPER = a4 BUILDDIR = _build # User-friendly check for sphinx-build @@ -175,3 +175,13 @@ pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +pdf: + $(SPHINXBUILD) -b pdf $(ALLSPHINXOPTS) _build/pdf + @echo + @echo "Build finished. The PDF files are in _build/pdf." + +dash: + $(SPHINXBUILD) -b dash $(ALLSPHINXOPTS) _build/dash + @echo + @echo "Build finished. The DASH files are in _build/dash." diff --git a/docs/_static/django-ddp-logo.png b/docs/_static/django-ddp-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c91f6ab261dcc328257e8df462b212cfab1b53ee GIT binary patch literal 140466 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%0AktcBHJj8UeV7SXHG2owR~s+ZS8*N`!<}BcmLW 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�>unekLEvGAxVmd4r0;<1~i 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 SvGO?#GRV`_&t;ucLK6VPKM&~u literal 0 HcmV?d00001 diff --git a/docs/admin/index.rst b/docs/admin/index.rst new file mode 100644 index 0000000..58e50fd --- /dev/null +++ b/docs/admin/index.rst @@ -0,0 +1,43 @@ +*********************** +Administration Handbook +*********************** + +This section is aimed at DevOps and project administrators to assist in +installing and maintaining a site using Django DDP. + +Requirements +============ + +You must be using PostgreSQL_ with psycopg2_ in your Django_ project for +django-ddp to work. There is no requirement on any asynchronous +framework such as Reddis or crossbar.io as they are simply not needed +given the asynchronous support provided by PostgreSQL_ with psycopg2_. + + +Installation +============ + +Install the latest release from pypi (recommended): + +.. code:: sh + + pip install django-ddp + +Don't forget to add `dddp` to your `requirements.txt` and/or the +`install_requires` section in `setup.py` for your project as necessary. + +Clone and use development version direct from GitHub to test pre-release +code (no GitHub account required): + +.. code:: sh + + pip install -e + git+https://github.com/commoncode/django-ddp@develop#egg=django-ddp + + +.. _Django: https://www.djangoproject.com/ +.. _Django signals: https://docs.djangoproject.com/en/stable/topics/signals/ +.. _Gevent: http://www.gevent.org/ +.. _PostgreSQL: http://postgresql.org/ +.. _psycopg2: http://initd.org/psycopg/ +.. _WebSockets: http://www.w3.org/TR/websockets/ diff --git a/docs/conf.py b/docs/conf.py index a153f23..3f4c805 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,7 +15,29 @@ import sys import os -import cloud_sptheme as csp +import django +from django.conf import settings + +settings.configure( + INSTALLED_APPS=[ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'dddp', + ], + DATABASES={ + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': os.environ.get('PGDATABASE', 'django_ddp_docs_project'), + 'USER': os.environ.get('PGUSER', os.environ['LOGNAME']), + 'PORT': int(os.environ.get('PGPORT', '0')) or None, + 'PASSWORD': os.environ.get('PGPASSWORD', '') or None, + 'HOST': os.environ.get('PGHOST', '') or None, + }, + }, +) +django.setup() + +import alabaster # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -32,13 +54,19 @@ import cloud_sptheme as csp # ones. extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', - 'cloud_sptheme.ext.index_styling', - 'cloud_sptheme.ext.relbar_toc', + 'rst2pdf.pdfbuilder', + 'sphinxcontrib.dashbuilder', + 'alabaster', ] +# Show `.. todo::` items in output. +todo_include_todos = True + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -107,17 +135,22 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'cloud' +html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - 'max_width': '80em', + 'logo': 'django-ddp-logo.png', + 'github_user': 'django-ddp', + 'github_repo': 'django-ddp', + 'github_button': 'true', + 'github_type': 'star', + 'github_banner': 'true', } # Add any paths that contain custom themes here, relative to this directory. -html_theme_path = [csp.get_theme_dir()] +html_theme_path = [alabaster.get_path()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". @@ -154,7 +187,15 @@ html_static_path = ['_static'] #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', + 'searchbox.html', + 'donate.html', + ], +} # Additional templates that should be rendered to pages, maps page names to # template names. @@ -267,3 +308,21 @@ texinfo_documents = [ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False + +# Grouping the document tree into PDF files. List of tuples +# (source start file, target name, title, author, options). +# +# If there is more than one author, separate them with \\. +# For example: r'Guido van Rossum\\Fred L. Drake, Jr., editor' +# +# The options element is a dictionary that lets you override +# this config per-document. +# For example, +# ('index', u'MyProject', u'My Project', u'Author Name', +# dict(pdf_compressed = True)) +# would mean that specific document would be compressed +# regardless of the global pdf_compressed setting. + +pdf_documents = [ + ('changes', u'django-ddp', u'django-ddp', u'Tyson Clugg'), +] diff --git a/docs/devel/api/dddp.api.rst b/docs/devel/api/dddp.api.rst new file mode 100644 index 0000000..0ec6ef4 --- /dev/null +++ b/docs/devel/api/dddp.api.rst @@ -0,0 +1,5 @@ +dddp.api +-------- + +.. automodule:: dddp.api + :members: diff --git a/docs/devel/api/index.rst b/docs/devel/api/index.rst new file mode 100644 index 0000000..39d6b25 --- /dev/null +++ b/docs/devel/api/index.rst @@ -0,0 +1,8 @@ +============= +API Reference +============= + +.. toctree:: + :glob: + + * diff --git a/docs/devel/index.rst b/docs/devel/index.rst new file mode 100644 index 0000000..74de317 --- /dev/null +++ b/docs/devel/index.rst @@ -0,0 +1,7 @@ +=================== +Developers Handbook +=================== + +.. toctree:: + + api/index diff --git a/docs/gulpfile.js b/docs/gulpfile.js new file mode 100644 index 0000000..cc4a95c --- /dev/null +++ b/docs/gulpfile.js @@ -0,0 +1,22 @@ +var exec = require('child_process').exec; +var gulp = require('gulp'); +var browserSync = require('browser-sync'); + +gulp.task('sphinx', function(cb) { + exec('make html', function(err, stdout, stderr) { + console.log(stdout); + console.log(stderr); + cb(err); + browserSync.reload(); + }); +}); + +gulp.task('default', ['sphinx'], function() { + browserSync({ + open: false, + server: { + baseDir: '_build/html/' + } + }); + gulp.watch(["../README.rst", "../LICENSE", "../CHANGES.rst", "**/*.rst", "_static/**", "conf.py"], ['sphinx']); +}); diff --git a/docs/index.rst b/docs/index.rst index 001c5eb..05a7927 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,51 +1,31 @@ -.. Django DDP documentation master file, created by - sphinx-quickstart on Tue Oct 13 23:24:39 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +########## +Django DDP +########## -.. include:: ../README.rst +.. image:: _static/django-ddp-logo.png + :alt: django-ddp — reactive ★ realtime ★ relational ★ robust + :align: center -Installation: -------------- -`Django DDP`_ is available for installation direct from PyPi_: - -.. code:: bash - - pip install django-ddp - -Links ------ - -.. image:: https://readthedocs.org/projects/django-ddp/badge/?version=latest - :target: https://readthedocs.org/projects/django-ddp/?badge=latest - :alt: Documentation Status - :align: right - -The latest documentation is available online at -https://django-ddp.readthedocs.org/. - -Source code is available online at https://github.com/commoncode/django-ddp. +.. rubric:: + Django DDP lets you create websites and mobile apps that are + reactive, realtime, relational & robust -- using Django and + Meteor together with the smallest server (and environmental) + footprint. Contents -------- .. toctree:: - :glob: - :maxdepth: 1 + :maxdepth: 2 - changelog - django-ddp* - - -Indices and tables -================== + readme + tutorial/index + admin/index + devel/index + reference/index * :ref:`genindex` * :ref:`modindex` * :ref:`search` -License -======= -.. include:: ../LICENSE - .. _pypi: https://pypi.python.org/pypi/django-ddp diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 0000000..4dc0eb5 --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1,3 @@ +.. _readme: + +.. include:: ../README.rst diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index f6a3529..cca3726 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -1,3 +1,3 @@ .. _changelog: -.. include:: ../CHANGES.rst +.. include:: ../../CHANGES.rst diff --git a/docs/reference/index.rst b/docs/reference/index.rst new file mode 100644 index 0000000..2b4f81b --- /dev/null +++ b/docs/reference/index.rst @@ -0,0 +1,9 @@ +========= +Reference +========= + +.. toctree:: + :maxdepth: 1 + + changelog + license diff --git a/docs/reference/license.rst b/docs/reference/license.rst new file mode 100644 index 0000000..3547d34 --- /dev/null +++ b/docs/reference/license.rst @@ -0,0 +1,5 @@ +License +------- + +.. include:: ../../LICENSE + :literal: diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst new file mode 100644 index 0000000..91d2282 --- /dev/null +++ b/docs/tutorial/index.rst @@ -0,0 +1,12 @@ +======== +Tutorial +======== + +.. todo:: This documentation is incomplete -- pull requests are welcome! + +* Install Python / virtualenv / Meteor +* Setup repo / add basics: + - setup.py + - django-admin.py startproject + - meteor create + - letsencrypt diff --git a/tox.ini b/tox.ini index 19f9648..6de5cd3 100644 --- a/tox.ini +++ b/tox.ini @@ -122,6 +122,7 @@ commands = check-manifest --ignore "dddp/test/build*,dddp/test/meteor_todos/.meteor/local*" {envpython} setup.py --quiet --no-user-cfg sdist --dist-dir={toxinidir}/dist/ {envpython} setup.py --quiet --no-user-cfg bdist_wheel --dist-dir={toxinidir}/dist/ + cd docs && make html # the `dist` environment doesn't need our package installed skip_install=True From b38a0447f66b7562abc4d99ed680c60c65074902 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 17:24:45 +1100 Subject: [PATCH 25/31] Close DB and yield after processing msg from WebSocket. --- CHANGES.rst | 2 ++ dddp/websocket.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e089d9d..c07e2a6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,8 @@ develop * Fail on start if any child threads can't start (eg: port in use). * Add missing versions and dates to the change log, and note on Semantic Versioning. +* Emit `django.core.signals.request_finished` to close DB connection and + yield to other greenlets between processing messages from WebSocket. * Back to universal wheels, thanks to PEP-0496 environment markers. * Fix for #23 (Python 3 compatibility). * Set `application_name` on PostgreSQL async connection. diff --git a/dddp/websocket.py b/dddp/websocket.py index 8a2f4d8..b6ecfa0 100644 --- a/dddp/websocket.py +++ b/dddp/websocket.py @@ -12,6 +12,7 @@ import traceback from six.moves import range as irange import ejson +import gevent import geventwebsocket from django.core import signals from django.core.handlers.base import BaseHandler @@ -197,6 +198,11 @@ class DDPWebSocketApplication(geventwebsocket.WebSocketApplication): except Exception as err: traceback.print_exc() self.error(err) + # emit request_finished signal to close DB connections + signals.request_finished.send(sender=self.__class__) + if msgs: + # yield to other greenlets before processing next msg + gevent.sleep() except geventwebsocket.WebSocketError as err: self.ws.close() From b43bff48c54c3e8094a1fd34d07969137846f573 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 17:25:44 +1100 Subject: [PATCH 26/31] Add rst2pdf to dev requirements for building PDF docs. --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 40fe1a6..bade6b9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,3 +3,4 @@ Sphinx==1.3.3 Sphinx-PyPI-upload==0.2.1 twine==1.6.4 sphinxcontrib-dashbuilder==0.1.0 +rst2pdf==0.93 From 179642ff098021966ed54dea21f3e119c9d91305 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 21:12:14 +1100 Subject: [PATCH 27/31] Fix DB close during service stop. --- dddp/postgres.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dddp/postgres.py b/dddp/postgres.py index 18aa32b..ef8718a 100644 --- a/dddp/postgres.py +++ b/dddp/postgres.py @@ -83,7 +83,9 @@ class PostgresGreenlet(gevent.Greenlet): finally: self.select_greenlet = None self.poll(conn) + self.poll(conn) cur.close() + self.poll(conn) conn.close() def stop(self): From a23d2f8056685ad37f9188ddaef0a979e55deec5 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 21:16:16 +1100 Subject: [PATCH 28/31] Crude support for pub access to `this.user`. --- CHANGES.rst | 2 ++ dddp/__init__.py | 3 ++ dddp/api.py | 73 ++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c07e2a6..cf0a14a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,8 @@ develop Github organisation). * Started work on documentation refresh. * Fail on start if any child threads can't start (eg: port in use). +* Rudimentary support for dynamic publications which may now refer to `this.user`. +* Correctly close DB connections during shutdown, useful for test suite. * Add missing versions and dates to the change log, and note on Semantic Versioning. * Emit `django.core.signals.request_finished` to close DB connection and diff --git a/dddp/__init__.py b/dddp/__init__.py index 834f6c7..8270b31 100644 --- a/dddp/__init__.py +++ b/dddp/__init__.py @@ -121,6 +121,9 @@ THREAD_LOCAL_FACTORIES = { 'alea_random': alea.Alea, 'random_streams': RandomStreams, 'serializer': serializer_factory, + 'user_id': lambda: None, + 'user_ddp_id': lambda: None, + 'user': lambda: None, } THREAD_LOCAL = ThreadLocal() METEOR_ID_CHARS = u'23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz' diff --git a/dddp/api.py b/dddp/api.py index b638c06..0e77f47 100644 --- a/dddp/api.py +++ b/dddp/api.py @@ -11,7 +11,7 @@ import uuid import dbarray from django.conf import settings import django.contrib.postgres.fields -from django.db import connections, router +from django.db import connections, router, transaction from django.db.models import aggregates, Q try: # pylint: disable=E0611 @@ -538,15 +538,40 @@ class Publication(APIMixin): name = None queries = None - def get_queries(self, *params): - """DDP get_queries - must override if using params.""" - if params: - raise NotImplementedError( - 'Publication params not implemented on %r publication.' % ( - self.name, - ), - ) - return self.queries[:] + def user_queries(self, user, *params): + """Return queries for this publication as seen by `user`.""" + try: + get_queries = self.get_queries + except AttributeError: + # statically defined queries + if self.queries is None: + raise NotImplementedError( + 'Must set either queries or implement get_queries method.', + ) + if params: + raise NotImplementedError( + 'Publication params not implemented on %r publication.' % ( + self.name, + ), + ) + return self.queries[:] + + if user is False: + # no need to play with `this.user_id` or `this.user_ddp_id`. + return get_queries(*params) + + # stash the old user details + old_user_id = this.user_id + old_user_ddp_id = this.user_ddp_id + # apply the desired user details + this.user_id = None if user is None else user.pk + this.user_ddp_id = None if user is None else get_meteor_id(user) + try: + return get_queries(*params) + finally: + # restore the old user details + this.user_id = old_user_id + this.user_ddp_id = old_user_ddp_id @api_endpoint def collections(self, *params): @@ -555,7 +580,7 @@ class Publication(APIMixin): set( hasattr(qs, 'model') and model_name(qs.model) or qs[1] for qs - in self.get_queries(*params) + in self.get_queries(False, *params) ) ) @@ -611,7 +636,7 @@ class DDP(APIMixin): (col, qs) for (qs, col) in ( self.qs_and_collection(qs) for qs - in pub.get_queries(*params) + in pub.user_queries(obj.user, *params) ) ) # mergebox via MVCC! For details on how this is possible, read this: @@ -635,7 +660,7 @@ class DDP(APIMixin): pk=obj.pk, ).order_by('pk').distinct(): other_pub = self.get_pub_by_name(other.publication) - for qs in other_pub.get_queries(*other.params): + for qs in other_pub.user_queries(other.user, *other.params): qs, col = self.qs_and_collection(qs) if col not in to_send: continue @@ -652,8 +677,21 @@ class DDP(APIMixin): @api_endpoint def sub(self, id_, name, *params): """Create subscription, send matched objects that haven't been sent.""" - return self.do_sub(id_, name, False, *params) + try: + return self.do_sub(id_, name, False, *params) + except Exception as err: + this.send({ + 'msg': 'nosub', + 'id': id_, + 'error': { + 'error': 500, + 'errorType': 'Meteor.Error', + 'message': '%s' % err, + 'reason': 'Subscription failed', + }, + }) + @transaction.atomic def do_sub(self, id_, name, silent, *params): """Subscribe the current thread to the specified publication.""" try: @@ -662,6 +700,7 @@ class DDP(APIMixin): if not silent: this.send({ 'msg': 'nosub', + 'id': id_, 'error': { 'error': 404, 'errorType': 'Meteor.Error', @@ -922,10 +961,14 @@ class DDP(APIMixin): collections__model_name=model_name(model), ).prefetch_related('collections'): pub = self.get_pub_by_name(sub.publication) + try: + queries = list(pub.user_queries(sub.user, *sub.params)) + except Exception: + queries = [] for qs, col in ( self.qs_and_collection(qs) for qs - in pub.get_queries(*sub.params) + in queries ): # check if obj is an instance of the model for the queryset if qs.model is not model: From df495423cfd86af177894758efba8aef5c74380a Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 21:21:23 +1100 Subject: [PATCH 29/31] Add test case with a GET request. --- CHANGES.rst | 4 +++ dddp/tests.py | 68 +++++++++++++++++++++++++++++++++++++++++++ requirements-test.txt | 2 +- setup.py | 3 ++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index cf0a14a..a53f838 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,10 @@ develop * Emit `django.core.signals.request_finished` to close DB connection and yield to other greenlets between processing messages from WebSocket. * Back to universal wheels, thanks to PEP-0496 environment markers. +* Expand test suite coverage to start a DDP server instance and perform + a HTTP GET request, no WebSocket/DDP tests yet. Fails in Pyton 3 + because of a bug in `gevent-websocket` + (https://bitbucket.org/noppo/gevent-websocket/issues/54/python-3-support). * Fix for #23 (Python 3 compatibility). * Set `application_name` on PostgreSQL async connection. * Send `django.core.signals.request_finished` when closing WebSocket. diff --git a/dddp/tests.py b/dddp/tests.py index 10c656e..3f98d06 100644 --- a/dddp/tests.py +++ b/dddp/tests.py @@ -1,9 +1,16 @@ """Django DDP test suite.""" +from __future__ import unicode_literals import doctest +import errno import os +import socket import unittest +from django.test import TestCase import dddp.alea +from dddp.main import DDPLauncher +# pylint: disable=E0611, F0401 +from six.moves.urllib_parse import urljoin os.environ['DJANGO_SETTINGS_MODULE'] = 'dddp.test.test_project.settings' @@ -12,12 +19,73 @@ DOCTEST_MODULES = [ ] +class DDPServerTestCase(TestCase): + + """Test case that starts a DDP server.""" + + server_addr = '127.0.0.1' + server_port_range = range(8000, 8080) + ssl_certfile_path = None + ssl_keyfile_path = None + + def setUp(self): + """Fire up the DDP server.""" + self.server_port = 8000 + kwargs = {} + if self.ssl_certfile_path: + kwargs['certfile'] = self.ssl_certfile_path + if self.ssl_keyfile_path: + kwargs['keyfile'] = self.ssl_keyfile_path + self.scheme = 'https' if kwargs else 'http' + for server_port in self.server_port_range: + self.server = DDPLauncher(debug=True) + self.server.add_web_servers( + [ + (self.server_addr, server_port), + ], + **kwargs + ) + try: + self.server.start() + self.server_port = server_port + return # server started + except socket.error as err: + if err.errno != errno.EADDRINUSE: + raise # error wasn't "address in use", re-raise. + continue # port in use, try next port. + raise RuntimeError('Failed to start DDP server.') + + def tearDown(self): + """Shut down the DDP server.""" + self.server.stop() + + def url(self, path): + """Return full URL for given path.""" + return urljoin( + '%s://%s:%d' % (self.scheme, self.server_addr, self.server_port), + path, + ) + + +class LaunchTestCase(DDPServerTestCase): + + """Test that server launches and handles GET request.""" + + def test_get(self): + """Perform HTTP GET.""" + import requests + resp = requests.get(self.url('/')) + self.assertEqual(resp.status_code, 200) + + def load_tests(loader, tests, pattern): """Specify which test cases to run.""" del pattern suite = unittest.TestSuite() # add all TestCase classes from this (current) module for attr in globals().values(): + if attr is DDPServerTestCase: + continue # not meant to be executed, is has no tests. try: if not issubclass(attr, unittest.TestCase): continue # not subclass of TestCase diff --git a/requirements-test.txt b/requirements-test.txt index dc85271..a5c10fa 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,2 +1,2 @@ # things required to run test suite -# (nothing required! we use unittest from stdlib...) +requests==2.9.0 diff --git a/setup.py b/setup.py index 9b0ca55..c310252 100644 --- a/setup.py +++ b/setup.py @@ -150,6 +150,9 @@ setuptools.setup( }, classifiers=CLASSIFIERS, test_suite='dddp.test.run_tests', + tests_require=[ + 'requests', + ], cmdclass={ 'build': Build, }, From 64aa79fab72d29699bc916868c87cfac9d1e8cec Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 21:29:35 +1100 Subject: [PATCH 30/31] Update CHANGES.rst, bump version number. --- CHANGES.rst | 7 ++++--- dddp/__init__.py | 2 +- docs/conf.py | 4 ++-- setup.py | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index a53f838..04ef82f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,9 +4,10 @@ Change Log All notable changes to this project will be documented in this file. This project adheres to `Semantic Versioning `_. -develop -------- -* Dropped support for Django 1.7 (didn't work anyway). +0.19.1 +------ +* Dropped support for Django 1.7 (support expired on December 1 2015, + see https://www.djangoproject.com/download/#supported-versions). * Require `setuptools>=18.5` at install time due to use of `python_platform_implementation` environment marker. * Moved repository to https://github.com/django-ddp/django-ddp (new diff --git a/dddp/__init__.py b/dddp/__init__.py index 8270b31..e027d7f 100644 --- a/dddp/__init__.py +++ b/dddp/__init__.py @@ -4,7 +4,7 @@ import sys from gevent.local import local from dddp import alea -__version__ = '0.18.1' +__version__ = '0.19.0' __url__ = 'https://github.com/django-ddp/django-ddp' default_app_config = 'dddp.apps.DjangoDDPConfig' diff --git a/docs/conf.py b/docs/conf.py index 3f4c805..d60fa22 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -88,9 +88,9 @@ copyright = u'2015, Tyson Clugg' # built documents. # # The short X.Y version. -version = '0.18' +version = '0.19' # The full version, including alpha/beta/rc tags. -release = '0.18.1' +release = '0.19.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index c310252..260f9bc 100644 --- a/setup.py +++ b/setup.py @@ -81,7 +81,7 @@ CLASSIFIERS = [ setuptools.setup( name='django-ddp', - version='0.18.1', + version='0.19.0', description=__doc__, long_description=open('README.rst').read(), author='Tyson Clugg', From 9c2c1aa831b2e9cd0f2180d1342c7d0d44b89595 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Wed, 16 Dec 2015 22:40:44 +1100 Subject: [PATCH 31/31] Test built distributions prior to release. --- MANIFEST.in | 7 +++++-- Makefile | 8 ++++---- tox.ini | 15 +++++++++------ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 046ad9d..377c556 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,10 @@ include .gitignore include Makefile exclude tox.ini graft dddp/test/meteor_todos -graft dddp/test/build -prune docs +prune dddp/test/build +prune dddp/test/meteor_todos/.meteor/local +graft docs +prune docs/_build +prune docs/node_modules exclude .travis.yml.sh exclude .travis.yml diff --git a/Makefile b/Makefile index 6396fa7..91e6e1b 100644 --- a/Makefile +++ b/Makefile @@ -19,10 +19,10 @@ clean-docs: $(MAKE) -C docs/ clean clean-dist: - rm -f "${SDIST}" "${WHEEL}" + rm -rf "${SDIST}" "${WHEEL}" dddp/test/build/ dddp/test/meteor_todos/.meteor/local/ clean-pyc: - find . -type f -name \*.pyc -print0 | xargs -0 rm + find . -type f -name \*.pyc -print0 | xargs -0 rm -f docs: $(shell find docs/ -type f -name \*.rst) docs/conf.py docs/Makefile $(shell find docs/_static/ -type f) $(shell find docs/_templates/ -type f) README.rst CHANGES.rst $(MAKE) -C docs/ clean html @@ -33,11 +33,11 @@ dist: ${SDIST} ${WHEEL} ${SDIST}: dist.intermediate @echo "Testing ${SDIST}..." - tox --installpkg ${SDIST} + tox --notest --installpkg ${SDIST} ${WHEEL}: dist.intermediate @echo "Testing ${WHEEL}..." - tox --installpkg ${WHEEL} + tox --notest --installpkg ${WHEEL} dist.intermediate: $(shell find dddp -type f) tox -e dist diff --git a/tox.ini b/tox.ini index 6de5cd3..ae3a0ab 100644 --- a/tox.ini +++ b/tox.ini @@ -118,16 +118,19 @@ deps = [testenv:dist] +install_command=sh -c 'pip install -U "setuptools>=18.5" "wheel>=0.25.0" "pip>=7.1.2" && pip install "$@" && sync' sh {opts} {packages} + +whitelist_externals=sh + commands = check-manifest --ignore "dddp/test/build*,dddp/test/meteor_todos/.meteor/local*" - {envpython} setup.py --quiet --no-user-cfg sdist --dist-dir={toxinidir}/dist/ - {envpython} setup.py --quiet --no-user-cfg bdist_wheel --dist-dir={toxinidir}/dist/ - cd docs && make html - -# the `dist` environment doesn't need our package installed -skip_install=True + {envpython} setup.py --no-user-cfg sdist --dist-dir={toxinidir}/dist/ + {envpython} setup.py --no-user-cfg bdist_wheel --dist-dir={toxinidir}/dist/ + sh -c "cd docs && sphinx-build -b html -d _build/doctrees -D latex_paper_size=a4 . _build/html" +usedevelop=True deps = + -rrequirements.txt -rrequirements-dev.txt check-manifest wheel