A lightweight ASGI framework
Project description
# bareasgi
A lightweight ASGI framework
## Status
Work in progress.
## Overview
This is a _bare_ ASGI web server framework. The goal is to provide
a minimal implementation, with other facilities (serving static files, CORS, sessions, etc.)
being implemented by optional packages. The goal is to keep the implementation
clear and lightweight.
## Functionality
The framework supports:
* Http,
* WebSockets,
* A basic method and path based router,
* Middleware.
## Examples
These examples use [uvicorn](https://www.uvicorn.org/) as the ASGI server.
### Simple Client
Here is a simple example which returns some text.
```python
import uvicorn
from bareasgi import Application, text_writer
async def http_request_callback(scope, info, matches, content):
return 200, [(b'content-type', b'text/plain')], text_writer('This is not a test')
app = Application()
app.http_router.add({'GET'}, '/{rest:path}', http_request_callback)
uvicorn.run(app, port=9009)
```
### Rest Server
Here is a simple rest server.
```python
import uvicorn
import json
from bareasgi import Application, text_reader, text_writer
async def get_info(scope, info, matches, content):
text = json.dumps(info)
return 200, [(b'content-type', b'application/json')], text_writer(text)
async def set_info(scope, info, matches, content):
text = await text_reader(content)
data = json.loads(text)
info.update(data)
return 204, None, None
app = Application(info={'name': 'Michael Caine'})
app.http_router.add({'GET'}, '/info', get_info)
app.http_router.add({'POST'}, '/info', set_info)
uvicorn.run(app, port=9009)
```
### WebSockets
A WebSocket example can be found in the examples folder. Here is the handler.
```python
async def test_callback(scope, info, matches, web_socket):
await web_socket.accept()
try:
while True:
text = await web_socket.receive()
if text is None:
break
await web_socket.send('You said: ' + text)
except Exception as error:
print(error)
await web_socket.close()
```
### Middleware
Here is a simple middleware example.
```python
import uvicorn
from bareasgi import Application, text_writer
async def first_middleware(scope, info, matches, content, handler):
info['message'] = 'This is first the middleware. '
response = await handler(scope, info, matches, content)
return response
async def second_middleware(scope, info, matches, content, handler):
info['message'] += 'This is the second middleware.'
response = await handler(scope, info, matches, content)
return response
async def http_request_callback(scope, info, matches, content):
return 200, [(b'content-type', b'text/plain')], text_writer(info['message'])
app = Application(middlewares=[first_middleware, second_middleware])
app.http_router.add({'GET'}, '/test', http_request_callback)
uvicorn.run(app, port=9009)
```
## API
### Application
The application class has the following constructor:
```python
Application(
middlewares: Optional[List[HttpMiddlewareCallback]],
http_router: Optional[HttpRouter],
web_socket_router: Optional[WebSocketRouter],
startup_handlers: Optional[List[StartupHandler]],
shutdown_handlers: Optional[List[ShutdownHandler]],
not_found_response: Optional[HttpResponse],
info: Optional[MutableMapping[str, Any]])
```
All arguments are optional.
The `info` argument provides a place for application specific data.
The application provides some properties that ccan be used for configuration:
```python
Application.info -> MutableMapping[str, Any]
Application.middlewares -> List[]
Application.http_router -> HttpRouter
Application.ws_router-> WebSocketRouter
Application.startup_handlers -> List[StartupHandler]
Application.shutdown_handlers -> List[ShutdownHandler]
```
### Routers
The routers are split into two: HTTP and WebSockets.
#### HttpRouter
The http router has the following structure:
```python
class HttpRouter:
@property
def not_found_response(self):
...
@not_found_response.setter
def not_found_response(self, value: HttpResponse):
...
def add(self, methods: AbstractSet[str], path: str, callback: HttpRequestCallback) -> None:
...
```
### WebSocketRouter
The WebSocket router has the following structure:
```python
class WebSocketRouter(metaclass=ABCMeta):
def add(self, path: str, callback: WebSocketRequestCallback) -> None:
...
```
### Paths
Here are some eexample paths:
```python
literal_path = '/foo/bar'
capture_trailing_paths = '/foo/{path}'
variables_path = '/foo/{name}/{id:int}/{created:datetime:%Y-%m-%d}'
```
Captured path segments are passed in to the callbacks as a dicctionary of route matches.
## Callacks
The framework uses async callbacks to handle requests.
### HttpRequestCallback
The HTTP request callback has the following structure:
```python
async def http_request_callback(scope: Scope, info: Info, matches: RouteMatches, content: Content) -> HttpResponse:
return 200, [(b'content-type', b'text/plain')], text_writer('This is not a test')
```
The response is a tuple of the staus code, a list of headers, and an async byyes generator for the response body.
The `scope` is a dictionary which holds the request information, e.g. server, scheme, method, etc.
The `content` is an sync iterator of bytes. The are some utility functions for extracting strings.
### HttpMiddlewareCallback
The middleware callback adds an http request callback as the last argument.
```python
async def first_middleware(scope: Scope, info: Info, matches: RouteMatches, content: Content, handler: HttpRequestCallback ) -> HttpResponse:
return await handler(scope, info, matches, content)
```
### WebSocketRequestCallback
The WebSocket request callback has the following form.
```python
async def test_callback(scope: Scope, info: Info, matches: RouteMatches, web_socket: WebSocket) -> None:
...
```
The WebSocket class it self has the following structure.
```python
class WebSocket:
async def accept(self, subprotocol: Optional[str] = None) -> None:
...
async def receive(self) -> Optional[Union[bytes, str]]:
...
async def send(self, content: Union[bytes, str]) -> None:
...
async def close(self, code: int = 1000) -> None:
...
```
The first call must be to acccept the socket.
## Utilities
There are a number of utility functions for reading content and writing the body.
```python
async bytes_reader(content: Content) -> bytes
async text_reader(content: Content, encoding: str = 'utf-8') -> str
async bytes_writer(buf: bytes) -> AsyncGenerator[bytes, None]
async text_writer(text: str, encoding: str = 'utf-8') -> AsyncGenerator[bytes, None]
```
A lightweight ASGI framework
## Status
Work in progress.
## Overview
This is a _bare_ ASGI web server framework. The goal is to provide
a minimal implementation, with other facilities (serving static files, CORS, sessions, etc.)
being implemented by optional packages. The goal is to keep the implementation
clear and lightweight.
## Functionality
The framework supports:
* Http,
* WebSockets,
* A basic method and path based router,
* Middleware.
## Examples
These examples use [uvicorn](https://www.uvicorn.org/) as the ASGI server.
### Simple Client
Here is a simple example which returns some text.
```python
import uvicorn
from bareasgi import Application, text_writer
async def http_request_callback(scope, info, matches, content):
return 200, [(b'content-type', b'text/plain')], text_writer('This is not a test')
app = Application()
app.http_router.add({'GET'}, '/{rest:path}', http_request_callback)
uvicorn.run(app, port=9009)
```
### Rest Server
Here is a simple rest server.
```python
import uvicorn
import json
from bareasgi import Application, text_reader, text_writer
async def get_info(scope, info, matches, content):
text = json.dumps(info)
return 200, [(b'content-type', b'application/json')], text_writer(text)
async def set_info(scope, info, matches, content):
text = await text_reader(content)
data = json.loads(text)
info.update(data)
return 204, None, None
app = Application(info={'name': 'Michael Caine'})
app.http_router.add({'GET'}, '/info', get_info)
app.http_router.add({'POST'}, '/info', set_info)
uvicorn.run(app, port=9009)
```
### WebSockets
A WebSocket example can be found in the examples folder. Here is the handler.
```python
async def test_callback(scope, info, matches, web_socket):
await web_socket.accept()
try:
while True:
text = await web_socket.receive()
if text is None:
break
await web_socket.send('You said: ' + text)
except Exception as error:
print(error)
await web_socket.close()
```
### Middleware
Here is a simple middleware example.
```python
import uvicorn
from bareasgi import Application, text_writer
async def first_middleware(scope, info, matches, content, handler):
info['message'] = 'This is first the middleware. '
response = await handler(scope, info, matches, content)
return response
async def second_middleware(scope, info, matches, content, handler):
info['message'] += 'This is the second middleware.'
response = await handler(scope, info, matches, content)
return response
async def http_request_callback(scope, info, matches, content):
return 200, [(b'content-type', b'text/plain')], text_writer(info['message'])
app = Application(middlewares=[first_middleware, second_middleware])
app.http_router.add({'GET'}, '/test', http_request_callback)
uvicorn.run(app, port=9009)
```
## API
### Application
The application class has the following constructor:
```python
Application(
middlewares: Optional[List[HttpMiddlewareCallback]],
http_router: Optional[HttpRouter],
web_socket_router: Optional[WebSocketRouter],
startup_handlers: Optional[List[StartupHandler]],
shutdown_handlers: Optional[List[ShutdownHandler]],
not_found_response: Optional[HttpResponse],
info: Optional[MutableMapping[str, Any]])
```
All arguments are optional.
The `info` argument provides a place for application specific data.
The application provides some properties that ccan be used for configuration:
```python
Application.info -> MutableMapping[str, Any]
Application.middlewares -> List[]
Application.http_router -> HttpRouter
Application.ws_router-> WebSocketRouter
Application.startup_handlers -> List[StartupHandler]
Application.shutdown_handlers -> List[ShutdownHandler]
```
### Routers
The routers are split into two: HTTP and WebSockets.
#### HttpRouter
The http router has the following structure:
```python
class HttpRouter:
@property
def not_found_response(self):
...
@not_found_response.setter
def not_found_response(self, value: HttpResponse):
...
def add(self, methods: AbstractSet[str], path: str, callback: HttpRequestCallback) -> None:
...
```
### WebSocketRouter
The WebSocket router has the following structure:
```python
class WebSocketRouter(metaclass=ABCMeta):
def add(self, path: str, callback: WebSocketRequestCallback) -> None:
...
```
### Paths
Here are some eexample paths:
```python
literal_path = '/foo/bar'
capture_trailing_paths = '/foo/{path}'
variables_path = '/foo/{name}/{id:int}/{created:datetime:%Y-%m-%d}'
```
Captured path segments are passed in to the callbacks as a dicctionary of route matches.
## Callacks
The framework uses async callbacks to handle requests.
### HttpRequestCallback
The HTTP request callback has the following structure:
```python
async def http_request_callback(scope: Scope, info: Info, matches: RouteMatches, content: Content) -> HttpResponse:
return 200, [(b'content-type', b'text/plain')], text_writer('This is not a test')
```
The response is a tuple of the staus code, a list of headers, and an async byyes generator for the response body.
The `scope` is a dictionary which holds the request information, e.g. server, scheme, method, etc.
The `content` is an sync iterator of bytes. The are some utility functions for extracting strings.
### HttpMiddlewareCallback
The middleware callback adds an http request callback as the last argument.
```python
async def first_middleware(scope: Scope, info: Info, matches: RouteMatches, content: Content, handler: HttpRequestCallback ) -> HttpResponse:
return await handler(scope, info, matches, content)
```
### WebSocketRequestCallback
The WebSocket request callback has the following form.
```python
async def test_callback(scope: Scope, info: Info, matches: RouteMatches, web_socket: WebSocket) -> None:
...
```
The WebSocket class it self has the following structure.
```python
class WebSocket:
async def accept(self, subprotocol: Optional[str] = None) -> None:
...
async def receive(self) -> Optional[Union[bytes, str]]:
...
async def send(self, content: Union[bytes, str]) -> None:
...
async def close(self, code: int = 1000) -> None:
...
```
The first call must be to acccept the socket.
## Utilities
There are a number of utility functions for reading content and writing the body.
```python
async bytes_reader(content: Content) -> bytes
async text_reader(content: Content, encoding: str = 'utf-8') -> str
async bytes_writer(buf: bytes) -> AsyncGenerator[bytes, None]
async text_writer(text: str, encoding: str = 'utf-8') -> AsyncGenerator[bytes, 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
bareasgi-0.2.3.tar.gz
(13.1 kB
view details)
Built Distribution
bareasgi-0.2.3-py3-none-any.whl
(19.5 kB
view details)
File details
Details for the file bareasgi-0.2.3.tar.gz
.
File metadata
- Download URL: bareasgi-0.2.3.tar.gz
- Upload date:
- Size: 13.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.12.1 pkginfo/1.5.0.1 requests/2.21.0 setuptools/39.1.0 requests-toolbelt/0.8.0 tqdm/4.29.1 CPython/3.7.1
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a59800450a269c346fa7810782a9ea675f108d7ef9d606eb8fe92478fa35e4ac |
|
MD5 | 76a53106c2093ec8e9fae55f2602cd0d |
|
BLAKE2b-256 | d7308b5dd5107f0c2495c9483afeb278174c4aeec98c249cef262ac3a85af386 |
File details
Details for the file bareasgi-0.2.3-py3-none-any.whl
.
File metadata
- Download URL: bareasgi-0.2.3-py3-none-any.whl
- Upload date:
- Size: 19.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.12.1 pkginfo/1.5.0.1 requests/2.21.0 setuptools/39.1.0 requests-toolbelt/0.8.0 tqdm/4.29.1 CPython/3.7.1
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 15abab8052c26f88533ea459914f0f19dec730891daee44126009de07420b0f6 |
|
MD5 | 53c08817e300aa9d9a16621f5b80e24b |
|
BLAKE2b-256 | ef107a57e90e73dc98c7c43e52c61c9993fd60d76d67e8c4487f6a8360b10fd3 |