Compare commits

..

15 Commits

Author SHA1 Message Date
Mario de Frutos
378458c0ae Merge pull request #534 from CartoDB/development
Release 0.34.1 of the server extension
2018-11-22 15:15:13 +01:00
Mario de Frutos
4ae4080bba Updated NEWS.md 2018-11-22 15:10:11 +01:00
Mario de Frutos
2bb67ba17a Merge pull request #533 from CartoDB/fix_isochrones_metrics
Fix isochrones metrics
2018-11-22 15:07:57 +01:00
Mario de Frutos
1027d554a5 Upgrade script from 0.34.0 to 0.34.1 2018-11-19 18:23:28 +01:00
Mario de Frutos
4e1f081952 Generated 0.34.1 artifact with changes 2018-11-19 18:11:23 +01:00
Mario de Frutos
87bb8bea68 Remove duped metrics for isochrones
We are counting the success/empty inside the iteration of the results
returned by the third party service.

For example, if we ask for 3 isochrones to a provider we count:

- 1 for success or empty
- If success we add 1 more success for each isochrone in the result
- N isolines_generated

but it should be

- 1 for success or empty
- N isolines_generated
2018-11-19 17:59:47 +01:00
Mario de Frutos
dc47f6f71b New version 0.34.1 artifacts 2018-11-19 17:51:26 +01:00
Mario de Frutos
765b2f0901 Merge pull request #529 from CartoDB/development
Release version 0.20.2 for python library
2018-10-31 14:00:31 +01:00
Mario de Frutos
36c42096e4 Updated NEWS.md 2018-10-31 13:58:29 +01:00
Mario de Frutos
909554b453 Merge pull request #528 from CartoDB/fix_qps_tomtom
Fix qps tomtom
2018-10-31 13:55:40 +01:00
Mario de Frutos
33f40bc945 TOMTOM uses 403 instead of 429 for rate limiting
That has a great problem when we're dealing with legit 403 status for
example deactivated user, forbidden access, etc.

I've added a check for the HTTP header `X-Error-Detail-Header` in order
to distinguish between legit 403 and 429 error messages

Possible values for `X-Error-Detail-Header` in a 403 error:

  o Service Requires SSL : http is used instead of https (secure)

  o Invalid Referer : invalid 'Referer' header value is send
  to https://api.tomtom.com and allowed referer values are
  configured on specific API key

  o Account Over Queries Per Second Limit : rate limit exceeded

  o Account Inactive : incorrect API key/API key no longer valid
2018-10-31 12:59:13 +01:00
Mario de Frutos
17c993f6ef Bump version 2018-10-24 12:00:44 +02:00
Mario de Frutos
6640909780 Add provider for QPS manger in tomtom services 2018-10-24 12:00:08 +02:00
Mario de Frutos
0e859d8955 Merge pull request #527 from CartoDB/change_isodistance_doc
Include note to explain why some isodistances could not be precise
2018-10-24 11:57:53 +02:00
Mario de Frutos
cd8173c7e0 Include note to explain why some isodistances could not be precise 2018-10-23 16:36:05 +02:00
14 changed files with 4192 additions and 11 deletions

16
NEWS.md
View File

