[Python-modules-commits] [python-restless] 01/07: Import python-restless_2.1.1.orig.tar.gz

Wolfgang Borgert debacle at moszumanska.debian.org
Sun Nov 12 12:02:31 UTC 2017


This is an automated email from the git hooks/post-receive script.

debacle pushed a commit to branch master
in repository python-restless.

commit 8f3c5b63f708ffd8bf8558e8ec4f528f2769a0f9
Author: W. Martin Borgert <debacle at debian.org>
Date:   Sun Nov 12 11:45:14 2017 +0100

    Import python-restless_2.1.1.orig.tar.gz
---
 .travis.yml                  |  27 +++++++++--
 AUTHORS                      |   9 ++--
 README.rst                   |   9 +++-
 docs/cookbook.rst            |   2 +-
 docs/extending.rst           |  90 +++++++++++++++++++++++++++++++++--
 docs/index.rst               |   3 +-
 docs/releasenotes/v2.0.3.rst |   2 +-
 docs/releasenotes/v2.1.0.rst |  19 ++++++++
 docs/releasenotes/v2.1.1.rst |  10 ++++
 docs/tutorial.rst            |   5 +-
 restless/__init__.py         |   2 +-
 restless/dj.py               |   3 +-
 restless/it.py               |  47 -------------------
 restless/preparers.py        | 103 +++++++++++++++++++++++++++++++++++++++-
 restless/resources.py        |   4 +-
 restless/serializers.py      |   2 +-
 restless/tnd.py              |   2 +-
 restless/utils.py            |   7 +--
 tests/test_dj.py             |  21 +++++++--
 tests/test_it.py             | 109 -------------------------------------------
 tests/test_preparers.py      |  71 +++++++++++++++++++++++++++-
 tox.ini                      |  37 ++++++++++-----
 22 files changed, 380 insertions(+), 204 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index f5d08bc..0aa0687 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,32 @@
+sudo: false
+
 language: python
 
 python:
-  - "3.5"
-  - "3.4"
   - "2.7"
-  - "pypy"
-  - "pypy3"
+  - "3.3"
+  - "3.4"
+  - "3.5"
+  - "pypy" # cPython 2.7
+
+env:
+  - DJANGO="1.8"
+  - DJANGO="1.9"
+  - DJANGO="1.10"
+  - DJANGO="1.11"
+
+matrix:
+  exclude:
+    - python: "3.3"
+      env: DJANGO="1.9"
+    - python: "3.3"
+      env: DJANGO="1.10"
+    - python: "3.3"
+      env: DJANGO="1.11"
 
 # command to install dependencies
 install:
-    - pip install tox-travis
+    - pip install six tox-travis
 
 # command to run tests
 script:
diff --git a/AUTHORS b/AUTHORS
index fee754d..d9a4fed 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,11 +1,11 @@
 Primary authors:
 
-* Daniel Lindsley
+* Daniel Lindsley (@toastdriven)
 
 Maintainers:
 
-* Bruno Marques
-* Sergio Oliveira
+* Bruno Marques (@ElSaico)
+* Sergio Oliveira (@seocam)
 
 Contributors:
 
@@ -25,3 +25,6 @@ Contributors:
 * leonsmith
 * PabloCastellano
 * skevy
+* Ana Carolina (@anacarolinats)
+* Xiaoli Wang (@Xiaoli)
+* Tony Bajan (@tonybajan)
diff --git a/README.rst b/README.rst
index ac8fa00..643d5d6 100644
--- a/README.rst
+++ b/README.rst
@@ -5,6 +5,10 @@ restless
 .. image:: https://travis-ci.org/toastdriven/restless.png?branch=master
         :target: https://travis-ci.org/toastdriven/restless
 
+.. image:: https://coveralls.io/repos/github/toastdriven/restless/badge.svg?branch=master
+   :target: https://coveralls.io/github/toastdriven/restless?branch=master
+
+
 A lightweight REST miniframework for Python.
 
 Documentation is at http://restless.readthedocs.org/.
@@ -29,6 +33,7 @@ Features
 * JSON output by default, but overridable
 * RESTful
 * Python 3.2+ (with shims to make broke-ass Python 2.6+ work)
+* Django 1.8+
 * Flexible
 
 
@@ -153,11 +158,11 @@ The test suite uses tox_ for simultaneous support of multiple versions of both
 Python and Django. The current versions of Python supported are:
 
 * CPython 2.7
+* CPython 3.3
 * CPython 3.4
 * CPython 3.5
+* CPython 3.6
 * PyPy (Python 2.7)
-* PyPy3 (Python 3.2)
-* PyPy3 beta (Python 3.3)
 
 You just need to install the Python interpreters above and the `tox` package
 (available via `pip`), then run the `tox` command.
diff --git a/docs/cookbook.rst b/docs/cookbook.rst
index eeb8637..9798646 100644
--- a/docs/cookbook.rst
+++ b/docs/cookbook.rst
@@ -18,7 +18,7 @@ do something like::
         def is_authenticated(self):
             return self.request.user.is_authenticated()
 
-If you need a more fine graned authentication you could check your current endpoint and do something like that:
+If you need a more fine graned authentication you could check your current endpoint and do something like that::
 
     class MyResource(DjangoResource):
         def is_authenticated(self):
diff --git a/docs/extending.rst b/docs/extending.rst
index 02f5d66..8dcea20 100644
--- a/docs/extending.rst
+++ b/docs/extending.rst
@@ -128,9 +128,9 @@ manually or (once again) by extending a built-in method.::
         @classmethod
         def urls(cls, name_prefix=None):
             urlpatterns = super(PostResource, cls).urls(name_prefix=name_prefix)
-            return urlpatterns + [
+            return [
                 url(r'^schema/$', cls.as_view('schema'), name=cls.build_url_name('schema', name_prefix)),
-            ]
+            ] + urlpatterns
 
 .. note::
 
@@ -146,9 +146,10 @@ in your browser & get a JSON schema view!
 Customizing Data Output
 =======================
 
-There are three approaches to customizing your data ouput.
+There are four approaches to customizing your data ouput.
 
 #. The built-in ``Preparer/FieldsPreparer`` (simple)
+#. The included ``SubPreparer/CollectionSubPreparer`` (slightly more complex)
 #. Overriding :py:meth:`restless.resources.Resource.prepare` (happy medium)
 #. Per-method data (flexible but most work)
 
@@ -183,6 +184,89 @@ periods (like a Python import path) and recursively uses the previous value to
 look up the next value until a final value is found.
 
 
+Subpreparers & Collections
+--------------------------
+
+Sometimes, your data isn't completely flat but is instead nested. This
+frequently occurs in conjunction with related data, such as a foreign key'd
+object or many-to-many scenario. In this case, you can lever "subpreparers".
+Restless ships with two of these, the ``SubPreparer`` & the
+``CollectionSubPreparer``.
+
+The ``SubPreparer`` is useful for a single nested relation. You define a
+regular ``Preparer/FieldsPreparer`` (perhaps in a shareable location), then
+use the ``SubPreparer`` to pull it in & incorporate the nested data. For
+example::
+
+    # We commonly expose author information in our API as nested data.
+    # This definition can happen in its own module or wherever needed.
+    author_preparer = FieldsPreparer(fields={
+        'id': 'pk',
+        'username': 'username',
+        'name': 'get_full_name',
+    })
+
+    # ...
+
+    # Then, in the main preparer, pull them in using `SubPreparer`.
+    preparer = FieldsPreparer(fields={
+        'author': SubPreparer('user', author_preparer),
+        # Other fields can come before/follow as normal.
+        'content': 'post',
+        'created': 'created_at',
+    })
+
+This results in output like::
+
+    {
+        "content": "Isn't my blog cool? I think so...",
+        "created": "2017-05-22T10:34:48",
+        "author": {
+            "id": 5,
+            "username": "joe",
+            "name": "Joe Bob"
+        }
+    }
+
+The ``CollectionSubPreparer`` operates on the same principle (define a set
+of fields to be nested), but works with collections of things. These collections
+should be ordered & behave similar to iterables like ``list``s & ``tuples``.
+As an example::
+
+    # Set up a preparer that handles the data for each thing in the broader
+    # collection.
+    # Again, this can be in its own module or just wherever it's needed.
+    comment_preparer = FieldsPreparer(fields={
+        'comment': 'comment_text',
+        'created': 'created',
+    })
+
+    # Use it with the ``CollectionSubPreparer`` to create a list
+    # of prepared sub items.
+    preparer = FieldsPreparer(fields={
+        # A normal blog post field.
+        'post': 'post_text',
+        # All the comments on the post.
+        'comments': CollectionSubPreparer('comments.all', comment_preparer),
+    })
+
+Which would produce output like::
+
+    {
+        "post": "Another day, another blog post.",
+        "comments": [
+            {
+                "comment": "I hear you. Boring day here too.",
+                "created": "2017-05-23T16:43:22"
+            },
+            {
+                "comment": "SPAM SPAM SPAM",
+                "created": "2017-05-24T21:21:21"
+            }
+        ]
+    }
+
+
 Overriding ``prepare``
 ----------------------
 
