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.1.tar.gz (13.1 kB view details)

Uploaded Source

Built Distribution

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

Uploaded Python 3

File details

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

File metadata

  • Download URL: bareasgi-0.2.1.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.1.tar.gz
Algorithm Hash digest
SHA256 77ab2e1b683db073172bc8994493af1be3fc603a42fd16a165e10530bb790d9a
MD5 f2a5ebf3e4164aa05cb6c97e5d1e5ed5
BLAKE2b-256 43d5f98cbadab59495859b6992a04758ab94c4caebb56a191a672203625f0f51

See more details on using hashes here.

File details

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

File metadata

  • Download URL: bareasgi-0.2.1-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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 548a6f11ce60a9e651081e85a1582fc2ce32e3b1135ba56d91f1df5b3fe88c76
MD5 6e793357746bec9fbe6961bbd23b70ac
BLAKE2b-256 7576d78514276e9fce14aa8d6e2ad714e9b95b5473c7847d3aa5a9300abac403

See more details on using hashes here.

Supported by

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