07 Mar 2014

Flask GeoIP API in python

We're going to create a simple flask webapp. I say simple because it only has three routes, however this doesn't mean it can't be extremely useful.

Jump to final code

The webapp will provide the following three functions:

Root route - Details for requesters IP

IP route - Details for the IP url parameter

Domain route - Details for the domain parameter

The JSON output of our API will look like this:

{ area_code: 408, city: "Campbell", continent: "NA", country_code: "US", country_code3: "USA", country_name: "United States", dma_code: 807, latitude: 37.28030000000001, longitude: -121.9567, metro_code: "San Francisco, CA", postal_code: "95008", region_code: "CA", time_zone: "America/Los_Angeles" }

Flask makes this very simple for us with its simple straight forward routing mechanism, we can get the request IP with request.remote_addr .

For our GeoIP functionality we'll be using the pygeoip library. You'll also need to download the latest available database from MaxMind.

We will take advantage of the jsonify function that Flask has to offer. This will take care of converting the dictionary returned from pygeoip into a JSON encoded string, and also setting the content-type response header to application/json .

from flask import Flask, request, jsonify import pygeoip app = Flask(__name__) # Make sure this points to your downloaded file gi = pygeoip.GeoIP('GeoIPCity.dat', pygeoip.MEMORY_CACHE) @app.route('/') def root(): geo_data = gi.record_by_addr(request.remote_addr) return jsonify(geo_data) if __name__ == '__main__': app.run(port=8000, debug=False)

The above will make the root url display the details for your IP address. However please note that it will not work on localhost, as your IP address for that request would simply be 127.0.0.1 .

Now you can add the routes for the custom IP or custom domain which will be passed in the URL.

@app.route('/ip/<ip_address>') def ip(ip_address): geo_data = gi.record_by_addr(ip_address) return jsonify(geo_data) @app.route('/domain/<domain_name>') def domain(domain_name): geo_data = gi.record_by_name(domain_name) return jsonify(geo_data)

The only problem so far is that if somebody sends a request for an invalid IP/domain, then geo_data will equal None . Upon calling jsonify(None) you will recieve an exception.

Because our app only serves one simple function, we can register a global exception catcher for 500 errors, and display an error the the requester.

# In general catching all 500 errors like this could be considered bad practice # But with an app which only serves single function like ours # It's a neat way to follow the DRY principal because all errors are the same @app.errorhandler(500) def error_500(e): return jsonify({'error': 'Error finding GeoIP data for that address'})

Final Code

That's it. You have yourself a JSON GeoIP API that can be used with ease. Here's all of it put together: