Code Monkey home page Code Monkey logo

tornado-json's Introduction

Tornado-JSON

Join the chat at https://gitter.im/hfaran/Tornado-JSON

Build Status Coverage Status Documentation Status

Latest Version Supported Python versions Development Status Download format License

Overview

Tornado-JSON is a small extension of Tornado with the intent of providing the tools necessary to get a JSON API up and running quickly.

Some of the key features the included modules provide:

  • Input and output JSON Schema validation by decorating RequestHandlers with @schema.validate
  • Automated route generation with routes.get_routes(package)
  • Automated GFM-formatted API documentation using schemas and provided descriptions
  • Standardized JSON output using the JSend specification

Usage

Check out the Hello World demo for a quick example and the accompanying walkthrough in the documentation. And then explore Tornado-JSON on readthedocs for the rest!

import tornado.ioloop
from tornado_json.routes import get_routes
from tornado_json.application import Application

import mywebapp


 # Automatically generate routes for your webapp
routes = get_routes(mywebapp)
# Create and start application
application = Application(routes=routes, settings={})
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()

Example Projects That Use Tornado-JSON

Installation

  • For the possibly stable
pip install Tornado-JSON
  • For the latest and greatest
git clone https://github.com/hfaran/Tornado-JSON.git
cd Tornado-JSON
python setup.py develop

Contributing

If there is something you would like to see improved, you would be awesome for opening an issue about it, and I'll promise my best to take a look.

Pull requests are absolutely welcome as well!

License

This project is licensed under the MIT License.

Running Tests

sudo pip2 install tox
sudo pip3 install tox
tox  # Will run test matrix

tornado-json's People

Contributors

digitaldavenyc avatar gitter-badger avatar hfaran avatar ktalik avatar mauler avatar movermeyer avatar mpecarina avatar rutsky avatar skaphander avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tornado-json's Issues

Error object inconsistency

Hi!
For invalid JSON inputs/outputs resulting JSON reply is { "status": "failure", "data": "error message blah" }, as well as it is for api_assert calls. Nonetheless, for unhandled exceptions producing 500 internal server error it looks like { "status": "failure", "message": "Internal server error" }, which makes it a bit diffucult to handle on client side. Could it be a bit more consistent pwease?

Create a changelog

  • Probably best if this existed as an .rst in the documentation
  • Investigate common ways of doing this

Better API documentation formatting

  • Add breaks (<br>) between routes in API documentation for better separation
  • Give the documentation info a title; it looks weird as a small bit of text on the bottom
  • Maybe don't use as giant headers as are being used right now; could probably reduce each by one-size, and for the smallest size just bold the item

Possible bug in api_doc_gen

My first post for this project, so I will start by saying thank you for creating this extension to Tornado!

So when I add a schema to a POST request, receiving the error below. Seems there is an error trying to split on the schema to generate the docs. I would think it's better to show a warning error for something like this if there is an issue parsing a schema somewhere, doc generation should be able to fail gracefully IMO.

Traceback (most recent call last):
  File "main.py", line 21, in <module>
    main()
  File "main.py", line 14, in main
    application = Application(routes=routes, settings={})
  File "/source/virtualenvs/grooveweb/lib/python3.4/site-packages/tornado_json/application.py", line 22, in __init__
    api_doc_gen(routes)
  File "/source/virtualenvs/grooveweb/lib/python3.4/site-packages/tornado_json/api_doc_gen.py", line 249, in api_doc_gen
    documentation = get_api_docs(routes)
  File "/source/virtualenvs/grooveweb/lib/python3.4/site-packages/tornado_json/api_doc_gen.py", line 236, in get_api_docs
    documentation.append(_get_route_doc(url, rh))
  File "/source/virtualenvs/grooveweb/lib/python3.4/site-packages/tornado_json/api_doc_gen.py", line 209, in _get_route_doc
    rh_doc=_add_indent(_get_rh_doc(rh), 4)
  File "/source/virtualenvs/grooveweb/lib/python3.4/site-packages/tornado_json/api_doc_gen.py", line 188, in _get_rh_doc
    for method_name, method in _get_rh_methods(rh)])
  File "/source/virtualenvs/grooveweb/lib/python3.4/site-packages/tornado_json/api_doc_gen.py", line 188, in <listcomp>
    for method_name, method in _get_rh_methods(rh)])
  File "/source/virtualenvs/grooveweb/lib/python3.4/site-packages/tornado_json/api_doc_gen.py", line 179, in _get_method_doc
    notes=_get_notes(method),
  File "/source/virtualenvs/grooveweb/lib/python3.4/site-packages/tornado_json/api_doc_gen.py", line 162, in _get_notes
    _add_indent(inspect.getdoc(method), 4)
  File "/source/virtualenvs/grooveweb/lib/python3.4/site-packages/tornado_json/api_doc_gen.py", line 101, in _add_indent
    lines = string.split("\n")
