From a09ad6bfe74bb761cbda375a4f6bea196fe7d711 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Sat, 7 Mar 2015 11:14:21 +0100 Subject: [PATCH] Simplify API service by using Flask-Restless --- contrib/test_api.py | 56 +++++++++ openstack_application_tutorial/api.py | 129 ++++----------------- openstack_application_tutorial/producer.py | 20 ++-- openstack_application_tutorial/tracker.py | 8 +- openstack_application_tutorial/worker.py | 14 +-- requirements.txt | 3 +- 6 files changed, 98 insertions(+), 132 deletions(-) create mode 100755 contrib/test_api.py diff --git a/contrib/test_api.py b/contrib/test_api.py new file mode 100755 index 0000000..d49cfba --- /dev/null +++ b/contrib/test_api.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import requests + +url = 'http://127.0.0.1:5000/api/fractal' +headers = {'Content-Type': 'application/json'} + +uuid = '13bf15a8-9f6c-4d59-956f-7d20f7484687' +data = { + 'uuid': uuid, + 'width': 100, + 'height': 100, + 'iterations': 10, + 'xa': 1.0, + 'xb': -1.0, + 'ya': 1.0, + 'yb': -1.0, +} +response = requests.post(url, data=json.dumps(data), headers=headers) +assert response.status_code == 201 + +response = requests.get(url, headers=headers) +assert response.status_code == 200 +print(response.json()) + +response = requests.get(url + '/' + uuid, headers=headers) +assert response.status_code == 200 +print(response.json()) + +data = { + 'checksum': 'c6fef4ef13a577066c2281b53c82ce2c7e94e', + 'duration': 10.12 +} +response = requests.put(url + '/' + uuid, data=json.dumps(data), + headers=headers) +assert response.status_code == 200 + +response = requests.get(url + '/' + uuid, headers=headers) +assert response.status_code == 200 +print(response.json()) + +response = requests.delete(url + '/' + uuid, headers=headers) +assert response.status_code == 204 diff --git a/openstack_application_tutorial/api.py b/openstack_application_tutorial/api.py index e27c011..1c49aa4 100644 --- a/openstack_application_tutorial/api.py +++ b/openstack_application_tutorial/api.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -12,11 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. -import json import sys import flask -from flask.ext.sqlalchemy import SQLAlchemy +import flask.ext.restless +import flask.ext.sqlalchemy from oslo_config import cfg from oslo_log import log @@ -28,8 +26,11 @@ cli_opts = [ cfg.StrOpt('listen-address', default='0.0.0.0', help='Listen address.'), + cfg.IntOpt('bind-port', + default='5000', + help='Bind port.'), cfg.StrOpt('database-url', - default='sqlite:////tmp/oat.db', + default='sqlite:////tmp/sqlite.db', help='Database connection URL.') ] @@ -46,118 +47,34 @@ CONF(args=sys.argv[1:], LOG = log.getLogger(__name__) app = flask.Flask(__name__) +app.config['DEBUG'] = CONF.debug app.config['SQLALCHEMY_DATABASE_URI'] = CONF.database_url -db = SQLAlchemy(app) +db = flask.ext.sqlalchemy.SQLAlchemy(app) class Fractal(db.Model): - __tablename__ = 'fractals' - - fractal_id = db.Column(db.String(36), primary_key=True) - data = db.Column(db.Text()) + uuid = db.Column(db.String(36), primary_key=True) + checksum = db.Column(db.String(256), unique=True) + duration = db.Column(db.Float) + width = db.Column(db.Integer, nullable=False) + height = db.Column(db.Integer, nullable=False) + iterations = db.Column(db.Integer, nullable=False) + xa = db.Column(db.Float, nullable=False) + xb = db.Column(db.Float, nullable=False) + ya = db.Column(db.Float, nullable=False) + yb = db.Column(db.Float, nullable=False) def __repr__(self): - return "" % self.uuid + return '' % self.uuid -@app.route("/") -def index(): - return flask.jsonify({}) - - -def get_fractal_from_database(fractal_id): - try: - return Fractal.query.get(fractal_id) - except Exception: - return None - - -def write_fractal_to_database(fractal_id, data): - fractal = Fractal( - fractal_id=fractal_id, - data=json.dumps(data) - ) - db.session.add(fractal) - db.session.commit() - - -@app.route('/v1/fractals//result', methods=['POST']) -def publish_result(fractal_id): - if (not flask.request.json or - not flask.request.json.viewkeys() & { - 'duration', 'checksum'}): - LOG.error("wrong request: %s" % flask.request.json) - flask.abort(400) - - fractal = get_fractal_from_database(fractal_id) - - if not fractal: - flask.abort(400) - - data = json.loads(fractal.data) - data['checksum'] = str(flask.request.json['checksum']) - data['duration'] = float(flask.request.json['duration']) - fractal.data = json.dumps(data) - db.session.commit() - data['uuid'] = fractal_id - return flask.jsonify(data), 201 - - -@app.route('/v1/fractals', methods=['POST']) -def create_fractal(): - if (not flask.request.json or - not flask.request.json.viewkeys() >= { - 'uuid', 'parameter', 'dimension'} or - not flask.request.json['parameter'].viewkeys() >= { - 'xa', 'xb', 'ya', 'yb', 'iterations'} or - not flask.request.json['dimension'].viewkeys() >= { - 'width', 'height'}): - LOG.error("wrong request: %s" % flask.request.json) - flask.abort(400) - - try: - fractal_id = str(flask.request.json['uuid']) - xa = float(flask.request.json['parameter']['xa']) - xb = float(flask.request.json['parameter']['xb']) - ya = float(flask.request.json['parameter']['ya']) - yb = float(flask.request.json['parameter']['yb']) - iterations = int(flask.request.json['parameter']['iterations']) - width = int(flask.request.json['dimension']['width']) - height = int(flask.request.json['dimension']['height']) - fractal = { - 'checksum': '', - 'duration': 0.0, - 'parameter': { - 'xa': xa, - 'xb': xb, - 'ya': ya, - 'yb': yb, - 'iterations': iterations - }, - 'dimension': { - 'width': width, - 'height': height - } - } - except Exception: - flask.abort(400) - - write_fractal_to_database(fractal_id, fractal) - - fractal['uuid'] = fractal_id - - return flask.jsonify(fractal), 201 - - -@app.errorhandler(404) -def not_found(error): - return flask.make_response(flask.jsonify({'error': 'Not found'}), 404) +db.create_all() +manager = flask.ext.restless.APIManager(app, flask_sqlalchemy_db=db) def main(): - db.create_all() - app.run(host=CONF.listen_address, debug=CONF.debug) - + manager.create_api(Fractal, methods=['GET', 'POST', 'DELETE', 'PUT']) + app.run(host=CONF.listen_address, port=CONF.bind_port) if __name__ == '__main__': main() diff --git a/openstack_application_tutorial/producer.py b/openstack_application_tutorial/producer.py index fdbc891..0c7a24f 100644 --- a/openstack_application_tutorial/producer.py +++ b/openstack_application_tutorial/producer.py @@ -111,17 +111,13 @@ def generate_task(): task = { 'uuid': str(uuid.uuid4()), - 'dimension': { - 'width': width, - 'height': height, - }, - 'parameter': { - 'iterations': iterations, - 'xa': xa, - 'xb': xb, - 'ya': ya, - 'yb': yb - } + 'width': width, + 'height': height, + 'iterations': iterations, + 'xa': xa, + 'xb': xb, + 'ya': ya, + 'yb': yb } return task @@ -137,7 +133,7 @@ def run(messaging, api_url): # NOTE(berendt): only necessary when using requests < 2.4.2 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} - requests.post("%s/v1/fractals" % api_url, json.dumps(task), + requests.post("%s/api/fractal" % api_url, json.dumps(task), headers=headers) LOG.info("generated task: %s" % task) with producers[messaging].acquire(block=True) as producer: diff --git a/openstack_application_tutorial/tracker.py b/openstack_application_tutorial/tracker.py index c3bd718..1b8fafd 100644 --- a/openstack_application_tutorial/tracker.py +++ b/openstack_application_tutorial/tracker.py @@ -77,15 +77,13 @@ class Tracker(ConsumerMixin): # NOTE(berendt): only necessary when using requests < 2.4.2 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} - requests.post("%s/v1/fractals/%s/result" % - (self.api_url, str(body['uuid'])), - json.dumps(result), headers=headers) + requests.put("%s/api/fractal/%s" % + (self.api_url, str(body['uuid'])), + json.dumps(result), headers=headers) message.ack() def main(): - LOG.info("XXX") - tracker = Tracker(CONF.amqp_url, CONF.api_url) if CONF.daemonize: diff --git a/openstack_application_tutorial/worker.py b/openstack_application_tutorial/worker.py index ec2d21c..723dadb 100644 --- a/openstack_application_tutorial/worker.py +++ b/openstack_application_tutorial/worker.py @@ -122,13 +122,13 @@ class Worker(ConsumerMixin): LOG.info("processing task %s" % body['uuid']) LOG.debug(body) start_time = time.time() - juliaset = JuliaSet(body['dimension']['width'], - body['dimension']['height'], - body['parameter']['xa'], - body['parameter']['xb'], - body['parameter']['ya'], - body['parameter']['yb'], - body['parameter']['iterations']) + juliaset = JuliaSet(body['width'], + body['height'], + body['xa'], + body['xb'], + body['ya'], + body['yb'], + body['iterations']) filename = os.path.join(self.target, "%s.png" % body['uuid']) elapsed_time = time.time() - start_time LOG.info("task %s processed in %f seconds" % diff --git a/requirements.txt b/requirements.txt index 71d35a8..d11e5a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,6 @@ mysql pillow python-daemon requests -flask -flask-sqlalchemy +flask-restless oslo.config oslo.log