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'}, '/{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.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.0.1.tar.gz (12.6 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.0.1-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: bareasgi-0.0.1.tar.gz
  • Upload date:
  • Size: 12.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.5.0.1 requests/2.21.0 setuptools/40.6.3 requests-toolbelt/0.8.0 tqdm/4.29.1 CPython/3.7.2

File hashes

Hashes for bareasgi-0.0.1.tar.gz
Algorithm Hash digest
SHA256 5882ee5b08598317f251a4e38e33988328db5a69ad16745ae665fb78f54a9b08
MD5 ca18f9726ce8751a3bdeb813f2c9d753
BLAKE2b-256 f2e52257002f9c55a098cd9b42bb1507a9fa014992be6aa6436bf02f3d1ce211

See more details on using hashes here.

File details

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

File metadata

  • Download URL: bareasgi-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 18.8 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/40.6.3 requests-toolbelt/0.8.0 tqdm/4.29.1 CPython/3.7.2

File hashes

Hashes for bareasgi-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6f971ea35435de813d46028d391b6b96e2a2c737df7bae13ba31e1627f637e80
MD5 999a69c581de0ceaea3475d53c42ef54
BLAKE2b-256 4ed81cf81f9ccfff18dae0286580541903f6c25590d98fdeda28ce70ed478139

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