Mercurial > hg > GlobalNeighbors
view globalneighbors/web.py @ 23:6891c5523b69
load with neighbors :)
author | Jeff Hammel <k0scist@gmail.com> |
---|---|
date | Sun, 25 Jun 2017 18:13:43 -0700 |
parents | e69cb496324e |
children |
line wrap: on
line source
#!/usr/bin/env python """ web handler for GlobalNeighbors """ # imports import argparse import json import os import sys import time from paste import httpserver from paste.urlparser import StaticURLParser from webob import Request, Response, exc from wsgiref import simple_server from .locations import locations from .locations import city_dict from .neighbors import read_neighbors_file from .read import read_cities from .read import read_city_list from .schema import fields from .schema import name from .template import template_dir from .template import TemplateLoader here = os.path.dirname(os.path.abspath(__file__)) static_dir = os.path.join(here, 'static') class PassthroughFileserver(object): """serve files if they exist""" def __init__(self, app, directory=static_dir): self.app = app self.directory = directory self.fileserver = StaticURLParser(self.directory) def __call__(self, environ, start_response): path = environ['PATH_INFO'].strip('/') if path and os.path.exists(os.path.join(self.directory, path)) and '..' not in path: return self.fileserver(environ, start_response) return self.app(environ, start_response) def autocomplete(cities, startswith=None, limit=None): """autocomplete function for city names""" ### TODO: # - sort once, ahead of time # - return most populous cities if startswith: retval = [] for i in cities: if i[name].startswith(startswith): retval.append({"name": i[name], "country code": i["country code"], "population": i['population'], "geonameid": i['geonameid']}) else: retval = [{"name": i[name], "country code": i["country code"], "population": i['population'], "geonameid": i['geonameid']} for i in cities] return sorted(retval, key=lambda x: (x['name'], -x['population']) )[:limit] class Handler(object): """base class for HTTP handler""" def __call__(self, environ, start_response): request = Request(environ) method = getattr(self, request.method, None) response = None if method is not None: response = method(request) if response is None: response = exc.HTTPNotFound() return response(environ, start_response) class CitiesHandler(Handler): """cities ReST API""" content_type = 'application/json' def __init__(self, locations, cities): self.locations = locations self._cities = cities def cities(self, startswith=None): """return list of cities""" return autocomplete(self._cities.values(), startswith=startswith) def GET(self, request): return Response(content_type=self.content_type, body=json.dumps(self.cities( startswith=request.GET.get('term')))) class NeighborsHandler(Handler): content_type = 'application/json' def __init__(self, neighbors): self.neighbors = neighbors def GET(self, request): geoid = request.GET.get('geoid') neighbors = self.neighbors.get(geoid, []) return Response(content_type=self.content_type, body=json.dumps(neighbors)) class GlobalHandler(Handler): """WSGI HTTP Handler""" content_type = 'text/html' def __init__(self, datafile, neighbors_file=None, template_dir=template_dir): # parse data self.datafile = datafile self.cities = read_city_list(self.datafile, fields=fields) self.locations = locations(self.cities) if neighbors_file: self.neighbors = read_neighbors_file(neighbors_file) else: self.neighbors = None # get country codes self.country_codes = sorted(set([city['country code'] for city in self.cities if city['country code']])) # convert cities to a dict for lookup self.cities = {city['geonameid'] : city for city in self.cities} # declare handlers self.handlers = {'/cities': CitiesHandler(self.locations, self.cities)} # template loader self.loader = TemplateLoader(template_dir) self.index = self.loader.load('index.html') self.citypage = self.loader.load('city.html') def GET(self, request): if request.path_info in ('', '/'): # Landing page body = self.index.render(n_cities=len(self.cities), country_codes=self.country_codes) return Response(content_type=self.content_type, body=body) elif request.path_info in self.handlers: return request.get_response(self.handlers[request.path_info]) else: try: geoid = int(request.path.strip('/')) city = self.cities.get(geoid) if not city: return variables = dict(city=city, neighbors=None) if self.neighbors: n_neighbors = request.GET.get('neighbors', 10) try: n_neighbors = int(n_neighbors) except ValueError as e: n_neighbors = 10 neighbors = self.neighbors.get(geoid, [])[:n_neighbors] neighbors = [{'name': self.cities[geoid]['name'], 'geoid': geoid, 'distance': distance} for geoid, distance in neighbors] variables['neighbors'] = neighbors return Response(content_type=self.content_type, body=self.citypage.render(variables)) except ValueError: pass def main(args=sys.argv[1:]): """CLI""" # parse command line parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('cities', help="cities1000 data file") parser.add_argument('-p', '--port', dest='port', type=int, default=8080, help="port to serve on") parser.add_argument('--hostname', dest='hostname', default='localhost', help="host name [DEFAULT: %(default)s]") parser.add_argument('--neighbors', dest='neighbors_file', help="file for nearest neighbors") options = parser.parse_args(args) # instantiate WSGI handler app = GlobalHandler(datafile=options.cities, neighbors_file=options.neighbors_file) # wrap it in a static file server app = PassthroughFileserver(app) print ("Serving on http://{hostname}:{port}/".format(**options.__dict__)) try: httpserver.serve(app, host='0.0.0.0', port=options.port) except KeyboardInterrupt: pass if __name__ == '__main__': main()