From d3f39144e18cae3c88e1922bae0061142b124422 Mon Sep 17 00:00:00 2001 From: Typhon66 Date: Sat, 2 Sep 2017 10:50:33 -0700 Subject: [PATCH 1/6] adding location to allow for header/body/query for consumption --- examples/cars/blueprints/car.py | 2 +- sanic_openapi/doc.py | 22 +++++++++++---- sanic_openapi/openapi.py | 47 ++++++++++++++------------------- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/examples/cars/blueprints/car.py b/examples/cars/blueprints/car.py index 50d6f8d7..1cfcec3c 100644 --- a/examples/cars/blueprints/car.py +++ b/examples/cars/blueprints/car.py @@ -26,7 +26,7 @@ def car_get(request, car_id): @blueprint.put("/", strict_slashes=True) @doc.summary("Updates a car") -@doc.consumes(Car) +@doc.consumes(Car, location='body') @doc.produces(Car) def car_put(request, car_id): return json(test_car) diff --git a/sanic_openapi/doc.py b/sanic_openapi/doc.py index 24abdcb1..e693792a 100644 --- a/sanic_openapi/doc.py +++ b/sanic_openapi/doc.py @@ -189,7 +189,7 @@ def serialize_schema(schema): # --------------------------------------------------------------- # -class RouteSpec: +class RouteSpec(object): consumes = None consumes_content_type = None produces = None @@ -203,8 +203,18 @@ class RouteSpec: def __init__(self): self.tags = [] + self.consumes = [] super().__init__() +class RouteField(object): + field = None + location = None + required = None + + def __init__(self, field, location, required): + self.field = field + self.location = location + self.required = required route_specs = defaultdict(RouteSpec) @@ -255,16 +265,18 @@ def inner(func): return inner -def consumes(*args, content_type=None): +def consumes(*args, content_type=None, location=None, required=False): def inner(func): if args: - route_specs[func].consumes = args[0] if len(args) == 1 else args - route_specs[func].consumes_content_type = content_type + for arg in args: + field = RouteField(arg, location, required) + route_specs[func].consumes.append(field) + route_specs[func].consumes_content_type = content_type return func return inner -def produces(*args, content_type=None): +def produces(*args, content_type=None, location=None): def inner(func): if args: route_specs[func].produces = args[0] if len(args) == 1 else args diff --git a/sanic_openapi/openapi.py b/sanic_openapi/openapi.py index 65d9687a..7706164a 100644 --- a/sanic_openapi/openapi.py +++ b/sanic_openapi/openapi.py @@ -84,31 +84,25 @@ def build_spec(app, loop): getattr(app.config, 'API_PRODUCES_CONTENT_TYPES', ['application/json']) # Parameters - Path & Query String - path_parameters = [{ - **serialize_schema(parameter.cast), - 'required': True, - 'in': 'path', - 'name': parameter.name, - } for parameter in route.parameters] - query_string_parameters = [] - body_parameters = [] - - if route_spec.consumes: - if _method in ('GET', 'DELETE', 'POST'): - spec = serialize_schema(route_spec.consumes) - if 'properties' in spec: - for name, prop_spec in spec['properties'].items(): - query_string_parameters.append({ - **prop_spec, - 'in': 'query', - 'name': name, - }) - else: - body_parameters.append({ - **serialize_schema(route_spec.consumes), - 'in': 'body', - 'name': 'body', - }) + route_parameters = [] + for parameter in route.parameters: + route_parameters.append({ + **serialize_schema(parameter.cast), + 'required': True, + 'in': 'path', + 'name': parameter.name + }) + + for consumer in route_spec.consumes: + spec = serialize_schema(consumer.field) + if 'properties' in spec: + for name, prop_spec in spec['properties'].items(): + route_parameters.append({ + **prop_spec, + 'required': consumer.required, + 'in': consumer.location, + 'name': name + }) endpoint = remove_nulls({ 'operationId': route_spec.operation or route.name, @@ -117,7 +111,7 @@ def build_spec(app, loop): 'consumes': consumes_content_types, 'produces': produces_content_types, 'tags': route_spec.tags or None, - 'parameters': path_parameters + query_string_parameters + body_parameters, + 'parameters': route_parameters, 'responses': { "200": { "description": None, @@ -157,7 +151,6 @@ def build_spec(app, loop): _spec['paths'] = paths - @blueprint.route('/spec.json') def spec(request): return json(_spec) From 129f8e67e0940e626896d93f10f8597c4ff58ac0 Mon Sep 17 00:00:00 2001 From: Typhon66 Date: Sat, 2 Sep 2017 10:53:44 -0700 Subject: [PATCH 2/6] update verison and docs --- README.md | 6 ++++++ sanic_openapi/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fa3467dc..fa57e4ba 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,12 @@ from sanic_openapi import doc @doc.produces({ "user": { "name": str, "id": int } }) async def get_user(request, user_id): ... + +@app.post("/user") +@doc.summary("Creates a user") +@doc.consumes({"user": { "name": str }}, location="body") +async def create_user(request): + ... ``` ### Model your input/output diff --git a/sanic_openapi/__init__.py b/sanic_openapi/__init__.py index 89d93a32..c95470e4 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.3.0' +__version__ = '0.4.0' __all__ = ['openapi_blueprint', 'swagger_blueprint'] From 486978f27e960e42483b345727d33ea875c1acc4 Mon Sep 17 00:00:00 2001 From: Typhon66 Date: Sat, 2 Sep 2017 10:57:12 -0700 Subject: [PATCH 3/6] fixing test --- tests/test_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_types.py b/tests/test_types.py index 5d804ea5..b261b33f 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -14,7 +14,7 @@ def test_list_default(): app.blueprint(openapi_blueprint) @app.put('/test') - @doc.consumes(doc.List(int, description="All the numbers")) + @doc.consumes(doc.List(int, description="All the numbers"), location="body") def test(request): return json({"test": True}) From 05c462b6527d510ccd2042052e69eba9a7420013 Mon Sep 17 00:00:00 2001 From: Typhon66 Date: Tue, 5 Sep 2017 00:26:21 -0700 Subject: [PATCH 4/6] fixing things up a bit --- examples/cars/blueprints/car.py | 1 + sanic_openapi/doc.py | 7 ++++--- sanic_openapi/openapi.py | 17 +++++++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/examples/cars/blueprints/car.py b/examples/cars/blueprints/car.py index 1cfcec3c..b9546664 100644 --- a/examples/cars/blueprints/car.py +++ b/examples/cars/blueprints/car.py @@ -27,6 +27,7 @@ def car_get(request, car_id): @blueprint.put("/", strict_slashes=True) @doc.summary("Updates a car") @doc.consumes(Car, location='body') +@doc.consumes({'AUTHORIZATION': str}, location='header') @doc.produces(Car) def car_put(request, car_id): return json(test_car) diff --git a/sanic_openapi/doc.py b/sanic_openapi/doc.py index e693792a..627e9eb3 100644 --- a/sanic_openapi/doc.py +++ b/sanic_openapi/doc.py @@ -211,7 +211,7 @@ class RouteField(object): location = None required = None - def __init__(self, field, location, required): + def __init__(self, field, location=None, required=False): self.field = field self.location = location self.required = required @@ -276,10 +276,11 @@ def inner(func): return inner -def produces(*args, content_type=None, location=None): +def produces(*args, content_type=None): def inner(func): if args: - route_specs[func].produces = args[0] if len(args) == 1 else args + field = RouteField(args[0]) + route_specs[func].produces = field route_specs[func].produces_content_type = content_type return func return inner diff --git a/sanic_openapi/openapi.py b/sanic_openapi/openapi.py index 7706164a..4e7f2c12 100644 --- a/sanic_openapi/openapi.py +++ b/sanic_openapi/openapi.py @@ -97,12 +97,25 @@ def build_spec(app, loop): spec = serialize_schema(consumer.field) if 'properties' in spec: for name, prop_spec in spec['properties'].items(): - route_parameters.append({ + route_param = { **prop_spec, 'required': consumer.required, 'in': consumer.location, 'name': name - }) + } + else: + route_param = { + **spec, + 'required': consumer.required, + 'in': consumer.location, + 'name': consumer.field.name if hasattr(consumer.field, 'name') else 'body' + } + + if '$ref' in route_param: + route_param["schema"] = {'$ref': route_param['$ref']} + del route_param['$ref'] + + route_parameters.append(route_param) endpoint = remove_nulls({ 'operationId': route_spec.operation or route.name, From 70008319ba2ac78bfa354a9da0cc7c0f19d88b5f Mon Sep 17 00:00:00 2001 From: Typhon66 Date: Tue, 5 Sep 2017 00:29:35 -0700 Subject: [PATCH 5/6] code formatting --- sanic_openapi/doc.py | 2 ++ sanic_openapi/openapi.py | 1 + 2 files changed, 3 insertions(+) diff --git a/sanic_openapi/doc.py b/sanic_openapi/doc.py index 627e9eb3..2014129c 100644 --- a/sanic_openapi/doc.py +++ b/sanic_openapi/doc.py @@ -206,6 +206,7 @@ def __init__(self): self.consumes = [] super().__init__() + class RouteField(object): field = None location = None @@ -216,6 +217,7 @@ def __init__(self, field, location=None, required=False): self.location = location self.required = required + route_specs = defaultdict(RouteSpec) diff --git a/sanic_openapi/openapi.py b/sanic_openapi/openapi.py index 4e7f2c12..1d31ab20 100644 --- a/sanic_openapi/openapi.py +++ b/sanic_openapi/openapi.py @@ -164,6 +164,7 @@ def build_spec(app, loop): _spec['paths'] = paths + @blueprint.route('/spec.json') def spec(request): return json(_spec) From 60053fea65d1a47a5eaaf7865a2873b713d30d85 Mon Sep 17 00:00:00 2001 From: Typhon66 Date: Tue, 5 Sep 2017 00:30:21 -0700 Subject: [PATCH 6/6] default location --- sanic_openapi/doc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic_openapi/doc.py b/sanic_openapi/doc.py index 2014129c..1dbc11b0 100644 --- a/sanic_openapi/doc.py +++ b/sanic_openapi/doc.py @@ -267,7 +267,7 @@ def inner(func): return inner -def consumes(*args, content_type=None, location=None, required=False): +def consumes(*args, content_type=None, location='query', required=False): def inner(func): if args: for arg in args: