Skip to content

Instantly share code, notes, and snippets.

@dnordberg
Created May 27, 2013 20:30
Show Gist options
  • Save dnordberg/5658936 to your computer and use it in GitHub Desktop.
Save dnordberg/5658936 to your computer and use it in GitHub Desktop.
Script used to help generate Swagger docs.
import re
import os
import argparse
import urlparse
import simplejson
from collections import defaultdict
from sqlalchemy.ext.declarative.api import DeclarativeMeta
SKIP_MODELS = ['BlogPost', 'BlogCategory', 'DBVersion']
SKIP_ROUTES = ['blog_post', 'blog_category', 'db_version']
api_docs_path = './docs/api/'
SWAGGER_VERSION = "1.1"
if not os.path.exists(api_docs_path):
os.makedirs(api_docs_path)
# Dict tree
def tree(): return defaultdict(tree)
def dicts(t): return {k: dicts(t[k]) for k in t}
def add(t, keys):
for key in keys:
t = t[key]
class APIDocumentationManager(object):
def __init__(self, app, tables, base_path, version, docs={}):
self.app = app
self.base_path = base_path
self.version = version
path_info = urlparse.urlsplit(base_path)
self.rel_path = path_info.path
self.models = self.get_models(tables)
self.resources = self.get_resources()
def get_models(self, mod):
models = dict([(name, cls)
for name, cls in mod.__dict__.items()
if hasattr(cls, '__table__')
and name not in SKIP_MODELS])
json_models = {}
for name, model in models.iteritems():
json_models[name] = {}
json_models[name]["id"] = name
properties = model.__table__.columns.items()
json_properties = {}
for propkey, prop in properties:
json_properties[propkey] = {"type": str(prop.type)}
json_models[name]["properties"] = json_properties
return json_models
def get_resources(self):
routes_by_resource = defaultdict(list)
for route in self.app.url_map.iter_rules():
if not route.rule.startswith(self.rel_path) \
or 'api-docs' in route.rule:
continue
url = route.rule.replace(self.rel_path, '')
parent = url.split('/')[0]
if not parent in SKIP_ROUTES:
routes_by_resource[parent].append(route)
return routes_by_resource
def update_index(self):
"""Writes a file api_docs_index.json and adds missing routes."""
api_routes = []
for resource, routes in self.resources.iteritems():
if not resource in SKIP_ROUTES:
api_routes.append({
'path': "/api-docs/{}".format(resource),
'description': 'The {} resource'.format(resource.capitalize())
})
with open('./docs/api/index.json', 'w') as api_docs_index:
simplejson.dump({"apiVersion": self.version,
"swaggerVersion": SWAGGER_VERSION,
"basePath": self.base_path,
"apis": api_routes,
},
api_docs_index,
indent=' ')
print "Updated index"
def update_route_spec(self):
for resource, routes in self.resources.iteritems():
api_routes = []
for route in routes:
operations = []
arguments = route.arguments # set
endpoint = route.endpoint # string
methods = route.methods # set
rule = route.rule # string
for method in methods:
response_class = resource.capitalize()
skip_params = False
if method == "GET":
if 'instid' in rule:
summary = 'Find {} by ID'.format(resource)
notes = 'Returns a {} based on ID'.format(resource)
else:
summary = 'Find {} instances'.format(resource)
notes = 'Returns {} instances'.format(resource)
skip_params = True
elif method == 'DELETE':
summary = 'Delete {} by ID'.format(resource)
notes = 'Returns 204 NO CONTENT'
response_class = 'void'
elif method == 'PUT' or method == 'PATCH':
summary = 'Update {} by ID'.format(resource)
notes = 'Returns the updated instance'
elif method == 'POST':
summary = 'Create {} by ID'.format(resource)
notes = 'Returns the created instance ID'
response_class = resource.capitalize()
else:
continue
parameters = []
if not skip_params:
for param in arguments:
description = ""
data_type = "string"
if param == "instid":
description = "Instance id"
parameters.append({
"name": param,
"description": description,
"paramType": "path",
"allowMultiple": False,
"dataType": data_type
})
error_response = [
{
"code": 400,
"reason": "Invalid ID supplied"
},
{
"code": 404,
"reason": "Not found"
},
{
"code":405,
"reason":"Validation exception"
}
]
operation = {
"httpMethod": method,
"summary": summary,
"notes": notes,
"responseClass": ''.join([s.capitalize() for s in response_class.split('_')]),
"nickname": endpoint.replace('.', '_'),
"parameters": parameters,
"errorResponse": error_response
}
operations.append(operation)
api_routes.append({
"path": route.rule.replace(self.rel_path, '/'),
"description": "Operations about {}".format(resource),
"operations": operations
})
route_spec = {
"swaggerVersion": SWAGGER_VERSION,
"basePath": self.base_path,
"resourcePath": '/{}'.format(resource),
"models": self.models,
"apis": api_routes,
}
with open('./docs/api/{}.json'.format(resource), 'w') as api_doc:
simplejson.dump(route_spec,
api_doc,
indent=' ')
print "Updated route spec {}".format(resource)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--update-index', action='store_true',
help='Updates index page with API routes.')
parser.add_argument('--update-route-spec', action='store_true',
help='Updates route spec for specified route parent.')
parser.add_argument('--base-path', type=str, default=None,
help='Base API path.')
parser.add_argument('--version', type=str, default=None,
help='API version.')
parser.add_argument('--app-module', type=str, default=None,
help='App module.')
parser.add_argument('--tables-module', type=str, default=None,
help='Tables module.')
args = parser.parse_args()
parts = args.app_module.split('.')
mod = parts[:-1]
ins = parts[-1]
app_module = __import__('.'.join(mod), {}, {}, [mod[-1]])
app = getattr(app_module, ins)
tables = __import__(args.tables_module, {}, {}, [args.tables_module])
documentationmanager = APIDocumentationManager(app, tables, args.base_path, args.version)
if args.update_index:
documentationmanager.update_index()
if args.update_route_spec:
documentationmanager.update_route_spec()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment