Compare commits

...

62 Commits

Author SHA1 Message Date
Javier Goizueta
ebbfae95cf Merge pull request #327 from CartoDB/mapzen_services_use_quotas
Mapzen services use quotas
2016-12-30 08:43:13 +01:00
Javier Goizueta
227c05bf70 Merge branch 'development' into mapzen_services_use_quotas
Bring in client 0.14.1 release
2016-12-30 08:29:54 +01:00
Rafa de la Torre
a800706249 Merge remote-tracking branch 'origin/master' into development 2016-12-29 17:27:56 +01:00
Rafa de la Torre
439f604a04 Merge pull request #334 from CartoDB/fix-cannot-find-geometry
Add cartodb to the search path
2016-12-29 12:43:34 +01:00
Rafa de la Torre
9791a5bada Add cartodb to the search path
See
https://github.com/CartoDB/dataservices-api/issues/324#issuecomment-269614566
2016-12-29 12:11:00 +01:00
Rafa de la Torre
9700ae966c Remove uneeded math for simplicity's sake 2016-12-28 14:44:48 +01:00
Javier Goizueta
bdf9627586 Fix typo
I needed to fix this; I felt some kitten was being killed somewhere
because of this. And it made baby Jesus cry.
2016-12-27 17:11:39 +01:00
Mario de Frutos
80b23c62c3 QPS timeout was badly calculated
timedelta microseconds is just the microseconds part of the timedelta
object not the elapsed time in microseconds.

I've change to use the total_seconds method to get all the elapsed time
in seconds and transform to miliseconds.
2016-12-23 11:40:30 +01:00
Mario de Frutos
23a2de0321 Add mapzen isolines to the quota checker 2016-12-22 17:38:10 +01:00
Javier Goizueta
629555e193 Generate release 0.14.1
This release cantains no actual code changes, only the
use of search_path at the top of the install/migrate scripts
2016-12-21 11:40:55 +01:00
Javier Goizueta
af993fde55 Merge branch 'development'
Bring in the setting of the search_path in the client SQL scripts
2016-12-21 11:16:29 +01:00
Javier Goizueta
4be6c9f31d Merge pull request #330 from CartoDB/324-search-path
Set search path before installing/update the extension
2016-12-21 10:47:08 +01:00
Javier Goizueta
9ee1d045c8 Fix tests 2016-12-21 09:54:08 +01:00
Rafa de la Torre
360b5bd57f Merge remote-tracking branch 'origin/development' into mapzen_services_use_quotas
Solve merge conflict in setup.py (version of python lib).
2016-12-20 18:14:01 +01:00
Javier Goizueta
ed1386d571 Merge branch 'development' 2016-12-20 16:43:42 +01:00
Javier Goizueta
32010669e8 New version of the python lib
To fix bug with period end date; see #322
2016-12-20 16:39:19 +01:00
Javier Goizueta
b0f10d1680 Merge pull request #329 from CartoDB/322-period_end_date
Fix problem with period end dates
2016-12-20 16:33:39 +01:00
Mario de Frutos
38754fec26 Cover refactor mapzen geocoder config with tests 2016-12-20 16:17:42 +01:00
Mario de Frutos
356135672b Sanitize quota get in the refactor part 2016-12-20 16:17:42 +01:00
Mario de Frutos
4d0abf9026 Bump python library version 2016-12-20 16:17:42 +01:00
Mario de Frutos
0672f2752b Improve tests and add more unit tests for the quota functionality 2016-12-20 16:17:41 +01:00
Javier Goizueta
18df3368ef Set search path before installing/update the extension
See #324
2016-12-20 12:57:40 +01:00
Javier Goizueta
b5514aea60 Avoid invalid end of period dates
Fixes #322
2016-12-20 12:48:36 +01:00
Javier Goizueta
53acd4d30e Add tests that reveal end-of-period date problem
See #322
2016-12-20 12:41:54 +01:00
Mario de Frutos
6c71d73498 Use the current quota for mapzen services 2016-12-20 11:52:06 +01:00
Rafa de la Torre
a00fca6d13 Update README.md
Add a bit about versioning stuff
2016-12-16 18:00:22 +01:00
Rafa de la Torre
29caaf9297 Update README.md
Remove misleading paragraph about `requirements.txt` and `setup.py` dependencies. Refer to https://packaging.python.org/requirements/ for an authoritative discussion.
2016-12-16 18:00:22 +01:00
Rafa de la Torre
6e134d1ea6 Update exception_safe.md 2016-12-15 19:10:18 +01:00
Rafa de la Torre
59ae4a5492 Merge pull request #325 from CartoDB/exception-safe-doc
Add Exception-safe documentaion
2016-12-15 19:06:37 +01:00
Javier Goizueta
b3e67afd92 Add internal documentaion about exception-safe functions 2016-12-15 19:03:12 +01:00
Rafa de la Torre
5b8fd70bdd Merge remote-tracking branch 'origin/development' 2016-12-14 17:23:09 +01:00
Rafa de la Torre
15438db59b Client 0.14.0 control and upgrade/downgrade files 2016-12-14 16:52:40 +01:00
Rafa de la Torre
0918f91bfa Merge pull request #323 from CartoDB/314-exception-safe-public-funcs
314 exception safe public funcs
2016-12-14 16:34:09 +01:00
Rafa de la Torre
3c60f3e93b Minor improvement for test #314
Make sure we return an empty record and that the mentioned code is never
reached.
2016-12-14 13:30:30 +01:00
Rafa de la Torre
8692fb12ca Add test case for multi_field #314 2016-12-14 12:57:51 +01:00
Rafa de la Torre
4523b2e04d Add test for multi_row case #314 2016-12-14 12:53:51 +01:00
Rafa de la Torre
5c8dbe91eb Add test for simple interface case #314 2016-12-14 12:39:56 +01:00
Rafa de la Torre
99b76afc33 Some versioning facilities for make release #314 2016-12-14 12:08:26 +01:00
Rafa de la Torre
c97f03b2e3 Add generated file to .gitignore #314 2016-12-14 12:06:51 +01:00
Rafa de la Torre
cd653bc496 Add diagnostics info to rest of cases #314 2016-12-14 10:35:44 +01:00
Rafa de la Torre
610cfaab57 Move exception info vars to the top #314 2016-12-14 10:33:50 +01:00
Rafa de la Torre
58b1713a0d Make the functions private #314 2016-12-14 10:27:43 +01:00
Rafa de la Torre
842be0ba85 Add diagnostics info to warning #314 2016-12-13 19:19:03 +01:00
Javier Goizueta
55a467f2df Merge branch 'development'
merge python 0.12.3 release
2016-12-13 18:13:14 +01:00
Javier Goizueta
4fc90626ab New version of the python lib
New release to change number of http connection retries to 1
2016-12-13 18:03:37 +01:00
Javier Goizueta
16cce3bddc Merge pull request #320 from CartoDB/317-revert-retries
Revert number of isochrone retries to 3
2016-12-13 18:00:48 +01:00
Rafa de la Torre
dbd5911a2a Implement the multi_field case #314 2016-12-13 17:00:55 +01:00
Rafa de la Torre
e53a39875e Implement the multi_row case #314 2016-12-13 16:51:11 +01:00
Rafa de la Torre
4cd72616ca Fix the no multi_row, no multi_field case #314
Fix for the `ERROR:  control reached end of function without RETURN` but
now need to implement for the two other cases.
2016-12-13 16:46:58 +01:00
Rafa de la Torre
fae7889fe3 First take at producing exception-safe functions #314 2016-12-13 16:27:38 +01:00
Javier Goizueta
1f53af65b9 Set number of http connection retries to 1
Change the maximum number of retries for connection to external services to 1.
2016-12-13 11:32:22 +01:00
Javier Goizueta
79fb796180 Fix bug in retries usage; mantain behaviour
The number of retries was being passed as the pool size parameter.
This is corrected but number of retries changed to 1 to maintain previous behaviour.
2016-12-13 10:07:10 +01:00
Rafa de la Torre
0adb5164d7 Update README.md
Add a bit about versioning stuff
2016-12-12 17:54:02 +01:00
Rafa de la Torre
147e0ab567 Update README.md
Remove misleading paragraph about `requirements.txt` and `setup.py` dependencies. Refer to https://packaging.python.org/requirements/ for an authoritative discussion.
2016-12-12 17:42:46 +01:00
Javier Goizueta
2953fda75c Revert num of isochrone retries to 3
See #317
2016-12-12 12:14:10 +01:00
Rafa de la Torre
f716fbb502 Merge pull request #318 from CartoDB/317-timeouts-patch
317 timeouts patch
2016-12-07 16:50:44 +01:00
Rafa de la Torre
98fa248fff New version of the python lib #317 2016-12-07 16:39:28 +01:00
Rafa de la Torre
7c348dee0f Mapzen isochrones: tweak retry params #317 2016-12-07 16:38:47 +01:00
Rafa de la Torre
bdeaadf33f Fix QPS: Compare everything in microseconds #317 2016-12-07 16:37:25 +01:00
Rafa de la Torre
a977bc97ab Merge pull request #315 from CartoDB/add_retries_to_requests
Make the HTTP requests retry on timeout
2016-12-07 12:51:02 +01:00
Mario de Frutos
51face5593 Make the HTTP requests retry on timeout 2016-12-02 17:01:05 +01:00
Mario de Frutos
a60a6f04b1 Update NEWS.md 2016-12-01 16:49:01 +01:00
37 changed files with 10384 additions and 191 deletions

25
NEWS.md
View File

