I am implementing a feature for generating RESTful-style APIs based on class-based views. The expectation is to obtain both list and detail endpoints through a single resource route. However, I found that the OpenAPI documentation is not generated as expected. When there are multiple blueprints or views, it fails to correctly group by blueprints.
Code snippet
from dataclasses import dataclass
from sanic import Sanic
from sanic.blueprints import Blueprint
from sanic.constants import HTTPMethod
from sanic.views import HTTPMethodView
class CreateModelMixin:
async def create(self, request, *args, **kwargs):
...
async def post(self, request, *args, **kwargs):
await self.create(request, *args, **kwargs)
class ListModelMixin:
async def list(self, request, *args, **kwargs):
...
async def get(self, request, *args, **kwargs):
return await self.list(request, *args, **kwargs)
class UpdateModelMixin:
async def update(self, request, *args, **kwargs):
...
async def put(self, request, *args, **kwargs):
return await self.update(request, *args, **kwargs)
class PartialUpdateModelMixin:
async def partial_update(self, request, *args, **kwargs):
...
async def patch(self, request, *args, **kwargs):
return await self.partial_update(request, *args, **kwargs)
class DestroyModelMixin:
async def destroy(self, request, *args, **kwargs):
...
async def delete(self, request, *args, **kwargs):
return await self.destroy(request, *args, **kwargs)
class RetrieveModelMixin:
async def retrieve(self, request, *args, **kwargs):
...
async def get(self, request, *args, **kwargs):
return await self.retrieve(request, *args, **kwargs)
class GenericViewSet(HTTPMethodView):
...
# async def get(self, request, *args, **kwargs):
# return text('get method')
#
# async def post(self, request, *args, **kwargs):
# return text('post method')
#
# async def put(self, request, *args, **kwargs):
# return text('put method')
#
# async def patch(self, request, *args, **kwargs):
# return text('patch method')
#
# async def delete(self, request, *args, **kwargs):
# return text('delete method')
class GenericViewSetB(HTTPMethodView):
...
# async def get(self, request, *args, **kwargs):
# return text('get method')
#
# async def post(self, request, *args, **kwargs):
# return text('post method')
# async def put(self, request, *args, **kwargs):
# return text('put method')
#
# async def patch(self, request, *args, **kwargs):
# return text('patch method')
#
# async def delete(self, request, *args, **kwargs):
# return text('delete method')
class AView(
GenericViewSet
):
...
class BView(
GenericViewSetB
):
...
@dataclass
class RestRoute:
mixin_map: dict
uri: str = ''
res_routes_map = {
'list-view': RestRoute(
mixin_map={
HTTPMethod.GET: ListModelMixin,
HTTPMethod.POST: CreateModelMixin,
},
),
'detail-view': RestRoute(
mixin_map={
HTTPMethod.GET: RetrieveModelMixin,
HTTPMethod.PUT: UpdateModelMixin,
HTTPMethod.PATCH: PartialUpdateModelMixin,
HTTPMethod.DELETE: DestroyModelMixin
},
)
}
def get_rest_routes(uri: str):
list_uri, detail_uri = '', ''
lstr, rstr, end_char = '/<', '>', '/'
if not all([rstr in uri, lstr in uri]):
list_uri = uri
else:
idx = uri.rindex(lstr)
list_uri = (idx > 0 and not uri.endswith(end_char)) and uri[:idx] or uri[:idx + 1] # 结尾字符处理
detail_uri = uri
res_routes_map['list-view'].uri = list_uri
res_routes_map['detail-view'].uri = detail_uri
return res_routes_map
def add_res_route(bp, view, uri: str, **kwargs):
class_kwargs = kwargs.pop('class_kwargs', {})
for key, route in get_rest_routes(uri).items():
view_class = type(f'{view.__name__}~{key}', (*route.mixin_map.values(), view), {})
handler = view_class.as_view(*kwargs.pop('class_args', ()), **class_kwargs)
bp.add_route(handler, route.uri, methods=route.mixin_map.keys(), **kwargs)
app = Sanic(__name__)
app.config.update(dict(
OAS_URL_PREFIX='/docs',
OAS_UI_DEFAULT='swagger',
SWAGGER_UI_CONFIGURATION={
"docExpansion": "list"
},
OAS_UI_SWAGGER_VERSION='5.0.0',
))
my = Blueprint('my', url_prefix='/my')
your = Blueprint('your', url_prefix='/your')
add_res_route(my, AView, '/aaa/<pk:int>/')
# Issue 1: The names displayed are all from BView and not grouped by blueprint names. Expanding one of the endpoints also expands another endpoint with the same name.
add_res_route(your, BView, '/bbb/<pk:int>/')
app.blueprint([my, your])
# Issue 2: The names displayed are all from AView. Expanding one of the endpoints also expands another endpoint with the same name.
# add_res_route(my, BView, '/bbb/<pk:int>/')
# app.blueprint([my])
Expected Behavior
Hope to add a resource view feature where by filling in a URL like /users/<pk:int>, a RESTful-style endpoint can be generated.
Is there an existing issue for this?
Describe the bug
I am implementing a feature for generating RESTful-style APIs based on class-based views. The expectation is to obtain both list and detail endpoints through a single resource route. However, I found that the OpenAPI documentation is not generated as expected. When there are multiple blueprints or views, it fails to correctly group by blueprints.
Code snippet
Expected Behavior
/users/<pk:int>
, a RESTful-style endpoint can be generated.How do you run Sanic?
Sanic CLI
Operating System
Linux
Sanic Version
23.12.1
Additional context
No response