Changeset 876
- Timestamp:
- 08/04/07 01:19:30 (1 year ago)
- Files:
-
- hammock/trunk/data.json (deleted)
- hammock/trunk/etc (added)
- hammock/trunk/etc/makefile_postgis (added)
- hammock/trunk/etc/places.sql (added)
- hammock/trunk/hammock.py (modified) (2 diffs)
- hammock/trunk/infoset.py (deleted)
- hammock/trunk/model.py (modified) (1 diff)
- hammock/trunk/sqltypes.py (added)
- hammock/trunk/templates/atom_entry.xml (added)
- hammock/trunk/templates/atom_feed.xml (added)
- hammock/trunk/templates/atom_service.xml (added)
- hammock/trunk/templates/collection.html (modified) (2 diffs)
- hammock/trunk/templates/collection.kml (added)
- hammock/trunk/templates/entry.xml (deleted)
- hammock/trunk/templates/item.html (modified) (2 diffs)
- hammock/trunk/templates/service.xml (deleted)
- hammock/trunk/urls.py (modified) (1 diff)
- hammock/trunk/view.py (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
hammock/trunk/hammock.py
r848 r876 1 1 #!env python 2 2 3 import daemon4 3 import model 5 4 import os … … 8 7 if __name__ == '__main__': 9 8 10 code = daemon.createDaemon()11 12 9 from wsgiref.simple_server import WSGIServer, WSGIRequestHandler 13 10 httpd = WSGIServer(('', 8081), WSGIRequestHandler) 14 11 httpd.set_app(urls.urls) 15 try:16 model.COLLECTION.load()17 except IOError:18 pass19 12 print "Serving HTTP on %s port %s ..." % httpd.socket.getsockname() 20 13 try: 21 f = open('hammock.pid', 'wb')22 f.write(str(os.getpid()))23 f.close()24 14 httpd.serve_forever() 25 15 except KeyboardInterrupt: 26 model.COLLECTION.save()27 16 raise 28 17 hammock/trunk/model.py
r861 r876 1 """2 SimpleFeature is a working example of a class that satisfies the Python GIS3 feature protocol.4 """5 1 6 import geojson 7 import time 2 from StringIO import StringIO 8 3 9 import infoset 4 from lxml import etree 5 from sqlalchemy import * 6 from sqltypes import Geometry 10 7 11 class PropertyCollection(dict): 12 13 """ 14 A Collection of feature properties. 15 """ 16 17 def __getattr__(self, name): 18 return self[name] 19 20 def __setattr__(self, name, value): 21 self[name] = value 8 from shapely.geometry import Point 22 9 23 10 24 class SimpleFeature(object): 11 ENGINE = create_engine("postgres://postgres:postgres@localhost/hammock0") 25 12 26 """ 27 A simple, Atom-ish, single geometry (WGS84) GIS feature. 28 """ 13 metadata = MetaData(ENGINE) 29 14 30 def __init__(self, id=None, geometry=None, title=None, summary=None, 31 published=None, updated=None): 32 """Initialize.""" 33 self.id = id 34 self.geometry = geometry 35 self.properties = PropertyCollection() 36 self.properties['title'] = title 37 self.properties['summary'] = summary 38 self.properties['published'] = published 39 self.properties['updated'] = updated 15 PLACES = Table( 16 'places', 17 metadata, 18 Column('place_id', Integer, primary_key=True), 19 Column('published', DateTime, default=func.current_timestamp()), 20 Column('updated', DateTime, 21 default=func.current_timestamp(), 22 onupdate=func.current_timestamp() 23 ), 24 Column('title', String(60)), 25 Column('summary', String()), 26 Column('kml', String()), 27 Column('the_geom', Geometry(4326, 'POINT')) 28 ) 40 29 41 def __getitem__(self, key): 42 """To satisfy the Python GIS feature protocol, we need to handle at 43 least id, geometry, and properties keys. 44 """ 45 try: 46 return getattr(self, key) 47 except AttributeError, e: 48 raise KeyError, e 30 class SQLCollection(object): 49 31 50 51 def createSimpleFeature(o): 52 """Create an instance of SimpleWebFeature from a dict, o. If o does not 53 match a Python feature object, simply return o. This function serves as a 54 simplejson decoder hook. See coding.load().""" 55 try: 56 id = o.get('id') 57 g = o.get('geometry') 58 p = o.get('properties') 59 return SimpleFeature(str(id), 60 {'type': str(g.get('type', None)), 61 'coordinates': g.get('coordinates', [])}, 62 title=p.get('title', None), 63 summary=p.get('summary', None), 64 published=str(p.get('published', None)), 65 updated=str(p.get('updated', None))) 66 except (KeyError, TypeError, AttributeError): 32 def __init__(self): 67 33 pass 68 return o69 70 71 # Collection72 73 class SimpleCollection(object):74 75 def __init__(self, size=30):76 self.size = 3077 self.dsname = 'data.json'78 self.data = [79 ('0', SimpleFeature('0',80 geometry={'type': 'Point', 'coordinates': [-105.8, 40.5]},81 title=u'Feature 0', summary=u'The canonical feature',82 published='2007-04-09T12:48:36-06:00',83 updated='2007-04-09T12:48:36-06:00'))84 ]85 86 def save(self):87 f = open(self.dsname, 'wb')88 geojson.dump(self.data, f)89 f.close()90 91 def load(self):92 f = open(self.dsname, 'rb')93 self.data = geojson.load(f, object_hook=createSimpleFeature)94 f.close()95 34 96 35 def get(self, key): 97 d = dict(self.data) 98 if key in d.keys(): 99 return d[key] 100 else: 36 result = PLACES.select(PLACES.c.place_id==key).execute() 37 return result.fetchone() 38 39 def __iter__(self): 40 result = PLACES.select().execute() 41 return iter(result.fetchall()) 42 43 def add(self, atom=None, kml=None): 44 if atom: 45 params = parse_atom(atom) 46 elif kml: 47 params = parse_kml(kml) 48 params["kml"] = kml 49 connection = ENGINE.connect() 50 transaction = connection.begin() 51 try: 52 connection.execute( 53 PLACES.insert(), 54 params, 55 published=func.current_timestamp(), 56 updated=func.current_timestamp(), 57 ) 58 row = connection.execute( 59 PLACES.select( 60 order_by=[desc(PLACES.c.place_id)], 61 limit=1 62 ) 63 ).fetchone() 64 newid = row['place_id'] 65 transaction.commit() 66 connection.close() 67 return newid 68 except: 69 transaction.rollback() 70 connection.close() 101 71 return None 102 72 103 def __iter__(self): 104 return iter(self.data) 73 def set(self, i, atom=None, kml=None): 74 if atom: 75 params = parse_atom(atom) 76 elif kml: 77 params = parse_kml(kml) 78 params["kml"] = kml 79 connection = ENGINE.connect() 80 transaction = connection.begin() 81 try: 82 connection.execute( 83 PLACES.update(PLACES.c.place_id==i), 84 params, 85 ) 86 transaction.commit() 87 connection.close() 88 except: 89 transaction.rollback() 90 connection.close() 91 raise 105 92 106 def add(self, xml=None, json=None): 107 try: 108 if xml: 109 ob = createSimpleFeature(infoset.parse_atom(xml)) 110 elif json: 111 ob = geojson.loads(json, object_hook=createSimpleFeature) 112 ob.id = str(int(self.data[0][0]) + 1) 113 ob.properties.published = \ 114 time.strftime('%Y-%m-%dT%H:%M:%S+00:00', time.gmtime()) 115 ob.properties.updated = ob.properties.published 116 except ValueError: 117 return None 118 if len(self.data) == self.size: 119 del self.data[-1] 120 self.data.insert(0, (ob.id, ob)) 121 return ob.id 93 @property 94 def updated(self): 95 result = PLACES.select( 96 PLACES.c.updated != None, 97 order_by=[desc(PLACES.c.updated)], 98 limit=1 99 ).execute().fetchone() 100 return result['updated'] 101 122 102 123 def set(self, i, xml=None): 124 for j in range(len(self.data)): 125 n, prev = self.data[j] 126 if i == n: 127 try: 128 if xml: 129 ob = createSimpleFeature(infoset.parse_atom(xml)) 130 ob.id = str(i) 131 ob.properties.published = prev.properties.published 132 ob.properties.updated = \ 133 time.strftime('%Y-%m-%dT%H:%M:%S+00:00', time.gmtime()) 134 except ValueError: 135 return None 136 self.data[j] = (str(i), ob) 137 break 138 139 COLLECTION = SimpleCollection() 103 COLLECTION = SQLCollection() 140 104 105 atom_nsd = { 106 'atom': "http://www.w3.org/2005/Atom", 107 'georss': "http://www.georss.org/georss" 108 } 109 110 def parse_atom(data): 111 tree = etree.parse(StringIO(data)) 112 root = tree.getroot() 113 try: 114 title = root.xpath('atom:title', atom_nsd)[0].text 115 except: 116 title = None 117 try: 118 summary = root.xpath('atom:summary', atom_nsd)[0].text 119 except: 120 summary = None 121 try: 122 lat, long = root.xpath('georss:point', atom_nsd)[0].text.split() 123 the_geom = Point(float(long), float(lat)) 124 except: 125 the_geom = None 126 127 return {"title": title, "summary": summary, "the_geom": the_geom} 128 129 kml_nsd = { 130 "kml": "http://earth.google.com/kml/2.1" 131 } 132 133 def parse_kml(data): 134 tree = etree.parse(StringIO(data)) 135 root = tree.getroot() 136 try: 137 title = root.xpath('kml:Document/kml:Placemark/kml:name', kml_nsd)[0].text 138 except: 139 title = None 140 try: 141 summary = root.xpath("kml:Document/kml:Placemark/kml:description", kml_nsd)[0].text 142 except: 143 summary = None 144 try: 145 long, lat, z = root.xpath("kml:Document/kml:Placemark/kml:Point/kml:coordinates", kml_nsd)[0].text.split(",") 146 the_geom = Point(float(long), float(lat)) 147 except: 148 the_geom = None 149 150 return {"title": title, "summary": summary, "the_geom": the_geom} 151 hammock/trunk/templates/collection.html
r854 r876 17 17 <div><a href="/index.html">Back to service index</a></div> 18 18 19 <h3>Properties</h3> 20 <div class="widget"> 21 <label>Updated:</label> 22 <br/> 23 <span py:content="updated.strftime('%Y-%m-%dT%H:%M:%S-06:00')">VALUE</span> 24 </div> 25 19 26 <h3>Items</h3> 20 <div py:for="i d, item in collection">27 <div py:for="item in collection"> 21 28 <a 22 py:with="atts={'href': 'features/%s.html' % i d}"29 py:with="atts={'href': 'features/%s.html' % item['place_id']}" 23 30 py:attrs="atts" 24 py:content="item .properties['title']"31 py:content="item['title']" 25 32 > 26 33 x … … 29 36 30 37 <h3>Alternate Representations</h3> 31 <div><a href="features .atom">Atom</a></div>38 <div><a href="features/">Atom</a></div> 32 39 <div><a href="features.kml">KML</a></div> 33 <div><a href="features">JSON</a></div>34 40 35 41 <p id="fine-print">Under-powered by Hammock</p> hammock/trunk/templates/item.html
r849 r876 17 17 <h1 py:content="title">Item</h1> 18 18 <div><a href="../features.html">Back to collection</a></div> 19 <h2 py:content="item .properties['title']">Title</h2>19 <h2 py:content="item['title']">Title</h2> 20 20 21 21 <h3>Geometry</h3> … … 23 23 <label>Type:</label> 24 24 <br/> 25 <span py:content="item .geometry['type']">VALUE</span>25 <span py:content="item['the_geom'].geom_type">VALUE</span> 26 26 </div> 27 27 <div class="widget"> 28 28 <label>Coordinates:</label> 29 29 <br/> 30 <span py:content=" str(item.geometry['coordinates'])">VALUE</span>30 <span py:content="item['the_geom'].wkt">VALUE</span> 31 31 </div> 32 32 33 33 <h3>Properties</h3> 34 <div 35 py:for="key in ['summary', 'published', 'updated']" 36 class="widget" 37 > 38 <label><span py:content="key.capitalize()">LABEL</span>:</label> 34 <div class="widget"> 35 <label>Summary:</label> 39 36 <br/> 40 <span py:content="item.properties[key]">VALUE</span> 37 <span py:content="item['summary']">VALUE</span> 38 </div> 39 <div class="widget"> 40 <label>Published:</label> 41 <br/> 42 <span py:content="item['published'].strftime('%Y-%m-%dT%H:%M:%S-06:00')">VALUE</span> 43 </div> 44 <div class="widget"> 45 <label>Updated:</label> 46 <br/> 47 <span py:content="item['updated'].strftime('%Y-%m-%dT%H:%M:%S-06:00')">VALUE</span> 41 48 </div> 42 49 43 50 <h3>Alternate Representations</h3> 44 <div><a py:attrs="{'href': './%s' % item.id}">JSON</a></div> 51 <div><a py:attrs="{'href': './%s' % item['place_id']}">Atom</a></div> 52 <div><a py:attrs="{'href': './%s.kml' % item['place_id']}">KML</a></div> 45 53 46 54 <p id="fine-print">Under-powered by Hammock</p> hammock/trunk/urls.py
r861 r876 5 5 urls.add('/index.html', GET=view.service_html) 6 6 urls.add('/index.atom', GET=view.service_atom) 7 urls.add('/features[/]', GET=view.list, POST=view.feature_post) 7 8 urls.add('/features[/]', 9 GET=view.collection_atom, POST=view.collection_POST 10 ) 11 urls.add('/features.atom', 12 GET=view.collection_atom, POST=view.collection_POST 13 ) 8 14 urls.add('/features.html', GET=view.collection_html) 9 urls.add('/features.atom', GET=view.atom, POST=view.feature_post_atom) 10 urls.add('/features.kml', GET=view.kml) 11 urls.add('/features/{id}[/]', GET=view.feature_get) 15 urls.add('/features.kml', GET=view.collection_kml) 16 #urls.add('/features/main.kml', GET=view.collection_nwlink) 17 18 urls.add('/features/{id}[/]', GET=view.feature_atom, PUT=view.feature_PUT) 19 urls.add('/features/{id}.atom', GET=view.feature_atom, PUT=view.feature_PUT) 12 20 urls.add('/features/{id}.html', GET=view.feature_html) 13 urls.add('/features/{id}.atom', GET=view.feature_atom, PUT=view.feature_edit) 14 urls.add('/features/{id}.json', GET=view.feature_get) 21 urls.add('/features/{id}.kml', GET=view.feature_kml, PUT=view.feature_PUT) 15 22 hammock/trunk/view.py
r861 r876 5 5 6 6 import envutil 7 import geojson8 import infoset9 7 import model 10 8 … … 22 20 23 21 def service_atom(environ, start_response): 24 tmpl = loader.load(' service.xml')22 tmpl = loader.load('atom_service.xml') 25 23 stream = tmpl.generate(site_url=envutil.site_url(environ)) 26 24 start_response("200 OK", [('Content-Type', 'application/atomsvc+xml')]) 27 25 return [stream.render()] 28 29 def list(environ, start_response):30 list = []31 url = envutil.request_url(environ)32 for item in model.COLLECTION:33 list.append({'id': item[1].id, 'uri': "%s/%s" % (url, item[1].id)})34 start_response("200 OK", [('Content-Type', 'text/plain')])35 return [geojson.dumps({'members': list})]36 26 37 27 def collection_html(environ, start_response): … … 39 29 stream = tmpl.generate( 40 30 title='Collection: Features', 31 updated=model.COLLECTION.updated, 41 32 collection=model.COLLECTION 42 33 ) … … 44 35 return [stream.render()] 45 36 46 def atom(environ, start_response): 47 feed = infoset.build_atom(environ, model.COLLECTION) 37 def collection_atom(environ, start_response): 38 tmpl = loader.load('atom_feed.xml') 39 self_url = envutil.request_url(environ) 40 alt_url = "%s/%s" % (envutil.site_url(environ), 'features.html') 41 stream = tmpl.generate( 42 title='Collection: Features', 43 self_url=self_url, 44 alt_url=alt_url, 45 updated=model.COLLECTION.updated, 46 collection=model.COLLECTION 47 ) 48 48 start_response("200 OK", [('Content-Type', 'application/atom+xml')]) 49 return [ '<?xml version="1.0" encoding="utf-8"?>', infoset.tostring(feed)]49 return [stream.render()] 50 50 51 def kml(environ, start_response): 52 kml = infoset.build_kml(environ, model.COLLECTION) 51 def collection_kml(environ, start_response): 52 tmpl = loader.load('collection.kml') 53 self_url = "%s/%s" % (envutil.site_url(environ), 'features/') 54 alt_url = "%s/%s" % (envutil.site_url(environ), 'features.html') 55 stream = tmpl.generate( 56 title='Collection: Features', 57 self_url=self_url, 58 alt_url=alt_url, 59 collection=model.COLLECTION 60 ) 53 61 start_response("200 OK", [('Content-Type', 'application/vnd.google-earth.kml+xml')]) 54 return ['<?xml version="1.0" encoding="utf-8"?>', infoset.tostring(kml)] 62 return [stream.render()] 63 64 def collection_POST(environ, start_response): 65 data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) 66 if environ['CONTENT_TYPE'] == 'application/atom+xml': 67 fid = model.COLLECTION.add(atom=data) 68 elif environ['CONTENT_TYPE'] == 'application/vnd.google-earth.kml+xml': 69 fid = model.COLLECTION.add(kml=data) 70 if fid is None: 71 start_response("400 Bad Request", [('Content-Type', 'text/plain')]) 72 return ['Posted data is malformed: ', data] 73 else: 74 url = envutil.request_url(environ) 75 start_response("200 OK", [('Content-Type', 'text/plain')]) 76 return ['Feature created with URI ', "%s/%s.atom" % (url[:-5], fid)] 55 77 56 78 def feature_html(environ, start_response): … … 59 81 tmpl = loader.load('item.html') 60 82 stream = tmpl.generate( 61 title='Feature: %s' % f .id,83 title='Feature: %s' % f['place_id'], 62 84 item = f 63 85 ) … … 68 90 fid = environ['wsgiorg.routing_args'][1]['id'] 69 91 f = model.COLLECTION.get(fid) 70 tmpl = loader.load(' entry.xml')92 tmpl = loader.load('atom_entry.xml') 71 93 stream = tmpl.generate(item = f) 72 94 start_response("200 OK", [('Content-Type', 'application/atom+xml')]) 73 95 return [stream.render()] 74 96 75 def feature_ get(environ, start_response):97 def feature_kml(environ, start_response): 76 98 fid = environ['wsgiorg.routing_args'][1]['id'] 77 99 f = model.COLLECTION.get(fid) 78 if not f: 79 start_response("404 Not Found", [('Content-Type', 'text/plain')]) 80 return [] 81 else: 82 start_response("200 OK", [('Content-Type', 'text/plain')]) 83 return [geojson.dumps(f)] 100 tmpl = loader.load('collection.kml') 101 self_url = "%s/%s" % (envutil.site_url(environ), 'features/') 102 alt_url = "%s/%s" % (envutil.site_url(environ), 'features.html') 103 stream = tmpl.generate( 104 title='Feature %s' % f['place_id'], 105 self_url=self_url, 106 alt_url=alt_url, 107 collection=[f] 108 ) 109 start_response("200 OK", [('Content-Type', 'application/vnd.google-earth.kml+xml')]) 110 return [stream.render()] 84 111 85 def feature_post(environ, start_response): 86 data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) 87 fid = model.COLLECTION.add(json=data) 88 if fid is None: 89 start_response("400 Bad Request", [('Content-Type', 'text/plain')]) 90 return ['Posted data is malformed: ', data] 91 else: 92 url = envutil.request_url(environ) 93 start_response("200 OK", [('Content-Type', 'text/plain')]) 94 return ['Feature created with URI ', "%s/%s" % (url, fid)] 95 96 def feature_post_atom(environ, start_response): 97 data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) 98 fid = model.COLLECTION.add(xml=data) 99 if fid is None: 100 start_response("400 Bad Request", [('Content-Type', 'text/plain')]) 101 return ['Posted data is malformed: ', data] 102 else: 103 url = envutil.request_url(environ) 104 start_response("200 OK", [('Content-Type', 'text/plain')]) 105 return ['Feature created with URI ', "%s/%s.atom" % (url[:-5], fid)] 106 107 def feature_edit(environ, start_response): 112 def feature_PUT(environ, start_response): 108 113 fid = environ['wsgiorg.routing_args'][1]['id'] 109 114 data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) … … 112 117 return ['Posted data is malformed: ', data] 113 118 else: 114 model.COLLECTION.set(fid, xml=data) 119 if environ['CONTENT_TYPE'] == 'application/atom+xml': 120 model.COLLECTION.set(fid, atom=data) 121 elif environ['CONTENT_TYPE'] == 'application/vnd.google-earth.kml+xml': 122 model.COLLECTION.set(fid, kml=data) 115 123 url = envutil.request_url(environ) 116 124 start_response("200 OK", [('Content-Type', 'text/plain')])
