Skip to content

Commit

Permalink
Merge pull request #8 from stevenpollack/heroku_app
Browse files Browse the repository at this point in the history
created first version of heroku app... Still doesn't have caching.
  • Loading branch information
stevenpollack committed Mar 13, 2016
2 parents f2f6142 + 7aef902 commit c8048d9
Show file tree
Hide file tree
Showing 17 changed files with 265 additions and 20 deletions.
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn app:app --log-file -
108 changes: 108 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from flask import Flask, request, json, Response, redirect
from requests import HTTPError
from warnings import warn
from iron_cache import IronCache
from dairy_queen.theatre import Theatre
import requests
from json import loads, dumps

#double_dip_cache = IronCache(name='double_dips')

app = Flask(__name__)
@app.route('/')
def route_to_apiary():
apiary_io = 'http://docs.dairyqueen1.apiary.io/'
return (redirect(apiary_io, code=302))

def hello_world():
try:
cache_key = create_cache_key(near, days_from_now)
double_dips = json.loads(double_dip_cache.get(key=cache_key).value)
warn('Fetched results from cache with key: ' + cache_key)
except ValueError as ve:
warn(str(ve))
showtimes = json.dumps({'error': '`date` must be a base-10 integer'})
status = 400
except HTTPError as e:
warn(str(e))
return('hello world')

@app.route('/double-dips', methods=['GET'])
def get_doubledips():
location = request.args.get('location')
days_from_now = request.args.get('days_from_now')
max_waiting_time = request.args.get('max_wait_mins')
max_overlap_time = request.args.get('max_overlap_mins')

status = None
msg = None
mimetype = 'application/json'

if location is None or not isinstance(location, str):
status = 400
msg = "'location' is mandatory and must be a string."

if days_from_now is not None:
try:
days_from_now = int(days_from_now)
except Exception:
status = 400
msg = "'days_from_now' must be a base-10 integer."
resp = Response(dumps({'msg': msg}), status=status, mimetype=mimetype)
return resp
else:
days_from_now = 0

if max_waiting_time is not None:
try:
max_waiting_time = int(max_waiting_time)
except Exception:
status = 400
msg = "'max_waiting_time' must be a base-10 integer"
resp = Response(dumps({'msg': msg}), status=status, mimetype=mimetype)
return resp
else:
max_waiting_time = 45

if max_overlap_time is not None:
try:
max_overlap_time = int(max_overlap_time)
except Exception:
status = 400
msg = "'max_overlap_time' must be a base-10 integer"
resp = Response(dumps({'msg': msg}), status=status, mimetype=mimetype)
return resp
else:
max_overlap_time = 5


gms_url = 'http://google-movies-scraper.herokuapp.com/movies'
gms_params = {
'near': location,
'date': days_from_now
}

# should definitely build some logic to handle response code of r...
r = requests.get(gms_url, params=gms_params)
theatres_json = loads(r.text)
output = []
for theatre in theatres_json:
try:
tmp_theatre = Theatre(name=theatre.get('name'),
program=theatre.get('program'),
address=theatre.get('address'))

tmp_json = tmp_theatre.to_json(max_waiting_time=max_waiting_time,
max_overlap_time=max_overlap_time)

output.append(tmp_json)
except TypeError as e:
warn(str(e))

status = 200
resp = Response(dumps(output), status=status, mimetype=mimetype)
return(resp)


if (__name__ == '__main__'):
app.run(debug=True, host='0.0.0.0', port=5000)
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import collections
from movie import Movie
from .movie import Movie

# SO- flatten arbirtrary list:
# http://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists-in-python#2158532
Expand Down Expand Up @@ -63,6 +63,10 @@ def __init__(self, movies):
if throw_error:
raise TypeError("DoubleDip can only be initialized from a movie or list of movies.")

def to_json(self, showtime_format='%H:%M'):
self.json = [movie.to_json(showtime_format) for movie in self]
return self.json