@@ -1,3 +1,28 @@
November 29st, 2016
===================
* Version 0.20.0 of the server and version 0.12.0 of the python library
* Added integration with the new Mapzen isochrones functionality
November 25st, 2016
===================
* Version 0.19.0 of the server, version 0.11.0 of the python library and version 0.13.0 of the client
* functions to check the quota, both server and client
* removed the no_params from the templates (this caused trouble, not needed anymore)
* bug fixes: observatory quota, quotas as integers, mapzen geocoder soft limit as bool
November 21st, 2016
===================
* Version 0.18.1 of the server and version 0.12.1 of the client
* Add new fields to the obs_meta_geometry due to new changes introduced in the DO 1.1.2 release
November 11st, 2016
===================
* Version 0.18.0 of the server and version 0.12.0 of the client
* Added obs_legacybuildermetada functions to grab the needed metadata in the builder while making a data enrichment analysis. Closes #286
* Added metadata functions that will be used in the future to gather the metadata for the new data enrichment UI. Closes #287
* Fixed integration test for street geocoding
* Makefile now has a new task to create a new release in the client part, as was in the server, using make release NEW_VERSION=x.x.x
November 7st, 2016
==================
* Version 0.17.0 of the server and version 0.10.0 of the python package

3
client/.gitignore vendored
View File

@@ -2,9 +2,10 @@ results/
regression.diffs
regression.out
20_public_functions.sql
25_exception_safe_private_functions.sql
30_plproxy_functions.sql
90_grant_execute.sql
cdb_geocoder_client--0.0.1.sql
cdb_geocoder_client--0.1.0.sql
cdb_geocoder_client--0.2.0.sql
cdb_geocoder_client--0.3.0.sql
cdb_geocoder_client--0.3.0.sql

View File

