In PythonOnWheels a controller defines the external interface to your application, your: API through routes. The controller holds the programming logic of your app. It manipulates the model and responds with the right format. For web applications this will be often returning a view (an html representation). But a controller can also easily offer other API calls returning json, xml or other formats.
In PythonOnWheels every model can represent itself as json, xml and csv so you can really easy make REST APIs that support those formats. You can also add your own response formats as needed.
When working with handlers that are linked to models, the convention is that you give the handler the same name as the model.
When you want to give your todo model an API you create a REST handler and name it todo. PythonOnWheels will automatically link model and handler without any further configuration.
Very often you link a handler to a model. The handler defines an API to create, alter or delete a model and returns the appropiate respones. But sometimes you need a handler that doesnt need a model at all. For this usecase you can use the shorties.
Shorties are standard handlers. You can find some pre-defined ones in handlers/shorties.py and handlers/hello_world.py. They perfectly define an API but they are pretty raw. Meaning not bound to conventions, linked to models etc. So they are in fact pure tornado handlers with the additional routing features of PythonOnWheels (like Flask routes, Class and Method routes)
The shorty handler above adds the route yourhost:yourport/hello. When this URL is called with a browser or abny other client like curl. PythonOnWheels will receive the request, dispatch it to the HelloHandler and call the Method linked to th HTTP request type. In this case the method will only be called for GET requests. For any other request PythonOnWheels will respond with an appropiate error message.
If you installed PythonOnWheels you can just try it out directly because the HelloHandler is part of any generated app. So just generate an app, start it and call localhost:8080/hello.
If you don't know how to do that just follow the "build the super simple todo app tutorial".
A very common use case is that you created a model, say a todo model and now you want to work with it. The typical actions you probably want to have are create, read, update and delete. This is so common that these action pack is called CRUD. It is also handy to list (returning all / or paging) and show models (return one specific model).
With PythonOnWheels you can generate handlers that automatically offer all those actions and also offers a REST API interface for it. API means that it automatically creates routes that map URLs and HTTP Verbs (GET, PUT...) to the right actions. Actions are just the corresponding methods of you handler.
So if you haven't done it, create a todo model now.
python generate_model.py -n todo -t sql
It is totally ok to create a NoSql model like -t tinydb or -t mongodb. Just remeber to give the handler the right type.
This is of course also done using a generator script. So all we do is executing:
python generate_handler.py -n todo -t sql --rest
Output:
CamelCased handler name: Todo
---------------------------------------
generating handler: Todo
----------------------------------------
... REST Handler
... created: /Users/khz/development/testapp/handlers/todo.py
This generates a fully working REST handler for us which is automatically linked to the to sql model and offers all the CRUD methods. This is how it looks like (or check in your app: handlers/todo.py)
You can see that it adds the class decorator @app.add_rest_routes and thats all. The mapping for all of the REST routes to the correspondin methods is done by PythonOnWheels. These are the default routes added.
Just start the server and send a request. (make sure you have at least upserted one model). If you don't know how to save a model read this.
To test your API just start the server:
python server.py
You will see infos about the routes and DB connections but the route info that intererst us the most is the
ROUTING: added RESTful routes for: Todo as /todo
If you see that it means that all the subroutes are created and you can now already call your API.
We do not have any fancy HTML views so far (in fact it is just one line to generate the according views in PythonOnWheels but we will leave this out on purpose). So let's make a request using the requests library.
import requests
r = requests.get("http://localhost:8080/todo/list")
r.text
'{"status": 200, "message": "todo, index", "data": "{\\"id\\": 1, \\"votes\\": 0, \\"last_updated\\": \\"2019-01-04T22:32:38+00:00\\", \\"created_at\\": \\"2019-01-04T22:32:38+00:00\\", \\"title\\": \\"a first todo\\", \\"text\\": \\"\\"}", "next": null, "prev": null}'
r = requests.get("http://localhost:8080/todo/show/1")
r.text
'{"status": 200, "message": "todo show", "data": "{\\"id\\": 1, \\"votes\\": 0, \\"last_updated\\": \\"2019-01-04T22:32:38+00:00\\", \\"created_at\\": \\"2019-01-04T22:32:38+00:00\\", \\"title\\": \\"a first todo\\", \\"text\\": \\"\\"}", "next": null, "prev": null}'
Cool. You now have a working REST API.
You can set routes by simply adding a class decorator to the handler class or decorate methods directly.
@app.add_route("/test/([0-9]+)*", dispatch={"get" : "test"})
@route("/", dispatch=["get"])
The only difference is that you don't have to specify the method to call in a method decorator ;) PythonOnWheels will then call the method of your handler if the route and the HTTP method matches.
The first parameter of route decorators specifies the route. With PythonOnWheels you can use Flask routes or RegEx routes. Just what you prefer or your use case needs.
Example for Flask routes:
@app.make_routes()
class HelloHandler(BaseHandler):
@route(r'/hello/<int:identifier>', dispatch=["get"])
def hello(self, identifier=None):
self.write("Hello world! " + str(identifier))
This will call the hello method on a HTTP GET call on host:port/hello followed by an optional identifier.
For Regex routes use:
@app.make_routes()
class HelloHandler(BaseHandler):
@route(r'/test/([0-9]+)*', dispatch=["get"])
def hello(self, identifier=None):
self.write("Hello world! " + str(identifier))
To add a direct route: matching the regular expression : /test/([0-9+]) and then calling the given method of your handler class. The regex group ([0-9+]) will be handed as the first parameter to test(self, index)