From 3cde7042a14d51d167c0ff023b0b8ab3488d58db Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 13 May 2016 20:56:04 +0200 Subject: [PATCH 01/10] pep 8 --- backend.py | 57 +++++++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/backend.py b/backend.py index 12a1a60..13fa190 100644 --- a/backend.py +++ b/backend.py @@ -4,26 +4,28 @@ import os import falcon import psycopg2 -import uuid import json -import codecs + def db_connect(): try: - db = psycopg2.connect(dbname="oedb") + db = psycopg2.connect(dbname="oedb") except: - db_host = os.getenv("DB_HOST","localhost") - db_user = os.getenv("DB_USER","oedb") - db_password = os.getenv("POSTGRES_PASSWORD","") - db = psycopg2.connect(dbname="oedb",host=db_host,password=db_password,user=db_user) + db_host = os.getenv("DB_HOST", "localhost") + db_user = os.getenv("DB_USER", "oedb") + db_password = os.getenv("POSTGRES_PASSWORD", "") + db = psycopg2.connect(dbname="oedb", host=db_host, + password=db_password, user=db_user) return db + def standard_headers(resp): resp.set_header('X-Powered-By', 'OpenEventDatabase') resp.set_header('Access-Control-Allow-Origin', '*') resp.set_header('Access-Control-Allow-Headers', 'X-Requested-With') + class StatsResource(object): def on_get(self, req, resp): db = db_connect() @@ -34,11 +36,12 @@ class StatsResource(object): db.close() standard_headers(resp) - resp.body = """{"events_count": %s, "last_created": "%s", "last_updated": "%s"}""" % (stat[0], stat[1],stat[2]) + resp.body = """{"events_count": %s, "last_created": "%s", "last_updated": "%s"}""" % (stat[0], stat[1], stat[2]) resp.status = falcon.HTTP_200 + class EventsResource(object): - def on_get(self,req,resp): + def on_get(self, req, resp): db = db_connect() cur = db.cursor() # get event geojson Feature @@ -53,18 +56,19 @@ JOIN geo ON (hash=events_geo)"""); ]}""" resp.status = falcon.HTTP_200 + class EventResource(object): - def maybe_insert_geometry(self,geometry,cur): + def maybe_insert_geometry(self, geometry, cur): # insert into geo table if not existing cur.execute("""INSERT INTO geo (hash, geom, geom_center) SELECT *, st_centroid(geom) FROM (SELECT md5(ewkt) as hash, st_setsrid(st_geomfromewkt(ewkt),4326) as geom FROM (SELECT st_asewkt(st_geomfromgeojson( %s )) as ewkt) as g) as i ON CONFLICT DO NOTHING RETURNING hash;""",(geometry,)) # get its id (md5 hash) h = cur.fetchone() if h is None: - cur.execute("""SELECT md5(st_asewkt(st_geomfromgeojson( %s )));""",(geometry,)) + cur.execute("""SELECT md5(st_asewkt(st_geomfromgeojson( %s )));""", (geometry,)) h = cur.fetchone() return h - def on_get(self, req, resp, id = None): + def on_get(self, req, resp, id=None): standard_headers(resp) db = db_connect() cur = db.cursor() @@ -76,8 +80,9 @@ class EventResource(object): event_bbox = cur.mogrify(" AND geom && ST_SetSRID(ST_MakeBox2D(ST_Point(%s,%s),ST_Point(%s,%s)),4326) ",tuple(req.params['bbox'])).decode("utf-8") event_dist = "" elif 'near' in req.params: - # limit search with location+distance (long, lat, distance in meters) - if len(req.params['near'])<3: + # Limit search with location+distance + # (long, lat, distance in meters) + if len(req.params['near']) < 3: dist = 1 else: dist = req.params['near'][2] @@ -89,28 +94,28 @@ class EventResource(object): if 'when' in req.params: # limit search with fixed time - event_when = cur.mogrify("tstzrange(%s,%s,'[]')",(req.params['when'],req.params['when'])).decode("utf-8") + event_when = cur.mogrify("tstzrange(%s,%s,'[]')", (req.params['when'], req.params['when'])).decode("utf-8") elif 'start' in req.params and 'stop' in req.params: # limit search with fixed time (start to stop) - event_when = cur.mogrify("tstzrange(%s,%s,'[]')",(req.params['start'],req.params['stop'])).decode("utf-8") + event_when = cur.mogrify("tstzrange(%s,%s,'[]')", (req.params['start'], req.params['stop'])).decode("utf-8") elif 'start' in req.params and 'stop' not in req.params: # limit search with fixed time (start to now) - event_when = cur.mogrify("tstzrange(%s,now(),'[]')",(req.params['start'],)).decode("utf-8") + event_when = cur.mogrify("tstzrange(%s,now(),'[]')", (req.params['start'],)).decode("utf-8") elif 'start' not in req.params and 'stop' in req.params: # limit search with fixed time (now to stop) - event_when = cur.mogrify("tstzrange(now(),%s,'[]')",(req.params['stop'],)).decode("utf-8") + event_when = cur.mogrify("tstzrange(now(),%s,'[]')", (req.params['stop'],)).decode("utf-8") else: event_when = """tstzrange(now(),now(),'[]')""" if 'what' in req.params: # limit search based on "what" - event_what = cur.mogrify(" AND events_what LIKE %s ",(req.params['what']+"%",)).decode("utf-8") + event_what = cur.mogrify(" AND events_what LIKE %s ", (req.params['what']+"%",)).decode("utf-8") else: event_what = "" if 'type' in req.params: # limit search based on type (scheduled, forecast, unscheduled) - event_type = cur.mogrify(" AND events_type = %s ",(req.params['type'],)).decode("utf-8") + event_type = cur.mogrify(" AND events_type = %s ", (req.params['type'],)).decode("utf-8") else: event_type = "" @@ -119,7 +124,7 @@ class EventResource(object): if req.params['geom'] == 'full': event_geom = "geom" - # search recent active events + # Search recent active events. cur.execute(""" SELECT '{"type":"Feature", "properties": '|| (events_tags::jsonb || jsonb_build_object('id',events_id,'createdate',createdate,'lastupdate',lastupdate """+event_dist+"""))::text ||', "geometry":'|| st_asgeojson("""+event_geom+""") ||' }' as feature FROM events @@ -154,7 +159,7 @@ WHERE events_id=%s;""", (id,)) # get request body payload (geojson Feature) body = req.stream.read().decode('utf-8') - j=json.loads(body) + j = json.loads(body) if "properties" not in j or "geometry" not in j: resp.body = "missing 'geometry' or 'properties' elements" resp.status = falcon.HTTP_400 @@ -176,10 +181,10 @@ WHERE events_id=%s;""", (id,)) # get the geometry part geometry=json.dumps(j['geometry']) h = self.maybe_insert_geometry(geometry,cur) - params = (j['properties']['type'],j['properties']['what'],event_start, event_stop, bounds, json.dumps(j['properties']),h[0]) - if (id): + params = (j['properties']['type'], j['properties']['what'], event_start, event_stop, bounds, json.dumps(j['properties']), h[0]) + if id: params = params + (id,) - cur.execute(query,params) + cur.execute(query, params) # get newly created event id e = cur.fetchone() db.commit() @@ -199,7 +204,7 @@ WHERE events_id=%s;""", (id,)) standard_headers(resp) db = db_connect() cur = db.cursor() - cur.execute("""DELETE FROM events WHERE events_id = %s;""",(id,)); + cur.execute("""DELETE FROM events WHERE events_id = %s;""", (id,)); db.commit() cur.close() db.close() From 5458bedd09c3fb9b2c6cb2c91954c0c14c0b8991 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 13 May 2016 20:58:37 +0200 Subject: [PATCH 02/10] Do not generate json by hand --- backend.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend.py b/backend.py index 13fa190..b91a8e2 100644 --- a/backend.py +++ b/backend.py @@ -36,7 +36,12 @@ class StatsResource(object): db.close() standard_headers(resp) - resp.body = """{"events_count": %s, "last_created": "%s", "last_updated": "%s"}""" % (stat[0], stat[1], stat[2]) + body = { + "events_count": stat[0], + "last_created": stat[1], + "last_updated": stat[2] + } + resp.body = json.dumps(body) resp.status = falcon.HTTP_200 From a9f1220bcacc4e1fb83a951ee14237c6ccfdd7f3 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 13 May 2016 21:10:58 +0200 Subject: [PATCH 03/10] Use DictCursor for stat query --- backend.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/backend.py b/backend.py index b91a8e2..e977ec4 100644 --- a/backend.py +++ b/backend.py @@ -4,6 +4,7 @@ import os import falcon import psycopg2 +import psycopg2.extras import json @@ -29,19 +30,14 @@ def standard_headers(resp): class StatsResource(object): def on_get(self, req, resp): db = db_connect() - cur = db.cursor() + cur = db.cursor(cursor_factory=psycopg2.extras.DictCursor) cur.execute("SELECT count(*) as events_count, max(createdate) as last_created, max(lastupdate) as last_updated from events;") stat = cur.fetchone() cur.close() db.close() standard_headers(resp) - body = { - "events_count": stat[0], - "last_created": stat[1], - "last_updated": stat[2] - } - resp.body = json.dumps(body) + resp.body = json.dumps(dict(stat)) resp.status = falcon.HTTP_200 From 71719e849f0bd965e571f7ec6e0bde9688218cc3 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 13 May 2016 21:11:33 +0200 Subject: [PATCH 04/10] import order --- backend.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend.py b/backend.py index e977ec4..f3077a8 100644 --- a/backend.py +++ b/backend.py @@ -1,11 +1,12 @@ # backend.py # openeventdatabase +import json import os + import falcon import psycopg2 import psycopg2.extras -import json def db_connect(): From 65dd8fad245a918883fc8423f6996ff9b4d8837a Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 13 May 2016 21:22:05 +0200 Subject: [PATCH 05/10] Simpler db connection pattern --- backend.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend.py b/backend.py index f3077a8..94f28e8 100644 --- a/backend.py +++ b/backend.py @@ -10,16 +10,11 @@ import psycopg2.extras def db_connect(): - try: - db = psycopg2.connect(dbname="oedb") - except: - db_host = os.getenv("DB_HOST", "localhost") - db_user = os.getenv("DB_USER", "oedb") - db_password = os.getenv("POSTGRES_PASSWORD", "") - db = psycopg2.connect(dbname="oedb", host=db_host, - password=db_password, user=db_user) - - return db + return psycopg2.connect( + dbname=os.getenv("DB_NAME", "oedb"), + host=os.getenv("DB_HOST", None), + password=os.getenv("POSTGRES_PASSWORD", None), + user=os.getenv("DB_USER", None)) def standard_headers(resp): From bc731e686cc7b73fdd997d1e68e9fbdbde6d585c Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 13 May 2016 21:25:33 +0200 Subject: [PATCH 06/10] Use Falcon middleware for common headers --- backend.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/backend.py b/backend.py index 94f28e8..f7d74e6 100644 --- a/backend.py +++ b/backend.py @@ -17,10 +17,12 @@ def db_connect(): user=os.getenv("DB_USER", None)) -def standard_headers(resp): - resp.set_header('X-Powered-By', 'OpenEventDatabase') - resp.set_header('Access-Control-Allow-Origin', '*') - resp.set_header('Access-Control-Allow-Headers', 'X-Requested-With') +class HeaderMiddleware: + + def process_response(self, req, resp, resource): + resp.set_header('X-Powered-By', 'OpenEventDatabase') + resp.set_header('Access-Control-Allow-Origin', '*') + resp.set_header('Access-Control-Allow-Headers', 'X-Requested-With') class StatsResource(object): @@ -32,7 +34,6 @@ class StatsResource(object): cur.close() db.close() - standard_headers(resp) resp.body = json.dumps(dict(stat)) resp.status = falcon.HTTP_200 @@ -46,7 +47,6 @@ class EventsResource(object): SELECT format('{"type":"Feature", "id": "'|| events_id::text ||'", "properties": '|| events_tags::text ||', "geometry":'|| st_asgeojson(geom)) ||' }' FROM events JOIN geo ON (hash=events_geo)"""); - standard_headers(resp) resp.body = """{"type": "FeatureCollection","features": [ """+""", """.join([x[0] for x in cur.fetchall()])+""" @@ -66,7 +66,6 @@ class EventResource(object): return h def on_get(self, req, resp, id=None): - standard_headers(resp) db = db_connect() cur = db.cursor() if id is None: @@ -152,7 +151,6 @@ WHERE events_id=%s;""", (id,)) db.close() def insert_or_update(self, req, resp, id, query): - standard_headers(resp) # get request body payload (geojson Feature) body = req.stream.read().decode('utf-8') @@ -198,7 +196,6 @@ WHERE events_id=%s;""", (id,)) self.insert_or_update(req, resp, id, """UPDATE events SET ( events_type, events_what, events_when, events_tags, events_geo) = (%s, %s, tstzrange(%s,%s,%s) , %s, %s) WHERE events_id = %s RETURNING events_id;""") def on_delete(self, req, resp, id): - standard_headers(resp) db = db_connect() cur = db.cursor() cur.execute("""DELETE FROM events WHERE events_id = %s;""", (id,)); @@ -210,8 +207,8 @@ WHERE events_id=%s;""", (id,)) else: resp.status = falcon.HTTP_404 -# falcon.API instances are callable WSGI apps -app = falcon.API() +# Falcon.API instances are callable WSGI apps. +app = falcon.API(middleware=[HeaderMiddleware()]) # Resources are represented by long-lived class instances events = EventsResource() From 49523bd566e6f90bba3d5dae738a6accae5b5d95 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 13 May 2016 21:49:52 +0200 Subject: [PATCH 07/10] Use DictCursor for events query --- backend.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/backend.py b/backend.py index f7d74e6..f2bbff2 100644 --- a/backend.py +++ b/backend.py @@ -39,18 +39,23 @@ class StatsResource(object): class EventsResource(object): + + def row_to_feature(self, row): + return { + "type": "Feature", + "geometry": json.loads(row['geometry']), + "id": row['events_id'], + "properties": row['events_tags'] + } + def on_get(self, req, resp): db = db_connect() - cur = db.cursor() - # get event geojson Feature - cur.execute(""" -SELECT format('{"type":"Feature", "id": "'|| events_id::text ||'", "properties": '|| events_tags::text ||', "geometry":'|| st_asgeojson(geom)) ||' }' -FROM events -JOIN geo ON (hash=events_geo)"""); - resp.body = """{"type": "FeatureCollection","features": [ -"""+""", -""".join([x[0] for x in cur.fetchall()])+""" -]}""" + cur = db.cursor(cursor_factory=psycopg2.extras.DictCursor) + cur.execute("SELECT events_id, events_tags, st_asgeojson(geom) as geometry FROM events JOIN geo ON (hash=events_geo)") + resp.body = json.dumps({ + "type": "FeatureCollection", + "features": [self.row_to_feature(r) for r in cur.fetchall()] + }) resp.status = falcon.HTTP_200 From 8b5107e6c166952d0a540429e65788c8c5be2730 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 17 May 2016 09:45:11 +0200 Subject: [PATCH 08/10] Factorize row_to_feature --- backend.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/backend.py b/backend.py index f2bbff2..4ca3302 100644 --- a/backend.py +++ b/backend.py @@ -38,20 +38,28 @@ class StatsResource(object): resp.status = falcon.HTTP_200 -class EventsResource(object): +class BaseEvent: def row_to_feature(self, row): + properties = dict(row['events_tags']) + properties.update({ + 'createdate': str(row['createdate']), + 'last_updated': str(row['lastupdate']) + }) return { "type": "Feature", "geometry": json.loads(row['geometry']), "id": row['events_id'], - "properties": row['events_tags'] + "properties": properties } + +class EventsResource(BaseEvent): + def on_get(self, req, resp): db = db_connect() cur = db.cursor(cursor_factory=psycopg2.extras.DictCursor) - cur.execute("SELECT events_id, events_tags, st_asgeojson(geom) as geometry FROM events JOIN geo ON (hash=events_geo)") + cur.execute("SELECT events_id, events_tags, createdate, lastupdate, st_asgeojson(geom) as geometry FROM events JOIN geo ON (hash=events_geo)") resp.body = json.dumps({ "type": "FeatureCollection", "features": [self.row_to_feature(r) for r in cur.fetchall()] @@ -59,10 +67,10 @@ class EventsResource(object): resp.status = falcon.HTTP_200 -class EventResource(object): +class EventResource(BaseEvent): def maybe_insert_geometry(self, geometry, cur): # insert into geo table if not existing - cur.execute("""INSERT INTO geo (hash, geom, geom_center) SELECT *, st_centroid(geom) FROM (SELECT md5(ewkt) as hash, st_setsrid(st_geomfromewkt(ewkt),4326) as geom FROM (SELECT st_asewkt(st_geomfromgeojson( %s )) as ewkt) as g) as i ON CONFLICT DO NOTHING RETURNING hash;""",(geometry,)) + cur.execute("""INSERT INTO geo (hash, geom, geom_center) SELECT *, st_centroid(geom) FROM (SELECT md5(ewkt) as hash, st_setsrid(st_geomfromewkt(ewkt),4326) as geom FROM (SELECT st_asewkt(st_geomfromgeojson( %s )) as ewkt) as g) as i ON CONFLICT DO NOTHING RETURNING hash;""", (geometry,)) # get its id (md5 hash) h = cur.fetchone() if h is None: @@ -72,7 +80,7 @@ class EventResource(object): def on_get(self, req, resp, id=None): db = db_connect() - cur = db.cursor() + cur = db.cursor(cursor_factory=psycopg2.extras.DictCursor) if id is None: # get query search parameters @@ -140,16 +148,12 @@ SELECT '{"type":"Feature", "properties": '|| (events_tags::jsonb || jsonb_build_ ]}""" resp.status = falcon.HTTP_200 else: - # get single event geojson Feature by id - cur.execute(""" -SELECT format('{"type":"Feature", "properties": '|| (events_tags::jsonb || jsonb_build_object('id',events_id,'createdate',createdate,'lastupdate',lastupdate))::text ||', "geometry":'|| st_asgeojson(geom)) ||' }' -FROM events -JOIN geo ON (hash=events_geo) -WHERE events_id=%s;""", (id,)) + # Get single event geojson Feature by id. + cur.execute("SELECT events_id, events_tags, createdate, lastupdate, st_asgeojson(geom) as geometry FROM events JOIN geo ON (hash=events_geo) WHERE events_id=%s", [id]) e = cur.fetchone() if e is not None: - resp.body = e[0] + resp.body = json.dumps(self.row_to_feature(e)) resp.status = falcon.HTTP_200 else: resp.status = falcon.HTTP_404 From a9af21418b44faf6bb5cbb01a3fc0c33a314c928 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 17 May 2016 10:05:39 +0200 Subject: [PATCH 09/10] Factorize rows_to_collection --- backend.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/backend.py b/backend.py index 4ca3302..06c4d1a 100644 --- a/backend.py +++ b/backend.py @@ -46,6 +46,8 @@ class BaseEvent: 'createdate': str(row['createdate']), 'last_updated': str(row['lastupdate']) }) + if "distance" in row: + properties['distance'] = row['distance'] return { "type": "Feature", "geometry": json.loads(row['geometry']), @@ -53,6 +55,12 @@ class BaseEvent: "properties": properties } + def rows_to_collection(self, rows): + return { + "type": "FeatureCollection", + "features": [self.row_to_feature(r) for r in rows] + } + class EventsResource(BaseEvent): @@ -60,10 +68,7 @@ class EventsResource(BaseEvent): db = db_connect() cur = db.cursor(cursor_factory=psycopg2.extras.DictCursor) cur.execute("SELECT events_id, events_tags, createdate, lastupdate, st_asgeojson(geom) as geometry FROM events JOIN geo ON (hash=events_geo)") - resp.body = json.dumps({ - "type": "FeatureCollection", - "features": [self.row_to_feature(r) for r in cur.fetchall()] - }) + resp.body = json.dumps(self.rows_to_collection(cur.fetchall())) resp.status = falcon.HTTP_200 @@ -95,8 +100,8 @@ class EventResource(BaseEvent): dist = 1 else: dist = req.params['near'][2] - event_bbox = cur.mogrify(" AND ST_Intersects(geom, ST_Buffer(st_setsrid(st_makepoint(%s,%s),4326)::geography,%s)::geometry) ",(req.params['near'][0], req.params['near'][1], dist)).decode("utf-8") - event_dist = cur.mogrify(", 'distance', ST_Length(ST_ShortestLine(geom, st_setsrid(st_makepoint(%s,%s),4326))::geography) ",(req.params['near'][0], req.params['near'][1])).decode("utf-8") + event_bbox = cur.mogrify(" AND ST_Intersects(geom, ST_Buffer(st_setsrid(st_makepoint(%s,%s),4326)::geography,%s)::geometry) ", (req.params['near'][0], req.params['near'][1], dist)).decode("utf-8") + event_dist = cur.mogrify("ST_Length(ST_ShortestLine(geom, st_setsrid(st_makepoint(%s,%s),4326))::geography) as distance,", (req.params['near'][0], req.params['near'][1])).decode("utf-8") else: event_bbox = "" event_dist = "" @@ -114,7 +119,7 @@ class EventResource(BaseEvent): # limit search with fixed time (now to stop) event_when = cur.mogrify("tstzrange(now(),%s,'[]')", (req.params['stop'],)).decode("utf-8") else: - event_when = """tstzrange(now(),now(),'[]')""" + event_when = "tstzrange(now(),now(),'[]')" if 'what' in req.params: # limit search based on "what" @@ -134,18 +139,13 @@ class EventResource(BaseEvent): event_geom = "geom" # Search recent active events. - cur.execute(""" -SELECT '{"type":"Feature", "properties": '|| (events_tags::jsonb || jsonb_build_object('id',events_id,'createdate',createdate,'lastupdate',lastupdate """+event_dist+"""))::text ||', "geometry":'|| st_asgeojson("""+event_geom+""") ||' }' as feature - FROM events - JOIN geo ON (hash=events_geo) """ + event_bbox +""" - WHERE events_when && """+ event_when + event_what + event_type +""" - ORDER BY createdate DESC - LIMIT 200; -""") - resp.body = """{"type":"FeatureCollection", "features": [ -"""+""", -""".join([x[0] for x in cur.fetchall()])+""" -]}""" + sql = """SELECT events_id, events_tags, createdate, lastupdate, {event_dist} st_asgeojson({event_geom}) as geometry FROM events JOIN geo ON (hash=events_geo) {event_bbox} WHERE events_when && {event_when} {event_what} {event_type} ORDER BY createdate DESC LIMIT 200""" + # No user generated content here, so format is safe. + sql = sql.format(event_dist=event_dist, event_geom=event_geom, + event_bbox=event_bbox, event_what=event_what, + event_when=event_when, event_type=event_type) + cur.execute(sql) + resp.body = json.dumps(self.rows_to_collection(cur.fetchall())) resp.status = falcon.HTTP_200 else: # Get single event geojson Feature by id. From 06a40638db5668546354976c1e786bcf7dabbdcc Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 17 May 2016 16:03:13 +0200 Subject: [PATCH 10/10] Use our own json encoder to deal with datetime --- backend.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/backend.py b/backend.py index 06c4d1a..b95abb9 100644 --- a/backend.py +++ b/backend.py @@ -1,6 +1,7 @@ # backend.py # openeventdatabase +from datetime import datetime import json import os @@ -17,6 +18,20 @@ def db_connect(): user=os.getenv("DB_USER", None)) +class EventEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, datetime): + return o.isoformat() + try: + return super().default(o) + except TypeError: + return str(o) + + +def dumps(data): + return json.dumps(data, cls=EventEncoder) + + class HeaderMiddleware: def process_response(self, req, resp, resource): @@ -34,7 +49,7 @@ class StatsResource(object): cur.close() db.close() - resp.body = json.dumps(dict(stat)) + resp.body = dumps(dict(stat)) resp.status = falcon.HTTP_200 @@ -43,8 +58,8 @@ class BaseEvent: def row_to_feature(self, row): properties = dict(row['events_tags']) properties.update({ - 'createdate': str(row['createdate']), - 'last_updated': str(row['lastupdate']) + 'createdate': row['createdate'], + 'last_updated': row['lastupdate'] }) if "distance" in row: properties['distance'] = row['distance'] @@ -68,7 +83,7 @@ class EventsResource(BaseEvent): db = db_connect() cur = db.cursor(cursor_factory=psycopg2.extras.DictCursor) cur.execute("SELECT events_id, events_tags, createdate, lastupdate, st_asgeojson(geom) as geometry FROM events JOIN geo ON (hash=events_geo)") - resp.body = json.dumps(self.rows_to_collection(cur.fetchall())) + resp.body = dumps(self.rows_to_collection(cur.fetchall())) resp.status = falcon.HTTP_200 @@ -145,7 +160,7 @@ class EventResource(BaseEvent): event_bbox=event_bbox, event_what=event_what, event_when=event_when, event_type=event_type) cur.execute(sql) - resp.body = json.dumps(self.rows_to_collection(cur.fetchall())) + resp.body = dumps(self.rows_to_collection(cur.fetchall())) resp.status = falcon.HTTP_200 else: # Get single event geojson Feature by id. @@ -153,7 +168,7 @@ class EventResource(BaseEvent): e = cur.fetchone() if e is not None: - resp.body = json.dumps(self.row_to_feature(e)) + resp.body = dumps(self.row_to_feature(e)) resp.status = falcon.HTTP_200 else: resp.status = falcon.HTTP_404 @@ -183,9 +198,9 @@ class EventResource(BaseEvent): db = db_connect() cur = db.cursor() # get the geometry part - geometry=json.dumps(j['geometry']) + geometry=dumps(j['geometry']) h = self.maybe_insert_geometry(geometry,cur) - params = (j['properties']['type'], j['properties']['what'], event_start, event_stop, bounds, json.dumps(j['properties']), h[0]) + params = (j['properties']['type'], j['properties']['what'], event_start, event_stop, bounds, dumps(j['properties']), h[0]) if id: params = params + (id,) cur.execute(query, params)