@@ -1,3 +1,19 @@
Nov 22th, 2018
==============
* Version `0.34.1` of the server extension
* Fixed isochrones metrics that increase `success_rows` field for every isochrone generated (#533)
Oct 31th, 2018
==============
* Version `0.20.2` of the python library
* Added missing provider property to the QPS decorator in other TomTom services
* Now we only retry with the properly header coming from TomTom
Oct 3rd, 2018
==============
* Version `0.20.1` of the python library
* Fix QPS manager to retry with 403 status codes coming from TomTom
Sep 13th, 2018 Sep 13th, 2018
============== ==============
* Version `0.34.0` of the server, and `0.26.0` of the client. * Version `0.34.0` of the server, and `0.26.0` of the client.

View File

@@ -16,6 +16,8 @@ The following functions provide an isoline generator service, based on time or d
Displays a contoured line on a map, connecting geometries to a defined area, measured by an equal range of distance (in meters). Displays a contoured line on a map, connecting geometries to a defined area, measured by an equal range of distance (in meters).
Note that not all the providers, for example TomTom, provide us a way to define the isoline limit in distance so we need to make some estimations. Due that estimations the produced isolines could not be 100% precise.
#### Arguments #### Arguments
Name | Type | Description | Accepted values Name | Type | Description | Accepted values

View File

@@ -0,0 +1,182 @@
--DO NOT MODIFY THIS FILE, IT IS GENERATED AUTOMATICALLY FROM SOURCES
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION cdb_dataservices_server UPDATE TO '0.34.1'" to load this file. \quit
-- HERE goes your code to upgrade/downgrade
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_isochrones(
username TEXT,
orgname TEXT,
source geometry(Geometry, 4326),
mode TEXT,
data_range integer[],
options text[])
RETURNS SETOF cdb_dataservices_server.isoline AS $$
import json
from cartodb_services.mapzen import MatrixClient, MapzenIsochrones
from cartodb_services.metrics import QuotaService
from cartodb_services.tools import Logger,LoggerConfig
from cartodb_services.mapzen.types import coordinates_to_polygon
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_isolines_routing_config = GD["user_isolines_routing_config_{0}".format(username)]
plpy.execute("SELECT cdb_dataservices_server._get_logger_config()")
logger_config = GD["logger_config"]
logger = Logger(logger_config)
# -- Check the quota
quota_service = QuotaService(user_isolines_routing_config, redis_conn)
if not quota_service.check_user_quota():
raise Exception('You have reached the limit of your quota')
try:
mapzen_isochrones = MapzenIsochrones(user_isolines_routing_config.mapzen_matrix_api_key,
logger, user_isolines_routing_config.mapzen_isochrones_service_params)
if source:
lat = plpy.execute("SELECT ST_Y('%s') AS lat" % source)[0]['lat']
lon = plpy.execute("SELECT ST_X('%s') AS lon" % source)[0]['lon']
origin = {'lat': lat, 'lon': lon}
else:
raise Exception('source is NULL')
resp = mapzen_isochrones.isochrone(origin, mode, data_range)
if resp:
result = []
for isochrone in resp:
result_polygon = coordinates_to_polygon(isochrone.coordinates)
if result_polygon:
result.append([source, isochrone.duration, result_polygon])
else:
result.append([source, isochrone.duration, None])
quota_service.increment_success_service_use()
quota_service.increment_isolines_service_use(len(result))
return result
else:
quota_service.increment_empty_service_use()
return []
except BaseException as e:
import sys
quota_service.increment_failed_service_use()
logger.error('Error trying to get mapzen isochrones', sys.exc_info(), data={"username": username, "orgname": orgname})
raise Exception('Error trying to get mapzen isochrones')
finally:
quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER STABLE PARALLEL RESTRICTED;
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapbox_isochrones(
username TEXT,
orgname TEXT,
source geometry(Geometry, 4326),
mode TEXT,
data_range integer[],
options text[])
RETURNS SETOF cdb_dataservices_server.isoline AS $$
from cartodb_services.tools import ServiceManager
from cartodb_services.mapbox import MapboxMatrixClient, MapboxIsolines
from cartodb_services.mapbox.types import TRANSPORT_MODE_TO_MAPBOX
from cartodb_services.tools import Coordinate
from cartodb_services.tools.coordinates import coordinates_to_polygon
from cartodb_services.refactor.service.mapbox_isolines_config import MapboxIsolinesConfigBuilder
import cartodb_services
cartodb_services.init(plpy, GD)
service_manager = ServiceManager('isolines', MapboxIsolinesConfigBuilder, username, orgname, GD)
service_manager.assert_within_limits()
try:
client = MapboxMatrixClient(service_manager.config.mapbox_api_key, service_manager.logger, service_manager.config.service_params)
mapbox_isolines = MapboxIsolines(client, service_manager.logger)
if source:
lat = plpy.execute("SELECT ST_Y('%s') AS lat" % source)[0]['lat']
lon = plpy.execute("SELECT ST_X('%s') AS lon" % source)[0]['lon']
origin = Coordinate(lon,lat)
else:
raise Exception('source is NULL')
profile = TRANSPORT_MODE_TO_MAPBOX.get(mode)
resp = mapbox_isolines.calculate_isochrone(origin, data_range, profile)
if resp:
result = []
for isochrone in resp:
result_polygon = coordinates_to_polygon(isochrone.coordinates)
if result_polygon:
result.append([source, isochrone.duration, result_polygon])
else:
result.append([source, isochrone.duration, None])
service_manager.quota_service.increment_success_service_use()
service_manager.quota_service.increment_isolines_service_use(len(result))
return result
else:
service_manager.quota_service.increment_empty_service_use()
return []
except BaseException as e:
import sys
service_manager.quota_service.increment_failed_service_use()
service_manager.logger.error('Error trying to get Mapbox isochrones', sys.exc_info(), data={"username": username, "orgname": orgname})
raise Exception('Error trying to get Mapbox isochrones')
finally:
service_manager.quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER STABLE PARALLEL RESTRICTED;
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_tomtom_isochrones(
username TEXT,
orgname TEXT,
source geometry(Geometry, 4326),
mode TEXT,
data_range integer[],
options text[])
RETURNS SETOF cdb_dataservices_server.isoline AS $$
from cartodb_services.tools import ServiceManager
from cartodb_services.tomtom import TomTomIsolines
from cartodb_services.tomtom.types import TRANSPORT_MODE_TO_TOMTOM
from cartodb_services.tools import Coordinate
from cartodb_services.tools.coordinates import coordinates_to_polygon
from cartodb_services.refactor.service.tomtom_isolines_config import TomTomIsolinesConfigBuilder
import cartodb_services
cartodb_services.init(plpy, GD)
service_manager = ServiceManager('isolines', TomTomIsolinesConfigBuilder, username, orgname, GD)
service_manager.assert_within_limits()
try:
tomtom_isolines = TomTomIsolines(service_manager.config.tomtom_api_key, service_manager.logger, service_manager.config.service_params)
if source:
lat = plpy.execute("SELECT ST_Y('%s') AS lat" % source)[0]['lat']
lon = plpy.execute("SELECT ST_X('%s') AS lon" % source)[0]['lon']
origin = Coordinate(lon,lat)
else:
raise Exception('source is NULL')
profile = TRANSPORT_MODE_TO_TOMTOM.get(mode)
resp = tomtom_isolines.calculate_isochrone(origin, data_range, profile)
if resp:
result = []
for isochrone in resp:
result_polygon = coordinates_to_polygon(isochrone.coordinates)
if result_polygon:
result.append([source, isochrone.duration, result_polygon])
else:
result.append([source, isochrone.duration, None])
service_manager.quota_service.increment_success_service_use()
service_manager.quota_service.increment_isolines_service_use(len(result))
return result
else:
service_manager.quota_service.increment_empty_service_use()
return []
except BaseException as e:
import sys
service_manager.quota_service.increment_failed_service_use()
service_manager.logger.error('Error trying to get TomTom isochrones', sys.exc_info(), data={"username": username, "orgname": orgname})
raise Exception('Error trying to get TomTom isochrones')
finally:
service_manager.quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER STABLE PARALLEL RESTRICTED;

View File

@@ -0,0 +1,188 @@
--DO NOT MODIFY THIS FILE, IT IS GENERATED AUTOMATICALLY FROM SOURCES
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION cdb_dataservices_server UPDATE TO '0.34.0'" to load this file. \quit
-- HERE goes your code to upgrade/downgrade
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_isochrones(
username TEXT,
orgname TEXT,
source geometry(Geometry, 4326),
mode TEXT,
data_range integer[],
options text[])
RETURNS SETOF cdb_dataservices_server.isoline AS $$
import json
from cartodb_services.mapzen import MatrixClient, MapzenIsochrones
from cartodb_services.metrics import QuotaService
from cartodb_services.tools import Logger,LoggerConfig
from cartodb_services.mapzen.types import coordinates_to_polygon
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_isolines_routing_config = GD["user_isolines_routing_config_{0}".format(username)]
plpy.execute("SELECT cdb_dataservices_server._get_logger_config()")
logger_config = GD["logger_config"]
logger = Logger(logger_config)
# -- Check the quota
quota_service = QuotaService(user_isolines_routing_config, redis_conn)
if not quota_service.check_user_quota():
raise Exception('You have reached the limit of your quota')
try:
mapzen_isochrones = MapzenIsochrones(user_isolines_routing_config.mapzen_matrix_api_key,
logger, user_isolines_routing_config.mapzen_isochrones_service_params)
if source:
lat = plpy.execute("SELECT ST_Y('%s') AS lat" % source)[0]['lat']
lon = plpy.execute("SELECT ST_X('%s') AS lon" % source)[0]['lon']
origin = {'lat': lat, 'lon': lon}
else:
raise Exception('source is NULL')
resp = mapzen_isochrones.isochrone(origin, mode, data_range)
if resp:
result = []
for isochrone in resp:
result_polygon = coordinates_to_polygon(isochrone.coordinates)
if result_polygon:
quota_service.increment_success_service_use()
result.append([source, isochrone.duration, result_polygon])
else:
quota_service.increment_empty_service_use()
result.append([source, isochrone.duration, None])
quota_service.increment_success_service_use()
quota_service.increment_isolines_service_use(len(result))
return result
else:
quota_service.increment_empty_service_use()
return []
except BaseException as e:
import sys
quota_service.increment_failed_service_use()
logger.error('Error trying to get mapzen isochrones', sys.exc_info(), data={"username": username, "orgname": orgname})
raise Exception('Error trying to get mapzen isochrones')
finally:
quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER STABLE PARALLEL RESTRICTED;
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapbox_isochrones(
username TEXT,
orgname TEXT,
source geometry(Geometry, 4326),
mode TEXT,
data_range integer[],
options text[])
RETURNS SETOF cdb_dataservices_server.isoline AS $$
from cartodb_services.tools import ServiceManager
from cartodb_services.mapbox import MapboxMatrixClient, MapboxIsolines
from cartodb_services.mapbox.types import TRANSPORT_MODE_TO_MAPBOX
from cartodb_services.tools import Coordinate
from cartodb_services.tools.coordinates import coordinates_to_polygon
from cartodb_services.refactor.service.mapbox_isolines_config import MapboxIsolinesConfigBuilder
import cartodb_services
cartodb_services.init(plpy, GD)
service_manager = ServiceManager('isolines', MapboxIsolinesConfigBuilder, username, orgname, GD)
service_manager.assert_within_limits()
try:
client = MapboxMatrixClient(service_manager.config.mapbox_api_key, service_manager.logger, service_manager.config.service_params)
mapbox_isolines = MapboxIsolines(client, service_manager.logger)
if source:
lat = plpy.execute("SELECT ST_Y('%s') AS lat" % source)[0]['lat']
lon = plpy.execute("SELECT ST_X('%s') AS lon" % source)[0]['lon']
origin = Coordinate(lon,lat)
else:
raise Exception('source is NULL')
profile = TRANSPORT_MODE_TO_MAPBOX.get(mode)
resp = mapbox_isolines.calculate_isochrone(origin, data_range, profile)
if resp:
result = []
for isochrone in resp:
result_polygon = coordinates_to_polygon(isochrone.coordinates)
if result_polygon:
service_manager.quota_service.increment_success_service_use()
result.append([source, isochrone.duration, result_polygon])
else:
service_manager.quota_service.increment_empty_service_use()
result.append([source, isochrone.duration, None])
service_manager.quota_service.increment_success_service_use()
service_manager.quota_service.increment_isolines_service_use(len(result))
return result
else:
service_manager.quota_service.increment_empty_service_use()
return []
except BaseException as e:
import sys
service_manager.quota_service.increment_failed_service_use()
service_manager.logger.error('Error trying to get Mapbox isochrones', sys.exc_info(), data={"username": username, "orgname": orgname})
raise Exception('Error trying to get Mapbox isochrones')
finally:
service_manager.quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER STABLE PARALLEL RESTRICTED;
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_tomtom_isochrones(
username TEXT,
orgname TEXT,
source geometry(Geometry, 4326),
mode TEXT,
data_range integer[],
options text[])
RETURNS SETOF cdb_dataservices_server.isoline AS $$
from cartodb_services.tools import ServiceManager
from cartodb_services.tomtom import TomTomIsolines
from cartodb_services.tomtom.types import TRANSPORT_MODE_TO_TOMTOM
from cartodb_services.tools import Coordinate
from cartodb_services.tools.coordinates import coordinates_to_polygon
from cartodb_services.refactor.service.tomtom_isolines_config import TomTomIsolinesConfigBuilder
import cartodb_services
cartodb_services.init(plpy, GD)
service_manager = ServiceManager('isolines', TomTomIsolinesConfigBuilder, username, orgname, GD)
service_manager.assert_within_limits()
try:
tomtom_isolines = TomTomIsolines(service_manager.config.tomtom_api_key, service_manager.logger, service_manager.config.service_params)
if source:
lat = plpy.execute("SELECT ST_Y('%s') AS lat" % source)[0]['lat']
lon = plpy.execute("SELECT ST_X('%s') AS lon" % source)[0]['lon']
origin = Coordinate(lon,lat)
else:
raise Exception('source is NULL')
profile = TRANSPORT_MODE_TO_TOMTOM.get(mode)
resp = tomtom_isolines.calculate_isochrone(origin, data_range, profile)
if resp:
result = []
for isochrone in resp:
result_polygon = coordinates_to_polygon(isochrone.coordinates)
if result_polygon:
service_manager.quota_service.increment_success_service_use()
result.append([source, isochrone.duration, result_polygon])
else:
service_manager.quota_service.increment_empty_service_use()
result.append([source, isochrone.duration, None])
service_manager.quota_service.increment_success_service_use()
service_manager.quota_service.increment_isolines_service_use(len(result))
return result
else:
service_manager.quota_service.increment_empty_service_use()
return []
except BaseException as e:
import sys
service_manager.quota_service.increment_failed_service_use()
service_manager.logger.error('Error trying to get TomTom isochrones', sys.exc_info(), data={"username": username, "orgname": orgname})
raise Exception('Error trying to get TomTom isochrones')
finally:
service_manager.quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER STABLE PARALLEL RESTRICTED;

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -295,10 +295,8 @@ RETURNS SETOF cdb_dataservices_server.isoline AS $$
for isochrone in resp: for isochrone in resp:
result_polygon = coordinates_to_polygon(isochrone.coordinates) result_polygon = coordinates_to_polygon(isochrone.coordinates)
if result_polygon: if result_polygon:
quota_service.increment_success_service_use()
result.append([source, isochrone.duration, result_polygon]) result.append([source, isochrone.duration, result_polygon])
else: else:
quota_service.increment_empty_service_use()
result.append([source, isochrone.duration, None]) result.append([source, isochrone.duration, None])
quota_service.increment_success_service_use() quota_service.increment_success_service_use()
quota_service.increment_isolines_service_use(len(result)) quota_service.increment_isolines_service_use(len(result))
@@ -356,10 +354,8 @@ RETURNS SETOF cdb_dataservices_server.isoline AS $$
for isochrone in resp: for isochrone in resp:
result_polygon = coordinates_to_polygon(isochrone.coordinates) result_polygon = coordinates_to_polygon(isochrone.coordinates)
if result_polygon: if result_polygon:
service_manager.quota_service.increment_success_service_use()
result.append([source, isochrone.duration, result_polygon]) result.append([source, isochrone.duration, result_polygon])
else: else:
service_manager.quota_service.increment_empty_service_use()
result.append([source, isochrone.duration, None]) result.append([source, isochrone.duration, None])
service_manager.quota_service.increment_success_service_use() service_manager.quota_service.increment_success_service_use()
service_manager.quota_service.increment_isolines_service_use(len(result)) service_manager.quota_service.increment_isolines_service_use(len(result))
@@ -416,10 +412,8 @@ RETURNS SETOF cdb_dataservices_server.isoline AS $$
for isochrone in resp: for isochrone in resp:
result_polygon = coordinates_to_polygon(isochrone.coordinates) result_polygon = coordinates_to_polygon(isochrone.coordinates)
if result_polygon: if result_polygon:
service_manager.quota_service.increment_success_service_use()
result.append([source, isochrone.duration, result_polygon]) result.append([source, isochrone.duration, result_polygon])
else: else:
service_manager.quota_service.increment_empty_service_use()
result.append([source, isochrone.duration, None]) result.append([source, isochrone.duration, None])
service_manager.quota_service.increment_success_service_use() service_manager.quota_service.increment_success_service_use()
service_manager.quota_service.increment_isolines_service_use(len(result)) service_manager.quota_service.increment_isolines_service_use(len(result))

View File

@@ -70,7 +70,7 @@ class TomTomGeocoder(Traceable):
return False return False
@qps_retry(qps=5) @qps_retry(qps=5, provider='tomtom')
def geocode(self, searchtext, city=None, state_province=None, def geocode(self, searchtext, city=None, state_province=None,
country=None): country=None):
response = self.geocode_meta(searchtext, city, state_province, country) response = self.geocode_meta(searchtext, city, state_province, country)
@@ -80,7 +80,7 @@ class TomTomGeocoder(Traceable):
else: else:
return response[0] return response[0]
@qps_retry(qps=5) @qps_retry(qps=5, provider='tomtom')
def geocode_meta(self, searchtext, city=None, state_province=None, def geocode_meta(self, searchtext, city=None, state_province=None,
country=None): country=None):
if searchtext: if searchtext:

View File

@@ -89,7 +89,7 @@ class TomTomRouting(Traceable):
point[ENTRY_LONGITUDE])) point[ENTRY_LONGITUDE]))
return geometry return geometry
@qps_retry(qps=5) @qps_retry(qps=5, provider='tomtom')
def directions(self, waypoints, profile=DEFAULT_PROFILE, def directions(self, waypoints, profile=DEFAULT_PROFILE,
date_time=DEFAULT_DEPARTAT): date_time=DEFAULT_DEPARTAT):
self._validate_profile(profile) self._validate_profile(profile)

