Using Swagger Client With App Engine
February 24, 2018
gae
appengine
swagger
Feels like Swagger is getting more traction every day as more and more companies are adopting it to spec out their APIs. That’s actually good! Swagger lets you get stuff done quickly both as a producer - build an API - and as a consumer - generate a Swagger client to consume that API. Easy, awesome and pretty straight forward! Except when you want to use that generated Swagger client in the App Engine to access that Swagger API.
App Engine Standard is a sandboxed environment. You can’t always do stuff the way you are used to. You have to play by their rules. But no need to worry! It’s actually pretty easy to solve once you know where to look.
Generate a Swagger client
In order to access a Swagger API you have to generate a Swagger client. Well, you actually don’t have to if you don’t want but it helps. Just trust me on this one. Lets generate a Petstore Swagger client in Python. NOTE: you might need to install Swagger code generator first.
$ swagger-codegen generate -l python \
-i http://petstore.swagger.io/v2/swagger.json \
-o petstore
The command will generate a Swagger client in Python and put it into petstore
directory.
Use Swagger client in the app
Lets take a simple Flask app for this example. To save us time I wrote a post on how to create a very simple Flask app on App Engine standard environment.
Swagger client is using urllib3 as
default http client library. There are two ways you can use urllib3
in Appengine
Standard: patch it to use App Engine’s URLFetch or enable sockets. Which one to use depends
on your situation and needs. URLFetch is more cost-effective as long as your
usage is within the limitations for free app. Sockets are only available for paid apps and
are in beta at the time of writing.
You can read more about various options here.
To be able to use urllib3 with URLFetch in AppEngine together with generated Swagger client you have to monkeypatch it.
import swagger_client
from urllib3.contrib.appengine import AppEngineManager
# create swagger client
client = swagger_client.PetApi()
# patch urllib3 for App Engine to use URLFetch
client.api_client.rest_client.pool_manager = AppEngineManager()
You can skip monkeypatching if you choose to go with sockets. Just add the following
lines to the app.yaml
.
env_variables:
GAE_USE_SOCKETS_HTTPLIB : 'true'
Here is the whole application file in its glory.
import json
from flask import Flask, request, jsonify, abort
from urllib3.contrib.appengine import AppEngineManager
# import our swagger client and friends
import swagger_client
from swagger_client.rest import ApiException
app = Flask(__name__)
app.debug = True
# create swagger client
client = swagger_client.PetApi()
# patch urllib3 for App Engine to use URLFetch
client.api_client.rest_client.pool_manager = AppEngineManager()
def jsonify_api_exception(error):
body = json.loads(error.body)
err = {'status_code': error.status, 'message': body['message']}
response = jsonify(err)
response.status_code = err['status_code']
return response
@app.route('/')
def index():
return 'hello from google app engine with swagger client'
@app.route('/pets.byId')
def pet_get():
# get pet id from query string
pet_id = request.args.get('id')
try:
resp = client.get_pet_by_id(pet_id)
return jsonify(resp.to_dict())
except ApiException as e:
# naive exception handling
print "Error calling PetApi.get_pet_by_id: %s\n" % e.status
return jsonify_api_exception(e)
Lets test it!
Lets start the local development server and try to fetch a few pets by id. Note that Swagger petstore is a public API and therefore there are no guarantees that the ids in examples below work.
# in one terminal window
$ dev_appserver.py --log_level debug .
# in another terminal window
$ curl localhost:8080/pets.byId?id=55
$ curl localhost:8080/pets.byId?id=87356
Conclusion
App Engine Standard is a restricted sanboxed environment. In there we have to play by their rules. Sometimes it’s just not that clear what those rules are and where to find them. But once we know them it’s pretty easy to find a way to adjust our code to work with them.