Simple web service using bottle.py

A very light-weight webservice that handles ajax requests from a pycon talk recommender web app.

The rest of the project can be found on github: https://github.com/mikecunha/pycon_reco


In [2]:
%%writefile app/pycon_service.py
#!/usr/bin/env python3

from bottle import route, run, request, static_file, get, response, hook
import pymongo

from datetime import datetime
from os import path, urandom
from base64 import b64encode
import sys
import re

try:
    client = pymongo.MongoClient('127.0.0.1', 27017, tz_aware=True)
    db = client.pycon
except:
    db = False

# for session id's in cookies
def generate_session():
    return str(b64encode(os.urandom(16)))


# read in related docs index
related_docs = {}
with open('rel_talks.txt', mode='rt', encoding='utf-8') as myfile:
    for doc_num, line in enumerate( myfile ):
        related_docs[str(doc_num)] = line.strip("\n").split(',')


rx = re.compile(u'([\u2014\-\u2019\n]|\.|\(|\)|\:|;|/|\[|\])', flags=re.UNICODE)

def sim_talks( dstring ):
    """ splits string of comma-delim docs into a list"""
    
    dstring = dstring.lower().strip(',')
    
    # replace punctuation with spaces
    dstring = rx.sub(" ", dstring )
    
    return [ doc_num.strip() for doc_num in dstring.split(',') ]
    
    
@route( '/rec', method='POST' )
def rec():
    """Handle ajax requests for related talks given a list 
    of doc ID's, return a list of related doc ID's """
    
    # FIXME do a proper connection class
    global db
    global client
    
    # Get list of requested talks to find rec's for
    response_val = { "talks": [] }
    inputval = request.forms.get('t')
    if inputval == None:
        return response_val
    elif inputval.strip(',') == '':
        return response_val
    else: 
        favd_docs = sim_talks( inputval )
        
    for fdoc in favd_docs:
        [ response_val['talks'].append(doc_id) for doc_id in related_docs[fdoc] ]
        
    if not db:
        # no databse, skip sessions and logging
        return response_val
    
    # handle user-session and logging
    if request.get_cookie("uid"):
        uid = request.get_cookie("uid")
    else:
        uid = generate_session()
        # 2592000 (30 days of seconds)
        response.set_cookie("uid", uid, secret=None, max_age=2592000 )
        
    # Gather info for logging and building a training set
    ip = request.get('REMOTE_ADDR')
    ua = request.get('HTTP_USER_AGENT')
    dt = datetime.now()
        
    # log favs to db for future training set
    try:
        db.sessions.update(
        { "_id": uid },
        { "$push": 
          { "favs": 
            { "talks": inputval.lower().strip(',').replace("$",""), 
              "ip": ip, 
              "user_agent": ua, 
              "time": dt } 
          }, "$inc": { "num_of_queries":1}
        },    
        upsert=True )
    except:
        # FIXME handle this better
        client = pymongo.MongoClient('127.0.0.1', 27017, tz_aware=True)
        db = client.pycon

    return response_val


@route('/milk_and', method='POST') 
def cookie():
    
    if request.get_cookie("uid"):
        return { }
    else:
        uid = generate_session()
        # 2592000 (30 days in seconds)
        response.set_cookie("uid", uid, secret=None, max_age=2592000 )

    return { }

if __name__ == '__main__':
    
    # need this if you're not serving the html page on localhost 
    # e.g. you're using file:///* in the browser
    @hook('after_request')
    def enable_cors():
        response.headers['Access-Control-Allow-Origin'] = '*'
    
    run(host='localhost', port=8181)


Overwriting app/pycon_service.py

To deploy via wsgi


In [3]:
%%writefile app/pycon_adapter.wsgi
import sys, os, bottle

sys.path = ['/path/to/apache/www/'] + sys.path
os.chdir(os.path.dirname(__file__))

os.environ[ 'MPLCONFIGDIR' ] = '/tmp/'

import pycon_service # This loads your application

application = bottle.default_app()


Overwriting app/pycon_adapter.wsgi