├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── app.py ├── manifest.yml ├── parser.py ├── requirements.txt ├── runtime.txt ├── static ├── images │ ├── prev.png │ └── preview.png ├── specs │ ├── petstore.yaml │ └── uber.yaml └── stylesheets │ └── style.css └── templates ├── intro.html ├── layout.html └── spec.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | Status API Training Shop Blog About Pricing 61 | 62 | env 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Owain Lewis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: python app.py 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API DOC 2 | 3 | Generate professional looking, human readable documentation from Swagger. 4 | 5 | A demo can be found [here](https://swagger-docs.herokuapp.com/docs/uber) 6 | 7 | Currently this only supports Swagger YAML format, but RAML will be next. You can 8 | quickly customize any of the templates. 9 | 10 | Simply drop your Swagger specs in static/specs and off you go :) 11 | 12 | ![](https://raw.githubusercontent.com/owainlewis/apidoc/master/static/images/preview.png) 13 | 14 | ## Getting Started 15 | 16 | This app can use either Python 2 or 3 and is pretty much a standard 17 | Flask application. 18 | 19 | ```bash 20 | # Install deps 21 | sudo apt-get install python3-pip 22 | pip3 install -r requirements.txt 23 | # Run it 24 | python3 app.py 25 | # Visit localhost:5000 26 | ``` 27 | 28 | Update the templates or stylesheets to suite your needs. 29 | 30 | ## Deploy to Heroku 31 | 32 | ```bash 33 | heroku create mydocs 34 | git push heroku master 35 | ``` 36 | 37 | # License 38 | 39 | [MIT License](LICENSE) 40 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import Flask, render_template 4 | from parser import SwaggerParser 5 | from os import listdir 6 | from os.path import isfile, join 7 | 8 | app = Flask(__name__) 9 | 10 | SPEC_PATH = "static/specs" 11 | 12 | def spec_exists(spec): 13 | return isfile(join(SPEC_PATH, spec)) 14 | 15 | def load_specs(): 16 | return [f.replace('.yaml','') for f in listdir(SPEC_PATH) if spec_exists(f)] 17 | 18 | @app.route("/docs/") 19 | def spec(name): 20 | parser = SwaggerParser("static/specs/%s.yaml" % name) 21 | return render_template('spec.html', spec = parser.spec, paths = parser.paths()) 22 | 23 | @app.route("/") 24 | def index(): 25 | return render_template('intro.html', specs=load_specs()) 26 | 27 | PORT = int(os.getenv('PORT', '5000')) 28 | 29 | if __name__ == "__main__": 30 | app.run(host='0.0.0.0', port=PORT) 31 | -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: swagger-docs 4 | memory: 128M 5 | -------------------------------------------------------------------------------- /parser.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | 3 | class SwaggerParser: 4 | 5 | def __init__(self, file_path): 6 | self.spec = self.__load(file_path) 7 | 8 | def __load(self, file_path): 9 | with open(file_path, 'r') as stream: 10 | return yaml.load(stream) 11 | 12 | def info(self): 13 | return self.spec.get('info', {}) 14 | 15 | def version(self): 16 | return self.info().get('version') 17 | 18 | def title(self): 19 | return self.info().get('title') 20 | 21 | def host(self): 22 | return self.spec.get('host') 23 | 24 | def basePath(self): 25 | return self.spec.get('basePath') 26 | 27 | def consumes(self): 28 | return self.spec.get('consumes') 29 | 30 | def produces(self): 31 | return self.spec.get('produces') 32 | 33 | def paths(self): 34 | paths = self.spec.get('paths', {}) 35 | results = [] 36 | for path, operations in paths.items(): 37 | for method, info in operations.items(): 38 | data = info.copy() 39 | data.update({'path': path, 'method': method}) 40 | results.append(data) 41 | return results 42 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | itsdangerous==0.24 3 | Jinja2==2.8 4 | MarkupSafe==0.23 5 | PyYAML==3.11 6 | Werkzeug==0.11.2 7 | wheel==0.24.0 8 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.3.6 2 | -------------------------------------------------------------------------------- /static/images/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owainlewis/apidoc/317673eb921ac6d38510f85e327c1f14b69d3c40/static/images/prev.png -------------------------------------------------------------------------------- /static/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owainlewis/apidoc/317673eb921ac6d38510f85e327c1f14b69d3c40/static/images/preview.png -------------------------------------------------------------------------------- /static/specs/petstore.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: Swagger Petstore 5 | license: 6 | name: MIT 7 | host: petstore.swagger.io 8 | basePath: /v1 9 | schemes: 10 | - http 11 | consumes: 12 | - application/json 13 | produces: 14 | - application/json 15 | paths: 16 | /pets: 17 | get: 18 | summary: List all pets 19 | operationId: listPets 20 | tags: 21 | - pets 22 | parameters: 23 | - name: limit 24 | in: query 25 | description: How many items to return at one time (max 100) 26 | required: false 27 | type: integer 28 | format: int32 29 | responses: 30 | 200: 31 | description: An paged array of pets 32 | headers: 33 | x-next: 34 | type: string 35 | description: A link to the next page of responses 36 | schema: 37 | $ref: Pets 38 | examples: 39 | application/json: | 40 | examples: 41 | application/json: | 42 | { 43 | "object": "balance", 44 | "available": [ 45 | { 46 | "currency": "usd", 47 | "amount": 7466650034, 48 | "source_types": { 49 | "card": 7466650034, 50 | "bitcoin_receiver": 0 51 | } 52 | } 53 | ], 54 | "livemode": false, 55 | "pending": [ 56 | { 57 | "currency": "usd", 58 | "amount": 34177050, 59 | "source_types": { 60 | "card": 34176951, 61 | "bitcoin_receiver": 99 62 | } 63 | } 64 | ] 65 | } 66 | default: 67 | description: unexpected error 68 | schema: 69 | $ref: Error 70 | post: 71 | summary: Create a pet 72 | operationId: createPets 73 | tags: 74 | - pets 75 | responses: 76 | 201: 77 | description: Null response 78 | default: 79 | description: unexpected error 80 | schema: 81 | $ref: Error 82 | /pets/{petId}: 83 | get: 84 | summary: Info for a specific pet 85 | operationId: showPetById 86 | tags: 87 | - pets 88 | parameters: 89 | - name: petId 90 | in: path 91 | required: true 92 | description: The id of the pet to retrieve 93 | type: string 94 | responses: 95 | 200: 96 | description: Expected response to a valid request 97 | schema: 98 | $ref: Pets 99 | default: 100 | description: unexpected error 101 | schema: 102 | $ref: Error 103 | definitions: 104 | Pet: 105 | required: 106 | - id 107 | - name 108 | properties: 109 | id: 110 | type: integer 111 | format: int64 112 | name: 113 | type: string 114 | tag: 115 | type: string 116 | Pets: 117 | type: array 118 | items: 119 | $ref: Pet 120 | Error: 121 | required: 122 | - code 123 | - message 124 | properties: 125 | code: 126 | type: integer 127 | format: int32 128 | message: 129 | type: string 130 | -------------------------------------------------------------------------------- /static/specs/uber.yaml: -------------------------------------------------------------------------------- 1 | # this is an example of the Uber API 2 | # as a demonstration of an API spec in YAML 3 | swagger: "2.0" 4 | info: 5 | title: Uber API 6 | description: Move your app forward with the Uber API 7 | version: "1.0.0" 8 | # the domain of the service 9 | host: api.uber.com 10 | # array of all schemes that your API supports 11 | schemes: 12 | - https 13 | # will be prefixed to all paths 14 | basePath: /v1 15 | produces: 16 | - application/json 17 | paths: 18 | /products: 19 | get: 20 | summary: Product Types 21 | description: The Products endpoint returns information about the Uber products offered at a given location. The response includes the display name and other details about each product, and lists the products in the proper display order. 22 | parameters: 23 | - name: latitude 24 | in: query 25 | description: Latitude component of location. 26 | required: true 27 | type: number 28 | format: double 29 | - name: longitude 30 | in: query 31 | description: Longitude component of location. 32 | required: true 33 | type: number 34 | format: double 35 | tags: 36 | - Products 37 | responses: 38 | 200: 39 | description: An array of products 40 | schema: 41 | type: array 42 | items: 43 | $ref: Product 44 | examples: 45 | application/json: | 46 | examples: 47 | application/json: | 48 | { 49 | "object": "balance", 50 | "available": [ 51 | { 52 | "currency": "usd", 53 | "amount": 7466650034, 54 | "source_types": { 55 | "card": 7466650034, 56 | "bitcoin_receiver": 0 57 | } 58 | } 59 | ], 60 | "livemode": false, 61 | "pending": [ 62 | { 63 | "currency": "usd", 64 | "amount": 34177050, 65 | "source_types": { 66 | "card": 34176951, 67 | "bitcoin_receiver": 99 68 | } 69 | } 70 | ] 71 | } 72 | default: 73 | description: Unexpected error 74 | schema: 75 | $ref: Error 76 | /estimates/price: 77 | get: 78 | summary: Price Estimates 79 | description: The Price Estimates endpoint returns an estimated price range for each product offered at a given location. The price estimate is provided as a formatted string with the full price range and the localized currency symbol.

The response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code for situations requiring currency conversion. When surge is active for a particular product, its surge_multiplier will be greater than 1, but the price estimate already factors in this multiplier. 80 | parameters: 81 | - name: start_latitude 82 | in: query 83 | description: Latitude component of start location. 84 | required: true 85 | type: number 86 | format: double 87 | - name: start_longitude 88 | in: query 89 | description: Longitude component of start location. 90 | required: true 91 | type: number 92 | format: double 93 | - name: end_latitude 94 | in: query 95 | description: Latitude component of end location. 96 | required: true 97 | type: number 98 | format: double 99 | - name: end_longitude 100 | in: query 101 | description: Longitude component of end location. 102 | required: true 103 | type: number 104 | format: double 105 | tags: 106 | - Estimates 107 | responses: 108 | 200: 109 | description: An array of price estimates by product 110 | schema: 111 | type: array 112 | items: 113 | $ref: PriceEstimate 114 | default: 115 | description: Unexpected error 116 | schema: 117 | $ref: Error 118 | /estimates/time: 119 | get: 120 | summary: Time Estimates 121 | description: The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs. 122 | parameters: 123 | - name: start_latitude 124 | in: query 125 | description: Latitude component of start location. 126 | required: true 127 | type: number 128 | format: double 129 | - name: start_longitude 130 | in: query 131 | description: Longitude component of start location. 132 | required: true 133 | type: number 134 | format: double 135 | - name: customer_uuid 136 | in: query 137 | type: string 138 | format: uuid 139 | description: Unique customer identifier to be used for experience customization. 140 | - name: product_id 141 | in: query 142 | type: string 143 | description: Unique identifier representing a specific product for a given latitude & longitude. 144 | tags: 145 | - Estimates 146 | responses: 147 | 200: 148 | description: An array of products 149 | schema: 150 | type: array 151 | items: 152 | $ref: Product 153 | default: 154 | description: Unexpected error 155 | schema: 156 | $ref: Error 157 | /me: 158 | get: 159 | summary: User Profile 160 | description: The User Profile endpoint returns information about the Uber user that has authorized with the application. 161 | tags: 162 | - User 163 | responses: 164 | 200: 165 | description: Profile information for a user 166 | schema: 167 | $ref: Profile 168 | default: 169 | description: Unexpected error 170 | schema: 171 | $ref: Error 172 | /history: 173 | get: 174 | summary: User Activity 175 | description: The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.

The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary. 176 | parameters: 177 | - name: offset 178 | in: query 179 | type: integer 180 | format: int32 181 | description: Offset the list of returned results by this amount. Default is zero. 182 | - name: limit 183 | in: query 184 | type: integer 185 | format: int32 186 | description: Number of items to retrieve. Default is 5, maximum is 100. 187 | tags: 188 | - User 189 | responses: 190 | 200: 191 | description: History information for the given user 192 | schema: 193 | $ref: Activities 194 | default: 195 | description: Unexpected error 196 | schema: 197 | $ref: Error 198 | definitions: 199 | Product: 200 | properties: 201 | product_id: 202 | type: string 203 | description: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles. 204 | description: 205 | type: string 206 | description: Description of product. 207 | display_name: 208 | type: string 209 | description: Display name of product. 210 | capacity: 211 | type: string 212 | description: Capacity of product. For example, 4 people. 213 | image: 214 | type: string 215 | description: Image URL representing the product. 216 | PriceEstimate: 217 | properties: 218 | product_id: 219 | type: string 220 | description: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles 221 | currency_code: 222 | type: string 223 | description: "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." 224 | display_name: 225 | type: string 226 | description: Display name of product. 227 | estimate: 228 | type: string 229 | description: Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or "Metered" for TAXI. 230 | low_estimate: 231 | type: number 232 | description: Lower bound of the estimated price. 233 | high_estimate: 234 | type: number 235 | description: Upper bound of the estimated price. 236 | surge_multiplier: 237 | type: number 238 | description: Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier. 239 | Profile: 240 | properties: 241 | first_name: 242 | type: string 243 | description: First name of the Uber user. 244 | last_name: 245 | type: string 246 | description: Last name of the Uber user. 247 | email: 248 | type: string 249 | description: Email address of the Uber user 250 | picture: 251 | type: string 252 | description: Image URL of the Uber user. 253 | promo_code: 254 | type: string 255 | description: Promo code of the Uber user. 256 | Activity: 257 | properties: 258 | uuid: 259 | type: string 260 | description: Unique identifier for the activity 261 | Activities: 262 | properties: 263 | offset: 264 | type: integer 265 | format: int32 266 | description: Position in pagination. 267 | limit: 268 | type: integer 269 | format: int32 270 | description: Number of items to retrieve (100 max). 271 | count: 272 | type: integer 273 | format: int32 274 | description: Total number of items available. 275 | history: 276 | type: array 277 | $ref: Activity 278 | Error: 279 | properties: 280 | code: 281 | type: integer 282 | format: int32 283 | message: 284 | type: string 285 | fields: 286 | type: string 287 | -------------------------------------------------------------------------------- /static/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 15px; 3 | color: #576f7f; 4 | font-family: 'Lato', Helvetica, sans-serif; 5 | line-height: 1.6; 6 | } 7 | 8 | .hero { 9 | background: #fafcfc; 10 | width: 100%; 11 | position: fixed; 12 | height: 100%; 13 | z-index: -1; 14 | } 15 | 16 | body .container { 17 | padding: 24px; 18 | background: white; 19 | box-shadow: 0 0 5px #CCC; 20 | } 21 | 22 | a, a:hover { color: #0099e5; font-weight: bold; } 23 | 24 | h1 { font-size: 32px; } 25 | h2 { font-size: 24px; } 26 | h3 { font-size: 18px; } 27 | 28 | h1, h2, h3, h4 { color: #0e304b; font-weight: bold; } 29 | 30 | pre, code { font-size: 14px; } 31 | 32 | html, body { 33 | height: 100%; 34 | width: 100%; 35 | } 36 | 37 | table { width: 100%; } 38 | 39 | .intro table { margin: 48px 0; } 40 | 41 | tr { border-bottom: 1px solid #eee; } 42 | 43 | td { padding: 3px; } 44 | 45 | #sidebar { 46 | position: fixed; 47 | top: 0; 48 | height: 100%; 49 | width: 238px; 50 | background: #fafcfc; 51 | border-right: 2px solid #F0F4F7; 52 | padding: 0 24px; 53 | color: #777; 54 | } 55 | 56 | img { max-width: 100%; } 57 | 58 | #sidebar h4 { margin: 24px 0 0 0; } 59 | 60 | #sidebar ul { list-style: none; margin: 12px 0 0 0; padding: 0; } 61 | 62 | #sidebar li { margin: 6px 0; } 63 | 64 | #sidebar a { font-weight: normal; color: #666; } 65 | 66 | #sidebar a:hover { text-decoration: none; color: #0099e5; } 67 | 68 | #sidebar #logo { margin-bottom: 24px; font-size: 24px; } 69 | #sidebar #logo a { font-weight: bold; color: #333; } 70 | 71 | #main { 72 | margin: 0 0 0 240px; 73 | } 74 | 75 | .method { 76 | display: flex; 77 | height: 100%; 78 | overflow: hidden; 79 | position: relative; 80 | } 81 | 82 | .left, .right { 83 | min-height: 500px; 84 | padding: 24px 48px; 85 | 86 | } 87 | 88 | .left { 89 | border-bottom: 2px solid #eee; 90 | flex: 0 0 50%; 91 | } 92 | 93 | .right { 94 | flex: 0 0 50%; 95 | background: #002451; 96 | color: #d0d4d7; 97 | border-bottom: 2px solid #002451; 98 | } 99 | 100 | .right h1, .right h2, .right h3, .right h4 { color: #dde4e8; } 101 | 102 | .intro { 103 | padding: 48px 0; 104 | } 105 | 106 | .request { 107 | padding: 6px; 108 | margin: 24px 0; 109 | border: 2px solid #ddd; 110 | } 111 | 112 | pre { background: #002451; color: white; border: 1px solid rgba(100,100,100,0.5); } 113 | 114 | .notice { 115 | background: #E1F5FE; 116 | width: 100%; 117 | padding: 24px; 118 | margin: 24px 0; 119 | } 120 | -------------------------------------------------------------------------------- /templates/intro.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | 4 |
5 | 6 |

My documentation

7 |

This is an example splash page for your APIs. You can customize this page in any way you like

8 | 9 |

APIs

10 | 11 | 12 | {% for spec in specs %} 13 | 14 | 15 | 16 | {% endfor %} 17 |
{{ spec.title() }}
18 |
19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My API Docs 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | {% block body %}{% endblock %} 18 | 19 | 20 | -------------------------------------------------------------------------------- /templates/spec.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | 4 | 16 | 17 |
18 |
19 |
20 |

{{ spec.info.title }} v{{ spec.info.version }}

21 | 22 | {{ spec.info.description }} 23 | 24 |

Here is a hand written introduction to your API specs

25 | 26 |
27 | All requests to our API must be 28 | authenticated. This is some text you can change. 29 |
30 | 31 |
32 |
33 |

Service Endpoint

34 |

https://{{ spec.get('host', 'N/A') }}

35 |
36 |
37 | 38 | {% for operation in paths %} 39 |
40 |
41 |

{{ operation['summary'] }}

42 |

{{ operation['description'] }}

43 |
44 |

HTTP Request

45 | 46 | {{ operation['method'].upper() }} {{ operation['path'] }} 47 | 48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | {% for status, info in operation['responses'].items() %} 56 | 57 | 58 | 59 | 60 | {% endfor %} 61 |
StatusDescription
{{ status }}{{ info['description'] }}
62 |
63 | 64 |
65 | {% for status, info in operation['responses'].items() %} 66 | {% if info.get('examples', {}).get('application/json') %} 67 |

Example Response

68 |
69 |               
70 |                   {{info.get('examples').get('application/json')}}
71 |               
72 |               
73 | {% endif %} 74 | {% endfor %} 75 |
76 |
77 | {% endfor %} 78 | {% endblock %} 79 | --------------------------------------------------------------------------------