Compare commits
24 Commits
python-0.1
...
python-0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1936374124 | ||
|
|
988a0c31dc | ||
|
|
0cd01ee9c9 | ||
|
|
3ed41b065a | ||
|
|
6c3260b6ee | ||
|
|
5005572f89 | ||
|
|
27fde5a910 | ||
|
|
b8e1120169 | ||
|
|
8e33cf9723 | ||
|
|
7b988e3f78 | ||
|
|
f79ac9297d | ||
|
|
854fbb803c | ||
|
|
d029ad7041 | ||
|
|
24c29c0847 | ||
|
|
31e79cb626 | ||
|
|
98eef7ae02 | ||
|
|
445d4cf97d | ||
|
|
3a86ffba71 | ||
|
|
f0a3249b4e | ||
|
|
ef7c5d9218 | ||
|
|
bd63346c50 | ||
|
|
c878384955 | ||
|
|
475df918c7 | ||
|
|
937440c79a |
20
NEWS.md
20
NEWS.md
@@ -1,3 +1,23 @@
|
||||
October 6th, 2017
|
||||
=================
|
||||
* Version `0.15.5` of the python library
|
||||
* googlemaps dependency updated to v2.5.1
|
||||
* Google geocoder performance boost: client connections are now reused between queries. See #401
|
||||
* Fixed issue with Google keys validity check. See #382
|
||||
* Fixed inconsistency in service usage failed requests tracking. See f0a3249
|
||||
* Client extension tests are now compatible with PostgreSQL 9.5 and 9.6
|
||||
|
||||
August 30th, 2017
|
||||
=============
|
||||
* Version `0.15.4` of the python library
|
||||
* Fixed invalid geometries for isochrones due to `generalize` option. See #397
|
||||
|
||||
August 24th, 2017
|
||||
=============
|
||||
* Improved the documentation
|
||||
* Version `0.15.3` of the python library
|
||||
* Disabled DO quota check for users that have it configured . See #395
|
||||
|
||||
August 23th, 2017
|
||||
=============
|
||||
* Version `0.27.0` of the server
|
||||
|
||||
@@ -21,7 +21,7 @@ DATA = $(NEW_EXTENSION_ARTIFACT) \
|
||||
SOURCES_DATA_DIR = sql/
|
||||
|
||||
|
||||
REGRESS = $(notdir $(basename $(wildcard test/sql/*test.sql)))
|
||||
REGRESS = $(notdir $(basename $(sort $(wildcard test/sql/*test.sql))))
|
||||
TEST_DIR = test/
|
||||
REGRESS_OPTS = --inputdir='$(TEST_DIR)' --outputdir='$(TEST_DIR)' --user='postgres'
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
-- Install dependencies
|
||||
CREATE EXTENSION postgis;
|
||||
CREATE EXTENSION schema_triggers;
|
||||
CREATE EXTENSION plpythonu;
|
||||
CREATE EXTENSION cartodb;
|
||||
CREATE EXTENSION plproxy;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
-- Mock the server function
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
-- Mock the server functions
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
SET client_min_messages TO warning;
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
-- Mock the server functions to raise exceptions
|
||||
@@ -31,8 +32,6 @@ 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
|
||||
------------------------------------------
|
||||
|
||||
@@ -40,15 +39,12 @@ PL/pgSQL function _cdb_geocode_street_point_exception_safe(text,text,text,text)
|
||||
|
||||
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,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
-- Mock the server functions
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
-- Mock the server functions
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
-- Mock the server functions
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
-- Mock the server functions
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
-- Mock the server functions
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
-- Mock the server functions
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Use regular user role
|
||||
SET ROLE test_regular_user;
|
||||
-- Add to the search path the schema
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
-- Install dependencies
|
||||
CREATE EXTENSION postgis;
|
||||
CREATE EXTENSION schema_triggers;
|
||||
CREATE EXTENSION plpythonu;
|
||||
CREATE EXTENSION cartodb;
|
||||
CREATE EXTENSION plproxy;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
SET client_min_messages TO warning;
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Add to the search path the schema
|
||||
SET search_path TO public,cartodb,cdb_dataservices_client;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
\set VERBOSITY terse
|
||||
-- Use regular user role
|
||||
SET ROLE test_regular_user;
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/local/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import googlemaps
|
||||
import base64
|
||||
from exceptions import InvalidGoogleCredentials
|
||||
|
||||
class GoogleMapsClientFactory():
|
||||
clients = {}
|
||||
|
||||
@classmethod
|
||||
def get(cls, client_id, client_secret):
|
||||
cache_key = "{}:{}".format(client_id, client_secret)
|
||||
client = cls.clients.get(cache_key)
|
||||
if not client:
|
||||
cls.assert_valid_crendentials(client_secret)
|
||||
client = googlemaps.Client(
|
||||
client_id=client_id,
|
||||
client_secret=client_secret)
|
||||
cls.clients[cache_key] = client
|
||||
return client
|
||||
|
||||
@classmethod
|
||||
def assert_valid_crendentials(cls, client_secret):
|
||||
if not cls.valid_credentials(client_secret):
|
||||
raise InvalidGoogleCredentials
|
||||
|
||||
@staticmethod
|
||||
def valid_credentials(client_secret):
|
||||
try:
|
||||
# Only fails if the string dont have a correct padding for b64
|
||||
# but this way we could provide a more clear error than
|
||||
# TypeError: Incorrect padding
|
||||
b64_secret = client_secret.replace('-', '+').replace('_', '/')
|
||||
base64.b64decode(b64_secret)
|
||||
return True
|
||||
except TypeError:
|
||||
return False
|
||||
@@ -1,22 +1,19 @@
|
||||
#!/usr/local/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import base64
|
||||
import googlemaps
|
||||
|
||||
from exceptions import MalformedResult, InvalidGoogleCredentials
|
||||
from exceptions import MalformedResult
|
||||
from client_factory import GoogleMapsClientFactory
|
||||
|
||||
|
||||
class GoogleMapsGeocoder:
|
||||
"""A Google Maps Geocoder wrapper for python"""
|
||||
|
||||
def __init__(self, client_id, client_secret, logger):
|
||||
if not self._valid_credentials(client_secret):
|
||||
raise InvalidGoogleCredentials('Invalid google secret key')
|
||||
self.client_id = self._clean_client_id(client_id)
|
||||
self.client_secret = client_secret
|
||||
self.geocoder = googlemaps.Client(
|
||||
client_id=self.client_id, client_secret=self.client_secret)
|
||||
self.geocoder = GoogleMapsClientFactory.get(self.client_id, self.client_secret)
|
||||
self._logger = logger
|
||||
|
||||
def geocode(self, searchtext, city=None, state=None,
|
||||
@@ -52,13 +49,3 @@ class GoogleMapsGeocoder:
|
||||
def _clean_client_id(self, client_id):
|
||||
# Consistency with how the client_id is saved in metadata
|
||||
return client_id.replace('client=', '')
|
||||
|
||||
def _valid_credentials(self, private_key):
|
||||
try:
|
||||
# Only fails if the string dont have a correct padding for b64
|
||||
# but this way we could provide a more clear error than
|
||||
# TypeError: Incorrect padding
|
||||
base64.b64decode(private_key)
|
||||
return True
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
@@ -28,7 +28,7 @@ def coordinates_to_polygon(coordinates):
|
||||
wkt_coordinates = ','.join(result_coordinates)
|
||||
|
||||
try:
|
||||
sql = "SELECT ST_MakePolygon(ST_GeomFromText('LINESTRING({0})', 4326)) as geom".format(wkt_coordinates)
|
||||
sql = "SELECT ST_CollectionExtract(ST_MakeValid(ST_MakePolygon(ST_GeomFromText('LINESTRING({0})', 4326))),3) as geom".format(wkt_coordinates)
|
||||
geometry = plpy.execute(sql, 1)[0]['geom']
|
||||
except BaseException as e:
|
||||
plpy.warning("Can't generate POLYGON from coordinates: {0}".format(e))
|
||||
|
||||
@@ -32,7 +32,7 @@ class QuotaService:
|
||||
|
||||
def increment_failed_service_use(self, amount=1):
|
||||
self._user_service.increment_service_use(
|
||||
self._user_service_config.service_type, "fail_responses",
|
||||
self._user_service_config.service_type, "failed_responses",
|
||||
amount=amount)
|
||||
self._log_service_process("fail")
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
redis==2.10.5
|
||||
hiredis==0.1.5
|
||||
python-dateutil==2.2
|
||||
googlemaps==2.4.2
|
||||
googlemaps==2.5.1
|
||||
rollbar==0.13.2
|
||||
# Dependency for googlemaps package
|
||||
requests==2.9.1
|
||||
|
||||
@@ -10,7 +10,7 @@ from setuptools import setup, find_packages
|
||||
setup(
|
||||
name='cartodb_services',
|
||||
|
||||
version='0.15.3',
|
||||
version='0.15.5',
|
||||
|
||||
description='CartoDB Services API Python Library',
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/local/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import googlemaps
|
||||
import unittest
|
||||
import base64
|
||||
|
||||
from cartodb_services.google.client_factory import GoogleMapsClientFactory
|
||||
from cartodb_services.google.exceptions import InvalidGoogleCredentials
|
||||
|
||||
class GoogleMapsClientFactoryTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
# reset client cache
|
||||
GoogleMapsClientFactory.clients = {}
|
||||
|
||||
def test_consecutive_calls_with_same_params_return_same_client(self):
|
||||
id = 'any_id'
|
||||
key = base64.b64encode('any_key')
|
||||
client1 = GoogleMapsClientFactory.get(id, key)
|
||||
client2 = GoogleMapsClientFactory.get(id, key)
|
||||
self.assertEqual(client1, client2)
|
||||
|
||||
def test_consecutive_calls_with_different_key_return_different_clients(self):
|
||||
"""
|
||||
This requirement is important for security reasons as well as not to
|
||||
cache a wrong key accidentally.
|
||||
"""
|
||||
id = 'any_id'
|
||||
key1 = base64.b64encode('any_key')
|
||||
key2 = base64.b64encode('another_key')
|
||||
client1 = GoogleMapsClientFactory.get(id, key1)
|
||||
client2 = GoogleMapsClientFactory.get(id, key2)
|
||||
self.assertNotEqual(client1, client2)
|
||||
|
||||
def test_consecutive_calls_with_different_ids_return_different_clients(self):
|
||||
"""
|
||||
This requirement is important for security reasons as well as not to
|
||||
cache a wrong key accidentally.
|
||||
"""
|
||||
id1 = 'any_id'
|
||||
id2 = 'another_id'
|
||||
key = base64.b64encode('any_key')
|
||||
client1 = GoogleMapsClientFactory.get(id1, key)
|
||||
client2 = GoogleMapsClientFactory.get(id2, key)
|
||||
self.assertNotEqual(client1, client2)
|
||||
|
||||
def test_invalid_credentials(self):
|
||||
with self.assertRaises(InvalidGoogleCredentials):
|
||||
GoogleMapsClientFactory.get('dummy_client_id', 'lalala')
|
||||
|
||||
def test_credentials_with_dashes_can_be_valid(self):
|
||||
client = GoogleMapsClientFactory.get('yet_another_dummy_client_id', 'Ola-k-ase---')
|
||||
self.assertIsInstance(client, googlemaps.Client)
|
||||
|
||||
def test_credentials_with_underscores_can_be_valid(self):
|
||||
client = GoogleMapsClientFactory.get('yet_another_dummy_client_id', 'Ola_k_ase___')
|
||||
self.assertIsInstance(client, googlemaps.Client)
|
||||
@@ -6,7 +6,7 @@ import requests_mock
|
||||
from mock import Mock
|
||||
|
||||
from cartodb_services.google import GoogleMapsGeocoder
|
||||
from cartodb_services.google.exceptions import MalformedResult, InvalidGoogleCredentials
|
||||
from cartodb_services.google.exceptions import MalformedResult
|
||||
|
||||
requests_mock.Mocker.TEST_PREFIX = 'test_'
|
||||
|
||||
@@ -118,9 +118,3 @@ class GoogleGeocoderTestCase(unittest.TestCase):
|
||||
searchtext='Calle Eloy Gonzalo 27',
|
||||
city='Madrid',
|
||||
country='España')
|
||||
|
||||
def test_invalid_credentials(self, req_mock):
|
||||
with self.assertRaises(InvalidGoogleCredentials):
|
||||
GoogleMapsGeocoder('dummy_client_id',
|
||||
'lalala',
|
||||
None)
|
||||
|
||||
@@ -52,7 +52,7 @@ class TestUserService(TestCase):
|
||||
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 1
|
||||
us.increment_service_use(self.NOKIA_GEOCODER, 'empty_responses')
|
||||
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 2
|
||||
us.increment_service_use(self.NOKIA_GEOCODER, 'fail_responses')
|
||||
us.increment_service_use(self.NOKIA_GEOCODER, 'failed_responses')
|
||||
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 2
|
||||
|
||||
def test_should_increment_org_used_quota(self):
|
||||
@@ -61,7 +61,7 @@ class TestUserService(TestCase):
|
||||
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 1
|
||||
us.increment_service_use(self.NOKIA_GEOCODER, 'empty_responses')
|
||||
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 2
|
||||
us.increment_service_use(self.NOKIA_GEOCODER, 'fail_responses')
|
||||
us.increment_service_use(self.NOKIA_GEOCODER, 'failed_responses')
|
||||
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 2
|
||||
|
||||
def test_should_increment_user_used_quota_in_for_multiples_dates(self):
|
||||
@@ -81,7 +81,7 @@ class TestUserService(TestCase):
|
||||
us.increment_service_use(self.NOKIA_GEOCODER, 'empty_responses',
|
||||
date=one_day_after)
|
||||
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 2
|
||||
us.increment_service_use(self.NOKIA_GEOCODER, 'fail_responses')
|
||||
us.increment_service_use(self.NOKIA_GEOCODER, 'failed_responses')
|
||||
assert us.used_quota(self.NOKIA_GEOCODER, date.today()) == 2
|
||||
|
||||
@freeze_time("2015-06-01")
|
||||
|
||||
Reference in New Issue
Block a user