Webkage is a fast and lightweight Python web framework.
Relies on Pytest and Werkzeug testing clients for Unit testing.
$ pip install werkzeug pytest
$ cd tests
$ pytest
=====================
- Introduction
- Routing
- Static Files
- Views
- Middlewares
- Url Parameters
- Url Query
- Form
- Form Files
- Json Request & Response
- Session and Cookies
- Redirects
- Templates
- HTTP Request header
- CSRF Tokens
Webkage is a lightweight Python web framework designed for speed and flexibility, with an emphasis on simplicity and control. It is built using the Python standard library and has minimal external dependencies, relying only on Jinja2 for templating.
To create a web application using Webkage, developers import an instance of the WSGI application and register routes. The development server can be started with the command App.serve().
For production deployment, the WSGI App entry point can be accessed via the wsgi attribute. Example using Gunicorn:
app.py
from webkage.application import App
from http_response import load, response
App = App()
def home(ctx):
resp = load("home.html")
return response(ctx, "200 OK", resp)
App.add_path("/", home)
#Run server
App.serve()
Always start the route pattern with "/"
This would spin up a development WSGI server that listens on 127.0.0.1:8000
This is a development server and it's not suitable for production. For a production based WSGI web server, the entry point for the WSGI App can be accessed via the wsgi
attribute.
Example using Gunicorn.
app.py
from webkage.application import App
from http_response import load, response
App = App()
def home(ctx):
resp = load("home.html")
return response(ctx, "200 OK", resp)
App.add_path("/", home)
wsgi = App.wsgi
To serve the app through Gunicorn:
$ gunicorn app:wsgi
In Webkage, routes are registered using the add_path
method of the application instance. Routes are associated with view functions.
app.py
...
def home(ctx):
...
def login(ctx):
...
## Register routes
App.add_path("/", home)
App.add_path("login/", login)
...
Url parameters like id and slug can also be used in routes.
app.py
...
App.add_path("product/:id/", productIdDetail)
App.add_path("product/:slug/", productSlugDetail)
...
The values of these parameters can be accessed in the view function.
Static files in Webkage are served by registering the directory from which the files will be served using the set_static
method.
app.py
from webkage.application import App
...
# Serve static
App.set_static("/static/", "static")
...
The first argument is the prefix of all static files to be served. The last argument is the directory from which the static files reside.
When registering routes, views must be assigned to the routes. A view function is a simple function with one parameter, returning an HTTP response using either webkage.http_response.response or webkage.http_response.json_response.
app.py
from webkage.http_response import json_response, response, load
...
def home(ctx):
resp = load("home.html")
return response(ctx, "200 OK", resp)
def json_home(ctx):
resp = {"Name":"Uzumaki", "Title":"Hokage"}
return json_response(ctx, "200 OK", resp)
...
The three arguments of both response functions are context object, status code and http reponse object.
List of http response codes can be accessed via http.HTTPStatus module.
response function returns a text/html
response while json_response returns a ``application/json` response to the client.
Middlewares can be achieved through decorators or high level functions.
app.py
...
def auth_middleware(func):
def new_view(ctx):
#perform actions here
return func(ctx)
return new_view
@auth_middleware
def dashboard(ctx):
...
App.add_path("dashboard/", dashboard)
...
Or
app.py
...
def auth_middleware(func):
def new_view(ctx):
#perform actions here
return func(ctx)
return new_view
def dashboard(ctx):
...
App.add_path("dashboard/", auth_middleware(dashboard))
...
Url parameters can be accessed via the Context object's params
attribute.
app.py
...
def product(ctx):
product_id = ctx.params["id"]
...
App.add_path("product/:id", product)
...
Url queries can be accessed via the Context object's query
attribute.
Same process used in accessing Url parameters.
app.py
...
def product(ctx):
product_id = ctx.query["id"]
...
App.add_path("product/", product)
...
Form values can be accessed via the context object's form
attribute.
app.py
...
def add_product(ctx):
if ctx.request["method"] == "POST":
product_name = ctx.form["name"]
...
App.add_path("product/add", add_product)
...
Files uploaded cannot be accessed via the form
attribute.
Files uploaded via HTTP forms can be accessed the Context object's formFile
attribute.
app.py
...
def new_ca(ctx):
csv_file = ctx.formFile["csv_file"]
#Access file's name via the filename attribute
with open(csv_file.filename, "wb") as f:
f.write(f.read())
...
App.add_path("ca/add", new_cases)
...
Json objects can be accessed the Context object's json
attribute; the value is a valid Python dictionary object.
Json response should be done with webkage.http_response.json_response, else the returned response will be in "text/html".
Webkage has no provision for File or Database based sions. It's solely a client-based one. All Cookies are HttpOnly by default.
Setting Cookies' value
app.py
...
def login(ctx):
ctx.session["name"] = "Rock Lee"
...
Setting Cookies to secured only
app.py
...
def login(ctx):
ctx.session["name"] = "Sakura"
ctx.secure(True)
...
...
Setting Cookies To HttpOnly or not
app.py
...
def login(ctx):
ctx.session["user"] = 45
ctx.httponly(False)
...
...
Setting Cookies' expiry
app.py
...
def login(ctx):
ctx.session["user"] = 56
ctx.will_expire("21 Oct 2015 07:28:00 GMT")
...
...
Flushing/Deleting Cookies' session
app.py
...
def logout(ctx):
ctx.flush()
...
...
Both Permanent and Temporary redirects can be acheived by specifying the right status code. Redirect can be acheived via wekage.http_response.redirect
app.py
from webkage.http_response import redirect
...
def secret(ctx):
return redirect(ctx, "301", "/home")
...
...
Webkage's Templates are powered by Jinja2's templating engine. Templates are expected to reside in ./templates
directory relatively to the module or file in which the views reside.
An example directory will look like:
program/
app.py
templates/
home.html
In home.html
, we might have:
home.html
<html>
<head>
<title>Home</title>
</head>
<body>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</body>
</html>
In app.py
, we might have:
app.py
from webkage.http_response, import load, response
...
def items_list(ctx):
data = {"items":["Shuriken", "Wood", "Boot", "Scroll"]}
resp = load("home.html", data)
return response(ctx, "200", resp)
...
The Context object's request
objects contain the following keys:
ctx.request[key]
content-length Length of HTTP request's body.
content-type HTTP requests' content type.
ip-address Client's IP address.
user-agent Client's user agent.
method Request method. Either of POST, GET, PUT, or DELETE.
protocol Mostly HTTP.
scheme Http, Https, etc.
Webkage does not implement CSRF tokens by default, users who are not building microservices can implement a Middleware to handle this.