AttributeError: 'NoneType' object has no attribute 'split'

Here is a copy of the schema I used in the class in case it's helpful

@schema.validate(
        input_schema={
            "type": "object",
            "properties": {
                "location": {"type": "array"},
                "image": {"type": "string"}
            }
        },
        input_example={
            "location": [40.1, 71.1],
            "image": "http://imgr.com/blajsjdjs.jpg"
        },
        output_schema={
            "type": "object",
            "properties": {
                "message": {"type": "string"}
            }
        },
        output_example={
            "message": "Very Important Post-It Note was posted."
        },
    )

Stacked decorators with @schema.validate and @gen.coroutine

First, thanks for developing this extension. It is extremely useful. Now for the issue.
When I use the @schema.validate decorator as specified, it works perfectly. Example:

class ProfilesAPIHandler(APIHandler):
    __url_names__ = ["profiles"]

class ProfileListHandler(ProfilesAPIHandler):
    @schema.validate(...)
    def get(self):
        ...

class ProfileIdHandler(ProfilesAPIHandler):
    @schema.validate(...)
    def get (self, account_id):
        ...

The above works great!

Now, I want to use @gen.coroutine to do some async calls and speed up requests. When I add in the decorator, I get doubled routes. For example:

class ProfileListHandler(ProfilesAPIHandler):
    @schema.validate(...)
    @gen.coroutine
    def get (self):
        ...

class ProfileIdHandler(ProfilesAPIHandler):
    @schema.validate(...)
    @gen.coroutine
    def get (self, account_id):
        ...

As a result of the above, the routes get mangled. The above results in these routes:

"/api/v1/accounts/profiles/?",
"<class 'handlers.api.v1.accounts.ProfileListHandler'>"

"/api/v1/accounts/profiles/?",
"<class 'handlers.api.v1.accounts.ProfileIdHandler'>"

"/api/v1/accounts/profiles/(?P<account_id>[a-zA-Z0-9_]+)/?$",
"<class 'handlers.api.v1.accounts.ProfileIdHandler'>"

For some reason, the ProfileIdHandler class gets returned as a route to both /profiles/ and /profiles/{}. This doesn't happen if @gen.coroutine is removed. Unfortunately, if I put @gen.coroutine above the @schema.validate, the coroutine does not work at all. Also, please note that I have a PUT method in ProfileIdHandler (though it isn't shown in the example), and it is also getting doubled into both /profiles and /profiles/{}. I'd love help resolving this issue as it is a blocker for me right now.

Proposal for a new Routing system.

I have been trying out the automatic routing system that you currently use in project, and I have found it to be not as convenient as I think it should be.
While it certainly is suited for some cases (i.e. when you store all your views inside one file, say, api.py), I found out that it doesn't work well with the project layout I find most convenient (and I have good reasons for that).
Currently, it walks a package for modules and creates routes respectively, so
handlers.api.ImagesHandler
becomes
/api/images
But when working with larger projects, I find this to be not as convenient as it might seem. The way I use my project, is that I create a handlers package and create modules for each resource I use respectively, so in my case with your current auto-routing, the module will be
handlers.images.ImagesHandler and handlers.images.ImageHandler
Which would be mapped to
/images/images and /images/image.
What I propose is a system that will A - have a defined base path for routes that can be defined when calling get_routes (i.e. get_routes(package, base='api'))
And names will be generated using the following scheme
handlers.images.ImagesHandler -> /api/images
handlers.images.ImageHandler -> /api/images/(.* )
With any additional loose handlers that don't contain module name within them will become actions for collection, like
handlers.images.PopularHandler -> /api/images/popular
With exceptions and custom routes that can be defined inside Handler classes.

This system is more suited for bigger projects where having a separate module for each resource can be pretty convenient and makes it easier to navigate the project.

I understand that you might be focused on other things, or don't really feel like implementing such system, so I've decided to take it up on my own and fill a pull-request once I'm done.
I would also try to implement changes I've proposed in my previous issue regarding metadata in jsend.

