Skip to content

Commit

Permalink
Merge pull request #58 from DirkGuijt/master
Browse files Browse the repository at this point in the history
sanic-openapi 0.5.0; many fixes, see description
  • Loading branch information
ahopkins committed Mar 27, 2019
2 parents 55fab44 + 9debb35 commit 44e692d
Show file tree
Hide file tree
Showing 20 changed files with 463 additions and 381 deletions.
77 changes: 72 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@ Give your Sanic API a UI and OpenAPI documentation, all for the price of free!
pip install sanic-openapi
```

Add OpenAPI and Swagger UI:
Add Swagger UI with the OpenAPI spec:

```python
from sanic_openapi import swagger_blueprint, openapi_blueprint
from sanic_openapi import swagger_blueprint

app.blueprint(openapi_blueprint)
app.blueprint(swagger_blueprint)
```

You'll now have a Swagger UI at the URL `/swagger`.
You'll now have a Swagger UI at the URL `/swagger` and an OpenAPI 2.0 spec at `/swagger/swagger.json`.
Your routes will be automatically categorized by their blueprints.

## Example
Expand All @@ -45,7 +44,7 @@ async def get_user(request, user_id):

@app.post("/user")
@doc.summary("Creates a user")
@doc.consumes({"user": { "name": str }}, location="body")
@doc.consumes(doc.JsonBody({"user": { "name": str }}), location="body")
async def create_user(request):
...
```
Expand Down Expand Up @@ -86,6 +85,29 @@ class Garage:
cars = doc.List(Car, description="All cars in the garage")
```

### Specify a JSON body without extensive modelling

```python
garage = doc.JsonBody({
"spaces": doc.Integer,
"cars": [
{
"make": doc.String,
"model": doc.String,
"year": doc.Integer
}
]
})

@app.post("/store/garage")
@doc.summary("Stores a garage object")
@doc.consumes(garage, content_type="application/json", location="body")
async def store_garage(request):
store_garage(request.json)
return json(request.json)
```


### Configure all the things

```python
Expand All @@ -96,3 +118,48 @@ app.config.API_TERMS_OF_SERVICE = 'Use with caution!'
app.config.API_PRODUCES_CONTENT_TYPES = ['application/json']
app.config.API_CONTACT_EMAIL = '[email protected]'
```

#### Including OpenAPI's host, basePath and security parameters

Just follow the OpenAPI 2.0 specification on this

``` python
app.config.API_HOST = 'subdomain.host.ext'
app.config.API_BASEPATH = '/v2/api/'

app.config.API_SECURITY = [
{
'authToken': []
}
]

app.config.API_SECURITY_DEFINITIONS = {
'authToken': {
'type': 'apiKey',
'in': 'header',
'name': 'Authorization',
'description': 'Paste your auth token and do not forget to add "Bearer " in front of it'
},
'OAuth2': {
'type': 'oauth2',
'flow': 'application',
'tokenUrl': 'https://your.authserver.ext/v1/token',
'scopes': {
'some_scope': 'Grants access to this API'
}
}
}

```

### Set responses for different HTTP status codes

```python
@app.get("/garage/<id>")
@doc.summary("Gets the whole garage")
@doc.produces(Garage)
@doc.response(404, {"message": str}, description="When the garage cannot be found")
async def get_garage(request, id):
garage = some_fetch_function(id)
return json(garage)
```
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
sanic==0.6.0
sanic==0.7.0
tox==2.7.0
3 changes: 1 addition & 2 deletions examples/cars/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from sanic import Sanic
from sanic_openapi import swagger_blueprint, openapi_blueprint
from sanic_openapi import swagger_blueprint
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

app = Sanic()

app.blueprint(openapi_blueprint)
app.blueprint(swagger_blueprint)
app.blueprint(car_blueprint)
app.blueprint(driver_blueprint)
Expand Down
5 changes: 2 additions & 3 deletions sanic_openapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from .openapi import blueprint as openapi_blueprint
from .swagger import blueprint as swagger_blueprint

__version__ = '0.4.0'
__all__ = ['openapi_blueprint', 'swagger_blueprint']
__version__ = '0.5.0'
__all__ = ['swagger_blueprint']
45 changes: 38 additions & 7 deletions sanic_openapi/doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 []
Expand Down Expand Up @@ -200,30 +215,34 @@ class RouteSpec(object):
blueprint = None
tags = None
exclude = None
response = None

def __init__(self):
self.tags = []
self.consumes = []
self.response = []
super().__init__()


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)


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]

Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
Loading

0 comments on commit 44e692d

Please sign in to comment.