diff --git a/docs/index.rst b/docs/index.rst
index 075a4f9..d6c5e7c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -4,14 +4,13 @@ restless
 
 A lightweight REST miniframework for Python.
 
-Works great with Django_, Flask_, Pyramid_, Tornado_ & Itty_, but should be useful for
+Works great with Django_, Flask_, Pyramid_ & Tornado_, but should be useful for
 many other Python web frameworks. Based on the lessons learned from Tastypie_
 & other REST libraries.
 
 .. _Django: http://djangoproject.com/
 .. _Flask: http://flask.pocoo.org/
 .. _Pyramid: http://www.pylonsproject.org/
-.. _Itty: https://pypi.python.org/pypi/itty
 .. _Tastypie: http://tastypieapi.org/
 .. _Tornado: http://www.tornadoweb.org/
 .. _tox: https://tox.readthedocs.io/
diff --git a/docs/releasenotes/v2.0.3.rst b/docs/releasenotes/v2.0.3.rst
index 51f47fd..1d4ae35 100644
--- a/docs/releasenotes/v2.0.3.rst
+++ b/docs/releasenotes/v2.0.3.rst
@@ -3,7 +3,7 @@ restless v2.0.3
 
 :date: 2016-11-21
 
-This release adds a change which was in restkiss v2.0.2 but got lost in the
+This release adds a change which was in restless v2.0.2 but got lost in the
 backporting process - sorry, everybody!
 
 
