Skip to content

zweifisch/klar

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

klar

a micro web framework built for fun

  • argument annotation
  • jsonschema intergration
from klar import App

app = App()

@app.get('/hello/<name>')
def hello(name: str, times: int = 1):
	return "hello " * times + name

run it using wsgi-supervisor

wsgi-supervisor app:app
$ curl 'localhost:3000/hello/klar?times=2'
hello hello klar

custom types using jsonschema

product = {
	"type": "object"
	"properties": {
		"name": {
			"type": "string"
		},
		"price": {
			"type": "number"
		},
	},
	"additionalProperties": False,
}

@app.post('/product')
def create(body: product, db):
	db.products.insert(body)
	return {"ok": True}

schemas can and should be imported from json or yaml files

|--app.py
|--schemas.json

product in schema.json

{
	product: {...}
}
from schemas import product

@app.post('/product')
def create(body: product):
	pass

dependency injection

provide a custom dependency using decorator

@app.provide('db')
def get_db_connection():
	conn = SomeDB(url="localhost:3349")
	return conn

import redis
app.provide('cache', (redis.Redis, {'host': 'localhost'}))

using db and cache in request handler

@get('/article/<article_id>')
def get_article(article_id:int, db, cache):
    pass

predefined components:

  • req
  • session
  • cookie
  • router

rest

from resource import product, catalog

app = App()

app.resources(product, catalog, prefix="/v1")

if __name__ == '__main__':
	app.run()

in product

from schema import product

# curl -X POST $host/v1/product -d @body
def create(body: product, db):
	return db.products.insert(body)

# curl $host/v1/product/$id
def show(product_id: str):
	item = db.products.find_one({_id: product_id})
	return item if item else 404

# curl $host/v1/product?shift=10&limit=10
def query(shift: int, limit: int, db):
	return db.products.find().skip(shift).limit(shift)

# curl -X PATCH $host/v1/product/$id -d @body
def modify(body: product, product_id: str):
	return db.products.update({_id: product_id}, {'$set': body})

# curl -X PUT $host/v1/product/$id -d @body
def replace(body: product, product_id: str):
	return db.products.update({_id: product_id}, body)

# curl -X DELETE $host/v1/product/$id
def destroy(product_id: str):
	return db.products.delete({_id: product_id})

custom method

from klar import method

@method('patch')
def like(product_id):
	return db.products.update({_id: product_id}, {'$inc': {'likes': 1}})

# curl -X PATCH $host/v1/product/$id/like

events

listening for an event

@on(404)
def not_found(req, res):
	print('%s not found' % req.path)
    res.body = "%s not found on this server" % req.path

custom events

@on('user-login')
def onlogin(userid, db):
	print('user: %s logged in' % userid)
	db.users.update({_id:userid}, {'$inc': {'logincount': 1}})

emit an event

def login(emitter):
	emitter.emit('user-login', userid=id)

post processing

def jsonp(req, res):
    callback = req.query.get('callback')
    if callback:
        res.body = "%s(%s)" % (callback, json.dumps(body))
        res.headers["Content-Type"] = "application/javascript"

@app.get('/resource') -> jsonp:
	return {"key": "value"}

more than one processors:

@app.get('/resource') -> (jsonp, etag):
	return {"key": "value"}

template rendering

|--app.py
|--templates
   |--home.html
import templates.home

@app.get('/') -> templates.home:
	return {"key": "value"}

templates.home accecpts an optional dict as argument it's basically equivalent to this:

@app.get('/'):
	return templates.home({"key": "value"})

mustache

depends on pystache, pip install pystache

use .mustache as extension

|--templates
   |--home.mustache
import templates.home

session

session depends on cache, but klar does't has it builtin

to use redis as session backend:

import redis

@app.provide('cache')
def cache():
	return redis.Redis(host='localhost', port=6379, db=0)

or

app.privide('cache', (redis.Redis, {'host': 'localhost'}))

use session

@app.post('/login')
def login(body, session):
	# check body.username and body.password
	if founduser:
		session.set('userid', userid)

@app.post('/login')
def logout(session):
	session.destroy()

@app.get('/admin')
def admin(session):
	if session.get('userid'):
		pass

cookies

cookies.get(key, default)
cookies.set(key, value)
cookies.delete(key)

cookies.set(key, value, httponly=True)
cookies.set_for_30_days(key, value)

serving static files

should only be used in development enviroment

app.static('/public/')
app.static('/public/', 'path/to/public/dir')

config

config file path will be read from enviroment variable $CONFIG

if it's empty config.py will be loaded

config.py

mongo = {
    "host": "127.0.0.1"
    "port": 27017
}
from pymongo import MongoClient

@app.provide('db')
def db(config):
    return MongoClient(**config.mongo)

reversed routing

@app.get('user/<id>')
def user(id:str):
    pass

get a link to previous handler

def another_handler(router):
    href = router.path_for('user', id=3221)

custom json encoder

from bson.objectid import ObjectId

@app.json_encode(ObjectId)
def encode_objectid(obj):
    return str(obj)

by default Iterable is converted to list

About

a micro web framework built for fun

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages