-
Notifications
You must be signed in to change notification settings - Fork 4
Implement AsyncAPI documentation #244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
0bc282c
Add AsyncAPI router and update dependencies
febus982 04d3d91
Create stub HTML rendered AsyncAPI docs
febus982 bc1d4d4
Add example websocket endpoint
febus982 d56ea39
Example rendering AsyncAPI from pydantic models and mapping
febus982 3ba9523
Stub router class to attempt auto-asyncapi-registration
febus982 53b53fc
Add TODO for overlapping models
febus982 aebbfb5
Batch-create json schemas
febus982 b7d99e7
Restructure registry
febus982 271ac7e
Create global registry with a decorator to register channels and func…
febus982 8109c87
Update dependencies
febus982 6a0dd9b
Change decorator implementation to functions
febus982 b87baad
Update line-length to 120 and update dependencies
febus982 1db967c
Fix edge case for messages without $defs
febus982 3679c94
Code style
febus982 7f7211e
Get application name from config, rename config dependency
febus982 d49d2cc
Add tests
febus982 fdcc28f
Include docs in schema
febus982 72ed197
Reduce cognitive complexity
febus982 6199961
Add details on documentation for AsyncAPI docs
febus982 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
authors = [ | ||
{name = "Federico Busetti", email = "[email protected]"}, | ||
] | ||
requires-python = "<3.14,>=3.9" | ||
requires-python = "<3.14,>=3.10" | ||
name = "bootstrap-fastapi-service" | ||
version = "0.1.0" | ||
description = "" | ||
|
@@ -14,7 +14,7 @@ dependencies = [ | |
"cloudevents-pydantic<1.0.0,>=0.0.3", | ||
"dependency-injector[pydantic]<5.0.0,>=4.41.0", | ||
"dramatiq[redis,watch]<2.0.0,>=1.17.1", | ||
"hiredis<4.0.0,>=3.1.0", # Recommended by dramatiq | ||
"hiredis<4.0.0,>=3.1.0", # Recommended by dramatiq | ||
"httpx>=0.23.0", | ||
"opentelemetry-distro[otlp]", | ||
"opentelemetry-instrumentation", | ||
|
@@ -28,6 +28,7 @@ dependencies = [ | |
"SQLAlchemy[asyncio,mypy]<3.0.0,>=2.0.0", | ||
"sqlalchemy-bind-manager", | ||
"structlog<25.1.1,>=25.1.0", | ||
"pydantic-asyncapi>=0.2.1", | ||
] | ||
|
||
[dependency-groups] | ||
|
@@ -115,6 +116,7 @@ testpaths = [ | |
|
||
[tool.ruff] | ||
target-version = "py39" | ||
line-length = 120 | ||
extend-exclude = [ | ||
"docs", | ||
] | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
from typing import List, Literal, Optional, Type | ||
|
||
import pydantic_asyncapi.v3 as pa | ||
from pydantic import BaseModel | ||
|
||
_info: pa.Info = pa.Info( | ||
title="AsyncAPI", | ||
version="1.0.0", | ||
) | ||
|
||
|
||
_servers = {} # type: ignore | ||
_channels = {} # type: ignore | ||
_operations = {} # type: ignore | ||
_components_schemas = {} # type: ignore | ||
|
||
|
||
def get_schema() -> pa.AsyncAPI: | ||
""" | ||
Function `get_schema` provides the complete AsyncAPI schema for the application, complying with | ||
version 3.0.0 of the AsyncAPI specification. It includes detailed information about info metadata, | ||
components, servers, channels, and operations required to set up and describe the asynchronous | ||
communication layer. | ||
|
||
Returns: | ||
pa.AsyncAPI: A fully constructed AsyncAPI schema object based on predefined configurations. | ||
""" | ||
return pa.AsyncAPI( | ||
asyncapi="3.0.0", | ||
info=_info, | ||
components=pa.Components( | ||
schemas=_components_schemas, | ||
), | ||
servers=_servers, | ||
channels=_channels, | ||
operations=_operations, | ||
) | ||
|
||
|
||
def init_asyncapi_info( | ||
title: str, | ||
version: str = "1.0.0", | ||
) -> None: | ||
""" | ||
Initializes the AsyncAPI information object with the specified title and version. | ||
|
||
This function creates and initializes an AsyncAPI Info object, which includes | ||
mandatory fields such as title and version. The title represents the name of the | ||
AsyncAPI document, and the version represents the version of the API. | ||
|
||
Parameters: | ||
title (str): The title of the AsyncAPI document. | ||
version (str): The version of the AsyncAPI document. Defaults to "1.0.0". | ||
|
||
Returns: | ||
None | ||
""" | ||
# We can potentially add the other info supported by pa.Info | ||
global _info | ||
_info = pa.Info( | ||
title=title, | ||
version=version, | ||
) | ||
|
||
|
||
def register_server( | ||
id: str, | ||
host: str, | ||
protocol: str, | ||
pathname: Optional[str] = None, | ||
) -> None: | ||
""" | ||
Registers a server with a unique identifier and its associated properties. | ||
This function accepts information about the server such as its host, | ||
protocol, and optionally its pathname, and stores it in the internal | ||
server registry identified by the unique ID. The parameters must be | ||
provided appropriately for proper registration. The server registry | ||
ensures that server configurations can be retrieved and managed based | ||
on the assigned identifier. | ||
|
||
Args: | ||
id: str | ||
A unique identifier for the server being registered. It is used | ||
as the key in the internal server registry. | ||
host: str | ||
The host address of the server. This may be an IP address or | ||
a domain name. | ||
protocol: str | ||
Communication protocol used by the server, such as "http" or "https". | ||
pathname: Optional[str] | ||
The optional pathname of the server. If provided, it will be | ||
associated with the registered server. | ||
|
||
Returns: | ||
None | ||
This function does not return a value. It modifies the internal | ||
server registry to include the provided server details. | ||
""" | ||
# TODO: Implement other server parameters | ||
_servers[id] = pa.Server( | ||
host=host, | ||
protocol=protocol, | ||
) | ||
if pathname is not None: | ||
_servers[id].pathname = pathname | ||
|
||
|
||
def register_channel( | ||
febus982 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
address: str, | ||
id: Optional[str] = None, | ||
description: Optional[str] = None, | ||
title: Optional[str] = None, | ||
server_id: Optional[str] = None, | ||
) -> None: | ||
""" | ||
Registers a communication channel with the specified parameters and updates the | ||
internal dictionary holding channel metadata. The function allows optional | ||
parameters to set additional properties such as description and title, and | ||
optionally associates the channel with a predefined server. | ||
|
||
Args: | ||
address (str): The address of the channel. | ||
id (Optional[str]): Unique identifier for the channel. Defaults to None. | ||
description (Optional[str]): Description of the channel. Defaults to None. | ||
title (Optional[str]): Title to be associated with the channel. Defaults to None. | ||
server_id (Optional[str]): Server identifier to link this channel to. | ||
Must exist in the internal server registry. Defaults to None. | ||
|
||
Returns: | ||
None | ||
""" | ||
# TODO: Define channel metadata in decorator | ||
_channels[id or address] = pa.Channel( | ||
address=address, | ||
servers=[], | ||
messages={}, | ||
) | ||
if description is not None: | ||
_channels[id or address].description = description | ||
if title is not None: | ||
_channels[id or address].title = title | ||
if server_id is not None and server_id in _servers: | ||
_channels[id or address].servers.append(pa.Reference(ref=f"#/servers/{server_id}")) # type: ignore | ||
|
||
|
||
def register_channel_operation( | ||
febus982 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
channel_id: str, | ||
operation_type: Literal["receive", "send"], | ||
messages: List[Type[BaseModel]], | ||
operation_name: Optional[str] = None, | ||
): | ||
if not _channels.get(channel_id): | ||
raise ValueError(f"Channel {channel_id} does not exist.") | ||
|
||
_operation_message_refs = [] | ||
for message in messages: | ||
# TODO: Check for overlapping model schemas, if they are different log a warning! | ||
_message_json_schema = message.model_json_schema( | ||
mode="validation" if operation_type == "receive" else "serialization", | ||
ref_template="#/components/schemas/{model}", | ||
) | ||
|
||
_components_schemas[message.__name__] = _message_json_schema | ||
|
||
if _message_json_schema.get("$defs"): | ||
_components_schemas.update(_message_json_schema["$defs"]) | ||
_channels[channel_id].messages[message.__name__] = pa.Message( # type: ignore | ||
payload=pa.Reference(ref=f"#/components/schemas/{message.__name__}") | ||
) | ||
|
||
# Cannot point to the /components path | ||
_operation_message_refs.append(pa.Reference(ref=f"#/channels/{channel_id}/messages/{message.__name__}")) | ||
|
||
_operations[operation_name or f"{channel_id}-{operation_type}"] = pa.Operation( | ||
action=operation_type, | ||
channel=pa.Reference(ref=f"#/channels/{channel_id}"), | ||
messages=_operation_message_refs, | ||
traits=[], | ||
) | ||
# TODO: Define operation traits | ||
# if operation_name is not None: | ||
# _operations[operation_name or f"{channel_id}-{operation_type}"].traits.append( | ||
# pa.OperationTrait( | ||
# title=operation_name, | ||
# summary=f"{operation_name} operation summary", | ||
# description=f"{operation_name} operation description", | ||
# ) | ||
# ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.