From 9cf72316eb993c6976891d97fbd961b615e58223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A7=D0=B5=D1=80=D0=B5=D0=BC=D1=83=D1=88=D0=BA=D0=B8?= =?UTF-8?q?=D0=BD=20=D0=92=D1=8F=D1=87=D0=B5=D1=81=D0=BB=D0=B0=D0=B2?= Date: Thu, 25 Jul 2019 11:04:09 +0300 Subject: [PATCH] fix path params schema --- star_resty/apidocs/setup.py | 16 +++++----- star_resty/apidocs/utils.py | 7 ++++- tests/test_apidocs.py | 62 ++++++++++++++++++++++++++++++++++++- tests/utils/method.py | 9 ++++++ 4 files changed, 83 insertions(+), 11 deletions(-) diff --git a/star_resty/apidocs/setup.py b/star_resty/apidocs/setup.py index 58fd98e..5565761 100644 --- a/star_resty/apidocs/setup.py +++ b/star_resty/apidocs/setup.py @@ -1,6 +1,5 @@ import inspect import logging -import re from typing import Dict, Optional, Sequence, Type, Union from apispec import APISpec @@ -13,7 +12,7 @@ from star_resty.method import Method from star_resty.method.meta import MethodMetaOptions from star_resty.method.request_parser import RequestParser -from .utils import resolve_schema_name +from .utils import convert_path, resolve_schema_name logger = logging.getLogger(__name__) @@ -52,9 +51,12 @@ def generate_api_docs(_: Request): return UJSONResponse(spec.to_dict()) -def get_open_api_version(openapi_version: str) -> int: - v = openapi_version.split('.', maxsplit=1)[0] - return int(v) +def get_open_api_version(version: str) -> int: + v = version.split('.', maxsplit=1)[0] + try: + return int(v) + except (ValueError, TypeError): + raise ValueError(f'Invalid open api version: {version}') def setup_paths(app: Starlette, spec: APISpec, version: int = 2, @@ -187,7 +189,3 @@ def create_error_schema_by_exc(e: Union[Exception, Type[Exception]]) -> Dict: schema['schema'] = error_schema return schema - - -def convert_path(path: str) -> str: - return re.sub(r'<([^>]+)>', r'{\1}', path) diff --git a/star_resty/apidocs/utils.py b/star_resty/apidocs/utils.py index b166516..363e1e6 100644 --- a/star_resty/apidocs/utils.py +++ b/star_resty/apidocs/utils.py @@ -1,7 +1,8 @@ import inspect +import re from typing import Any -__all__ = ('resolve_schema_name',) +__all__ = ('resolve_schema_name', 'convert_path') def resolve_schema_name(schema: Any) -> str: @@ -12,3 +13,7 @@ def resolve_schema_name(schema: Any) -> str: name = f'{cls.__module__}.{cls.__qualname__}' return name + + +def convert_path(path: str) -> str: + return re.sub(r'{([^:]+).*}', r'{\1}', path) diff --git a/tests/test_apidocs.py b/tests/test_apidocs.py index d676a06..bd9dda2 100644 --- a/tests/test_apidocs.py +++ b/tests/test_apidocs.py @@ -1,8 +1,9 @@ from starlette.applications import Starlette +from starlette.routing import Mount, Route, Router from starlette.testclient import TestClient from star_resty.apidocs import setup_spec -from .utils.method import CreateUser +from .utils.method import CreateUser, GetUser, SearchUser def test_generate_api_docs(): @@ -44,3 +45,62 @@ def test_generate_api_docs(): 'type': 'integer'}, 'name': {'type': 'string'}}, 'type': 'object'}} + + +def test_generate_api_docs_for_router(): + routes = [ + Mount('/v1', Router([ + Route('/users', CreateUser.as_endpoint(), methods=['POST']), + Route('/users', SearchUser.as_endpoint(), methods=['GET']) + ])) + ] + app = Starlette(routes=routes) + + setup_spec(app, title='test') + + client = TestClient(app) + resp = client.get('/apidocs.json') + assert resp is not None + body = resp.json() + assert body is not None + assert body.get('paths') == { + '/v1/users': { + 'post': {'tags': ['users'], 'description': 'create user', 'produces': ['application/json'], + 'parameters': [ + {'in': 'path', 'name': 'id', 'required': True, 'type': 'integer', 'format': 'int32'}, + {'in': 'body', 'required': False, 'name': 'body', + 'schema': {'$ref': '#/definitions/tests.utils.method.BodySchema'}}], + 'responses': { + '201': {'schema': {'$ref': '#/definitions/tests.utils.method.CreateUserResponse'}}, + '400': {'description': 'Bad request'}}}, + 'get': {'tags': ['default'], 'produces': ['application/json'], + 'parameters': [ + {'in': 'path', 'name': 'id', 'required': True, 'type': 'integer', 'format': 'int32'}, + {'in': 'query', 'name': 'q', 'required': False, 'type': 'string'}], + 'responses': { + '200': {'schema': {'$ref': '#/definitions/tests.utils.method.SearchUserResponse'}}, + '400': {'description': 'Bad request'}}}}} + + +def test_generate_api_docs_for_path(): + app = Starlette() + + setup_spec(app, title='test') + app.add_route('/users/{user_id:int}', GetUser.as_endpoint(), methods=['POST']) + + client = TestClient(app) + resp = client.get('/apidocs.json') + assert resp is not None + body = resp.json() + assert body is not None + assert body.get('paths') == { + '/users/{user_id}': { + 'post': { + 'tags': ['users'], 'description': 'get user', 'produces': ['application/json'], + 'parameters': [ + {'in': 'path', 'name': 'id', 'required': True, 'type': 'integer', 'format': 'int32'}, + {'in': 'body', 'required': False, 'name': 'body', + 'schema': {'$ref': '#/definitions/tests.utils.method.BodySchema'}}], + 'responses': { + '200': {'schema': {'$ref': '#/definitions/tests.utils.method.CreateUserResponse'}}, + '400': {'description': 'Bad request'}}}}} diff --git a/tests/utils/method.py b/tests/utils/method.py index dc380fd..1e8e4dc 100644 --- a/tests/utils/method.py +++ b/tests/utils/method.py @@ -37,6 +37,15 @@ async def execute(self, user: path(PathParams), return {'id': user['id'], **payload} +class GetUser(Method): + meta = Operation(tag='users', description='get user') + response_schema = CreateUserResponse + + async def execute(self, user: path(PathParams), + payload: json_payload(BodySchema)): + return {'id': 1} + + class SearchUser(Method): mata = Operation(tag='users', description='search user') response_schema = SearchUserResponse