Simplify API service by using Flask-Restless

This commit is contained in:
Christian Berendt 2015-03-07 11:14:21 +01:00
parent 85a090649d
commit a09ad6bfe7
6 changed files with 98 additions and 132 deletions

56
contrib/test_api.py Executable file
View File

@ -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

View File

@ -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 "<Fractal(uuid='%s')>" % self.uuid
return '<Fractal %s>' % 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/<string:fractal_id>/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()

View File

@ -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:

View File

@ -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:

View File

@ -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" %

View File

@ -6,7 +6,6 @@ mysql
pillow
python-daemon
requests
flask
flask-sqlalchemy
flask-restless
oslo.config
oslo.log