[Python-modules-commits] [python-restless] 01/07: Import python-restless_2.0.3.orig.tar.gz
Wolfgang Borgert
debacle at moszumanska.debian.org
Mon Dec 26 03:21:02 UTC 2016
This is an automated email from the git hooks/post-receive script.
debacle pushed a commit to branch master
in repository python-restless.
commit 15a5e04ae4fc0a19a8d512bbf6ccec7e4cb618a4
Author: W. Martin Borgert <debacle at debian.org>
Date: Mon Dec 26 02:41:30 2016 +0000
Import python-restless_2.0.3.orig.tar.gz
---
.gitignore | 8 +++-
.travis.yml | 13 +++---
AUTHORS | 19 ++++++++
README.rst | 40 +++++++----------
docs/conf.py | 11 ++---
docs/cookbook.rst | 8 ++++
docs/extending.rst | 8 ++--
docs/index.rst | 5 ++-
docs/releasenotes/v2.0.2.rst | 36 +++++++++++++++
docs/releasenotes/v2.0.3.rst | 14 ++++++
docs/tutorial.rst | 14 +++---
examples/django/posts/urls.py | 6 +--
restless/__init__.py | 3 +-
restless/constants.py | 14 ++++++
restless/dj.py | 22 +++++----
restless/exceptions.py | 80 ++++++++++++++++++++++++++++++++-
restless/fl.py | 10 ++++-
restless/it.py | 13 ++++--
restless/preparers.py | 4 +-
restless/pyr.py | 13 ++++--
restless/resources.py | 27 ++++-------
restless/serializers.py | 10 +++--
restless/tnd.py | 12 +++--
restless/utils.py | 4 +-
rtfd-requirements.txt | 1 +
setup.py | 5 ++-
test2_requirements.txt | 3 --
test3_requirements.txt | 8 ----
tests/test_dj.py | 71 ++++++++++++++++++++++-------
tests/test_fl.py | 27 ++++++-----
tests/test_it.py | 19 ++++----
tests/test_preparers.py | 10 ++++-
tests/test_pyr.py | 26 ++++++-----
tests/test_resources.py | 8 ++--
tests/test_serializers.py | 7 +++
tests/test_tnd.py | 102 ++++++++++++++++++++++++++++++++++--------
tox.ini | 29 ++++++++++++
37 files changed, 524 insertions(+), 186 deletions(-)
diff --git a/.gitignore b/.gitignore
index 68e0779..69095b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,6 @@
__pycache__
build
dist
-env2
-env3
docs/_build
tests/.coverage
tests/cover
@@ -12,3 +10,9 @@ tests/htmlcov
.coverage
htmlcov
*.egg-info
+.cache/
+.eggs/
+.tox/
+*.swp
+.idea/
+*.whl
diff --git a/.travis.yml b/.travis.yml
index a404880..f5d08bc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,19 +1,16 @@
language: python
python:
+ - "3.5"
- "3.4"
- - "3.3"
- "2.7"
+ - "pypy"
+ - "pypy3"
# command to install dependencies
install:
- - pip install -r test${TRAVIS_PYTHON_VERSION:0:1}_requirements.txt
- - pip install coveralls
+ - pip install tox-travis
# command to run tests
script:
- - py.test --cov=restless
-
-# command to notify coveralls
-after_success:
- - coveralls
+ - tox
diff --git a/AUTHORS b/AUTHORS
index fe23116..fee754d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -2,7 +2,26 @@ Primary authors:
* Daniel Lindsley
+Maintainers:
+
+* Bruno Marques
+* Sergio Oliveira
Contributors:
* Matt George
+* socketubs
+* mission-liao
+* frewsxcv
+* viniciuscainelli
+* schmitch
+* karmux
+* binarydud
+* issackelly
+* jmwohl
+* jphalip
+* mindflayer
+* czartur
+* leonsmith
+* PabloCastellano
+* skevy
diff --git a/README.rst b/README.rst
index f92d619..ac8fa00 100644
--- a/README.rst
+++ b/README.rst
@@ -4,8 +4,6 @@ restless
.. image:: https://travis-ci.org/toastdriven/restless.png?branch=master
:target: https://travis-ci.org/toastdriven/restless
-.. image:: https://coveralls.io/repos/toastdriven/restless/badge.png
- :target: https://coveralls.io/r/toastdriven/restless
A lightweight REST miniframework for Python.
@@ -21,6 +19,7 @@ many other Python web frameworks. Based on the lessons learned from Tastypie_
.. _Itty: https://pypi.python.org/pypi/itty
.. _Tastypie: http://tastypieapi.org/
.. _Tornado: http://www.tornadoweb.org/
+.. _tox: https://tox.readthedocs.io/
Features
@@ -29,7 +28,7 @@ Features
* Small, fast codebase
* JSON output by default, but overridable
* RESTful
-* Python 3.3+ (with shims to make broke-ass Python 2.6+ work)
+* Python 3.2+ (with shims to make broke-ass Python 2.6+ work)
* Flexible
@@ -130,15 +129,15 @@ Hooking it up:
.. code:: python
# api/urls.py
- from django.conf.urls.default import url, patterns, include
+ from django.conf.urls.default import url, include
from posts.api import PostResource
- urlpatterns = patterns('',
+ urlpatterns = [
# The usual suspects, then...
url(r'^api/posts/', include(PostResource.urls())),
- )
+ ]
Licence
@@ -150,24 +149,15 @@ BSD
Running the Tests
=================
-Getting the tests running looks like:
+The test suite uses tox_ for simultaneous support of multiple versions of both
+Python and Django. The current versions of Python supported are:
-.. code:: sh
+* CPython 2.7
+* CPython 3.4
+* CPython 3.5
+* PyPy (Python 2.7)
+* PyPy3 (Python 3.2)
+* PyPy3 beta (Python 3.3)
- $ virtualenv -p python3 env3
- $ . env3/bin/activate
- $ pip install -r test3_requirements.txt
- $ export PYTHONPATH=`pwd`
- $ py.test -s -v --cov=restless --cov-report=html tests
-
-For Python 2:
-
-.. code:: sh
-
- $ virtualenv env2
- $ . env2/bin/activate
- $ pip install -r test2_requirements.txt
- $ export PYTHONPATH=`pwd`
- $ py.test -s -v --cov=restless --cov-report=html tests
-
-Coverage is at about 94%, so please don't make it worse. :D
+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/conf.py b/docs/conf.py
index 48a8be4..74054d1 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -52,10 +52,11 @@ copyright = u'2014, Daniel Lindsley'
# |version| and |release|, also used in various other places throughout the
# built documents.
#
-# The short X.Y version.
-version = '2.0.1'
+import restless # noqa
# The full version, including alpha/beta/rc tags.
-release = '2.0.1'
+release = restless.VERSION
+# The short X.Y version.
+version = restless.VERSION
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -243,8 +244,8 @@ man_pages = [
# dir menu entry, description, category)
texinfo_documents = [
('index', 'restless', u'restless Documentation',
- u'Daniel Lindsley', 'restless', 'One line description of project.',
- 'Miscellaneous'),
+ u'Daniel Lindsley', 'restless',
+ u'A lightweight REST miniframework for Python.', 'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
diff --git a/docs/cookbook.rst b/docs/cookbook.rst
index 6b5967d..eeb8637 100644
--- a/docs/cookbook.rst
+++ b/docs/cookbook.rst
@@ -18,3 +18,11 @@ 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:
+
+ class MyResource(DjangoResource):
+ def is_authenticated(self):
+ if self.endpoint in ('update', 'create'):
+ return self.request.user.is_authenticated()
+ else:
+ return True
diff --git a/docs/extending.rst b/docs/extending.rst
index 29c9670..02f5d66 100644
--- a/docs/extending.rst
+++ b/docs/extending.rst
@@ -92,7 +92,7 @@ Finally, it's just a matter of hooking up the URLs as well. You can do this
manually or (once again) by extending a built-in method.::
# Add the correct import here.
- from django.conf.urls import patterns, url
+ from django.conf.urls import url
from restless.dj import DjangoResource
from restless.resources import skip_prepare
@@ -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 + patterns('',
+ return urlpatterns + [
url(r'^schema/$', cls.as_view('schema'), name=cls.build_url_name('schema', name_prefix)),
- )
+ ]
.. note::
@@ -374,7 +374,7 @@ up your code into a validation method. An example of this might look like...::
from django.forms import ModelForm
from restless.dj import DjangoResource
- from restless.exceptions import HttpError
+ from restless.exceptions import BadRequest
class UserForm(ModelForm):
diff --git a/docs/index.rst b/docs/index.rst
index ef8ed03..075a4f9 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -14,6 +14,7 @@ many other Python web frameworks. Based on the lessons learned from Tastypie_
.. _Itty: https://pypi.python.org/pypi/itty
.. _Tastypie: http://tastypieapi.org/
.. _Tornado: http://www.tornadoweb.org/
+.. _tox: https://tox.readthedocs.io/
Features
@@ -22,7 +23,7 @@ Features
* Small, fast codebase
* JSON output by default, but overridable
* RESTful
-* Python 3.3+ (with shims to make broke-ass Python 2.6+ work)
+* Python 3.2+ (with shims to make broke-ass Python 2.6+ work)
* Flexible
@@ -75,6 +76,8 @@ Release Notes
.. toctree::
:maxdepth: 1
+ releasenotes/v2.0.3
+ releasenotes/v2.0.2
releasenotes/v2.0.1
releasenotes/v2.0.0
releasenotes/v1.4.0
diff --git a/docs/releasenotes/v2.0.2.rst b/docs/releasenotes/v2.0.2.rst
new file mode 100644
index 0000000..1ecdc89
--- /dev/null
+++ b/docs/releasenotes/v2.0.2.rst
@@ -0,0 +1,36 @@
+restless v2.0.2
+===============
+
+:date: 2016-11-14
+
+This release makes some long-needed changes on error handling for ``Resource``
+and its subclasses, plus support for both Django >= 1.9 and Tornado >= 4.0 and
+allowing alphanumeric PKs on all supported frameworks.
+
+
+Features
+--------
+
+* Allowed PKs with dashes and alphanumeric digits. (SHA: e52333b)
+* Reworked test suite so that it uses ``tox`` for simultaneously testing on
+ CPython and PyPy, both 2.x and 3.x (SHA: 2035e21, SHA: 9ca0e8c, SHA: 3915980
+ & SHA: a1d2d96)
+* Reworked ``Resource`` so that it throws a ``NotImplementedError`` instead of
+ returning an ``HttpResponse`` from Django. (SHA: 27859c8)
+* Added several ``HttpError`` subclasses. (SHA: e2aff93)
+* Changed ``Resource`` so that it allows any serializable object on the response
+ body. (SHA: 1e3522b & SHA: b70a492)
+
+
+Bugfixes
+--------
+
+* Changed ``JSONSerializer`` to throw a ``BadRequest`` upon a serialization
+ error. (SHA: 8471463)
+* Updated ``DjangoResource`` to use lists instead of the deprecated
+ ``django.conf.urls.patterns`` object. (SHA: f166e4d & SHA: f94c500)
+* Fixed ``FieldsPreparer`` behavior when parsing objects with a custom
+ ``__getattr__``. (SHA: 665ef31)
+* Applied Debian's fix to Tornado tests for version 4.0.0 onwards. (SHA: 372e00a)
+* Skips tests for all unavailable frameworks. (SHA: 8b81b17)
+
diff --git a/docs/releasenotes/v2.0.3.rst b/docs/releasenotes/v2.0.3.rst
new file mode 100644
index 0000000..51f47fd
--- /dev/null
+++ b/docs/releasenotes/v2.0.3.rst
@@ -0,0 +1,14 @@
+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
+backporting process - sorry, everybody!
+
+
+Features
+--------
+
+* Changed all ``Resource`` subclasses so that a 204 No Content response sends
+ ``text/plain`` on ``Content-Type``. (SHA: 116da9f & SHA: b10be61)
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index ececea8..e6b8521 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -81,7 +81,7 @@ behavior of the various `REST methods`_.
.. _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
+.. _`REST methods`: http://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services
About Resources
@@ -252,17 +252,17 @@ practice would be to create a URLconf just for the API portion of your site.::
# The ``settings.ROOT_URLCONF`` file
# myproject/urls.py
- from django.conf.urls import patterns, url, include
+ from django.conf.urls import url, include
# Add this!
from posts.api import PostResource
- urlpatterns = patterns('',
+ urlpatterns = [
# The usual fare, then...
# Add this!
url(r'api/posts/', include(PostResource.urls())),
- )
+ ]
Note that unlike some other CBVs (admin specifically), the ``urls`` here is a
**METHOD**, not an attribute/property. Those parens are important!
@@ -272,13 +272,13 @@ Manual URLconfs
You can also manually hook up URLs by specifying something like::
- urlpatterns = patterns('',
+ urlpatterns = [
# ...
# Identical to the above.
url(r'api/posts/$', PostResource.as_list(), name='api_post_list'),
url(r'api/posts/(?P<pk>\d+)/$', PostResource.as_detail(), name='api_post_detail'),
- )
+ ]
Testing the API
@@ -597,7 +597,7 @@ http://127.0.0.1:8000/api/posts/3/).
We can also update it. Restless expects **complete** bodies (don't try to send
partial updates, that's typically reserved for ``PATCH``).::
- $ curl -X POST -H "Content-Type: application/json" -d '{"title": "Another new library released!", "author": "daniel", "body": "I just released a new piece of software!"}' http://127.0.0.1:8000/api/posts/3/
+ $ curl -X PUT -H "Content-Type: application/json" -d '{"title": "Another new library released!", "author": "daniel", "body": "I just released a new piece of software!"}' http://127.0.0.1:8000/api/posts/3/
And we can delete our new data if we decide we don't like it.::
diff --git a/examples/django/posts/urls.py b/examples/django/posts/urls.py
index 7e78962..4d235aa 100644
--- a/examples/django/posts/urls.py
+++ b/examples/django/posts/urls.py
@@ -1,12 +1,12 @@
-from django.conf.urls import patterns, url, include
+from django.conf.urls import url, include
from .api import PostResource
-urlpatterns = patterns('',
+urlpatterns = [
url(r'^posts/', include(PostResource.urls())),
# Alternatively, if you don't like the defaults...
# url(r'^posts/$', PostResource.as_list(), name='api_posts_list'),
# url(r'^posts/(?P<pk>\d+)/$', PostResource.as_detail(), name='api_posts_detail'),
-)
+]
diff --git a/restless/__init__.py b/restless/__init__.py
index de706bf..c1ac75c 100644
--- a/restless/__init__.py
+++ b/restless/__init__.py
@@ -1,6 +1,7 @@
__author__ = 'Daniel Lindsley'
__license__ = 'BSD'
-__version__ = (2, 0, 1)
+__version__ = (2, 0, 3)
+VERSION = '.'.join(map(str, __version__))
from .resources import Resource
diff --git a/restless/constants.py b/restless/constants.py
index a715e54..f322d1e 100644
--- a/restless/constants.py
+++ b/restless/constants.py
@@ -6,8 +6,22 @@ NO_CONTENT = 204
BAD_REQUEST = 400
UNAUTHORIZED = 401
+FORBIDDEN = 403
NOT_FOUND = 404
METHOD_NOT_ALLOWED = 405
+NOT_ACCEPTABLE = 406
+CONFLICT = 409
+GONE = 410
+PRECONDITION_FAILED = 412
+UNSUPPORTED_MEDIA_TYPE = 415
+EXPECTATION_FAILED = 417
+I_AM_A_TEAPOT = 418
+UNPROCESSABLE_ENTITY = 422
+LOCKED = 423
+FAILED_DEPENDENCY = 424
+TOO_MANY_REQUESTS = 429
+UNAVAILABLE_FOR_LEGAL_REASONS = 451
APPLICATION_ERROR = 500
METHOD_NOT_IMPLEMENTED = 501
+UNAVAILABLE = 503
diff --git a/restless/dj.py b/restless/dj.py
index 3ffb778..4503b4c 100644
--- a/restless/dj.py
+++ b/restless/dj.py
@@ -1,11 +1,12 @@
import six
from django.conf import settings
-from django.conf.urls import patterns, url
+from django.conf.urls import url
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponse, Http404
from django.views.decorators.csrf import csrf_exempt
+from .constants import OK, NO_CONTENT
from .exceptions import NotFound
from .resources import Resource
@@ -27,12 +28,15 @@ class DjangoResource(Resource):
return csrf_exempt(super(DjangoResource, self).as_detail(*args, **kwargs))
def is_debug(self):
- # By default, Django-esque.
return settings.DEBUG
- def build_response(self, data, status=200):
- # By default, Django-esque.
- resp = HttpResponse(data, content_type='application/json')
+ 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'
+ resp = HttpResponse(data, content_type=content_type)
resp.status_code = status
return resp
@@ -82,9 +86,9 @@ class DjangoResource(Resource):
``api_blogpost_list``
:type name_prefix: string
- :returns: A ``patterns`` object for ``include(...)``
+ :returns: A list of ``url`` objects for ``include(...)``
"""
- return patterns('',
+ return [
url(r'^$', cls.as_list(), name=cls.build_url_name('list', name_prefix)),
- url(r'^(?P<pk>\d+)/$', cls.as_detail(), name=cls.build_url_name('detail', name_prefix)),
- )
+ url(r'^(?P<pk>[\w-]+)/$', cls.as_detail(), name=cls.build_url_name('detail', name_prefix)),
+ ]
diff --git a/restless/exceptions.py b/restless/exceptions.py
index f1ad719..79ca042 100644
--- a/restless/exceptions.py
+++ b/restless/exceptions.py
@@ -1,5 +1,10 @@
-from .constants import APPLICATION_ERROR, UNAUTHORIZED, NOT_FOUND, BAD_REQUEST
-from .constants import METHOD_NOT_ALLOWED, METHOD_NOT_IMPLEMENTED
+from .constants import (APPLICATION_ERROR, UNAUTHORIZED, NOT_FOUND, BAD_REQUEST,
+ FORBIDDEN, NOT_ACCEPTABLE, GONE, PRECONDITION_FAILED,
+ CONFLICT, UNSUPPORTED_MEDIA_TYPE, EXPECTATION_FAILED,
+ I_AM_A_TEAPOT, TOO_MANY_REQUESTS, UNPROCESSABLE_ENTITY,
+ UNAVAILABLE_FOR_LEGAL_REASONS, FAILED_DEPENDENCY,
+ LOCKED)
+from .constants import METHOD_NOT_ALLOWED, METHOD_NOT_IMPLEMENTED, UNAVAILABLE
class RestlessError(Exception):
@@ -44,6 +49,11 @@ class Unauthorized(HttpError):
msg = "Unauthorized."
+class Forbidden(HttpError):
+ status = FORBIDDEN
+ msg = "Permission denied."
+
+
class NotFound(HttpError):
status = NOT_FOUND
msg = "Resource not found."
@@ -54,6 +64,72 @@ class MethodNotAllowed(HttpError):
msg = "The specified HTTP method is not allowed."
+class NotAcceptable(HttpError):
+ # TODO: make serializers handle it?
+ status = NOT_ACCEPTABLE
+ msg = "Unable to send content specified on the request's Accept header(s)."
+
+
+class Conflict(HttpError):
+ status = CONFLICT
+ msg = "There was a conflict when processing the request."
+
+
+class Gone(HttpError):
+ status = GONE
+ msg = "Resource removed permanently."
+
+
+class PreconditionFailed(HttpError):
+ status = PRECONDITION_FAILED
+ msg = "Unable to satisfy one or more request preconditions."
+
+
+class UnsupportedMediaType(HttpError):
+ status = UNSUPPORTED_MEDIA_TYPE
+ msg = "Type of media provided on request is not supported."
+
+
+class ExpectationFailed(HttpError):
+ status = EXPECTATION_FAILED
+ msg = "Unable to satisfy requirements of Expect header."
+
+
+class IAmATeapot(HttpError):
+ status = I_AM_A_TEAPOT
+ msg = "This is a teapot; do not attempt to brew coffee with it."
+
+
+class UnprocessableEntity(HttpError):
+ status = UNPROCESSABLE_ENTITY
+ msg = "Request cannot be followed due to a semantic error."
+
+
+class Locked(HttpError):
+ status = LOCKED
+ msg = "Resource is locked."
+
+
+class FailedDependency(HttpError):
+ status = FAILED_DEPENDENCY
+ msg = "Request failed due to a previous failed request."
+
+
+class TooManyRequests(HttpError):
+ status = TOO_MANY_REQUESTS
+ msg = "There was a conflict when processing the request."
+
+
+class UnavailableForLegalReasons(HttpError):
+ status = UNAVAILABLE_FOR_LEGAL_REASONS
+ msg = "Resource made unavailable by a legal decision."
+
+
class MethodNotImplemented(HttpError):
status = METHOD_NOT_IMPLEMENTED
msg = "The specified HTTP method is not implemented."
+
+
+class Unavailable(HttpError):
+ status = UNAVAILABLE
+ msg = "There was a conflict when processing the request."
diff --git a/restless/fl.py b/restless/fl.py
index 851e596..ea34c06 100644
--- a/restless/fl.py
+++ b/restless/fl.py
@@ -1,6 +1,7 @@
from flask import make_response
from flask import request
+from .constants import OK, NO_CONTENT
from .resources import Resource
@@ -44,9 +45,14 @@ class FlaskResource(Resource):
from flask import current_app
return current_app.debug
- def build_response(self, data, status=200):
+ 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 make_response(data, status, {
- 'Content-Type': 'application/json'
+ 'Content-Type': content_type,
})
@classmethod
diff --git a/restless/it.py b/restless/it.py
index 277c17b..36e5566 100644
--- a/restless/it.py
+++ b/restless/it.py
@@ -1,6 +1,8 @@
import re
import itty
+
+from restless.constants import OK, NO_CONTENT
from restless.resources import Resource
@@ -16,8 +18,13 @@ class IttyResource(Resource):
def is_debug(self):
return self.debug
- def build_response(self, data, status=200):
- return itty.Response(data, status=status, content_type='application/json')
+ 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):
@@ -30,7 +37,7 @@ class IttyResource(Resource):
:returns: ``None``
"""
list_url = "%s" % itty.add_slash(rule_prefix)
- detail_url = "%s" % itty.add_slash(rule_prefix + "/(?P<pk>\d+)")
+ 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)
diff --git a/restless/preparers.py b/restless/preparers.py
index 5d9fed9..1373fa3 100644
--- a/restless/preparers.py
+++ b/restless/preparers.py
@@ -101,10 +101,10 @@ class FieldsPreparer(Preparer):
part = parts[0]
remaining_lookup = '.'.join(parts[1:])
- if hasattr(data, 'keys') and hasattr(data, '__getitem__'):
+ if callable(getattr(data, 'keys', None)) and hasattr(data, '__getitem__'):
# Dictionary enough for us.
value = data[part]
- else:
+ elif data is not None:
# Assume it's an object.
value = getattr(data, part)
diff --git a/restless/pyr.py b/restless/pyr.py
index 2d6d162..a94faf2 100644
--- a/restless/pyr.py
+++ b/restless/pyr.py
@@ -1,7 +1,9 @@
from pyramid.response import Response
+from .constants import OK, NO_CONTENT
from .resources import Resource
+
class PyramidResource(Resource):
"""
A Pyramid-specific ``Resource`` subclass.
@@ -26,8 +28,13 @@ class PyramidResource(Resource):
return _wrapper
- def build_response(self, data, status=200):
- resp = Response(data, status_code=status, content_type="application/json")
+ 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'
+ resp = Response(data, status_code=status, content_type=content_type)
return resp
@classmethod
@@ -42,7 +49,7 @@ class PyramidResource(Resource):
:param routename_prefix: (Optional) A prefix for the URL's name (for
resolving). The default is ``None``, which will autocreate a prefix
based on the class name. Ex: ``BlogPostResource`` ->
- ``api_blog_post_list``
+ ``api_blogpost_list``
:type routename_prefix: string
:returns: The final name
diff --git a/restless/resources.py b/restless/resources.py
index c260e7f..f6ad6c9 100644
--- a/restless/resources.py
+++ b/restless/resources.py
@@ -4,7 +4,7 @@ import sys
from .constants import OK, CREATED, ACCEPTED, NO_CONTENT
from .data import Data
from .exceptions import MethodNotImplemented, Unauthorized
-from .preparers import Preparer, FieldsPreparer
+from .preparers import Preparer
from .serializers import JSONSerializer
from .utils import format_traceback
@@ -145,9 +145,8 @@ class Resource(object):
"""
Returns the HTTP method for the current request.
- The default implementation is Django-specific, so if you're integrating
- with a new web framework, you'll need to override this method within
- your subclass.
+ If you're integrating with a new web framework, you might need to
+ override this method within your subclass.
:returns: The HTTP method in uppercase
:rtype: string
@@ -161,9 +160,8 @@ class Resource(object):
Useful for deserializing the content the user sent (typically JSON).
- The default implementation is Django-specific, so if you're integrating
- with a new web framework, you'll need to override this method within
- your subclass.
+ If you're integrating with a new web framework, you might need to
+ override this method within your subclass.
:returns: The body of the request
:rtype: string
@@ -175,9 +173,8 @@ class Resource(object):
"""
Given some data, generates an HTTP response.
- The default implementation is Django-specific, so if you're integrating
- with a new web framework, you'll need to override this method within
- your subclass.
+ If you're integrating with a new web framework, you **MUST**
+ override this method within your subclass.
:param data: The body of the response to send
:type data: string
@@ -188,13 +185,7 @@ class Resource(object):
:returns: A response object
"""
- # TODO: Remove the Django.
- # This should be plain old WSGI by default, if possible.
- # By default, Django-esque.
- from django.http import HttpResponse
- resp = HttpResponse(data, content_type='application/json')
- resp.status_code = status
- return resp
+ raise NotImplementedError()
def build_error(self, err):
"""
@@ -208,7 +199,7 @@ class Resource(object):
:returns: A response object
"""
data = {
- 'error': six.text_type(err),
+ 'error': err.args[0],
}
if self.is_debug():
diff --git a/restless/serializers.py b/restless/serializers.py
index 89104ce..b372993 100644
--- a/restless/serializers.py
+++ b/restless/serializers.py
@@ -1,3 +1,4 @@
+from .exceptions import BadRequest
from .utils import json, MoreTypesJSONEncoder
@@ -58,9 +59,12 @@ class JSONSerializer(Serializer):
:returns: The deserialized data
:rtype: ``list`` or ``dict``
"""
- if isinstance(body, bytes):
- return json.loads(body.decode('utf-8'))
- return json.loads(body)
+ try:
+ if isinstance(body, bytes):
+ return json.loads(body.decode('utf-8'))
+ return json.loads(body)
+ except ValueError:
+ raise BadRequest('Request body is not valid JSON')
def serialize(self, data):
"""
diff --git a/restless/tnd.py b/restless/tnd.py
index 00ed3c8..206163a 100644
--- a/restless/tnd.py
+++ b/restless/tnd.py
@@ -1,5 +1,5 @@
from tornado import web, gen
-from .constants import OK
+from .constants import OK, NO_CONTENT
from .resources import Resource
from .exceptions import MethodNotImplemented, Unauthorized
@@ -128,8 +128,14 @@ class TornadoResource(Resource):
def request_body(self):
return self.request.body
- def build_response(self, data, status=200):
- self.ref_rh.set_header("Content-Type", "application/json; charset=UTF-8")
+ 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'
+ self.ref_rh.set_header("Content-Type", "{}; charset=UTF-8"
+ .format(content_type))
self.ref_rh.set_status(status)
self.ref_rh.finish(data)
diff --git a/restless/utils.py b/restless/utils.py
index 00d8929..96acffa 100644
--- a/restless/utils.py
+++ b/restless/utils.py
@@ -1,6 +1,7 @@
import datetime
import decimal
import traceback
+import uuid
try:
import json
@@ -18,12 +19,13 @@ class MoreTypesJSONEncoder(json.JSONEncoder):
* ``datetime.date``
* ``datetime.time``
* ``decimal.Decimal``
+ * ``uuid.UUID``
"""
def default(self, data):
if isinstance(data, (datetime.datetime, datetime.date, datetime.time)):
return data.isoformat()
- elif isinstance(data, decimal.Decimal):
+ elif isinstance(data, decimal.Decimal) or isinstance(data, uuid.UUID):
return str(data)
else:
return super(MoreTypesJSONEncoder, self).default(data)
diff --git a/rtfd-requirements.txt b/rtfd-requirements.txt
new file mode 100644
index 0000000..94a0e83
--- /dev/null
+++ b/rtfd-requirements.txt
@@ -0,0 +1 @@
+Django
diff --git a/setup.py b/setup.py
index 14c3354..44dbb9e 100644
--- a/setup.py
+++ b/setup.py
@@ -2,10 +2,12 @@
# -*- coding: utf-8 -*-
from setuptools import setup
+import restless
+
setup(
name='restless',
- version='2.0.1',
+ version=restless.VERSION,
description='A lightweight REST miniframework for Python.',
author='Daniel Lindsley',
author_email='daniel at toastdriven.com',
@@ -22,6 +24,7 @@ setup(
],
tests_require=[
'mock',
+ 'tox',
],
classifiers=[
'Development Status :: 5 - Production/Stable',
diff --git a/test2_requirements.txt b/test2_requirements.txt
deleted file mode 100644
index bbed9a4..0000000
--- a/test2_requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
--r test3_requirements.txt
-
-itty==0.8.2
diff --git a/test3_requirements.txt b/test3_requirements.txt
deleted file mode 100644
index 677f055..0000000
--- a/test3_requirements.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-six>=1.4.0
-pytest==2.5.2
-pytest-cov==1.6
-Django==1.6.1
-Flask==0.10.1
-pyramid==1.4.5
-tornado==3.2.2
-
diff --git a/tests/test_dj.py b/tests/test_dj.py
index bcdc5be..9aaf1bd 100644
--- a/tests/test_dj.py
+++ b/tests/test_dj.py
@@ -1,13 +1,18 @@
import unittest
-from django.http import Http404
-from django.core.exceptions import ObjectDoesNotExist
+try:
+ from django.http import Http404
... 724 lines suppressed ...
--
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