Merge pull request #146 from CartoDB/development

Version 0.6.2
This commit is contained in:
Mario de Frutos 2016-04-18 10:26:30 +02:00
commit ad5d25f0a0
18 changed files with 1449 additions and 108 deletions

View File

@ -13,8 +13,8 @@ OLD_VERSIONS = $(wildcard old_versions/*.sql)
# @see http://www.postgresql.org/docs/current/static/extend-pgxs.html # @see http://www.postgresql.org/docs/current/static/extend-pgxs.html
DATA = $(NEW_EXTENSION_ARTIFACT) \ DATA = $(NEW_EXTENSION_ARTIFACT) \
$(OLD_VERSIONS) \ $(OLD_VERSIONS) \
cdb_dataservices_server--0.6.1--0.6.0.sql \ cdb_dataservices_server--0.6.2--0.6.1.sql \
cdb_dataservices_server--0.6.0--0.6.1.sql cdb_dataservices_server--0.6.1--0.6.2.sql
REGRESS = $(notdir $(basename $(wildcard test/sql/*test.sql))) REGRESS = $(notdir $(basename $(wildcard test/sql/*test.sql)))
TEST_DIR = test/ TEST_DIR = test/

View File

@ -0,0 +1,102 @@
--DO NOT MODIFY THIS FILE, IT IS GENERATED AUTOMATICALLY FROM SOURCES
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION cdb_dataservices_server UPDATE TO '0.6.2'" to load this file. \quit
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_geocode_street_point(username TEXT, orgname TEXT, searchtext TEXT, city TEXT DEFAULT NULL, state_province TEXT DEFAULT NULL, country TEXT DEFAULT NULL)
RETURNS Geometry AS $$
from cartodb_services.mapzen import MapzenGeocoder
from cartodb_services.mapzen.types import country_to_iso3
from cartodb_services.metrics import QuotaService
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_geocoder_config = GD["user_geocoder_config_{0}".format(username)]
quota_service = QuotaService(user_geocoder_config, redis_conn)
if not quota_service.check_user_quota():
plpy.error('You have reach the limit of your quota')
try:
geocoder = MapzenGeocoder(user_geocoder_config.mapzen_api_key)
country_iso3 = None
if country:
country_iso3 = country_to_iso3(country)
coordinates = geocoder.geocode(searchtext=searchtext, city=city,
state_province=state_province,
country=country_iso3)
if coordinates:
quota_service.increment_success_service_use()
plan = plpy.prepare("SELECT ST_SetSRID(ST_MakePoint($1, $2), 4326); ", ["double precision", "double precision"])
point = plpy.execute(plan, [coordinates[0], coordinates[1]], 1)[0]
return point['st_setsrid']
else:
quota_service.increment_empty_service_use()
return None
except BaseException as e:
import sys, traceback
type_, value_, traceback_ = sys.exc_info()
quota_service.increment_failed_service_use()
error_msg = 'There was an error trying to geocode using mapzen geocoder: {0}'.format(e)
plpy.notice(traceback.format_tb(traceback_))
plpy.error(error_msg)
finally:
quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu;
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_route_point_to_point(
username TEXT,
orgname TEXT,
origin geometry(Point, 4326),
destination geometry(Point, 4326),
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
import json
from cartodb_services.mapzen import MapzenRouting, MapzenRoutingResponse
from cartodb_services.mapzen.types import polyline_to_linestring
from cartodb_services.metrics import QuotaService
from cartodb_services.tools import Coordinate
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_routing_config = GD["user_routing_config_{0}".format(username)]
quota_service = QuotaService(user_routing_config, redis_conn)
if not quota_service.check_user_quota():
plpy.error('You have reach the limit of your quota')
try:
client = MapzenRouting(user_routing_config.mapzen_api_key)
if not origin or not destination:
plpy.notice("Empty origin or destination")
quota_service.increment_empty_service_use()
return [None, None, None]
orig_lat = plpy.execute("SELECT ST_Y('%s') AS lat" % origin)[0]['lat']
orig_lon = plpy.execute("SELECT ST_X('%s') AS lon" % origin)[0]['lon']
origin_coordinates = Coordinate(orig_lon, orig_lat)
dest_lat = plpy.execute("SELECT ST_Y('%s') AS lat" % destination)[0]['lat']
dest_lon = plpy.execute("SELECT ST_X('%s') AS lon" % destination)[0]['lon']
dest_coordinates = Coordinate(dest_lon, dest_lat)
resp = client.calculate_route_point_to_point(origin_coordinates, dest_coordinates, mode, options, units)
if resp and resp.shape:
shape_linestring = polyline_to_linestring(resp.shape)
if shape_linestring:
quota_service.increment_success_service_use()
return [shape_linestring, resp.length, resp.duration]
else:
quota_service.increment_empty_service_use()
return [None, None, None]
else:
quota_service.increment_empty_service_use()
return [None, None, None]
except BaseException as e:
import sys, traceback
type_, value_, traceback_ = sys.exc_info()
quota_service.increment_failed_service_use()
error_msg = 'There was an error trying to obtain route using mapzen provider: {0}'.format(e)
plpy.notice(traceback.format_tb(traceback_))
plpy.error(error_msg)
finally:
quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER;

View File

@ -0,0 +1,98 @@
--DO NOT MODIFY THIS FILE, IT IS GENERATED AUTOMATICALLY FROM SOURCES
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION cdb_dataservices_server UPDATE TO '0.6.1'" to load this file. \quit
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_geocode_street_point(username TEXT, orgname TEXT, searchtext TEXT, city TEXT DEFAULT NULL, state_province TEXT DEFAULT NULL, country TEXT DEFAULT NULL)
RETURNS Geometry AS $$
from cartodb_services.mapzen import MapzenGeocoder
from cartodb_services.mapzen.types import country_to_iso3
from cartodb_services.metrics import QuotaService
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_geocoder_config = GD["user_geocoder_config_{0}".format(username)]
quota_service = QuotaService(user_geocoder_config, redis_conn)
try:
geocoder = MapzenGeocoder(user_geocoder_config.mapzen_app_key)
country_iso3 = None
if country:
country_iso3 = country_to_iso3(country)
coordinates = geocoder.geocode(searchtext=searchtext, city=city,
state_province=state_province,
country=country_iso3)
if coordinates:
quota_service.increment_success_service_use()
plan = plpy.prepare("SELECT ST_SetSRID(ST_MakePoint($1, $2), 4326); ", ["double precision", "double precision"])
point = plpy.execute(plan, [coordinates[0], coordinates[1]], 1)[0]
return point['st_setsrid']
else:
quota_service.increment_empty_service_use()
return None
except BaseException as e:
import sys, traceback
type_, value_, traceback_ = sys.exc_info()
quota_service.increment_failed_service_use()
error_msg = 'There was an error trying to geocode using mapzen geocoder: {0}'.format(e)
plpy.notice(traceback.format_tb(traceback_))
plpy.error(error_msg)
finally:
quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu;
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_route_point_to_point(
username TEXT,
orgname TEXT,
origin geometry(Point, 4326),
destination geometry(Point, 4326),
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
import json
from cartodb_services.mapzen import MapzenRouting, MapzenRoutingResponse
from cartodb_services.mapzen.types import polyline_to_linestring
from cartodb_services.metrics import QuotaService
from cartodb_services.tools import Coordinate
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_routing_config = GD["user_routing_config_{0}".format(username)]
quota_service = QuotaService(user_routing_config, redis_conn)
try:
client = MapzenRouting(user_routing_config.mapzen_app_key)
if not origin or not destination:
plpy.notice("Empty origin or destination")
quota_service.increment_empty_service_use()
return [None, None, None]
orig_lat = plpy.execute("SELECT ST_Y('%s') AS lat" % origin)[0]['lat']
orig_lon = plpy.execute("SELECT ST_X('%s') AS lon" % origin)[0]['lon']
origin_coordinates = Coordinate(orig_lon, orig_lat)
dest_lat = plpy.execute("SELECT ST_Y('%s') AS lat" % destination)[0]['lat']
dest_lon = plpy.execute("SELECT ST_X('%s') AS lon" % destination)[0]['lon']
dest_coordinates = Coordinate(dest_lon, dest_lat)
resp = client.calculate_route_point_to_point(origin_coordinates, dest_coordinates, mode, options, units)
if resp and resp.shape:
shape_linestring = polyline_to_linestring(resp.shape)
if shape_linestring:
quota_service.increment_success_service_use()
return [shape_linestring, resp.length, resp.duration]
else:
quota_service.increment_empty_service_use()
return [None, None, None]
else:
quota_service.increment_empty_service_use()
return [None, None, None]
except BaseException as e:
import sys, traceback
type_, value_, traceback_ = sys.exc_info()
quota_service.increment_failed_service_use()
error_msg = 'There was an error trying to obtain route using mapzen provider: {0}'.format(e)
plpy.notice(traceback.format_tb(traceback_))
plpy.error(error_msg)
finally:
quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER;

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
comment = 'CartoDB dataservices server extension' comment = 'CartoDB dataservices server extension'
default_version = '0.6.1' default_version = '0.6.2'
requires = 'plpythonu, postgis, cdb_geocoder' requires = 'plpythonu, postgis, cdb_geocoder'
superuser = true superuser = true
schema = cdb_dataservices_server schema = cdb_dataservices_server

View File

@ -27,6 +27,8 @@ RETURNS cdb_dataservices_server.simple_route AS $$
user_routing_config = GD["user_routing_config_{0}".format(username)] user_routing_config = GD["user_routing_config_{0}".format(username)]
quota_service = QuotaService(user_routing_config, redis_conn) quota_service = QuotaService(user_routing_config, redis_conn)
if not quota_service.check_user_quota():
plpy.error('You have reach the limit of your quota')
try: try:
client = MapzenRouting(user_routing_config.mapzen_app_key) client = MapzenRouting(user_routing_config.mapzen_app_key)
@ -257,6 +259,8 @@ RETURNS Geometry AS $$
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_geocoder_config = GD["user_geocoder_config_{0}".format(username)] user_geocoder_config = GD["user_geocoder_config_{0}".format(username)]
quota_service = QuotaService(user_geocoder_config, redis_conn) quota_service = QuotaService(user_geocoder_config, redis_conn)
if not quota_service.check_user_quota():
plpy.error('You have reach the limit of your quota')
try: try:
geocoder = MapzenGeocoder(user_geocoder_config.mapzen_app_key) geocoder = MapzenGeocoder(user_geocoder_config.mapzen_app_key)

View File

@ -24,9 +24,11 @@ RETURNS cdb_dataservices_server.simple_route AS $$
user_routing_config = GD["user_routing_config_{0}".format(username)] user_routing_config = GD["user_routing_config_{0}".format(username)]
quota_service = QuotaService(user_routing_config, redis_conn) quota_service = QuotaService(user_routing_config, redis_conn)
if not quota_service.check_user_quota():
plpy.error('You have reach the limit of your quota')
try: try:
client = MapzenRouting(user_routing_config.mapzen_app_key) client = MapzenRouting(user_routing_config.mapzen_api_key)
if not origin or not destination: if not origin or not destination:
plpy.notice("Empty origin or destination") plpy.notice("Empty origin or destination")

View File

@ -89,18 +89,23 @@ $$ LANGUAGE plpythonu;
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_geocode_street_point(username TEXT, orgname TEXT, searchtext TEXT, city TEXT DEFAULT NULL, state_province TEXT DEFAULT NULL, country TEXT DEFAULT NULL) CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_geocode_street_point(username TEXT, orgname TEXT, searchtext TEXT, city TEXT DEFAULT NULL, state_province TEXT DEFAULT NULL, country TEXT DEFAULT NULL)
RETURNS Geometry AS $$ RETURNS Geometry AS $$
from cartodb_services.mapzen import MapzenGeocoder from cartodb_services.mapzen import MapzenGeocoder
from cartodb_services.mapzen.types import country_to_iso3
from cartodb_services.metrics import QuotaService from cartodb_services.metrics import QuotaService
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_geocoder_config = GD["user_geocoder_config_{0}".format(username)] user_geocoder_config = GD["user_geocoder_config_{0}".format(username)]
quota_service = QuotaService(user_geocoder_config, redis_conn) quota_service = QuotaService(user_geocoder_config, redis_conn)
if not quota_service.check_user_quota():
plpy.error('You have reach the limit of your quota')
try: try:
geocoder = MapzenGeocoder(user_geocoder_config.mapzen_app_key) geocoder = MapzenGeocoder(user_geocoder_config.mapzen_api_key)
country_iso3 = None country_iso3 = None
if country: if country:
country_iso3 = country_to_iso3(country) country_iso3 = country_to_iso3(country)
coordinates = geocoder.geocode(searchtext=searchtext, country=country_iso3) coordinates = geocoder.geocode(searchtext=searchtext, city=city,
state_province=state_province,
country=country_iso3)
if coordinates: if coordinates:
quota_service.increment_success_service_use() quota_service.increment_success_service_use()
plan = plpy.prepare("SELECT ST_SetSRID(ST_MakePoint($1, $2), 4326); ", ["double precision", "double precision"]) plan = plpy.prepare("SELECT ST_SetSRID(ST_MakePoint($1, $2), 4326); ", ["double precision", "double precision"])

View File

@ -25,7 +25,7 @@ SELECT cartodb.cdb_conf_setconf('heremaps_conf', '{"geocoder": {"app_id": "dummy
(1 row) (1 row)
SELECT cartodb.cdb_conf_setconf('mapzen_conf', '{"routing_app_key": "dummy_key", "geocoder_app_key": "dummy_key"}'); SELECT cartodb.cdb_conf_setconf('mapzen_conf', '{"routing": {"api_key": "routing_dummy_api_key", "monthly_quota": 1500000}, "geocoder": {"api_key": "geocoder_dummy_api_key", "monthly_quota": 1500000}}');
cdb_conf_setconf cdb_conf_setconf
------------------ ------------------

View File

@ -12,7 +12,7 @@ CREATE EXTENSION cdb_dataservices_server;
SELECT cartodb.cdb_conf_setconf('redis_metrics_config', '{"redis_host": "localhost", "redis_port": 6379, "timeout": 0.1, "redis_db": 5}'); SELECT cartodb.cdb_conf_setconf('redis_metrics_config', '{"redis_host": "localhost", "redis_port": 6379, "timeout": 0.1, "redis_db": 5}');
SELECT cartodb.cdb_conf_setconf('redis_metadata_config', '{"redis_host": "localhost", "redis_port": 6379, "timeout": 0.1, "redis_db": 5}'); SELECT cartodb.cdb_conf_setconf('redis_metadata_config', '{"redis_host": "localhost", "redis_port": 6379, "timeout": 0.1, "redis_db": 5}');
SELECT cartodb.cdb_conf_setconf('heremaps_conf', '{"geocoder": {"app_id": "dummy_id", "app_code": "dummy_code", "geocoder_cost_per_hit": 1}, "isolines": {"app_id": "dummy_id", "app_code": "dummy_code"}}'); SELECT cartodb.cdb_conf_setconf('heremaps_conf', '{"geocoder": {"app_id": "dummy_id", "app_code": "dummy_code", "geocoder_cost_per_hit": 1}, "isolines": {"app_id": "dummy_id", "app_code": "dummy_code"}}');
SELECT cartodb.cdb_conf_setconf('mapzen_conf', '{"routing_app_key": "dummy_key", "geocoder_app_key": "dummy_key"}'); SELECT cartodb.cdb_conf_setconf('mapzen_conf', '{"routing": {"api_key": "routing_dummy_api_key", "monthly_quota": 1500000}, "geocoder": {"api_key": "geocoder_dummy_api_key", "monthly_quota": 1500000}}');
SELECT cartodb.cdb_conf_setconf('logger_conf', '{"geocoder_log_path": "/dev/null"}'); SELECT cartodb.cdb_conf_setconf('logger_conf', '{"geocoder_log_path": "/dev/null"}');
-- Mock the varnish invalidation function -- Mock the varnish invalidation function

View File

@ -10,10 +10,15 @@ class ConfigException(Exception):
class ServiceConfig(object): class ServiceConfig(object):
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
def __init__(self, redis_connection, username, orgname=None): def __init__(self, redis_connection, db_conn, username, orgname=None):
self._redis_connection = redis_connection self._redis_connection = redis_connection
self._username = username self._username = username
self._orgname = orgname self._orgname = orgname
self._db_config = ServicesDBConfig(db_conn)
if redis_connection:
self._redis_config = ServicesRedisConfig(redis_connection).build(username, orgname)
else:
self._redis_config = None
@abc.abstractproperty @abc.abstractproperty
def service_type(self): def service_type(self):
@ -30,24 +35,30 @@ class ServiceConfig(object):
class RoutingConfig(ServiceConfig): class RoutingConfig(ServiceConfig):
ROUTING_CONFIG_KEYS = ['username', 'orgname', 'mapzen_app_key'] PERIOD_END_DATE = 'period_end_date'
MAPZEN_APP_KEY = 'mapzen_app_key'
USERNAME_KEY = 'username'
ORGNAME_KEY = 'orgname'
def __init__(self, redis_connection, db_conn, username, orgname=None): def __init__(self, redis_connection, db_conn, username, orgname=None):
super(RoutingConfig, self).__init__(redis_connection, username, super(RoutingConfig, self).__init__(redis_connection, db_conn,
orgname) username, orgname)
db_config = ServicesDBConfig(db_conn) self._mapzen_api_key = self._db_config.mapzen_routing_api_key
self._mapzen_app_key = db_config.mapzen_routing_app_key self._monthly_quota = self._db_config.mapzen_routing_monthly_quota
self._period_end_date = date_parse(self._redis_config[self.PERIOD_END_DATE])
@property @property
def service_type(self): def service_type(self):
return 'routing_mapzen' return 'routing_mapzen'
@property @property
def mapzen_app_key(self): def mapzen_api_key(self):
return self._mapzen_app_key return self._mapzen_api_key
@property
def monthly_quota(self):
return self._monthly_quota
@property
def period_end_date(self):
return self._period_end_date
class IsolinesRoutingConfig(ServiceConfig): class IsolinesRoutingConfig(ServiceConfig):
@ -56,43 +67,17 @@ class IsolinesRoutingConfig(ServiceConfig):
'period_end_date', 'username', 'orgname', 'period_end_date', 'username', 'orgname',
'heremaps_isolines_app_id', 'heremaps_isolines_app_code', 'heremaps_isolines_app_id', 'heremaps_isolines_app_code',
'geocoder_type'] 'geocoder_type']
NOKIA_APP_ID_KEY = 'heremaps_isolines_app_id'
NOKIA_APP_CODE_KEY = 'heremaps_isolines_app_code'
QUOTA_KEY = 'here_isolines_quota' QUOTA_KEY = 'here_isolines_quota'
SOFT_LIMIT_KEY = 'soft_here_isolines_limit' SOFT_LIMIT_KEY = 'soft_here_isolines_limit'
USERNAME_KEY = 'username'
ORGNAME_KEY = 'orgname'
PERIOD_END_DATE = 'period_end_date' PERIOD_END_DATE = 'period_end_date'
GEOCODER_TYPE_KEY = 'geocoder_type' GEOCODER_TYPE_KEY = 'geocoder_type'
GOOGLE_GEOCODER = 'google' GOOGLE_GEOCODER = 'google'
def __init__(self, redis_connection, db_conn, username, orgname=None): def __init__(self, redis_connection, db_conn, username, orgname=None):
super(IsolinesRoutingConfig, self).__init__(redis_connection, username, super(IsolinesRoutingConfig, self).__init__(redis_connection, db_conn,
orgname) username, orgname)
config = self.__get_user_config(username, orgname) filtered_config = {key: self._redis_config[key] for key in self.ROUTING_CONFIG_KEYS if key in self._redis_config.keys()}
db_config = ServicesDBConfig(db_conn) self.__parse_config(filtered_config, self._db_config)
filtered_config = {key: config[key] for key in self.ROUTING_CONFIG_KEYS if key in config.keys()}
self.__parse_config(filtered_config, db_config)
def __get_user_config(self, username, orgname):
user_config = self._redis_connection.hgetall(
"rails:users:{0}".format(username))
if not user_config:
raise ConfigException("""There is no user config available. Please check your configuration.'""")
else:
if orgname:
self.__get_organization_config(orgname, user_config)
return user_config
def __get_organization_config(self, orgname, user_config):
org_config = self._redis_connection.hgetall(
"rails:orgs:{0}".format(orgname))
if not org_config:
raise ConfigException("""There is no organization config available. Please check your configuration.'""")
else:
user_config[self.QUOTA_KEY] = org_config[self.QUOTA_KEY]
user_config[self.PERIOD_END_DATE] = org_config[self.PERIOD_END_DATE]
def __parse_config(self, filtered_config, db_config): def __parse_config(self, filtered_config, db_config):
self._geocoder_type = filtered_config[self.GEOCODER_TYPE_KEY].lower() self._geocoder_type = filtered_config[self.GEOCODER_TYPE_KEY].lower()
@ -137,10 +122,10 @@ class IsolinesRoutingConfig(ServiceConfig):
class InternalGeocoderConfig(ServiceConfig): class InternalGeocoderConfig(ServiceConfig):
def __init__(self, redis_connection, db_conn, username, orgname=None): def __init__(self, redis_connection, db_conn, username, orgname=None):
super(InternalGeocoderConfig, self).__init__(redis_connection, # For now, internal geocoder doesn't use the redis config
super(InternalGeocoderConfig, self).__init__(None, db_conn,
username, orgname) username, orgname)
db_config = ServicesDBConfig(db_conn) self._log_path = self._db_config.geocoder_log_path
self._log_path = db_config.geocoder_log_path
@property @property
def service_type(self): def service_type(self):
@ -169,7 +154,7 @@ class GeocoderConfig(ServiceConfig):
'geocoding_quota', 'soft_geocoding_limit', 'geocoding_quota', 'soft_geocoding_limit',
'geocoder_type', 'period_end_date', 'geocoder_type', 'period_end_date',
'heremaps_geocoder_app_id', 'heremaps_geocoder_app_code', 'heremaps_geocoder_app_id', 'heremaps_geocoder_app_code',
'mapzen_geocoder_app_key', 'username', 'orgname'] 'mapzen_geocoder_api_key', 'username', 'orgname']
NOKIA_GEOCODER_REDIS_MANDATORY_KEYS = ['geocoding_quota', 'soft_geocoding_limit'] NOKIA_GEOCODER_REDIS_MANDATORY_KEYS = ['geocoding_quota', 'soft_geocoding_limit']
NOKIA_GEOCODER = 'heremaps' NOKIA_GEOCODER = 'heremaps'
NOKIA_GEOCODER_APP_ID_KEY = 'heremaps_geocoder_app_id' NOKIA_GEOCODER_APP_ID_KEY = 'heremaps_geocoder_app_id'
@ -178,7 +163,7 @@ class GeocoderConfig(ServiceConfig):
GOOGLE_GEOCODER_API_KEY = 'google_maps_api_key' GOOGLE_GEOCODER_API_KEY = 'google_maps_api_key'
GOOGLE_GEOCODER_CLIENT_ID = 'google_maps_client_id' GOOGLE_GEOCODER_CLIENT_ID = 'google_maps_client_id'
MAPZEN_GEOCODER = 'mapzen' MAPZEN_GEOCODER = 'mapzen'
MAPZEN_GEOCODER_API_KEY = 'mapzen_geocoder_app_key' MAPZEN_GEOCODER_API_KEY = 'mapzen_geocoder_api_key'
GEOCODER_TYPE = 'geocoder_type' GEOCODER_TYPE = 'geocoder_type'
QUOTA_KEY = 'geocoding_quota' QUOTA_KEY = 'geocoding_quota'
SOFT_LIMIT_KEY = 'soft_geocoding_limit' SOFT_LIMIT_KEY = 'soft_geocoding_limit'
@ -187,12 +172,10 @@ class GeocoderConfig(ServiceConfig):
PERIOD_END_DATE = 'period_end_date' PERIOD_END_DATE = 'period_end_date'
def __init__(self, redis_connection, db_conn, username, orgname=None): def __init__(self, redis_connection, db_conn, username, orgname=None):
super(GeocoderConfig, self).__init__(redis_connection, username, super(GeocoderConfig, self).__init__(redis_connection, db_conn,
orgname) username, orgname)
db_config = ServicesDBConfig(db_conn) filtered_config = {key: self._redis_config[key] for key in self.GEOCODER_CONFIG_KEYS if key in self._redis_config.keys()}
config = self.__get_user_config(username, orgname) self.__parse_config(filtered_config, self._db_config)
filtered_config = {key: config[key] for key in self.GEOCODER_CONFIG_KEYS if key in config.keys()}
self.__parse_config(filtered_config, db_config)
self.__check_config(filtered_config) self.__check_config(filtered_config)
def __get_user_config(self, username, orgname): def __get_user_config(self, username, orgname):
@ -226,7 +209,7 @@ class GeocoderConfig(ServiceConfig):
if self.GOOGLE_GEOCODER_API_KEY not in filtered_config.keys(): if self.GOOGLE_GEOCODER_API_KEY not in filtered_config.keys():
raise ConfigException("""Google geocoder need the mandatory parameter 'google_maps_private_key'""") raise ConfigException("""Google geocoder need the mandatory parameter 'google_maps_private_key'""")
elif filtered_config[self.GEOCODER_TYPE].lower() == self.MAPZEN_GEOCODER: elif filtered_config[self.GEOCODER_TYPE].lower() == self.MAPZEN_GEOCODER:
if not self.mapzen_app_key: if not self.mapzen_api_key:
raise ConfigException("""Mapzen config is not setted up""") raise ConfigException("""Mapzen config is not setted up""")
return True return True
@ -249,7 +232,8 @@ class GeocoderConfig(ServiceConfig):
self._google_maps_client_id = filtered_config[self.GOOGLE_GEOCODER_CLIENT_ID] self._google_maps_client_id = filtered_config[self.GOOGLE_GEOCODER_CLIENT_ID]
self._cost_per_hit = 0 self._cost_per_hit = 0
elif filtered_config[self.GEOCODER_TYPE].lower() == self.MAPZEN_GEOCODER: elif filtered_config[self.GEOCODER_TYPE].lower() == self.MAPZEN_GEOCODER:
self._mapzen_app_key = db_config.mapzen_geocoder_app_key self._mapzen_api_key = db_config.mapzen_geocoder_api_key
self._geocoding_quota = db_config.mapzen_geocoder_monthly_quota
self._cost_per_hit = 0 self._cost_per_hit = 0
@property @property
@ -283,10 +267,10 @@ class GeocoderConfig(ServiceConfig):
@property @property
def geocoding_quota(self): def geocoding_quota(self):
if self.heremaps_geocoder: if self.google_geocoder:
return self._geocoding_quota
else:
return None return None
else:
return self._geocoding_quota
@property @property
def soft_geocoding_limit(self): def soft_geocoding_limit(self):
@ -305,8 +289,8 @@ class GeocoderConfig(ServiceConfig):
return self._heremaps_app_code return self._heremaps_app_code
@property @property
def mapzen_app_key(self): def mapzen_api_key(self):
return self._mapzen_app_key return self._mapzen_api_key
@property @property
def is_high_resolution(self): def is_high_resolution(self):
@ -351,8 +335,10 @@ class ServicesDBConfig:
raise ConfigException('Mapzen configuration missing') raise ConfigException('Mapzen configuration missing')
else: else:
mapzen_conf = json.loads(mapzen_conf_json) mapzen_conf = json.loads(mapzen_conf_json)
self._mapzen_routing_app_key = mapzen_conf['routing_app_key'] self._mapzen_routing_api_key = mapzen_conf['routing']['api_key']
self._mapzen_geocoder_app_key = mapzen_conf['geocoder_app_key'] self._mapzen_routing_quota = mapzen_conf['routing']['monthly_quota']
self._mapzen_geocoder_api_key = mapzen_conf['geocoder']['api_key']
self._mapzen_geocoder_quota = mapzen_conf['geocoder']['monthly_quota']
def _get_logger_config(self): def _get_logger_config(self):
logger_conf_json = self._get_conf('logger_conf') logger_conf_json = self._get_conf('logger_conf')
@ -391,13 +377,57 @@ class ServicesDBConfig:
return self._heremaps_geocoder_cost_per_hit return self._heremaps_geocoder_cost_per_hit
@property @property
def mapzen_routing_app_key(self): def mapzen_routing_api_key(self):
return self._mapzen_routing_app_key return self._mapzen_routing_api_key
@property @property
def mapzen_geocoder_app_key(self): def mapzen_routing_monthly_quota(self):
return self._mapzen_geocoder_app_key return self._mapzen_routing_quota
@property
def mapzen_geocoder_api_key(self):
return self._mapzen_geocoder_api_key
@property
def mapzen_geocoder_monthly_quota(self):
return self._mapzen_geocoder_quota
@property @property
def geocoder_log_path(self): def geocoder_log_path(self):
return self._geocoder_log_path return self._geocoder_log_path
class ServicesRedisConfig:
GOOGLE_GEOCODER_API_KEY = 'google_maps_api_key'
GOOGLE_GEOCODER_CLIENT_ID = 'google_maps_client_id'
QUOTA_KEY = 'geocoding_quota'
PERIOD_END_DATE = 'period_end_date'
def __init__(self, redis_conn):
self._redis_connection = redis_conn
def build(self, username, password):
return self.__get_user_config(username, password)
def __get_user_config(self, username, orgname):
user_config = self._redis_connection.hgetall(
"rails:users:{0}".format(username))
if not user_config:
raise ConfigException("""There is no user config available. Please check your configuration.'""")
else:
if orgname:
self.__get_organization_config(orgname, user_config)
return user_config
def __get_organization_config(self, orgname, user_config):
org_config = self._redis_connection.hgetall(
"rails:orgs:{0}".format(orgname))
if not org_config:
raise ConfigException("""There is no organization config available. Please check your configuration.'""")
else:
user_config[self.QUOTA_KEY] = org_config[self.QUOTA_KEY]
user_config[self.PERIOD_END_DATE] = org_config[self.PERIOD_END_DATE]
user_config[self.GOOGLE_GEOCODER_CLIENT_ID] = org_config[self.GOOGLE_GEOCODER_CLIENT_ID]
user_config[self.GOOGLE_GEOCODER_API_KEY] = org_config[self.GOOGLE_GEOCODER_API_KEY]

View File

@ -70,6 +70,9 @@ class QuotaChecker:
elif re.match('here_isolines', elif re.match('here_isolines',
self._user_service_config.service_type) is not None: self._user_service_config.service_type) is not None:
return self.__check_isolines_quota() return self.__check_isolines_quota()
elif re.match('routing_mapzen',
self._user_service_config.service_type) is not None:
return self.__check_routing_quota()
else: else:
return False return False
@ -100,3 +103,14 @@ class QuotaChecker:
return True return True
else: else:
return False return False
def __check_routing_quota(self):
user_quota = self._user_service_config.monthly_quota
today = date.today()
service_type = self._user_service_config.service_type
current_used = self._user_service.used_quota(service_type, today)
if (user_quota > 0 and current_used <= user_quota):
return True
else:
return False

View File

@ -41,11 +41,12 @@ class UserMetricsService:
return current_use return current_use
def __used_isolines_quota(self, service_type, date): def __used_isolines_quota(self, service_type, date):
""" Recover the used quota for the user in the current month """
date_from, date_to = self.__current_billing_cycle() date_from, date_to = self.__current_billing_cycle()
current_use = 0 current_use = 0
isolines_generated = self.get_metrics(service_type, isolines_generated = self.get_metrics(service_type,
'isolines_generated', date_from, 'isolines_generated', date_from,
date_to) date_to)
empty_responses = self.get_metrics(service_type, empty_responses = self.get_metrics(service_type,
'empty_responses', date_from, 'empty_responses', date_from,
date_to) date_to)
@ -53,12 +54,27 @@ class UserMetricsService:
return current_use return current_use
def __used_routing_quota(self, service_type, date):
""" Recover the used quota for the user in the current month """
date_from, date_to = self.__current_billing_cycle()
current_use = 0
success_responses = self.get_metrics(service_type,
'success_responses', date_from,
date_to)
empty_responses = self.get_metrics(service_type,
'empty_responses', date_from,
date_to)
current_use += (success_responses + empty_responses)
def increment_service_use(self, service_type, metric, date=date.today(), amount=1): return current_use
def increment_service_use(self, service_type, metric, date=date.today(),
amount=1):
""" Increment the services uses in monthly and daily basis""" """ Increment the services uses in monthly and daily basis"""
self.__increment_user_uses(service_type, metric, date, amount) self.__increment_user_uses(service_type, metric, date, amount)
if self._orgname: if self._orgname:
self.__increment_organization_uses(service_type, metric, date, amount) self.__increment_organization_uses(service_type, metric, date,
amount)
def get_metrics(self, service, metric, date_from, date_to): def get_metrics(self, service, metric, date_from, date_to):
aggregated_metric = 0 aggregated_metric = 0
@ -83,11 +99,12 @@ class UserMetricsService:
service_type, metric, date) service_type, metric, date)
self._redis_connection.zincrby(redis_prefix, date.day, amount) self._redis_connection.zincrby(redis_prefix, date.day, amount)
def __parse_redis_prefix(self, prefix, entity_name, service_type, metric, date): def __parse_redis_prefix(self, prefix, entity_name, service_type, metric,
date):
yearmonth_key = date.strftime('%Y%m') yearmonth_key = date.strftime('%Y%m')
redis_name = "{0}:{1}:{2}:{3}:{4}".format(prefix, entity_name, redis_name = "{0}:{1}:{2}:{3}:{4}".format(prefix, entity_name,
service_type, metric, service_type, metric,
yearmonth_key) yearmonth_key)
return redis_name return redis_name

View File

@ -10,7 +10,7 @@ from setuptools import setup, find_packages
setup( setup(
name='cartodb_services', name='cartodb_services',
version='0.4.4', version='0.4.5',
description='CartoDB Services API Python Library', description='CartoDB Services API Python Library',

View File

@ -46,6 +46,6 @@ def _plpy_execute_side_effect(*args, **kwargs):
if args[0] == "SELECT cartodb.CDB_Conf_GetConf('heremaps_conf') as conf": if args[0] == "SELECT cartodb.CDB_Conf_GetConf('heremaps_conf') as conf":
return [{'conf': '{"geocoder": {"app_id": "app_id", "app_code": "code", "geocoder_cost_per_hit": 1}, "isolines": {"app_id": "app_id", "app_code": "code"}}'}] return [{'conf': '{"geocoder": {"app_id": "app_id", "app_code": "code", "geocoder_cost_per_hit": 1}, "isolines": {"app_id": "app_id", "app_code": "code"}}'}]
elif args[0] == "SELECT cartodb.CDB_Conf_GetConf('mapzen_conf') as conf": elif args[0] == "SELECT cartodb.CDB_Conf_GetConf('mapzen_conf') as conf":
return [{'conf': '{"routing_app_key": "app_key", "geocoder_app_key": "app_key"}'}] return [{'conf': '{"routing": {"api_key": "valhalla-Z61FWEs", "monthly_quota": 1500000}, "geocoder": {"api_key": "search-d744tp0", "monthly_quota": 1500000}}'}]
elif args[0] == "SELECT cartodb.CDB_Conf_GetConf('logger_conf') as conf": elif args[0] == "SELECT cartodb.CDB_Conf_GetConf('logger_conf') as conf":
return [{'conf': '{"geocoder_log_path": "/dev/null"}'}] return [{'conf': '{"geocoder_log_path": "/dev/null"}'}]

View File

@ -1,7 +1,7 @@
import test_helper import test_helper
from mockredis import MockRedis from mockredis import MockRedis
from cartodb_services.metrics import QuotaService from cartodb_services.metrics import QuotaService
from cartodb_services.metrics import GeocoderConfig from cartodb_services.metrics import GeocoderConfig, RoutingConfig
from unittest import TestCase from unittest import TestCase
from nose.tools import assert_raises from nose.tools import assert_raises
from datetime import datetime, date from datetime import datetime, date
@ -18,79 +18,124 @@ class TestQuotaService(TestCase):
self.redis_conn = MockRedis() self.redis_conn = MockRedis()
def test_should_return_true_if_user_quota_with_no_use(self): def test_should_return_true_if_user_quota_with_no_use(self):
qs = self.__build_quota_service('test_user') qs = self.__build_geocoder_quota_service('test_user')
assert qs.check_user_quota() is True assert qs.check_user_quota() is True
def test_should_return_true_if_org_quota_with_no_use(self): def test_should_return_true_if_org_quota_with_no_use(self):
qs = self.__build_quota_service('test_user', orgname='test_org') qs = self.__build_geocoder_quota_service('test_user',
orgname='test_org')
assert qs.check_user_quota() is True assert qs.check_user_quota() is True
def test_should_return_true_if_user_quota_is_not_completely_used(self): def test_should_return_true_if_user_quota_is_not_completely_used(self):
qs = self.__build_quota_service('test_user') qs = self.__build_geocoder_quota_service('test_user')
test_helper.increment_geocoder_uses(self.redis_conn, 'test_user') test_helper.increment_geocoder_uses(self.redis_conn, 'test_user')
assert qs.check_user_quota() is True assert qs.check_user_quota() is True
def test_should_return_true_if_org_quota_is_not_completely_used(self): def test_should_return_true_if_org_quota_is_not_completely_used(self):
qs = self.__build_quota_service('test_user', orgname='test_org') qs = self.__build_geocoder_quota_service('test_user',
orgname='test_org')
test_helper.increment_geocoder_uses(self.redis_conn, 'test_user', test_helper.increment_geocoder_uses(self.redis_conn, 'test_user',
orgname='test_org') orgname='test_org')
assert qs.check_user_quota() is True assert qs.check_user_quota() is True
def test_should_return_false_if_user_quota_is_surpassed(self): def test_should_return_false_if_user_quota_is_surpassed(self):
qs = self.__build_quota_service('test_user') qs = self.__build_geocoder_quota_service('test_user')
test_helper.increment_geocoder_uses(self.redis_conn, 'test_user', test_helper.increment_geocoder_uses(self.redis_conn, 'test_user',
amount=300) amount=300)
assert qs.check_user_quota() is False assert qs.check_user_quota() is False
def test_should_return_false_if_org_quota_is_surpassed(self): def test_should_return_false_if_org_quota_is_surpassed(self):
qs = self.__build_quota_service('test_user', orgname='test_org') qs = self.__build_geocoder_quota_service('test_user',
orgname='test_org')
test_helper.increment_geocoder_uses(self.redis_conn, 'test_user', test_helper.increment_geocoder_uses(self.redis_conn, 'test_user',
orgname='test_org', amount=400) orgname='test_org', amount=400)
assert qs.check_user_quota() is False assert qs.check_user_quota() is False
def test_should_return_true_if_user_quota_is_surpassed_but_soft_limit_is_enabled(self): def test_should_return_true_if_user_quota_is_surpassed_but_soft_limit_is_enabled(self):
qs = self.__build_quota_service('test_user', soft_limit=True) qs = self.__build_geocoder_quota_service('test_user', soft_limit=True)
test_helper.increment_geocoder_uses(self.redis_conn, 'test_user', test_helper.increment_geocoder_uses(self.redis_conn, 'test_user',
amount=300) amount=300)
assert qs.check_user_quota() is True assert qs.check_user_quota() is True
def test_should_return_true_if_org_quota_is_surpassed_but_soft_limit_is_enabled(self): def test_should_return_true_if_org_quota_is_surpassed_but_soft_limit_is_enabled(self):
qs = self.__build_quota_service('test_user', orgname='test_org', qs = self.__build_geocoder_quota_service('test_user',
soft_limit=True) orgname='test_org',
soft_limit=True)
test_helper.increment_geocoder_uses(self.redis_conn, 'test_user', test_helper.increment_geocoder_uses(self.redis_conn, 'test_user',
orgname='test_org', amount=400) orgname='test_org', amount=400)
assert qs.check_user_quota() is True assert qs.check_user_quota() is True
def test_should_check_user_increment_and_quota_check_correctly(self): def test_should_check_user_increment_and_quota_check_correctly(self):
qs = self.__build_quota_service('test_user', quota=2) qs = self.__build_geocoder_quota_service('test_user', quota=2)
qs.increment_success_service_use() qs.increment_success_service_use()
assert qs.check_user_quota() is True assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=2) qs.increment_success_service_use(amount=2)
assert qs.check_user_quota() is False assert qs.check_user_quota() is False
month = date.today().strftime('%Y%m')
def test_should_check_org_increment_and_quota_check_correctly(self): def test_should_check_org_increment_and_quota_check_correctly(self):
qs = self.__build_quota_service('test_user', orgname='test_org', qs = self.__build_geocoder_quota_service('test_user', quota=2,
quota=2) orgname='test_org')
qs.increment_success_service_use() qs.increment_success_service_use()
assert qs.check_user_quota() is True assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=2) qs.increment_success_service_use(amount=2)
assert qs.check_user_quota() is False assert qs.check_user_quota() is False
month = date.today().strftime('%Y%m')
def __build_quota_service(self, username, quota=100, service='heremaps', def test_should_check_user_mapzen_geocoder_quota_correctly(self):
orgname=None, soft_limit=False, qs = self.__build_geocoder_quota_service('test_user', service='mapzen')
end_date = datetime.today()): qs.increment_success_service_use()
assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=1500000)
assert qs.check_user_quota() is False
def test_should_check_org_mapzen_geocoder_quota_correctly(self):
qs = self.__build_geocoder_quota_service('test_user', orgname='testorg',
service='mapzen')
qs.increment_success_service_use()
assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=1500000)
assert qs.check_user_quota() is False
def test_should_check_user_routing_quota_correctly(self):
qs = self.__build_routing_quota_service('test_user')
qs.increment_success_service_use()
assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=1500000)
assert qs.check_user_quota() is False
def test_should_check_org_routing_quota_correctly(self):
qs = self.__build_routing_quota_service('test_user', orgname='testorg')
qs.increment_success_service_use()
assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=1500000)
assert qs.check_user_quota() is False
def __prepare_quota_service(self, username, quota, service, orgname,
soft_limit, end_date):
test_helper.build_redis_user_config(self.redis_conn, username, test_helper.build_redis_user_config(self.redis_conn, username,
quota = quota, service = service, quota=quota, service=service,
soft_limit = soft_limit, soft_limit=soft_limit,
end_date = end_date) end_date=end_date)
if orgname: if orgname:
test_helper.build_redis_org_config(self.redis_conn, orgname, test_helper.build_redis_org_config(self.redis_conn, orgname,
quota=quota, end_date=end_date) quota=quota, end_date=end_date)
plpy_mock = test_helper.build_plpy_mock() self._plpy_mock = test_helper.build_plpy_mock()
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
username, orgname)
return QuotaService(geocoder_config,
redis_connection = self.redis_conn)
def __build_geocoder_quota_service(self, username, quota=100,
service='heremaps', orgname=None,
soft_limit=False,
end_date=datetime.today()):
self.__prepare_quota_service(username, quota, service, orgname,
soft_limit, end_date)
geocoder_config = GeocoderConfig(self.redis_conn, self._plpy_mock,
username, orgname)
return QuotaService(geocoder_config, redis_connection=self.redis_conn)
def __build_routing_quota_service(self, username, quota=100,
service='routing_mapzen', orgname=None,
soft_limit=False,
end_date=datetime.today()):
self.__prepare_quota_service(username, quota, service, orgname,
soft_limit, end_date)
routing_config = RoutingConfig(self.redis_conn, self._plpy_mock,
username, orgname)
return QuotaService(routing_config, redis_connection=self.redis_conn)