Flask

First Flask web app

As everyone should know by now, I love coding challenges. A while ago I came across this one, which is rather long:
Using python with gevent 0.13.x and your choice of additional libraries and/or frameworks, implement a single HTTP server with API endpoints that provide the following functionalities:

      A Fibonacci endpoint that accepts a number and returns the Fibonacci calculation for that number, and returns result in JSON format. example:

      1. $ curl -s 'http://127.0.0.1:8080/fib/13'
      2. {"response": 233}
      1. $ curl -s 'http://127.0.0.1:8080/fib/12'
      2. {"response": 144}
      An endpoint that fetches the Google homepage and returns the sha1 of the response message-body (HTTP body data).example:

      1. $ curl -s 'http://127.0.0.1:8080/google-body'
      2. {"response": "272cca559ffe719d20ac90adb9fc4e5716479e96"}
      Using some external storage of your choice (can be redis, memcache, sqlite, mysql, etc), provide a means to store and then retrieve a value.Example:

      1. $ curl -d 'value=something' 'http://127.0.0.1:8080/store'
      2. $ curl 'http://127.0.0.1:8080/store'
      3. {"response": "something"}</li>

At one point in my past a coworker talked about his love of the Flask micro framework. Since this is just a simple web API, I figured I'd give it a shot. This is a bit of a complicated task with many pieces, so let's set a game plan for the rest of this post. I'm going to share the specific functions related to each piece of functionality, then at the very end I will share the entire code base so everyone can see how it all fits together. One last note - to test out the run, you need to call the run.py script, which will also be included below. Ready … BREAK!

Let's tackle the google-body endpoint first:

  1. @app.route('/google-body')
  2. def google_body():
  3. try:
  4. sh = sha.new(urlopen('http://www.google.com').read())
  5. return jsonify({'response' : sh.hexdigest()})
  6. except Exception as e:
  7. return jsonify({'response' : 'ERROR: %s' % str(e)})

While this code block is very compact, it's not difficult to understand. Go out to the internet, get the source code for google's home page and insert that into the sha object. Wrap up the hexdigest results into a dictionary object, throw that into the jsonify function, and send it on its way. Don't forget to package it all in a nice try/except block for safety.

Onto the Fibonacci API call:

  1. @app.route('/fib/<number>')
  2. def fib(number):
  3. try:
  4. return jsonify({'response' : real_fib(int(number))})
  5. except ValueError:
  6. return jsonify({'response' : 'ERROR: Input not a number'})
  7.  
  8. @lru_cache()
  9. def real_fib(n):
  10. """
  11. This code was modified from the fib code in the python3 functools
  12. documentation.
  13. """
  14. if n < 2:
  15. return n
  16. return real_fib(n-1) + real_fib(n-2)

During PyCon US 2012, I became aware of the lru_cache decorator in python3.3. I also learned that Raymond Hettinger wrote code that would allow it work in python2, which allowed me to copy the code from the python3 documentation with little modification. Knowing about the lru_cache allowed me to write a nice and concise Fibonacci function reminiscent of something I might write in Haskell or some other functional language.

Now the last part of this challenge, and the longest. Before we get to the python, I feel that it might help your understanding if you know what kind of database schema we're working with. So I'm going to post that first, then the python code.

  1. drop table if exists entries;
  2. create table entries (
  3. id integer primary key autoincrement,
  4. value string not null
  5. );

  1. @app.route('/store', methods=['GET', 'POST'])
  2. def store():
  3. if request.method == 'POST':
  4. try:
  5. g.db.execute('insert into entries (value) values (?)',
  6. [request.form['value']])
  7. g.db.commit()
  8. resp = jsonify()
  9. resp.status_code = 200
  10. return resp
  11. except Exception as e:
  12. return jsonify({"response" : "ERROR: %s" % str(e)})
  13. else:
  14. try:
  15. cur = g.db.execute('select value from entries order by id desc')
  16. #fetchone returns a list. To better meet the requirements,
  17. #just slicing the head of the list and output that.
  18. return jsonify({'response' : cur.fetchone()[0]})
  19. except IndexError:
  20. return jsonify({'response' : 'NOTHING IN THE DATABASE'})

This step is obviously a little more complex - the function has to process both the GET and the POST HTTP methods, while using an outside database to store and retrieve the information. I believe the code here is simple enough for you to understand, so I won't explain every line. For the POST method I had to do some juggling to get the desired return results. In the example above a POST method works, but does not receive an actual response. I was able to create this by using the jsonify object to create an empty Flask.Response object, and then set the status code of that response.

