From 1b347b7ad0b62bcc6c40a7f3d93627c207ce1193 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Fri, 6 Nov 2015 17:06:52 +0100 Subject: [PATCH 01/14] Checking from redis at localhost --- .gitignore | 1 + .../cartodb_geocoder/__init__.py | 0 .../cartodb_geocoder/quota_service.py | 45 +++++++++++++++++++ .../python/cartodb_geocoder/requirements.txt | 1 + server/lib/python/cartodb_geocoder/setup.py | 0 5 files changed, 47 insertions(+) create mode 100644 .gitignore create mode 100644 server/lib/python/cartodb_geocoder/cartodb_geocoder/__init__.py create mode 100644 server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py create mode 100644 server/lib/python/cartodb_geocoder/requirements.txt create mode 100644 server/lib/python/cartodb_geocoder/setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/__init__.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py new file mode 100644 index 0000000..070b4bd --- /dev/null +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py @@ -0,0 +1,45 @@ +import redis +from datetime import date + +class QuotaService: + """ Class to manage all the quota operation for the Geocoder SQL API Extension """ + + GEOCODING_QUOTA_KEY = "geocoding_quota" + + def __init__(self, logger, user_id, transaction_id): + self.logger = logger + self.user_id = user_id + self.transaction_id = transaction_id + self.redis_conn = self.__get_redis_connection() + + def check_user_quota(self): + """ Get the user quota and add it to redis in order to cache it """ + # TODO: Check if the redis key geocoder::user_id::tx_id exists: + # a) If exists check the quota + user_quota = self.get_user_quota() + current_used = self.get_current_used_quota() + return True if (current_used + 1) < user_quota else False + + def get_user_quota(self): + return self.redis_conn.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) + + def get_current_used_quota(self): + # TODO: Check if exist geocoder:user_id:year_month , if yes sum up all of it + current_used = 0 + for _, value in self.redis_conn.hscan_iter(self.__get_month_redis_key()): + current_used += int(value) + return current_used + + def increment_georeference_use(self): + pass + + def __get_redis_connection(self): + pool = redis.ConnectionPool(host='localhost', port=6379, db=5) + return redis.Redis(connection_pool=pool) + + def __get_month_redis_key(self): + today = date.today() + return "geocoder:{0}:{1}".format(self.user_id, today.strftime("%Y%m")) + + def __get_user_redis_key(self): + return "geocoder:{0}".format(self.user_id) \ No newline at end of file diff --git a/server/lib/python/cartodb_geocoder/requirements.txt b/server/lib/python/cartodb_geocoder/requirements.txt new file mode 100644 index 0000000..d964b9a --- /dev/null +++ b/server/lib/python/cartodb_geocoder/requirements.txt @@ -0,0 +1 @@ +redis-py==2.10.5 diff --git a/server/lib/python/cartodb_geocoder/setup.py b/server/lib/python/cartodb_geocoder/setup.py new file mode 100644 index 0000000..e69de29 From 48052f8a701f6ebe374f240d272059c6ed073d89 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Fri, 6 Nov 2015 17:49:11 +0100 Subject: [PATCH 02/14] Increment every time a row is georeferenced successfully --- .../cartodb_geocoder/quota_service.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py index 070b4bd..dab3f2f 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py @@ -6,35 +6,43 @@ class QuotaService: GEOCODING_QUOTA_KEY = "geocoding_quota" - def __init__(self, logger, user_id, transaction_id): + def __init__(self, logger, user_id, transaction_id, redis_host='localhost', redis_port=6379, redis_db = 5): self.logger = logger self.user_id = user_id self.transaction_id = transaction_id + self.redis_host = redis_host + self.redis_port = redis_port + self.redis_db = redis_db self.redis_conn = self.__get_redis_connection() def check_user_quota(self): - """ Get the user quota and add it to redis in order to cache it """ - # TODO: Check if the redis key geocoder::user_id::tx_id exists: - # a) If exists check the quota + """ Check if the current user quota surpasses the current quota """ + # TODO We need to add the hard/soft limit flag for the geocoder user_quota = self.get_user_quota() current_used = self.get_current_used_quota() + self.logger.debug("User quota: {0} --- Current used quota: {1}".format(user_quota, current_used)) return True if (current_used + 1) < user_quota else False def get_user_quota(self): - return self.redis_conn.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) + # Check for exceptions or redis timeout + user_quota = self.redis_conn.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) + return int(user_quota) def get_current_used_quota(self): - # TODO: Check if exist geocoder:user_id:year_month , if yes sum up all of it + """ Recover the used quota for the user in the current month """ + # Check for exceptions or redis timeout current_used = 0 for _, value in self.redis_conn.hscan_iter(self.__get_month_redis_key()): current_used += int(value) return current_used - def increment_georeference_use(self): - pass + def increment_georeference_use(self, amount=1): + """ Increment the geocoder use in 1 """ + # TODO Manage exceptions or timeout + self.redis_conn.hincrby(self.__get_month_redis_key(), self.transaction_id,amount) def __get_redis_connection(self): - pool = redis.ConnectionPool(host='localhost', port=6379, db=5) + pool = redis.ConnectionPool(host=self.redis_host, port=self.redis_port, db=self.redis_db) return redis.Redis(connection_pool=pool) def __get_month_redis_key(self): From 904336a298a555795137c9feafbd5f52a6e1cd28 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Fri, 6 Nov 2015 17:50:54 +0100 Subject: [PATCH 03/14] Added plpythonu function example --- .../example/server_func_example.sql | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 server/lib/python/cartodb_geocoder/example/server_func_example.sql diff --git a/server/lib/python/cartodb_geocoder/example/server_func_example.sql b/server/lib/python/cartodb_geocoder/example/server_func_example.sql new file mode 100644 index 0000000..259069d --- /dev/null +++ b/server/lib/python/cartodb_geocoder/example/server_func_example.sql @@ -0,0 +1,24 @@ +CREATE OR REPLACE +FUNCTION geocode_admin0(search text, tx_id bigint, user_id name) + RETURNS Geometry AS +$$ + import logging + from sys import path + path.append( '/home/ubuntu/www/cartodb-geocoder/server/lib/python/cartodb_geocoder' ) + import quota_service + + LOG_FILENAME = '/tmp/plpython.log' + logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG) + + qs = quota_service.QuotaService(logging, user_id, tx_id) + if qs.check_user_quota(): + result = plpy.execute("SELECT geom FROM geocode_admin0_polygons(Array[\'{0}\']::text[])".format(search)) + logging.debug("Number of rows: {0} --- Status: {1}".format(result.nrows(), result.status())) + if result.status() == 5 and result.nrows() == 1: + qs.increment_georeference_use() + return result[0]["geom"] + else: + raise Exception('Something wrong with the georefence operation') + else: + raise Exception('Not enough quota for this user') +$$ LANGUAGE plpythonu; \ No newline at end of file From a375e9dcfb25819c722b34137a9476c0bb0a8d42 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Mon, 9 Nov 2015 16:43:10 +0100 Subject: [PATCH 04/14] Cached redis connections --- .../cartodb_geocoder/quota_service.py | 62 +++++++++++++++---- .../example/server_func_example.sql | 10 ++- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py index dab3f2f..3f4fca5 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py @@ -5,15 +5,25 @@ class QuotaService: """ Class to manage all the quota operation for the Geocoder SQL API Extension """ GEOCODING_QUOTA_KEY = "geocoding_quota" + REDIS_CONNECTION_KEY = "redis_connection" + REDIS_CONNECTION_HOST = "redis_host" + REDIS_CONNECTION_PORT = "redis_port" + REDIS_CONNECTION_DB = "redis_db" - def __init__(self, logger, user_id, transaction_id, redis_host='localhost', redis_port=6379, redis_db = 5): + def __init__(self, logger, user_id, transaction_id, **kwargs): self.logger = logger self.user_id = user_id self.transaction_id = transaction_id - self.redis_host = redis_host - self.redis_port = redis_port - self.redis_db = redis_db - self.redis_conn = self.__get_redis_connection() + self.cache = {} + + if self.REDIS_CONNECTION_KEY in kwargs: + self.redis_connection = self.__get_redis_connection(redis_connection=kwargs[self.REDIS_CONNECTION_KEY]) + else: + if self.REDIS_CONNECTION_HOST not in kwargs: + raise "You have to provide redis configuration" + redis_config = self.__build_redis_config(kwargs) + self.redis_connection = self.__get_redis_connection(redis_config = redis_config) + def check_user_quota(self): """ Check if the current user quota surpasses the current quota """ @@ -25,25 +35,51 @@ class QuotaService: def get_user_quota(self): # Check for exceptions or redis timeout - user_quota = self.redis_conn.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) - return int(user_quota) + user_quota = self.redis_connection.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) + return int(user_quota) if user_quota else 0 def get_current_used_quota(self): """ Recover the used quota for the user in the current month """ # Check for exceptions or redis timeout current_used = 0 - for _, value in self.redis_conn.hscan_iter(self.__get_month_redis_key()): + for _, value in self.redis_connection.hscan_iter(self.__get_month_redis_key()): current_used += int(value) return current_used def increment_georeference_use(self, amount=1): - """ Increment the geocoder use in 1 """ # TODO Manage exceptions or timeout - self.redis_conn.hincrby(self.__get_month_redis_key(), self.transaction_id,amount) + self.redis_connection.hincrby(self.__get_month_redis_key(), self.transaction_id,amount) - def __get_redis_connection(self): - pool = redis.ConnectionPool(host=self.redis_host, port=self.redis_port, db=self.redis_db) - return redis.Redis(connection_pool=pool) + def get_redis_connection(self): + return self.redis_connection + + def __get_redis_connection(self, redis_connection=None, redis_config=None): + if redis_connection: + self.__add_redis_connection_to_cache(redis_connection) + conn = redis_connection + else: + conn = self.__create_redis_connection(redis_config) + + return conn + + def __create_redis_connection(self, redis_config): + # Pool not needed + # Try to not create a connection every time, add it to a cache + self.logger.debug("Connecting to redis...") + pool = redis.ConnectionPool(host=redis_config['host'], port=redis_config['port'], db=redis_config['db']) + conn = redis.Redis(connection_pool=pool) + self.__add_redis_connection_to_cache(conn) + return conn + + def __build_redis_config(self, config): + redis_host = config[self.REDIS_CONNECTION_HOST] if self.REDIS_CONNECTION_HOST in config else 'localhost' + redis_port = config[self.REDIS_CONNECTION_PORT] if self.REDIS_CONNECTION_PORT in config else 6379 + redis_db = config[self.REDIS_CONNECTION_DB] if self.REDIS_CONNECTION_DB in config else 5 + return {'host': redis_host, 'port': redis_port, 'db': redis_db} + + def __add_redis_connection_to_cache(self, connection): + """ Cache the redis connection to avoid reach the limit of connections """ + self.cache = {self.transaction_id: {self.REDIS_CONNECTION_KEY: connection}} def __get_month_redis_key(self): today = date.today() diff --git a/server/lib/python/cartodb_geocoder/example/server_func_example.sql b/server/lib/python/cartodb_geocoder/example/server_func_example.sql index 259069d..b8e63e3 100644 --- a/server/lib/python/cartodb_geocoder/example/server_func_example.sql +++ b/server/lib/python/cartodb_geocoder/example/server_func_example.sql @@ -10,15 +10,21 @@ $$ LOG_FILENAME = '/tmp/plpython.log' logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG) - qs = quota_service.QuotaService(logging, user_id, tx_id) + if user_id in SD and tx_id in SD[user_id] and 'redis_connection' in SD[user_id][tx_id]: + logging.debug("Using redis cached connection...") + qs = quota_service.QuotaService(logging, user_id, tx_id, redis_connection=SD[user_id][tx_id]['redis_connection']) + else: + qs = quota_service.QuotaService(logging, user_id, tx_id, redis_host='localhost', redis_port=6379, redis_db=5) + if qs.check_user_quota(): result = plpy.execute("SELECT geom FROM geocode_admin0_polygons(Array[\'{0}\']::text[])".format(search)) - logging.debug("Number of rows: {0} --- Status: {1}".format(result.nrows(), result.status())) if result.status() == 5 and result.nrows() == 1: qs.increment_georeference_use() + SD[user_id] = {tx_id: {'redis_connection': qs.get_redis_connection()}} return result[0]["geom"] else: raise Exception('Something wrong with the georefence operation') else: raise Exception('Not enough quota for this user') + $$ LANGUAGE plpythonu; \ No newline at end of file From 76c3b18dd624b34b1e69d29cc1904f23e05910c0 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Mon, 9 Nov 2015 16:43:18 +0100 Subject: [PATCH 05/14] Client function example --- .../cartodb_geocoder/example/client_func_example.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 server/lib/python/cartodb_geocoder/example/client_func_example.sql diff --git a/server/lib/python/cartodb_geocoder/example/client_func_example.sql b/server/lib/python/cartodb_geocoder/example/client_func_example.sql new file mode 100644 index 0000000..9144283 --- /dev/null +++ b/server/lib/python/cartodb_geocoder/example/client_func_example.sql @@ -0,0 +1,12 @@ +CREATE OR REPLACE FUNCTION geocode_admin0_polygons(search text) + RETURNS SETOF Geometry AS $$ +BEGIN + RETURN QUERY SELECT geocode_admin0_polygons(search, session_user, txid_current()); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION geocode_admin0_polygons(search text, user_id name, tx_id bigint) +RETURNS Geometry AS $$ + CONNECT 'dbname=cartodb_dev_user_274bf952-8568-4598-9efd-be92ed3d2ead_db user=postgres'; + SELECT geom FROM geocode_admin0(search, tx_id, user_id); +$$ LANGUAGE plproxy; \ No newline at end of file From 8ed26d4dd0a63fbe8507d274065e6a9c6d5633e2 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Tue, 10 Nov 2015 12:55:59 +0100 Subject: [PATCH 06/14] Client example using the cdb_conf table --- .../example/client_func_example.sql | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/server/lib/python/cartodb_geocoder/example/client_func_example.sql b/server/lib/python/cartodb_geocoder/example/client_func_example.sql index 9144283..7c0a580 100644 --- a/server/lib/python/cartodb_geocoder/example/client_func_example.sql +++ b/server/lib/python/cartodb_geocoder/example/client_func_example.sql @@ -1,12 +1,29 @@ -CREATE OR REPLACE FUNCTION geocode_admin0_polygons(search text) - RETURNS SETOF Geometry AS $$ -BEGIN - RETURN QUERY SELECT geocode_admin0_polygons(search, session_user, txid_current()); -END; -$$ LANGUAGE plpgsql; +# cdb_conf geocoder config example +INSERT INTO cdb_conf VALUES ('geocoder_conf', '{"geocoder_db": {"host": "localhost", "port": "5432", db": "cartodb_dev_user_274bf952-8568-4598-9efd-be92ed3d2ead_db", "user": "development_cartodb_user_274bf952-8568-4598-9efd-be92ed3d2ead"}, "redis": {"host": "localhost", "port": 6379, "db": 5 } }') -CREATE OR REPLACE FUNCTION geocode_admin0_polygons(search text, user_id name, tx_id bigint) +CREATE OR REPLACE FUNCTION cartodb._Geocoder_Admin0_Polygons(search text) + RETURNS Geometry AS +$$ + db_connection_str = plpy.execute("SELECT * FROM cartodb._Geocoder_Server_Conf() conf;")[0]['conf'] + return plpy.execute("SELECT cartodb._Geocoder_Admin0_Polygons('{0}', session_user, txid_current(), '{1}') as geom".format(search, db_connection_str))[0]['geom'] +$$ LANGUAGE plpythonu SECURITY DEFINER; + +CREATE OR REPLACE +FUNCTION cartodb._Geocoder_Server_Conf() + RETURNS text AS +$$ + conf = plpy.execute("SELECT cartodb.CDB_Conf_GetConf('geocoder_conf') conf")[0]['conf'] + if conf is None: + raise "There is no geocoder server configuration " + else: + import json + params = json.loads(conf) + db_params = params['geocoder_db'] + return "host={0} port={1} dbname={2} user={3}".format(db_params['host'],db_params['port'],db_params['db'],db_params['user']) +$$ LANGUAGE 'plpythonu'; + +CREATE OR REPLACE FUNCTION cartodb._Geocoder_Admin0_Polygons(search text, user_id name, tx_id bigint, db_connection_str text) RETURNS Geometry AS $$ - CONNECT 'dbname=cartodb_dev_user_274bf952-8568-4598-9efd-be92ed3d2ead_db user=postgres'; - SELECT geom FROM geocode_admin0(search, tx_id, user_id); + CONNECT db_connection_str; + SELECT geocode_admin0(search, tx_id, user_id); $$ LANGUAGE plproxy; \ No newline at end of file From ab19c1b8edde60e7ad17148a1e6538171ff94e1b Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Tue, 10 Nov 2015 13:48:39 +0100 Subject: [PATCH 07/14] Added setup.py for the package --- server/lib/python/cartodb_geocoder/setup.py | 40 +++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/server/lib/python/cartodb_geocoder/setup.py b/server/lib/python/cartodb_geocoder/setup.py index e69de29..edcd74b 100644 --- a/server/lib/python/cartodb_geocoder/setup.py +++ b/server/lib/python/cartodb_geocoder/setup.py @@ -0,0 +1,40 @@ +""" +CartoDB Geocoder Python Library + +See: +https://github.com/CartoDB/geocoder-api +""" + +from setuptools import setup, find_packages + +setup( + name='cartodb_geocoder', + + version='0.0.1', + + description='CartoDB Geocoder Python Library', + + url='https://github.com/CartoDB/geocoder-api', + + author='Data Services Team - CartoDB', + author_email='dataservices@cartodb.com', + + license='MIT', + + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Mapping comunity', + 'Topic :: Maps :: Mapping Tools', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 2.7', + ], + + keywords='maps api mapping tools geocoder', + + packages=find_packages(exclude=['contrib', 'docs', 'tests']), + + extras_require={ + 'dev': ['unittest'], + 'test': ['unittest'], + } +) From bb3b2cc2dae30549a18ad9b3dac1145b763aff32 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Tue, 10 Nov 2015 13:48:52 +0100 Subject: [PATCH 08/14] Change to use the installed package --- .../python/cartodb_geocoder/example/server_func_example.sql | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/lib/python/cartodb_geocoder/example/server_func_example.sql b/server/lib/python/cartodb_geocoder/example/server_func_example.sql index b8e63e3..4f7bbc5 100644 --- a/server/lib/python/cartodb_geocoder/example/server_func_example.sql +++ b/server/lib/python/cartodb_geocoder/example/server_func_example.sql @@ -3,9 +3,7 @@ FUNCTION geocode_admin0(search text, tx_id bigint, user_id name) RETURNS Geometry AS $$ import logging - from sys import path - path.append( '/home/ubuntu/www/cartodb-geocoder/server/lib/python/cartodb_geocoder' ) - import quota_service + from cartodb_geocoder import quota_service LOG_FILENAME = '/tmp/plpython.log' logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG) From 7eab3e1f903f125889c3fbb46cb89a46fd8fbe67 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Tue, 10 Nov 2015 16:32:53 +0100 Subject: [PATCH 09/14] Refactor, extracted user_service --- .../cartodb_geocoder/quota_service.py | 80 ++----------------- .../cartodb_geocoder/user_service.py | 73 +++++++++++++++++ .../example/client_func_example.sql | 6 +- .../example/server_func_example.sql | 4 +- 4 files changed, 86 insertions(+), 77 deletions(-) create mode 100644 server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py index 3f4fca5..7c170cb 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py @@ -1,89 +1,25 @@ import redis +import user_service from datetime import date class QuotaService: """ Class to manage all the quota operation for the Geocoder SQL API Extension """ - GEOCODING_QUOTA_KEY = "geocoding_quota" - REDIS_CONNECTION_KEY = "redis_connection" - REDIS_CONNECTION_HOST = "redis_host" - REDIS_CONNECTION_PORT = "redis_port" - REDIS_CONNECTION_DB = "redis_db" - def __init__(self, logger, user_id, transaction_id, **kwargs): self.logger = logger - self.user_id = user_id + self.user_service = user_service.UserService(logger, user_id, **kwargs) self.transaction_id = transaction_id - self.cache = {} - - if self.REDIS_CONNECTION_KEY in kwargs: - self.redis_connection = self.__get_redis_connection(redis_connection=kwargs[self.REDIS_CONNECTION_KEY]) - else: - if self.REDIS_CONNECTION_HOST not in kwargs: - raise "You have to provide redis configuration" - redis_config = self.__build_redis_config(kwargs) - self.redis_connection = self.__get_redis_connection(redis_config = redis_config) - def check_user_quota(self): """ Check if the current user quota surpasses the current quota """ # TODO We need to add the hard/soft limit flag for the geocoder - user_quota = self.get_user_quota() - current_used = self.get_current_used_quota() + user_quota = self.user_service.get_user_quota() + current_used = self.user_service.get_current_used_quota() self.logger.debug("User quota: {0} --- Current used quota: {1}".format(user_quota, current_used)) return True if (current_used + 1) < user_quota else False - def get_user_quota(self): - # Check for exceptions or redis timeout - user_quota = self.redis_connection.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) - return int(user_quota) if user_quota else 0 + def increment_geocoder_use(self, amount=1): + self.user_service.increment_geocoder_use(self.transaction_id) - def get_current_used_quota(self): - """ Recover the used quota for the user in the current month """ - # Check for exceptions or redis timeout - current_used = 0 - for _, value in self.redis_connection.hscan_iter(self.__get_month_redis_key()): - current_used += int(value) - return current_used - - def increment_georeference_use(self, amount=1): - # TODO Manage exceptions or timeout - self.redis_connection.hincrby(self.__get_month_redis_key(), self.transaction_id,amount) - - def get_redis_connection(self): - return self.redis_connection - - def __get_redis_connection(self, redis_connection=None, redis_config=None): - if redis_connection: - self.__add_redis_connection_to_cache(redis_connection) - conn = redis_connection - else: - conn = self.__create_redis_connection(redis_config) - - return conn - - def __create_redis_connection(self, redis_config): - # Pool not needed - # Try to not create a connection every time, add it to a cache - self.logger.debug("Connecting to redis...") - pool = redis.ConnectionPool(host=redis_config['host'], port=redis_config['port'], db=redis_config['db']) - conn = redis.Redis(connection_pool=pool) - self.__add_redis_connection_to_cache(conn) - return conn - - def __build_redis_config(self, config): - redis_host = config[self.REDIS_CONNECTION_HOST] if self.REDIS_CONNECTION_HOST in config else 'localhost' - redis_port = config[self.REDIS_CONNECTION_PORT] if self.REDIS_CONNECTION_PORT in config else 6379 - redis_db = config[self.REDIS_CONNECTION_DB] if self.REDIS_CONNECTION_DB in config else 5 - return {'host': redis_host, 'port': redis_port, 'db': redis_db} - - def __add_redis_connection_to_cache(self, connection): - """ Cache the redis connection to avoid reach the limit of connections """ - self.cache = {self.transaction_id: {self.REDIS_CONNECTION_KEY: connection}} - - def __get_month_redis_key(self): - today = date.today() - return "geocoder:{0}:{1}".format(self.user_id, today.strftime("%Y%m")) - - def __get_user_redis_key(self): - return "geocoder:{0}".format(self.user_id) \ No newline at end of file + def get_user_service(self): + return self.user_service \ No newline at end of file diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py new file mode 100644 index 0000000..5e7c578 --- /dev/null +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py @@ -0,0 +1,73 @@ +import redis +from datetime import date + +class UserService: + """ Class to manage all the user info """ + + GEOCODING_QUOTA_KEY = "geocoding_quota" + REDIS_CONNECTION_KEY = "redis_connection" + REDIS_CONNECTION_HOST = "redis_host" + REDIS_CONNECTION_PORT = "redis_port" + REDIS_CONNECTION_DB = "redis_db" + + REDIS_DEFAULT_USER_DB = 5 + REDIS_DEFAULT_HOST = 'localhost' + REDIS_DEFAULT_PORT = 6379 + + def __init__(self, logger, user_id, **kwargs): + self.user_id = user_id + self.logger = logger + if self.REDIS_CONNECTION_KEY in kwargs: + self.redis_connection = self.__get_redis_connection(redis_connection=kwargs[self.REDIS_CONNECTION_KEY]) + else: + if self.REDIS_CONNECTION_HOST not in kwargs: + raise "You have to provide redis configuration" + redis_config = self.__build_redis_config(kwargs) + self.redis_connection = self.__get_redis_connection(redis_config = redis_config) + + def get_user_quota(self): + # Check for exceptions or redis timeout + user_quota = self.redis_connection.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) + return int(user_quota) if user_quota else 0 + + def get_current_used_quota(self): + """ Recover the used quota for the user in the current month """ + # Check for exceptions or redis timeout + current_used = 0 + for _, value in self.redis_connection.hscan_iter(self.__get_month_redis_key()): + current_used += int(value) + return current_used + + def increment_geocoder_use(self, key, amount=1): + # TODO Manage exceptions or timeout + self.redis_connection.hincrby(self.__get_month_redis_key(),key,amount) + + def get_redis_connection(self): + return self.redis_connection + + def __get_redis_connection(self, redis_connection=None, redis_config=None): + if redis_connection: + conn = redis_connection + else: + conn = self.__create_redis_connection(redis_config) + + return conn + + def __create_redis_connection(self, redis_config): + self.logger.debug("Connecting to redis...") + pool = redis.ConnectionPool(host=redis_config['host'], port=redis_config['port'], db=redis_config['db']) + conn = redis.Redis(connection_pool=pool) + return conn + + def __build_redis_config(self, config): + redis_host = config[self.REDIS_CONNECTION_HOST] if self.REDIS_CONNECTION_HOST in config else self.REDIS_DEFAULT_HOST + redis_port = config[self.REDIS_CONNECTION_PORT] if self.REDIS_CONNECTION_PORT in config else self.REDIS_CONNECTION_PORT + redis_db = config[self.REDIS_CONNECTION_DB] if self.REDIS_CONNECTION_DB in config else self.REDIS_DEFAULT_USER_DB + return {'host': redis_host, 'port': redis_port, 'db': redis_db} + + def __get_month_redis_key(self): + today = date.today() + return "geocoder:{0}:{1}".format(self.user_id, today.strftime("%Y%m")) + + def __get_user_redis_key(self): + return "geocoder:{0}".format(self.user_id) \ No newline at end of file diff --git a/server/lib/python/cartodb_geocoder/example/client_func_example.sql b/server/lib/python/cartodb_geocoder/example/client_func_example.sql index 7c0a580..4ae26d0 100644 --- a/server/lib/python/cartodb_geocoder/example/client_func_example.sql +++ b/server/lib/python/cartodb_geocoder/example/client_func_example.sql @@ -1,7 +1,7 @@ # cdb_conf geocoder config example INSERT INTO cdb_conf VALUES ('geocoder_conf', '{"geocoder_db": {"host": "localhost", "port": "5432", db": "cartodb_dev_user_274bf952-8568-4598-9efd-be92ed3d2ead_db", "user": "development_cartodb_user_274bf952-8568-4598-9efd-be92ed3d2ead"}, "redis": {"host": "localhost", "port": 6379, "db": 5 } }') -CREATE OR REPLACE FUNCTION cartodb._Geocoder_Admin0_Polygons(search text) +CREATE OR REPLACE FUNCTION cartodb._geocoder_admin0_polygons(search text) RETURNS Geometry AS $$ db_connection_str = plpy.execute("SELECT * FROM cartodb._Geocoder_Server_Conf() conf;")[0]['conf'] @@ -9,7 +9,7 @@ $$ $$ LANGUAGE plpythonu SECURITY DEFINER; CREATE OR REPLACE -FUNCTION cartodb._Geocoder_Server_Conf() +FUNCTION cartodb._geocoder_server_conf() RETURNS text AS $$ conf = plpy.execute("SELECT cartodb.CDB_Conf_GetConf('geocoder_conf') conf")[0]['conf'] @@ -22,7 +22,7 @@ $$ return "host={0} port={1} dbname={2} user={3}".format(db_params['host'],db_params['port'],db_params['db'],db_params['user']) $$ LANGUAGE 'plpythonu'; -CREATE OR REPLACE FUNCTION cartodb._Geocoder_Admin0_Polygons(search text, user_id name, tx_id bigint, db_connection_str text) +CREATE OR REPLACE FUNCTION cartodb._geocoder_admin0_polygons(search text, user_id name, tx_id bigint, db_connection_str text) RETURNS Geometry AS $$ CONNECT db_connection_str; SELECT geocode_admin0(search, tx_id, user_id); diff --git a/server/lib/python/cartodb_geocoder/example/server_func_example.sql b/server/lib/python/cartodb_geocoder/example/server_func_example.sql index 4f7bbc5..2190cda 100644 --- a/server/lib/python/cartodb_geocoder/example/server_func_example.sql +++ b/server/lib/python/cartodb_geocoder/example/server_func_example.sql @@ -17,8 +17,8 @@ $$ if qs.check_user_quota(): result = plpy.execute("SELECT geom FROM geocode_admin0_polygons(Array[\'{0}\']::text[])".format(search)) if result.status() == 5 and result.nrows() == 1: - qs.increment_georeference_use() - SD[user_id] = {tx_id: {'redis_connection': qs.get_redis_connection()}} + qs.increment_geocoder_use() + SD[user_id] = {tx_id: {'redis_connection': qs.get_user_service().get_redis_connection()}} return result[0]["geom"] else: raise Exception('Something wrong with the georefence operation') From 29ec5da86d2779cfa786d7c460a7eb0b82a81a05 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Tue, 10 Nov 2015 16:49:27 +0100 Subject: [PATCH 10/14] Added properties instead of getters --- .../cartodb_geocoder/quota_service.py | 11 +++++----- .../cartodb_geocoder/user_service.py | 21 ++++++++++--------- .../example/server_func_example.sql | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py index 7c170cb..7bac2c0 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py @@ -7,19 +7,20 @@ class QuotaService: def __init__(self, logger, user_id, transaction_id, **kwargs): self.logger = logger - self.user_service = user_service.UserService(logger, user_id, **kwargs) + self._user_service = user_service.UserService(logger, user_id, **kwargs) self.transaction_id = transaction_id def check_user_quota(self): """ Check if the current user quota surpasses the current quota """ # TODO We need to add the hard/soft limit flag for the geocoder - user_quota = self.user_service.get_user_quota() - current_used = self.user_service.get_current_used_quota() + user_quota = self.user_service.user_quota() + current_used = self.user_service.used_quota_month() self.logger.debug("User quota: {0} --- Current used quota: {1}".format(user_quota, current_used)) return True if (current_used + 1) < user_quota else False def increment_geocoder_use(self, amount=1): self.user_service.increment_geocoder_use(self.transaction_id) - def get_user_service(self): - return self.user_service \ No newline at end of file + @property + def user_service(self): + return self._user_service \ No newline at end of file diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py index 5e7c578..382054c 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py @@ -18,32 +18,33 @@ class UserService: self.user_id = user_id self.logger = logger if self.REDIS_CONNECTION_KEY in kwargs: - self.redis_connection = self.__get_redis_connection(redis_connection=kwargs[self.REDIS_CONNECTION_KEY]) + self._redis_connection = self.__get_redis_connection(redis_connection=kwargs[self.REDIS_CONNECTION_KEY]) else: if self.REDIS_CONNECTION_HOST not in kwargs: raise "You have to provide redis configuration" redis_config = self.__build_redis_config(kwargs) - self.redis_connection = self.__get_redis_connection(redis_config = redis_config) + self._redis_connection = self.__get_redis_connection(redis_config = redis_config) - def get_user_quota(self): + def user_quota(self): # Check for exceptions or redis timeout - user_quota = self.redis_connection.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) + user_quota = self._redis_connection.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) return int(user_quota) if user_quota else 0 - def get_current_used_quota(self): + def used_quota_month(self): """ Recover the used quota for the user in the current month """ # Check for exceptions or redis timeout current_used = 0 - for _, value in self.redis_connection.hscan_iter(self.__get_month_redis_key()): + for _, value in self._redis_connection.hscan_iter(self.__get_month_redis_key()): current_used += int(value) return current_used def increment_geocoder_use(self, key, amount=1): # TODO Manage exceptions or timeout - self.redis_connection.hincrby(self.__get_month_redis_key(),key,amount) + self._redis_connection.hincrby(self.__get_month_redis_key(),key,amount) - def get_redis_connection(self): - return self.redis_connection + @property + def redis_connection(self): + return self._redis_connection def __get_redis_connection(self, redis_connection=None, redis_config=None): if redis_connection: @@ -61,7 +62,7 @@ class UserService: def __build_redis_config(self, config): redis_host = config[self.REDIS_CONNECTION_HOST] if self.REDIS_CONNECTION_HOST in config else self.REDIS_DEFAULT_HOST - redis_port = config[self.REDIS_CONNECTION_PORT] if self.REDIS_CONNECTION_PORT in config else self.REDIS_CONNECTION_PORT + redis_port = config[self.REDIS_CONNECTION_PORT] if self.REDIS_CONNECTION_PORT in config else self.REDIS_DEFAULT_PORT redis_db = config[self.REDIS_CONNECTION_DB] if self.REDIS_CONNECTION_DB in config else self.REDIS_DEFAULT_USER_DB return {'host': redis_host, 'port': redis_port, 'db': redis_db} diff --git a/server/lib/python/cartodb_geocoder/example/server_func_example.sql b/server/lib/python/cartodb_geocoder/example/server_func_example.sql index 2190cda..7460dc4 100644 --- a/server/lib/python/cartodb_geocoder/example/server_func_example.sql +++ b/server/lib/python/cartodb_geocoder/example/server_func_example.sql @@ -18,7 +18,7 @@ $$ result = plpy.execute("SELECT geom FROM geocode_admin0_polygons(Array[\'{0}\']::text[])".format(search)) if result.status() == 5 and result.nrows() == 1: qs.increment_geocoder_use() - SD[user_id] = {tx_id: {'redis_connection': qs.get_user_service().get_redis_connection()}} + SD[user_id] = {tx_id: {'redis_connection': qs.user_service.redis_connection}} return result[0]["geom"] else: raise Exception('Something wrong with the georefence operation') From f22807553f5d454136152e2fbf789b9889b36343 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Tue, 10 Nov 2015 16:54:47 +0100 Subject: [PATCH 11/14] Removed logger --- .../cartodb_geocoder/cartodb_geocoder/quota_service.py | 6 ++---- .../cartodb_geocoder/cartodb_geocoder/user_service.py | 4 +--- .../python/cartodb_geocoder/example/server_func_example.sql | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py index 7bac2c0..bf49548 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py @@ -5,9 +5,8 @@ from datetime import date class QuotaService: """ Class to manage all the quota operation for the Geocoder SQL API Extension """ - def __init__(self, logger, user_id, transaction_id, **kwargs): - self.logger = logger - self._user_service = user_service.UserService(logger, user_id, **kwargs) + def __init__(self, user_id, transaction_id, **kwargs): + self._user_service = user_service.UserService(user_id, **kwargs) self.transaction_id = transaction_id def check_user_quota(self): @@ -15,7 +14,6 @@ class QuotaService: # TODO We need to add the hard/soft limit flag for the geocoder user_quota = self.user_service.user_quota() current_used = self.user_service.used_quota_month() - self.logger.debug("User quota: {0} --- Current used quota: {1}".format(user_quota, current_used)) return True if (current_used + 1) < user_quota else False def increment_geocoder_use(self, amount=1): diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py index 382054c..145dc06 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py @@ -14,9 +14,8 @@ class UserService: REDIS_DEFAULT_HOST = 'localhost' REDIS_DEFAULT_PORT = 6379 - def __init__(self, logger, user_id, **kwargs): + def __init__(self, user_id, **kwargs): self.user_id = user_id - self.logger = logger if self.REDIS_CONNECTION_KEY in kwargs: self._redis_connection = self.__get_redis_connection(redis_connection=kwargs[self.REDIS_CONNECTION_KEY]) else: @@ -55,7 +54,6 @@ class UserService: return conn def __create_redis_connection(self, redis_config): - self.logger.debug("Connecting to redis...") pool = redis.ConnectionPool(host=redis_config['host'], port=redis_config['port'], db=redis_config['db']) conn = redis.Redis(connection_pool=pool) return conn diff --git a/server/lib/python/cartodb_geocoder/example/server_func_example.sql b/server/lib/python/cartodb_geocoder/example/server_func_example.sql index 7460dc4..564268c 100644 --- a/server/lib/python/cartodb_geocoder/example/server_func_example.sql +++ b/server/lib/python/cartodb_geocoder/example/server_func_example.sql @@ -10,9 +10,9 @@ $$ if user_id in SD and tx_id in SD[user_id] and 'redis_connection' in SD[user_id][tx_id]: logging.debug("Using redis cached connection...") - qs = quota_service.QuotaService(logging, user_id, tx_id, redis_connection=SD[user_id][tx_id]['redis_connection']) + qs = quota_service.QuotaService(user_id, tx_id, redis_connection=SD[user_id][tx_id]['redis_connection']) else: - qs = quota_service.QuotaService(logging, user_id, tx_id, redis_host='localhost', redis_port=6379, redis_db=5) + qs = quota_service.QuotaService(user_id, tx_id, redis_host='localhost', redis_port=6379, redis_db=5) if qs.check_user_quota(): result = plpy.execute("SELECT geom FROM geocode_admin0_polygons(Array[\'{0}\']::text[])".format(search)) From fbd48135b2d05d4053d95d230bac275a64436172 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Wed, 11 Nov 2015 11:47:38 +0100 Subject: [PATCH 12/14] Added tests for user service --- .../cartodb_geocoder/quota_service.py | 6 ++- .../cartodb_geocoder/user_service.py | 16 ++++---- server/lib/python/cartodb_geocoder/setup.py | 2 +- .../test/test_quota_service.py | 0 .../test/test_user_service.py | 37 +++++++++++++++++++ 5 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 server/lib/python/cartodb_geocoder/test/test_quota_service.py create mode 100644 server/lib/python/cartodb_geocoder/test/test_user_service.py diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py index bf49548..490c0b9 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py @@ -13,11 +13,13 @@ class QuotaService: """ Check if the current user quota surpasses the current quota """ # TODO We need to add the hard/soft limit flag for the geocoder user_quota = self.user_service.user_quota() - current_used = self.user_service.used_quota_month() + today = date.today() + current_used = self.user_service.used_quota_month(today.year, today.month) return True if (current_used + 1) < user_quota else False def increment_geocoder_use(self, amount=1): - self.user_service.increment_geocoder_use(self.transaction_id) + today = date.today() + self.user_service.increment_geocoder_use(today.year, today.month, self.transaction_id) @property def user_service(self): diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py index 145dc06..b804f14 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py @@ -20,26 +20,26 @@ class UserService: self._redis_connection = self.__get_redis_connection(redis_connection=kwargs[self.REDIS_CONNECTION_KEY]) else: if self.REDIS_CONNECTION_HOST not in kwargs: - raise "You have to provide redis configuration" + raise Exception("You have to provide redis configuration") redis_config = self.__build_redis_config(kwargs) self._redis_connection = self.__get_redis_connection(redis_config = redis_config) def user_quota(self): # Check for exceptions or redis timeout user_quota = self._redis_connection.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) - return int(user_quota) if user_quota else 0 + return int(user_quota) if user_quota and int(user_quota) >= 0 else 0 - def used_quota_month(self): + def used_quota_month(self, year, month): """ Recover the used quota for the user in the current month """ # Check for exceptions or redis timeout current_used = 0 - for _, value in self._redis_connection.hscan_iter(self.__get_month_redis_key()): + for _, value in self._redis_connection.hscan_iter(self.__get_month_redis_key(year,month)): current_used += int(value) return current_used - def increment_geocoder_use(self, key, amount=1): + def increment_geocoder_use(self, year, month, key, amount=1): # TODO Manage exceptions or timeout - self._redis_connection.hincrby(self.__get_month_redis_key(),key,amount) + self._redis_connection.hincrby(self.__get_month_redis_key(year, month),key,amount) @property def redis_connection(self): @@ -64,9 +64,9 @@ class UserService: redis_db = config[self.REDIS_CONNECTION_DB] if self.REDIS_CONNECTION_DB in config else self.REDIS_DEFAULT_USER_DB return {'host': redis_host, 'port': redis_port, 'db': redis_db} - def __get_month_redis_key(self): + def __get_month_redis_key(self, year, month): today = date.today() - return "geocoder:{0}:{1}".format(self.user_id, today.strftime("%Y%m")) + return "geocoder:{0}:{1}{2}".format(self.user_id, year, month) def __get_user_redis_key(self): return "geocoder:{0}".format(self.user_id) \ No newline at end of file diff --git a/server/lib/python/cartodb_geocoder/setup.py b/server/lib/python/cartodb_geocoder/setup.py index edcd74b..bc71c8f 100644 --- a/server/lib/python/cartodb_geocoder/setup.py +++ b/server/lib/python/cartodb_geocoder/setup.py @@ -35,6 +35,6 @@ setup( extras_require={ 'dev': ['unittest'], - 'test': ['unittest'], + 'test': ['unittest', 'nose', 'mockredispy'], } ) diff --git a/server/lib/python/cartodb_geocoder/test/test_quota_service.py b/server/lib/python/cartodb_geocoder/test/test_quota_service.py new file mode 100644 index 0000000..e69de29 diff --git a/server/lib/python/cartodb_geocoder/test/test_user_service.py b/server/lib/python/cartodb_geocoder/test/test_user_service.py new file mode 100644 index 0000000..491b123 --- /dev/null +++ b/server/lib/python/cartodb_geocoder/test/test_user_service.py @@ -0,0 +1,37 @@ +from mockredis import MockRedis +from cartodb_geocoder import user_service +from unittest import TestCase +from nose.tools import assert_raises + + +class TestUserService(TestCase): + + def setUp(self): + self.fake_redis_connection = MockRedis() + self.us = user_service.UserService('user_id', redis_connection = self.fake_redis_connection) + + def test_user_quota_should_be_10(self): + self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', 10) + assert self.us.user_quota() == 10 + + def test_should_return_0_if_negative_quota(self): + self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', -10) + assert self.us.user_quota() == 0 + + def test_should_return_0_if_not_user(self): + assert self.us.user_quota() == 0 + + def test_user_used_quota_for_a_month(self): + self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id', 10) + self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id_2', 10) + assert self.us.used_quota_month(2015, 11) == 20 + + def test_user_not_amount_in_used_quota_for_month_should_be_0(self): + assert self.us.used_quota_month(2015, 11) == 0 + + def test_increment_used_quota(self): + self.us.increment_geocoder_use(2015, 11, 'tx_id', 1) + assert self.us.used_quota_month(2015, 11) == 1 + + def test_exception_if_not_redis_config(self): + assert_raises(Exception, user_service.UserService, 'user_id') \ No newline at end of file From a18bfa955451cccf24c2a7741b29e348e4710b19 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Wed, 11 Nov 2015 12:28:24 +0100 Subject: [PATCH 13/14] Added quota service tests --- .../test/test_quota_service.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/server/lib/python/cartodb_geocoder/test/test_quota_service.py b/server/lib/python/cartodb_geocoder/test/test_quota_service.py index e69de29..d9c5a50 100644 --- a/server/lib/python/cartodb_geocoder/test/test_quota_service.py +++ b/server/lib/python/cartodb_geocoder/test/test_quota_service.py @@ -0,0 +1,27 @@ +from mockredis import MockRedis +from cartodb_geocoder import quota_service +from unittest import TestCase +from nose.tools import assert_raises + + +class TestQuotaService(TestCase): + + def setUp(self): + self.fake_redis_connection = MockRedis() + self.qs = quota_service.QuotaService('user_id', 'tx_id', redis_connection = self.fake_redis_connection) + + def test_should_return_true_if_quota_with_no_use(self): + self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', 100) + assert self.qs.check_user_quota() == True + + def test_should_return_true_if_quota_is_not_completely_used(self): + self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', 100) + self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id', 10) + self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id_2', 10) + assert self.qs.check_user_quota() == True + + def test_should_return_false_if_quota_is_surpassed(self): + self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', 1) + self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id', 10) + self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id_2', 10) + assert self.qs.check_user_quota() == False \ No newline at end of file From 6a75eae03c7317a1ce9f02b5eb24c382d1f69115 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Wed, 11 Nov 2015 13:17:15 +0100 Subject: [PATCH 14/14] Added soft/hard geocoder limit --- .../cartodb_geocoder/quota_service.py | 3 ++- .../cartodb_geocoder/user_service.py | 7 +++++++ .../cartodb_geocoder/test/test_quota_service.py | 13 ++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py index 490c0b9..5d61fad 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/quota_service.py @@ -15,7 +15,8 @@ class QuotaService: user_quota = self.user_service.user_quota() today = date.today() current_used = self.user_service.used_quota_month(today.year, today.month) - return True if (current_used + 1) < user_quota else False + soft_geocoder_limit = self.user_service.soft_geocoder_limit() + return True if soft_geocoder_limit or (current_used + 1) < user_quota else False def increment_geocoder_use(self, amount=1): today = date.today() diff --git a/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py index b804f14..3e22d48 100644 --- a/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py +++ b/server/lib/python/cartodb_geocoder/cartodb_geocoder/user_service.py @@ -5,6 +5,8 @@ class UserService: """ Class to manage all the user info """ GEOCODING_QUOTA_KEY = "geocoding_quota" + GEOCODING_SOFT_LIMIT_KEY = "soft_geocoder_limit" + REDIS_CONNECTION_KEY = "redis_connection" REDIS_CONNECTION_HOST = "redis_host" REDIS_CONNECTION_PORT = "redis_port" @@ -29,6 +31,11 @@ class UserService: user_quota = self._redis_connection.hget(self.__get_user_redis_key(), self.GEOCODING_QUOTA_KEY) return int(user_quota) if user_quota and int(user_quota) >= 0 else 0 + def soft_geocoder_limit(self): + """ Check what kind of limit the user has """ + soft_limit = self._redis_connection.hget(self.__get_user_redis_key(), self.GEOCODING_SOFT_LIMIT_KEY) + return True if soft_limit == '1' else False + def used_quota_month(self, year, month): """ Recover the used quota for the user in the current month """ # Check for exceptions or redis timeout diff --git a/server/lib/python/cartodb_geocoder/test/test_quota_service.py b/server/lib/python/cartodb_geocoder/test/test_quota_service.py index d9c5a50..d4c673a 100644 --- a/server/lib/python/cartodb_geocoder/test/test_quota_service.py +++ b/server/lib/python/cartodb_geocoder/test/test_quota_service.py @@ -8,14 +8,14 @@ class TestQuotaService(TestCase): def setUp(self): self.fake_redis_connection = MockRedis() + self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', 100) + self.fake_redis_connection.hset('geocoder:user_id','soft_geocoder_limit', 0) self.qs = quota_service.QuotaService('user_id', 'tx_id', redis_connection = self.fake_redis_connection) def test_should_return_true_if_quota_with_no_use(self): - self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', 100) assert self.qs.check_user_quota() == True def test_should_return_true_if_quota_is_not_completely_used(self): - self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', 100) self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id', 10) self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id_2', 10) assert self.qs.check_user_quota() == True @@ -24,4 +24,11 @@ class TestQuotaService(TestCase): self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', 1) self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id', 10) self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id_2', 10) - assert self.qs.check_user_quota() == False \ No newline at end of file + assert self.qs.check_user_quota() == False + + def test_should_return_true_if_quota_is_surpassed_but_soft_limit_is_enabled(self): + self.fake_redis_connection.hset('geocoder:user_id','geocoding_quota', 1) + self.fake_redis_connection.hset('geocoder:user_id','soft_geocoder_limit', 1) + self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id', 10) + self.fake_redis_connection.hset('geocoder:user_id:201511','tx_id_2', 10) + assert self.qs.check_user_quota() == True \ No newline at end of file