View File

@@ -6,6 +6,9 @@ from exceptions import TimeoutException
DEFAULT_RETRY_TIMEOUT = 60 DEFAULT_RETRY_TIMEOUT = 60
DEFAULT_QUERIES_PER_SECOND = 10 DEFAULT_QUERIES_PER_SECOND = 10
TOMTOM_403_RATE_LIMIT_HEADER = 'Account Over Queries Per Second Limit'
TOMTOM_DETAIL_HEADER = 'X-Error-Detail-Header'
def qps_retry(original_function=None, **options): def qps_retry(original_function=None, **options):
""" Query Per Second retry decorator """ Query Per Second retry decorator
@@ -46,6 +49,8 @@ class QPSService:
response = getattr(e, 'response', None) response = getattr(e, 'response', None)
if response is not None: if response is not None:
if self._provider is not None and self._provider == 'tomtom' and (response.status_code == 403): if self._provider is not None and self._provider == 'tomtom' and (response.status_code == 403):
if response.headers.get(TOMTOM_DETAIL_HEADER) != TOMTOM_403_RATE_LIMIT_HEADER:
raise e
self.retry(start_time, attempt_number) self.retry(start_time, attempt_number)
elif response.status_code == 429: elif response.status_code == 429:
self.retry(start_time, attempt_number) self.retry(start_time, attempt_number)

View File

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