what is it?

it's a small library on top of werkzeug to create REST APIs (and websites) using JSON

why?

because I make websites and I don't like big frameworks, and I don't like the webpy way of doing things

no, really, why??

because I'm tired of coding the same stuff all over again, so I decided to put them together for great justice

why the name?

because as everyone knows, internet is a series of tubes

where is the code?

the code is hosted at github, you can submit bugs or send me messages there

features?

in no particular order

  • simple sintax using decorators to map an url to a method
  • match urls with regular expression
  • automatic marshalling and unmarshalling of objects
  • based on werkzeug (which is awesome)
  • dispatch based on content type
  • based on wsgi
  • google appengine ready
  • choose your template engine and ORM
  • automatic generation of code for requests, classes and API tests

examples?

hello world

import tubes

handler = tubes.Handler()

@handler.get("^.*$", produces=tubes.HTML)
def hello_world(handler):
    return "hello world!"

tubes.run(handler)

run it

$ python hello_world.py
* Running on http://0.0.0.0:8000/
    

test it

point your browser to that address, you can add anything after the url, that handler will match any url

more!

hello name

import tubes

     handler = tubes.Handler()


     @handler.get("^/?$", produces=tubes.HTML)
     def hello_world(handler):
         return "hello world!"

     # match one or more characters of any type, optionally an ending slash
     @handler.get("^/(.+?)/?$", produces=tubes.HTML)
     def hello_name(handler, name):
         return "hello " + name

     tubes.run(handler)
     

run it

 # same as before, just change the file name in the following examples
 $ python hello_name.py
 * Running on http://0.0.0.0:8000/
     

test it

point your browser to that address, you will se a hello world, now go to http://0.0.0.0:8000/yourname you should see "hello yourname". you can change yourname for any string or path

responses

import tubes

    handler = tubes.Handler()

    @handler.get("^/?$", produces=tubes.HTML)
    def hello_world(handler):
        return "hello world!"

    @handler.get("^/fail/?$", produces=tubes.HTML)
    def fail(handler):
        return tubes.Response("introductory example fail", 500)

    @handler.get("^/not-here/?$", produces=tubes.HTML)
    def not_here(handler):
        return tubes.Response("nothing to see here, please move along", 404)

    @handler.get("^/redirect/?$", produces=tubes.HTML)
    def redirect(handler):
        return tubes.redirect("/", code=301)

    # you can make werkzeug reload the server everytime a change is made
    # also you can enable a debugger useful when developing to inspect exceptions
    # on the browser
    tubes.run(handler, use_reloader=True, use_debugger=True)
    

test it

point your browser to that the following addresses hello world, fail (500 error), not found (404 error), redirect (to hello world)

advanced

import re
import tubes
# this module generates code for us (you can ignore it if you want)
import intertubes

# decorator to allow python<->json<->javascript
@tubes.JsonClass()
class User(object):
    """a class that represents an user"""
    USER_FORMAT = '[a-z][a-zA-Z0-9]*'
    ALL_USER_FORMAT = '^' + USER_FORMAT + '$'

    def __init__(self, user=None, mail=None, firstname=None,
            lastname=None, website=None):
        """constructor"""
        self.user = user
        self.mail = mail
        self.firstname = firstname
        self.lastname = lastname
        self.website = website

    @classmethod
    def is_valid_username(cls, username):
        '''return True if username matches User.USER_FORMAT
        '''
        return re.match(User.ALL_USER_FORMAT, username) is not None

handler = tubes.Handler()
# if the path starts with /files serve
# as static files from the files/ directory
handler.register_static_path('/files', 'files/')

# a dict to simulate a database
USERS = {}

# when a post is made to this url the body is extracted and procesed by running
# the User.from_json method, that way we receive a User object
@handler.post('^/user/?$', accepts=tubes.JSON, transform_body=User.from_json)
def new_user(handler, user):
    # add the new user
    if not User.is_valid_username(user.user):
        return tubes.Response('Invalid username', 500)

    USERS[user.user] = user

# update user
@handler.put('^/user/?$', accepts=tubes.JSON, transform_body=User.from_json)
def update_user(handler, user):
    if user.user in USERS:
        USERS[user.user] = user
    else:
        return tubes.Response("user not found", 404)

# match a valid username identifier
# if no produces keyword is given assume JSON
@handler.get('^/user/(' + User.USER_FORMAT + ')/?')
def get_user(handler, username):
    if username in USERS:
        return USERS[username]

    return tubes.Response("user not found", 404)

# return all users
@handler.get('^/users/?')
def get_users(handler):
    # isn't this easy?
    return User.to_json_list(USERS.values())

@handler.delete('^/user/(' + User.USER_FORMAT + ')/?', transform_body=User.from_json)
def remove_user(handler, username):
    if username in USERS:
        del USERS[username]
    else:
        return tubes.Response("user not found", 404)

# return the generated requests javascript code on that url
# (on production you should save it to a file)
@handler.get('^/requests.js/?$', produces=tubes.JS)
def requests(request):
    '''return the requests.js file to interact with this API'''
    return tubes.Response(REQUESTS)

# return the generated model javascript code on that url
# (on production you should save it to a file)
@handler.get('^/model.js/?$', produces=tubes.JS)
def model(request):
    '''return the model.js file to interact with this API'''
    return MODEL

# show the API test page here
@handler.get('^/test.html/?$', produces=tubes.HTML)
def test(request):
    '''return a dummy html to play with the API'''
    return TEST_PAGE

REQUESTS = intertubes.generate_requests(handler)
MODEL = intertubes.generate_model([User])
TEST_PAGE = intertubes.generate_html_example(handler,
        ('/files/json2.js', '/model.js'))

if __name__ == '__main__':
    tubes.run(handler, use_reloader=True)

test it

point your browser to http://0.0.0.0:8000/test.html to test the API from the generated test page.

you can also see the code generated for the requests and model

try to add a new user with something like

model.User('marianoguerra', 'user@account')
     
on the newUser form. That content will be evaluated and sent to the server, then try with getUser/removeUser (using the username you used above) and getUsers.

keep adding methods and reloading the test page to test them