implemented tornado-based client. (#6367)

This commit is contained in:
Yusuke Tsutsumi 2017-08-28 00:10:06 -07:00 committed by wing328
parent 17b71f1707
commit 8bdaf493dd
5 changed files with 295 additions and 1 deletions

31
bin/python-tornado-petstore.sh Executable file
View File

@ -0,0 +1,31 @@
#!/bin/sh
SCRIPT="$0"
while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done
if [ ! -d "${APP_DIR}" ]; then
APP_DIR=`dirname "$SCRIPT"`/..
APP_DIR=`cd "${APP_DIR}"; pwd`
fi
executable="./modules/swagger-codegen-cli/target/swagger-codegen-cli.jar"
if [ ! -f "$executable" ]
then
mvn clean package
fi
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/swagger-codegen/src/main/resources/python -i modules/swagger-codegen/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml -l python -o samples/client/petstore/python-tornado -DpackageName=petstore_api --library tornado $@"
java $JAVA_OPTS -jar $executable $ags

View File

@ -130,6 +130,7 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig
supportedLibraries.put("urllib3", "urllib3-based client");
supportedLibraries.put("asyncio", "Asyncio-based client (python 3.5+)");
supportedLibraries.put("tornado", "tornado-based client");
CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use");
libraryOption.setDefault(DEFAULT_LIBRARY);
cliOptions.add(libraryOption);
@ -216,6 +217,9 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig
if ("asyncio".equals(getLibrary())) {
supportingFiles.add(new SupportingFile("asyncio/rest.mustache", swaggerFolder, "rest.py"));
additionalProperties.put("asyncio", "true");
} else if ("tornado".equals(getLibrary())) {
supportingFiles.add(new SupportingFile("tornado/rest.mustache", swaggerFolder, "rest.py"));
additionalProperties.put("tornado", "true");
} else {
supportingFiles.add(new SupportingFile("rest.mustache", swaggerFolder, "rest.py"));
}

View File

@ -7,6 +7,9 @@ import re
import json
import mimetypes
import tempfile
{{#tornado}}
import tornado.gen
{{/tornado}}
from multiprocessing.pool import ThreadPool
from datetime import date, datetime
@ -81,6 +84,9 @@ class ApiClient(object):
def set_default_header(self, header_name, header_value):
self.default_headers[header_name] = header_value
{{#tornado}}
@tornado.gen.coroutine
{{/tornado}}
{{#asyncio}}async {{/asyncio}}def __call_api(self, resource_path, method,
path_params=None, query_params=None, header_params=None,
body=None, post_params=None, files=None,
@ -134,7 +140,7 @@ class ApiClient(object):
url = self.configuration.host + resource_path
# perform request and return response
response_data = {{#asyncio}}await {{/asyncio}}self.request(method, url,
response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request(method, url,
query_params=query_params,
headers=header_params,
post_params=post_params, body=body,

View File

@ -21,6 +21,9 @@ REQUIRES = ["urllib3 >= 1.15", "six >= 1.10", "certifi", "python-dateutil"]
{{#asyncio}}
REQUIRES.append("aiohttp")
{{/asyncio}}
{{#tornado}}
REQUIRES.append("tornado")
{{/tornado}}
setup(
name=NAME,

View File

@ -0,0 +1,250 @@
# coding: utf-8
{{>partial_header}}
import io
import json
import ssl
import certifi
import logging
import re
import tornado
import tornado.gen
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
# python 2 and python 3 compatibility library
from six import PY3
from six.moves.urllib.parse import urlencode
from urllib3.filepost import encode_multipart_formdata
logger = logging.getLogger(__name__)
class RESTResponse(io.IOBase):
def __init__(self, resp, data):
self.tornado_response = resp
self.status = resp.code
self.reason = resp.reason
self.data = data
def getheaders(self):
"""
Returns a CIMultiDictProxy of the response headers.
"""
return self.tornado_response.headers
def getheader(self, name, default=None):
"""
Returns a given response header.
"""
return self.tornado_response.headers.get(name, default)
class RESTClientObject:
def __init__(self, configuration, pools_size=4, maxsize=4):
# maxsize is the number of requests to host that are allowed in parallel
# ca_certs vs cert_file vs key_file
# http://stackoverflow.com/a/23957365/2985775
# ca_certs
if configuration.ssl_ca_cert:
ca_certs = configuration.ssl_ca_cert
else:
# if not set certificate file, use Mozilla's root certificates.
ca_certs = certifi.where()
self.ssl_context = ssl_context = ssl.SSLContext()
if configuration.cert_file:
ssl_context.load_cert_chain(
configuration.cert_file, keyfile=configuration.key_file
)
self.proxy_port = self.proxy_host = None
# https pool manager
if configuration.proxy:
self.proxy_port = 80
self.proxy_host = configuration.proxy
self.pool_manager = AsyncHTTPClient()
@tornado.gen.coroutine
def request(self, method, url, query_params=None, headers=None,
body=None, post_params=None, _preload_content=True, _request_timeout=None):
"""
:param method: http request method
:param url: http request url
:param query_params: query parameters in the url
:param headers: http request headers
:param body: request json body, for `application/json`
:param post_params: request post parameters,
`application/x-www-form-urlencoded`
and `multipart/form-data`
:param _preload_content: this is a non-applicable field for the AiohttpClient.
:param _request_timeout: timeout setting for this request. If one number provided, it will be total request
timeout. It can also be a pair (tuple) of (connection, read) timeouts.
"""
method = method.upper()
assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', 'PATCH', 'OPTIONS']
if post_params and body:
raise ValueError(
"body parameter cannot be used with post_params parameter."
)
request = HTTPRequest(url)
request.ssl_context = self.ssl_context
request.proxy_host = self.proxy_host
request.proxy_port = self.proxy_port
request.method = method
if headers:
request.headers = headers
if 'Content-Type' not in headers:
request.headers['Content-Type'] = 'application/json'
request.request_timeout = _request_timeout or 5 * 60
post_params = post_params or {}
if query_params:
request.url += '?' + urlencode(query_params)
# For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
if re.search('json', headers['Content-Type'], re.IGNORECASE):
if body:
body = json.dumps(body)
request.body = body
elif headers['Content-Type'] == 'application/x-www-form-urlencoded':
request.body = urlencode(post_params)
# TODO: transform to multipart form
elif headers['Content-Type'] == 'multipart/form-data':
request.body = encode_multipart_formdata(post_params)
# Pass a `bytes` parameter directly in the body to support
# other content types than Json when `body` argument is provided
# in serialized form
elif isinstance(body, bytes):
request.body = body
else:
# Cannot generate the request from given parameters
msg = """Cannot prepare a request message for provided arguments.
Please check that your arguments match declared content type."""
raise ApiException(status=0, reason=msg)
r = yield self.pool_manager.fetch(request)
r = RESTResponse(r, r.body)
# log response body
logger.debug("response body: %s", r.data)
if not 200 <= r.status <= 299:
raise ApiException(http_resp=r)
return r
@tornado.gen.coroutine
def GET(self, url, headers=None, query_params=None, _preload_content=True, _request_timeout=None):
result = yield self.request("GET", url,
headers=headers,
_preload_content=_preload_content,
_request_timeout=_request_timeout,
query_params=query_params)
raise tornado.gen.Return(result)
@tornado.gen.coroutine
def HEAD(self, url, headers=None, query_params=None, _preload_content=True, _request_timeout=None):
result = yield self.request("HEAD", url,
headers=headers,
_preload_content=_preload_content,
_request_timeout=_request_timeout,
query_params=query_params)
raise tornado.gen.Return(result)
@tornado.gen.coroutine
def OPTIONS(self, url, headers=None, query_params=None, post_params=None, body=None, _preload_content=True,
_request_timeout=None):
result = yield self.request("OPTIONS", url,
headers=headers,
query_params=query_params,
post_params=post_params,
_preload_content=_preload_content,
_request_timeout=_request_timeout,
body=body)
raise tornado.gen.Return(result)
@tornado.gen.coroutine
def DELETE(self, url, headers=None, query_params=None, body=None, _preload_content=True, _request_timeout=None):
result = yield self.request("DELETE", url,
headers=headers,
query_params=query_params,
_preload_content=_preload_content,
_request_timeout=_request_timeout,
body=body)
raise tornado.gen.Return(result)
@tornado.gen.coroutine
def POST(self, url, headers=None, query_params=None, post_params=None, body=None, _preload_content=True,
_request_timeout=None):
result = yield self.request("POST", url,
headers=headers,
query_params=query_params,
post_params=post_params,
_preload_content=_preload_content,
_request_timeout=_request_timeout,
body=body)
raise tornado.gen.Return(result)
@tornado.gen.coroutine
def PUT(self, url, headers=None, query_params=None, post_params=None, body=None, _preload_content=True,
_request_timeout=None):
result = yield self.request("PUT", url,
headers=headers,
query_params=query_params,
post_params=post_params,
_preload_content=_preload_content,
_request_timeout=_request_timeout,
body=body)
raise tornado.gen.Return(result)
@tornado.gen.coroutine
def PATCH(self, url, headers=None, query_params=None, post_params=None, body=None, _preload_content=True,
_request_timeout=None):
result = yield self.request("PATCH", url,
headers=headers,
query_params=query_params,
post_params=post_params,
_preload_content=_preload_content,
_request_timeout=_request_timeout,
body=body)
raise tornado.gen.Return(result)
class ApiException(Exception):
def __init__(self, status=None, reason=None, http_resp=None):
if http_resp:
self.status = http_resp.status
self.reason = http_resp.reason
self.body = http_resp.data
self.headers = http_resp.getheaders()
else:
self.status = status
self.reason = reason
self.body = None
self.headers = None
def __str__(self):
"""
Custom error messages for exception
"""
error_message = "({0})\n"\
"Reason: {1}\n".format(self.status, self.reason)
if self.headers:
error_message += "HTTP response headers: {0}\n".format(self.headers)
if self.body:
error_message += "HTTP response body: {0}\n".format(self.body)
return error_message