At the end of the day I'm pretty happy with this. It wasn't a hard challenge, but certainly allowed me to learn a bit about Flask. If I did over again, I might improve things by creating my own decorator to abstract all the try/except blocks. The errors would have to become more generic and maybe less helpful, but that is a worthwhile cost for being able to live the “Don't Repeat Yourself” mentality. The decorator would look something like this (the code below has not been tested - caveat emptor):

  1. def try_block(f):
  2. @wraps
  3. def wrapper(*args, **kwds):
  4. try:
  5. return f(*args,**kwds)
  6. except ValueError:
  7. return jsonify({'response' : 'ERROR: Input not a number'})

As more functions start to use this decorator it'll most likely get uglier as it has to juggle more and more exceptions. While that is certainly a cost of having all of the error checking wrapped up in one location, the benefit that I can see is if another programmer were to add a new piece of functionality, he would know exactly where to go to add in the exceptions if it wasn't there already.

  1. @app.route('/fib/<number>')
  2. @try_block
  3. def fib(number):
  4. return jsonify({'response' : real_fib(int(number))})

I think this would clean up the function quite a bit.

Next time I'm going to show how to do this in Haskell using the Yesod web framework. As always, questions, and comments are welcomed.

run.py

  1. from gevent.wsgi import WSGIServer
  2. from playhaven import app, init_db
  3.  
  4. init_db()
  5. http_server = WSGIServer(('0.0.0.0', 8080), app)
  6. http_server.serve_forever()

challenge.py

  1. from __future__ import with_statement
  2. from urllib2 import urlopen
  3. from contextlib import closing
  4. from flask import Flask, request, g, jsonify
  5. from lru_cache import lru_cache
  6. import sqlite3
  7. import sha
  8.  
  9. DATABASE = '/tmp/challenge.db'
  10. DEBUG = True
  11. SECRET_KEY = 'c29tZXRoaW5nY2xldmVyaGVyZQ==\n'
  12. USERNAME = 'challenge'
  13. PASSWORD = 'chang3m3'
  14.  
  15. app = Flask(__name__)
  16. app.config.from_object(__name__)
  17.  
  18. app.config.from_envvar('CHALLENGE_SETTINGS', silent=True)
  19.  
  20. def connect_db():
  21. return sqlite3.connect(app.config['DATABASE'])
  22.  
  23. def init_db():
  24. with closing(connect_db()) as db:
  25. with app.open_resource('schema.sql') as f:
  26. db.cursor().executescript(f.read())
  27. db.commit()
  28.  
  29. @app.before_request
  30. def before_request():
  31. g.db = connect_db()
  32.  
  33. @app.teardown_request
  34. def teardown_request(exception):
  35. g.db.close()
  36.  
  37. @app.route('/fib/<number>')
  38. def fib(number):
  39. try:
  40. return jsonify({'response' : real_fib(int(number))})
  41. except ValueError:
  42. return jsonify({'response' : 'ERROR: Input not a number'})
  43.  
  44. @lru_cache()
  45. def real_fib(n):
  46. """
  47. This code was modified from the fib code in the python3 functools
  48. documentation.
  49. """
  50. if n < 2:
  51. return n
  52. return real_fib(n-1) + real_fib(n-2)
  53.  
  54. @app.route('/google-body')
  55. def google_body():
  56. try:
  57. sh = sha.new(urlopen('http://www.google.com').read())
  58. return jsonify({'response' : sh.hexdigest()})
  59. except Exception as e:
  60. return jsonify({'response' : 'ERROR: %s' % str(e)})
  61.  
  62. @app.route('/store', methods=['GET', 'POST'])
  63. def store():
  64. if request.method == 'POST':
  65. try:
  66. g.db.execute('insert into entries (value) values (?)',
  67. [request.form['value']])
  68. g.db.commit()
  69. resp = jsonify()
  70. resp.status_code = 200
  71. return resp
  72. except Exception as e:
  73. return jsonify({"response" : "ERROR: %s" % str(e)})
  74. else:
  75. try:
  76. cur = g.db.execute('select value from entries order by id desc')
  77. #fetchone returns a list. To better meet the requirements,
  78. #just slicing the head of the list and output that.
  79. return jsonify({'response' : cur.fetchone()[0]})
  80. except IndexError:
  81. return jsonify({'response' : 'NOTHING IN THE DATABASE'})
  82.  
  83. if __name__ == '__main__':
  84. app.run()

If you made it this far down into the article, hopefully you liked it enough to share it with your friends. Thanks if you do, I appreciate it.

Bookmark and Share

Syndicate content