@@ -57,9 +57,11 @@ all: $(DATA)
.PHONY: release
release: $(EXTENSION).control $(SOURCES_DATA)
test -n "$(NEW_VERSION)" # $$NEW_VERSION VARIABLE MISSING. Eg. make release NEW_VERSION=0.x.0
mv *.sql old_versions
git mv *.sql old_versions
$(SED) $(REPLACEMENTS) $(EXTENSION).control
git add $(EXTENSION).control
cat $(SOURCES_DATA_DIR)/*.sql > $(EXTENSION)--$(NEW_VERSION).sql
git add $(EXTENSION)--$(NEW_VERSION).sql
$(ERB) version=$(NEW_VERSION) upgrade_downgrade_template.erb > $(EXTENSION)--$(EXTVERSION)--$(NEW_VERSION).sql
$(ERB) version=$(EXTVERSION) upgrade_downgrade_template.erb > $(EXTENSION)--$(NEW_VERSION)--$(EXTVERSION).sql

View File

@@ -0,0 +1,9 @@
--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_client UPDATE TO '0.14.1'" to load this file. \quit
-- Make sure we have a sane search path to create/update the extension
SET search_path = "$user",cartodb,public,cdb_dataservices_client;
-- This release introduces no changes other than the use of
-- search path in the install and migration scripts

View File

@@ -0,0 +1,9 @@
--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_client UPDATE TO '0.14.0'" to load this file. \quit
-- Make sure we have a sane search path to create/update the extension
SET search_path = "$user",cartodb,public,cdb_dataservices_client;
-- This release introduces no changes other than the use of
-- search path in the install and migration scripts

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
comment = 'CartoDB dataservices client API extension'
default_version = '0.13.0'
default_version = '0.14.1'
requires = 'plproxy, cartodb'
superuser = true
schema = cdb_dataservices_client

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
--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_client UPDATE TO '0.13.0'" to load this file. \quit
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_admin0_polygon_exception_safe(country_name text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_admin1_polygon_exception_safe(admin1_name text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_admin1_polygon_exception_safe(admin1_name text, country_name text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_namedplace_point_exception_safe(city_name text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_namedplace_point_exception_safe(city_name text, country_name text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_namedplace_point_exception_safe(city_name text, admin1_name text, country_name text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_postalcode_polygon_exception_safe(postal_code text, country_name text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_postalcode_point_exception_safe(postal_code text, country_name text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_ipaddress_point_exception_safe(ip_address text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_geocode_street_point_exception_safe(searchtext text, city text, state_province text, country text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_here_geocode_street_point_exception_safe(searchtext text, city text, state_province text, country text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_google_geocode_street_point_exception_safe(searchtext text, city text, state_province text, country text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_mapzen_geocode_street_point_exception_safe(searchtext text, city text, state_province text, country text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_isodistance_exception_safe(source geometry(Geometry, 4326), mode text, range integer[], options text[]);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_isochrone_exception_safe(source geometry(Geometry, 4326), mode text, range integer[], options text[]);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_mapzen_isochrone_exception_safe(source geometry(Geometry, 4326), mode text, range integer[], options text[]);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_mapzen_isodistance_exception_safe(source geometry(Geometry, 4326), mode text, range integer[], options text[]);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_route_point_to_point_exception_safe(origin geometry(Point, 4326), destination geometry(Point, 4326), mode text, options text[], units text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_route_with_waypoints_exception_safe(waypoints geometry(Point, 4326)[], mode text, options text[], units text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_get_demographic_snapshot_exception_safe(geom geometry(Geometry, 4326), time_span text, geometry_level text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_get_segment_snapshot_exception_safe(geom geometry(Geometry, 4326), geometry_level text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getdemographicsnapshot_exception_safe(geom geometry(Geometry, 4326), time_span text, geometry_level text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getsegmentsnapshot_exception_safe(geom geometry(Geometry, 4326), geometry_level text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getboundary_exception_safe(geom geometry(Geometry, 4326), boundary_id text, time_span text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getboundaryid_exception_safe(geom geometry(Geometry, 4326), boundary_id text, time_span text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getboundarybyid_exception_safe(geometry_id text, boundary_id text, time_span text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getboundariesbygeometry_exception_safe(geom geometry(Geometry, 4326), boundary_id text, time_span text, overlap_type text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getboundariesbypointandradius_exception_safe(geom geometry(Geometry, 4326), radius numeric, boundary_id text, time_span text, overlap_type text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getpointsbygeometry_exception_safe(geom geometry(Geometry, 4326), boundary_id text, time_span text, overlap_type text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getpointsbypointandradius_exception_safe(geom geometry(Geometry, 4326), radius numeric, boundary_id text, time_span text, overlap_type text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getmeasure_exception_safe(geom Geometry, measure_id text, normalize text, boundary_id text, time_span text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getmeasurebyid_exception_safe(geom_ref text, measure_id text, boundary_id text, time_span text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getcategory_exception_safe(geom Geometry, category_id text, boundary_id text, time_span text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getuscensusmeasure_exception_safe(geom Geometry, name text, normalize text, boundary_id text, time_span text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getuscensuscategory_exception_safe(geom Geometry, name text, boundary_id text, time_span text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getpopulation_exception_safe(geom Geometry, normalize text, boundary_id text, time_span text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_search_exception_safe(search_term text, relevant_boundary text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getavailableboundaries_exception_safe(geom Geometry, timespan text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_dumpversion_exception_safe();
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getavailablenumerators_exception_safe(bounds geometry(Geometry, 4326), filter_tags text[], denom_id text, geom_id text, timespan text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getavailabledenominators_exception_safe(bounds geometry(Geometry, 4326), filter_tags text[], numer_id text, geom_id text, timespan text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getavailablegeometries_exception_safe(bounds geometry(Geometry, 4326), filter_tags text[], numer_id text, denom_id text, timespan text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_getavailabletimespans_exception_safe(bounds geometry(Geometry, 4326), filter_tags text[], numer_id text, denom_id text, geom_id text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._obs_legacybuildermetadata_exception_safe(aggregate_type text);
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_service_quota_info_exception_safe();
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_enough_quota_exception_safe(service TEXT, input_size NUMERIC);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
--
-- Exception-safe private DataServices API function
--
CREATE OR REPLACE FUNCTION <%= DATASERVICES_CLIENT_SCHEMA %>._<%= name %>_exception_safe (<%= params_with_type_and_default.join(' ,') %>)
RETURNS <%= return_type %> AS $$
DECLARE
<% if not multi_row %>ret <%= return_type %>;<% end %>
username text;
orgname text;
_returned_sqlstate TEXT;
_message_text TEXT;
_pg_exception_context TEXT;
BEGIN
IF session_user = 'publicuser' OR session_user ~ 'cartodb_publicuser_*' THEN
RAISE EXCEPTION 'The api_key must be provided';
END IF;
SELECT u, o INTO username, orgname FROM <%= DATASERVICES_CLIENT_SCHEMA %>._cdb_entity_config() AS (u text, o text);
-- JSON value stored "" is taken as literal
IF username IS NULL OR username = '' OR username = '""' THEN
RAISE EXCEPTION 'Username is a mandatory argument, check it out';
END IF;
<% if multi_row %>
BEGIN
RETURN QUERY
SELECT * FROM <%= DATASERVICES_CLIENT_SCHEMA %>._<%= name %>(<%= ['username', 'orgname'].concat(params).join(', ') %>);
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS _returned_sqlstate = RETURNED_SQLSTATE,
_message_text = MESSAGE_TEXT,
_pg_exception_context = PG_EXCEPTION_CONTEXT;
RAISE WARNING USING ERRCODE = _returned_sqlstate, MESSAGE = _message_text, DETAIL = _pg_exception_context;
END;
<% elsif multi_field %>
BEGIN
SELECT * FROM <%= DATASERVICES_CLIENT_SCHEMA %>._<%= name %>(<%= ['username', 'orgname'].concat(params).join(', ') %>) INTO ret;
RETURN ret;
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS _returned_sqlstate = RETURNED_SQLSTATE,
_message_text = MESSAGE_TEXT,
_pg_exception_context = PG_EXCEPTION_CONTEXT;
RAISE WARNING USING ERRCODE = _returned_sqlstate, MESSAGE = _message_text, DETAIL = _pg_exception_context;
RETURN ret;
END;
<% else %>
BEGIN
SELECT <%= DATASERVICES_CLIENT_SCHEMA %>._<%= name %>(<%= ['username', 'orgname'].concat(params).join(', ') %>) INTO ret;
RETURN ret;
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS _returned_sqlstate = RETURNED_SQLSTATE,
_message_text = MESSAGE_TEXT,
_pg_exception_context = PG_EXCEPTION_CONTEXT;
RAISE WARNING USING ERRCODE = _returned_sqlstate, MESSAGE = _message_text, DETAIL = _pg_exception_context;
RETURN ret;
END;
<% end %>
END;
$$ LANGUAGE 'plpgsql' SECURITY DEFINER;

View File

@@ -1 +1,2 @@
GRANT EXECUTE ON FUNCTION <%= DATASERVICES_CLIENT_SCHEMA %>.<%= name %>(<%= params_with_type.join(', ') %>) TO publicuser;
GRANT EXECUTE ON FUNCTION <%= DATASERVICES_CLIENT_SCHEMA %>._<%= name %>_exception_safe(<%= params_with_type.join(', ') %>) TO publicuser;

View File

@@ -1,3 +1,6 @@
--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 "CREATE EXTENSION cdb_dataservices_client" to load this file. \quit
-- Make sure we have a sane search path to create/update the extension
SET search_path = "$user",cartodb,public,cdb_dataservices_client;

View File

@@ -0,0 +1,56 @@
SET client_min_messages TO warning;
SET search_path TO public,cartodb,cdb_dataservices_client;
-- Mock the server functions to raise exceptions
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_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 $$
BEGIN
RAISE EXCEPTION 'Not enough quota or any other exception whatsoever.';
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_isodistance(username text, orgname text, source geometry, mode text, range integer[], options text[] DEFAULT ARRAY[]::text[])
RETURNS SETOF isoline AS $$
BEGIN
RAISE EXCEPTION 'Not enough quota or any other exception whatsoever.';
END;
$$ LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_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_client.simple_route AS $$
DECLARE
ret cdb_dataservices_client.simple_route;
BEGIN
RAISE EXCEPTION 'Not enough quota or any other exception whatsoever.';
-- This code shall never be reached
SELECT NULL, 5.33, 100 INTO ret;
RETURN ret;
END;
$$ LANGUAGE 'plpgsql';
-- Use regular user role
SET ROLE test_regular_user;
-- Exercise the exception safe and the proxied functions
SELECT _cdb_geocode_street_point_exception_safe('One street, 1');
WARNING: cdb_dataservices_client._cdb_geocode_street_point(6): [contrib_regression] REMOTE ERROR: Not enough quota or any other exception whatsoever.
DETAIL: SQL statement "SELECT cdb_dataservices_client._cdb_geocode_street_point(username, orgname, searchtext, city, state_province, country)"
PL/pgSQL function _cdb_geocode_street_point_exception_safe(text,text,text,text) line 21 at SQL statement
_cdb_geocode_street_point_exception_safe
------------------------------------------
(1 row)
SELECT * FROM _cdb_isodistance_exception_safe('POINT(-3.70568 40.42028)'::geometry, 'walk', ARRAY[300]::integer[]);
WARNING: cdb_dataservices_client._cdb_isodistance(6): [contrib_regression] REMOTE ERROR: Not enough quota or any other exception whatsoever.
DETAIL: PL/pgSQL function _cdb_isodistance_exception_safe(geometry,text,integer[],text[]) line 21 at RETURN QUERY
center | data_range | the_geom
--------+------------+----------
(0 rows)
SELECT * FROM _cdb_route_point_to_point_exception_safe('POINT(-3.70237112 40.41706163)'::geometry,'POINT(-3.69909883 40.41236875)'::geometry, 'car', ARRAY['mode_type=shortest']::text[]);
WARNING: cdb_dataservices_client._cdb_route_point_to_point(7): [contrib_regression] REMOTE ERROR: Not enough quota or any other exception whatsoever.
DETAIL: SQL statement "SELECT * FROM cdb_dataservices_client._cdb_route_point_to_point(username, orgname, origin, destination, mode, options, units)"
PL/pgSQL function _cdb_route_point_to_point_exception_safe(geometry,geometry,text,text[],text) line 21 at SQL statement
shape | length | duration
-------+--------+----------
| |
(1 row)

View File

@@ -0,0 +1,41 @@
SET client_min_messages TO warning;
SET search_path TO public,cartodb,cdb_dataservices_client;
-- Mock the server functions to raise exceptions
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_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 $$
BEGIN
RAISE EXCEPTION 'Not enough quota or any other exception whatsoever.';
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_isodistance(username text, orgname text, source geometry, mode text, range integer[], options text[] DEFAULT ARRAY[]::text[])
RETURNS SETOF isoline AS $$
BEGIN
RAISE EXCEPTION 'Not enough quota or any other exception whatsoever.';
END;
$$ LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_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_client.simple_route AS $$
DECLARE
ret cdb_dataservices_client.simple_route;
BEGIN
RAISE EXCEPTION 'Not enough quota or any other exception whatsoever.';
-- This code shall never be reached
SELECT NULL, 5.33, 100 INTO ret;
RETURN ret;
END;
$$ LANGUAGE 'plpgsql';
-- Use regular user role
SET ROLE test_regular_user;
-- Exercise the exception safe and the proxied functions
SELECT _cdb_geocode_street_point_exception_safe('One street, 1');
SELECT * FROM _cdb_isodistance_exception_safe('POINT(-3.70568 40.42028)'::geometry, 'walk', ARRAY[300]::integer[]);
SELECT * FROM _cdb_route_point_to_point_exception_safe('POINT(-3.70237112 40.41706163)'::geometry,'POINT(-3.69909883 40.41236875)'::geometry, 'car', ARRAY['mode_type=shortest']::text[]);

View File

@@ -2,4 +2,7 @@
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION cdb_dataservices_client UPDATE TO '<%= version %>'" to load this file. \quit
-- Make sure we have a sane search path to create/update the extension
SET search_path = "$user",cartodb,public,cdb_dataservices_client;
-- HERE goes your code to upgrade/downgrade

View File

@@ -0,0 +1,36 @@
# Exception-Safe functions
The public API dataservices functions emit exceptions in general when an error occurs
or a limiting condition is met (e.g. quotas are exceeded).
For each public function `func` we have a internal function named `_func_exception_safe` which
acts as a wrapper to the public function, with the same signature, but captures
exceptions generated during its execution (except those due to incomplete configuration or
authentication issues) and returns NULL or empty set values in those cases.
Please note these functions are considered **not public** and therefore their API (including which exceptions are wrapped and which ones are not) may change.
Instead of raising an exception they raise warnings, hopefully containing the same information of the original exception.
## Intended Use
These functions are useful in cases when it is undesirable to rollback a transaction.
Fo example if a table is geocoded with:
```sql
UPDATE table SET the_geom=cdb_geocode_street_point(user,NULL,address,city,NULL,country);
```
In case of the user geocoding quota being exhausted mid-process, the user could
incur in external service expenses but any geocoded data would be lost due to the
transaction rollback.
We can avoid the problem using the corresponding exception-safe function:
```sql
UPDATE table SET the_geom=_cdb_geocode_street_point_exception_safe(user,NULL,address,city,NULL,country);
```
# Addition Information
See https://github.com/CartoDB/dataservices-api/issues/314 for more information.

View File

@@ -46,5 +46,7 @@ cd $(git rev-parse --show-toplevel)/test
python run_tests.py --host=$YOUR_HOST $YOUR_USERNAME $YOUR_API_KEY
```
## TODO
- Move dependencies expressed in `requirements.txt` to `setup.py`
## Versioning
Once you're satisfied with your changes, it is time to bump the version number in the `setup.py`. A couple of rules:
- **Backwards compatibility**: in general all changes shall be backwards compatible. Do not remove any code used from the server public `pl/python` functions or you'll run into problems when deploying.
- **Semantic versioning**: we try to stick to [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html)

View File

@@ -4,6 +4,7 @@
import json
import requests
from requests.adapters import HTTPAdapter
from exceptions import *
from cartodb_services.metrics import Traceable
@@ -17,6 +18,7 @@ class HereMapsGeocoder(Traceable):
DEFAULT_GEN = 9
READ_TIMEOUT = 60
CONNECT_TIMEOUT = 10
MAX_RETRIES=1
ADDRESS_PARAMS = [
'city',
@@ -88,7 +90,10 @@ class HereMapsGeocoder(Traceable):
'gen': self.gen
}
request_params.update(params)
response = requests.get(self.host, params=request_params,
# TODO Extract HTTP client wrapper
session = requests.Session()
session.mount(self.host, HTTPAdapter(max_retries=self.MAX_RETRIES))
response = session.get(self.host, params=request_params,
timeout=(self.CONNECT_TIMEOUT, self.READ_TIMEOUT))
self.add_response_data(response, self._logger)
if response.status_code == requests.codes.ok:

View File

@@ -2,6 +2,7 @@ import requests
import json
from exceptions import WrongParams
from requests.adapters import HTTPAdapter
from cartodb_services.metrics import Traceable
@@ -13,6 +14,7 @@ class HereMapsRoutingIsoline(Traceable):
ISOLINE_PATH = '/routing/7.2/calculateisoline.json'
READ_TIMEOUT = 60
CONNECT_TIMEOUT = 10
MAX_RETRIES = 1
ACCEPTED_MODES = {
"walk": "pedestrian",
@@ -53,6 +55,9 @@ class HereMapsRoutingIsoline(Traceable):
data_range,
range_type,
parsed_options)
# TODO Extract HTTP client wrapper
session = requests.Session()
session.mount(self._url, HTTPAdapter(max_retries=self.MAX_RETRIES))
response = requests.get(self._url, params=request_params,
timeout=(self.CONNECT_TIMEOUT, self.READ_TIMEOUT))
self.add_response_data(response, self._logger)

View File

@@ -2,6 +2,7 @@ import requests
import json
import re
from requests.adapters import HTTPAdapter
from exceptions import WrongParams, MalformedResult, ServiceException
from qps import qps_retry
from cartodb_services.tools import Coordinate, PolyLine
@@ -14,6 +15,7 @@ class MapzenGeocoder(Traceable):
BASE_URL = 'https://search.mapzen.com/v1/search'
READ_TIMEOUT = 60
CONNECT_TIMEOUT = 10
MAX_RETRIES = 1
def __init__(self, app_key, logger, base_url=BASE_URL):
self._app_key = app_key
@@ -27,7 +29,10 @@ class MapzenGeocoder(Traceable):
state_province,
country, search_type)
try:
response = requests.get(self._url, params=request_params,
# TODO Extract HTTP client wrapper
session = requests.Session()
session.mount(self._url, HTTPAdapter(max_retries=self.MAX_RETRIES))
response = session.get(self._url, params=request_params,
timeout=(self.CONNECT_TIMEOUT, self.READ_TIMEOUT))
self.add_response_data(response, self._logger)
if response.status_code == requests.codes.ok:

View File

@@ -2,6 +2,7 @@ import requests
import json
import re
from requests.adapters import HTTPAdapter
from exceptions import WrongParams, MalformedResult, ServiceException
from qps import qps_retry
@@ -12,6 +13,7 @@ class MapzenIsochrones:
BASE_URL = 'https://matrix.mapzen.com/isochrone'
READ_TIMEOUT = 60
CONNECT_TIMEOUT = 10
MAX_RETRIES = 1
ACCEPTED_MODES = {
"walk": "pedestrian",
@@ -23,12 +25,15 @@ class MapzenIsochrones:
self._url = base_url
self._logger = logger
@qps_retry
@qps_retry(qps=7)
def isochrone(self, locations, costing, ranges):
request_params = self._parse_request_params(locations, costing,
ranges)
try:
response = requests.get(self._url, params=request_params,
# TODO Extract HTTP client wrapper
session = requests.Session()
session.mount(self._url, HTTPAdapter(max_retries=self.MAX_RETRIES))
response = session.get(self._url, params=request_params,
timeout=(self.CONNECT_TIMEOUT,
self.READ_TIMEOUT))

View File

@@ -55,7 +55,7 @@ class QPSService:
def retry(self, first_request_time, retry_count):
elapsed = datetime.now() - first_request_time
if elapsed.microseconds > (self._retry_timeout * 1000.0):
if elapsed.total_seconds() > self._retry_timeout:
raise TimeoutException()
# inverse qps * (1.5 ^ i) is an increased sleep time of 1.5x per

View File

@@ -2,6 +2,7 @@ import requests
import json
import re
from requests.adapters import HTTPAdapter
from exceptions import WrongParams, MalformedResult, ServiceException
from qps import qps_retry
from cartodb_services.tools import Coordinate, PolyLine
@@ -14,6 +15,7 @@ class MapzenRouting(Traceable):
PRODUCTION_ROUTING_BASE_URL = 'https://valhalla.mapzen.com/route'
READ_TIMEOUT = 60
CONNECT_TIMEOUT = 10
MAX_RETRIES=1
ACCEPTED_MODES = {
"walk": "pedestrian",
@@ -46,7 +48,10 @@ class MapzenRouting(Traceable):
mode_param,
units)
request_params = self.__parse_request_parameters(json_request_params)
response = requests.get(self._url, params=request_params,
# TODO Extract HTTP client wrapper
session = requests.Session()
session.mount(self._url, HTTPAdapter(max_retries=self.MAX_RETRIES))
response = session.get(self._url, params=request_params,
timeout=(self.CONNECT_TIMEOUT, self.READ_TIMEOUT))
self.add_response_data(response, self._logger)
if response.status_code == requests.codes.ok:

View File

@@ -43,12 +43,20 @@ class ServiceConfig(object):
def metrics_log_path(self):
return self._metrics_log_path
def _get_effective_monthly_quota(self, quota_key, default=0):
quota_from_redis = self._redis_config.get(quota_key, None)
if quota_from_redis and quota_from_redis <> '':
return int(quota_from_redis)
else:
return default
def __get_metrics_log_path(self):
if self.METRICS_LOG_KEY:
return self._db_config.logger_config.get(self.METRICS_LOG_KEY, None)
else:
return None
class DataObservatoryConfig(ServiceConfig):
METRICS_LOG_KEY = 'do_log_path'
@@ -92,9 +100,7 @@ class ObservatorySnapshotConfig(DataObservatoryConfig):
self._soft_limit = True
else:
self._soft_limit = False
self._monthly_quota = 0
if self.QUOTA_KEY in self._redis_config:
self._monthly_quota = int(self._redis_config[self.QUOTA_KEY])
self._monthly_quota = self._get_effective_monthly_quota(self.QUOTA_KEY)
self._connection_str = self._db_config.data_observatory_connection_str
@property
@@ -116,9 +122,7 @@ class ObservatoryConfig(DataObservatoryConfig):
self._soft_limit = True
else:
self._soft_limit = False
self._monthly_quota = 0
if self.QUOTA_KEY in self._redis_config:
self._monthly_quota = int(self._redis_config[self.QUOTA_KEY])
self._monthly_quota = self._get_effective_monthly_quota(self.QUOTA_KEY)
self._connection_str = self._db_config.data_observatory_connection_str
@property
@@ -173,14 +177,7 @@ class RoutingConfig(ServiceConfig):
return self._soft_limit
def _set_monthly_quota(self):
self._monthly_quota = self._get_effective_monthly_quota()
def _get_effective_monthly_quota(self):
quota_from_redis = self._redis_config.get(self.QUOTA_KEY)
if quota_from_redis and quota_from_redis <> '':
return int(quota_from_redis)
else:
return self._db_config.mapzen_routing_monthly_quota
self._monthly_quota = self._get_effective_monthly_quota(self.QUOTA_KEY)
def _set_soft_limit(self):
if self.SOFT_LIMIT_KEY in self._redis_config and self._redis_config[self.SOFT_LIMIT_KEY].lower() == 'true':
@@ -217,18 +214,16 @@ class IsolinesRoutingConfig(ServiceConfig):
self._isolines_provider = self.DEFAULT_PROVIDER
self._geocoder_provider = filtered_config[self.GEOCODER_PROVIDER_KEY].lower()
self._period_end_date = date_parse(filtered_config[self.PERIOD_END_DATE])
self._isolines_quota = self._get_effective_monthly_quota(self.QUOTA_KEY)
if filtered_config[self.SOFT_LIMIT_KEY].lower() == 'true':
self._soft_isolines_limit = True
else:
self._soft_isolines_limit = False
if self._isolines_provider == self.HEREMAPS_PROVIDER:
self._isolines_quota = int(filtered_config[self.QUOTA_KEY])
self._heremaps_app_id = db_config.heremaps_isolines_app_id
self._heremaps_app_code = db_config.heremaps_isolines_app_code
if filtered_config[self.SOFT_LIMIT_KEY].lower() == 'true':
self._soft_isolines_limit = True
else:
self._soft_isolines_limit = False
elif self._isolines_provider == self.MAPZEN_PROVIDER:
self._mapzen_matrix_api_key = self._db_config.mapzen_matrix_api_key
self._isolines_quota = self._db_config.mapzen_matrix_monthly_quota
self._soft_isolines_limit = False
@property
def service_type(self):
@@ -361,8 +356,10 @@ class GeocoderConfig(ServiceConfig):
self._geocoder_provider = filtered_config[self.GEOCODER_PROVIDER].lower()
else:
self._geocoder_provider = self.DEFAULT_PROVIDER
self._geocoding_quota = int(filtered_config[self.QUOTA_KEY])
self._geocoding_quota = self._get_effective_monthly_quota(self.QUOTA_KEY)
self._period_end_date = date_parse(filtered_config[self.PERIOD_END_DATE])
if filtered_config[self.SOFT_LIMIT_KEY].lower() == 'true':
self._soft_geocoding_limit = True
else:
@@ -377,7 +374,6 @@ class GeocoderConfig(ServiceConfig):
self._cost_per_hit = 0
elif self._geocoder_provider == self.MAPZEN_GEOCODER:
self._mapzen_api_key = db_config.mapzen_geocoder_api_key
self._geocoding_quota = db_config.mapzen_geocoder_monthly_quota
self._cost_per_hit = 0
@property

View File

@@ -1,5 +1,17 @@
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta
from calendar import monthrange
def last_day_of_month(year, month):
"""last valid day of a month"""
return monthrange(year, month)[1]
def latest_valid_date(year, month, day):
"""latest date not later than the day specified"""
valid_day = min(day, last_day_of_month(year, month))
return date(year, month, valid_day)
class UserMetricsService:
@@ -8,6 +20,7 @@ class UserMetricsService:
SERVICE_GEOCODER_NOKIA = 'geocoder_here'
SERVICE_GEOCODER_CACHE = 'geocoder_cache'
SERVICE_HERE_ISOLINES = 'here_isolines'
SERVICE_MAPZEN_ISOLINES = 'mapzen_isolines'
SERVICE_MAPZEN_ROUTING = 'routing_mapzen'
SERVICE_OBSERVATORY = 'obs_general'
DAY_OF_MONTH_ZERO_PADDED = '%d'
@@ -21,6 +34,8 @@ class UserMetricsService:
def used_quota(self, service_type, date):
if service_type == self.SERVICE_HERE_ISOLINES:
return self.__used_isolines_quota(service_type, date)
if service_type == self.SERVICE_MAPZEN_ISOLINES:
return self.__used_isolines_quota(service_type, date)
elif service_type == self.SERVICE_MAPZEN_ROUTING:
return self.__used_routing_quota(service_type, date)
elif service_type == self.SERVICE_OBSERVATORY:
@@ -143,9 +158,9 @@ class UserMetricsService:
today = date.today()
if end_period_day > today.day:
temp_date = today + relativedelta(months=-1)
date_from = date(temp_date.year, temp_date.month, end_period_day)
date_from = latest_valid_date(temp_date.year, temp_date.month, end_period_day)
else:
date_from = date(today.year, today.month, end_period_day)
date_from = latest_valid_date(today.year, today.month, end_period_day)
return date_from, today

View File

@@ -67,6 +67,7 @@ class MapzenGeocoderConfig(object):
@property
def username(self):
return self._username
@property
def organization(self):
return self._organization
@@ -86,16 +87,13 @@ class MapzenGeocoderConfigBuilder(object):
self._username = username
self._orgname = orgname
def get(self):
mapzen_server_conf = self._server_conf.get('mapzen_conf')
geocoding_quota = mapzen_server_conf['geocoder']['monthly_quota']
mapzen_api_key = mapzen_server_conf['geocoder']['api_key']
geocoding_quota = self._get_quota(mapzen_server_conf)
soft_geocoding_limit = self._user_conf.get('soft_geocoding_limit').lower() == 'true'
cost_per_hit=0
cost_per_hit = 0
period_end_date_str = self._org_conf.get('period_end_date') or self._user_conf.get('period_end_date')
period_end_date = date_parse(period_end_date_str)
@@ -110,3 +108,10 @@ class MapzenGeocoderConfigBuilder(object):
mapzen_api_key,
self._username,
self._orgname)
def _get_quota(self, mapzen_server_conf):
geocoding_quota = self._org_conf.get('geocoding_quota') or self._user_conf.get('geocoding_quota')
if geocoding_quota is '':
return 0
return int(geocoding_quota)

View File

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

View File

@@ -1,7 +1,282 @@
from unittest import TestCase
from mockredis import MockRedis
from datetime import datetime, timedelta
from ..test_helper import *
from cartodb_services.metrics.config import RoutingConfig, ServicesRedisConfig
from cartodb_services.metrics.config import *
class TestGeocoderUserConfig(TestCase):
GEOCODER_PROVIDERS = ['heremaps', 'mapzen', 'google']
def setUp(self):
self.redis_conn = MockRedis()
plpy_mock_config()
def test_should_return_geocoder_config_for_user(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
provider=geocoder_provider, quota=100)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', None)
if geocoder_provider == 'heremaps':
assert geocoder_config.heremaps_geocoder is True
assert geocoder_config.geocoding_quota == 100
elif geocoder_provider == 'mapzen':
assert geocoder_config.mapzen_geocoder is True
assert geocoder_config.geocoding_quota == 100
elif geocoder_provider == 'google':
assert geocoder_config.google_geocoder is True
assert geocoder_config.geocoding_quota is None
assert geocoder_config.soft_geocoding_limit is False
def test_should_return_quota_0_when_is_0_in_redis(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
quota=0, provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', None)
if geocoder_provider is not 'google':
assert geocoder_config.geocoding_quota == 0
def test_should_return_quota_0_if_quota_is_empty(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
quota='', provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', None)
if geocoder_provider is not 'google':
assert geocoder_config.geocoding_quota == 0
def test_should_return_quota_None_when_is_provider_is_google(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
quota=0, provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', None)
if geocoder_provider is 'google':
assert geocoder_config.geocoding_quota == None
def test_should_return_true_if_soft_limit_is_true(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
quota=0, soft_limit=True,
provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', None)
assert geocoder_config.soft_geocoding_limit == True
def test_should_return_false_if_soft_limit_is_empty_string(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
quota=0, soft_limit='',
provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', None)
assert geocoder_config.soft_geocoding_limit == False
class TestGeocoderOrgConfig(TestCase):
GEOCODER_PROVIDERS = ['heremaps', 'mapzen', 'google']
def setUp(self):
self.redis_conn = MockRedis()
plpy_mock_config()
def test_should_return_org_config(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
provider=geocoder_provider)
build_redis_org_config(self.redis_conn, 'test_org', 'geocoding',
quota=200, end_date=yesterday,
provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
if geocoder_provider == 'heremaps':
assert geocoder_config.heremaps_geocoder is True
assert geocoder_config.geocoding_quota == 200
elif geocoder_provider == 'mapzen':
assert geocoder_config.mapzen_geocoder is True
assert geocoder_config.geocoding_quota == 200
elif geocoder_provider == 'google':
assert geocoder_config.google_geocoder is True
assert geocoder_config.geocoding_quota is None
assert geocoder_config.soft_geocoding_limit is False
assert geocoder_config.period_end_date.date() == yesterday.date()
def test_should_return_0_quota_if_has_0_in_redis_config(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
provider=geocoder_provider)
build_redis_org_config(self.redis_conn, 'test_org', 'geocoding',
quota=0, end_date=yesterday,
provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
if geocoder_provider is not 'google':
assert geocoder_config.geocoding_quota == 0
def test_should_return_0_if_quota_is_empty_for_org_in_redis(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
provider=geocoder_provider)
build_redis_org_config(self.redis_conn, 'test_org', 'geocoding',
quota='', end_date=yesterday,
provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
if geocoder_provider is not 'google':
assert geocoder_config.geocoding_quota == 0
def test_should_return_None_if_provider_is_google(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
provider=geocoder_provider)
build_redis_org_config(self.redis_conn, 'test_org', 'geocoding',
quota='', end_date=yesterday,
provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
if geocoder_provider is 'google':
assert geocoder_config.geocoding_quota == None
def test_should_return_user_quota_if_is_not_defined_for_org(self):
for geocoder_provider in self.GEOCODER_PROVIDERS:
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'geocoding',
quota=100, provider=geocoder_provider)
build_redis_org_config(self.redis_conn, 'test_org', 'geocoding',
quota=None, end_date=yesterday,
provider=geocoder_provider)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
if geocoder_provider is not 'google':
assert geocoder_config.geocoding_quota == 100
class TestIsolinesUserConfig(TestCase):
ISOLINES_PROVIDERS = ['heremaps', 'mapzen']
def setUp(self):
self.redis_conn = MockRedis()
plpy_mock_config()
def test_should_return_user_config_for_isolines(self):
for isolines_provider in self.ISOLINES_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'isolines',
quota=100, provider=isolines_provider)
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
'test_user')
if isolines_provider is 'mapzen':
assert isolines_config.service_type is 'mapzen_isolines'
else:
assert isolines_config.service_type is 'here_isolines'
assert isolines_config.isolines_quota == 100
assert isolines_config.soft_isolines_limit is False
def test_should_return_0_quota_for_0_value(self):
for isolines_provider in self.ISOLINES_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'isolines',
provider=isolines_provider, quota=0,
soft_limit=True)
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
'test_user')
assert isolines_config.isolines_quota == 0
def test_should_return_0_quota_for_empty_quota_value(self):
for isolines_provider in self.ISOLINES_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'isolines',
provider=isolines_provider, quota='')
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
'test_user')
assert isolines_config.isolines_quota == 0
def test_should_return_true_soft_limit(self):
for isolines_provider in self.ISOLINES_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'isolines',
provider=isolines_provider, quota=0,
soft_limit=True)
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
'test_user')
assert isolines_config.soft_isolines_limit is True
def test_should_return_false_soft_limit_with_empty_string(self):
for isolines_provider in self.ISOLINES_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'isolines',
provider=isolines_provider, quota=0,
soft_limit='')
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
'test_user')
assert isolines_config.soft_isolines_limit is False
class TestIsolinesOrgConfig(TestCase):
ISOLINES_PROVIDERS = ['heremaps', 'mapzen']
def setUp(self):
self.redis_conn = MockRedis()
plpy_mock_config()
def test_should_return_org_config_for_isolines(self):
yesterday = datetime.today() - timedelta(days=1)
for isolines_provider in self.ISOLINES_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'isolines',
provider=isolines_provider)
build_redis_org_config(self.redis_conn, 'test_org', 'isolines',
quota=200, end_date=yesterday,
provider=isolines_provider)
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
assert isolines_config.isolines_quota == 200
assert isolines_config.soft_isolines_limit is False
assert isolines_config.period_end_date.date() == yesterday.date()
def test_should_return_quota_0_for_0_redis_quota(self):
yesterday = datetime.today() - timedelta(days=1)
for isolines_provider in self.ISOLINES_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'isolines',
provider=isolines_provider,
soft_limit=True)
build_redis_org_config(self.redis_conn, 'test_org', 'isolines',
quota=0, end_date=yesterday,
provider=isolines_provider)
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
assert isolines_config.isolines_quota == 0
def test_should_return_quota_0_for_empty_string_quota_in_org_config(self):
yesterday = datetime.today() - timedelta(days=1)
for isolines_provider in self.ISOLINES_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'isolines',
provider=isolines_provider)
build_redis_org_config(self.redis_conn, 'test_org', 'isolines',
quota='', end_date=yesterday,
provider=isolines_provider)
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
assert isolines_config.isolines_quota == 0
def test_should_return_user_quota_for_non_existent_org_quota(self):
yesterday = datetime.today() - timedelta(days=1)
for isolines_provider in self.ISOLINES_PROVIDERS:
build_redis_user_config(self.redis_conn, 'test_user', 'isolines',
provider=isolines_provider, quota=100)
build_redis_org_config(self.redis_conn, 'test_org', 'isolines',
quota=None, end_date=yesterday,
provider=isolines_provider)
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
assert isolines_config.isolines_quota == 100
class TestRoutingConfig(TestCase):
@@ -13,11 +288,6 @@ class TestRoutingConfig(TestCase):
self._user_key = "rails:users:{0}".format(self._username)
self._redis_conn.hset(self._user_key, 'period_end_date', '2016-10-10')
def test_should_pick_quota_from_server_by_default(self):
orgname = None
config = RoutingConfig(self._redis_conn, self._db_conn, self._username, orgname)
assert config.monthly_quota == 1500000
def test_should_pick_quota_from_redis_if_present(self):
self._redis_conn.hset(self._user_key, 'mapzen_routing_quota', 1000)
orgname = None
@@ -38,7 +308,6 @@ class TestRoutingConfig(TestCase):
config = RoutingConfig(self._redis_conn, self._db_conn, self._username, orgname)
assert config.monthly_quota == 5000
def test_should_have_soft_limit_false_by_default(self):
orgname = None
config = RoutingConfig(self._redis_conn, self._db_conn, self._username, orgname)
@@ -51,6 +320,136 @@ class TestRoutingConfig(TestCase):
assert config.soft_limit == True
class TestDataObservatoryUserConfig(TestCase):
def setUp(self):
self.redis_conn = MockRedis()
plpy_mock_config()
def test_should_return_config_for_obs_snapshot(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=100, end_date=yesterday)
do_config = ObservatorySnapshotConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.monthly_quota == 100
assert do_config.soft_limit is False
assert do_config.period_end_date.date() == yesterday.date()
def test_should_return_true_if_soft_limit_is_true_in_redis(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=0, soft_limit=True, end_date=yesterday)
do_config = ObservatorySnapshotConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.soft_limit is True
def test_should_return_0_if_quota_is_0_in_redis(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=0, end_date=yesterday)
do_config = ObservatorySnapshotConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.monthly_quota == 0
def test_should_return_0_if_quota_is_empty_in_redis(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota='', end_date=yesterday)
do_config = ObservatorySnapshotConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.monthly_quota == 0
def test_should_return_config_for_obs_snapshot(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=100, end_date=yesterday)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.monthly_quota == 100
assert do_config.soft_limit is False
assert do_config.period_end_date.date() == yesterday.date()
def test_should_return_0_if_quota_is_0_in_redis(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=0, end_date=yesterday)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.monthly_quota == 0
def test_should_return_0_if_quota_is_empty_in_redis(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota='', end_date=yesterday)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.monthly_quota == 0
def test_should_return_true_if_soft_limit_is_true_in_redis(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=0, soft_limit=True, end_date=yesterday)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.soft_limit is True
def test_should_return_true_if_soft_limit_is_empty_string_in_redis(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=0, soft_limit='', end_date=yesterday)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.soft_limit is False
class TestDataObservatoryOrgConfig(TestCase):
def setUp(self):
self.redis_conn = MockRedis()
plpy_mock_config()
def test_should_return_organization_config(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=100, end_date=yesterday)
build_redis_org_config(self.redis_conn, 'test_org', 'data_observatory',
quota=200, end_date=yesterday)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
assert do_config.monthly_quota == 200
assert do_config.period_end_date.date() == yesterday.date()
def test_should_return_quota_0_for_0_in_org_quota_config(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=100)
build_redis_org_config(self.redis_conn, 'test_org', 'data_observatory',
quota=0, end_date=yesterday)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
assert do_config.monthly_quota == 0
def test_should_return_quota_0_for_empty_in_org_quota_config(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=100)
build_redis_org_config(self.redis_conn, 'test_org', 'data_observatory',
quota='', end_date=yesterday)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
assert do_config.monthly_quota == 0
def test_should_return_user_config_when_org_quota_is_not_defined(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user', 'data_observatory',
quota=100)
build_redis_org_config(self.redis_conn, 'test_org', 'data_observatory',
quota=None, end_date=yesterday)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
assert do_config.monthly_quota == 100
class TestServicesRedisConfig(TestCase):
def test_it_picks_mapzen_routing_quota_from_redis(self):
redis_conn = MockRedis()

View File

@@ -0,0 +1,154 @@
from unittest import TestCase
from mockredis import MockRedis
from datetime import datetime
from cartodb_services.refactor.service.mapzen_geocoder_config import *
from cartodb_services.refactor.storage.redis_config import *
from cartodb_services.refactor.storage.mem_config import InMemoryConfigStorage
class TestMapzenGeocoderUserConfig(TestCase):
def setUp(self):
self._redis_connection = MockRedis()
self._server_config = InMemoryConfigStorage({"server_conf": {"environment": "testing"},
"mapzen_conf":
{"geocoder":
{"api_key": "search-xxxxxxx", "monthly_quota": 1500000}
}, "logger_conf": {}})
self._username = 'test_user'
self._user_key = "rails:users:{0}".format(self._username)
self._user_config = RedisUserConfigStorageBuilder(self._redis_connection,
self._username).get()
self._org_config = RedisOrgConfigStorageBuilder(self._redis_connection,
None).get()
self._set_default_config_values()
def test_config_values_are_ok(self):
config = MapzenGeocoderConfigBuilder(self._server_config,
self._user_config,
self._org_config,
self._username,
None).get()
assert config.geocoding_quota == 100
assert config.soft_geocoding_limit == False
assert config.period_end_date == datetime.strptime('2016-12-31 00:00:00', "%Y-%m-%d %H:%M:%S")
assert config.service_type == 'geocoder_mapzen'
assert config.provider == 'mapzen'
assert config.is_high_resolution == True
assert config.cost_per_hit == 0
assert config.mapzen_api_key == 'search-xxxxxxx'
assert config.username == 'test_user'
assert config.organization is None
def test_quota_should_be_0_if_redis_value_is_0(self):
self._redis_connection.hset(self._user_key, 'geocoding_quota', '0')
config = MapzenGeocoderConfigBuilder(self._server_config,
self._user_config,
self._org_config,
self._username,
None).get()
assert config.geocoding_quota == 0
def test_quota_should_be_0_if_redis_value_is_empty_string(self):
self._redis_connection.hset(self._user_key, 'geocoding_quota', '')
config = MapzenGeocoderConfigBuilder(self._server_config,
self._user_config,
self._org_config,
self._username,
None).get()
assert config.geocoding_quota == 0
def test_soft_limit_should_be_true(self):
self._redis_connection.hset(self._user_key, 'soft_geocoding_limit', 'true')
config = MapzenGeocoderConfigBuilder(self._server_config,
self._user_config,
self._org_config,
self._username,
None).get()
assert config.soft_geocoding_limit == True
def test_soft_limit_should_be_false_if_is_empty_string(self):
self._redis_connection.hset(self._user_key, 'soft_geocoding_limit', '')
config = MapzenGeocoderConfigBuilder(self._server_config,
self._user_config,
self._org_config,
self._username,
None).get()
assert config.soft_geocoding_limit == False
def _set_default_config_values(self):
self._redis_connection.hset(self._user_key, 'geocoding_quota', '100')
self._redis_connection.hset(self._user_key, 'soft_geocoding_limit', 'false')
self._redis_connection.hset(self._user_key, 'period_end_date', '2016-12-31 00:00:00')
class TestMapzenGeocoderOrgConfig(TestCase):
def setUp(self):
self._redis_connection = MockRedis()
self._server_config = InMemoryConfigStorage({"server_conf": {"environment": "testing"},
"mapzen_conf":
{"geocoder":
{"api_key": "search-xxxxxxx", "monthly_quota": 1500000}
}, "logger_conf": {}})
self._username = 'test_user'
self._organization = 'test_org'
self._user_key = "rails:users:{0}".format(self._username)
self._org_key = "rails:orgs:{0}".format(self._organization)
self._user_config = RedisUserConfigStorageBuilder(self._redis_connection,
self._username).get()
self._org_config = RedisOrgConfigStorageBuilder(self._redis_connection,
self._organization).get()
self._set_default_config_values()
def test_config_org_values_are_ok(self):
config = MapzenGeocoderConfigBuilder(self._server_config,
self._user_config,
self._org_config,
self._username,
self._organization).get()
assert config.geocoding_quota == 200
assert config.soft_geocoding_limit == False
assert config.period_end_date == datetime.strptime('2016-12-31 00:00:00', "%Y-%m-%d %H:%M:%S")
assert config.service_type == 'geocoder_mapzen'
assert config.provider == 'mapzen'
assert config.is_high_resolution == True
assert config.cost_per_hit == 0
assert config.mapzen_api_key == 'search-xxxxxxx'
assert config.username == 'test_user'
assert config.organization is 'test_org'
def test_quota_should_be_0_if_redis_value_is_0(self):
self._redis_connection.hset(self._org_key, 'geocoding_quota', '0')
config = MapzenGeocoderConfigBuilder(self._server_config,
self._user_config,
self._org_config,
self._username,
self._organization).get()
assert config.geocoding_quota == 0
def test_quota_should_use_user_quota_value_if_redis_value_is_empty_string(self):
self._redis_connection.hset(self._org_key, 'geocoding_quota', '')
config = MapzenGeocoderConfigBuilder(self._server_config,
self._user_config,
self._org_config,
self._username,
self._organization).get()
assert config.geocoding_quota == 100
def test_quota_should_be_0_if_both_user_and_org_have_empty_string(self):
self._redis_connection.hset(self._user_key, 'geocoding_quota', '')
self._redis_connection.hset(self._org_key, 'geocoding_quota', '')
config = MapzenGeocoderConfigBuilder(self._server_config,
self._user_config,
self._org_config,
self._username,
self._organization).get()
assert config.geocoding_quota == 0
def _set_default_config_values(self):
self._redis_connection.hset(self._user_key, 'geocoding_quota', '100')
self._redis_connection.hset(self._user_key, 'soft_geocoding_limit', 'false')
self._redis_connection.hset(self._user_key, 'period_end_date', '2016-12-15 00:00:00')
self._redis_connection.hset(self._org_key, 'geocoding_quota', '200')
self._redis_connection.hset(self._org_key, 'period_end_date', '2016-12-31 00:00:00')

View File

@@ -1,60 +0,0 @@
from test_helper import *
from unittest import TestCase
from nose.tools import assert_raises
from mockredis import MockRedis
from datetime import datetime, timedelta
from cartodb_services.metrics import GeocoderConfig, ObservatorySnapshotConfig, ConfigException
class TestConfig(TestCase):
def setUp(self):
self.redis_conn = MockRedis()
plpy_mock_config()
def test_should_return_list_of_nokia_geocoder_config_if_its_ok(self):
build_redis_user_config(self.redis_conn, 'test_user')
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', None)
assert geocoder_config.heremaps_geocoder is True
assert geocoder_config.geocoding_quota == 100
assert geocoder_config.soft_geocoding_limit is False
def test_should_return_list_of_nokia_geocoder_config_ok_for_org(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user')
build_redis_org_config(self.redis_conn, 'test_org',
quota=200, end_date=yesterday)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
'test_user', 'test_org')
assert geocoder_config.heremaps_geocoder is True
assert geocoder_config.geocoding_quota == 200
assert geocoder_config.soft_geocoding_limit is False
assert geocoder_config.period_end_date.date() == yesterday.date()
def test_should_return_config_for_obs_snapshot(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user',
do_quota=100, soft_do_limit=True,
end_date=yesterday)
do_config = ObservatorySnapshotConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.monthly_quota == 100
assert do_config.soft_limit is True
assert do_config.period_end_date.date() == yesterday.date()
def test_should_return_db_quota_if_not_redis_quota_config_obs_snapshot(self):
yesterday = datetime.today() - timedelta(days=1)
build_redis_user_config(self.redis_conn, 'test_user',
end_date=yesterday)
do_config = ObservatorySnapshotConfig(self.redis_conn, plpy_mock,
'test_user')
assert do_config.monthly_quota == 0
assert do_config.soft_limit is False
assert do_config.period_end_date.date() == yesterday.date()
def test_should_raise_exception_when_missing_parameters(self):
plpy_mock._reset()
build_redis_user_config(self.redis_conn, 'test_user')
assert_raises(ConfigException, GeocoderConfig, self.redis_conn,
plpy_mock, 'test_user', None)

View File

@@ -1,4 +1,5 @@
from datetime import datetime, date
from dateutil.tz import tzlocal
from mock import Mock, MagicMock
import random
import sys
@@ -8,46 +9,60 @@ plpy_mock = MockPlPy()
sys.modules['plpy'] = plpy_mock
def build_redis_user_config(redis_conn, username, quota=100, soft_limit=False,
service="heremaps", isolines_quota=0,
do_quota=None, soft_do_limit=None,
do_general_quota=None, soft_do_general_limit=None,
def build_redis_user_config(redis_conn, username, service, quota=100,
soft_limit=False, provider="heremaps",
end_date=datetime.today()):
end_date_tz = end_date.replace(tzinfo=tzlocal())
user_redis_name = "rails:users:{0}".format(username)
redis_conn.hset(user_redis_name, 'soft_geocoding_limit', soft_limit)
redis_conn.hset(user_redis_name, 'geocoding_quota', quota)
redis_conn.hset(user_redis_name, 'here_isolines_quota', isolines_quota)
redis_conn.hset(user_redis_name, 'geocoder_provider', service)
redis_conn.hset(user_redis_name, 'isolines_provider', service)
redis_conn.hset(user_redis_name, 'routing_provider', service)
redis_conn.hset(user_redis_name, 'period_end_date', end_date)
if do_quota:
redis_conn.hset(user_redis_name, 'obs_snapshot_quota', do_quota)
if soft_do_limit:
redis_conn.hset(user_redis_name, 'soft_obs_snapshot_limit',
soft_do_limit)
if do_general_quota:
redis_conn.hset(user_redis_name, 'obs_general_quota', do_general_quota)
if soft_do_general_limit:
redis_conn.hset(user_redis_name, 'soft_obs_general_limit',
soft_do_general_limit)
if service is 'geocoding':
redis_conn.hset(user_redis_name, 'geocoder_provider', provider)
redis_conn.hset(user_redis_name, 'geocoding_quota', str(quota))
redis_conn.hset(user_redis_name, 'soft_geocoding_limit', str(soft_limit).lower())
elif service is 'isolines':
redis_conn.hset(user_redis_name, 'isolines_provider', provider)
redis_conn.hset(user_redis_name, 'here_isolines_quota', str(quota))
redis_conn.hset(user_redis_name, 'soft_here_isolines_limit', str(soft_limit).lower())
elif service is 'routing':
redis_conn.hset(user_redis_name, 'routing_provider', provider)
redis_conn.hset(user_redis_name, 'mapzen_routing_quota', str(quota))
redis_conn.hset(user_redis_name, 'soft_mapzen_routing_limit', str(soft_limit).lower())
elif service is 'data_observatory':
redis_conn.hset(user_redis_name, 'obs_snapshot_quota', str(quota))
redis_conn.hset(user_redis_name, 'obs_general_quota', str(quota))
redis_conn.hset(user_redis_name, 'soft_obs_snapshot_limit', str(soft_limit).lower())
redis_conn.hset(user_redis_name, 'soft_obs_general_limit', str(soft_limit).lower())
redis_conn.hset(user_redis_name, 'google_maps_client_id', '')
redis_conn.hset(user_redis_name, 'google_maps_api_key', '')
redis_conn.hset(user_redis_name, 'period_end_date', end_date_tz.strftime("%Y-%m-%d %H:%M:%S %z"))
def build_redis_org_config(redis_conn, orgname, quota=100, service="heremaps",
isolines_quota=0, do_quota=None,
do_general_quota=None, end_date=datetime.today()):
def build_redis_org_config(redis_conn, orgname, service, quota=100,
provider="heremaps", end_date=datetime.now(tzlocal())):
org_redis_name = "rails:orgs:{0}".format(orgname)
redis_conn.hset(org_redis_name, 'geocoding_quota', quota)
redis_conn.hset(org_redis_name, 'here_isolines_quota', isolines_quota)
if do_quota:
redis_conn.hset(org_redis_name, 'obs_snapshot_quota', do_quota)
if do_general_quota:
redis_conn.hset(org_redis_name, 'obs_snapshot_quota', do_quota)
redis_conn.hset(org_redis_name, 'period_end_date', end_date)
end_date_tz = end_date.replace(tzinfo=tzlocal())
if service is 'geocoding':
redis_conn.hset(org_redis_name, 'geocoder_provider', provider)
if quota is not None:
redis_conn.hset(org_redis_name, 'geocoding_quota', str(quota))
elif service is 'isolines':
redis_conn.hset(org_redis_name, 'isolines_provider', provider)
if quota is not None:
redis_conn.hset(org_redis_name, 'here_isolines_quota', str(quota))
elif service is 'routing':
redis_conn.hset(org_redis_name, 'routing_provider', provider)
if quota is not None:
redis_conn.hset(org_redis_name, 'mapzen_routing_quota', str(quota))
elif service is 'data_observatory':
if quota is not None:
redis_conn.hset(org_redis_name, 'obs_snapshot_quota', str(quota))
redis_conn.hset(org_redis_name, 'obs_general_quota', str(quota))
redis_conn.hset(org_redis_name, 'google_maps_client_id', '')
redis_conn.hset(org_redis_name, 'google_maps_api_key', '')
redis_conn.hset(org_redis_name, 'period_end_date', end_date_tz.strftime("%Y-%m-%d %H:%M:%S %z"))
def increment_service_uses(redis_conn, username, orgname=None,

View File

@@ -1,7 +1,7 @@
from test_helper import *
from mockredis import MockRedis
from cartodb_services.metrics import QuotaService
from cartodb_services.metrics import GeocoderConfig, RoutingConfig, ObservatorySnapshotConfig, IsolinesRoutingConfig
from cartodb_services.metrics import *
from unittest import TestCase
from nose.tools import assert_raises
from datetime import datetime, date
@@ -35,26 +35,26 @@ class TestQuotaService(TestCase):
qs = self.__build_geocoder_quota_service('test_user',
orgname='test_org')
increment_service_uses(self.redis_conn, 'test_user',
orgname='test_org')
orgname='test_org')
assert qs.check_user_quota() is True
def test_should_return_false_if_user_quota_is_surpassed(self):
qs = self.__build_geocoder_quota_service('test_user')
increment_service_uses(self.redis_conn, 'test_user',
amount=300)
amount=300)
assert qs.check_user_quota() is False
def test_should_return_false_if_org_quota_is_surpassed(self):
qs = self.__build_geocoder_quota_service('test_user',
orgname='test_org')
increment_service_uses(self.redis_conn, 'test_user',
orgname='test_org', amount=400)
orgname='test_org', amount=400)
assert qs.check_user_quota() is False
def test_should_return_true_if_user_quota_is_surpassed_but_soft_limit_is_enabled(self):
qs = self.__build_geocoder_quota_service('test_user', soft_limit=True)
increment_service_uses(self.redis_conn, 'test_user',
amount=300)
amount=300)
assert qs.check_user_quota() is True
def test_should_return_true_if_org_quota_is_surpassed_but_soft_limit_is_enabled(self):
@@ -62,7 +62,7 @@ class TestQuotaService(TestCase):
orgname='test_org',
soft_limit=True)
increment_service_uses(self.redis_conn, 'test_user',
orgname='test_org', amount=400)
orgname='test_org', amount=400)
assert qs.check_user_quota() is True
def test_should_check_user_increment_and_quota_check_correctly(self):
@@ -81,15 +81,17 @@ class TestQuotaService(TestCase):
assert qs.check_user_quota() is False
def test_should_check_user_mapzen_geocoder_quota_correctly(self):
qs = self.__build_geocoder_quota_service('test_user', service='mapzen')
qs = self.__build_geocoder_quota_service('test_user',
provider='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_org_mapzen_geocoder_quota_correctly(self):
qs = self.__build_geocoder_quota_service('test_user', orgname='testorg',
service='mapzen')
qs = self.__build_geocoder_quota_service('test_user',
orgname='testorg',
provider='mapzen')
qs.increment_success_service_use()
assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=1500000)
@@ -111,16 +113,17 @@ class TestQuotaService(TestCase):
def test_should_check_user_isolines_quota_correctly(self):
qs = self.__build_isolines_quota_service('test_user')
qs.increment_success_service_use()
qs.increment_isolines_service_use()
assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=1500000)
qs.increment_isolines_service_use(amount=1500000)
assert qs.check_user_quota() is False
def test_should_check_org_isolines_quota_correctly(self):
qs = self.__build_isolines_quota_service('test_user', orgname='testorg')
qs.increment_success_service_use()
qs = self.__build_isolines_quota_service('test_user',
orgname='testorg')
qs.increment_isolines_service_use()
assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=1500000)
qs.increment_isolines_service_use(amount=1500000)
assert qs.check_user_quota() is False
def test_should_check_user_obs_snapshot_quota_correctly(self):
@@ -138,55 +141,77 @@ class TestQuotaService(TestCase):
qs.increment_success_service_use(amount=100000)
assert qs.check_user_quota() is False
def __prepare_quota_service(self, username, quota, service, orgname,
soft_limit, do_quota, soft_do_limit, end_date):
build_redis_user_config(self.redis_conn, username,
quota=quota, service=service,
soft_limit=soft_limit,
soft_do_limit=soft_do_limit,
do_quota=do_quota,
end_date=end_date)
def test_should_check_user_obs_quota_correctly(self):
qs = self.__build_obs_snapshot_quota_service('test_user')
qs.increment_success_service_use()
assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=100000)
assert qs.check_user_quota() is False
def test_should_check_org_obs_quota_correctly(self):
qs = self.__build_obs_quota_service('test_user',
orgname='testorg')
qs.increment_success_service_use()
assert qs.check_user_quota() is True
qs.increment_success_service_use(amount=100000)
assert qs.check_user_quota() is False
def __prepare_quota_service(self, username, service, quota, provider,
orgname, soft_limit, end_date):
build_redis_user_config(self.redis_conn, username, service,
quota=quota, provider=provider,
soft_limit=soft_limit, end_date=end_date)
if orgname:
build_redis_org_config(self.redis_conn, orgname,
quota=quota, service=service,
do_quota=do_quota,
end_date=end_date)
build_redis_org_config(self.redis_conn, orgname, service,
quota=quota, provider=provider,
end_date=end_date)
def __build_geocoder_quota_service(self, username, quota=100,
service='heremaps', orgname=None,
provider='heremaps', orgname=None,
soft_limit=False,
end_date=datetime.today()):
self.__prepare_quota_service(username, quota, service, orgname,
soft_limit, 0, False, end_date)
self.__prepare_quota_service(username, 'geocoding', quota,
provider, orgname, soft_limit, end_date)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
username, orgname)
return QuotaService(geocoder_config, redis_connection=self.redis_conn)
def __build_routing_quota_service(self, username, service='mapzen',
def __build_routing_quota_service(self, username, provider='mapzen',
orgname=None, soft_limit=False,
quota=100, end_date=datetime.today()):
self.__prepare_quota_service(username, quota, service, orgname,
soft_limit, 0, False, end_date)
self.__prepare_quota_service(username, 'routing', quota, provider,
orgname, soft_limit, end_date)
routing_config = RoutingConfig(self.redis_conn, plpy_mock,
username, orgname)
return QuotaService(routing_config, redis_connection=self.redis_conn)
def __build_isolines_quota_service(self, username, service='mapzen',
def __build_isolines_quota_service(self, username, provider='mapzen',
orgname=None, soft_limit=False,
quota=100, end_date=datetime.today()):
self.__prepare_quota_service(username, quota, service, orgname,
soft_limit, 0, False, end_date)
self.__prepare_quota_service(username, 'isolines', quota, provider,
orgname, soft_limit, end_date)
isolines_config = IsolinesRoutingConfig(self.redis_conn, plpy_mock,
username, orgname)
return QuotaService(isolines_config, redis_connection=self.redis_conn)
def __build_obs_snapshot_quota_service(self, username, quota=100,
service='obs_snapshot',
orgname=None,
soft_limit=False,
end_date=datetime.today()):
self.__prepare_quota_service(username, 0, service, orgname, False,
quota, soft_limit, end_date)
provider='obs_snapshot',
orgname=None,
soft_limit=False,
end_date=datetime.today()):
self.__prepare_quota_service(username, 'data_observatory', quota,
None, orgname, soft_limit, end_date)
do_config = ObservatorySnapshotConfig(self.redis_conn, plpy_mock,
username, orgname)
return QuotaService(do_config, redis_connection=self.redis_conn)
def __build_obs_quota_service(self, username, quota=100,
provider='obs_general',
orgname=None,
soft_limit=False,
end_date=datetime.today()):
self.__prepare_quota_service(username, 'data_observatory', quota,
None, orgname, soft_limit, end_date)
do_config = ObservatoryConfig(self.redis_conn, plpy_mock,
username, orgname)
return QuotaService(do_config, redis_connection=self.redis_conn)

View File

@@ -23,6 +23,10 @@ class TestUserService(TestCase):
amount=400)
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 400
def test_user_quota_for_a_month_shorter_than_end_day(self):
us = self.__build_user_service('test_user', end_date=datetime(2016,1,31))
assert us.used_quota(self.NOKIA_GEOCODER, date(2016,2,10)) == 0
def test_org_used_quota_for_a_day(self):
us = self.__build_user_service('test_user', orgname='test_org')
increment_service_uses(self.redis_conn, 'test_user',
@@ -30,6 +34,10 @@ class TestUserService(TestCase):
amount=400)
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 400
def test_org_quota_quota_for_a_month_shorter_than_end_day(self):
us = self.__build_user_service('test_user', orgname='test_org', end_date=datetime(2016,1,31))
assert us.used_quota(self.NOKIA_GEOCODER, date(2016,2,10)) == 0
def test_user_not_amount_in_used_quota_for_month_should_be_0(self):
us = self.__build_user_service('test_user')
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 0
@@ -108,7 +116,7 @@ class TestUserService(TestCase):
def zscore_counter(self):
return self._zscore_counter
self.redis_conn = MockRedisWithCounter()
us = self.__build_user_service('test_user', end_date=date.today())
us = self.__build_user_service('test_user', end_date=datetime.today())
us.used_quota(self.NOKIA_GEOCODER, date(2015, 6, 15))
#('user:test_user:geocoder_here:success_responses:201506', 15)
@@ -132,16 +140,17 @@ class TestUserService(TestCase):
assert self.redis_conn.zscore('org:test_org:geocoder_here:success_responses:201506', '1') == None
def __build_user_service(self, username, quota=100, service='heremaps',
orgname=None, soft_limit=False,
end_date=date.today()):
build_redis_user_config(self.redis_conn, username,
quota=quota, service=service,
soft_limit=soft_limit,
end_date=end_date)
def __build_user_service(self, username, service='geocoding', quota=100,
provider='heremaps', orgname=None,
soft_limit=False, end_date=datetime.today()):
build_redis_user_config(self.redis_conn, username, service,
quota=quota, provider=provider,
soft_limit=soft_limit,
end_date=end_date)
if orgname:
build_redis_org_config(self.redis_conn, orgname,
quota=quota, end_date=end_date)
build_redis_org_config(self.redis_conn, orgname, service,
provider=provider, quota=quota,
end_date=end_date)
geocoder_config = GeocoderConfig(self.redis_conn, plpy_mock,
username, orgname)
return UserMetricsService(geocoder_config, self.redis_conn)