ASGI compliant web microframework
Project description
Tonberry
An ASGI compliant web microframework that takes a class based approach to routing. Influenced by CherryPy but made compatible with asyncio. A companion ASGI server named Qactuar was spawned from this project which is currently in the works.
Installing
$ pip install tonberry
Getting Started
2 Minute App Example
from tonberry import quick_start, expose
class Root:
@expose.get
async def index(self):
return "Hello, world!"
quick_start(Root)
# Go to http://localhost:8080
Features Example
import asyncio
from dataclasses import dataclass
import uvicorn
from tonberry import create_app, expose, File, websocket, jinja
from tonberry.content_types import TextPlain, TextHTML, ApplicationJson
@dataclass
class Request:
arg1: int
arg2: str
@dataclass
class Response:
param1: str
param2: float
class SubPage:
@expose.post
async def index(self, request: Request) -> Response:
"""
This class `SubPage` is assigned a route below in the `Root` class as a
class attribute.
With type hints indicating a dataclass object, the body of the request
will automatically be deserialized into that object, even if it
contains nested dataclasses and types will be checked thanks to the
library dacite. Returning a dataclass will result in it being serialized
into a JSON string and the content-type header will be set to
application/json.
URL: http://127.0.0.1:8888/subpage
POST body: {"arg1": 3, "arg2": "something"}
Response body: {"param1": "SOMETHING", "param2": 4.5}
"""
return Response(request.arg2.upper(), request.arg1 * 1.5)
@expose.get
async def hello(self, name: str) -> TextPlain:
"""
Arguments to methods can come from the leftover parts of the URI after
the route has found a match, querystrings, form-url-encoded data or json
strings.
URL: http://127.0.0.1:8888/subpage/hello/{name}
"""
return f"Hi {name}"
class Root:
subpage = SubPage()
@expose.get
async def index(self) -> TextPlain:
"""
The index method behaves similarly to an index.html file in most web
servers.
URL: http://127.0.0.1:8888
"""
return "Hello, world!"
@expose.get('somepage')
async def some_page(self) -> TextHTML:
"""
Returning a file like object result in the file contents being read and
put into the response body.
The expose decorator methods can take an optional argument for the name
you would like to use for the route if you don't want it to be the name
of the method.
To indicate what the content-type header you want to set then use a type
hints for the return value from tonberry.content_types. This feature may
or may not stay as part of the project. It will not overwrite any
content type set manually inside the method.
URL: http://127.0.0.1:8888/somepage
"""
return File('some_page.html')
@expose.post
async def do_a_thing(self, data1: int, data2: str) -> ApplicationJson:
"""
Even without a dataclass the individule top level keys in JSON object
will be passed as arguments. It is possible to return strings, UTF-8
encoded bytes, integers, file like objects, dicts or lists (as long as
they are JSON serializable) and dataclasses.
URL: http://127.0.0.1:8888/do_a_thing
POST body: {"data1": 2, "data2": "things to do"}
"""
complete, success, result = await do_that_thing(data1, data2)
return {"completed": complete, "outcome": success, "body": result}
@expose.get
async def use_jinja(self) -> TextPlain:
"""
In the config a template path can be configured to point to where all
your Jinja2 template files are located. To use a template just call
the `jinja` function with a file name and a context dict with the
desired replacement values.
URL: http://127.0.0.1:8888/use_jinja
Response Body: I say hello!
"""
return jinja(file_name="jinja.txt", context={"my_var": "hello"})
@expose.websocket
async def ws(self):
"""
Basic example of using a websocket. Sending and receiving are done
through the websocket object.
URL: ws://127.0.0.1:8888/ws
"""
data = await websocket.receive_text()
await websocket.send_text(f"echo {data}")
count = 0
while websocket.client_is_connected:
count += 1
await websocket.send_text(f"Hello {count}")
await asyncio.sleep(3)
if __name__ == "__main__":
app = create_app(root=Root)
app.add_static_route(path_root="./static_files", route="static")
# Using uvicorn here but any ASGI server will work just as well
uvicorn.run(app, host="127.0.0.1", port=8888)
Contributing
Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests.
Versioning
SemVer is used for versioning. For the versions available, see the tags on this repository.
Authors
- Anthony Post - Ayehavgunne
License
This project is licensed under the MIT License - see the LICENSE.txt file for details
Acknowledgments
- CherryPy
- Quart
- Starlette
- uvicorn
TODO
- JWT integration
- Authentication
- URL generation
- Tests
- Documentation
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Tonberry-0.2.1.tar.gz
(19.1 kB
view details)
File details
Details for the file Tonberry-0.2.1.tar.gz
.
File metadata
- Download URL: Tonberry-0.2.1.tar.gz
- Upload date:
- Size: 19.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 91a575fcb93af46367cbb9b1a3713142b4425156245ea8417b8e37106ef08c35 |
|
MD5 | 4d564d2ccfd320902a03b59bb1828fa3 |
|
BLAKE2b-256 | 885ca878080fbfb740359705a0145c0bcad313b0f1720d536fc9e6de6fb99ce6 |