[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