From 015e8505718a79807688362a7036cfc0ac0a3bc4 Mon Sep 17 00:00:00 2001 From: Arthur Goldberg Date: Tue, 4 May 2021 23:08:05 +0200 Subject: [PATCH] sanic 21.3 support (#218) * s21 * style * Cleanup testing * Reformatting * Bump version * squash * Run black Co-authored-by: Adam Hopkins --- docs/conf.py | 6 +- examples/cars/blueprints/car.py | 4 +- examples/cars/blueprints/driver.py | 4 +- examples/cars/blueprints/garage.py | 4 +- examples/cars/blueprints/manufacturer.py | 4 +- examples/cars/blueprints/repair.py | 4 +- examples/cars/main.py | 4 +- examples/cars_oas3/blueprints/car.py | 4 +- examples/cars_oas3/blueprints/driver.py | 4 +- examples/cars_oas3/blueprints/garage.py | 4 +- examples/cars_oas3/blueprints/manufacturer.py | 4 +- examples/cars_oas3/blueprints/repair.py | 4 +- examples/cars_oas3/main.py | 4 +- examples/class_based_view/main.py | 2 +- sanic_openapi/__init__.py | 14 ++- sanic_openapi/openapi2/__init__.py | 3 +- sanic_openapi/openapi2/blueprint.py | 7 +- sanic_openapi/openapi3/__init__.py | 8 +- sanic_openapi/openapi3/blueprint.py | 10 +- sanic_openapi/openapi3/builders.py | 2 +- sanic_openapi/openapi3/openapi.py | 2 +- sanic_openapi/utils.py | 93 ++++++++++++++----- tests/conftest.py | 1 + tests/test_api.py | 20 +++- tests/test_autodoc.py | 39 ++++---- tests/test_decorators.py | 12 ++- tests/test_fields.py | 23 ++--- tests/test_oas3.py | 28 +++--- tests/test_swagger.py | 50 +++++----- tox.ini | 5 +- 30 files changed, 234 insertions(+), 139 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 60ec870f..44e64fa2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,11 +13,14 @@ import os import sys +from recommonmark.transform import AutoStructify + +import sanic_openapi + docs_directory = os.path.dirname(os.path.abspath(__file__)) root_directory = os.path.dirname(docs_directory) sys.path.insert(0, root_directory) -import sanic_openapi # -- Project information ----------------------------------------------------- @@ -58,7 +61,6 @@ html_static_path = ["_static"] html_css_files = ["css/custom.css"] -from recommonmark.transform import AutoStructify def setup(app): diff --git a/examples/cars/blueprints/car.py b/examples/cars/blueprints/car.py index 87942664..699df12c 100644 --- a/examples/cars/blueprints/car.py +++ b/examples/cars/blueprints/car.py @@ -1,8 +1,8 @@ -from data import test_car, test_success -from models import Car, Status from sanic.blueprints import Blueprint from sanic.response import json +from data import test_car, test_success +from models import Car, Status from sanic_openapi import doc blueprint = Blueprint('Car', '/car') diff --git a/examples/cars/blueprints/driver.py b/examples/cars/blueprints/driver.py index 35dc41ea..577b345b 100644 --- a/examples/cars/blueprints/driver.py +++ b/examples/cars/blueprints/driver.py @@ -1,10 +1,10 @@ import json -from data import test_driver, test_success -from models import Driver, Status from sanic.blueprints import Blueprint from sanic.response import json +from data import test_driver, test_success +from models import Driver, Status from sanic_openapi import doc blueprint = Blueprint('Driver', '/driver') diff --git a/examples/cars/blueprints/garage.py b/examples/cars/blueprints/garage.py index 0c3d8983..dfe2800e 100644 --- a/examples/cars/blueprints/garage.py +++ b/examples/cars/blueprints/garage.py @@ -1,10 +1,10 @@ import json -from data import test_garage, test_success -from models import Car, Garage, Status from sanic.blueprints import Blueprint from sanic.response import json +from data import test_garage, test_success +from models import Car, Garage, Status from sanic_openapi import doc blueprint = Blueprint('Garage', '/garage') diff --git a/examples/cars/blueprints/manufacturer.py b/examples/cars/blueprints/manufacturer.py index b9fd40b9..9f121ec1 100644 --- a/examples/cars/blueprints/manufacturer.py +++ b/examples/cars/blueprints/manufacturer.py @@ -1,8 +1,8 @@ -from data import test_manufacturer, test_success -from models import Driver, Status from sanic.blueprints import Blueprint from sanic.response import json +from data import test_manufacturer, test_success +from models import Driver, Status from sanic_openapi import doc blueprint = Blueprint('Manufacturer', '/manufacturer') diff --git a/examples/cars/blueprints/repair.py b/examples/cars/blueprints/repair.py index d96ef4c4..bf74382f 100644 --- a/examples/cars/blueprints/repair.py +++ b/examples/cars/blueprints/repair.py @@ -2,11 +2,11 @@ from sanic.response import json from sanic.views import HTTPMethodView +from data import test_station +from models import Station from sanic_openapi import doc blueprint = Blueprint('Repair', '/repair') -from data import test_station -from models import Station class RepairStation(HTTPMethodView): diff --git a/examples/cars/main.py b/examples/cars/main.py index 62e0b33e..0de33fdf 100644 --- a/examples/cars/main.py +++ b/examples/cars/main.py @@ -1,10 +1,10 @@ +from sanic import Sanic + from blueprints.car import blueprint as car_blueprint from blueprints.driver import blueprint as driver_blueprint from blueprints.garage import blueprint as garage_blueprint from blueprints.manufacturer import blueprint as manufacturer_blueprint from blueprints.repair import blueprint as repair_blueprint -from sanic import Sanic - from sanic_openapi import swagger_blueprint app = Sanic("Cars API example") diff --git a/examples/cars_oas3/blueprints/car.py b/examples/cars_oas3/blueprints/car.py index a2f307c0..ff38a5da 100644 --- a/examples/cars_oas3/blueprints/car.py +++ b/examples/cars_oas3/blueprints/car.py @@ -1,8 +1,8 @@ -from data import test_car, test_success -from models import Car, Status from sanic.blueprints import Blueprint from sanic.response import json +from data import test_car, test_success +from models import Car, Status from sanic_openapi import openapi blueprint = Blueprint('Car', '/car') diff --git a/examples/cars_oas3/blueprints/driver.py b/examples/cars_oas3/blueprints/driver.py index 0c1fe6b0..f0c5d350 100644 --- a/examples/cars_oas3/blueprints/driver.py +++ b/examples/cars_oas3/blueprints/driver.py @@ -1,10 +1,10 @@ import json -from data import test_driver, test_success -from models import Driver, Status from sanic.blueprints import Blueprint from sanic.response import json +from data import test_driver, test_success +from models import Driver, Status from sanic_openapi import openapi blueprint = Blueprint('Driver', '/driver') diff --git a/examples/cars_oas3/blueprints/garage.py b/examples/cars_oas3/blueprints/garage.py index 04c983fc..f66fe481 100644 --- a/examples/cars_oas3/blueprints/garage.py +++ b/examples/cars_oas3/blueprints/garage.py @@ -1,10 +1,10 @@ import json -from data import test_garage, test_success -from models import Car, Garage, Status from sanic.blueprints import Blueprint from sanic.response import json +from data import test_garage, test_success +from models import Car, Garage, Status from sanic_openapi import openapi blueprint = Blueprint('Garage', '/garage') diff --git a/examples/cars_oas3/blueprints/manufacturer.py b/examples/cars_oas3/blueprints/manufacturer.py index 775679af..667210d0 100644 --- a/examples/cars_oas3/blueprints/manufacturer.py +++ b/examples/cars_oas3/blueprints/manufacturer.py @@ -1,8 +1,8 @@ -from data import test_manufacturer, test_success -from models import Driver, Status from sanic.blueprints import Blueprint from sanic.response import json +from data import test_manufacturer, test_success +from models import Driver, Status from sanic_openapi import openapi blueprint = Blueprint('Manufacturer', '/manufacturer') diff --git a/examples/cars_oas3/blueprints/repair.py b/examples/cars_oas3/blueprints/repair.py index 7e79cec1..6e698965 100644 --- a/examples/cars_oas3/blueprints/repair.py +++ b/examples/cars_oas3/blueprints/repair.py @@ -2,11 +2,11 @@ from sanic.response import json from sanic.views import HTTPMethodView +from data import test_station +from models import Station from sanic_openapi import openapi blueprint = Blueprint('Repair', '/repair') -from data import test_station -from models import Station class RepairStation(HTTPMethodView): diff --git a/examples/cars_oas3/main.py b/examples/cars_oas3/main.py index a39c06b5..9fc0ec5b 100644 --- a/examples/cars_oas3/main.py +++ b/examples/cars_oas3/main.py @@ -1,10 +1,10 @@ +from sanic import Sanic + from blueprints.car import blueprint as car_blueprint from blueprints.driver import blueprint as driver_blueprint from blueprints.garage import blueprint as garage_blueprint from blueprints.manufacturer import blueprint as manufacturer_blueprint from blueprints.repair import blueprint as repair_blueprint -from sanic import Sanic - from sanic_openapi import openapi3_blueprint app = Sanic("Cars API example") diff --git a/examples/class_based_view/main.py b/examples/class_based_view/main.py index 26009e9c..9d116b0d 100644 --- a/examples/class_based_view/main.py +++ b/examples/class_based_view/main.py @@ -1,7 +1,7 @@ -from blueprint import blueprint from sanic import Sanic from sanic.response import json +from blueprint import blueprint from sanic_openapi import doc, swagger_blueprint app = Sanic("Class Based View example") diff --git a/sanic_openapi/__init__.py b/sanic_openapi/__init__.py index bb7e075d..37deeba2 100644 --- a/sanic_openapi/__init__.py +++ b/sanic_openapi/__init__.py @@ -1,7 +1,13 @@ -from .openapi3 import openapi3_blueprint, openapi -from .openapi2 import openapi2_blueprint, doc +from .openapi2 import doc, openapi2_blueprint +from .openapi3 import openapi, openapi3_blueprint swagger_blueprint = openapi2_blueprint -__version__ = "0.6.2" -__all__ = ["openapi2_blueprint", "swagger_blueprint", "openapi3_blueprint", "openapi", "doc"] +__version__ = "21.3.0" +__all__ = [ + "openapi2_blueprint", + "swagger_blueprint", + "openapi3_blueprint", + "openapi", + "doc", +] diff --git a/sanic_openapi/openapi2/__init__.py b/sanic_openapi/openapi2/__init__.py index 2a2c26bc..8beebe7f 100644 --- a/sanic_openapi/openapi2/__init__.py +++ b/sanic_openapi/openapi2/__init__.py @@ -1,4 +1,3 @@ from .blueprint import blueprint_factory - -openapi2_blueprint = blueprint_factory() \ No newline at end of file +openapi2_blueprint = blueprint_factory() diff --git a/sanic_openapi/openapi2/blueprint.py b/sanic_openapi/openapi2/blueprint.py index 979f9769..11008737 100644 --- a/sanic_openapi/openapi2/blueprint.py +++ b/sanic_openapi/openapi2/blueprint.py @@ -46,7 +46,7 @@ def build_spec(app, loop): paths = {} - for uri, route_name, route_parameters, method_handlers in get_all_routes(app, swagger_blueprint.url_prefix): + for (uri, route_name, route_parameters, method_handlers) in get_all_routes(app, swagger_blueprint.url_prefix): # --------------------------------------------------------------- # # Methods @@ -57,7 +57,7 @@ def build_spec(app, loop): route_spec = route_specs.get(_handler) or RouteSpec() - if _method == "OPTIONS" or route_spec.exclude: + if route_spec.exclude: continue api_consumes_content_types = getattr(app.config, "API_CONSUMES_CONTENT_TYPES", ["application/json"]) @@ -152,7 +152,8 @@ def build_spec(app, loop): } ) - # otherwise, update with anything parsed from the docstrings yaml + # otherwise, update with anything parsed from the + # docstrings yaml endpoint.update(autodoc_endpoint) methods[_method.lower()] = endpoint diff --git a/sanic_openapi/openapi3/__init__.py b/sanic_openapi/openapi3/__init__.py index e680eee7..8eeea925 100644 --- a/sanic_openapi/openapi3/__init__.py +++ b/sanic_openapi/openapi3/__init__.py @@ -1,3 +1,7 @@ +""" + isort:skip_file +""" + from collections import defaultdict from .builders import OperationBuilder, SpecificationBuilder @@ -8,7 +12,7 @@ operations = defaultdict(OperationBuilder) specification = SpecificationBuilder() -from .blueprint import blueprint_factory +from .blueprint import blueprint_factory # noqa -openapi3_blueprint = blueprint_factory() \ No newline at end of file +openapi3_blueprint = blueprint_factory() diff --git a/sanic_openapi/openapi3/blueprint.py b/sanic_openapi/openapi3/blueprint.py index a881cf1f..c3f958a9 100644 --- a/sanic_openapi/openapi3/blueprint.py +++ b/sanic_openapi/openapi3/blueprint.py @@ -6,6 +6,8 @@ from ..utils import get_all_routes, get_blueprinted_routes from . import operations, specification +DEFAULT_SWAGGER_UI_CONFIG = {"apisSorter": "alpha", "operationsSorter": "alpha"} + def blueprint_factory(): oas3_blueprint = Blueprint("openapi", url_prefix="/swagger") @@ -27,7 +29,13 @@ def spec(request): @oas3_blueprint.route("/swagger-config") def config(request): - return json(getattr(request.app.config, "SWAGGER_UI_CONFIGURATION", {})) + return json( + getattr( + request.app.config, + "SWAGGER_UI_CONFIGURATION", + DEFAULT_SWAGGER_UI_CONFIG, + ) + ) @oas3_blueprint.listener("before_server_start") def build_spec(app, loop): diff --git a/sanic_openapi/openapi3/builders.py b/sanic_openapi/openapi3/builders.py index 8c35162e..dbbed4c1 100644 --- a/sanic_openapi/openapi3/builders.py +++ b/sanic_openapi/openapi3/builders.py @@ -146,7 +146,7 @@ def build(self) -> OpenAPI: paths = self._build_paths() tags = self._build_tags() - url_servers = getattr(self, "_urls", None) + url_servers = getattr(self, "_urls", None) servers = [] if url_servers is not None: for url_server in url_servers: diff --git a/sanic_openapi/openapi3/openapi.py b/sanic_openapi/openapi3/openapi.py index bd2fc316..54817f7c 100644 --- a/sanic_openapi/openapi3/openapi.py +++ b/sanic_openapi/openapi3/openapi.py @@ -6,7 +6,7 @@ from typing import Any from . import operations -from .types import ( +from .types import ( # noqa Array, Binary, Boolean, diff --git a/sanic_openapi/utils.py b/sanic_openapi/utils.py index 36cac557..acbe56fa 100644 --- a/sanic_openapi/utils.py +++ b/sanic_openapi/utils.py @@ -50,7 +50,8 @@ def get_blueprinted_routes(app): for route in blueprint.routes: if hasattr(route.handler, "view_class"): - # class based view + # before sanic 21.3, route.handler could be a number of + # different things, so have to type check for http_method in route.methods: _handler = getattr(route.handler.view_class, http_method.lower(), None) if _handler: @@ -61,31 +62,79 @@ def get_blueprinted_routes(app): def get_all_routes(app, skip_prefix): uri_filter = get_uri_filter(app) - for uri, route in app.router.routes_all.items(): - # Ignore routes under swagger blueprint - if route.uri.startswith(skip_prefix): - continue - # Apply the URI filter - if uri_filter(uri): - continue + # new sanic 21.3 style routing... + if hasattr(app, "ctx"): + for group in app.router.groups.values(): + uri = f"/{group.path}" - # route.name will be None when using class based view - if route.name and "static" in route.name: - continue + # prior to sanic 21.3 routes came in both forms + # (e.g. /test and /test/ ) + # after sanic 21.3 routes come in one form, + # with an attribute "strict", + # so we simulate that ourselves: + + uris = [uri] + if not group.strict and len(uri) > 1: + alt = uri[:-1] if uri.endswith("/") else f"{uri}/" + uris.append(alt) + + for uri in uris: + if uri_filter(uri): + continue - # create dict httpMethod -> handler - # e.g. {"GET" -> lambda request: response} + if group.raw_path.startswith(skip_prefix.lstrip("/")): + continue - if type(route.handler) is CompositionView: - method_handlers = route.handler.handlers + for parameter in group.params.values(): + uri = re.sub( + "<" + parameter.name + ".*?>", + "{" + parameter.name + "}", + uri, + ) - elif hasattr(route.handler, "view_class"): - method_handlers = {method: getattr(route.handler.view_class, method.lower()) for method in route.methods} - else: - method_handlers = {method: route.handler for method in route.methods} + for route in group: + if route.name and "static" in route.name: + continue + + method_handlers = [(method, route.handler) for method in route.methods] + + _, name = route.name.split(".", 1) + yield (uri, name, route.params.values(), method_handlers) + + continue + else: + for uri, route in app.router.routes_all.items(): + # Ignore routes under swagger blueprint + if uri.startswith(skip_prefix): + continue + + # Apply the URI filter + if uri_filter(uri): + continue + + # route.name will be None when using class based view + if route.name and "static" in route.name: + continue + + # create dict httpMethod -> handler + # e.g. {"GET" -> lambda request: response} + + if type(route.handler) is CompositionView: + method_handlers = route.handler.handlers + + elif hasattr(route.handler, "view_class"): + method_handlers = { + method: getattr(route.handler.view_class, method.lower()) for method in route.methods + } + else: + method_handlers = {method: route.handler for method in route.methods} - for parameter in route.parameters: - uri = re.sub("<" + parameter.name + ".*?>", "{" + parameter.name + "}", uri) + for parameter in route.parameters: + uri = re.sub( + "<" + parameter.name + ".*?>", + "{" + parameter.name + "}", + uri, + ) - yield uri, route.name, route.parameters, method_handlers.items() + yield uri, route.name, route.parameters, method_handlers.items() diff --git a/tests/conftest.py b/tests/conftest.py index b7b3e223..2c118d20 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import itertools + import pytest from sanic import Sanic diff --git a/tests/test_api.py b/tests/test_api.py index 44322d45..0c470fbc 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,5 +1,5 @@ -import json import itertools +import json from inspect import isawaitable from sanic import Sanic @@ -8,6 +8,7 @@ from sanic_openapi import doc, openapi2_blueprint from sanic_openapi.openapi2 import api + def test_message_api_response(): """ The goal here is to test whether the `json_response()` decorator is applied. @@ -102,6 +103,23 @@ def test_documentation(): pprint.pprint(app_response.json) pprint.pprint(benchmark_response.json) + + # sanic 21.3 modifys route.name to include the app name + # so manually check they are the same without the app name, + # then set them to be the same. + + for path in benchmark_response.json["paths"]: + for method in benchmark_response.json["paths"][path]: + assert ( + app_response.json["paths"][path][method]["operationId"].split(".")[-1] + == benchmark_response.json["paths"][path][method]["operationId"].split( + "." + )[-1] + ) + app_response.json["paths"][path][method][ + "operationId" + ] = benchmark_response.json["paths"][path][method]["operationId"] + assert app_response.status == benchmark_response.status == 200 assert app_response.json == benchmark_response.json diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 928aea25..6e2ff5eb 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1,28 +1,27 @@ from sanic_openapi import autodoc - tests = [] -_ = '' +_ = "" -tests.append({'doc': _, 'expects': {}}) +tests.append({"doc": _, "expects": {}}) -_ = 'one line docstring' +_ = "one line docstring" -tests.append({'doc': _, 'expects': {"summary": "one line docstring"}}) +tests.append({"doc": _, "expects": {"summary": "one line docstring"}}) -_ = ''' +_ = """ first line more lines -''' +""" -tests.append({'doc': _, 'expects': { - "summary": "first line", - "description": "more lines"}}) +tests.append( + {"doc": _, "expects": {"summary": "first line", "description": "more lines"}} +) -_ = ''' +_ = """ first line more lines @@ -32,12 +31,18 @@ responses: '200': description: OK -''' - -tests.append({'doc': _, 'expects': { - "summary": "first line", - "description": "more lines", - "responses": {"200": {"description": "OK"}}}}) +""" + +tests.append( + { + "doc": _, + "expects": { + "summary": "first line", + "description": "more lines", + "responses": {"200": {"description": "OK"}}, + }, + } +) def test_autodoc(): diff --git a/tests/test_decorators.py b/tests/test_decorators.py index b8fde42b..bbfb874a 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,6 +1,6 @@ import pytest - from sanic.response import text + from sanic_openapi import doc @@ -120,7 +120,7 @@ def test(request): @pytest.mark.parametrize( "produces_args, produces_kwargs, responses", [ - ([], {}, {'200': {'description': 'OK'}}), + ([], {}, {"200": {"description": "OK"}}), ([doc.String], {}, {"200": {"schema": {"type": "string"}}}), ( [TestSchema], @@ -146,7 +146,7 @@ def test(request): @pytest.mark.parametrize( "response_args, responses", [ - ([], {'200': {'description': 'OK'}}), + ([], {"200": {"description": "OK"}}), ([201, {}], {"201": {"schema": {"type": "object", "properties": {}}}}), ], ) @@ -168,7 +168,7 @@ def test(request): @pytest.mark.parametrize( "produces_args, produces_kwargs, response_args, responses", [ - ([], {}, [], {'200': {'description': 'OK'}}), + ([], {}, [], {"200": {"description": "OK"}}), ([doc.String], {}, [200, {}], {"200": {"schema": {"type": "string"}}}), ( [TestSchema], @@ -181,7 +181,9 @@ def test(request): ), ], ) -def test_produces_and_response(app, produces_args, produces_kwargs, response_args, responses): +def test_produces_and_response( + app, produces_args, produces_kwargs, response_args, responses +): @app.post("/") @doc.produces(*produces_args, **produces_kwargs) @doc.response(*response_args) diff --git a/tests/test_fields.py b/tests/test_fields.py index 55e010e0..7983e49d 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,7 +1,8 @@ -import pytest - from datetime import date, datetime + +import pytest from sanic.response import HTTPResponse, text + from sanic_openapi import doc @@ -257,11 +258,18 @@ def test_uuid_field(app): def test(request): return HTTPResponse(status=204) + @app.get("/") + @doc.consumes(field, location="formData", required=True) + @doc.response(204, {}) + def test2(request): + return HTTPResponse(status=204) + _, response = app.test_client.get("/swagger/swagger.json") assert response.status == 200 assert response.content_type == "application/json" swagger_json = response.json + path = swagger_json["paths"]["/{id}"]["get"] assert path["parameters"][0] == { "in": "path", @@ -271,17 +279,6 @@ def test(request): "required": True, } - @app.get("/") - @doc.consumes(field, location="formData", required=True) - @doc.response(204, {}) - def test(request): - return HTTPResponse(status=204) - - _, response = app.test_client.get("/swagger/swagger.json") - assert response.status == 200 - assert response.content_type == "application/json" - - swagger_json = response.json path = swagger_json["paths"]["/"]["get"] assert path["parameters"][0] == { "in": "formData", diff --git a/tests/test_oas3.py b/tests/test_oas3.py index 92e75266..6bf31c6c 100644 --- a/tests/test_oas3.py +++ b/tests/test_oas3.py @@ -1,9 +1,7 @@ import itertools -import json -from inspect import isawaitable from sanic import Sanic -from sanic.response import HTTPResponse, json as json_response +from sanic.response import json as json_response from sanic_openapi import openapi, openapi3_blueprint @@ -16,14 +14,16 @@ def test_documentation(): _, app_response = app.test_client.get("/swagger/swagger.json") post_operation = app_response.json["paths"]["/garage"]["post"] - body_props = post_operation["requestBody"]["content"]["application/json"]["schema"]["properties"] + body_props = post_operation["requestBody"]["content"]["application/json"]["schema"][ + "properties" + ] assert post_operation["description"] == "Create a new garage" car_schema_in_body = body_props["cars"] - assert car_schema_in_body["required"] == False + assert car_schema_in_body["required"] is False assert car_schema_in_body["type"] == "array" assert len(car_schema_in_body["items"]["properties"]) == 3 spaces_schema_in_body = body_props["spaces"] - assert spaces_schema_in_body["required"] == True + assert spaces_schema_in_body["required"] is True assert spaces_schema_in_body["format"] == "int32" assert spaces_schema_in_body["type"] == "integer" assert spaces_schema_in_body["description"] == "Space available in the garage" @@ -35,33 +35,33 @@ def test_documentation(): def get_app(): """ - Creates a Sanic application whose routes are documented using the `openPI` module. + Creates a Sanic application whose routes are documented + using the `openPI` module. """ app = Sanic("test_api_oas3_{}".format(next(app_ID))) app.blueprint(openapi3_blueprint) - class Car: manufacturer = openapi.String(description="Car manufacturer", required=True) model = openapi.String(description="Car model", required=True) production_date = openapi.Date(description="Car year", required=True) - class Garage: - spaces = openapi.Integer(description="Space available in the garage", required=True) + spaces = openapi.Integer( + description="Space available in the garage", required=True + ) cars = openapi.Array(Car, required=False) - - @app.post('/garage') + @app.post("/garage") @openapi.description("Create a new garage") @openapi.body( - { "application/json" : Garage }, + {"application/json": Garage}, description="Body description", location="/garage", required=True, ) @openapi.response( - 201, { "application/json" : Garage }, "A new Garage has been created" + 201, {"application/json": Garage}, "A new Garage has been created" ) async def create_garage(request): return json_response(request.json, 201) diff --git a/tests/test_swagger.py b/tests/test_swagger.py index aa7eed5d..63a2dd51 100644 --- a/tests/test_swagger.py +++ b/tests/test_swagger.py @@ -42,13 +42,19 @@ def get_handler(request): def test_swagger_endpoint(app): _, response = app.test_client.get("/swagger/") assert response.status == 200 - assert response.content_type == "text/html" + assert ( + response.content_type == "text/html" # pre sanic21.3 + or response.content_type == "text/html; charset=utf-8" + ) # post sanic21.3 def test_swagger_endpoint_redirect(app): _, response = app.test_client.get("/swagger") assert response.status == 200 - assert response.content_type == "text/html" + assert ( + response.content_type == "text/html" # pre sanic21.3 + or response.content_type == "text/html; charset=utf-8" + ) # post sanic21.3 assert len(response.history) == 1 status = getattr( response.history[0], "status", getattr(response.history[0], "status_code", None) @@ -58,7 +64,7 @@ def test_swagger_endpoint_redirect(app): @pytest.mark.skip( - reason="https://github.com/sanic-org/sanic-openapi/pull/111#pullrequestreview-255118509" + reason="https://github.com/sanic-org/sanic-openapi/pull/111#pullrequestreview-255118509" # noqa ) def test_swagger_json(app): _, response = app.test_client.get("/swagger/swagger.json") @@ -83,6 +89,11 @@ def test(request): assert response.content_type == "application/json" swagger_json = response.json + + # sanic 21.3 changes the route.name to include the app name + assert "test" in swagger_json["paths"]["/"][method]["operationId"] + swagger_json["paths"]["/"][method]["operationId"] = "test" + assert swagger_json["paths"] == { "/": { method: { @@ -90,7 +101,7 @@ def test(request): "consumes": ["application/json"], "produces": ["application/json"], "parameters": [], - "responses": {'200': {'description': 'OK'}}, + "responses": {"200": {"description": "OK"}}, } } } @@ -114,6 +125,11 @@ def test(request): swagger_json = response.json assert {"name": "test"} in swagger_json["tags"] + + # sanic 21.3 changes the route.name to include the app name + assert "test.test" in swagger_json["paths"]["/"][method]["operationId"] + swagger_json["paths"]["/"][method]["operationId"] = "test.test" + assert swagger_json["paths"] == { "/": { method: { @@ -122,7 +138,7 @@ def test(request): "produces": ["application/json"], "tags": ["test"], "parameters": [], - "responses": {'200': {'description': 'OK'}}, + "responses": {"200": {"description": "OK"}}, } } } @@ -130,7 +146,8 @@ def test(request): def test_class_based_view(app): """ - In sanic_openapi/swagger.py#n124, class based view will not document endpoint with options method. + In sanic_openapi/swagger.py#n124, class based view will not document + endpoint with options method. """ app.add_route(SimpleView.as_view(), "/") @@ -139,10 +156,8 @@ def test_class_based_view(app): assert response.content_type == "application/json" swagger_json = response.json - methods = METHODS.copy() - methods.remove("options") - assert sorted(set(methods)) == sorted(set(swagger_json["paths"]["/"].keys())) + assert sorted(set(METHODS)) == sorted(set(swagger_json["paths"]["/"].keys())) def test_blueprint_class_based_view(app): @@ -156,10 +171,8 @@ def test_blueprint_class_based_view(app): assert response.content_type == "application/json" swagger_json = response.json - methods = METHODS.copy() - methods.remove("options") - assert sorted(set(methods)) == sorted(set(swagger_json["paths"]["/"].keys())) + assert sorted(set(METHODS)) == sorted(set(swagger_json["paths"]["/"].keys())) assert {"name": "test"} in swagger_json["tags"] @@ -287,19 +300,6 @@ def test(request, name): assert "/{name}" in swagger_json["paths"] -def test_ignore_options_route(app): - @app.options("/") - def test(request): - return text("test") - - _, response = app.test_client.get("/swagger/swagger.json") - assert response.status == 200 - assert response.content_type == "application/json" - - swagger_json = response.json - assert swagger_json["paths"] == {} - - def test_route_filter_all(app): app.config.update({"API_URI_FILTER": "all"}) diff --git a/tox.ini b/tox.ini index 45d18604..824d7820 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {py37,py38,py39}-sanic{lts, 19.03.1, 19.06.0, 19.09.0, 20.03.0}, check +envlist = {py37,py38,py39}-sanic{lts, 19.03.1, 19.06.0, 19.09.0, 20.03.0, 20.12.3, 21.03.2}, check [travis] @@ -17,6 +17,9 @@ deps = sanic19.06.0: sanic==19.06.0 sanic19.09.0: sanic==19.09.0 sanic20.03.0: sanic==20.03.0 + sanic20.12.3: sanic==20.12.3 + sanic21.03.2: sanic==21.03.2 + sanic21.03.2: sanic_testing commands = pip install -e .['test']