A very micro http framework.
Project description
A very micro HTTP framework.
Features
Very simple, less-code & fast
Using object dispatcher instead of regex route dispatcher.
Url-Encoded, Multipart and JSON form parsing.
No request and or response objects is available, everything is combined in nanohttp.context.
A very flexible configuration system: pymlconf
Dispatching arguments using the obj.__annonations__
Method(verb) dispatcher.
Roadmap
The road map is to keep it simple with 100% code coverage. no built-in template engine and or ORM will be included.
Install
PyPI
$ pip install nanohttp
From Source
$ cd path/to/nanohttp
$ pip install -e .
Quick Start
demo.py
from nanohttp import Controller, RestController, context, html, json, HttpFound
class TipsControllers(RestController):
@json
def get(self, tip_id: int = None):
if tip_id is None:
return [dict(id=i, title="Tip %s" % i) for i in range(1, 4)]
else:
return dict(
id=tip_id,
title="Tip %s" % tip_id
)
@json
def post(self, tip_id: int = None):
tip_title = context.form.get('title')
print(tip_id, tip_title)
# Updating the tips title
# TipStore.get(tip_id).update(tip_title)
raise HttpFound('/tips/')
class Root(Controller):
tips = TipsControllers()
@html
def index(self):
yield """
<html><head><title>nanohttp Demo</title></head><body>
<form method="POST" action="/tips/2">
<input type="text" name="title" />
<input type="submit" value="Update" />
</form>
</body></html>
"""
$ nanohttp demo
Or
from nanohttp import quickstart
quickstart(Root())
WSGI
Do you need a WSGI application?
wsgi.py
from nanohttp import configure
configure(config='<yaml config string>', files=['path/to/config.file', '...'], dirs=['path/to/config/directory', '...'])
app = Root().load_app()
# Pass the ``app`` to any ``WSGI`` server you want.
Serve it by gunicorn:
gunicorn --reload wsgi:app
Config File
Create a demo.yml file. The file below is same as the default configuration.
debug: true
domain:
cookie:
http_only: false
secure: false
You may use nanohttp.settings anywhere to access the config values.
from nanohttp import Controller, html, settings
class Root(Controller):
@html
def index(self):
yield '<html><head><title>nanohttp demo</title></head><body>'
yield '<h2>debug flag is: %s</h2>' % settings.debug
yield '</body></html>'
Passing the config file(s) using command line:
$ nanohttp -c demo.yml [-c another.yml] demo
Passing the config file(s) Using python:
from nanohttp import quickstart
quickstart(Root(), config='<YAML config string>')
Command Line Interface
$ nanohttp -h
usage: nanohttp [-h] [-c CONFIG_FILE] [-d CONFIG_DIRECTORY] [-b {HOST:}PORT]
[-C DIRECTORY] [-V]
[{MODULE{.py}}{:CLASS}]
positional arguments:
{MODULE{.py}}{:CLASS}
The python module and controller class to launch.
default is python built-in's : `demo_app`, And the
default value for `:CLASS` is `:Root` if omitted.
optional arguments:
-h, --help show this help message and exit
-c CONFIG_FILE, --config-file CONFIG_FILE
This option may be passed multiple times.
-d CONFIG_DIRECTORY, --config-directory CONFIG_DIRECTORY
This option may be passed multiple times.
-b {HOST:}PORT, --bind {HOST:}PORT
Bind Address. default: 8080
-C DIRECTORY, --directory DIRECTORY
Change to this path before starting the server default
is: `.`
-V, --version Show the version.
Trailing slashes
If the Controller.__remove_trailing_slash__ is True, then all trailing slashes are ignored.
def test_trailing_slash(self):
self.assert_get('/users/10/jobs/', expected_response='User: 10\nAttr: jobs\n')
Decorators
Available decorators are: action, html, text, json, xml, binary
Those decorators are useful to encapsulate response preparation such as setting Content-Type HTTP header.
Take a look at the code of the action decorator, all other decorators are derived from this:
def action(*verbs, encoding='utf-8', content_type=None, inner_decorator=None):
def _decorator(func):
if inner_decorator is not None:
func = inner_decorator(func)
func.__http_methods__ = verbs if verbs else 'any'
func.__response_encoding__ = encoding
if content_type:
func.__content_type__ = content_type
return func
if verbs and callable(verbs[0]):
f = verbs[0]
verbs = tuple()
return _decorator(f)
else:
return _decorator
Other decorators are defined using functools.partial:
html = functools.partial(action, content_type='text/html')
text = functools.partial(action, content_type='text/plain')
json = functools.partial(action, content_type='application/json', inner_decorator=jsonify)
xml = functools.partial(action, content_type='application/xml')
binary = functools.partial(action, content_type='application/octet-stream', encoding=None)
Of-course, you can set the response content type using:
context.response_content_type = 'application/pdf'
Of-course, you can define your very own decorator to make your code DRY:
import functools
from nanohttp import action, RestController
pdf = functools.partial(action, content_type='application/pdf')
class MyController(RestController)
@pdf
def get(index):
.......
Serving Static file(s)
The nanohttp.Static class is responsible to serve static files:
from nanohttp import Controller, Static
class Root(Controller):
static = Static('path/to/static/directory', default_document='index.html')
Then you can access static files on /static/filename.ext
A simple way to run server and only serve static files is:
cd path/to/static/directory
nanohttp :Static
Accessing request payload
The context.form is a dictionary representing the request payload, supported request formats are query-string, multipart/form-data, application/x-www-form-urlencoded and json.
from nanohttp import context, RestController
class TipsControllers(RestController):
@json
def post(self, tip_id: int = None):
tip_title = context.form.get('title')
Dispatcher
The requested path will be split-ed by / and python’s getattr will be used on the Root controller recursively to find specific callable to handle request.
from nanohttp import RestController
class Nested(RestController):
pass
class Root()
children = Nested()
Then you can access methods on nested controller using: http://host:port/children
On the RestController dispatcher tries to dispatch request using HTTP method(verb) at first.
Context
The context object is a proxy to an instance of nanohttp.Context which is unique per request.
Hooks
A few hooks are available in Controller class: app_load, begin_request, begin_response, end_response, request_error.
For example this how I detect JWT token and refresh it if possible:
class JwtController(Controller):
token_key = 'HTTP_AUTHORIZATION'
refresh_token_cookie_key = 'refresh-token'
def begin_request(self):
if self.token_key in context.environ:
encoded_token = context.environ[self.token_key]
try:
context.identity = JwtPrincipal.decode(encoded_token)
except itsdangerous.SignatureExpired as ex:
refresh_token_encoded = context.cookies.get(self.refresh_token_cookie_key)
if refresh_token_encoded:
# Extracting session_id
session_id = ex.payload.get('sessionId')
if session_id:
context.identity = new_token = self.refresh_jwt_token(refresh_token_encoded, session_id)
if new_token:
context.response_headers.add_header('X-New-JWT-Token', new_token.encode().decode())
except itsdangerous.BadData:
pass
if not hasattr(context, 'identity'):
context.identity = None
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file nanohttp-0.2.0.tar.gz
.
File metadata
- Download URL: nanohttp-0.2.0.tar.gz
- Upload date:
- Size: 21.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 |
dd4570c67eb1eda8ba9334d6b69d0448b8166c75fd53e1965397d0c532cc637b
|
|
MD5 |
6efd0b73523655ad86d7ade0baf77ada
|
|
BLAKE2b-256 |
fcba4de976f2c0daaee4113ed785886fe39ba4745fe35a9abdd0fdbcfac6c65a
|