Closed morph027 closed 1 year ago
This is the data it tries to serialize in https://github.com/sanic-org/sanic-ext/blob/v22.9.0/sanic_ext/extensions/openapi/types.py#L57
{'openapi': '3.0.3', 'info': {'title': 'Signal Cli REST API', 'version': '22.8.1', 'description': 'This is the Signal Cli REST API documentation.', 'license': {'name': 'MIT', 'url': 'https://mit-license.org/'}, 'contact': {}}, 'paths': {'/v1/about': {'get': {'operationId': 'get~about_v1.about_v1_get', 'summary': 'Lists general information about the API.', 'tags': ['General'], 'responses': {200: {'content': {'application/json': {'schema': {'mode': <class 'str'>, 'versions': typing.List[str]}}}, 'description': 'OK'}}, 'description': 'Returns the supported API versions.'}}, '/v1/search': {'get': {'operationId': 'get~search_v1.search_v1_get', 'summary': 'Check if one or more phone numbers are registered with the Signal Service.', 'tags': ['Search'], 'parameters': [{'name': 'numbers', 'schema': {'type': 'array', 'items': {'type': 'string'}}, 'description': 'Numbers to check', 'required': True, 'in': 'query'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 200: {'content': {'application/json': {'schema': {'type': 'array', 'items': {'type': 'object', 'properties': {'number': {'type': 'string'}, 'registered': {'type': 'boolean'}}}}}}, 'description': 'OK'}}, 'description': 'Check if one or more phone numbers are registered with the Signal Service.'}}, '/v2/send': {'post': {'operationId': 'post~send_v2.send_v2_post', 'summary': 'Send a signal message.', 'tags': ['Messages'], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 201: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'timestamp': {'type': 'string'}}}}}, 'description': 'Created'}}, 'description': 'Send a signal message.`number` can be ommited if API is running w/ `PYTHON_SIGNAL_CLI_REST_API_ACCOUNT`', 'requestBody': {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'recipients': {'type': 'array', 'items': {'type': 'string'}}, 'message': {'type': 'string'}, 'number': {'type': 'string', 'nullable': True}, 'base64_attachments': {'type': 'array', 'items': {'type': 'string'}, 'nullable': True}, 'mentions': {'type': 'array', 'items': {'type': 'string'}, 'nullable': True}}}}}, 'required': True}}}, '/openapi/assets/swagger/{__file_uri__}': {'get': {'operationId': 'get~swagger-assets', 'summary': 'Swagger-Assets', 'parameters': [{'name': '__file_uri__', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {'default': {'description': 'OK'}}}}, '/v1/groups/{number}': {'get': {'operationId': 'get~groups_of_number_v1.groups_for_number_get', 'summary': 'List all Signal Groups.', 'tags': ['Groups'], 'parameters': [{'name': 'number', 'schema': {'type': 'string'}, 'description': 'Registered Phone Number', 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 200: {'content': {'application/json': {'schema': {'type': 'array', 'items': {'type': 'object', 'properties': {'blocked': {'type': 'boolean'}, 'id': {'type': 'string'}, 'invite_link': {'type': 'string'}, 'members': {'type': 'array', 'items': {'type': 'string'}}, 'name': {'type': 'string'}, 'pending_invites': {'type': 'array', 'items': {'type': 'string'}}, 'pending_requests': {'type': 'array', 'items': {'type': 'string'}}, 'message_expiration_timer': {'type': 'integer', 'format': 'int32'}, 'admins': {'type': 'array', 'items': {'type': 'string'}}, 'description': {'type': 'string'}}}}}}, 'description': 'OK'}}, 'description': 'List all Signal Groups.'}, 'post': {'operationId': 'post~create_group_v1.create_group_v1_post', 'summary': 'Create a new Signal Group with the specified members.', 'tags': ['Groups'], 'parameters': [{'name': 'number', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 201: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'group_id': {'type': 'string'}}}}}, 'description': 'Created'}}, 'description': 'Create a new Signal Group.', 'requestBody': {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'name': {'type': 'string'}, 'members': {'type': 'array', 'items': {'type': 'string'}}, 'permissions': {'type': 'object', 'nullable': True, 'properties': {'add_members': {'type': 'string', 'default': 'only-admins'}, 'edit_group': {'type': 'string', 'default': 'only-admins'}}}, 'group_link': {'type': 'string', 'enum': ['disabled', 'enabled', 'enabled-with-approval']}, 'admins': {'type': 'array', 'items': {'type': 'string'}, 'nullable': True}, 'description': {'type': 'string', 'nullable': True}, 'base64_avatar': {'type': 'string', 'nullable': True}, 'message_expiration_timer': {'type': 'integer', 'format': 'int32', 'nullable': True}}}}}, 'required': True}}}, '/v1/groups/{number}/{groupid}': {'patch': {'operationId': 'patch~update_group_v1.update_group_v1_patch', 'summary': 'Update an existing Signal Group.', 'tags': ['Groups'], 'parameters': [{'name': 'groupid', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}, {'name': 'number', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 200: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'blocked': {'type': 'boolean'}, 'id': {'type': 'string'}, 'invite_link': {'type': 'string'}, 'members': {'type': 'array', 'items': {'type': 'string'}}, 'name': {'type': 'string'}, 'pending_invites': {'type': 'array', 'items': {'type': 'string'}}, 'pending_requests': {'type': 'array', 'items': {'type': 'string'}}, 'message_expiration_timer': {'type': 'integer', 'format': 'int32'}, 'admins': {'type': 'array', 'items': {'type': 'string'}}, 'description': {'type': 'string'}}}}}, 'description': 'OK'}}, 'description': 'Update an existing Signal Group.', 'requestBody': {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'group_link': {'type': 'string', 'enum': ['unchanged', 'disabled', 'enabled', 'enabled-with-approval']}, 'add_admins': {'type': 'array', 'items': {'type': 'string'}, 'nullable': True}, 'add_members': {'type': 'array', 'items': {'type': 'string'}, 'nullable': True}}}}}}}, 'get': {'operationId': 'get~group_details_v1.groups_of_number_get', 'summary': 'List a Signal Group.', 'tags': ['Groups'], 'parameters': [{'name': 'groupid', 'schema': {'type': 'string'}, 'description': 'Group ID', 'required': True, 'in': 'path'}, {'name': 'number', 'schema': {'type': 'string'}, 'description': 'Registered Phone Number', 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 200: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'blocked': {'type': 'boolean'}, 'id': {'type': 'string'}, 'invite_link': {'type': 'string'}, 'members': {'type': 'array', 'items': {'type': 'string'}}, 'name': {'type': 'string'}, 'pending_invites': {'type': 'array', 'items': {'type': 'string'}}, 'pending_requests': {'type': 'array', 'items': {'type': 'string'}}, 'message_expiration_timer': {'type': 'integer', 'format': 'int32'}, 'admins': {'type': 'array', 'items': {'type': 'string'}}, 'description': {'type': 'string'}}}}}, 'description': 'OK'}}, 'description': 'List a Signal Group.'}, 'delete': {'operationId': 'delete~delete_group_v1.delete_group_v1_delete', 'summary': 'Delete the specified Signal Group.', 'tags': ['Groups'], 'parameters': [{'name': 'groupid', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}, {'name': 'number', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 204: {'content': {'*/*': {'schema': {}}}, 'description': 'Deleted'}}, 'description': 'Delete a Signal Group.'}}, '/v1/groups/{number}/{groupid}/join': {'post': {'operationId': 'post~join_group_v1.join_group_v1_post', 'summary': 'Join a Signal Group.', 'tags': ['Groups'], 'parameters': [{'name': 'groupid', 'schema': {'type': 'string'}, 'description': 'Group invite link like https://signal.group/#...', 'required': True, 'in': 'path'}, {'name': 'number', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 204: {'content': {'*/*': {'schema': {'type': 'string'}}}, 'description': 'OK'}}, 'description': 'Join a Signal Group.'}}, '/v1/groups/{number}/{groupid}/quit': {'post': {'operationId': 'post~quit_group_v1.quit_group_v1_post', 'summary': 'Quit (leave) a Signal Group.', 'tags': ['Groups'], 'parameters': [{'name': 'groupid', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}, {'name': 'number', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 204: {'content': {'*/*': {'schema': {'type': 'string'}}}, 'description': 'OK'}}, 'description': 'Quit (leave) a Signal Group.'}}, '/v1/reactions/{number}': {'post': {'operationId': 'post~reactions_v1.reactions_v1_post', 'summary': 'Send a reaction.', 'tags': ['Reactions'], 'parameters': [{'name': 'number', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 201: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'timestamp': {'type': 'string'}}}}}, 'description': 'Created'}}, 'description': 'Send a reaction.', 'requestBody': {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'reaction': {'type': 'string'}, 'target_author': {'type': 'string'}, 'timestamp': {'type': 'integer', 'format': 'int32'}, 'recipient': {'type': 'string'}, 'groupid': {'type': 'string', 'nullable': True}, 'remove': {'type': 'boolean', 'nullable': True}}}}}, 'required': True}}, 'delete': {'operationId': 'delete~reactions_v1.reactions_v1_delete', 'summary': 'Delete a reaction.', 'tags': ['Reactions'], 'parameters': [{'name': 'number', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 200: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'timestamp': {'type': 'string'}}}}}, 'description': 'Deleted'}}, 'description': 'Delete a reaction.', 'requestBody': {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'recipient': {'type': 'string'}, 'target_author': {'type': 'string'}, 'timestamp': {'type': 'integer', 'format': 'int32'}}}}}, 'required': True}}}, '/v1/register/{number}': {'post': {'operationId': 'post~register_v1.register_post', 'summary': 'Register a phone number.', 'tags': ['Devices'], 'parameters': [{'name': 'number', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 201: {'content': {'application/json': {'schema': {'type': 'object'}}}, 'description': 'Created'}}, 'description': 'Register a phone number with the signal network.', 'requestBody': {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'captcha': {'type': 'string', 'nullable': True}, 'use_voice': {'type': 'boolean', 'nullable': True}}}}}}}}, '/v1/send/{recipient}': {'post': {'operationId': 'post~send_v1.send_v1_post', 'summary': 'Send a signal message.', 'tags': ['Messages'], 'parameters': [{'name': 'recipient', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 201: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'timestamp': {'type': 'string'}}}}}, 'description': 'Created'}}, 'description': 'Send a signal message.`number` can be ommited if API is running w/ `PYTHON_SIGNAL_CLI_REST_API_`', 'requestBody': {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'message': {'type': 'string'}, 'number': {'type': 'string', 'nullable': True}, 'base64_attachments': {'type': 'array', 'items': {'type': 'string'}}}}}}, 'required': True}}}, '/v1/register/{number}/verify/{token}': {'post': {'operationId': 'post~verify_v1.verify_post', 'summary': 'Verify a registered phone number.', 'tags': ['Devices'], 'parameters': [{'name': 'token', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}, {'name': 'number', 'schema': {'type': 'string'}, 'required': True, 'in': 'path'}], 'responses': {400: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'error': {'type': 'string'}}}}}, 'description': 'Bad Request'}, 201: {'content': {'application/json': {'schema': {'type': 'object'}}}, 'description': 'Created'}}, 'description': 'Verify a registered phone number with the signal network.', 'requestBody': {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'pin': {'type': 'string', 'nullable': True}}}}}}}}}, 'tags': [{'name': 'General'}, {'name': 'Search'}, {'name': 'Messages'}, {'name': 'Groups'}, {'name': 'Reactions'}, {'name': 'Devices'}], 'servers': [], 'security': []}
LMK if you can make a small repro example :sunglasses:
Fails
from dataclasses import dataclass, field
from typing import Optional, List
from sanic import Sanic
from sanic.response import json
from sanic_ext import openapi, validate
app = Sanic("Test")
@dataclass
class Body:
param_a: str
param_b: int
param_c: Optional[list[str]] = field(default_factory=list)
@app.post("/")
@openapi.body({"application/json": Body})
@openapi.response(
200,
{
"application/json": {
"mode": str,
"versions": List[str],
}
},
description="OK",
)
@validate(Body)
async def test(request, body: Body):
return json({"message": "Hello world!"})
Works:
from dataclasses import dataclass, field
from typing import Optional, List
from sanic import Sanic
from sanic.response import json
from sanic_ext import openapi, validate
app = Sanic("Test")
@dataclass
class Body:
param_a: str
param_b: int
param_c: Optional[list[str]] = field(default_factory=list)
@dataclass
class Response:
mode: str
versions: List[str]
@app.post("/")
@openapi.body({"application/json": Body})
@openapi.response(
200,
{
"application/json": Response
},
description="OK",
)
@validate(Body)
async def test(request, body: Body):
return json({"message": "Hello world!"})
So specifying a dataclass as response is fine (which is okay for me, just need to migrate one single endpoint).
Ahh.... yeah, that makes sense. The PR I added for the dict
in the boy does not apply to response. I'll add that soon and release it as a patch. It was an oversight.
Describe the bug
I've upgraded my project to 22.9.0 and when trying to open swagger UI, it fails with the following stacktrace:
To Reproduce
Not sure if this happens on my project only. Will try to re-create with a blank dummy project.
Expected behavior
Render openapi.json