-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.py
196 lines (153 loc) · 5.06 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import os
import threading
from datetime import datetime
import hashlib
import json
import functools
from io import BytesIO
from gzip import GzipFile
from flask import Flask, Response, request, send_from_directory, after_this_request
import config
from events.meetup import Meetup
from events import exporters
class WeBuild:
def __init__(self, config):
self.events_data = []
self.meetup = Meetup(config)
self.data_hash = ''
self.last_checked_timestamp = datetime.utcnow()
self.lock = threading.Lock()
app = Flask(__name__)
webuild = WeBuild(config)
def gzipped(f):
@functools.wraps(f)
def view_func(*args, **kwargs):
@after_this_request
def zipper(response):
if ('gzip' not in request.headers.get('Accept-Encoding', '').lower() or
not 200 <= response.status_code < 300 or
'Content-Encoding' in response.headers):
return response
response.direct_passthrough = False
gzip_buffer = BytesIO()
with GzipFile(mode='wb',
compresslevel=7,
fileobj=gzip_buffer) as gzip_file:
gzip_file.write(response.get_data())
response.set_data(gzip_buffer.getvalue())
response.headers['Content-Encoding'] = 'gzip'
response.headers['Vary'] = 'Accept-Encoding'
response.headers['Content-Length'] = len(response.data)
return response
return f(*args, **kwargs)
return view_func
def set_headers(f):
@functools.wraps(f)
def view_func(*args, **kwargs):
@after_this_request
def set_response_headers(response):
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Cache-Control'] = 'public, max-age=30'
response.set_etag(webuild.data_hash)
return response
return f(*args, **kwargs)
return view_func
def check_etag(f):
@functools.wraps(f)
def decorated_function(*args, **kwargs):
@app.before_request
def check_headers():
if request.if_none_match and webuild.data_hash in request.if_none_match:
print('304 response!')
return Response(status=304)
if request.method not in ('GET', 'OPTIONS'):
return Response('Invalid method', status=405)
return f(*args, **kwargs)
return decorated_function
def get_events():
global webuild
data = webuild.meetup.grab_events()
m = hashlib.sha1(json.dumps(data, ensure_ascii=False).encode('utf8'))
data_hash = m.hexdigest()
if len(data) > 0 and data_hash != webuild.data_hash:
webuild.lock.acquire()
webuild.events_data = data
webuild.data_hash = data_hash
webuild.lock.release()
return
def run():
get_events()
return app
@app.route('/')
def hello():
return Response('Welcome to webuild\'s API')
@app.route('/groups')
@check_etag
@set_headers
@gzipped
def groups():
return Response(
json.dumps(webuild.meetup.good_groups(), ensure_ascii=False),
content_type='application/json')
@app.route('/filtered_groups')
@check_etag
@set_headers
@gzipped
def filtered_groups():
return Response(
json.dumps(webuild.meetup.bad_groups(), ensure_ascii=False),
content_type='application/json')
@app.route('/events')
@check_etag
@set_headers
@gzipped
def events():
if request.if_none_match and webuild.data_hash in request.if_none_match:
print('304 response!')
return Response(status=304)
resp = {
'meta': {
'generated_at': webuild.last_checked_timestamp.isoformat(),
'location': config.meetup['params']['location'],
'total_events': len(webuild.events_data)
},
'events': webuild.events_data
}
return Response(json.dumps(resp, ensure_ascii=False),
content_type='application/json')
@app.route('/filtered_events')
@check_etag
@set_headers
@gzipped
def filtered_events():
return Response(
json.dumps(webuild.meetup.bad_events(), ensure_ascii=False),
content_type='application/json')
@app.route('/cal')
@check_etag
@set_headers
@gzipped
def cal():
return Response(
exporters.events_to_ics(webuild.events_data),
content_type='text/calendar; charset=utf-8')
@app.route('/cron')
def cron():
global webuild
now = datetime.utcnow()
if (now - webuild.last_checked_timestamp).seconds > 300: # 5 mins
webuild.lock.acquire()
webuild.last_checked_timestamp = now
webuild.lock.release()
w = threading.Thread(name='worker', target=get_events)
w.start()
return 'Done at {}'.format(webuild.last_checked_timestamp)
@app.route('/favicon.ico')
def favicon():
return send_from_directory(os.path.join(app.root_path, 'static'),
'favicon.ico', mimetype='image/x-icon')
if __name__ == "__main__":
app = run()
port = int(os.environ.get('PORT', 8080))
print('Serving on port {}'.format(port))
app.run(host='0.0.0.0', port=port)