From 786d006ec6b747fe84e6160f2ae8cb3cba9b3735 Mon Sep 17 00:00:00 2001 From: Dirk Guijt Date: Sat, 20 Jan 2018 20:21:23 +0100 Subject: [PATCH 01/14] updated the swagger UI to version v3.9.2, added decorator to provide reponses for non-200 status codes, now possible to configure authorization for swagger, fixed the issue where swagger always expects the openapi/spec.json path to be at the root of the url, added doc field-type specifically for post endpoints that consumes a json body (the openapi spec is invalid when using the Dictionary for this), fixed some other openapi spec invalidities, updated requirements, bumped minor version number --- dev-requirements.txt | 2 +- sanic_openapi/__init__.py | 2 +- sanic_openapi/doc.py | 45 +++- sanic_openapi/openapi.py | 38 ++-- sanic_openapi/ui/index.html | 22 +- sanic_openapi/ui/oauth2-redirect.html | 59 ++--- sanic_openapi/ui/swagger-ui-bundle.js | 202 +++++++++--------- sanic_openapi/ui/swagger-ui-bundle.js.map | 2 +- .../ui/swagger-ui-standalone-preset.js | 19 +- .../ui/swagger-ui-standalone-preset.js.map | 2 +- sanic_openapi/ui/swagger-ui.css | 2 +- sanic_openapi/ui/swagger-ui.css.map | 2 +- sanic_openapi/ui/swagger-ui.js | 21 +- sanic_openapi/ui/swagger-ui.js.map | 2 +- setup.py | 4 +- 15 files changed, 221 insertions(+), 203 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 200cabc..af12dc5 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,2 +1,2 @@ -sanic==0.6.0 +sanic==0.7.0 tox==2.7.0 diff --git a/sanic_openapi/__init__.py b/sanic_openapi/__init__.py index c95470e..bc83633 100644 --- a/sanic_openapi/__init__.py +++ b/sanic_openapi/__init__.py @@ -1,5 +1,5 @@ from .openapi import blueprint as openapi_blueprint from .swagger import blueprint as swagger_blueprint -__version__ = '0.4.0' +__version__ = '0.5.0' __all__ = ['openapi_blueprint', 'swagger_blueprint'] diff --git a/sanic_openapi/doc.py b/sanic_openapi/doc.py index 1dbc11b..2a83388 100644 --- a/sanic_openapi/doc.py +++ b/sanic_openapi/doc.py @@ -91,6 +91,21 @@ def serialize(self): } +class JsonBody(Field): + def __init__(self, fields=None, **kwargs): + self.fields = fields or {} + super().__init__(**kwargs, name="body") + + def serialize(self): + return { + "schema": { + "type": "object", + "properties": {key: serialize_schema(schema) for key, schema in self.fields.items()}, + }, + **super().serialize() + } + + class List(Field): def __init__(self, items=None, *args, **kwargs): self.items = items or [] @@ -200,10 +215,12 @@ class RouteSpec(object): blueprint = None tags = None exclude = None + response = None def __init__(self): self.tags = [] self.consumes = [] + self.response = [] super().__init__() @@ -211,11 +228,13 @@ class RouteField(object): field = None location = None required = None + description = None - def __init__(self, field, location=None, required=False): + def __init__(self, field, location=None, required=False, description=None): self.field = field self.location = location self.required = required + self.description = description route_specs = defaultdict(RouteSpec) @@ -223,7 +242,7 @@ def __init__(self, field, location=None, required=False): def route(summary=None, description=None, consumes=None, produces=None, consumes_content_type=None, produces_content_type=None, - exclude=None): + exclude=None, response=None): def inner(func): route_spec = route_specs[func] @@ -241,6 +260,8 @@ def inner(func): route_spec.produces_content_type = produces_content_type if exclude is not None: route_spec.exclude = exclude + if response is not None: + route_spec.response = response return func return inner @@ -273,17 +294,27 @@ def inner(func): for arg in args: field = RouteField(arg, location, required) route_specs[func].consumes.append(field) - route_specs[func].consumes_content_type = content_type + route_specs[func].consumes_content_type = [content_type] + return func + return inner + + +def produces(*args, description=None, content_type=None): + def inner(func): + if args: + routefield = RouteField(args[0], description=description) + route_specs[func].produces = routefield + route_specs[func].produces_content_type = [content_type] return func return inner -def produces(*args, content_type=None): +def response(*args, description=None): def inner(func): if args: - field = RouteField(args[0]) - route_specs[func].produces = field - route_specs[func].produces_content_type = content_type + status_code = args[0] + routefield = RouteField(args[1], description=description) + route_specs[func].response.append((status_code, routefield)) return func return inner diff --git a/sanic_openapi/openapi.py b/sanic_openapi/openapi.py index 1d31ab2..7eb1e4b 100644 --- a/sanic_openapi/openapi.py +++ b/sanic_openapi/openapi.py @@ -29,17 +29,24 @@ def build_spec(app, loop): "version": getattr(app.config, 'API_VERSION', '1.0.0'), "title": getattr(app.config, 'API_TITLE', 'API'), "description": getattr(app.config, 'API_DESCRIPTION', ''), - "termsOfService": getattr(app.config, 'API_TERMS_OF_SERVICE', None), + "termsOfService": getattr(app.config, 'API_TERMS_OF_SERVICE', ''), "contact": { "email": getattr(app.config, 'API_CONTACT_EMAIL', None) }, "license": { - "email": getattr(app.config, 'API_LICENSE_NAME', None), + "name": getattr(app.config, 'API_LICENSE_NAME', None), "url": getattr(app.config, 'API_LICENSE_URL', None) - } + }, + } _spec['schemes'] = getattr(app.config, 'API_SCHEMES', ['http']) + # --------------------------------------------------------------- # + # Authorization + # --------------------------------------------------------------- # + + _spec['securityDefinitions'] = getattr(app.config, 'API_SECURITY', None) + # --------------------------------------------------------------- # # Blueprint Tags # --------------------------------------------------------------- # @@ -79,9 +86,9 @@ def build_spec(app, loop): continue consumes_content_types = route_spec.consumes_content_type or \ - getattr(app.config, 'API_CONSUMES_CONTENT_TYPES', ['application/json']) + getattr(app.config, 'API_CONSUMES_CONTENT_TYPES', ['application/json']) produces_content_types = route_spec.produces_content_type or \ - getattr(app.config, 'API_PRODUCES_CONTENT_TYPES', ['application/json']) + getattr(app.config, 'API_PRODUCES_CONTENT_TYPES', ['application/json']) # Parameters - Path & Query String route_parameters = [] @@ -117,6 +124,19 @@ def build_spec(app, loop): route_parameters.append(route_param) + responses = { + "200": { + "schema": serialize_schema(route_spec.produces.field) if route_spec.produces else None, + "description": route_spec.produces.description if route_spec.produces else None + } + } + + for (status_code, routefield) in route_spec.response: + responses[f"{status_code}"] = { + "schema": serialize_schema(routefield.field), + "description": routefield.description + } + endpoint = remove_nulls({ 'operationId': route_spec.operation or route.name, 'summary': route_spec.summary, @@ -125,13 +145,7 @@ def build_spec(app, loop): 'produces': produces_content_types, 'tags': route_spec.tags or None, 'parameters': route_parameters, - 'responses': { - "200": { - "description": None, - "examples": None, - "schema": serialize_schema(route_spec.produces) if route_spec.produces else None - } - }, + 'responses': responses }) methods[_method.lower()] = endpoint diff --git a/sanic_openapi/ui/index.html b/sanic_openapi/ui/index.html index c103039..b757023 100644 --- a/sanic_openapi/ui/index.html +++ b/sanic_openapi/ui/index.html @@ -5,21 +5,21 @@ Swagger UI - +