Skip to main content

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]
```


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

bareasgi-0.2.4.tar.gz (13.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bareasgi-0.2.4-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file bareasgi-0.2.4.tar.gz.

File metadata

  • Download URL: bareasgi-0.2.4.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

Hashes for bareasgi-0.2.4.tar.gz
Algorithm Hash digest
SHA256 92e2c92b35804f21b7e66a20e261951468b253b97aa23fe9a093f4d2d4f9052d
MD5 d44158a419ad86ecfed9611c943fe6df
BLAKE2b-256 4fa29224791032d401eb6e613d85b5feed578d5bfe28ccd38097d49e39ce63a3

See more details on using hashes here.

File details

Details for the file bareasgi-0.2.4-py3-none-any.whl.

File metadata

  • Download URL: bareasgi-0.2.4-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

Hashes for bareasgi-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 0be198305c7bb7b0d272076e0b900da8cbf6a14ed0cd19162ac2a81e10db4528
MD5 7b50f6a9008ebfbcd627596e5fd80821
BLAKE2b-256 a4fca5b51a47487f95da7317848c700d84a66b8aaf8365311abe0fe0dd4ec3c7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page