Skip to content

Commit

Permalink
merge: add class-based views in swagger.py
Browse files Browse the repository at this point in the history
  • Loading branch information
fMeow committed May 16, 2019
2 parents df7724f + f82af54 commit 36c72ca
Show file tree
Hide file tree
Showing 23 changed files with 556 additions and 476 deletions.
17 changes: 17 additions & 0 deletions .github/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 90
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 30
# Issues with these labels will never be considered stale
exemptLabels:
- bug
- urgent
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. If this
is incorrect, please respond with an update. Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
79 changes: 73 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Sanic OpenAPI

[![Build Status](https://travis-ci.org/channelcat/sanic-openapi.svg?branch=master)](https://travis-ci.org/channelcat/sanic-openapi)
[![Build Status](https://travis-ci.org/huge-success/sanic-openapi.svg?branch=master)](https://travis-ci.org/huge-success/sanic-openapi)
[![PyPI](https://img.shields.io/pypi/v/sanic-openapi.svg)](https://pypi.python.org/pypi/sanic-openapi/)
[![PyPI](https://img.shields.io/pypi/pyversions/sanic-openapi.svg)](https://pypi.python.org/pypi/sanic-openapi/)

Expand All @@ -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 = 'channelcat@gmail.com'
```

#### 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,5 +1,5 @@
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
Expand All @@ -8,7 +8,6 @@

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.3'
__all__ = ['swagger_blueprint']
48 changes: 40 additions & 8 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 All @@ -105,7 +120,8 @@ def serialize(self):
items = serialize_schema(self.items[0])
return {
"type": "array",
"items": items
"items": items,
**super().serialize()
}


Expand Down Expand Up @@ -200,30 +216,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 +261,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 +295,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 36c72ca

Please sign in to comment.