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
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