Customizable routing for argspec

From @kanoc in #45:

"""
A more flexible routing system would be really nice, as the current implementation is severely limited. In a test project I ended up setting up the routes manually.

Besides the module names issue (which could be solved by Misaka42's proposal), it would be nice to have more options when generating routes with parameters that get passed to the HTTP methods. For example, imagine we have a resource (ID 123) that has a list of items and we want to have a Handler that gets one item (ID 456) and we need both the resource and item IDs. With current implementation the URL looks like /resource/123/456. It would be nice to have an URL /resource/123/item/456 instead.
"""

coveralls reports incorrect coverage

Coverage is being reported at 1%.

Perhaps the following failure on Travis-CI is related?

=========================== 8 passed in 0.29 seconds ===========================
___________________________________ summary ____________________________________
  py27: commands succeeded
  congratulations :)
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/usr/lib/python2.7/atexit.py", line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File "/usr/lib/python2.7/multiprocessing/util.py", line 284, in _exit_function
    info('process shutting down')
TypeError: 'NoneType' object is not callable
Error in sys.exitfunc:
Traceback (most recent call last):
  File "/usr/lib/python2.7/atexit.py", line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File "/usr/lib/python2.7/multiprocessing/util.py", line 284, in _exit_function
    info('process shutting down')
TypeError: 'NoneType' object is not callable
The command "coverage run --source=tornado_json setup.py test" exited with 0.
after_success
$ coveralls
Submitting coverage to coveralls.io...
Coverage submitted!
Job #22.1
https://coveralls.io/jobs/896003
Done. Your build exited with 0.

shouldn't error pages be content-type json?

hi guys

shouldn't e.g. a 404 result in a JSON response, for consistency? using Postman and GETing a unknown path at present yields what looks like tornado's standard error response, which is content-type HTML.

Can't use @schema.validate and return error from `get()` method

Consider following example:

class ObjectsHandler(APIHandler):
    __url_names__ = ["objects"]

    @schema.validate(output_schema={
        "type": "object",
    })
    def get(self, id):
        object = some_find_object(id)

        if object is None:
            self.error("Object not found")
        else:
            return object

I want to return object type if object is found and output error Jsend status if it's not.
According to validate() implementation it's currently impossible.

Improve tests

  • Write tests for api_doc_gen, application etc. to improve coverage
  • Write larger tests for existing coverage

Consider additional parameters in main response object.

Right now, if I a successful request is served, {'status': 'success', 'data': 'YOURDATA'} will be in an output. It would be nice if one could provide additional information (i.e. total number of objects and page offset for pagination) inside an additional parameter, like {'status': 'success', 'data': 'YOURDATA', "_meta": {"offset" 10, "totalCount": 24}}.
I know that this is kinda against JSSend specification, but I think that this will be a very nice feature.

Use default values for missing properties.

It would be nice if the package could add default values for missing properties to the body if mentioned in the schema.
Untested Example:

@schema.validate(
    input_schema={
        "type": "object",
        "properties": {
            "foo": {
                "type": "integer",
                "default": 42
            }
        },
        "required": []
    }
)
def post(self):
    # This raises KeyError if foo is omitted but it would be nice if it returns 42.
    self.body['foo']  

Of cause I can do something like self.body.get('foo', 42) but that's not that awesome for large projects.

By the way: Thanks for this nice package.

Using dynamic schemas

Is there an easy way to create e. g. an input schema dynamicly? At the moment the schema is loaded at import time. I would like to set an enum property an fill it with data from my database which are, as one know, not available at import time and might change later.
Example:

@schema.validate(
    input_schema={
        "type": "object",
        "properties": {
            "user": {
                "enum": [
                    ... # Put a list of all registered users here. How to do this?
                ]
            },
        },
        "required": ["user"]
    }
)
def post(self):
    ...

By the way: Thanks for this nice package again. :-)

New interface proposal for schema definition

Rather than using the awkward apid class variable...

from tornado_json import schema

@schema.validate
def post(self):
    """POST the required parameters to rule the world

    * `secret`: The secret to ruling the world 
    """
    __schemas__ = schema.Schema(
        input=input_schema,
        output=output_schema
    )
    __schemas__["input"]["example"] = ...
    __schemas__["output"]["example"] = ...

    # Do some cool stuff here
    # ...
    return world

That is:

  • Schemas will be defined inside the relevant method
  • The documentation will be provided in the docstring

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.