SocketIO framework driven by the AsyncAPI specification. Built on top of Flask-SocketIO. Inspired by Connexion.
Project description
Asynction
SocketIO python framework driven by the AsyncAPI specification. Built on top of Flask-SocketIO. Inspired by Connexion.
The purpose of Asynction is to empower a specification first approach when developing SocketIO APIs in Python. It guarantees that your API will work in accordance with its documentation.
Disclaimer: Asynction is still at an early stage and should not be used in production codebases.
Features
- Payload validation (for both incoming and outgoing events), based on the message schemata within the API specification.
- HTTP request validation, upon connection, based on the channel binding schemata within the API specification.
- Callback validation, upon the ACK of a message, based on the message
x-ack
schemata within the API specification. - Automatic registration of all event and error handlers defined within the API specification.
- Mock server support
- AsyncAPI playground (coming soon)
- Authentication à la Connexion (coming soon)
A complete example can be found here (includes examples of both normal and mock server implementations).
Prerequisites
- Python 3.7 (or higher)
Install
$ pip install asynction
With mock server support:
$ pip install asynction[mock]
Usage (basic example)
Example event and error handler callables located at ./my_api/handlers.py
:
# /user namespace
def user_sign_up(data):
logger.info("Signing up user...")
emit("metrics", "signup", namespace="/admin", broadcast=True, callback=cb)
def user_log_in(data):
logger.info("Logging in user...")
emit("metrics", "login", namespace="/admin", broadcast=True, callback=cb)
return True # Ack
def user_error(e):
logger.error("Error: %s", e)
# /admin namespace
def authenticated_connect():
token = request.args["token"]
def admin_error(e):
logger.error("Admin error: %s", e)
Example specification located at ./docs/asyncapi.yaml
:
asyncapi: 2.1.0
info:
title: User Account Service
version: 1.0.0
description: This service is in charge of processing user accounts
servers:
production:
url: my-company.com/api/socket.io # Customizes the `path` kwarg that is fed into the `SocketIO` constructor
protocol: wss
channels:
/user: # A channel is essentially a SocketIO namespace
publish:
message:
oneOf: # The oneOf Messages relationship expresses the supported events that a client may emit under the `/user` namespace
- $ref: "#/components/messages/UserSignUp"
- $ref: "#/components/messages/UserLogIn"
x-handlers: # Default namespace handlers (such as connect, disconnect and error)
error: my_api.handlers.user_error # Equivelant of: `@socketio.on_error("/user")`
/admin:
subscribe:
message:
oneOf:
- "#/components/messages/Metrics"
x-handlers:
connect: my_api.handlers.authenticated_connect # Equivelant of: `@socketio.on("connect", namespace="/admin")`
error: my_api.handlers.admin_error
bindings: # Bindings are used to validate the HTTP request upon connection
$ref: "#/components/channelBindings/AuthenticatedWsBindings"
components:
messages:
UserSignUp:
name: sign up # The SocketIO event name. Use `message` or `json` for unnamed events.
payload: # Asynction uses payload JSON Schemata for message validation
type: object
x-handler: my_api.handlers.user_sign_up # The handler that is to be registered. Equivelant of: `@socketio.on("sign up", namespace="/user")`
UserLogIn:
name: log in
payload:
type: object
x-handler: my_api.handlers.user_log_in
x-ack: # Specifies the structure of the ACK data that the client should expect
args:
type: boolean
Metrics:
name: metrics
payload:
type: string
enum: [signup, login]
x-ack: # Specifies the structure of the ACK data that the server expects
args:
type: string
channelBindings:
AuthenticatedWsBindings:
ws:
query:
type: object
properties:
token:
type: string
required: [token]
Bootstrap the AsynctionSocketIO server:
from asynction import AsynctionSocketIO
from flask import Flask
flask_app = Flask(__name__)
asio = AsynctionSocketIO.from_spec(
spec_path="./docs/asyncapi.yaml",
app=flask_app,
message_queue="redis://localhost:6379",
# or any other kwarg that the flask_socketio.SocketIO constructor accepts
)
if __name__ == "__main__":
asio.run(app=flask_app)
The AsynctionSocketIO
class extends the SocketIO
class of the Flask-SocketIO library.
The above asio
server object has all the event and error handlers registered, and is ready to run.
Validation of the message payloads, the channel bindings and the ack callbacks is also enabled by default.
Without Asynction, one would need to add additional boilerplate to register the handlers (as shown here) and implement the respective validators.
Mock server
Asynction can also create a fake "mock" based off an AsyncAPI document. This enables the consumers of a SocketIO API to interract with the API before it's even built.
from asynction import MockAsynctionSocketIO
from flask import Flask
flask_app = Flask(__name__)
mock_asio = MockAsynctionSocketIO.from_spec(
spec_path="./docs/asyncapi.yaml",
app=flask_app,
)
if __name__ == "__main__":
mock_asio.run(app=flask_app)
The mock server:
- Listens for all events defined in the given spec, returning fake acknowledgements where applicable.
- Periodically emits events containing payloads of fake data, for the clients to listen on.
The fake data generation is fueled by Faker, hence the use of the mock server functionality requires the installation of extra dependecies: pip install asynction[mock]
To make the fake generated data more realistic, one may attach faker providers to the string schemata of their spec using the format keyword of JSON Schema:
# example of a Message object
NewMessageReceived:
name: new message
payload:
type: object
properties:
username:
type: string
format: first_name
message:
type: string
format: sentence
required: [username, message]
The formats supported are essentially all the faker providers that yield a string value.
Further resources
Specification Extentions
Asynction has extended the AsyncAPI 2.0.0 specification to provide support for coupling SocketIO semantical entities (such as namespaces, events and acks) to python objects (such as handler callabes or other flask_socketio.SocketIO
methods). Some of the extentions below are necessary to express the Socket.IO protocol semantics, while others are solely needed for the programmatic purposes of Asynction. The extentions introduced adhere to the Specification Extention guidelines of the AsyncAPI spec.
For further guidance on how to generally express a SocketIO API using AsyncAPI, refer to this article: https://dedouss.is/posts/2021-07-14-documenting-socketio-part-2.html
Event handler
The x-handler
field MAY be defined as an additional property of the Message Object. The value of this field MUST be of string
type, expressing a dot joint path to a python callable (the event handler).
Message Objects listed under a subscribe
operation MUST include the x-handler
field.
Message Objects listed under a publish
operation SHOULD NOT include the x-handler
field.
Default namespace handlers
The x-handlers
field MAY be defined as an additional property of the Channel Item Object. The value of this field SHOULD be a Channel Handlers Object.
Channel Handlers Object
Field Name | Type | Description |
---|---|---|
connect | string |
Dot joint path to the python connect handler callable |
disconnect | string |
Dot joint path to the python disconnect handler callable |
error | string |
Dot joint path to the python error handler callable |
ACK packet
The basic unit of information in the Socket.IO protocol is the packet. There are 7 distinct packet types. The publish
and subscribe
Message Objects expressed in the A2S YAML above correspond to the EVENT and BINARY_EVENT packet types. These are essentially the packets that are transmitted when the Socket.IO sender invokes the emit
or send
API functions of the Socket.IO library (regardless of implementation). In turn, the Socket.IO event receiver handles the received event using the on
API function of the Socket.IO library. As part of the on
handler, the receiver may choose to return an acknowledgement of the received message. This acknowledgement is conveyed back to the transmitter via the ACK and BINARY_ACK packet types. This ack data is passed as input into the callback that the message transmitter has provided through the emit
/send
invocation.
In order to express the above acknowledgement semantics, the A2S specification needs to be extended as follows:
- Message Objects MAY include the
x-ack
field. The value of this field SHOULD be a Message Ack Object. - Components Object MAY include the
x-messageAcks
field. The value of this field should be of type:Map[string, Message Ack Object | Reference Object]
Although Asynction uses these fields to validate the input args of the callback functions, these ACK extentions are necessary to express semantics of the Socket.IO protocol, regardless of any tooling used for automation / code generation.
Message Ack Object
Field Name | Type | Description |
---|---|---|
args | Schema Object | Schema of the arguments that are passed as input to the acknowledgement callback function. In the case of multiple arguments, use the array type to express the tuple. |
In the future, the Message Ack Object may be extended with extra fields to enable additional documentation of the callback.
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
Built Distribution
File details
Details for the file asynction-0.0.11.tar.gz
.
File metadata
- Download URL: asynction-0.0.11.tar.gz
- Upload date:
- Size: 18.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.2 CPython/3.9.7
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0531263ce10849685353bc2848d14621340a08950313dd9dce981d13a512ea34 |
|
MD5 | 9df530b3d86bb3023e312cbde0df5eaa |
|
BLAKE2b-256 | 117cabafbc8aa0e97c821895be04b4cf99d09cc89aaf37e76a25d922d8b7fa64 |
File details
Details for the file asynction-0.0.11-py3-none-any.whl
.
File metadata
- Download URL: asynction-0.0.11-py3-none-any.whl
- Upload date:
- Size: 16.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.2 CPython/3.9.7
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0f8ba09ece6171eac8b2e847b43ec4524e9deb9160af14e9674c66cb0e3909a9 |
|
MD5 | 8d116421d50b95b99b693b0f48bc833a |
|
BLAKE2b-256 | 890478c7dfb4df505da67c73ddde3d04f9ec0e877ebcc9f07848dca74c71f495 |