Skip to content

Commit

Permalink
Merge pull request #32 from hfaran/utilsrefactor
Browse files Browse the repository at this point in the history
Refactor of ``utils`` module
  • Loading branch information
hfaran committed Feb 18, 2014
2 parents b018782 + e45c9f0 commit a918590
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 150 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Tornado-JSON is a small extension of [Tornado](http://www.tornadoweb.org/en/stab

Some of the key features the included modules provide:

* Input and output [JSON Schema](http://json-schema.org/) validation by decorating RequestHandlers with `io_schema`
* Input and output [JSON Schema](http://json-schema.org/) validation by decorating RequestHandlers with `schema.validate`
* Automated *route generation* with `routes.get_routes(package)`
* *Automated Public API documentation* using schemas and provided descriptions
* Standardized output using the [JSend](http://labs.omniti.com/labs/jsend) specification
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ in the documentation.
Some of the key features the included modules provide:

- Input and output `JSON Schema <http://json-schema.org/>`__ validation
by decorating RequestHandlers with ``io_schema``
by decorating RequestHandlers with ``schema.validate``
- Automated *route generation* with ``routes.get_routes(package)``
- *Automated Public API documentation* using schemas and provided
descriptions
Expand Down
19 changes: 11 additions & 8 deletions demos/helloworld/helloworld/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from tornado import gen

from tornado_json.requesthandlers import APIHandler
from tornado_json.utils import io_schema
from tornado_json import schema


class HelloWorldHandler(APIHandler):
Expand All @@ -17,12 +17,12 @@ class HelloWorldHandler(APIHandler):
"doc": "Shouts hello to the world!",
}

# Decorate any HTTP methods with the `io_schema` decorator
# Decorate any HTTP methods with the `schema.validate` decorator
# to validate input to it and output from it as per the
# the schema for the method defined in `apid`
# Simply use `return` rather than `self.write` to write back
# your output.
@io_schema
@schema.validate
def get(self):
return "Hello world!"

Expand All @@ -43,13 +43,13 @@ class AsyncHelloWorld(APIHandler):
def hello(self, callback=None):
callback("Hello (asynchronous) world!")

@io_schema
@schema.validate
@gen.coroutine
def get(self):
# Asynchronously yield a result from a method
res = yield gen.Task(self.hello)

# When using the io_schema decorator asynchronously,
# When using the `schema.validate` decorator asynchronously,
# we can return the output desired by raising
# `tornado.gen.Return(value)` which returns a
# Future that the decorator will yield.
Expand Down Expand Up @@ -97,9 +97,9 @@ class PostIt(APIHandler):
"""
}

@io_schema
@schema.validate
def post(self):
# io_schema will JSON-decode `self.request.body` for us
# `schema.validate` will JSON-decode `self.request.body` for us
# and set self.body as the result, so we can use that here
return {
"message": "{} was posted.".format(self.body["title"])
Expand All @@ -124,7 +124,7 @@ class Greeting(APIHandler):
# arguments; here, you can GET /api/greeting/John/Smith and you will
# get a response back that says, "Greetings, John Smith!"
# You can match the regex equivalent of `\w+`.
@io_schema
@schema.validate
def get(self, fname, lname):
return "Greetings, {} {}!".format(fname, lname)

Expand All @@ -135,6 +135,9 @@ class FreeWilledHandler(APIHandler):
# if you want your handlers to do something more custom,
# they definitely can.
def get(self):
# If you don't know where `self.success` comes from, it is defined
# in the `JSendMixin` mixin in tornado_json.jsend. `APIHandler`
# inherits from this and thus gets the methods.
self.success("I don't need no stinkin' schema validation.")
# If you're feeling really bold, you could even skip JSend
# altogether and do the following EVIL thing:
Expand Down
10 changes: 10 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ Changelog
_
---------


v0.20 - Refactor of ``utils`` module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Functions that did not belong in ``utils`` were moved to more relevant modules. This change changes the interface for Tornado-JSON in quite a big way. The following changes were made (that are not backwards compatible).

* ``api_assert`` and ``APIError`` were moved to ``tornado_json.exceptions``
* ``io_schema`` was renamed ``validate`` and moved to ``tornado_json.schema``


v0.14 - Bugfixes thanks to 100% coverage
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ in the documentation.
Some of the key features the included modules provide:

- Input and output `JSON Schema <http://json-schema.org/>`__ validation
by decorating RequestHandlers with ``io_schema``
by decorating RequestHandlers with ``schema.validate``
- Automated *route generation* with ``routes.get_routes(package)``
- *Automated Public API documentation* using schemas and provided
descriptions
Expand Down
10 changes: 5 additions & 5 deletions docs/requesthandler_guidelines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ for an example. Here is an example for POST:
``doc`` is the **public** accompanying documentation that will be
available on the wiki.

Use the ``io_schema`` decorator on methods which will automatically
Use the ``schema.validate`` decorator on methods which will automatically
validate the request body and output against the schemas in
``apid[method_name]``. Additionally, ``return`` the data from the
request handler, rather than writing it back (the decorator will take
Expand All @@ -38,7 +38,7 @@ care of that).
.. code:: python
class ExampleHandler(APIHandler):
@io_schema
@schema.validate
def post(self):
...
return data
Expand All @@ -48,15 +48,15 @@ Assertions
----------


Use ``utils.api_assert`` to fail when some the client does not meet some
Use ``exceptions.api_assert`` to fail when some the client does not meet some
API pre-condition/requirement, e.g., an invalid or incomplete request is
made. When using an assertion is not suitable,
``raise APIError( ... )``; don't use JSend ``fail`` directly.
``raise APIError( ... )``; don't use ``self.fail`` directly.

.. code:: python
class ExampleHandler(APIHandler):
@io_schema
@schema.validate
def post(self):
...
api_assert(condition, status_code, log_message=log_message)
8 changes: 4 additions & 4 deletions docs/using_tornado_json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ helloworld/api.py

Now comes the fun part where we develop the actual web app. We'll import
``APIHandler`` (this is the handler you should subclass for API routes),
and the ``io_schema`` decorator which will validate input and output
and the ``schema.validate`` decorator which will validate input and output
schema for us.

.. code:: python
from tornado_json.requesthandlers import APIHandler
from tornado_json.utils import io_schema
from tornado_json import schema
class HelloWorldHandler(APIHandler):
"""Hello!"""
Expand Down Expand Up @@ -95,12 +95,12 @@ back. Notice that rather than using ``self.write`` as we usually would,
we simply return the data we want to write back, which will then be
validated against the output schema and be written back according to the
`JSend <http://labs.omniti.com/labs/jsend>`__ specification. The
``io_schema`` decorator handles all of this so be sure to decorate any
``schema.validate`` decorator handles all of this so be sure to decorate any
HTTP methods with it.

.. code:: python
@io_schema
@schema.validate
def get(self):
return "Hello world!"
Expand Down
6 changes: 3 additions & 3 deletions tests/func_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
try:
sys.path.append('.')
from tornado_json import routes
from tornado_json import utils
from tornado_json import schema
from tornado_json import application
from tornado_json import requesthandlers
sys.path.append('demos/helloworld')
Expand Down Expand Up @@ -68,11 +68,11 @@ class ExplodingHandler(requesthandlers.APIHandler):
},
}

@utils.io_schema
@schema.validate
def get(self):
return "I am not the handler you are looking for."

@utils.io_schema
@schema.validate
def post(self):
return "Fission mailed."

Expand Down
31 changes: 16 additions & 15 deletions tests/test_tornado_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
try:
sys.path.append('.')
from tornado_json import routes
from tornado_json import utils
from tornado_json import schema
from tornado_json import exceptions
from tornado_json import jsend
sys.path.append('demos/helloworld')
import helloworld
Expand All @@ -28,7 +29,7 @@ class Request(object):
request = Request()

def fail(message):
raise utils.APIError(message)
raise exceptions.APIError(message)

def success(self, message):
raise SuccessException
Expand Down Expand Up @@ -78,15 +79,15 @@ class TestUtils(TestTornadoJSONBase):
"""Tests the utils module"""

def test_api_assert(self):
"""Test utils.api_assert"""
with pytest.raises(utils.APIError):
utils.api_assert(False, 400)
"""Test exceptions.api_assert"""
with pytest.raises(exceptions.APIError):
exceptions.api_assert(False, 400)

utils.api_assert(True, 400)
exceptions.api_assert(True, 400)

class TerribleHandler(MockRequestHandler):

"""This 'handler' is used in test_io_schema"""
"""This 'handler' is used in test_validate"""

apid = {
"get": {
Expand All @@ -105,17 +106,17 @@ class TerribleHandler(MockRequestHandler):
},
}

@utils.io_schema
@schema.validate
def get(self):
return "I am not the handler you are looking for."

@utils.io_schema
@schema.validate
def post(self):
return "Fission mailed."

class ReasonableHandler(MockRequestHandler):

"""This 'handler' is used in test_io_schema"""
"""This 'handler' is used in test_validate"""

apid = {
"get": {
Expand All @@ -138,23 +139,23 @@ class ReasonableHandler(MockRequestHandler):
},
}

@utils.io_schema
@schema.validate
def get(self, fname, lname):
return "I am the handler you are looking for, {} {}".format(
fname, lname)

@utils.io_schema
@schema.validate
def post(self):
# Test that self.body is available as expected
assert self.body == {"I am a": "JSON object"}
return "Mail received."

# DONE: Test io_schema functionally instead; pytest.raises does
# DONE: Test validate functionally instead; pytest.raises does
# not seem to be catching errors being thrown after change
# to async compatible code.
# The following test left here as antiquity.
# def test_io_schema(self):
# """Tests the utils.io_schema decorator"""
# def test_validate(self):
# """Tests the schema.validate decorator"""
# th = self.TerribleHandler()
# rh = self.ReasonableHandler()

Expand Down
2 changes: 1 addition & 1 deletion tornado_json/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
# Alternatively, just put the version in a text file or something to avoid
# this.

__version__ = '0.14'
__version__ = '0.20'
18 changes: 18 additions & 0 deletions tornado_json/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from tornado.web import HTTPError


class APIError(HTTPError):

"""Equivalent to ``RequestHandler.HTTPError`` except for in name"""


def api_assert(condition, *args, **kwargs):
"""Assertion to fail with if not ``condition``
Asserts that ``condition`` is ``True``, else raises an ``APIError``
with the provided ``args`` and ``kwargs``
:type condition: bool
"""
if not condition:
raise APIError(*args, **kwargs)
2 changes: 1 addition & 1 deletion tornado_json/requesthandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from jsonschema import ValidationError

from tornado_json.jsend import JSendMixin
from tornado_json.utils import APIError
from tornado_json.exceptions import APIError


class BaseHandler(RequestHandler):
Expand Down
2 changes: 1 addition & 1 deletion tornado_json/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def extract_method(wrapped_method):
:rtype: any([types.FunctionType, types.MethodType])
"""
# If method was decorated with io_schema, the original method
# If method was decorated with validate, the original method
# is available as orig_func thanks to our container decorator
return wrapped_method.orig_func if \
hasattr(wrapped_method, "orig_func") else wrapped_method
Expand Down
Loading

0 comments on commit a918590

Please sign in to comment.