commit cf16476a0f33f460117598ad3366a08742b0d842 Author: hellerve Date: Wed Dec 12 13:46:03 2018 +0100 make it so diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa1235f --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# API builder + +A simple script to autmatically generate Python API clients from JSON-based +Swagger descriptions. Works with a few selected Swagger definitions, but I +haven’t tested it on many different formats. Very much just a Proof of Concept +that happens to be useful. + +
+ +Have fun! diff --git a/api_builder.py b/api_builder.py new file mode 100644 index 0000000..5e43be1 --- /dev/null +++ b/api_builder.py @@ -0,0 +1,71 @@ +import re +import sys + +import inflection +import requests + + +with open('prelude.py') as f: + prelude = f.read() + + + +reserved = dir(__builtins__) + + +def download(url): + return requests.get(url).json() + + +def mkname(method, name, params): + singular = inflection.singularize(name) + if method == 'get' and 'id_' not in params: + return 'list_{}'.format(name) + elif method == 'get': + return singular + elif method == 'post': + return 'create_{}'.format(singular) + elif method == 'put' or method == 'PATCH': + return 'update_{}'.format(singular) + elif method == 'delete': + return 'delete_{}'.format(singular) + return name + + +def mkparam(param): + param = inflection.underscore(param) + if param in reserved: + return "{}_".format(param) + return param + + +def pretty_print(name, data): + info = data['info'] + r = re.compile('\\/\\{([^\\}]*)\\}') + r2 = re.compile('\\{([^\\}]*)\\}') + print(prelude) + print('class {}(Client):'.format(name)) + print(' """{}\n\n {}"""'.format(info['title'], info['description'])) + for path, fns in data['paths'].items(): + for method, body in fns.items(): + method = method.lower() + fmt = r2.sub('{}', path) + params = [mkparam(param) for param in r.findall(path)] + name = r.sub('', path).replace("/", "_").replace('-', '_')[1:] + name = mkname(method, name, params) + params = ", {}".format(", ".join(params)) if params else '' + print(' def {}(self{}):'.format(name, params)) + print(' """{}"""'.format(', '.join(body['tags']))) + print(' return self.{}("{}"{})\n'.format(method, fmt, params)) + + +def generate(name, url): + data = download(url) + pretty_print(name, data) + + +if __name__ == '__main__': + if len(sys.argv) != 3: + print("usage: {} ".format(sys.argv[0])) + sys.exit(1) + generate(sys.argv[1], sys.argv[2]) diff --git a/prelude.py b/prelude.py new file mode 100644 index 0000000..b564ba6 --- /dev/null +++ b/prelude.py @@ -0,0 +1,83 @@ +import requests + +class Client(): + """ + A generic API client. + """ + + def __init__(self, url, verify=True, auth=None): + """ + Constructs a new Client object. + """ + self.url = url + self.verify = verify + self.auth = auth + + def request(self, scheme, url, data=None, params=None): + """ + Low-level request interface to client. Takes a HTTP request scheme (lower + case!), a URL to request (relative), and optionally data to add to the + request. Either returns the JSON body of the request or raises a + HttpException. + """ + url = self.url + url + headers = { + "User-Agent": + "Autogenerated API wrapper (generated by api_builder.py)", + "Content-Type": "application/json", + } + # this is a nice little hack to make the API nicer + # we pass the scheme as string, but have it as attributes in requests + fn = requests.__getattribute__(scheme) + + res = fn(url, headers=headers, json=data, params=params, auth=self.auth) + + res.raise_for_status() + + if not res.content: + return None + + try: + return res.json() + except ValueError: + return res.content + + def get(self, url, data=None, params=None): + """ + Low-level GET request interface to client. Takes a URL to request + (relative), and optionally data to add to the request. Either returns + the JSON body of the request or raises a HttpException. + """ + return self.request("get", url, data, params) + + def put(self, url, data=None, params=None): + """ + Low-level PUT request interface to client. Takes a URL to request + (relative), and optionally data to add to the request. Either returns + the JSON body of the request or raises a HttpException. + """ + return self.request("put", url, data, params) + + def post(self, url, data=None, params=None): + """ + Low-level POST request interface to client. Takes a URL to request + (relative), and optionally data to add to the request. Either returns + the JSON body of the request or raises a HttpException. + """ + return self.request("post", url, data, params) + + def patch(self, url, data=None, params=None): + """ + Low-level PATCH request interface to client. Takes a URL to request + (relative), and optionally data to add to the request. Either returns + the JSON body of the request or raises a HttpException. + """ + return self.request("patch", url, data, params) + + def delete(self, url, data=None, params=None): + """ + Low-level DELETE request interface to client. Takes a URL to request + (relative), and optionally data to add to the request. Either returns + the JSON body of the request or raises a HttpException. + """ + return self.request("delete", url, data, params) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e41fa7c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +inflection +requests