Swill is an ASGI framework for creating RPC backends that use Websockets for communication and Msgpack for serialization.
This project is still in alpha phase. The underlying protocol and implementations may still change
# Server
from server import swill
from typing import Tuple
import swill
app = swill.app.Swill(__name__)
@app.handle()
def add(request: swill.Request[Tuple[int, int]]) -> int:
return request.data[0] + request.data[1]
// Generic Client
import {Client} from '@swillorg/swill';
const client = new Client();
await client.connect('localhost');
const result = await client.call('add', [1, 2]);
console.log(result); // 3
There are two different lifecycles in Swill - The Connection lifecycle and the Request lifecycle.
Connections are created when a WebSocket is accepted and contain some details about the initial HTTP request from the client (eg: Cookies, headers, etc.). A connection has a unique id and persists for the life of the WebSocket.
A request lifecycle starts when the client sends a WebSocket message to the server and ends
when the server finalises the request by sending either a response (for single requests), an
END_OF_STREAM
message for streaming requests or an ERROR
message. Requests are always
attached to one connection.
msgspec is used to perform serialization and validation
on messages being sent and received. Additionally, messages can be enhanced with ValidatedStructs
to perform constraint validation on data. Constraints support annotated-types
as well as method validators using the @validator
decorator
Typing annotations are used in conjunction with msgspec
to determine how handlers process
messages. Handlers can handle single or streaming requests and responses.
Handlers can accept streaming requests where the client sends multiple messages for
processing. Streaming requests provide an async iterator to handle the messages. When a
client is finished streaming it sends a special END_OF_STREAM
message.
Handlers can stream responses by yielding multiple messages. When a handler returns it
sends the client a special END_OF_STREAM
message and (optionally) trailing metadata.
Clients can also cancel streaming responses by sending a special CLOSE
message.
Errors are their own message type that contain some pre-defined error codes while also allowing arbitrary data.
Both clients and server can send metadata for a specific request. Metadata are arbitrary key/value pairs that can be used to send data alongside a message.
Client metadata is sent with the initial request. For streaming requests the metadata can be sent on its own too.
The server can send leading metadata with the initial response or immediately before sending any data regardless if it is a streaming or single handler. It cannot send it more than once and cannot send metadata with any subsequent messages.
Trailing metadata can only be sent with the END_OF_MESSAGE
message during streaming
responses or with the response of a single response.
At each stage of the connection and request lifecycles, handlers can be invoked which
receive data such as connection information or request information. These can be used
to modify the connection or request as needed. Currently supported are (in order):
before_connection
, before_accept
, before_request
, before_request_metadata
,
before_request_data
, before_request_message
, before_leading_metadata
,
before_response_message
, before_trailing_metadata
, after_request
, after_connection
Query the application with the introspection API
>>> response = await client.introspect() # Equivalent to calling 'swill.introspect'
>>> async for introspected_handler in response.data:
...pprint(introspected_handler)
IntrospectedRpc(
name="add",
request=RpcArguments(
streams=False
type=List[int, int]
),
response=RpcArguments(
streams=False
type=List[int, int]
),
)
IntrospectedRpc(
name="lines",
request_type=int,
request_streams=False,
response_type=str,
response_streams=True,
)
Generate documentation from your handlers, types and docstrings automatically.
- Python Client
- Typescript Client
- Auto-Generated Client Libraries for Typescript
- Streaming requests
- Streaming responses
- Leading and trailing metadata
- Exception Handling
- Tracebacks
- Connection Management
- Session Management
- Redis Backend
- PubSub Plugin
- Modules (ie: Flask Blueprints)
- Static File Serving
- Introspection
- Auto-Generated API Doc/Playground
- [-] Unit Tests
- [-] Constraint validators on Deserialize