diff --git a/docs/releasenotes/v2.1.0.rst b/docs/releasenotes/v2.1.0.rst
new file mode 100644
index 0000000..69b5c22
--- /dev/null
+++ b/docs/releasenotes/v2.1.0.rst
@@ -0,0 +1,19 @@
+restless v2.1.0
+===============
+
+:date: 2017-06-01
+
+
+Features
+--------
+
+* Added ``SubPreparer`` and ``CollectionSubPreparer`` classes to make easier to nest responses
+* Hability of using callables in preparers (as soon as they don't have args)
+
+Changes
+-------
+
+* Dropped Itty support :(
+* Proper HTTP status messages
+* Added support to Django 1.9 to 1.11 (dropped support to Django <= 1.7)
+* Proper wrapping for decorators
diff --git a/docs/releasenotes/v2.1.1.rst b/docs/releasenotes/v2.1.1.rst
new file mode 100644
index 0000000..a5efe0c
--- /dev/null
+++ b/docs/releasenotes/v2.1.1.rst
@@ -0,0 +1,10 @@
+restless v2.1.1
+===============
+
+:date: 2017-06-01
+
+
+Bug Fixes
+-------
+
+* Fixed an issue caused by trying to import six on setup.py file
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index e6b8521..b9bede3 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -63,7 +63,7 @@ Alternately, you can download the latest development source from Github::
 Getting Started
 ===============
 
-Restless currently supports Django_, Flask_, Pyramid_ & Itty_.
+Restless currently supports Django_, Flask_, Pyramid_ & Tornado_.
 For the purposes of most of this
 tutorial, we'll assume you're using Django. The process for developing &
 interacting with the API via Flask is nearly identical (& we'll be covering the
@@ -80,7 +80,6 @@ behavior of the various `REST methods`_.
 .. _Django: http://djangoproject.com/
 .. _Flask: http://flask.pocoo.org/
 .. _Pyramid: http://www.pylonsproject.org/
-.. _Itty: https://pypi.python.org/pypi/itty
 .. _`REST methods`: http://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services
 
 
@@ -548,7 +547,7 @@ adding the ``is_authenticated`` method.::
         def detail(self, pk):
             return Post.objects.get(id=pk)
 
-        def create(self, data):
+        def create(self):
             return Post.objects.create(
                 title=self.data['title'],
                 user=User.objects.get(username=self.data['author']),
diff --git a/restless/__init__.py b/restless/__init__.py
index c1ac75c..74ebaa3 100644
--- a/restless/__init__.py
+++ b/restless/__init__.py
@@ -1,6 +1,6 @@
 __author__ = 'Daniel Lindsley'
 __license__ = 'BSD'
-__version__ = (2, 0, 3)
+__version__ = (2, 1, 1)
 VERSION = '.'.join(map(str, __version__))
 
 
diff --git a/restless/dj.py b/restless/dj.py
index 4503b4c..3862170 100644
--- a/restless/dj.py
+++ b/restless/dj.py
@@ -36,8 +36,7 @@ class DjangoResource(Resource):
             content_type = 'text/plain'
         else:
             content_type = 'application/json'
-        resp = HttpResponse(data, content_type=content_type)
-        resp.status_code = status
+        resp = HttpResponse(data, content_type=content_type, status=status)
         return resp
 
     def build_error(self, err):
diff --git a/restless/it.py b/restless/it.py
deleted file mode 100644
index 36e5566..0000000
--- a/restless/it.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import re
-
-import itty
-
-from restless.constants import OK, NO_CONTENT
-from restless.resources import Resource
-
-
-class IttyResource(Resource):
-    """
-    A Itty-specific ``Resource`` subclass.
-
-    Doesn't require any special configuration, but helps when working in a
-    Itty environment.
-    """
-    debug = False
-
-    def is_debug(self):
-        return self.debug
-
-    def build_response(self, data, status=OK):
-        if status == NO_CONTENT:
-            # Avoid crashing the client when it tries to parse nonexisting JSON.
-            content_type = 'text/plain'
-        else:
-            content_type = 'application/json'
-        return itty.Response(data, status=status, content_type=content_type)
-
-    @classmethod
-    def setup_urls(cls, rule_prefix):
-        """
-        A convenience method for hooking up the URLs.
-
-        This automatically adds a list & a detail endpoint to your request
-        mappings.
-
-        :returns: ``None``
-        """
-        list_url = "%s" % itty.add_slash(rule_prefix)
-        detail_url = "%s" % itty.add_slash(rule_prefix + "/(?P<pk>[\w-]+)")
-
-        list_re = re.compile("^%s$" % list_url)
-        detail_re = re.compile("^%s$" % detail_url)
-
-        for method in ('GET', 'POST', 'PUT', 'DELETE'):
-            itty.REQUEST_MAPPINGS[method].append((list_re, list_url, cls.as_list()))
-            itty.REQUEST_MAPPINGS[method].append((detail_re, detail_url, cls.as_detail()))
diff --git a/restless/preparers.py b/restless/preparers.py
index 1373fa3..1393931 100644
--- a/restless/preparers.py
+++ b/restless/preparers.py
@@ -55,7 +55,10 @@ class FieldsPreparer(Preparer):
             return data
 
         for fieldname, lookup in self.fields.items():
-            result[fieldname] = self.lookup_data(lookup, data)
+            if isinstance(lookup, SubPreparer):
+                result[fieldname] = lookup.prepare(data)
+            else:
+                result[fieldname] = self.lookup_data(lookup, data)
 
         return result
 
@@ -108,8 +111,106 @@ class FieldsPreparer(Preparer):
             # Assume it's an object.
             value = getattr(data, part)
 
+        # Call if it's callable except if it's a Django DB manager instance
+        #   We check if is a manager by checking the db_manager (duck typing)
+        if callable(value) and not hasattr(value, 'db_manager'):
+            value = value()
+
         if not remaining_lookup:
             return value
 
         # There's more to lookup, so dive in recursively.
         return self.lookup_data(remaining_lookup, value)
+
+
+class SubPreparer(FieldsPreparer):
+    """
+    A preparation class designed to be used within other preparers.
+
+    This is primary to enable deeply-nested structures, allowing you
+    to compose/share definitions as well. Typical usage consists of creating
+    a configured instance of a FieldsPreparer, then use a `SubPreparer` to
+    pull it in.
+
+    Example::
+
+        # First, define the nested fields you'd like to expose.
+        author_preparer = FieldsPreparer(fields={
+            'id': 'pk',
+            'username': 'username',
+            'name': 'get_full_name',
+        })
+        # Then, in the main preparer, pull them in using `SubPreparer`.
+        preparer = FieldsPreparer(fields={
+            'author': SubPreparer('user', author_preparer),
+            # Other fields can come before/follow as normal.
+            'content': 'post',
+            'created': 'created_at',
+        })
+
+    """
+    def __init__(self, lookup, preparer):
+        self.lookup = lookup
+        self.preparer = preparer
+
+    def get_inner_data(self, data):
+        """
+        Used internally so that the correct data is extracted out of the
+        broader dataset, allowing the preparer being called to deal with just
+        the expected subset.
+        """
+        return self.lookup_data(self.lookup, data)
+
+    def prepare(self, data):
+        """
+        Handles passing the data to the configured preparer.
+
+        Uses the ``get_inner_data`` method to provide the correct subset of
+        the data.
+
+        Returns a dictionary of data as the response.
+        """
+        return self.preparer.prepare(self.get_inner_data(data))
+
+
+class CollectionSubPreparer(SubPreparer):
+    """
+    A preparation class designed to handle collections of data.
+
+    This is useful in the case where you have a 1-to-many or many-to-many
+    relationship of data to expose as part of the parent data.
+
+    Example::
+
+        # First, set up a preparer that handles the data for each thing in
+        # the broader collection.
+        comment_preparer = FieldsPreparer(fields={
+            'comment': 'comment_text',
+            'created': 'created',
+        })
+        # Then use it with the ``CollectionSubPreparer`` to create a list
+        # of prepared sub items.
+        preparer = FieldsPreparer(fields={
+            # A normal blog post field.
+            'post': 'post_text',
+            # All the comments on the post.
+            'comments': CollectionSubPreparer('comments.all', comment_preparer),
+        })
+
+    """
+    def prepare(self, data):
+        """
+        Handles passing each item in the collection data to the configured
+        subpreparer.
+
+        Uses a loop and the ``get_inner_data`` method to provide the correct
+        item of the data.
+
+        Returns a list of data as the response.
+        """
+        result = []
+
+        for item in self.get_inner_data(data):
+            result.append(self.preparer.prepare(item))
+
+        return result
diff --git a/restless/resources.py b/restless/resources.py
index f6ad6c9..644319d 100644
--- a/restless/resources.py
+++ b/restless/resources.py
@@ -1,4 +1,4 @@
-import six
+from functools import wraps
 import sys
 
 from .constants import OK, CREATED, ACCEPTED, NO_CONTENT
@@ -13,6 +13,7 @@ def skip_prepare(func):
     """
     A convenience decorator for indicating the raw data should not be prepared.
     """
+    @wraps(func)
     def _wrapper(self, *args, **kwargs):
         value = func(self, *args, **kwargs)
         return Data(value, should_prepare=False)
@@ -132,6 +133,7 @@ class Resource(object):
 
         :returns: View function
         """
+        @wraps(cls)
         def _wrapper(request, *args, **kwargs):
             # Make a new instance so that no state potentially leaks between
             # instances.
diff --git a/restless/serializers.py b/restless/serializers.py
index b372993..43147d9 100644
--- a/restless/serializers.py
+++ b/restless/serializers.py
@@ -35,7 +35,7 @@ class Serializer(object):
         in the appropriate format.
 
         :param data: The body for the response
-        :type data: string
+        :type data: ``list`` or ``dict``
 
         :returns: A serialized version of the data
         :rtype: string
diff --git a/restless/tnd.py b/restless/tnd.py
index 206163a..8231d39 100644
--- a/restless/tnd.py
+++ b/restless/tnd.py
@@ -141,7 +141,7 @@ class TornadoResource(Resource):
         self.ref_rh.finish(data)
 
     def is_debug(self):
-        return self.application.settings['debug']
+        return self.application.settings.get('debug', False)
 
     @gen.coroutine
     def handle(self, endpoint, *args, **kwargs):
diff --git a/restless/utils.py b/restless/utils.py
index 96acffa..b8e3b4e 100644
--- a/restless/utils.py
+++ b/restless/utils.py
@@ -1,13 +1,10 @@
+
 import datetime
 import decimal
+import json
 import traceback
 import uuid
 
-try:
-    import json
-except ImportError:
-    import simplejson as json
-
 
 class MoreTypesJSONEncoder(json.JSONEncoder):
     """
diff --git a/tests/test_dj.py b/tests/test_dj.py
index 9aaf1bd..d5da9b8 100644
--- a/tests/test_dj.py
+++ b/tests/test_dj.py
@@ -1,17 +1,23 @@
 import unittest
 
 try:
+    from http.client import responses
+except ImportError:
+    from httplib import responses
+
+try:
+    from django.conf import settings
+except ImportError:
+    settings = None
+    DjangoResource = object
+else:
     from django.http import Http404
     from django.core.exceptions import ObjectDoesNotExist
 
     # Ugh. Settings for Django.
-    from django.conf import settings
     settings.configure(DEBUG=True)
 
     from restless.dj import DjangoResource
-except ImportError:
-    settings = None
-    DjangoResource = object
 
 from restless.exceptions import Unauthorized
 from restless.preparers import FieldsPreparer
@@ -222,6 +228,8 @@ class DjangoResourceTestCase(unittest.TestCase):
         resp = self.res.handle('list')
         self.assertEqual(resp['Content-Type'], 'application/json')
         self.assertEqual(resp.status_code, 501)
+        self.assertEqual(resp.reason_phrase.title(), responses[501])
+
         resp_json = json.loads(resp.content.decode('utf-8'))
         self.assertEqual(
             resp_json['error'], "Unsupported method 'TRACE' for list endpoint.")
@@ -235,6 +243,8 @@ class DjangoResourceTestCase(unittest.TestCase):
         resp = self.res.handle('list')
         self.assertEqual(resp['Content-Type'], 'application/json')
         self.assertEqual(resp.status_code, 401)
+        self.assertEqual(resp.reason_phrase.title(), responses[401])
+
         resp_json = json.loads(resp.content.decode('utf-8'))
         self.assertEqual(resp_json['error'], 'Unauthorized.')
         self.assertTrue('traceback' in resp_json)
@@ -270,6 +280,7 @@ class DjangoResourceTestCase(unittest.TestCase):
         resp = self.res.handle('detail')
         self.assertEqual(resp['Content-Type'], 'application/json')
         self.assertEqual(resp.status_code, 500)
+        self.assertEqual(resp.reason_phrase.title(), responses[500])
         self.assertEqual(json.loads(resp.content.decode('utf-8')), {
             'error': {
                 'code': 'random-crazy',
@@ -287,6 +298,7 @@ class DjangoResourceTestCase(unittest.TestCase):
         resp = self.res.handle('detail', 1001)
         self.assertEqual(resp['Content-Type'], 'application/json')
         self.assertEqual(resp.status_code, 404)
+        self.assertEqual(resp.reason_phrase.title(), responses[404])
         self.assertEqual(json.loads(resp.content.decode('utf-8')), {
             'error': 'Model with pk 1001 not found.'
         })
@@ -302,6 +314,7 @@ class DjangoResourceTestCase(unittest.TestCase):
         resp = res.handle('detail', 1001)
         self.assertEqual(resp['Content-Type'], 'application/json')
         self.assertEqual(resp.status_code, 404)
+        self.assertEqual(resp.reason_phrase.title(), responses[404])
         self.assertEqual(json.loads(resp.content.decode('utf-8')), {
             'error': 'Model with pk 1001 not found.'
         })
diff --git a/tests/test_it.py b/tests/test_it.py
deleted file mode 100644
index 9d765cf..0000000
--- a/tests/test_it.py
+++ /dev/null
@@ -1,109 +0,0 @@
-import unittest
-
-try:
-    import itty
-    from restless.it import IttyResource
-except ImportError:
-    itty = None
-    IttyResource = object
-
-from restless.utils import json
-
-from .fakes import FakeHttpRequest
-
-
-class ItTestResource(IttyResource):
-    fake_db = []
-
-    def fake_init(self):
-        # Just for testing.
-        self.__class__.fake_db = [
-            {"id": 'dead-beef', "title": 'First post'},
-            {"id": 'de-faced', "title": 'Another'},
-            {"id": 'bad-f00d', "title": 'Last'},
-        ]
-
-    def list(self):
-        return self.fake_db
-
-    def detail(self, pk):
-        for item in self.fake_db:
-            if item['id'] == pk:
-                return item
-
-    def create(self):
-        self.fake_db.append(self.data)
-
-
- at unittest.skipIf(not itty, "itty is not available")
-class IttyResourceTestCase(unittest.TestCase):
-    def setUp(self):
-        super(IttyResourceTestCase, self).setUp()
-        self.res = ItTestResource()
-
-        # Just for the fake data.
-        self.res.fake_init()
-
-    def test_as_list(self):
-        list_endpoint = ItTestResource.as_list()
-        request = FakeHttpRequest('GET')
-
-        resp = list_endpoint(request)
-        self.assertEqual(resp.content_type, 'application/json')
-        self.assertEqual(resp.status, 200)
-        self.assertEqual(json.loads(resp.output), {
-            'objects': [
-                {
-                    'id': 'dead-beef',
-                    'title': 'First post'
-                },
-                {
-                    'id': 'de-faced',
-                    'title': 'Another'
-                },
-                {
-                    'id': 'bad-f00d',
-                    'title': 'Last'
-                }
-            ]
-        })
-
-    def test_as_detail(self):
-        detail_endpoint = ItTestResource.as_detail()
-        request = FakeHttpRequest('GET')
-
-        resp = detail_endpoint(request, 'de-faced')
-        self.assertEqual(resp.content_type, 'application/json')
-        self.assertEqual(resp.status, 200)
-        self.assertEqual(json.loads(resp.output), {
-            'id': 'de-faced',
-            'title': 'Another'
-        })
-
-    def test_is_debug(self):
-        self.assertFalse(self.res.is_debug())
-
-        self.res.debug = True
-        self.addCleanup(setattr, self.res, 'debug', False)
-        self.assertTrue(self.res.is_debug())
-
-    def test_build_response(self):
-        resp = self.res.build_response('Hello, world!', status=302)
-        self.assertEqual(resp.status, 302)
-        self.assertEqual(resp.content_type, 'application/json')
-        self.assertEqual(resp.output, 'Hello, world!')
-
-    def test_setup_urls(self):
-        self.assertEqual(len(itty.REQUEST_MAPPINGS['GET']), 0)
-        self.assertEqual(len(itty.REQUEST_MAPPINGS['POST']), 0)
-        self.assertEqual(len(itty.REQUEST_MAPPINGS['PUT']), 0)
-        self.assertEqual(len(itty.REQUEST_MAPPINGS['DELETE']), 0)
-
-        ItTestResource.setup_urls('/test')
-        self.assertEqual(len(itty.REQUEST_MAPPINGS['GET']), 2)
-        self.assertEqual(len(itty.REQUEST_MAPPINGS['POST']), 2)
-        self.assertEqual(len(itty.REQUEST_MAPPINGS['PUT']), 2)
-        self.assertEqual(len(itty.REQUEST_MAPPINGS['DELETE']), 2)
-        self.assertEqual(itty.REQUEST_MAPPINGS['GET'][0][1], '/test/')
-        self.assertEqual(itty.REQUEST_MAPPINGS['GET'][1][1],
-                         '/test/(?P<pk>[\\w-]+)/')
diff --git a/tests/test_preparers.py b/tests/test_preparers.py
index 009f66d..a3e485d 100644
--- a/tests/test_preparers.py
+++ b/tests/test_preparers.py
@@ -1,6 +1,7 @@
 import unittest
 
-from restless.preparers import Preparer, FieldsPreparer
+from restless.preparers import (CollectionSubPreparer, SubPreparer,
+                                FieldsPreparer)
 
 
 class InstaObj(object):
@@ -8,6 +9,9 @@ class InstaObj(object):
         for k, v in kwargs.items():
             setattr(self, k, v)
 
+    def dont(self):
+        return {'panic': 'vogon'}
+
 
 class LookupDataTestCase(unittest.TestCase):
     def setUp(self):
@@ -35,6 +39,14 @@ class LookupDataTestCase(unittest.TestCase):
                 ),
             },
             'parent': None,
+            'who': [
+                {'name': 'Ford'},
+                {'name': 'Arthur'},
+                {'name': 'Beeblebrox'},
+            ],
+            'dont': lambda: {
+                'panic': 'vogon',
+            },
         }
 
     def test_dict_simple(self):
@@ -74,3 +86,60 @@ class LookupDataTestCase(unittest.TestCase):
     def test_complex_miss(self):
         with self.assertRaises(AttributeError):
             self.preparer.lookup_data('more.nested.nope', self.dict_data)
+
+    def test_obj_callable(self):
+        self.assertEqual(
+            self.preparer.lookup_data('dont.panic', self.obj_data),
+            'vogon',
+        )
+
+    def test_dict_callable(self):
+        self.assertEqual(
+            self.preparer.lookup_data('dont.panic', self.dict_data),
+            'vogon',
+        )
+
+    def test_prepare_simple(self):
+        preparer = FieldsPreparer(fields={
+            'flying': 'say',
+        })
+        preped = preparer.prepare(self.obj_data)
+        self.assertEqual(preped, {'flying': 'what'})
+
+    def test_prepare_subpreparer(self):
+        subpreparer = FieldsPreparer(fields={
+            'id': 'id',
+            'data': 'data',
+        })
+        preparer = FieldsPreparer(fields={
+            'flying': 'say',
+            'wale': SubPreparer('moof.buried', subpreparer),
+        })
+        preped = preparer.prepare(self.obj_data)
+
+    def test_prepare_subsubpreparer(self):
+        subsubpreparer = FieldsPreparer(fields={
+            'really': 'yes',
+        })
+        subpreparer = FieldsPreparer(fields={
+            'data': SubPreparer('data', subsubpreparer),
+        })
+        preparer = FieldsPreparer(fields={
+            'wale': SubPreparer('moof.buried', subpreparer),
+        })
+        preped = preparer.prepare(self.obj_data)
+        self.assertEqual(preped, {'wale': {'data': {'really': 'no'}}})
+
+    def test_prepare_collection_subpreparer(self):
+        subpreparer = FieldsPreparer(fields={
+            'name': 'name',
+        })
+        preparer = FieldsPreparer(fields={
+            'who': CollectionSubPreparer('who', subpreparer),
+        })
+        preped = preparer.prepare(self.dict_data)
+        self.assertEqual(preped, {'who': [
+            {'name': 'Ford'},
+            {'name': 'Arthur'},
+            {'name': 'Beeblebrox'},
+        ]})
diff --git a/tox.ini b/tox.ini
index d756e93..0408d6f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,29 +1,44 @@
 [tox]
-envlist = py{27,34,35,py2,py32,py33}-{dj18,dj19}
+envlist =
+    py{27,33,34,35,py2}-{dj18}
+    py{27,34,35,py2}-{dj19,dj110,dj111}
+    py{36}-{dj111}
 
 [testenv]
 basepython =
     py27: python2.7
+    py33: python3.3
     py34: python3.4
     py35: python3.5
+    py36: python3.6
     pypy2: pypy
-    pypy32: pypy3.2
-    pypy33: pypy3.3
 deps =
     six
     pytest
     pytest-cov
-    py{27,34,35,py2}: Flask
-    Pyramid
+    WebOb>=1.3.1,<1.7
+    Pyramid<1.8
     tornado
+    py{27,33,34,35}: Flask>=0.10
     dj18: Django>=1.8,<1.9
     dj19: Django>=1.9,<1.10
+    dj110: Django>=1.10,<1.11
+    dj111: Django>=1.11,<1.12
 commands =
     py.test --cov=restless
 
-[tox:travis]
-2.7 = py27
-3.4 = py34
-3.5 = py35
-pypy = pypy2
-pypy3 = pypy32
\ No newline at end of file
+[travis]
+python =
+    2.7: py27
+    3.3: py33
+    3.4: py34
+    3.5: py35
+    3.6: py36
+    pypy: pypy2
+
+[travis:env]
+DJANGO =
+    1.8: dj18
+    1.9: dj19
+    1.10: dj110
+    1.11: dj111

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-restless.git



More information about the Python-modules-commits mailing list