def __repr__(self):
output = "DoubleDip([" + ", ".join([movie.__repr__() for movie in self]) + "])"
return(output)
Expand Down
15 changes: 12 additions & 3 deletions dairy_queen/movie.py → dairy_queen/dairy_queen/movie.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import datetime
from datetime import datetime, timedelta

class Movie:
"""A Movie is an object that stores information for a particular movie (assumed
Expand All @@ -20,13 +20,22 @@ def __init__(self, name, runtime, showtime, showtime_format='%H:%M'):
if runtime <= 0:
raise ValueError('runtime must be a positive integer')

self.start = datetime.datetime.strptime(showtime, showtime_format)
self.start = datetime.strptime(showtime, showtime_format)

self.runtime = datetime.timedelta(minutes=runtime)
self.runtime = timedelta(minutes=runtime)
self.name = name

self.end = self.start + self.runtime

def to_json(self, showtime_format='%H:%M'):
self.json = {
'movie': self.name,
'length': int(self.runtime.total_seconds()/60),
'startTime': self.start.strftime(showtime_format),
'endTime': self.end.strftime(showtime_format)
}
return self.json

def __str__(self):
output = '%s (%s min): showing from %s to %s' % \
(self.name, str(self.runtime.total_seconds() / 60), self.start, self.end)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
27 changes: 27 additions & 0 deletions dairy_queen/dairy_queen/tests/test_doubledip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from dairy_queen.doubledip import DoubleDip
from dairy_queen.movie import Movie

class TestDoubleDip:
def test_to_json(self):

doubledip = DoubleDip([
Movie(name='a', runtime=60, showtime='12:45'),
Movie(name='b', runtime=120, showtime='14:00')
])

json_output = [
{
'movie': 'a',
'length': 60,
'startTime': '12:45',
'endTime': '13:45'
},
{
'movie': 'b',
'length': 120,
'startTime': '14:00',
'endTime': '16:00'
}
]

assert doubledip.to_json() == json_output
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from doubledip import FlatList
from dairy_queen.doubledip import FlatList

class TestFlatList:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import pytest # for fixtures
from theatre import Theatre
from doubledip import DoubleDip
from dairy_queen.theatre import Theatre
from dairy_queen.doubledip import DoubleDip
from operator import attrgetter # this is for sorting


# only import test json once per session.
@pytest.fixture(scope = 'module')
def theatre_json():
import json
return json.load(open('tests/example_theatre.json'))

return json.load(open('dairy_queen/tests/example_theatre.json'))

def create_theatre_and_calc_double_dips(theatre_json, max_waiting_mins = 20, max_overlap_mins = 6):
theatre = Theatre(name = theatre_json['name'], program = theatre_json['program'])
theatre.calculate_double_dips(max_waiting_mins, max_waiting_mins)
theatre.program.sort(key = attrgetter('name'))
return (theatre)


class TestTheatre:

def test_calculate_double_dip_1(self, theatre_json):
# we should expect to see a list of 1 double dip, connecting
# movie 'a' to movie 'b'
Expand Down Expand Up @@ -90,3 +89,61 @@ def test_calculate_double_dip_6(self, theatre_json):
]

assert theatre.double_dips == expected_dips

def test_to_json(self):
theatre_json = {
"name": "Test Theatre 5",
"description": "Test triplet when there's an unacceptable distance",
"program": [
{
"name": "a",
"runtime": 60,
"showtimes": "16:00"
},
{
"name": "b",
"runtime": 60,
"showtimes": "17:05"
},
{
"name": "c",
"runtime": 60,
"showtimes": "19:05"
}
]
}

expected_output = {
'name': theatre_json['name'],
'address': theatre_json.get('address'),
'doubleDips': [
[
{
'movie': theatre_json['program'][0]['name'],
'length': theatre_json['program'][0]['runtime'],
'startTime': theatre_json['program'][0]['showtimes'],
'endTime': "17:00"
},
{
'movie': theatre_json['program'][1]['name'],
'length': theatre_json['program'][1]['runtime'],
'startTime': theatre_json['program'][1]['showtimes'],
'endTime': "18:05"
}
],
[
{
'movie': theatre_json['program'][2]['name'],
'length': theatre_json['program'][2]['runtime'],
'startTime': theatre_json['program'][2]['showtimes'],
'endTime': "20:05"
}
]
]
}

theatre = Theatre(name=theatre_json.get('name'),
program=theatre_json.get('program'),
address=theatre_json.get('address'))

assert theatre.to_json() == expected_output
33 changes: 27 additions & 6 deletions dairy_queen/theatre.py → dairy_queen/dairy_queen/theatre.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import warnings
import datetime
from operator import attrgetter # this is for sorting
from movie import Movie
from doubledip import DoubleDip
from .movie import Movie
from .doubledip import DoubleDip

class Theatre:
""" Theatre object
Expand All @@ -19,14 +19,22 @@ class Theatre:
"""

double_dips = None
address = None
json = None

def __init__(self, name, program):
def __init__(self, name, program, address=None):

if isinstance(name, str):
self.name = name
else:
raise TypeError("'name' must be a string")

if address is not None:
if isinstance(address, str):
self.address = address
else:
raise TypeError("'address' must be a string")

self.program = []

# there's a subtle problem with program is a dictionary (or tuple)
Expand Down Expand Up @@ -56,8 +64,7 @@ def __init__(self, name, program):
showtime=showtime)
)
except ValueError:
warnings.warn(movie.get('name') + "could not be coerced to a Movie due to bad runtime...")

warnings.warn(movie.get('name') + " could not be coerced to a Movie due to bad runtime...")

def __str__(self):
output = self.name + "\n\nProgram:\n"
Expand All @@ -68,7 +75,21 @@ def __repr__(self):
output = "Theatre(name=%r, program=%r)" % (self.name, self.program)
return(output)

def calculate_double_dips(self, max_waiting_time=45, max_overlap_time=30):
def to_json(self, max_waiting_time=45, max_overlap_time=30, showtime_format='%H:%M'):
if self.double_dips is None:
self.calculate_double_dips(max_waiting_time, max_overlap_time)
return self.to_json(max_waiting_time, max_overlap_time)

self.json = {
'name': self.name,
'address': self.address,
'doubleDips': [double_dip.to_json(showtime_format) for double_dip in self.double_dips]
}

return self.json


def calculate_double_dips(self, max_waiting_time=45, max_overlap_time=5):

max_waiting_time = datetime.timedelta(minutes = max_waiting_time)
max_overlap_time = datetime.timedelta(minutes = max_overlap_time)
Expand Down
Binary file added dairy_queen/dist/dairy_queen-0.1.0.tar.gz
Binary file not shown.
3 changes: 3 additions & 0 deletions dairy_queen/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
py==1.4.31
pytest==2.8.5
wheel==0.29.0
File renamed without changes.
10 changes: 7 additions & 3 deletions setup.py → dairy_queen/setup.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from setuptools import setup

setup(name='dairy_queen',
version='1.0.0',
version='0.1.0',
description='package to calculate double dips in a given theatre',
url='http://github.com/stevenpollack/dairy_queen',
author='Steven Pollack',
author_email='[email protected]',
setup_requires = ['pytest-runner'],
tests_require = ['pytest'],
setup_requires=['pytest-runner'],
tests_require=['pytest'],
license='MIT',
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 3"
],
packages=['dairy_queen'],
zip_safe=False)
13 changes: 12 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
-e git+https://github.com/stevenpollack/dairy_queen.git@heroku_app#egg=dairy_queen&subdirectory=dairy_queen
Flask==0.10.1
gunicorn==19.4.5
iron-cache==0.3.2
iron-core==1.2.0
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
py==1.4.31
pytest==2.8.5
wheel==0.29.0
python-dateutil==2.5.0
requests==2.9.1
six==1.10.0
Werkzeug==0.11.4

0 comments on commit c8048d9

Please sign in to comment.