Navigation Menu

Skip to content

Commit

Permalink
Merge pull request #73 from ryanbreen/external-config
Browse files Browse the repository at this point in the history
Add ExternalConfigServer
  • Loading branch information
migurski committed Jul 17, 2012
2 parents 3aa1c16 + 2094afa commit 3798eaf
Showing 1 changed file with 138 additions and 0 deletions.
138 changes: 138 additions & 0 deletions TileStache/Goodies/ExternalConfigServer.py
@@ -0,0 +1,138 @@
""" ExternalConfigServer is a replacement for WSGITileServer that uses external
configuration fetched via HTTP to service all config requests.
Example usage, with gunicorn (http://gunicorn.org):
gunicorn --bind localhost:8888 "TileStache.Goodies.ExternalConfigServer:WSGIServer(url)"
"""

from urllib import urlopen
from sets import Set
import logging

try:
from json import load as json_load
except ImportError:
from simplejson import load as json_load

import TileStache

class DynamicLayers:

def __init__(self, config, url_root, cache_responses, dirpath):
self.config = config
self.url_root = url_root
self.dirpath = dirpath
self.cache_responses = cache_responses;
self.seen_layers = {}
self.lookup_failures = Set()

def keys(self):
return self.seen_layers.keys()

def items(self):
return self.seen_layers.items()

def parse_layer(self, layer_json):
layer_dict = json_load(layer_json)
return TileStache.Config._parseConfigfileLayer(layer_dict, self.config, self.dirpath)

def __contains__(self, key):
# If caching is enabled and we've seen a request for this layer before, return True unless
# the prior lookup failed to find this layer.
if self.cache_responses:
if key in self.seen_layers:
return True
elif key in self.lookup_failures:
return False

res = urlopen(self.url_root + "/layer/" + key)

if self.cache_responses:
if res.getcode() != 200:
# Cache a failed lookup
self.lookup_failures.add(key)
else :
# If lookup succeeded and we are caching, parse the layer now so that a subsequent
# call to __getitem__ doesn't require a call to the config server. If we aren't
# caching, we skip this step to avoid an unnecessary json parse.
try:
self.seen_layers[key] = self.parse_layer(res)
except ValueError:
# The JSON received by the config server was invalid. Treat this layer as a
# failure. We don't want to raise ValueError from here because other parts
# of TileStache are just expecting a boolean response from __contains__
logging.error("Invalid JSON response seen for %s", key)
self.lookup_failures.add(key)
return False

if res.getcode() != 200:
logging.info("Config response code %s for %s", res.getcode(), key)
return res.getcode() == 200

def __getitem__(self, key):
if self.cache_responses:
if key in self.seen_layers:
return self.seen_layers[key]
elif key in self.lookup_failures:
# If we are caching, raise KnownUnknown if we have previously failed to find this layer
raise TileStache.KnownUnknown("Layer %s previously not found", key)

logging.debug("Requesting layer %s", self.url_root + "/layer/" + key)
res = urlopen(self.url_root + "/layer/" + key)
if (res.getcode() != 200) :
logging.info("Config response code %s for %s", res.getcode(), key)
if (self.cache_responses) :
self.lookup_failures.add(key)
raise TileStache.KnownUnknown("Layer %s not found", key)

try :
layer = self.parse_layer(res)
self.seen_layers[key] = layer
return layer
except ValueError:
logging.error("Invalid JSON response seen for %s", key)
if (self.cache_responses) :
# If caching responses, cache this failure
self.lookup_failures.add(key)
# KnownUnknown seems like the appropriate thing to raise here since this is akin
# to a missing configuration.
raise TileStache.KnownUnknown("Failed to parse JSON configuration for %s", key)

class ExternalConfiguration:

def __init__(self, url_root, cache_dict, cache_responses, dirpath):
self.cache = TileStache.Config._parseConfigfileCache(cache_dict, dirpath)
self.dirpath = dirpath
self.layers = DynamicLayers(self, url_root, cache_responses, dirpath)

class WSGIServer (TileStache.WSGITileServer):

"""
Wrap WSGI application, passing it a custom configuration.
The WSGI application is an instance of TileStache:WSGITileServer.
This method is initiated with a url_root that contains the scheme, host, port
and path that must prefix the API calls on our local server. Any valid http
or https urls should work.
The cache_responses parameter tells TileStache to cache all responses from
the configuration server.
"""

def __init__(self, url_root, cache_responses=True, debug_level="DEBUG"):
logging.basicConfig(level=debug_level)

# Call API server at url to grab cache_dict
cache_dict = json_load(urlopen(url_root + "/cache"))

dirpath = '/tmp/stache'

config = ExternalConfiguration(url_root, cache_dict, cache_responses, dirpath)

TileStache.WSGITileServer.__init__(self, config, False)

def __call__(self, environ, start_response):
response = TileStache.WSGITileServer.__call__(self, environ, start_response)
return response

0 comments on commit 3798eaf

Please sign in to comment.