Last active
December 22, 2017 22:31
-
-
Save deeplook/fdb466008695c0982930383ac7ee3ef7 to your computer and use it in GitHub Desktop.
Explore a RESTful Swagger/OpenAPI specification (JSON file) with Swagger-UI in a browser.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| """ | |
| Explore a RESTful Swagger/OpenAPI specification with Swagger-UI in a browser. | |
| Features: | |
| - download a recent Swagger-UI release | |
| - extract its "dist" folder | |
| - copy desired JSON file into "dist" folder | |
| - modify "dist/index.html" to point to this JSON file | |
| - run a local webserver serving static content from "dist" | |
| - open a browser to show the JSON file inside Swagger-UI | |
| - continue to browse the API in Swagger-UI after terminating this script | |
| (and the webserver it runs) | |
| Run like this: | |
| $ python browse_rest_api.py --verbose my_swagger_spec.json | |
| downloading https://github.com/swagger-api/swagger-ui/archive/v3.5.0.zip \ | |
| to /Users/gherman/Downloads/v3.5.0.zip | |
| extracting swagger-ui-3.5.0/dist from /Users/gherman/Downloads/v3.5.0.zip | |
| copying my_swagger_spec.json to swagger-ui-3.5.0/dist/ | |
| replacing start URL in swagger-ui-3.5.0/dist/index.html | |
| starting webserver on http://localhost:8080 | |
| opening webbrowser on http://localhost:8080 | |
| 127.0.0.1 - - [29/Nov/2017 00:07:23] "GET / HTTP/1.1" 200 - | |
| 127.0.0.1 - - [29/Nov/2017 00:07:23] "GET /swagger-ui.css HTTP/1.1" 200 - | |
| 127.0.0.1 - - [29/Nov/2017 00:07:23] "GET /swagger-ui-standalone-preset.js HTTP/1.1" 200 - | |
| 127.0.0.1 - - [29/Nov/2017 00:07:24] "GET /swagger-ui-bundle.js HTTP/1.1" 200 - | |
| 127.0.0.1 - - [29/Nov/2017 00:07:24] "GET /my_swagger_spec.json HTTP/1.1" 200 - | |
| ^C | |
| terminating webserver | |
| (Swagger-UI should still work, but not reloading the page!) | |
| Developed and tested on Python 3.6 and MacOS 10.12.6. | |
| """ | |
| import os | |
| import re | |
| import sys | |
| import time | |
| import shutil | |
| import zipfile | |
| import webbrowser | |
| import argparse | |
| from multiprocessing import Process | |
| from http.server import HTTPServer, SimpleHTTPRequestHandler | |
| from os.path import expanduser, expandvars, exists, join, basename | |
| import requests | |
| __author__ = "Dinu Gherman" | |
| __license__ = "GPL3" | |
| # FIXME: find a free port | |
| PORT = 8080 | |
| def run_webui_server(root_path): | |
| """ | |
| Run simple HTTP server from some root path. | |
| """ | |
| os.chdir(root_path) | |
| server_address = ('', PORT) | |
| handler = SimpleHTTPRequestHandler | |
| httpd = HTTPServer(server_address, handler) | |
| try: | |
| httpd.serve_forever() | |
| except KeyboardInterrupt: | |
| pass | |
| def open_browser(url): | |
| """ | |
| Open given URL with default browser (to be called from a different process). | |
| """ | |
| webbrowser.open_new(url) | |
| class OpenApiBrowser(object): | |
| """ | |
| Browser a local API specification (JSON file) with default browser. | |
| """ | |
| def __init__(self, json_path, verbose=True): | |
| self.json_path = json_path | |
| self.verbose = verbose | |
| def download(self, url): | |
| """ | |
| Download a zipped release of Swagger-UI. | |
| """ | |
| zip_path = expandvars(join("${HOME}", "Downloads", basename(url))) | |
| if exists(zip_path): | |
| if self.verbose: | |
| print("found {}".format(zip_path)) | |
| else: | |
| if self.verbose: | |
| print("downloading {} to {}".format(url, zip_path)) | |
| content = requests.get(url).content | |
| open(zip_path, "wb").write(content) | |
| return zip_path | |
| def extract_ui(self, zip_path, dist_path): | |
| """ | |
| Unpack the Swagger-UI folder only from downloaded ZIP. | |
| """ | |
| if self.verbose: | |
| print("extracting {} from {}".format(dist_path, zip_path)) | |
| zf = zipfile.ZipFile(zip_path) | |
| members = zf.infolist() | |
| for m in members: | |
| if m.filename.startswith(dist_path): | |
| zf.extract(m) | |
| def inject_spec_file(self, dist_path): | |
| """ | |
| Copy Swagger/OpenAPI specification JSON file into Swagger-UI folder. | |
| """ | |
| if self.verbose: | |
| print("copying {} to {}".format(self.json_path, dist_path + '/')) | |
| shutil.copy(self.json_path, dist_path + '/') | |
| def modify_start_url(self, index_path): | |
| """ | |
| Modify the default URL in index.html to point to given specification file. | |
| """ | |
| if self.verbose: | |
| print("replacing start URL in {} ".format(index_path)) | |
| html = open(index_path).read() | |
| default_url = "http://petstore.swagger.io/v2/swagger.json" | |
| new_url = "http://localhost:" + \ | |
| str(PORT) + "/" + basename(self.json_path) | |
| html1 = re.sub(default_url, new_url, html) | |
| open(index_path, "w").write(html1) | |
| def run_webui_process(self, address, path): | |
| """ | |
| Run a webserver showing the Swagger-UI at some path. | |
| """ | |
| if self.verbose: | |
| print("starting webserver on {}".format(address)) | |
| p1 = Process(target=run_webui_server, args=(path,)) | |
| p1.start() | |
| return p1 | |
| def open_webui(self, address): | |
| """ | |
| Open the Swagger-UI in a webbrowser. | |
| """ | |
| if self.verbose: | |
| print("opening webbrowser on {}".format(address)) | |
| p2 = Process(target=open_browser, args=(address,)) | |
| p2.start() | |
| return p2 | |
| def wait_until_interrupted(self, p1, p2): | |
| """ | |
| Wait until next KeyboardInterrupt, then exit the completed processes. | |
| """ | |
| try: | |
| p1.join() | |
| p2.join() | |
| except KeyboardInterrupt: | |
| pass | |
| if self.verbose: | |
| print() | |
| print("terminating webserver") | |
| print("(Swagger-UI should still work, but not reloading the page!)") | |
| def open(self, url): | |
| """ | |
| Do all the necessary work. | |
| """ | |
| zip_path = self.download(url) | |
| # FIXME: find this by sniffing into zipfile... | |
| dist_path = "swagger-ui-3.5.0/dist" | |
| self.extract_ui(zip_path, dist_path) | |
| # FIXME: put this into a temporary folder? | |
| # put the json_path file into swagger-ui-3.5.0/dist | |
| self.inject_spec_file(dist_path) | |
| self.modify_start_url(dist_path + "/index.html") | |
| # set-up two processes to run in parallel, one for running a webserver, | |
| # the other for opening a webbrowser showing what the first is serving | |
| address = "http://localhost:" + str(PORT) | |
| p1 = self.run_webui_process(address, dist_path) | |
| time.sleep(0.5) | |
| p2 = self.open_webui(address) | |
| self.wait_until_interrupted(p1, p2) | |
| if __name__ == '__main__': | |
| desc = 'Explore a RESTful API spec. (JSON file) with Swagger-UI in a browser.' | |
| p = argparse.ArgumentParser(description=desc) | |
| p.add_argument('-V', '--verbose', | |
| action='store_true', | |
| help='Print additional information on standard out.') | |
| p.add_argument('path', | |
| metavar='PATH', | |
| help='Path of JSON input file with Swagger/OpenAPI specification.') | |
| args = p.parse_args() | |
| url = "https://github.com/swagger-api/swagger-ui/archive/v3.5.0.zip" | |
| browser = OpenApiBrowser(args.path, verbose=args.verbose) | |
| browser.open(url) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment