From 4249283dbb5b308dbd69bf32cda717e7913b1f0c Mon Sep 17 00:00:00 2001 From: chrstnbwnkl Date: Thu, 17 Jun 2021 08:38:41 +0200 Subject: [PATCH 1/8] Changed Router to Client base class --- routingpy/{routers => }/base.py | 159 +--------------------- routingpy/client_default.py | 189 ++++++++++++++++++++++++++ routingpy/routers/__init__.py | 2 +- routingpy/routers/google.py | 14 +- routingpy/routers/graphhopper.py | 14 +- routingpy/routers/heremaps.py | 16 ++- routingpy/routers/mapbox_osrm.py | 16 ++- routingpy/routers/mapbox_valhalla.py | 8 +- routingpy/routers/openrouteservice.py | 16 ++- routingpy/routers/osrm.py | 14 +- routingpy/routers/valhalla.py | 16 ++- tests/test_base.py | 37 ++--- tests/test_graphhopper.py | 2 +- 13 files changed, 280 insertions(+), 223 deletions(-) rename routingpy/{routers => }/base.py (57%) create mode 100644 routingpy/client_default.py diff --git a/routingpy/routers/base.py b/routingpy/base.py similarity index 57% rename from routingpy/routers/base.py rename to routingpy/base.py index 704bf96..a14882f 100644 --- a/routingpy/routers/base.py +++ b/routingpy/base.py @@ -19,18 +19,13 @@ """ from routingpy import exceptions -from routingpy.utils import get_ordinal -from ..__version__ import __version__ +from routingpy.__version__ import __version__ from abc import ABCMeta, abstractmethod -from datetime import datetime from datetime import timedelta -import warnings import requests from urllib.parse import urlencode import json -import random -import time _DEFAULT_USER_AGENT = "routingpy/v{}".format(__version__) _RETRIABLE_STATUSES = set([503]) @@ -90,7 +85,7 @@ class options(object): DEFAULT = type('object', (object, ), {'__repr__': lambda self: 'DEFAULT'})() -class Router(metaclass=ABCMeta): +class BaseClient(metaclass=ABCMeta): """Abstract base class every router inherits from. Authentication is handled in each subclass.""" def __init__( self, @@ -141,8 +136,6 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default False. :type skip_api_error: bool """ - - self._session = requests.Session() self.base_url = base_url self.retry_over_query_limit = retry_over_query_limit if retry_over_query_limit is False else options.default_retry_over_query_limit @@ -171,6 +164,7 @@ def __init__( self._req = None + @abstractmethod def _request( self, url, @@ -181,134 +175,8 @@ def _request( requests_kwargs=None, dry_run=None ): - """Performs HTTP GET/POST with credentials, returning the body as - JSON. - - :param url: URL path for the request. Should begin with a slash. - :type url: string - - :param get_params: HTTP GET parameters. - :type get_params: dict or list of tuples - - :param post_params: HTTP POST parameters. Only specified by calling method. - :type post_params: dict - - :param first_request_time: The time of the first request (None if no - retries have occurred). - :type first_request_time: :class:`datetime.datetime` - - :param retry_counter: The number of this retry, or zero for first attempt. - :type retry_counter: int - - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. - :type requests_kwargs: dict - - :param dry_run: If 'true', only prints URL and parameters. 'true' or 'false'. - :type dry_run: string - - :raises routingpy.exceptions.RouterApiError: when the API returns an error due to faulty configuration. - :raises routingpy.exceptions.RouterServerError: when the API returns a server error. - :raises routingpy.exceptions.RouterError: when anything else happened while requesting. - :raises routingpy.exceptions.JSONParseError: when the JSON response can't be parsed. - :raises routingpy.exceptions.Timeout: when the request timed out. - :raises routingpy.exceptions.TransportError: when something went wrong while trying to - execute a request. - - :returns: raw JSON response. - :rtype: dict - """ - - if not first_request_time: - first_request_time = datetime.now() - - elapsed = datetime.now() - first_request_time - if elapsed > self.retry_timeout: - raise exceptions.Timeout() - - if retry_counter > 0: - # 0.5 * (1.5 ^ i) is an increased sleep time of 1.5x per iteration, - # starting at 0.5s when retry_counter=1. The first retry will occur - # at 1, so subtract that first. - delay_seconds = 1.5**(retry_counter - 1) - - # Jitter this value by 50% and pause. - time.sleep(delay_seconds * (random.random() + 0.5)) - - authed_url = self._generate_auth_url(url, get_params) - - # Default to the client-level self.requests_kwargs, with method-level - # requests_kwargs arg overriding. - requests_kwargs = requests_kwargs or {} - final_requests_kwargs = dict(self.requests_kwargs, **requests_kwargs) - - # Determine GET/POST. - requests_method = self._session.get - if post_params is not None: - requests_method = self._session.post - if final_requests_kwargs['headers']['Content-Type'] == 'application/json': - final_requests_kwargs["json"] = post_params - else: - # Send as x-www-form-urlencoded key-value pair string (e.g. Mapbox API) - final_requests_kwargs['data'] = post_params - - # Only print URL and parameters for dry_run - if dry_run: - print( - "url:\n{}\nParameters:\n{}".format( - self.base_url + authed_url, json.dumps(final_requests_kwargs, indent=2) - ) - ) - return - - try: - response = requests_method(self.base_url + authed_url, **final_requests_kwargs) - self._req = response.request - - except requests.exceptions.Timeout: - raise exceptions.Timeout() - - tried = retry_counter + 1 - - if response.status_code in _RETRIABLE_STATUSES: - # Retry request. - warnings.warn( - 'Server down.\nRetrying for the {}{} time.'.format(tried, get_ordinal(tried)), - UserWarning - ) - - return self._request( - url, get_params, post_params, first_request_time, retry_counter + 1, requests_kwargs - ) - - try: - result = self._get_body(response) - - return result - - except exceptions.RouterApiError: - if self.skip_api_error: - warnings.warn( - "Router {} returned an API error with " - "the following message:\n{}".format(self.__class__.__name__, response.text) - ) - return - - raise - - except exceptions.RetriableRequest as e: - if isinstance(e, exceptions.OverQueryLimit) and not self.retry_over_query_limit: - raise - - warnings.warn( - 'Rate limit exceeded.\nRetrying for the {}{} time.'.format(tried, get_ordinal(tried)), - UserWarning - ) - # Retry request. - return self._request( - url, get_params, post_params, first_request_time, retry_counter + 1, requests_kwargs - ) + """Must be implemented for inheriting client classes.""" + pass @property def req(self): @@ -359,20 +227,3 @@ def _generate_auth_url(path, params): params = params return path + "?" + requests.utils.unquote_unreserved(urlencode(params)) - - @abstractmethod - def directions(self): - """Implement this method for the router's directions endpoint or raise NotImplementedError""" - pass - - @abstractmethod - def isochrones(self): - """Implement this method for the router's isochrones endpoint or raise NotImplementedError""" - pass - - @abstractmethod - def matrix(self): - """Implement this method for the router's matrix endpoint or raise NotImplementedError""" - pass - - # Other endpoints are allowed and encouraged! diff --git a/routingpy/client_default.py b/routingpy/client_default.py new file mode 100644 index 0000000..6ee3be0 --- /dev/null +++ b/routingpy/client_default.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2021 GIS OPS UG +# +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# + +from .base import BaseClient, DEFAULT, _RETRIABLE_STATUSES +from routingpy import exceptions +from routingpy.utils import get_ordinal + +from datetime import datetime +import json +import random +import requests +import time +import warnings + + +class Client(BaseClient): + + def __init__( + self, + base_url, + user_agent=None, + timeout=DEFAULT, + retry_timeout=None, + requests_kwargs=None, + retry_over_query_limit=None, + skip_api_error=None + ): + """ + Initializes the default client + """ + + self._session = requests.Session() + super(Client, self).__init__( + base_url, user_agent=user_agent, timeout=timeout, retry_timeout=retry_timeout, requests_kwargs=requests_kwargs, + retry_over_query_limit=retry_over_query_limit, skip_api_error=skip_api_error + ) + + def _request( + self, + url, + get_params={}, + post_params=None, + first_request_time=None, + retry_counter=0, + requests_kwargs=None, + dry_run=None + ): + """Performs HTTP GET/POST with credentials, returning the body as + JSON. + + :param url: URL path for the request. Should begin with a slash. + :type url: string + + :param get_params: HTTP GET parameters. + :type get_params: dict or list of tuples + + :param post_params: HTTP POST parameters. Only specified by calling method. + :type post_params: dict + + :param first_request_time: The time of the first request (None if no + retries have occurred). + :type first_request_time: :class:`datetime.datetime` + + :param retry_counter: The number of this retry, or zero for first attempt. + :type retry_counter: int + + :param requests_kwargs: Extra keyword arguments for the requests + library, which among other things allow for proxy auth to be + implemented. + :type requests_kwargs: dict + + :param dry_run: If 'true', only prints URL and parameters. 'true' or 'false'. + :type dry_run: string + + :raises routingpy.exceptions.RouterApiError: when the API returns an error due to faulty configuration. + :raises routingpy.exceptions.RouterServerError: when the API returns a server error. + :raises routingpy.exceptions.RouterError: when anything else happened while requesting. + :raises routingpy.exceptions.JSONParseError: when the JSON response can't be parsed. + :raises routingpy.exceptions.Timeout: when the request timed out. + :raises routingpy.exceptions.TransportError: when something went wrong while trying to + execute a request. + + :returns: raw JSON response. + :rtype: dict + """ + + if not first_request_time: + first_request_time = datetime.now() + + elapsed = datetime.now() - first_request_time + if elapsed > self.retry_timeout: + raise exceptions.Timeout() + + if retry_counter > 0: + # 0.5 * (1.5 ^ i) is an increased sleep time of 1.5x per iteration, + # starting at 0.5s when retry_counter=1. The first retry will occur + # at 1, so subtract that first. + delay_seconds = 1.5**(retry_counter - 1) + + # Jitter this value by 50% and pause. + time.sleep(delay_seconds * (random.random() + 0.5)) + + authed_url = self._generate_auth_url(url, get_params) + + # Default to the client-level self.requests_kwargs, with method-level + # requests_kwargs arg overriding. + requests_kwargs = requests_kwargs or {} + final_requests_kwargs = dict(self.requests_kwargs, **requests_kwargs) + + # Determine GET/POST. + requests_method = self._session.get + if post_params is not None: + requests_method = self._session.post + if final_requests_kwargs['headers']['Content-Type'] == 'application/json': + final_requests_kwargs["json"] = post_params + else: + # Send as x-www-form-urlencoded key-value pair string (e.g. Mapbox API) + final_requests_kwargs['data'] = post_params + + # Only print URL and parameters for dry_run + if dry_run: + print( + "url:\n{}\nParameters:\n{}".format( + self.base_url + authed_url, json.dumps(final_requests_kwargs, indent=2) + ) + ) + return + + try: + response = requests_method(self.base_url + authed_url, **final_requests_kwargs) + self._req = response.request + + except requests.exceptions.Timeout: + raise exceptions.Timeout() + + tried = retry_counter + 1 + + if response.status_code in _RETRIABLE_STATUSES: + # Retry request. + warnings.warn( + 'Server down.\nRetrying for the {}{} time.'.format(tried, get_ordinal(tried)), + UserWarning + ) + + return self._request( + url, get_params, post_params, first_request_time, retry_counter + 1, requests_kwargs + ) + + try: + result = self._get_body(response) + + return result + + except exceptions.RouterApiError: + if self.skip_api_error: + warnings.warn( + "Router {} returned an API error with " + "the following message:\n{}".format(self.__class__.__name__, response.text) + ) + return + + raise + + except exceptions.RetriableRequest as e: + if isinstance(e, exceptions.OverQueryLimit) and not self.retry_over_query_limit: + raise + + warnings.warn( + 'Rate limit exceeded.\nRetrying for the {}{} time.'.format(tried, get_ordinal(tried)), + UserWarning + ) + # Retry request. + return self._request( + url, get_params, post_params, first_request_time, retry_counter + 1, requests_kwargs + ) diff --git a/routingpy/routers/__init__.py b/routingpy/routers/__init__.py index 96e8902..3ff6d31 100644 --- a/routingpy/routers/__init__.py +++ b/routingpy/routers/__init__.py @@ -13,7 +13,7 @@ .. _`contribution guidelines`: https://github.com/gis-ops/routing-py/blob/master/CONTRIBUTING.md .. _here: https://github.com/gis-ops/routing-py#api """ -from .base import options # noqa: F401 +from routingpy.base import options # noqa: F401 from routingpy.exceptions import RouterNotFound from .openrouteservice import ORS diff --git a/routingpy/routers/google.py b/routingpy/routers/google.py index 5f302de..bc59566 100644 --- a/routingpy/routers/google.py +++ b/routingpy/routers/google.py @@ -15,7 +15,8 @@ # the License. # -from .base import Router, DEFAULT +from routingpy.base import DEFAULT +from routingpy.client_default import Client from routingpy import convert, utils from routingpy.direction import Directions, Direction from routingpy.matrix import Matrix @@ -23,7 +24,7 @@ from operator import itemgetter -class Google(Router): +class Google: """Performs requests to the Google API services.""" _base_url = "https://maps.googleapis.com/maps/api" @@ -36,7 +37,8 @@ def __init__( retry_timeout=None, requests_kwargs={}, retry_over_query_limit=True, - skip_api_error=None + skip_api_error=None, + client=Client ): """ Initializes a Google client. @@ -85,7 +87,7 @@ def __init__( self.key = api_key - super(Google, self).__init__( + self.client = client( self._base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, skip_api_error ) @@ -281,7 +283,7 @@ def directions( # noqa: C901 params['transit_routing_preference'] = transit_routing_preference return self._parse_direction_json( - self._request('/directions/json', get_params=params, dry_run=dry_run), alternatives + self.client._request('/directions/json', get_params=params, dry_run=dry_run), alternatives ) @staticmethod @@ -462,7 +464,7 @@ def matrix( # noqa: C901 params['transit_routing_preference'] = transit_routing_preference return self._parse_matrix_json( - self._request('/distancematrix/json', get_params=params, dry_run=dry_run) + self.client._request('/distancematrix/json', get_params=params, dry_run=dry_run) ) @staticmethod diff --git a/routingpy/routers/graphhopper.py b/routingpy/routers/graphhopper.py index f10ff69..abbf959 100644 --- a/routingpy/routers/graphhopper.py +++ b/routingpy/routers/graphhopper.py @@ -17,7 +17,8 @@ from typing import List, Tuple # noqa: F401 -from .base import Router, DEFAULT +from routingpy.base import DEFAULT +from routingpy.client_default import Client from routingpy import convert from routingpy import utils from routingpy.direction import Direction, Directions @@ -25,7 +26,7 @@ from routingpy.matrix import Matrix -class Graphhopper(Router): +class Graphhopper: """Performs requests to the Graphhopper API services.""" _DEFAULT_BASE_URL = "https://graphhopper.com/api/1" @@ -39,7 +40,8 @@ def __init__( retry_timeout=None, requests_kwargs={}, retry_over_query_limit=False, - skip_api_error=None + skip_api_error=None, + client=Client ): """ Initializes an graphhopper client. @@ -94,7 +96,7 @@ def __init__( raise KeyError("API key must be specified.") self.key = api_key - super(Graphhopper, self).__init__( + self.client = client( base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, skip_api_error ) @@ -493,7 +495,7 @@ def isochrones( params.append(('debug', convert._convert_bool(debug))) return self._parse_isochrone_json( - self._request("/isochrone", get_params=params, dry_run=dry_run), type, intervals[0], buckets, + self.client._request("/isochrone", get_params=params, dry_run=dry_run), type, intervals[0], buckets, center ) @@ -612,7 +614,7 @@ def matrix( if debug is not None: params.append(('debug', convert._convert_bool(debug))) - return self._parse_matrix_json(self._request('/matrix', get_params=params, dry_run=dry_run), ) + return self._parse_matrix_json(self.client._request('/matrix', get_params=params, dry_run=dry_run), ) @staticmethod def _parse_matrix_json(response): diff --git a/routingpy/routers/heremaps.py b/routingpy/routers/heremaps.py index 7ed60a3..62c3c08 100644 --- a/routingpy/routers/heremaps.py +++ b/routingpy/routers/heremaps.py @@ -15,7 +15,8 @@ # the License. # -from .base import Router, DEFAULT +from routingpy.base import DEFAULT +from routingpy.client_default import Client from routingpy import convert from routingpy.direction import Direction, Directions from routingpy.isochrone import Isochrones, Isochrone @@ -24,7 +25,7 @@ from operator import itemgetter -class HereMaps(Router): +class HereMaps: """Performs requests to the HERE Maps API services.""" _DEFAULT_BASE_URL = 'https://route.api.here.com/routing/7.2' @@ -40,7 +41,8 @@ def __init__( requests_kwargs=None, retry_over_query_limit=False, skip_api_error=None, - api_key=None + api_key=None, + client=Client ): """ Initializes a HERE Maps client. @@ -108,7 +110,7 @@ def __init__( self.base_url = self._DEFAULT_BASE_URL self.auth = {"app_id": self.app_id, "app_code": self.app_code} - super(HereMaps, self).__init__( + self.client = client( self.base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, skip_api_error ) @@ -764,7 +766,7 @@ def directions( # noqa: C901 params["speedProfile"] = speed_profile return self._parse_direction_json( - self._request( + self.client._request( convert._delimit_list(["/calculateroute", format], '.'), get_params=params, dry_run=dry_run @@ -1057,7 +1059,7 @@ def isochrones( # noqa: C901 params["speedProfile"] = speed_profile return self._parse_isochrone_json( - self._request( + self.client._request( convert._delimit_list(["/calculateisoline", format], '.'), get_params=params, dry_run=dry_run @@ -1337,7 +1339,7 @@ def matrix( # noqa: C901 params["speedProfile"] = speed_profile return self._parse_matrix_json( - self._request( + self.client._request( convert._delimit_list(["/calculatematrix", format], '.'), get_params=params, dry_run=dry_run diff --git a/routingpy/routers/mapbox_osrm.py b/routingpy/routers/mapbox_osrm.py index 1d9d828..4d6c2a9 100644 --- a/routingpy/routers/mapbox_osrm.py +++ b/routingpy/routers/mapbox_osrm.py @@ -18,14 +18,15 @@ Core client functionality, common across all API requests. """ -from .base import Router, DEFAULT +from routingpy.base import DEFAULT +from routingpy.client_default import Client from routingpy import convert, utils from routingpy.direction import Direction, Directions from routingpy.isochrone import Isochrone, Isochrones from routingpy.matrix import Matrix -class MapboxOSRM(Router): +class MapboxOSRM: """Performs requests to the OSRM API services.""" _base_url = 'https://api.mapbox.com' @@ -38,7 +39,8 @@ def __init__( retry_timeout=None, requests_kwargs=None, retry_over_query_limit=False, - skip_api_error=None + skip_api_error=None, + client=Client ): """ Initializes a Mapbox OSRM client. @@ -87,7 +89,7 @@ def __init__( self.api_key = api_key - super(MapboxOSRM, self).__init__( + self.client = client( self._base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, skip_api_error ) @@ -289,7 +291,7 @@ def directions( # noqa: C901 get_params = {'access_token': self.api_key} if self.api_key else {} return self._parse_direction_json( - self._request( + self.client._request( "/directions/v5/mapbox/" + profile, get_params=get_params, post_params=params, @@ -416,7 +418,7 @@ def isochrones( params['generalize'] = generalize return self._parse_isochrone_json( - self._request( + self.client._request( "/isochrone/v1/" + profile + '/' + locations_string, get_params=params, dry_run=dry_run ), intervals, locations ) @@ -502,7 +504,7 @@ def matrix( params['fallback_speed'] = str(fallback_speed) return self._parse_matrix_json( - self._request( + self.client._request( "/directions-matrix/v1/mapbox/" + profile + '/' + coords, get_params=params, dry_run=dry_run diff --git a/routingpy/routers/mapbox_valhalla.py b/routingpy/routers/mapbox_valhalla.py index d189d66..6c23f3e 100644 --- a/routingpy/routers/mapbox_valhalla.py +++ b/routingpy/routers/mapbox_valhalla.py @@ -15,7 +15,8 @@ # the License. from .valhalla import Valhalla -from .base import DEFAULT +from routingpy.base import DEFAULT +from routingpy.client_default import Client class MapboxValhalla(Valhalla): @@ -31,7 +32,8 @@ def __init__( retry_timeout=None, requests_kwargs=None, retry_over_query_limit=False, - skip_api_error=None + skip_api_error=None, + client=Client ): """ Initializes a Valhalla client. @@ -80,5 +82,5 @@ def __init__( super(MapboxValhalla, self).__init__( self._base_url, api_key, user_agent, timeout, retry_timeout, requests_kwargs, - retry_over_query_limit, skip_api_error + retry_over_query_limit, skip_api_error, client=client ) diff --git a/routingpy/routers/openrouteservice.py b/routingpy/routers/openrouteservice.py index 8b1f057..3b9589c 100644 --- a/routingpy/routers/openrouteservice.py +++ b/routingpy/routers/openrouteservice.py @@ -15,14 +15,15 @@ # the License. # -from .base import Router, DEFAULT +from routingpy.base import DEFAULT +from routingpy.client_default import Client from routingpy import utils from routingpy.direction import Direction, Directions from routingpy.isochrone import Isochrone, Isochrones from routingpy.matrix import Matrix -class ORS(Router): +class ORS: """Performs requests to the ORS API services.""" _DEFAULT_BASE_URL = 'https://api.openrouteservice.org' @@ -36,7 +37,8 @@ def __init__( retry_timeout=None, requests_kwargs=None, retry_over_query_limit=False, - skip_api_error=None + skip_api_error=None, + client=Client ): """ Initializes an openrouteservice client. @@ -95,7 +97,7 @@ def __init__( headers.update({'Authorization': api_key}) requests_kwargs.update({'headers': headers}) - super(ORS, self).__init__( + self.client = client( base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, skip_api_error ) @@ -305,7 +307,7 @@ def directions( # noqa: C901 params['options'] = options return self._parse_direction_json( - self._request( + self.client._request( "/v2/directions/" + profile + '/' + format, get_params={}, post_params=params, @@ -453,7 +455,7 @@ def isochrones( params["intersections"] = intersections return self._parse_isochrone_json( - self._request( + self.client._request( "/v2/isochrones/" + profile + '/geojson', get_params={}, post_params=params, @@ -547,7 +549,7 @@ def matrix( params["units"] = units return self._parse_matrix_json( - self._request( + self.client._request( "/v2/matrix/" + profile + '/json', get_params={}, post_params=params, dry_run=dry_run ) ) diff --git a/routingpy/routers/osrm.py b/routingpy/routers/osrm.py index 43effe1..32b28a6 100644 --- a/routingpy/routers/osrm.py +++ b/routingpy/routers/osrm.py @@ -17,13 +17,14 @@ from typing import List # noqa: F401 -from .base import Router, DEFAULT +from routingpy.base import DEFAULT +from routingpy.client_default import Client from routingpy import convert, utils from routingpy.direction import Directions, Direction from routingpy.matrix import Matrix -class OSRM(Router): +class OSRM: """Performs requests to the OSRM API services.""" _DEFAULT_BASE_URL = 'https://router.project-osrm.org' @@ -36,7 +37,8 @@ def __init__( retry_timeout=None, requests_kwargs=None, retry_over_query_limit=False, - skip_api_error=None + skip_api_error=None, + client=Client ): """ Initializes an OSRM client. @@ -84,7 +86,7 @@ def __init__( :type skip_api_error: bool """ - super(OSRM, self).__init__( + self.client = client( base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, skip_api_error ) @@ -194,7 +196,7 @@ def directions( params["overview"] = convert._convert_bool(overview) return self._parse_direction_json( - self._request("/route/v1/" + profile + '/' + coords, get_params=params, dry_run=dry_run), + self.client._request("/route/v1/" + profile + '/' + coords, get_params=params, dry_run=dry_run), alternatives, geometries ) @@ -321,7 +323,7 @@ def matrix( params['annotations'] = convert._delimit_list(annotations) return self._parse_matrix_json( - self._request("/table/v1/" + profile + '/' + coords, get_params=params, dry_run=dry_run) + self.client._request("/table/v1/" + profile + '/' + coords, get_params=params, dry_run=dry_run) ) @staticmethod diff --git a/routingpy/routers/valhalla.py b/routingpy/routers/valhalla.py index fb0cb00..318d0ae 100644 --- a/routingpy/routers/valhalla.py +++ b/routingpy/routers/valhalla.py @@ -20,7 +20,8 @@ from typing import List, Union # noqa: F401 -from .base import Router, DEFAULT +from routingpy.base import DEFAULT +from routingpy.client_default import Client from routingpy import utils from routingpy.direction import Direction from routingpy.isochrone import Isochrone, Isochrones @@ -29,7 +30,7 @@ from operator import itemgetter -class Valhalla(Router): +class Valhalla: """Performs requests to a Valhalla instance.""" def __init__( self, @@ -40,7 +41,8 @@ def __init__( retry_timeout=None, requests_kwargs=None, retry_over_query_limit=False, - skip_api_error=None + skip_api_error=None, + client=Client ): """ Initializes a Valhalla client. @@ -93,7 +95,7 @@ def __init__( self.api_key = api_key - super(Valhalla, self).__init__( + self.client = client( base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, skip_api_error ) @@ -229,7 +231,7 @@ def directions( get_params = {'access_token': self.api_key} if self.api_key else {} return self._parse_direction_json( - self._request("/route", get_params=get_params, post_params=params, dry_run=dry_run), units + self.client._request("/route", get_params=get_params, post_params=params, dry_run=dry_run), units ) @staticmethod @@ -408,7 +410,7 @@ def isochrones( # noqa: C901 get_params = {'access_token': self.api_key} if self.api_key else {} return self._parse_isochrone_json( - self._request("/isochrone", get_params=get_params, post_params=params, dry_run=dry_run), + self.client._request("/isochrone", get_params=get_params, post_params=params, dry_run=dry_run), intervals, locations ) @@ -543,7 +545,7 @@ def matrix( get_params = {'access_token': self.api_key} if self.api_key else {} return self._parse_matrix_json( - self._request( + self.client._request( '/sources_to_targets', get_params=get_params, post_params=params, dry_run=dry_run ), units ) diff --git a/tests/test_base.py b/tests/test_base.py index 9eea163..d26ce08 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -21,13 +21,14 @@ import time import routingpy -from routingpy.routers import options, base +from routingpy.routers import options +from routingpy import client_default import tests as _test -class RouterMock(base.Router): +class ClientMock(client_default.Client): def __init__(self, *args, **kwargs): - super(RouterMock, self).__init__(*args, **kwargs) + super(ClientMock, self).__init__(*args, **kwargs) def directions(self, *args, **kwargs): return self._request(*args, **kwargs) @@ -41,7 +42,7 @@ def matrix(self): class BaseTest(_test.TestCase): def setUp(self): - self.router = RouterMock("https://httpbin.org/") + self.client = ClientMock("https://httpbin.org/") self.params = {'c': 'd', 'a': 'b', '1': '2'} def test_router_by_name(self): @@ -57,7 +58,7 @@ def test_options(self): options.default_retry_timeout = 10 options.default_retry_over_query_limit = False options.default_proxies = {'https': '192.103.10.102'} - new_router = RouterMock('https://foo.bar') + new_client = ClientMock('https://foo.bar') req_kwargs = { 'timeout': options.default_timeout, 'headers': { @@ -66,11 +67,11 @@ def test_options(self): }, 'proxies': options.default_proxies } - self.assertEqual(req_kwargs, new_router.requests_kwargs) - self.assertEqual(new_router.retry_over_query_limit, options.default_retry_over_query_limit) + self.assertEqual(req_kwargs, new_client.requests_kwargs) + self.assertEqual(new_client.retry_over_query_limit, options.default_retry_over_query_limit) def test_urlencode(self): - encoded_params = self.router._generate_auth_url('directions', self.params) + encoded_params = self.client._generate_auth_url('directions', self.params) self.assertEqual("directions?1=2&a=b&c=d", encoded_params) @responses.activate @@ -84,12 +85,12 @@ def test_skip_api_error(self): content_type='application/json' ) - client = RouterMock(base_url="https://httpbin.org", skip_api_error=False) + client = ClientMock(base_url="https://httpbin.org", skip_api_error=False) print(client.skip_api_error) with self.assertRaises(routingpy.exceptions.RouterApiError): client.directions(url='/post', post_params=self.params) - client = RouterMock(base_url="https://httpbin.org", skip_api_error=True) + client = ClientMock(base_url="https://httpbin.org", skip_api_error=True) client.directions(url='/post', post_params=self.params) self.assertEqual(responses.calls[1].response.json(), query) @@ -104,7 +105,7 @@ def test_retry_timeout(self): content_type='application/json' ) - client = RouterMock(base_url="https://httpbin.org", retry_over_query_limit=True, retry_timeout=3) + client = ClientMock(base_url="https://httpbin.org", retry_over_query_limit=True, retry_timeout=3) with self.assertRaises(routingpy.exceptions.OverQueryLimit): client.directions(url='/post', post_params=query) @@ -120,7 +121,7 @@ def test_raise_over_query_limit(self): ) with self.assertRaises(routingpy.exceptions.OverQueryLimit): - client = RouterMock(base_url="https://httpbin.org", retry_over_query_limit=False) + client = ClientMock(base_url="https://httpbin.org", retry_over_query_limit=False) client.directions(url='/post', post_params=query) @responses.activate @@ -137,7 +138,7 @@ def test_raise_timeout_retriable_requests(self): content_type='application/json' ) - client = RouterMock(base_url="https://httpbin.org", retry_timeout=retry_timeout) + client = ClientMock(base_url="https://httpbin.org", retry_timeout=retry_timeout) start = time.time() with self.assertRaises(routingpy.exceptions.Timeout): @@ -157,7 +158,7 @@ def test_dry_run(self): content_type='application/json' ) - self.router.directions(get_params={'format_out': 'geojson'}, url='directions/', dry_run='true') + self.client.directions(get_params={'format_out': 'geojson'}, url='directions/', dry_run='true') self.assertEqual(0, len(responses.calls)) @@ -167,7 +168,7 @@ def test_headers(self): timeout = {'holaDieWaldFee': 600} headers = {'headers': {'X-Rate-Limit': '50'}} - client = RouterMock("https://httpbin.org", requests_kwargs=dict(timeout, **headers)) + client = ClientMock("https://httpbin.org", requests_kwargs=dict(timeout, **headers)) self.assertDictContainsSubset(timeout, client.requests_kwargs) self.assertDictContainsSubset(headers['headers'], client.requests_kwargs['headers']) @@ -184,7 +185,7 @@ def test_req_property(self): content_type='application/json' ) - self.router.directions(url='routes', get_params={'a': 'b'}) + self.client.directions(url='routes', get_params={'a': 'b'}) - assert isinstance(self.router.req, requests.PreparedRequest) - self.assertEqual('https://httpbin.org/routes?a=b', self.router.req.url) + assert isinstance(self.client.req, requests.PreparedRequest) + self.assertEqual('https://httpbin.org/routes?a=b', self.client.req.url) diff --git a/tests/test_graphhopper.py b/tests/test_graphhopper.py index 76c5df3..c7baaef 100644 --- a/tests/test_graphhopper.py +++ b/tests/test_graphhopper.py @@ -206,4 +206,4 @@ def test_index_destinations_matrix(self): query = deepcopy(ENDPOINTS_QUERIES[self.name]['matrix']) query['destinations'] = [100] - self.assertRaises(IndexError, lambda: self.client.matrix(**query)) + self.assertRaises(IndexError, lambda: self.client.matrix(**query)) \ No newline at end of file From 2240be085bab0b668aec7c1bb4ab3b9cc422073f Mon Sep 17 00:00:00 2001 From: chrstnbwnkl Date: Thu, 17 Jun 2021 14:09:10 +0200 Subject: [PATCH 2/8] added client to graphhopper class --- routingpy/routers/graphhopper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routingpy/routers/graphhopper.py b/routingpy/routers/graphhopper.py index abbf959..cd2a467 100644 --- a/routingpy/routers/graphhopper.py +++ b/routingpy/routers/graphhopper.py @@ -377,7 +377,7 @@ def directions( # noqa: C901 ) return self._parse_directions_json( - self._request('/route', get_params=params, dry_run=dry_run), algorithm, elevation + self.client._request('/route', get_params=params, dry_run=dry_run), algorithm, elevation ) @staticmethod From 27a8f24daa086d4f721b2e7d2c5aa718db54a24b Mon Sep 17 00:00:00 2001 From: chrstnbwnkl Date: Fri, 18 Jun 2021 11:54:16 +0200 Subject: [PATCH 3/8] updated docstrings --- routingpy/base.py | 2 +- routingpy/client_default.py | 40 +++++++++++++++++++++++++-- routingpy/routers/google.py | 5 +++- routingpy/routers/graphhopper.py | 5 +++- routingpy/routers/heremaps.py | 5 +++- routingpy/routers/mapbox_osrm.py | 5 +++- routingpy/routers/mapbox_valhalla.py | 5 +++- routingpy/routers/openrouteservice.py | 5 +++- routingpy/routers/osrm.py | 5 +++- routingpy/routers/valhalla.py | 5 +++- 10 files changed, 71 insertions(+), 11 deletions(-) diff --git a/routingpy/base.py b/routingpy/base.py index a14882f..64faf8c 100644 --- a/routingpy/base.py +++ b/routingpy/base.py @@ -86,7 +86,7 @@ class options(object): class BaseClient(metaclass=ABCMeta): - """Abstract base class every router inherits from. Authentication is handled in each subclass.""" + """Abstract base class every client inherits from. Authentication is handled in each subclass.""" def __init__( self, base_url, diff --git a/routingpy/client_default.py b/routingpy/client_default.py index 6ee3be0..438a7fe 100644 --- a/routingpy/client_default.py +++ b/routingpy/client_default.py @@ -28,7 +28,7 @@ class Client(BaseClient): - + """Default client class for requests handling. Uses the requests package.""" def __init__( self, base_url, @@ -40,7 +40,43 @@ def __init__( skip_api_error=None ): """ - Initializes the default client + :param base_url: The base URL for the request. All routers must provide a default. + Should not have a trailing slash. + :type base_url: string + + :param user_agent: User-Agent to send with the requests to routing API. + Overrides ``options.default_user_agent``. + :type user_agent: string + + :param timeout: Combined connect and read timeout for HTTP requests, in + seconds. Specify "None" for no timeout. + :type timeout: int + + :param retry_timeout: Timeout across multiple retriable requests, in + seconds. + :type retry_timeout: int + + :param requests_kwargs: Extra keyword arguments for the requests + library, which among other things allow for proxy auth to be + implemented. + + Example: + + >>> from routingpy.routers import ORS + >>> router = ORS(my_key, requests_kwargs={'proxies': {'https': '129.125.12.0'}}) + >>> print(router.client.proxies) + {'https': '129.125.12.0'} + + :type requests_kwargs: dict + + :param retry_over_query_limit: If True, client will not raise an exception + on HTTP 429, but instead jitter a sleeping timer to pause between + requests until HTTP 200 or retry_timeout is reached. + :type retry_over_query_limit: bool + + :param skip_api_error: Continue with batch processing if a :class:`routingpy.exceptions.RouterApiError` is + encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default False. + :type skip_api_error: bool """ self._session = requests.Session() diff --git a/routingpy/routers/google.py b/routingpy/routers/google.py index bc59566..23dd055 100644 --- a/routingpy/routers/google.py +++ b/routingpy/routers/google.py @@ -69,7 +69,7 @@ def __init__( >>> router = Google(my_key, requests_kwargs={ >>> 'proxies': {'https': '129.125.12.0'} >>> }) - >>> print(router.proxies) + >>> print(router.client.proxies) {'https': '129.125.12.0'} :type requests_kwargs: dict @@ -83,6 +83,9 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default :attr:`routingpy.routers.options.default_skip_api_error`. :type skip_api_error: bool + + :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` + :type client: abc.ABCMeta """ self.key = api_key diff --git a/routingpy/routers/graphhopper.py b/routingpy/routers/graphhopper.py index cd2a467..b18b359 100644 --- a/routingpy/routers/graphhopper.py +++ b/routingpy/routers/graphhopper.py @@ -76,7 +76,7 @@ def __init__( >>> router = Graphhopper(my_key, requests_kwargs={ >>> 'proxies': {'https': '129.125.12.0'} >>> }) - >>> print(router.proxies) + >>> print(router.client.proxies) {'https': '129.125.12.0'} :type requests_kwargs: dict @@ -90,6 +90,9 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default :attr:`routingpy.routers.options.default_skip_api_error`. :type skip_api_error: bool + + :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` + :type client: abc.ABCMeta """ if base_url == self._DEFAULT_BASE_URL and api_key is None: diff --git a/routingpy/routers/heremaps.py b/routingpy/routers/heremaps.py index 62c3c08..4da5182 100644 --- a/routingpy/routers/heremaps.py +++ b/routingpy/routers/heremaps.py @@ -76,7 +76,7 @@ def __init__( >>> router = HereMaps(my_key, requests_kwargs={ >>> 'proxies': {'https': '129.125.12.0'} >>> }) - >>> print(router.proxies) + >>> print(router.client.proxies) {'https': '129.125.12.0'} :type requests_kwargs: dict @@ -90,6 +90,9 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default :attr:`routingpy.routers.options.default_skip_api_error`. :type skip_api_error: bool + + :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` + :type client: abc.ABCMeta """ if app_id is None and app_code is None and api_key is None: diff --git a/routingpy/routers/mapbox_osrm.py b/routingpy/routers/mapbox_osrm.py index 4d6c2a9..4c75ec4 100644 --- a/routingpy/routers/mapbox_osrm.py +++ b/routingpy/routers/mapbox_osrm.py @@ -71,7 +71,7 @@ def __init__( >>> router = MapboxOSRM(my_key, requests_kwargs={ >>> 'proxies': {'https': '129.125.12.0'} >>> }) - >>> print(router.proxies) + >>> print(router.client.proxies) {'https': '129.125.12.0'} :type requests_kwargs: dict @@ -85,6 +85,9 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default :attr:`routingpy.routers.options.default_skip_api_error`. :type skip_api_error: bool + + :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` + :type client: abc.ABCMeta """ self.api_key = api_key diff --git a/routingpy/routers/mapbox_valhalla.py b/routingpy/routers/mapbox_valhalla.py index 6c23f3e..6b696ef 100644 --- a/routingpy/routers/mapbox_valhalla.py +++ b/routingpy/routers/mapbox_valhalla.py @@ -64,7 +64,7 @@ def __init__( >>> router = MapboxValhalla(my_key, requests_kwargs={ >>> 'proxies': {'https': '129.125.12.0'} >>> }) - >>> print(router.proxies) + >>> print(router.client.proxies) {'https': '129.125.12.0'} :type requests_kwargs: dict @@ -78,6 +78,9 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default :attr:`routingpy.routers.options.default_skip_api_error`. :type skip_api_error: bool + + :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` + :type client: abc.ABCMeta """ super(MapboxValhalla, self).__init__( diff --git a/routingpy/routers/openrouteservice.py b/routingpy/routers/openrouteservice.py index 3b9589c..0eb3e33 100644 --- a/routingpy/routers/openrouteservice.py +++ b/routingpy/routers/openrouteservice.py @@ -73,7 +73,7 @@ def __init__( >>> router = ORS(my_key, requests_kwargs={ >>> 'proxies': {'https': '129.125.12.0'} >>> }) - >>> print(router.proxies) + >>> print(router.client.proxies) {'https': '129.125.12.0'} :type requests_kwargs: dict @@ -87,6 +87,9 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default :attr:`routingpy.routers.options.default_skip_api_error`. :type skip_api_error: bool + + :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` + :type client: abc.ABCMeta """ if base_url == self._DEFAULT_BASE_URL and api_key is None: diff --git a/routingpy/routers/osrm.py b/routingpy/routers/osrm.py index 32b28a6..ece245a 100644 --- a/routingpy/routers/osrm.py +++ b/routingpy/routers/osrm.py @@ -70,7 +70,7 @@ def __init__( >>> router = OSRM(my_key, requests_kwargs={ >>> 'proxies': {'https': '129.125.12.0'} >>> }) - >>> print(router.proxies) + >>> print(router.client.proxies) {'https': '129.125.12.0'} :type requests_kwargs: dict @@ -84,6 +84,9 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default :attr:`routingpy.routers.options.default_skip_api_error`. :type skip_api_error: bool + + :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` + :type client: abc.ABCMeta """ self.client = client( diff --git a/routingpy/routers/valhalla.py b/routingpy/routers/valhalla.py index 318d0ae..7f4a31b 100644 --- a/routingpy/routers/valhalla.py +++ b/routingpy/routers/valhalla.py @@ -77,7 +77,7 @@ def __init__( >>> router = Valhalla(my_key, requests_kwargs={ >>> 'proxies': {'https': '129.125.12.0'} >>> }) - >>> print(router.proxies) + >>> print(router.client.proxies) {'https': '129.125.12.0'} :type requests_kwargs: dict @@ -91,6 +91,9 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default :attr:`routingpy.routers.options.default_skip_api_error`. :type skip_api_error: bool + + :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` + :type client: abc.ABCMeta """ self.api_key = api_key From 910aea678145a23878743724cf49fd84dde0b6e7 Mon Sep 17 00:00:00 2001 From: chrstnbwnkl Date: Mon, 21 Jun 2021 10:19:15 +0200 Subject: [PATCH 4/8] moved request_args to default client --- routingpy/base.py | 43 +++++++++---------------------------- routingpy/client_default.py | 17 +++++++++++++-- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/routingpy/base.py b/routingpy/base.py index 64faf8c..e3bb471 100644 --- a/routingpy/base.py +++ b/routingpy/base.py @@ -82,20 +82,20 @@ class options(object): # To avoid trouble when respecting timeout for individual routers (i.e. can't be None, since that's no timeout) -DEFAULT = type('object', (object, ), {'__repr__': lambda self: 'DEFAULT'})() +DEFAULT = type('object', (object,), {'__repr__': lambda self: 'DEFAULT'})() class BaseClient(metaclass=ABCMeta): """Abstract base class every client inherits from. Authentication is handled in each subclass.""" + def __init__( - self, - base_url, - user_agent=None, - timeout=DEFAULT, - retry_timeout=None, - requests_kwargs=None, - retry_over_query_limit=None, - skip_api_error=None + self, + base_url, + user_agent=None, + timeout=DEFAULT, + retry_timeout=None, + retry_over_query_limit=None, + skip_api_error=None ): """ :param base_url: The base URL for the request. All routers must provide a default. @@ -125,8 +125,6 @@ def __init__( >>> print(router.proxies) {'https': '129.125.12.0'} - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. @@ -143,38 +141,17 @@ def __init__( self.skip_api_error = skip_api_error or options.default_skip_api_error - self.requests_kwargs = requests_kwargs or {} self.headers = { "User-Agent": user_agent or options.default_user_agent, 'Content-Type': 'application/json' } - try: - self.headers.update(self.requests_kwargs['headers']) - except KeyError: - pass - self.requests_kwargs['headers'] = self.headers - self.timeout = timeout if timeout != DEFAULT else options.default_timeout - self.requests_kwargs['timeout'] = self.timeout - - self.proxies = self.requests_kwargs.get('proxies') or options.default_proxies - if self.proxies: - self.requests_kwargs['proxies'] = self.proxies self._req = None @abstractmethod - def _request( - self, - url, - get_params={}, - post_params=None, - first_request_time=None, - retry_counter=0, - requests_kwargs=None, - dry_run=None - ): + def _request(self): """Must be implemented for inheriting client classes.""" pass diff --git a/routingpy/client_default.py b/routingpy/client_default.py index 438a7fe..d3daa14 100644 --- a/routingpy/client_default.py +++ b/routingpy/client_default.py @@ -15,7 +15,7 @@ # the License. # -from .base import BaseClient, DEFAULT, _RETRIABLE_STATUSES +from .base import BaseClient, DEFAULT, _RETRIABLE_STATUSES, options from routingpy import exceptions from routingpy.utils import get_ordinal @@ -81,10 +81,23 @@ def __init__( self._session = requests.Session() super(Client, self).__init__( - base_url, user_agent=user_agent, timeout=timeout, retry_timeout=retry_timeout, requests_kwargs=requests_kwargs, + base_url, user_agent=user_agent, timeout=timeout, retry_timeout=retry_timeout, retry_over_query_limit=retry_over_query_limit, skip_api_error=skip_api_error ) + self.requests_kwargs = requests_kwargs or {} + try: + self.headers.update(self.requests_kwargs['headers']) + except KeyError: + pass + + self.requests_kwargs['headers'] = self.headers + self.requests_kwargs['timeout'] = self.timeout + + self.proxies = self.requests_kwargs.get('proxies') or options.default_proxies + if self.proxies: + self.requests_kwargs['proxies'] = self.proxies + def _request( self, url, From cb758ad96574cf072527e0a40d72c1b8c40373f6 Mon Sep 17 00:00:00 2001 From: chrstnbwnkl Date: Mon, 21 Jun 2021 15:41:51 +0200 Subject: [PATCH 5/8] code formatting --- routingpy/{base.py => client_base.py} | 59 +-- routingpy/client_default.py | 86 +++-- routingpy/routers/__init__.py | 2 +- routingpy/routers/google.py | 128 +++---- routingpy/routers/graphhopper.py | 111 +++--- routingpy/routers/heremaps.py | 506 ++++++++++++++------------ routingpy/routers/mapbox_osrm.py | 147 ++++---- routingpy/routers/mapbox_valhalla.py | 19 +- routingpy/routers/openrouteservice.py | 126 ++++--- routingpy/routers/osrm.py | 74 ++-- routingpy/routers/valhalla.py | 165 +++++---- tests/test_base.py | 71 ++-- tests/test_graphhopper.py | 2 +- 13 files changed, 797 insertions(+), 699 deletions(-) rename routingpy/{base.py => client_base.py} (81%) diff --git a/routingpy/base.py b/routingpy/client_base.py similarity index 81% rename from routingpy/base.py rename to routingpy/client_base.py index e3bb471..00c0326 100644 --- a/routingpy/base.py +++ b/routingpy/client_base.py @@ -18,14 +18,14 @@ Core client functionality, common across all routers. """ -from routingpy import exceptions + from routingpy.__version__ import __version__ from abc import ABCMeta, abstractmethod from datetime import timedelta import requests from urllib.parse import urlencode -import json + _DEFAULT_USER_AGENT = "routingpy/v{}".format(__version__) _RETRIABLE_STATUSES = set([503]) @@ -82,20 +82,21 @@ class options(object): # To avoid trouble when respecting timeout for individual routers (i.e. can't be None, since that's no timeout) -DEFAULT = type('object', (object,), {'__repr__': lambda self: 'DEFAULT'})() +DEFAULT = type("object", (object,), {"__repr__": lambda self: "DEFAULT"})() class BaseClient(metaclass=ABCMeta): """Abstract base class every client inherits from. Authentication is handled in each subclass.""" def __init__( - self, - base_url, - user_agent=None, - timeout=DEFAULT, - retry_timeout=None, - retry_over_query_limit=None, - skip_api_error=None + self, + base_url, + user_agent=None, + timeout=DEFAULT, + retry_timeout=None, + retry_over_query_limit=None, + skip_api_error=None, + **kwargs ): """ :param base_url: The base URL for the request. All routers must provide a default. @@ -136,18 +137,24 @@ def __init__( """ self.base_url = base_url - self.retry_over_query_limit = retry_over_query_limit if retry_over_query_limit is False else options.default_retry_over_query_limit + self.retry_over_query_limit = ( + retry_over_query_limit + if retry_over_query_limit is False + else options.default_retry_over_query_limit + ) self.retry_timeout = timedelta(seconds=retry_timeout or options.default_retry_timeout) self.skip_api_error = skip_api_error or options.default_skip_api_error self.headers = { "User-Agent": user_agent or options.default_user_agent, - 'Content-Type': 'application/json' + "Content-Type": "application/json", } self.timeout = timeout if timeout != DEFAULT else options.default_timeout + self.kwargs = kwargs + self._req = None @abstractmethod @@ -155,34 +162,6 @@ def _request(self): """Must be implemented for inheriting client classes.""" pass - @property - def req(self): - """Holds the :class:`requests.PreparedRequest` property for the last request.""" - return self._req - - @staticmethod - def _get_body(response): - status_code = response.status_code - - try: - body = response.json() - except json.decoder.JSONDecodeError: - raise exceptions.JSONParseError("Can't decode JSON response:{}".format(response.text)) - - if status_code == 429: - raise exceptions.OverQueryLimit(status_code, body) - - if 400 <= status_code < 500: - raise exceptions.RouterApiError(status_code, body) - - if 500 <= status_code: - raise exceptions.RouterServerError(status_code, body) - - if status_code != 200: - raise exceptions.RouterError(status_code, body) - - return body - @staticmethod def _generate_auth_url(path, params): """Returns the path and query string portion of the request URL, first diff --git a/routingpy/client_default.py b/routingpy/client_default.py index d3daa14..a687a9a 100644 --- a/routingpy/client_default.py +++ b/routingpy/client_default.py @@ -15,7 +15,7 @@ # the License. # -from .base import BaseClient, DEFAULT, _RETRIABLE_STATUSES, options +from .client_base import BaseClient, DEFAULT, _RETRIABLE_STATUSES, options from routingpy import exceptions from routingpy.utils import get_ordinal @@ -29,15 +29,16 @@ class Client(BaseClient): """Default client class for requests handling. Uses the requests package.""" + def __init__( self, base_url, user_agent=None, timeout=DEFAULT, retry_timeout=None, - requests_kwargs=None, retry_over_query_limit=None, - skip_api_error=None + skip_api_error=None, + **kwargs ): """ :param base_url: The base URL for the request. All routers must provide a default. @@ -81,22 +82,27 @@ def __init__( self._session = requests.Session() super(Client, self).__init__( - base_url, user_agent=user_agent, timeout=timeout, retry_timeout=retry_timeout, - retry_over_query_limit=retry_over_query_limit, skip_api_error=skip_api_error + base_url, + user_agent=user_agent, + timeout=timeout, + retry_timeout=retry_timeout, + retry_over_query_limit=retry_over_query_limit, + skip_api_error=skip_api_error, + **kwargs ) - self.requests_kwargs = requests_kwargs or {} + self.kwargs = kwargs or {} try: - self.headers.update(self.requests_kwargs['headers']) + self.headers.update(self.kwargs["headers"]) except KeyError: pass - self.requests_kwargs['headers'] = self.headers - self.requests_kwargs['timeout'] = self.timeout + self.kwargs["headers"] = self.headers + self.kwargs["timeout"] = self.timeout - self.proxies = self.requests_kwargs.get('proxies') or options.default_proxies + self.proxies = self.kwargs.get("proxies") or options.default_proxies if self.proxies: - self.requests_kwargs['proxies'] = self.proxies + self.kwargs["proxies"] = self.proxies def _request( self, @@ -105,8 +111,8 @@ def _request( post_params=None, first_request_time=None, retry_counter=0, - requests_kwargs=None, - dry_run=None + dry_run=None, + **client_kwargs ): """Performs HTTP GET/POST with credentials, returning the body as JSON. @@ -158,27 +164,27 @@ def _request( # 0.5 * (1.5 ^ i) is an increased sleep time of 1.5x per iteration, # starting at 0.5s when retry_counter=1. The first retry will occur # at 1, so subtract that first. - delay_seconds = 1.5**(retry_counter - 1) + delay_seconds = 1.5 ** (retry_counter - 1) # Jitter this value by 50% and pause. time.sleep(delay_seconds * (random.random() + 0.5)) authed_url = self._generate_auth_url(url, get_params) - # Default to the client-level self.requests_kwargs, with method-level - # requests_kwargs arg overriding. - requests_kwargs = requests_kwargs or {} - final_requests_kwargs = dict(self.requests_kwargs, **requests_kwargs) + # Default to the client-level self.kwargs, with method-level + # client_kwargs arg overriding. + client_kwargs = client_kwargs or {} + final_requests_kwargs = dict(self.kwargs, **client_kwargs) # Determine GET/POST. requests_method = self._session.get if post_params is not None: requests_method = self._session.post - if final_requests_kwargs['headers']['Content-Type'] == 'application/json': + if final_requests_kwargs["headers"]["Content-Type"] == "application/json": final_requests_kwargs["json"] = post_params else: # Send as x-www-form-urlencoded key-value pair string (e.g. Mapbox API) - final_requests_kwargs['data'] = post_params + final_requests_kwargs["data"] = post_params # Only print URL and parameters for dry_run if dry_run: @@ -201,12 +207,12 @@ def _request( if response.status_code in _RETRIABLE_STATUSES: # Retry request. warnings.warn( - 'Server down.\nRetrying for the {}{} time.'.format(tried, get_ordinal(tried)), - UserWarning + "Server down.\nRetrying for the {}{} time.".format(tried, get_ordinal(tried)), + UserWarning, ) return self._request( - url, get_params, post_params, first_request_time, retry_counter + 1, requests_kwargs + url, get_params, post_params, first_request_time, retry_counter + 1, **client_kwargs ) try: @@ -229,10 +235,38 @@ def _request( raise warnings.warn( - 'Rate limit exceeded.\nRetrying for the {}{} time.'.format(tried, get_ordinal(tried)), - UserWarning + "Rate limit exceeded.\nRetrying for the {}{} time.".format(tried, get_ordinal(tried)), + UserWarning, ) # Retry request. return self._request( - url, get_params, post_params, first_request_time, retry_counter + 1, requests_kwargs + url, get_params, post_params, first_request_time, retry_counter + 1, **client_kwargs ) + + @property + def req(self): + """Holds the :class:`requests.PreparedRequest` property for the last request.""" + return self._req + + @staticmethod + def _get_body(response): + status_code = response.status_code + + try: + body = response.json() + except json.decoder.JSONDecodeError: + raise exceptions.JSONParseError("Can't decode JSON response:{}".format(response.text)) + + if status_code == 429: + raise exceptions.OverQueryLimit(status_code, body) + + if 400 <= status_code < 500: + raise exceptions.RouterApiError(status_code, body) + + if 500 <= status_code: + raise exceptions.RouterServerError(status_code, body) + + if status_code != 200: + raise exceptions.RouterError(status_code, body) + + return body diff --git a/routingpy/routers/__init__.py b/routingpy/routers/__init__.py index f32fbbf..0aeb765 100644 --- a/routingpy/routers/__init__.py +++ b/routingpy/routers/__init__.py @@ -13,7 +13,7 @@ .. _`contribution guidelines`: https://github.com/gis-ops/routing-py/blob/master/CONTRIBUTING.md .. _here: https://github.com/gis-ops/routing-py#api """ -from routingpy.base import options # noqa: F401 +from routingpy.client_base import options # noqa: F401 from routingpy.exceptions import RouterNotFound from .openrouteservice import ORS diff --git a/routingpy/routers/google.py b/routingpy/routers/google.py index 23dd055..6534bf9 100644 --- a/routingpy/routers/google.py +++ b/routingpy/routers/google.py @@ -15,7 +15,7 @@ # the License. # -from routingpy.base import DEFAULT +from routingpy.client_base import DEFAULT from routingpy.client_default import Client from routingpy import convert, utils from routingpy.direction import Directions, Direction @@ -35,10 +35,10 @@ def __init__( user_agent=None, timeout=DEFAULT, retry_timeout=None, - requests_kwargs={}, retry_over_query_limit=True, skip_api_error=None, - client=Client + client=Client, + **client_kwargs ): """ Initializes a Google client. @@ -91,8 +91,13 @@ def __init__( self.key = api_key self.client = client( - self._base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, - skip_api_error + self._base_url, + user_agent, + timeout, + retry_timeout, + retry_over_query_limit, + skip_api_error, + **client_kwargs ) class WayPoint(object): @@ -106,7 +111,8 @@ class WayPoint(object): >>> waypoint = Google.WayPoint(position=[8.15315, 52.53151], waypoint_type='coords', stopover=False) >>> route = Google(api_key).directions(locations=[[[8.58232, 51.57234]], waypoint, [7.15315, 53.632415]]) """ - def __init__(self, position, waypoint_type='coords', stopover=True): + + def __init__(self, position, waypoint_type="coords", stopover=True): """ Constructs a waypoint with additional information, such as via or encoded lines. @@ -127,18 +133,18 @@ def __init__(self, position, waypoint_type='coords', stopover=True): def make_waypoint(self): - waypoint = '' - if self.waypoint_type == 'coords': + waypoint = "" + if self.waypoint_type == "coords": waypoint += convert._delimit_list(list(reversed(self.position))) - elif self.waypoint_type == 'place_id': - waypoint += self.waypoint_type + ':' + self.position - elif self.waypoint_type == 'enc': - waypoint += self.waypoint_type + ':' + self.position + ':' + elif self.waypoint_type == "place_id": + waypoint += self.waypoint_type + ":" + self.position + elif self.waypoint_type == "enc": + waypoint += self.waypoint_type + ":" + self.position + ":" else: raise ValueError("waypoint_type only supports enc, place_id, coords") if not self.stopover: - waypoint = 'via:' + waypoint + waypoint = "via:" + waypoint return waypoint @@ -157,7 +163,7 @@ def directions( # noqa: C901 traffic_model=None, transit_mode=None, transit_routing_preference=None, - dry_run=None + dry_run=None, ): """Get directions between an origin point and a destination point. @@ -223,16 +229,16 @@ def directions( # noqa: C901 :rtype: :class:`routingpy.direction.Direction` or :class:`routingpy.direction.Directions` """ - params = {'mode': profile} + params = {"mode": profile} origin, destination = locations[0], locations[-1] if isinstance(origin, (list, tuple)): - params['origin'] = convert._delimit_list(list(reversed(origin))) + params["origin"] = convert._delimit_list(list(reversed(origin))) elif isinstance(origin, self.WayPoint): raise TypeError("The first and last locations must be list/tuple of [lon, lat]") if isinstance(destination, (list, tuple)): - params['destination'] = convert._delimit_list(list(reversed(destination))) + params["destination"] = convert._delimit_list(list(reversed(destination))) elif isinstance(origin, self.WayPoint): raise TypeError("The first and last locations must be list/tuple of [lon, lat]") @@ -245,48 +251,48 @@ def directions( # noqa: C901 elif isinstance(coord, self.WayPoint): waypoints.append(coord.make_waypoint()) if optimize: - waypoints.insert(0, 'optimize:true') + waypoints.insert(0, "optimize:true") - params['waypoints'] = convert._delimit_list(waypoints, '|') + params["waypoints"] = convert._delimit_list(waypoints, "|") if self.key is not None: params["key"] = self.key if alternatives is not None: - params['alternatives'] = convert._convert_bool(alternatives) + params["alternatives"] = convert._convert_bool(alternatives) if avoid: - params['avoid'] = convert._delimit_list(avoid, '|') + params["avoid"] = convert._delimit_list(avoid, "|") if language: - params['language'] = language + params["language"] = language if region: - params['region'] = region + params["region"] = region if units: - params['units'] = units + params["units"] = units if arrival_time and departure_time: raise ValueError("Specify either arrival_time or departure_time.") if arrival_time: - params['arrival_time'] = str(arrival_time) + params["arrival_time"] = str(arrival_time) if departure_time: - params['departure_time'] = str(departure_time) + params["departure_time"] = str(departure_time) if traffic_model: - params['traffic_model'] = traffic_model + params["traffic_model"] = traffic_model if transit_mode: - params['transit_mode'] = convert._delimit_list(transit_mode, '|') + params["transit_mode"] = convert._delimit_list(transit_mode, "|") if transit_routing_preference: - params['transit_routing_preference'] = transit_routing_preference + params["transit_routing_preference"] = transit_routing_preference return self._parse_direction_json( - self.client._request('/directions/json', get_params=params, dry_run=dry_run), alternatives + self.client._request("/directions/json", get_params=params, dry_run=dry_run), alternatives ) @staticmethod @@ -299,17 +305,17 @@ def _parse_direction_json(response, alternatives): if alternatives: routes = [] - for route in response['routes']: + for route in response["routes"]: geometry = [] duration, distance = 0, 0 - for leg in route['legs']: - duration += leg['duration']['value'] - distance += leg['distance']['value'] - for step in leg['steps']: + for leg in route["legs"]: + duration += leg["duration"]["value"] + distance += leg["distance"]["value"] + for step in leg["steps"]: geometry.extend( [ list(reversed(coords)) - for coords in utils.decode_polyline5(step['polyline']['points']) + for coords in utils.decode_polyline5(step["polyline"]["points"]) ] ) routes.append( @@ -321,14 +327,14 @@ def _parse_direction_json(response, alternatives): else: geometry = [] duration, distance = 0, 0 - for leg in response['routes'][0]['legs']: - duration = int(leg['duration']['value']) - distance = int(leg['distance']['value']) - for step in leg['steps']: + for leg in response["routes"][0]["legs"]: + duration = int(leg["duration"]["value"]) + distance = int(leg["distance"]["value"]) + for step in leg["steps"]: geometry.extend( [ list(reversed(coords)) - for coords in utils.decode_polyline5(step['polyline']['points']) + for coords in utils.decode_polyline5(step["polyline"]["points"]) ] ) return Direction(geometry=geometry, duration=duration, distance=distance, raw=response) @@ -351,9 +357,9 @@ def matrix( # noqa: C901 traffic_model=None, transit_mode=None, transit_routing_preference=None, - dry_run=None + dry_run=None, ): - """ Gets travel distance and time for a matrix of origins and destinations. + """Gets travel distance and time for a matrix of origins and destinations. :param locations: Two or more pairs of lng/lat values. :type locations: list of list @@ -413,7 +419,7 @@ def matrix( # noqa: C901 :returns: A matrix from the specified sources and destinations. :rtype: :class:`routingpy.matrix.Matrix` """ - params = {'mode': profile} + params = {"mode": profile} waypoints = [] for coord in locations: @@ -427,47 +433,47 @@ def matrix( # noqa: C901 sources_coords = itemgetter(*sources)(sources_coords) if not isinstance(sources_coords, (list, tuple)): sources_coords = [sources_coords] - params['origins'] = convert._delimit_list(sources_coords, '|') + params["origins"] = convert._delimit_list(sources_coords, "|") destinations_coords = waypoints if destinations is not None: destinations_coords = itemgetter(*destinations)(destinations_coords) if not isinstance(destinations_coords, (list, tuple)): destinations_coords = [destinations_coords] - params['destinations'] = convert._delimit_list(destinations_coords, '|') + params["destinations"] = convert._delimit_list(destinations_coords, "|") if self.key is not None: params["key"] = self.key if avoid: - params['avoid'] = convert._delimit_list(avoid, '|') + params["avoid"] = convert._delimit_list(avoid, "|") if language: - params['language'] = language + params["language"] = language if region: - params['region'] = region + params["region"] = region if units: - params['units'] = units + params["units"] = units if arrival_time: - params['arrival_time'] = str(arrival_time) + params["arrival_time"] = str(arrival_time) if departure_time: - params['departure_time'] = str(departure_time) + params["departure_time"] = str(departure_time) if traffic_model: - params['traffic_model'] = traffic_model + params["traffic_model"] = traffic_model if transit_mode: - params['transit_mode'] = convert._delimit_list(transit_mode, '|') + params["transit_mode"] = convert._delimit_list(transit_mode, "|") if transit_routing_preference: - params['transit_routing_preference'] = transit_routing_preference + params["transit_routing_preference"] = transit_routing_preference return self._parse_matrix_json( - self.client._request('/distancematrix/json', get_params=params, dry_run=dry_run) + self.client._request("/distancematrix/json", get_params=params, dry_run=dry_run) ) @staticmethod @@ -476,14 +482,12 @@ def _parse_matrix_json(response): return Matrix() durations = [ - [destination['duration']['value'] - for destination in origin['elements']] - for origin in response['rows'] + [destination["duration"]["value"] for destination in origin["elements"]] + for origin in response["rows"] ] distances = [ - [destination['distance']['value'] - for destination in origin['elements']] - for origin in response['rows'] + [destination["distance"]["value"] for destination in origin["elements"]] + for origin in response["rows"] ] return Matrix(durations, distances, response) diff --git a/routingpy/routers/graphhopper.py b/routingpy/routers/graphhopper.py index b18b359..79e1ff0 100644 --- a/routingpy/routers/graphhopper.py +++ b/routingpy/routers/graphhopper.py @@ -17,7 +17,7 @@ from typing import List, Tuple # noqa: F401 -from routingpy.base import DEFAULT +from routingpy.client_base import DEFAULT from routingpy.client_default import Client from routingpy import convert from routingpy import utils @@ -38,10 +38,10 @@ def __init__( user_agent=None, timeout=DEFAULT, retry_timeout=None, - requests_kwargs={}, retry_over_query_limit=False, skip_api_error=None, - client=Client + client=Client, + **client_kwargs ): """ Initializes an graphhopper client. @@ -100,8 +100,13 @@ def __init__( self.key = api_key self.client = client( - base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, - skip_api_error + base_url, + user_agent, + timeout, + retry_timeout, + retry_over_query_limit, + skip_api_error, + **client_kwargs ) def directions( # noqa: C901 @@ -134,7 +139,7 @@ def directions( # noqa: C901 dry_run=None, snap_prevention=None, curb_side=None, - turn_costs=None + turn_costs=None, ): """Get directions between an origin point and a destination point. @@ -280,7 +285,7 @@ def directions( # noqa: C901 ``snap_prevention``, ``curb_side``, ``turn_costs`` parameters """ - params = [('vehicle', profile)] + params = [("vehicle", profile)] for coordinate in locations: coord_latlng = reversed([convert._format_float(f) for f in coordinate]) @@ -318,13 +323,13 @@ def directions( # noqa: C901 params.append(("point_hint", hint)) if snap_prevention: - params.append(('snap_prevention', convert._delimit_list(snap_prevention))) + params.append(("snap_prevention", convert._delimit_list(snap_prevention))) if turn_costs: - params.append(('turn_costs', convert._convert_bool(turn_costs))) + params.append(("turn_costs", convert._convert_bool(turn_costs))) if curb_side: - params.append(('curb_side', convert._delimit_list(curb_side))) + params.append(("curb_side", convert._delimit_list(curb_side))) ### all below params will only work if ch is disabled @@ -350,13 +355,13 @@ def directions( # noqa: C901 params.append(("block_area", block_area)) if avoid is not None: - params.append(("avoid", convert._delimit_list(avoid, ';'))) + params.append(("avoid", convert._delimit_list(avoid, ";"))) if algorithm is not None: - params.append(('algorithm', algorithm)) + params.append(("algorithm", algorithm)) - if algorithm == 'round_trip': + if algorithm == "round_trip": if round_trip_distance is not None: params.append(("round_trip.distance", round_trip_distance)) @@ -364,7 +369,7 @@ def directions( # noqa: C901 if round_trip_seed is not None: params.append(("round_trip.seed", round_trip_seed)) - if algorithm == 'alternative_route': + if algorithm == "alternative_route": if alternative_route_max_paths is not None: params.append(("alternative_route.max_paths", alternative_route_max_paths)) @@ -380,43 +385,42 @@ def directions( # noqa: C901 ) return self._parse_directions_json( - self.client._request('/route', get_params=params, dry_run=dry_run), algorithm, elevation + self.client._request("/route", get_params=params, dry_run=dry_run), algorithm, elevation ) @staticmethod def _parse_directions_json(response, algorithm, elevation): if response is None: # pragma: no cover - if algorithm == 'alternative_route': + if algorithm == "alternative_route": return Directions() else: return Direction() - if algorithm == 'alternative_route': + if algorithm == "alternative_route": routes = [] - for route in response['paths']: + for route in response["paths"]: geometry = [ - list(reversed(coord)) - for coord in utils.decode_polyline5(route['points'], elevation) + list(reversed(coord)) for coord in utils.decode_polyline5(route["points"], elevation) ] routes.append( Direction( geometry=geometry, - duration=int(route['time'] / 1000), - distance=int(route['distance']), - raw=route + duration=int(route["time"] / 1000), + distance=int(route["distance"]), + raw=route, ) ) return Directions(routes, response) else: geometry = [ list(reversed(coord)) - for coord in utils.decode_polyline5(response['paths'][0]['points'], elevation) + for coord in utils.decode_polyline5(response["paths"][0]["points"], elevation) ] return Direction( geometry=geometry, - duration=int(response['paths'][0]['time'] / 1000), - distance=int(response['paths'][0]['distance']), - raw=response + duration=int(response["paths"][0]["time"] / 1000), + distance=int(response["paths"][0]["distance"]), + raw=response, ) def isochrones( @@ -424,12 +428,12 @@ def isochrones( locations, profile, intervals, - type='json', + type="json", buckets=1, interval_type=None, reverse_flow=None, debug=None, - dry_run=None + dry_run=None, ): """Gets isochrones or equidistants for a range of time/distance values around a given set of coordinates. @@ -471,13 +475,13 @@ def isochrones( :rtype: :class:`routingpy.isochrone.Isochrones` """ - params = [('vehicle', profile), ('type', type)] + params = [("vehicle", profile), ("type", type)] if convert._is_list(intervals): - if interval_type in (None, 'time'): - params.append(('time_limit', intervals[0])) - elif interval_type == 'distance': - params.append(('distance_limit', intervals[0])) + if interval_type in (None, "time"): + params.append(("time_limit", intervals[0])) + elif interval_type == "distance": + params.append(("distance_limit", intervals[0])) else: raise TypeError("Parameter range={} must be of type list or tuple".format(range)) @@ -489,17 +493,20 @@ def isochrones( params.append(("key", self.key)) if buckets is not None: - params.append(('buckets', buckets)) + params.append(("buckets", buckets)) if reverse_flow is not None: - params.append(('reverse_flow', convert._convert_bool(reverse_flow))) + params.append(("reverse_flow", convert._convert_bool(reverse_flow))) if debug is not None: - params.append(('debug', convert._convert_bool(debug))) + params.append(("debug", convert._convert_bool(debug))) return self._parse_isochrone_json( - self.client._request("/isochrone", get_params=params, dry_run=dry_run), type, intervals[0], buckets, - center + self.client._request("/isochrone", get_params=params, dry_run=dry_run), + type, + intervals[0], + buckets, + center, ) @staticmethod @@ -508,14 +515,14 @@ def _parse_isochrone_json(response, type, max_range, buckets, center): return Isochrones() isochrones = [] - accessor = 'polygons' if type == 'json' else 'features' + accessor = "polygons" if type == "json" else "features" for index, polygon in enumerate(response[accessor]): isochrones.append( Isochrone( geometry=[ - l[:2] for l in polygon['geometry']['coordinates'][0] # noqa: E741 + l[:2] for l in polygon["geometry"]["coordinates"][0] # noqa: E741 ], # takes in elevation for some reason - interval=int(max_range * ((polygon['properties']['bucket'] + 1) / buckets)), + interval=int(max_range * ((polygon["properties"]["bucket"] + 1) / buckets)), center=center, ) ) @@ -528,11 +535,11 @@ def matrix( profile, sources=None, destinations=None, - out_array=['times', 'distances'], + out_array=["times", "distances"], debug=None, - dry_run=None + dry_run=None, ): - """ Gets travel distance and time for a matrix of origins and destinations. + """Gets travel distance and time for a matrix of origins and destinations. For more details visit https://docs.graphhopper.com/#tag/Matrix-API. @@ -569,14 +576,14 @@ def matrix( :returns: A matrix from the specified sources and destinations. :rtype: :class:`routingpy.matrix.Matrix` """ - params = [('vehicle', profile)] + params = [("vehicle", profile)] if self.key is not None: params.append(("key", self.key)) if sources is None and destinations is None: locations = (reversed([convert._format_float(f) for f in coord]) for coord in locations) - params.extend([('point', ",".join(coord)) for coord in locations]) + params.extend([("point", ",".join(coord)) for coord in locations]) else: sources_out = locations @@ -615,15 +622,17 @@ def matrix( params.append(("out_array", e)) if debug is not None: - params.append(('debug', convert._convert_bool(debug))) + params.append(("debug", convert._convert_bool(debug))) - return self._parse_matrix_json(self.client._request('/matrix', get_params=params, dry_run=dry_run), ) + return self._parse_matrix_json( + self.client._request("/matrix", get_params=params, dry_run=dry_run), + ) @staticmethod def _parse_matrix_json(response): if response is None: # pragma: no cover return Matrix() - durations = response.get('times') - distances = response.get('distances') + durations = response.get("times") + distances = response.get("distances") return Matrix(durations=durations, distances=distances, raw=response) diff --git a/routingpy/routers/heremaps.py b/routingpy/routers/heremaps.py index 4da5182..358005d 100644 --- a/routingpy/routers/heremaps.py +++ b/routingpy/routers/heremaps.py @@ -15,7 +15,7 @@ # the License. # -from routingpy.base import DEFAULT +from routingpy.client_base import DEFAULT from routingpy.client_default import Client from routingpy import convert from routingpy.direction import Direction, Directions @@ -28,8 +28,8 @@ class HereMaps: """Performs requests to the HERE Maps API services.""" - _DEFAULT_BASE_URL = 'https://route.api.here.com/routing/7.2' - _APIKEY_BASE_URL = 'https://route.ls.hereapi.com/routing/7.2' + _DEFAULT_BASE_URL = "https://route.api.here.com/routing/7.2" + _APIKEY_BASE_URL = "https://route.ls.hereapi.com/routing/7.2" def __init__( self, @@ -38,11 +38,11 @@ def __init__( user_agent=None, timeout=DEFAULT, retry_timeout=None, - requests_kwargs=None, retry_over_query_limit=False, skip_api_error=None, api_key=None, - client=Client + client=Client, + **client_kwargs ): """ Initializes a HERE Maps client. @@ -114,8 +114,13 @@ def __init__( self.auth = {"app_id": self.app_id, "app_code": self.app_code} self.client = client( - self.base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, - skip_api_error + self.base_url, + user_agent, + timeout, + retry_timeout, + retry_over_query_limit, + skip_api_error, + **client_kwargs ) class Waypoint(object): @@ -128,14 +133,15 @@ class Waypoint(object): >>> waypoint = HereMaps.Waypoint(position=[8.15315, 52.53151], waypoint_type='passThrough', stopover_duration=120, transit_radius=500) >>> route = HereMaps(api_key).directions(locations=[[[8.58232, 51.57234]], waypoint, [7.15315, 53.632415]]) """ + def __init__( self, position, waypoint_type=None, stopover_duration=None, - transit_radius='', - user_label='', - heading='' + transit_radius="", + user_label="", + heading="", ): """ :param position: Indicates that the parameter contains a geographical position. @@ -176,22 +182,22 @@ def __init__( def _make_waypoint(self): - here_waypoint = ['geo'] + here_waypoint = ["geo"] if self.waypoint_type is not None and self.stopover_duration is not None: here_waypoint.append( - convert._delimit_list([self.waypoint_type, self.stopover_duration], ',') + convert._delimit_list([self.waypoint_type, self.stopover_duration], ",") ) elif self.waypoint_type is not None: here_waypoint.append(self.waypoint_type) position = convert._delimit_list( - [convert._format_float(f) for f in list(reversed(self.position))], ',' + [convert._format_float(f) for f in list(reversed(self.position))], "," ) - position += ';' + self.transit_radius - position += ';' + self.user_label - position += ';' + self.heading + position += ";" + self.transit_radius + position += ";" + self.user_label + position += ";" + self.heading here_waypoint.append(position) - return convert._delimit_list(here_waypoint, '!') + return convert._delimit_list(here_waypoint, "!") class RoutingMode(object): """ @@ -204,8 +210,9 @@ class RoutingMode(object): >>> route = HereMaps(api_key).directions(locations=location_list, profile=profile) """ + def __init__( - self, mode_type='fastest', mode_transport_type='car', mode_traffic=None, features=None + self, mode_type="fastest", mode_transport_type="car", mode_traffic=None, features=None ): """ :param mode_type: RoutingType relevant to calculation. @@ -234,21 +241,21 @@ def make_routing_mode(self): routing_mode.append(self.mode_transport_type) if self.mode_traffic is not None: - routing_mode.append('traffic:' + self.mode_traffic) + routing_mode.append("traffic:" + self.mode_traffic) if self.features is not None: get_features = [] for f, w in self.features.items(): - get_features.append(convert._delimit_list([f, str(w)], ':')) - routing_mode.append(convert._delimit_list(get_features, ',')) - return convert._delimit_list(routing_mode, ';') + get_features.append(convert._delimit_list([f, str(w)], ":")) + routing_mode.append(convert._delimit_list(get_features, ",")) + return convert._delimit_list(routing_mode, ";") def directions( # noqa: C901 self, locations, profile, - mode_type='fastest', - format='json', + mode_type="fastest", + format="json", request_id=None, avoid_areas=None, avoid_links=None, @@ -269,7 +276,7 @@ def directions( # noqa: C901 json_attributes=None, json_callback=None, representation=None, - route_attributes=['waypoints', 'summary', 'shape', 'boundingBox', 'legs'], + route_attributes=["waypoints", "summary", "shape", "boundingBox", "legs"], leg_attributes=None, maneuver_attributes=None, link_attributes=None, @@ -297,7 +304,7 @@ def directions( # noqa: C901 consumption_model=None, custom_consumption_details=None, speed_profile=None, - dry_run=None + dry_run=None, ): """Get directions between an origin point and a destination point. @@ -596,7 +603,11 @@ def directions( # noqa: C901 :rtype: :class:`routingpy.direction.Direction` or :class:`routingpy.direction.Directions` """ - self.base_url = 'https://route.api.here.com/routing/7.2' if self.api_key is None else 'https://route.ls.hereapi.com/routing/7.2' + self.base_url = ( + "https://route.api.here.com/routing/7.2" + if self.api_key is None + else "https://route.ls.hereapi.com/routing/7.2" + ) params = self.auth.copy() locations = self._build_locations(locations) @@ -606,7 +617,7 @@ def directions( # noqa: C901 params[wp_index] = wp if isinstance(profile, str): - params["mode"] = mode_type + ';' + profile + params["mode"] = mode_type + ";" + profile elif isinstance(profile, self.RoutingMode): params["mode"] = profile.make_routing_mode() @@ -619,15 +630,19 @@ def directions( # noqa: C901 convert._delimit_list( [ convert._delimit_list( - [convert._format_float(f) for f in list(reversed(pair))], ',' - ) for pair in bounding_box - ], ';' - ) for bounding_box in avoid_areas - ], '!' + [convert._format_float(f) for f in list(reversed(pair))], "," + ) + for pair in bounding_box + ], + ";", + ) + for bounding_box in avoid_areas + ], + "!", ) if avoid_links is not None: - params["avoidLinks"] = convert._delimit_list(avoid_links, ',') + params["avoidLinks"] = convert._delimit_list(avoid_links, ",") if avoid_seasonal_closures is not None: params["avoidSeasonalClosures"] = convert._convert_bool(avoid_seasonal_closures) @@ -636,16 +651,16 @@ def directions( # noqa: C901 params["avoidTurns"] = avoid_turns if allowed_zones is not None: - params["allowedZones"] = convert._delimit_list(allowed_zones, ',') + params["allowedZones"] = convert._delimit_list(allowed_zones, ",") if exclude_zones is not None: - params["excludeZones"] = convert._delimit_list(exclude_zones, ',') + params["excludeZones"] = convert._delimit_list(exclude_zones, ",") if exclude_zone_types is not None: - params["excludeZoneTypes"] = convert._delimit_list(exclude_zone_types, ',') + params["excludeZoneTypes"] = convert._delimit_list(exclude_zone_types, ",") if exclude_countries is not None: - params["excludeCountries"] = convert._delimit_list(exclude_countries, ',') + params["excludeCountries"] = convert._delimit_list(exclude_countries, ",") if departure is not None: params["departure"] = departure @@ -661,16 +676,16 @@ def directions( # noqa: C901 if view_bounds is not None: params["viewBounds"] = convert._delimit_list( [ - convert._delimit_list([convert._format_float(f) - for f in list(reversed(pair))], ',') + convert._delimit_list([convert._format_float(f) for f in list(reversed(pair))], ",") for pair in view_bounds - ], ';' + ], + ";", ) if resolution is not None: - params["resolution"] = str(resolution['viewresolution']) - if 'snapresolution' in resolution: - params["resolution"] += ':' + str(resolution['snapresolution']) + params["resolution"] = str(resolution["viewresolution"]) + if "snapresolution" in resolution: + params["resolution"] += ":" + str(resolution["snapresolution"]) if instruction_format is not None: params["instructionFormat"] = instruction_format @@ -682,25 +697,25 @@ def directions( # noqa: C901 params["jsonCallback"] = json_callback if representation is not None: - params["representation"] = convert._delimit_list(representation, ',') + params["representation"] = convert._delimit_list(representation, ",") if route_attributes is not None: - params["routeAttributes"] = convert._delimit_list(route_attributes, ',') + params["routeAttributes"] = convert._delimit_list(route_attributes, ",") if leg_attributes is not None: - params["legAttributes"] = convert._delimit_list(leg_attributes, ',') + params["legAttributes"] = convert._delimit_list(leg_attributes, ",") if maneuver_attributes is not None: - params["maneuverAttributes"] = convert._delimit_list(maneuver_attributes, ',') + params["maneuverAttributes"] = convert._delimit_list(maneuver_attributes, ",") if link_attributes is not None: - params["linkAttributes"] = convert._delimit_list(link_attributes, ',') + params["linkAttributes"] = convert._delimit_list(link_attributes, ",") if line_attributes is not None: - params["lineAttributes"] = convert._delimit_list(line_attributes, ',') + params["lineAttributes"] = convert._delimit_list(line_attributes, ",") if generalization_tolerances is not None: - params["generalizationTolerances"] = convert._delimit_list(generalization_tolerances, ',') + params["generalizationTolerances"] = convert._delimit_list(generalization_tolerances, ",") if vehicle_type is not None: params["vehicleType"] = vehicle_type @@ -712,7 +727,7 @@ def directions( # noqa: C901 params["maxNumberOfChanges"] = max_number_of_changes if avoid_transport_types is not None: - params["avoidTransportTypes"] = convert._delimit_list(avoid_transport_types, ',') + params["avoidTransportTypes"] = convert._delimit_list(avoid_transport_types, ",") if walk_time_multiplier is not None: params["walkTimeMultiplier"] = walk_time_multiplier @@ -733,7 +748,7 @@ def directions( # noqa: C901 params["trailersCount"] = trailers_count if shipped_hazardous_goods is not None: - params["shippedHazardousGoods"] = convert._delimit_list(shipped_hazardous_goods, ',') + params["shippedHazardousGoods"] = convert._delimit_list(shipped_hazardous_goods, ",") if limited_weight is not None: params["limitedWeight"] = limited_weight @@ -751,7 +766,7 @@ def directions( # noqa: C901 params["length"] = length if tunnel_category is not None: - params["tunnelCategory"] = convert._delimit_list(tunnel_category, ',') + params["tunnelCategory"] = convert._delimit_list(tunnel_category, ",") if truck_restriction_penalty is not None: params["truckRestrictionPenalty"] = truck_restriction_penalty @@ -770,11 +785,11 @@ def directions( # noqa: C901 return self._parse_direction_json( self.client._request( - convert._delimit_list(["/calculateroute", format], '.'), + convert._delimit_list(["/calculateroute", format], "."), get_params=params, - dry_run=dry_run + dry_run=dry_run, ), - alternatives=alternatives + alternatives=alternatives, ) @staticmethod @@ -787,16 +802,16 @@ def _parse_direction_json(response, alternatives): if alternatives is not None and alternatives > 1: routes = [] - for route in response['response']['route']: + for route in response["response"]["route"]: routes.append( Direction( geometry=[ - list(reversed(list((map(float, coordinates.split(',')))))) - for coordinates in route['shape'] + list(reversed(list((map(float, coordinates.split(",")))))) + for coordinates in route["shape"] ], - duration=int(route['summary']['baseTime']), - distance=int(route['summary']['distance']), - raw=route + duration=int(route["summary"]["baseTime"]), + distance=int(route["summary"]["distance"]), + raw=route, ) ) @@ -804,11 +819,11 @@ def _parse_direction_json(response, alternatives): else: geometry = [ - list(reversed(list(map(float, coordinates.split(','))))) - for coordinates in response['response']['route'][0].get('shape') + list(reversed(list(map(float, coordinates.split(","))))) + for coordinates in response["response"]["route"][0].get("shape") ] - duration = int(response['response']['route'][0]['summary'].get('baseTime')) - distance = int(response['response']['route'][0]['summary'].get('distance')) + duration = int(response["response"]["route"][0]["summary"].get("baseTime")) + distance = int(response["response"]["route"][0]["summary"].get("distance")) return Direction(geometry=geometry, duration=duration, distance=distance, raw=response) @@ -817,10 +832,10 @@ def isochrones( # noqa: C901 locations, profile, intervals, - mode_type='fastest', - interval_type='time', - format='json', - center_type='start', + mode_type="fastest", + interval_type="time", + format="json", + center_type="start", request_id=None, arrival=None, departure=None, @@ -842,7 +857,7 @@ def isochrones( # noqa: C901 consumption_model=None, custom_consumption_details=None, speed_profile=None, - dry_run=None + dry_run=None, ): """Gets isochrones or equidistants for a range of time/distance values around a given set of coordinates. @@ -989,18 +1004,22 @@ def isochrones( # noqa: C901 :rtype: dict """ - self.base_url = 'https://isoline.route.api.here.com/routing/7.2' if self.api_key is None else 'https://isoline.route.ls.hereapi.com/routing/7.2' + self.base_url = ( + "https://isoline.route.api.here.com/routing/7.2" + if self.api_key is None + else "https://isoline.route.ls.hereapi.com/routing/7.2" + ) params = self.auth.copy() params[center_type] = self._build_locations(locations)[0] if isinstance(profile, str): - params["mode"] = mode_type + ';' + profile + params["mode"] = mode_type + ";" + profile elif isinstance(profile, self.RoutingMode): params["mode"] = profile.make_routing_mode() if intervals is not None: - params["range"] = convert._delimit_list(intervals, ',') + params["range"] = convert._delimit_list(intervals, ",") if interval_type is not None: params["rangeType"] = interval_type @@ -1032,7 +1051,7 @@ def isochrones( # noqa: C901 params["trailersCount"] = trailers_count if shipped_hazardous_goods is not None: - params["shippedHazardousGoods"] = convert._delimit_list(shipped_hazardous_goods, ',') + params["shippedHazardousGoods"] = convert._delimit_list(shipped_hazardous_goods, ",") if limited_weight is not None: params["limitedWeight"] = limited_weight @@ -1050,7 +1069,7 @@ def isochrones( # noqa: C901 params["length"] = length if tunnel_category is not None: - params["tunnelCategory"] = convert._delimit_list(tunnel_category, ',') + params["tunnelCategory"] = convert._delimit_list(tunnel_category, ",") if consumption_model is not None: params["consumptionModel"] = consumption_model @@ -1063,10 +1082,11 @@ def isochrones( # noqa: C901 return self._parse_isochrone_json( self.client._request( - convert._delimit_list(["/calculateisoline", format], '.'), + convert._delimit_list(["/calculateisoline", format], "."), get_params=params, - dry_run=dry_run - ), intervals + dry_run=dry_run, + ), + intervals, ) @staticmethod @@ -1075,13 +1095,13 @@ def _parse_isochrone_json(response, intervals): return Isochrones() geometries = [] - for idx, isochrones in enumerate(response['response']['isoline']): + for idx, isochrones in enumerate(response["response"]["isoline"]): range_polygons = [] - if 'component' in isochrones: - for component in isochrones['component']: - if 'shape' in component: + if "component" in isochrones: + for component in isochrones["component"]: + if "shape" in component: coordinates_list = [] - for coordinates in component['shape']: + for coordinates in component["shape"]: coords = [float(f) for f in coordinates.split(",")] coordinates_list.append(reversed(coords)) range_polygons.append(coordinates_list) @@ -1090,7 +1110,7 @@ def _parse_isochrone_json(response, intervals): Isochrone( geometry=range_polygons, interval=intervals[idx], - center=list(response['response']['start']['mappedPosition'].values()) + center=list(response["response"]["start"]["mappedPosition"].values()), ) ) @@ -1100,8 +1120,8 @@ def matrix( # noqa: C901 self, locations, profile, - format='json', - mode_type='fastest', + format="json", + mode_type="fastest", sources=None, destinations=None, search_range=None, @@ -1111,7 +1131,7 @@ def matrix( # noqa: C901 exclude_countries=None, departure=None, matrix_attributes=None, - summary_attributes=['traveltime', 'costfactor', 'distance'], + summary_attributes=["traveltime", "costfactor", "distance"], truck_type=None, trailers_count=None, shipped_hazardous_goods=None, @@ -1122,136 +1142,140 @@ def matrix( # noqa: C901 length=None, tunnel_category=None, speed_profile=None, - dry_run=None + dry_run=None, ): - """ Gets travel distance and time for a matrix of origins and destinations. + """Gets travel distance and time for a matrix of origins and destinations. - :param locations: The coordinates tuple the route should be calculated - from in order of visit. Can be a list/tuple of [lon, lat] or :class:`HereMaps.Waypoint` instance or a - combination of those. For further explanation, see - https://developer.here.com/documentation/routing/topics/resource-param-type-waypoint.html - :type locations: list of list or list of :class:`HereMaps.Waypoint` + :param locations: The coordinates tuple the route should be calculated + from in order of visit. Can be a list/tuple of [lon, lat] or :class:`HereMaps.Waypoint` instance or a + combination of those. For further explanation, see + https://developer.here.com/documentation/routing/topics/resource-param-type-waypoint.html + :type locations: list of list or list of :class:`HereMaps.Waypoint` - :param profile: Specifies the routing mode of transport and further options. - Can be a str or :class:`HereMaps.RoutingMode` - https://developer.here.com/documentation/routing/topics/resource-param-type-routing-mode.html - :type profile: str or :class:`HereMaps.RoutingMode` + :param profile: Specifies the routing mode of transport and further options. + Can be a str or :class:`HereMaps.RoutingMode` + https://developer.here.com/documentation/routing/topics/resource-param-type-routing-mode.html + :type profile: str or :class:`HereMaps.RoutingMode` - :param mode_type: RoutingType relevant to calculation. One of [fastest, shortest, balanced]. Default fastest. - https://developer.here.com/documentation/routing/topics/resource-param-type-routing-mode.html#ariaid-title2 - :type mode_type: str + :param mode_type: RoutingType relevant to calculation. One of [fastest, shortest, balanced]. Default fastest. + https://developer.here.com/documentation/routing/topics/resource-param-type-routing-mode.html#ariaid-title2 + :type mode_type: str - :param sources: The starting points for the matrix. - Specifies an index referring to coordinates. - :type sources: list of int - - :param destinations: The destination points for the routes. - Specifies an index referring to coordinates. - :type destinations: list of int - - :param search_range: Defines the maximum search range for destination - waypoints, in meters. This parameter is especially useful for optimizing - matrix calculation where the maximum desired effective distance is known - in advance. Destination waypoints with a longer effective distance than - specified by searchRange will be skipped. The parameter is optional. - In pedestrian mode the default search range is 20 km. - If parameter is omitted in other modes, no range limit will apply. - :type search_range: int - - :param avoid_areas: Areas which the route must not cross. - Array of BoundingBox. Example with 2 bounding boxes - https://developer.here.com/documentation/routing/topics/resource-param-type-bounding-box.html - :type avoid_areas: list of list of list - - :param avoid_links: Links which the route must not cross. - The list of LinkIdTypes. - :type avoid_areas: list of string - - :param avoid_turns: List of turn types that the route should avoid. Defaults to empty list. - https://developer.here.com/documentation/routing/topics/resource-type-enumerations.html - :type avoid_turns: str - - :param exclude_countries: Countries that must be excluded from route calculation. - :type exclude_countries: list of str - - :param departure: Time when travel is expected to start. Traffic speed and - incidents are taken into account - when calculating the route (note that in case of a past - departure time the historical traffic is limited to one year). - You can use now to specify the current time. Specify either departure - or arrival, not both. When the optional timezone offset is not - specified, the time is assumed to be the local. - Formatted as iso time, e.g. 2018-07-04T17:00:00+02. - :type departure: str - - :param matrix_attributes: Defines which attributes are included in the - response as part of the data representation of the route matrix entries. - Defaults to indices and summary. - https://developer.here.com/documentation/routing/topics/resource-calculate-matrix.html#resource-calculate-matrix__matrix-route-attribute-type - :type matrix_attributes: list of str - - :param summary_attributes: Defines which attributes are included in - the response as part of the data representation of the matrix - entries summaries. Defaults to costfactor. - https://developer.here.com/documentation/routing/topics/resource-calculate-matrix.html#resource-calculate-matrix__matrix-route-summary-attribute-type - :type matrix_attributes: list of str - - :param truck_type: Truck routing only, specifies the vehicle type. - Defaults to truck. - :type truck_type: str - - :param trailers_count: Truck routing only, specifies number of - trailers pulled by a vehicle. The provided value must be between 0 and 4. - Defaults to 0. - :type trailers_count: int - - :param shipped_hazardous_goods: Truck routing only, list of hazardous - materials in the vehicle. Please refer to the enumeration type - HazardousGoodTypeType for available values. Note the value - allhazardousGoods does not apply to the request parameter. - https://developer.here.com/documentation/routing/topics/resource-type-enumerations.html#resource-type-enumerations__enum-hazardous-good-type-type - :type shipped_hazardous_goods: list of str - - :param limited_weight: Truck routing only, vehicle weight including - trailers and shipped goods, in tons. The provided value must be - between 0 and 1000. - :type limited_weight: int - - :param weight_per_axle: Truck routing only, vehicle weight per axle - in tons. The provided value must be between 0 and 1000. - :type limited_weight: int - - :param height: Truck routing only, vehicle height in meters. The - provided value must be between 0 and 50. - :type height: int - - :param width: Truck routing only, vehicle width in meters. - The provided value must be between 0 and 50. - :type width: int - - :param length: Truck routing only, vehicle length in meters. - The provided value must be between 0 and 300. - :type length: int - - :param tunnel_category: Truck routing only, specifies the tunnel - category to restrict certain route links. The route will pass - only through tunnels of a less strict category. - :type tunnel_category: list of str - - :param speed_profile: Specifies the speed profile variant for a given - routing mode. The speed profile affects travel time estimation as - well as roads evaluation when computing the fastest route. - Note that computed routes might differ depending on a used profile. - https://developer.here.com/documentation/routing/topics/resource-param-type-speed-profile-type.html - :type speed_profile: str - - :param dry_run: Print URL and parameters without sending the request. - :param dry_run: bool - - :returns: raw JSON response - :rtype: dict - """ - self.base_url = 'https://matrix.route.api.here.com/routing/7.2' if self.api_key is None else 'https://matrix.route.ls.hereapi.com/routing/7.2' + :param sources: The starting points for the matrix. + Specifies an index referring to coordinates. + :type sources: list of int + + :param destinations: The destination points for the routes. + Specifies an index referring to coordinates. + :type destinations: list of int + + :param search_range: Defines the maximum search range for destination + waypoints, in meters. This parameter is especially useful for optimizing + matrix calculation where the maximum desired effective distance is known + in advance. Destination waypoints with a longer effective distance than + specified by searchRange will be skipped. The parameter is optional. + In pedestrian mode the default search range is 20 km. + If parameter is omitted in other modes, no range limit will apply. + :type search_range: int + + :param avoid_areas: Areas which the route must not cross. + Array of BoundingBox. Example with 2 bounding boxes + https://developer.here.com/documentation/routing/topics/resource-param-type-bounding-box.html + :type avoid_areas: list of list of list + + :param avoid_links: Links which the route must not cross. + The list of LinkIdTypes. + :type avoid_areas: list of string + + :param avoid_turns: List of turn types that the route should avoid. Defaults to empty list. + https://developer.here.com/documentation/routing/topics/resource-type-enumerations.html + :type avoid_turns: str + + :param exclude_countries: Countries that must be excluded from route calculation. + :type exclude_countries: list of str + + :param departure: Time when travel is expected to start. Traffic speed and + incidents are taken into account + when calculating the route (note that in case of a past + departure time the historical traffic is limited to one year). + You can use now to specify the current time. Specify either departure + or arrival, not both. When the optional timezone offset is not + specified, the time is assumed to be the local. + Formatted as iso time, e.g. 2018-07-04T17:00:00+02. + :type departure: str + + :param matrix_attributes: Defines which attributes are included in the + response as part of the data representation of the route matrix entries. + Defaults to indices and summary. + https://developer.here.com/documentation/routing/topics/resource-calculate-matrix.html#resource-calculate-matrix__matrix-route-attribute-type + :type matrix_attributes: list of str + + :param summary_attributes: Defines which attributes are included in + the response as part of the data representation of the matrix + entries summaries. Defaults to costfactor. + https://developer.here.com/documentation/routing/topics/resource-calculate-matrix.html#resource-calculate-matrix__matrix-route-summary-attribute-type + :type matrix_attributes: list of str + + :param truck_type: Truck routing only, specifies the vehicle type. + Defaults to truck. + :type truck_type: str + + :param trailers_count: Truck routing only, specifies number of + trailers pulled by a vehicle. The provided value must be between 0 and 4. + Defaults to 0. + :type trailers_count: int + + :param shipped_hazardous_goods: Truck routing only, list of hazardous + materials in the vehicle. Please refer to the enumeration type + HazardousGoodTypeType for available values. Note the value + allhazardousGoods does not apply to the request parameter. + https://developer.here.com/documentation/routing/topics/resource-type-enumerations.html#resource-type-enumerations__enum-hazardous-good-type-type + :type shipped_hazardous_goods: list of str + + :param limited_weight: Truck routing only, vehicle weight including + trailers and shipped goods, in tons. The provided value must be + between 0 and 1000. + :type limited_weight: int + + :param weight_per_axle: Truck routing only, vehicle weight per axle + in tons. The provided value must be between 0 and 1000. + :type limited_weight: int + + :param height: Truck routing only, vehicle height in meters. The + provided value must be between 0 and 50. + :type height: int + + :param width: Truck routing only, vehicle width in meters. + The provided value must be between 0 and 50. + :type width: int + + :param length: Truck routing only, vehicle length in meters. + The provided value must be between 0 and 300. + :type length: int + + :param tunnel_category: Truck routing only, specifies the tunnel + category to restrict certain route links. The route will pass + only through tunnels of a less strict category. + :type tunnel_category: list of str + + :param speed_profile: Specifies the speed profile variant for a given + routing mode. The speed profile affects travel time estimation as + well as roads evaluation when computing the fastest route. + Note that computed routes might differ depending on a used profile. + https://developer.here.com/documentation/routing/topics/resource-param-type-speed-profile-type.html + :type speed_profile: str + + :param dry_run: Print URL and parameters without sending the request. + :param dry_run: bool + + :returns: raw JSON response + :rtype: dict + """ + self.base_url = ( + "https://matrix.route.api.here.com/routing/7.2" + if self.api_key is None + else "https://matrix.route.ls.hereapi.com/routing/7.2" + ) params = self.auth.copy() locations = self._build_locations(locations) @@ -1273,7 +1297,7 @@ def matrix( # noqa: C901 params["destination" + str(i)] = location if isinstance(profile, str): - params["mode"] = mode_type + ';' + profile + params["mode"] = mode_type + ";" + profile elif isinstance(profile, self.RoutingMode): params["mode"] = profile.make_routing_mode() @@ -1286,30 +1310,34 @@ def matrix( # noqa: C901 convert._delimit_list( [ convert._delimit_list( - [convert._format_float(f) for f in list(reversed(pair))], ',' - ) for pair in bounding_box - ], ';' - ) for bounding_box in avoid_areas - ], '!' + [convert._format_float(f) for f in list(reversed(pair))], "," + ) + for pair in bounding_box + ], + ";", + ) + for bounding_box in avoid_areas + ], + "!", ) if avoid_links is not None: - params["avoidLinks"] = convert._delimit_list(avoid_links, ',') + params["avoidLinks"] = convert._delimit_list(avoid_links, ",") if avoid_turns is not None: params["avoidTurns"] = avoid_turns if exclude_countries is not None: - params["excludeCountries"] = convert._delimit_list(exclude_countries, ',') + params["excludeCountries"] = convert._delimit_list(exclude_countries, ",") if departure is not None: params["departure"] = departure.isoformat() if matrix_attributes is not None: - params["matrixAttributes"] = convert._delimit_list(matrix_attributes, ',') + params["matrixAttributes"] = convert._delimit_list(matrix_attributes, ",") if summary_attributes is not None: - params["summaryAttributes"] = convert._delimit_list(summary_attributes, ',') + params["summaryAttributes"] = convert._delimit_list(summary_attributes, ",") if truck_type is not None: params["truckType"] = truck_type @@ -1318,7 +1346,7 @@ def matrix( # noqa: C901 params["trailersCount"] = trailers_count if shipped_hazardous_goods is not None: - params["shippedHazardousGoods"] = convert._delimit_list(shipped_hazardous_goods, ',') + params["shippedHazardousGoods"] = convert._delimit_list(shipped_hazardous_goods, ",") if limited_weight is not None: params["limitedWeight"] = limited_weight @@ -1336,16 +1364,16 @@ def matrix( # noqa: C901 params["length"] = length if tunnel_category is not None: - params["tunnelCategory"] = convert._delimit_list(tunnel_category, ',') + params["tunnelCategory"] = convert._delimit_list(tunnel_category, ",") if speed_profile is not None: params["speedProfile"] = speed_profile return self._parse_matrix_json( self.client._request( - convert._delimit_list(["/calculatematrix", format], '.'), + convert._delimit_list(["/calculatematrix", format], "."), get_params=params, - dry_run=dry_run + dry_run=dry_run, ) ) @@ -1360,21 +1388,21 @@ def _parse_matrix_json(response): index_distances = [] next_ = None - mtx_objects = response['response']['matrixEntry'] + mtx_objects = response["response"]["matrixEntry"] length = len(mtx_objects) for index, obj in enumerate(mtx_objects): if index < (length - 1): next_ = mtx_objects[index + 1] - if 'travelTime' in obj['summary']: - index_durations.append(obj['summary']['travelTime']) + if "travelTime" in obj["summary"]: + index_durations.append(obj["summary"]["travelTime"]) else: - index_durations.append(obj['summary']['costfactor']) + index_durations.append(obj["summary"]["costfactor"]) - if 'distance' in obj['summary']: - index_distances.append(obj['summary']['distance']) + if "distance" in obj["summary"]: + index_distances.append(obj["summary"]["distance"]) - if next_['startIndex'] > obj['startIndex']: + if next_["startIndex"] > obj["startIndex"]: durations.append(index_durations) distances.append(index_distances) index_durations = [] @@ -1397,8 +1425,8 @@ def _build_locations(self, coordinates, matrix=False): if isinstance(locations, self.Waypoint): locations.append(locations._make_waypoint()) elif isinstance(locations, (list, tuple)): - wp = 'geo!' + convert._delimit_list( - [convert._format_float(f) for f in list(reversed(coord))], ',' + wp = "geo!" + convert._delimit_list( + [convert._format_float(f) for f in list(reversed(coord))], "," ) locations.append(wp) else: @@ -1410,8 +1438,8 @@ def _build_locations(self, coordinates, matrix=False): # Isochrones elif isinstance(coordinates[0], float): - center = 'geo!' + convert._delimit_list( - [convert._format_float(f) for f in list(reversed(coordinates))], ',' + center = "geo!" + convert._delimit_list( + [convert._format_float(f) for f in list(reversed(coordinates))], "," ) locations.append(center) # Isochrones using waypoint class diff --git a/routingpy/routers/mapbox_osrm.py b/routingpy/routers/mapbox_osrm.py index 296103f..73f298b 100644 --- a/routingpy/routers/mapbox_osrm.py +++ b/routingpy/routers/mapbox_osrm.py @@ -18,7 +18,7 @@ Core client functionality, common across all API requests. """ -from routingpy.base import DEFAULT +from routingpy.client_base import DEFAULT from routingpy.client_default import Client from routingpy import convert, utils from routingpy.direction import Direction, Directions @@ -29,7 +29,7 @@ class MapboxOSRM: """Performs requests to the OSRM API services.""" - _base_url = 'https://api.mapbox.com' + _base_url = "https://api.mapbox.com" def __init__( self, @@ -37,10 +37,10 @@ def __init__( user_agent=None, timeout=DEFAULT, retry_timeout=None, - requests_kwargs=None, retry_over_query_limit=False, skip_api_error=None, - client=Client + client=Client, + **client_kwargs ): """ Initializes a Mapbox OSRM client. @@ -93,8 +93,13 @@ def __init__( self.api_key = api_key self.client = client( - self._base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, - skip_api_error + self._base_url, + user_agent, + timeout, + retry_timeout, + retry_over_query_limit, + skip_api_error, + **client_kwargs ) def directions( # noqa: C901 @@ -118,7 +123,7 @@ def directions( # noqa: C901 voice_units=None, waypoint_names=None, waypoint_targets=None, - dry_run=None + dry_run=None, ): """Get directions between an origin point and a destination point. @@ -227,17 +232,17 @@ def directions( # noqa: C901 """ coords = convert._delimit_list( - [convert._delimit_list([convert._format_float(f) for f in pair]) for pair in locations], ';' + [convert._delimit_list([convert._format_float(f) for f in pair]) for pair in locations], ";" ) - params = {'coordinates': coords} + params = {"coordinates": coords} if radiuses: - params["radiuses"] = convert._delimit_list(radiuses, ';') + params["radiuses"] = convert._delimit_list(radiuses, ";") if bearings: params["bearings"] = convert._delimit_list( - [convert._delimit_list(pair) for pair in bearings], ';' + [convert._delimit_list(pair) for pair in bearings], ";" ) if alternatives is not None: @@ -259,39 +264,39 @@ def directions( # noqa: C901 params["overview"] = convert._convert_bool(overview) if exclude is not None: - params['exclude'] = exclude + params["exclude"] = exclude if approaches: - params['approaches'] = ';' + convert._delimit_list(approaches, ';') + params["approaches"] = ";" + convert._delimit_list(approaches, ";") if banner_instructions: - params['banner_instuctions'] = convert._convert_bool(banner_instructions) + params["banner_instuctions"] = convert._convert_bool(banner_instructions) if language: - params['language'] = language + params["language"] = language if roundabout_exits: - params['roundabout_exits'] = convert._convert_bool(roundabout_exits) + params["roundabout_exits"] = convert._convert_bool(roundabout_exits) if voice_instructions: - params['voide_instructions'] = convert._convert_bool(voice_instructions) + params["voide_instructions"] = convert._convert_bool(voice_instructions) if voice_units: - params['voice_units'] = voice_units + params["voice_units"] = voice_units if waypoint_names: - params['waypoint_names'] = convert._delimit_list(waypoint_names, ';') + params["waypoint_names"] = convert._delimit_list(waypoint_names, ";") if waypoint_targets: - params['waypoint_targets'] = ';' + convert._delimit_list( + params["waypoint_targets"] = ";" + convert._delimit_list( [ - convert._delimit_list([convert._format_float(f) - for f in pair]) + convert._delimit_list([convert._format_float(f) for f in pair]) for pair in waypoint_targets - ], ';' + ], + ";", ) - get_params = {'access_token': self.api_key} if self.api_key else {} + get_params = {"access_token": self.api_key} if self.api_key else {} return self._parse_direction_json( self.client._request( @@ -299,10 +304,10 @@ def directions( # noqa: C901 get_params=get_params, post_params=params, dry_run=dry_run, - requests_kwargs={"headers": { - "Content-Type": 'application/x-www-form-urlencoded' - }}, - ), alternatives, geometries + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ), + alternatives, + geometries, ) @staticmethod @@ -314,18 +319,16 @@ def _parse_direction_json(response, alternatives, geometry_format): return Direction() def _parse_geometry(route_geometry): - if geometry_format in (None, 'polyline'): + if geometry_format in (None, "polyline"): geometry = [ - list(reversed(coord)) - for coord in utils.decode_polyline5(route_geometry, is3d=False) + list(reversed(coord)) for coord in utils.decode_polyline5(route_geometry, is3d=False) ] - elif geometry_format == 'polyline6': + elif geometry_format == "polyline6": geometry = [ - list(reversed(coord)) - for coord in utils.decode_polyline6(route_geometry, is3d=False) + list(reversed(coord)) for coord in utils.decode_polyline6(route_geometry, is3d=False) ] - elif geometry_format == 'geojson': - geometry = route_geometry['coordinates'] + elif geometry_format == "geojson": + geometry = route_geometry["coordinates"] else: raise ValueError( "OSRM: parameter geometries needs one of ['polyline', 'polyline6', 'geojson" @@ -334,22 +337,22 @@ def _parse_geometry(route_geometry): if alternatives: routes = [] - for route in response['routes']: + for route in response["routes"]: routes.append( Direction( - geometry=_parse_geometry(route['geometry']), - duration=int(route['duration']), - distance=int(route['distance']), - raw=route + geometry=_parse_geometry(route["geometry"]), + duration=int(route["duration"]), + distance=int(route["distance"]), + raw=route, ) ) return Directions(routes, response) else: return Direction( - geometry=_parse_geometry(response['routes'][0]['geometry']), - duration=int(response['routes'][0]['duration']), - distance=int(response['routes'][0]['distance']), - raw=response + geometry=_parse_geometry(response["routes"][0]["geometry"]), + duration=int(response["routes"][0]["duration"]), + distance=int(response["routes"][0]["distance"]), + raw=response, ) def isochrones( @@ -361,7 +364,7 @@ def isochrones( polygons=None, denoise=None, generalize=None, - dry_run=None + dry_run=None, ): """Gets isochrones or equidistants for a range of time values around a given set of coordinates. @@ -401,29 +404,31 @@ def isochrones( """ params = { - "contours_minutes": convert._delimit_list([int(x / 60) for x in sorted(intervals)], ','), - 'access_token': self.api_key, - 'costing': profile + "contours_minutes": convert._delimit_list([int(x / 60) for x in sorted(intervals)], ","), + "access_token": self.api_key, + "costing": profile, } - locations_string = convert._delimit_list(locations, ',') + locations_string = convert._delimit_list(locations, ",") if contours_colors: - params["contours_colors"] = convert._delimit_list(contours_colors, ',') + params["contours_colors"] = convert._delimit_list(contours_colors, ",") if polygons is not None: - params['polygons'] = polygons + params["polygons"] = polygons if denoise: - params['denoise'] = denoise + params["denoise"] = denoise if generalize: - params['generalize'] = generalize + params["generalize"] = generalize return self._parse_isochrone_json( self.client._request( - "/isochrone/v1/" + profile + '/' + locations_string, get_params=params, dry_run=dry_run - ), intervals, locations + "/isochrone/v1/" + profile + "/" + locations_string, get_params=params, dry_run=dry_run + ), + intervals, + locations, ) @staticmethod @@ -433,11 +438,13 @@ def _parse_isochrone_json(response, intervals, locations): return Isochrones( [ Isochrone( - geometry=isochrone['geometry']['coordinates'], + geometry=isochrone["geometry"]["coordinates"], interval=intervals[idx], - center=locations - ) for idx, isochrone in enumerate(list(reversed(response['features']))) - ], response + center=locations, + ) + for idx, isochrone in enumerate(list(reversed(response["features"]))) + ], + response, ) def matrix( @@ -448,7 +455,7 @@ def matrix( destinations=None, annotations=None, fallback_speed=None, - dry_run=None + dry_run=None, ): """ Gets travel distance and time for a matrix of origins and destinations. @@ -489,28 +496,28 @@ def matrix( """ coords = convert._delimit_list( - [convert._delimit_list([convert._format_float(f) for f in pair]) for pair in locations], ';' + [convert._delimit_list([convert._format_float(f) for f in pair]) for pair in locations], ";" ) - params = {'access_token': self.api_key} + params = {"access_token": self.api_key} if sources: - params['sources'] = convert._delimit_list(sources, ';') + params["sources"] = convert._delimit_list(sources, ";") if destinations: - params['destinations'] = convert._delimit_list(destinations, ';') + params["destinations"] = convert._delimit_list(destinations, ";") if annotations: - params['annotations'] = convert._delimit_list(annotations) + params["annotations"] = convert._delimit_list(annotations) if fallback_speed: - params['fallback_speed'] = str(fallback_speed) + params["fallback_speed"] = str(fallback_speed) return self._parse_matrix_json( self.client._request( - "/directions-matrix/v1/mapbox/" + profile + '/' + coords, + "/directions-matrix/v1/mapbox/" + profile + "/" + coords, get_params=params, - dry_run=dry_run + dry_run=dry_run, ) ) @@ -520,5 +527,5 @@ def _parse_matrix_json(response): return Matrix() return Matrix( - durations=response.get('durations'), distances=response.get('distances'), raw=response + durations=response.get("durations"), distances=response.get("distances"), raw=response ) diff --git a/routingpy/routers/mapbox_valhalla.py b/routingpy/routers/mapbox_valhalla.py index 6b696ef..ccce107 100644 --- a/routingpy/routers/mapbox_valhalla.py +++ b/routingpy/routers/mapbox_valhalla.py @@ -15,14 +15,14 @@ # the License. from .valhalla import Valhalla -from routingpy.base import DEFAULT +from routingpy.client_base import DEFAULT from routingpy.client_default import Client class MapboxValhalla(Valhalla): """Performs requests to Mapbox's Valhalla instance.""" - _base_url = 'https://api.mapbox.com/valhalla/v1' + _base_url = "https://api.mapbox.com/valhalla/v1" def __init__( self, @@ -30,10 +30,10 @@ def __init__( user_agent=None, timeout=DEFAULT, retry_timeout=None, - requests_kwargs=None, retry_over_query_limit=False, skip_api_error=None, - client=Client + client=Client, + **client_kwargs ): """ Initializes a Valhalla client. @@ -84,6 +84,13 @@ def __init__( """ super(MapboxValhalla, self).__init__( - self._base_url, api_key, user_agent, timeout, retry_timeout, requests_kwargs, - retry_over_query_limit, skip_api_error, client=client + self._base_url, + api_key, + user_agent, + timeout, + retry_timeout, + retry_over_query_limit, + skip_api_error, + client=client, + **client_kwargs ) diff --git a/routingpy/routers/openrouteservice.py b/routingpy/routers/openrouteservice.py index 0eb3e33..dddab0a 100644 --- a/routingpy/routers/openrouteservice.py +++ b/routingpy/routers/openrouteservice.py @@ -15,7 +15,7 @@ # the License. # -from routingpy.base import DEFAULT +from routingpy.client_base import DEFAULT from routingpy.client_default import Client from routingpy import utils from routingpy.direction import Direction, Directions @@ -26,7 +26,7 @@ class ORS: """Performs requests to the ORS API services.""" - _DEFAULT_BASE_URL = 'https://api.openrouteservice.org' + _DEFAULT_BASE_URL = "https://api.openrouteservice.org" def __init__( self, @@ -35,10 +35,10 @@ def __init__( user_agent=None, timeout=DEFAULT, retry_timeout=None, - requests_kwargs=None, retry_over_query_limit=False, skip_api_error=None, - client=Client + client=Client, + **client_kwargs ): """ Initializes an openrouteservice client. @@ -95,21 +95,26 @@ def __init__( if base_url == self._DEFAULT_BASE_URL and api_key is None: raise KeyError("API key must be specified.") - requests_kwargs = requests_kwargs or {} - headers = requests_kwargs.get('headers') or {} - headers.update({'Authorization': api_key}) - requests_kwargs.update({'headers': headers}) + client_kwargs = client_kwargs or {} + headers = client_kwargs.get("headers") or {} + headers.update({"Authorization": api_key}) + client_kwargs.update({"headers": headers}) self.client = client( - base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, - skip_api_error + base_url, + user_agent, + timeout, + retry_timeout, + retry_over_query_limit, + skip_api_error, + **client_kwargs ) def directions( # noqa: C901 self, locations, profile, - format='geojson', + format="geojson", preference=None, alternative_routes=None, units=None, @@ -128,7 +133,7 @@ def directions( # noqa: C901 extra_info=None, suppress_warnings=None, options=None, - dry_run=None + dry_run=None, ): """Get directions between an origin point and a destination point. @@ -249,12 +254,16 @@ def directions( # noqa: C901 if alternative_routes: if not isinstance(alternative_routes, dict): raise TypeError("alternative_routes must be a dict.") - if not all([key in alternative_routes.keys() - for key in ['share_factor', 'target_count', 'weight_factor']]): + if not all( + [ + key in alternative_routes.keys() + for key in ["share_factor", "target_count", "weight_factor"] + ] + ): raise ValueError( "alternative_routes needs 'share_factor', 'target_count', 'weight_factor' keys" ) - params['alternative_routes'] = alternative_routes + params["alternative_routes"] = alternative_routes if units: params["units"] = units @@ -284,7 +293,7 @@ def directions( # noqa: C901 params["radiuses"] = radiuses if maneuvers is not None: - params['maneuvers'] = maneuvers + params["maneuvers"] = maneuvers if bearings: params["bearings"] = bearings @@ -299,23 +308,26 @@ def directions( # noqa: C901 params["extra_info"] = extra_info if suppress_warnings is not None: - params['suppress_warnings'] = suppress_warnings + params["suppress_warnings"] = suppress_warnings if options: - if profile == 'driving-hgv' and options.get('profile_params'): - if options['profile_params'].get('restrictions') and not options.get('vehicle_type'): + if profile == "driving-hgv" and options.get("profile_params"): + if options["profile_params"].get("restrictions") and not options.get("vehicle_type"): raise ValueError( "ORS: options.vehicle_type must be specified for driving-hgv if restrictions are set." ) - params['options'] = options + params["options"] = options return self._parse_direction_json( self.client._request( - "/v2/directions/" + profile + '/' + format, + "/v2/directions/" + profile + "/" + format, get_params={}, post_params=params, - dry_run=dry_run - ), format, units, alternative_routes + dry_run=dry_run, + ), + format, + units, + alternative_routes, ) @staticmethod @@ -324,52 +336,52 @@ def _parse_direction_json(response, format, units, alternative_routes): return Direction() units_factor = 1 - if units == 'mi': + if units == "mi": units_factor = 0.621371 * 1000 - elif units == 'km': + elif units == "km": units_factor = 1000 - if format == 'geojson': + if format == "geojson": if alternative_routes: routes = [] - for route in response['features']: + for route in response["features"]: routes.append( Direction( - geometry=route['geometry']['coordinates'], - distance=int(route['properties']['summary']['distance']), - duration=int(route['properties']['summary']['duration']), - raw=route + geometry=route["geometry"]["coordinates"], + distance=int(route["properties"]["summary"]["distance"]), + duration=int(route["properties"]["summary"]["duration"]), + raw=route, ) ) return Directions(routes, response) else: - geometry = response['features'][0]['geometry']['coordinates'] - duration = int(response['features'][0]['properties']['summary']['duration']) - distance = int(response['features'][0]['properties']['summary']['distance']) + geometry = response["features"][0]["geometry"]["coordinates"] + duration = int(response["features"][0]["properties"]["summary"]["duration"]) + distance = int(response["features"][0]["properties"]["summary"]["distance"]) return Direction(geometry=geometry, duration=duration, distance=distance, raw=response) - elif format == 'json': + elif format == "json": if alternative_routes: routes = [] - for route in response['routes']: + for route in response["routes"]: geometry = [ - list(reversed(coord)) for coord in utils.decode_polyline5(route['geometry']) + list(reversed(coord)) for coord in utils.decode_polyline5(route["geometry"]) ] routes.append( Direction( geometry=geometry, - distance=int(route['summary']['distance']), - duration=int(route['summary']['duration'] * units_factor), - raw=route + distance=int(route["summary"]["distance"]), + duration=int(route["summary"]["duration"] * units_factor), + raw=route, ) ) return Directions(routes, response) else: geometry = [ list(reversed(coord)) - for coord in utils.decode_polyline5(response['routes'][0]['geometry']) + for coord in utils.decode_polyline5(response["routes"][0]["geometry"]) ] - duration = int(response['routes'][0]['summary']['duration']) - distance = int(response['routes'][0]['summary']['distance'] * units_factor) + duration = int(response["routes"][0]["summary"]["duration"]) + distance = int(response["routes"][0]["summary"]["distance"] * units_factor) return Direction(geometry=geometry, duration=duration, distance=distance, raw=response) @@ -384,7 +396,7 @@ def isochrones( smoothing=None, attributes=None, intersections=None, - dry_run=None + dry_run=None, ): """Gets isochrones or equidistants for a range of time/distance values around a given set of coordinates. @@ -459,10 +471,10 @@ def isochrones( return self._parse_isochrone_json( self.client._request( - "/v2/isochrones/" + profile + '/geojson', + "/v2/isochrones/" + profile + "/geojson", get_params={}, post_params=params, - dry_run=dry_run + dry_run=dry_run, ) ) @@ -472,12 +484,12 @@ def _parse_isochrone_json(response): return Isochrones() isochrones = [] - for idx, isochrone in enumerate(response['features']): + for idx, isochrone in enumerate(response["features"]): isochrones.append( Isochrone( - geometry=isochrone['geometry']['coordinates'][0], - interval=isochrone['properties']['value'], - center=isochrone['properties']['center'] + geometry=isochrone["geometry"]["coordinates"][0], + interval=isochrone["properties"]["value"], + center=isochrone["properties"]["center"], ) ) @@ -492,9 +504,9 @@ def matrix( metrics=None, resolve_locations=None, units=None, - dry_run=None + dry_run=None, ): - """ Gets travel distance and time for a matrix of origins and destinations. + """Gets travel distance and time for a matrix of origins and destinations. :param locations: Two or more pairs of lng/lat values. :type locations: list of list @@ -537,10 +549,10 @@ def matrix( params = {"locations": locations, "profile": profile} if sources: - params['sources'] = sources + params["sources"] = sources if destinations: - params['destinations'] = destinations + params["destinations"] = destinations if metrics: params["metrics"] = metrics @@ -553,7 +565,7 @@ def matrix( return self._parse_matrix_json( self.client._request( - "/v2/matrix/" + profile + '/json', get_params={}, post_params=params, dry_run=dry_run + "/v2/matrix/" + profile + "/json", get_params={}, post_params=params, dry_run=dry_run ) ) @@ -561,6 +573,6 @@ def matrix( def _parse_matrix_json(response): if response is None: # pragma: no cover return Matrix() - durations = response.get('durations') - distances = response.get('distances') + durations = response.get("durations") + distances = response.get("distances") return Matrix(durations=durations, distances=distances, raw=response) diff --git a/routingpy/routers/osrm.py b/routingpy/routers/osrm.py index ece245a..d8e44c7 100644 --- a/routingpy/routers/osrm.py +++ b/routingpy/routers/osrm.py @@ -17,7 +17,7 @@ from typing import List # noqa: F401 -from routingpy.base import DEFAULT +from routingpy.client_base import DEFAULT from routingpy.client_default import Client from routingpy import convert, utils from routingpy.direction import Directions, Direction @@ -27,7 +27,7 @@ class OSRM: """Performs requests to the OSRM API services.""" - _DEFAULT_BASE_URL = 'https://router.project-osrm.org' + _DEFAULT_BASE_URL = "https://router.project-osrm.org" def __init__( self, @@ -35,10 +35,10 @@ def __init__( user_agent=None, timeout=DEFAULT, retry_timeout=None, - requests_kwargs=None, retry_over_query_limit=False, skip_api_error=None, - client=Client + client=Client, + **client_kwargs ): """ Initializes an OSRM client. @@ -90,8 +90,13 @@ def __init__( """ self.client = client( - base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, - skip_api_error + base_url, + user_agent, + timeout, + retry_timeout, + retry_over_query_limit, + skip_api_error, + **client_kwargs ) def directions( @@ -106,7 +111,7 @@ def directions( annotations=None, geometries=None, overview=None, - dry_run=None + dry_run=None, ): """Get directions between an origin point and a destination point. @@ -167,17 +172,17 @@ def directions( """ coords = convert._delimit_list( - [convert._delimit_list([convert._format_float(f) for f in pair]) for pair in locations], ';' + [convert._delimit_list([convert._format_float(f) for f in pair]) for pair in locations], ";" ) params = dict() if radiuses: - params["radiuses"] = convert._delimit_list(radiuses, ';') + params["radiuses"] = convert._delimit_list(radiuses, ";") if bearings: params["bearings"] = convert._delimit_list( - [convert._delimit_list(pair) for pair in bearings], ';' + [convert._delimit_list(pair) for pair in bearings], ";" ) if alternatives is not None: @@ -199,8 +204,11 @@ def directions( params["overview"] = convert._convert_bool(overview) return self._parse_direction_json( - self.client._request("/route/v1/" + profile + '/' + coords, get_params=params, dry_run=dry_run), - alternatives, geometries + self.client._request( + "/route/v1/" + profile + "/" + coords, get_params=params, dry_run=dry_run + ), + alternatives, + geometries, ) @staticmethod @@ -212,12 +220,12 @@ def _parse_direction_json(response, alternatives, geometry_format): return Direction() def _parse_geometry(route_geometry): - if geometry_format in (None, 'polyline'): + if geometry_format in (None, "polyline"): geometry = utils.decode_polyline5(route_geometry, is3d=False) - elif geometry_format == 'polyline6': + elif geometry_format == "polyline6": geometry = utils.decode_polyline6(route_geometry, is3d=False) - elif geometry_format == 'geojson': - geometry = route_geometry['coordinates'] + elif geometry_format == "geojson": + geometry = route_geometry["coordinates"] else: raise ValueError( "OSRM: parameter geometries needs one of ['polyline', 'polyline6', 'geojson" @@ -226,22 +234,22 @@ def _parse_geometry(route_geometry): if alternatives: routes = [] - for route in response['routes']: + for route in response["routes"]: routes.append( Direction( - geometry=_parse_geometry(route['geometry']), - duration=int(route['duration']), - distance=int(route['distance']), - raw=route + geometry=_parse_geometry(route["geometry"]), + duration=int(route["duration"]), + distance=int(route["distance"]), + raw=route, ) ) return Directions(routes, response) else: return Direction( - geometry=_parse_geometry(response['routes'][0]['geometry']), - duration=int(response['routes'][0]['duration']), - distance=int(response['routes'][0]['distance']), - raw=response + geometry=_parse_geometry(response["routes"][0]["geometry"]), + duration=int(response["routes"][0]["duration"]), + distance=int(response["routes"][0]["distance"]), + raw=response, ) def isochrones(self): # pragma: no cover @@ -256,7 +264,7 @@ def matrix( sources=None, destinations=None, dry_run=None, - annotations=['duration', 'distance'] + annotations=["duration", "distance"], ): """ Gets travel distance and time for a matrix of origins and destinations. @@ -311,22 +319,24 @@ def matrix( """ coords = convert._delimit_list( - [convert._delimit_list([convert._format_float(f) for f in pair]) for pair in locations], ';' + [convert._delimit_list([convert._format_float(f) for f in pair]) for pair in locations], ";" ) params = dict() if sources: - params['sources'] = convert._delimit_list(sources, ';') + params["sources"] = convert._delimit_list(sources, ";") if destinations: - params['destinations'] = convert._delimit_list(destinations, ';') + params["destinations"] = convert._delimit_list(destinations, ";") if annotations: - params['annotations'] = convert._delimit_list(annotations) + params["annotations"] = convert._delimit_list(annotations) return self._parse_matrix_json( - self.client._request("/table/v1/" + profile + '/' + coords, get_params=params, dry_run=dry_run) + self.client._request( + "/table/v1/" + profile + "/" + coords, get_params=params, dry_run=dry_run + ) ) @staticmethod @@ -335,5 +345,5 @@ def _parse_matrix_json(response): return Matrix() return Matrix( - durations=response.get('durations'), distances=response.get('distances'), raw=response + durations=response.get("durations"), distances=response.get("distances"), raw=response ) diff --git a/routingpy/routers/valhalla.py b/routingpy/routers/valhalla.py index 7f4a31b..a60366a 100644 --- a/routingpy/routers/valhalla.py +++ b/routingpy/routers/valhalla.py @@ -20,7 +20,7 @@ from typing import List, Union # noqa: F401 -from routingpy.base import DEFAULT +from routingpy.client_base import DEFAULT from routingpy.client_default import Client from routingpy import utils from routingpy.direction import Direction @@ -32,6 +32,7 @@ class Valhalla: """Performs requests to a Valhalla instance.""" + def __init__( self, base_url, @@ -39,10 +40,10 @@ def __init__( user_agent=None, timeout=DEFAULT, retry_timeout=None, - requests_kwargs=None, retry_over_query_limit=False, skip_api_error=None, - client=Client + client=Client, + **client_kwargs ): """ Initializes a Valhalla client. @@ -99,8 +100,13 @@ def __init__( self.api_key = api_key self.client = client( - base_url, user_agent, timeout, retry_timeout, requests_kwargs, retry_over_query_limit, - skip_api_error + base_url, + user_agent, + timeout, + retry_timeout, + retry_over_query_limit, + skip_api_error, + **client_kwargs ) class Waypoint(object): @@ -116,13 +122,14 @@ class Waypoint(object): >>> waypoint = Valhalla.WayPoint(position=[8.15315, 52.53151], type='through', heading=120, heading_tolerance=10, minimum_reachability=10, radius=400) >>> route = Valhalla('http://localhost').directions(locations=[[[8.58232, 51.57234]], waypoint, [7.15315, 53.632415]]) """ + def __init__(self, position, **kwargs): self._position = position self._kwargs = kwargs def _make_waypoint(self): - waypoint = {'lon': self._position[0], 'lat': self._position[1]} + waypoint = {"lon": self._position[0], "lat": self._position[1]} for k, v in self._kwargs.items(): waypoint[k] = v @@ -199,42 +206,43 @@ def directions( params = dict(costing=profile) - params['locations'] = self._build_locations(locations) + params["locations"] = self._build_locations(locations) if options or preference: - params['costing_options'] = dict() - profile = profile if profile not in ('multimodal', 'transit') else 'transit' - params['costing_options'][profile] = dict() + params["costing_options"] = dict() + profile = profile if profile not in ("multimodal", "transit") else "transit" + params["costing_options"][profile] = dict() if options: - params['costing_options'][profile] = options - if preference == 'shortest': - params['costing_options'][profile]['shortest'] = True + params["costing_options"][profile] = options + if preference == "shortest": + params["costing_options"][profile]["shortest"] = True if any((units, language, directions_type)): - params['directions_options'] = dict() + params["directions_options"] = dict() if units: - params['directions_options']['units'] = units + params["directions_options"]["units"] = units if language: - params['directions_options']['language'] = language + params["directions_options"]["language"] = language if directions_type: - params['directions_options']['directions_type'] = directions_type + params["directions_options"]["directions_type"] = directions_type if avoid_locations: - params['avoid_locations'] = self._build_locations(avoid_locations) + params["avoid_locations"] = self._build_locations(avoid_locations) if avoid_polygons: - params['avoid_polygons'] = avoid_polygons + params["avoid_polygons"] = avoid_polygons if date_time: - params['date_time'] = date_time + params["date_time"] = date_time if id: - params['id'] = id + params["id"] = id - get_params = {'access_token': self.api_key} if self.api_key else {} + get_params = {"access_token": self.api_key} if self.api_key else {} return self._parse_direction_json( - self.client._request("/route", get_params=get_params, post_params=params, dry_run=dry_run), units + self.client._request("/route", get_params=get_params, post_params=params, dry_run=dry_run), + units, ) @staticmethod @@ -243,12 +251,12 @@ def _parse_direction_json(response, units): return Direction() geometry, duration, distance = [], 0, 0 - for leg in response['trip']['legs']: - geometry.extend([list(reversed(coord)) for coord in utils.decode_polyline6(leg['shape'])]) - duration += leg['summary']['time'] + for leg in response["trip"]["legs"]: + geometry.extend([list(reversed(coord)) for coord in utils.decode_polyline6(leg["shape"])]) + duration += leg["summary"]["time"] - factor = 0.621371 if units == 'mi' else 1 - distance += int(leg['summary']['length'] * 1000 * factor) + factor = 0.621371 if units == "mi" else 1 + distance += int(leg["summary"]["length"] * 1000 * factor) return Direction(geometry=geometry, duration=int(duration), distance=int(distance), raw=response) @@ -272,7 +280,7 @@ def isochrones( # noqa: C901 date_time=None, show_locations=None, id=None, - dry_run=None + dry_run=None, ): """Gets isochrones or equidistants for a range of time values around a given set of coordinates. @@ -358,10 +366,10 @@ def isochrones( # noqa: C901 contours = [] for idx, r in enumerate(intervals): - key = 'time' + key = "time" value = r / 60 - if interval_type == 'distance': - key = 'distance' + if interval_type == "distance": + key = "distance" value = r / 1000 d = {key: value} @@ -379,42 +387,45 @@ def isochrones( # noqa: C901 } if options: - params['costing_options'] = dict() - profile = profile if profile != 'multimodal' else 'transit' - params['costing_options'][profile] = dict() + params["costing_options"] = dict() + profile = profile if profile != "multimodal" else "transit" + params["costing_options"][profile] = dict() if options: - params['costing_options'][profile] = options - if preference == 'shortest': - params['costing_options'][profile]['shortest'] = True + params["costing_options"][profile] = options + if preference == "shortest": + params["costing_options"][profile]["shortest"] = True if polygons is not None: - params['polygons'] = polygons + params["polygons"] = polygons if denoise: - params['denoise'] = denoise + params["denoise"] = denoise if generalize: - params['generalize'] = generalize + params["generalize"] = generalize if avoid_locations: - params['avoid_locations'] = self._build_locations(avoid_locations) + params["avoid_locations"] = self._build_locations(avoid_locations) if avoid_polygons: - params['avoid_polygons'] = avoid_polygons + params["avoid_polygons"] = avoid_polygons if date_time: - params['date_time'] = date_time + params["date_time"] = date_time if show_locations is not None: - params['show_locations'] = show_locations + params["show_locations"] = show_locations if id: - params['id'] = id + params["id"] = id - get_params = {'access_token': self.api_key} if self.api_key else {} + get_params = {"access_token": self.api_key} if self.api_key else {} return self._parse_isochrone_json( - self.client._request("/isochrone", get_params=get_params, post_params=params, dry_run=dry_run), - intervals, locations + self.client._request( + "/isochrone", get_params=get_params, post_params=params, dry_run=dry_run + ), + intervals, + locations, ) @staticmethod @@ -423,13 +434,13 @@ def _parse_isochrone_json(response, intervals, locations): return Isochrones() isochrones = [] - for idx, feature in enumerate(reversed(response['features'])): - if feature['geometry']['type'] in ('LineString', 'Polygon'): + for idx, feature in enumerate(reversed(response["features"])): + if feature["geometry"]["type"] in ("LineString", "Polygon"): isochrones.append( Isochrone( - geometry=feature['geometry']['coordinates'], + geometry=feature["geometry"]["coordinates"], interval=intervals[idx], - center=locations + center=locations, ) ) @@ -447,7 +458,7 @@ def matrix( avoid_polygons=None, units=None, id=None, - dry_run=None + dry_run=None, ): """ Gets travel distance and time for a matrix of origins and destinations. @@ -502,10 +513,10 @@ def matrix( :returns: A matrix from the specified sources and destinations. :rtype: :class:`routingpy.matrix.Matrix` - """ + """ params = { - 'costing': profile, + "costing": profile, } locations = self._build_locations(locations) @@ -515,42 +526,43 @@ def matrix( sources_coords = itemgetter(*sources)(sources_coords) if isinstance(sources_coords, dict): sources_coords = [sources_coords] - params['sources'] = sources_coords + params["sources"] = sources_coords dest_coords = locations if destinations is not None: dest_coords = itemgetter(*destinations)(dest_coords) if isinstance(dest_coords, dict): dest_coords = [dest_coords] - params['targets'] = dest_coords + params["targets"] = dest_coords if options: - params['costing_options'] = dict() - profile = profile if profile != 'multimodal' else 'transit' - params['costing_options'][profile] = dict() + params["costing_options"] = dict() + profile = profile if profile != "multimodal" else "transit" + params["costing_options"][profile] = dict() if options: - params['costing_options'][profile] = options - if preference == 'shortest': - params['costing_options'][profile]['shortest'] = True + params["costing_options"][profile] = options + if preference == "shortest": + params["costing_options"][profile]["shortest"] = True if avoid_locations: - params['avoid_locations'] = self._build_locations(avoid_locations) + params["avoid_locations"] = self._build_locations(avoid_locations) if avoid_polygons: - params['avoid_polygons'] = avoid_polygons + params["avoid_polygons"] = avoid_polygons if units: params["units"] = units if id: - params['id'] = id + params["id"] = id - get_params = {'access_token': self.api_key} if self.api_key else {} + get_params = {"access_token": self.api_key} if self.api_key else {} return self._parse_matrix_json( self.client._request( - '/sources_to_targets', get_params=get_params, post_params=params, dry_run=dry_run - ), units + "/sources_to_targets", get_params=get_params, post_params=params, dry_run=dry_run + ), + units, ) @staticmethod @@ -558,14 +570,13 @@ def _parse_matrix_json(response, units): if response is None: # pragma: no cover return Matrix() - factor = 0.621371 if units == 'mi' else 1 + factor = 0.621371 if units == "mi" else 1 durations = [ - [destination['time'] for destination in origin] for origin in response['sources_to_targets'] + [destination["time"] for destination in origin] for origin in response["sources_to_targets"] ] distances = [ - [int(destination['distance'] * 1000 * factor) - for destination in origin] - for origin in response['sources_to_targets'] + [int(destination["distance"] * 1000 * factor) for destination in origin] + for origin in response["sources_to_targets"] ] return Matrix(durations=durations, distances=distances, raw=response) @@ -579,7 +590,7 @@ def _build_locations(self, coordinates): if isinstance(coordinates[0], (list, tuple, self.Waypoint)): for idx, coord in enumerate(coordinates): if isinstance(coord, (list, tuple)): - locations.append({'lon': coord[0], 'lat': coord[1]}), + locations.append({"lon": coord[0], "lat": coord[1]}), elif isinstance(coord, self.Waypoint): locations.append(coord._make_waypoint()) else: @@ -589,6 +600,6 @@ def _build_locations(self, coordinates): ) ) elif isinstance(coordinates[0], float): - locations.append({'lon': coordinates[0], 'lat': coordinates[1]}) + locations.append({"lon": coordinates[0], "lat": coordinates[1]}) return locations diff --git a/tests/test_base.py b/tests/test_base.py index d26ce08..48653ec 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -43,35 +43,32 @@ def matrix(self): class BaseTest(_test.TestCase): def setUp(self): self.client = ClientMock("https://httpbin.org/") - self.params = {'c': 'd', 'a': 'b', '1': '2'} + self.params = {"c": "d", "a": "b", "1": "2"} def test_router_by_name(self): for s in routingpy.routers._SERVICE_TO_ROUTER.keys(): routingpy.routers.get_router_by_name(s) with self.assertRaises(routingpy.exceptions.RouterNotFound): - routingpy.routers.get_router_by_name('orsm') + routingpy.routers.get_router_by_name("orsm") def test_options(self): options.default_user_agent = "my_agent" options.default_timeout = 10 options.default_retry_timeout = 10 options.default_retry_over_query_limit = False - options.default_proxies = {'https': '192.103.10.102'} - new_client = ClientMock('https://foo.bar') + options.default_proxies = {"https": "192.103.10.102"} + new_client = ClientMock("https://foo.bar") req_kwargs = { - 'timeout': options.default_timeout, - 'headers': { - 'User-Agent': options.default_user_agent, - 'Content-Type': 'application/json' - }, - 'proxies': options.default_proxies + "timeout": options.default_timeout, + "headers": {"User-Agent": options.default_user_agent, "Content-Type": "application/json"}, + "proxies": options.default_proxies, } - self.assertEqual(req_kwargs, new_client.requests_kwargs) + self.assertEqual(req_kwargs, new_client.kwargs) self.assertEqual(new_client.retry_over_query_limit, options.default_retry_over_query_limit) def test_urlencode(self): - encoded_params = self.client._generate_auth_url('directions', self.params) + encoded_params = self.client._generate_auth_url("directions", self.params) self.assertEqual("directions?1=2&a=b&c=d", encoded_params) @responses.activate @@ -79,19 +76,19 @@ def test_skip_api_error(self): query = self.params responses.add( responses.POST, - 'https://httpbin.org/post', + "https://httpbin.org/post", json=query, status=400, - content_type='application/json' + content_type="application/json", ) client = ClientMock(base_url="https://httpbin.org", skip_api_error=False) print(client.skip_api_error) with self.assertRaises(routingpy.exceptions.RouterApiError): - client.directions(url='/post', post_params=self.params) + client.directions(url="/post", post_params=self.params) client = ClientMock(base_url="https://httpbin.org", skip_api_error=True) - client.directions(url='/post', post_params=self.params) + client.directions(url="/post", post_params=self.params) self.assertEqual(responses.calls[1].response.json(), query) @responses.activate @@ -99,30 +96,30 @@ def test_retry_timeout(self): query = self.params responses.add( responses.POST, - 'https://httpbin.org/post', + "https://httpbin.org/post", json=query, status=429, - content_type='application/json' + content_type="application/json", ) client = ClientMock(base_url="https://httpbin.org", retry_over_query_limit=True, retry_timeout=3) with self.assertRaises(routingpy.exceptions.OverQueryLimit): - client.directions(url='/post', post_params=query) + client.directions(url="/post", post_params=query) @responses.activate def test_raise_over_query_limit(self): query = self.params responses.add( responses.POST, - 'https://httpbin.org/post', + "https://httpbin.org/post", json=query, status=429, - content_type='application/json' + content_type="application/json", ) with self.assertRaises(routingpy.exceptions.OverQueryLimit): client = ClientMock(base_url="https://httpbin.org", retry_over_query_limit=False) - client.directions(url='/post', post_params=query) + client.directions(url="/post", post_params=query) @responses.activate def test_raise_timeout_retriable_requests(self): @@ -132,17 +129,17 @@ def test_raise_timeout_retriable_requests(self): query = self.params responses.add( responses.POST, - 'https://httpbin.org/post', + "https://httpbin.org/post", json=query, status=503, - content_type='application/json' + content_type="application/json", ) client = ClientMock(base_url="https://httpbin.org", retry_timeout=retry_timeout) start = time.time() with self.assertRaises(routingpy.exceptions.Timeout): - client.directions(url='/post', post_params=self.params) + client.directions(url="/post", post_params=self.params) end = time.time() self.assertTrue(retry_timeout < end - start < 2 * retry_timeout) @@ -152,26 +149,26 @@ def test_dry_run(self): responses.add( responses.POST, - 'https://api.openrouteservice.org/directions', + "https://api.openrouteservice.org/directions", json=None, status=200, - content_type='application/json' + content_type="application/json", ) - self.client.directions(get_params={'format_out': 'geojson'}, url='directions/', dry_run='true') + self.client.directions(get_params={"format_out": "geojson"}, url="directions/", dry_run="true") self.assertEqual(0, len(responses.calls)) def test_headers(self): # Test that existing request_kwargs keys are not scrubbed - timeout = {'holaDieWaldFee': 600} - headers = {'headers': {'X-Rate-Limit': '50'}} + timeout = {"holaDieWaldFee": 600} + headers = {"headers": {"X-Rate-Limit": "50"}} - client = ClientMock("https://httpbin.org", requests_kwargs=dict(timeout, **headers)) + client = ClientMock("https://httpbin.org", **dict(timeout, **headers)) - self.assertDictContainsSubset(timeout, client.requests_kwargs) - self.assertDictContainsSubset(headers['headers'], client.requests_kwargs['headers']) + self.assertDictContainsSubset(timeout, client.kwargs) + self.assertDictContainsSubset(headers["headers"], client.kwargs["headers"]) @responses.activate def test_req_property(self): @@ -179,13 +176,13 @@ def test_req_property(self): responses.add( responses.GET, - 'https://httpbin.org/routes?a=b', + "https://httpbin.org/routes?a=b", json={}, status=200, - content_type='application/json' + content_type="application/json", ) - self.client.directions(url='routes', get_params={'a': 'b'}) + self.client.directions(url="routes", get_params={"a": "b"}) assert isinstance(self.client.req, requests.PreparedRequest) - self.assertEqual('https://httpbin.org/routes?a=b', self.client.req.url) + self.assertEqual("https://httpbin.org/routes?a=b", self.client.req.url) diff --git a/tests/test_graphhopper.py b/tests/test_graphhopper.py index 267b367..45637b1 100644 --- a/tests/test_graphhopper.py +++ b/tests/test_graphhopper.py @@ -208,4 +208,4 @@ def test_index_destinations_matrix(self): query = deepcopy(ENDPOINTS_QUERIES[self.name]["matrix"]) query["destinations"] = [100] - self.assertRaises(IndexError, lambda: self.client.matrix(**query)) \ No newline at end of file + self.assertRaises(IndexError, lambda: self.client.matrix(**query)) From a8efdfbfcb8a6e7550d926afd15beb35bffbb753 Mon Sep 17 00:00:00 2001 From: chrstnbwnkl Date: Mon, 21 Jun 2021 17:23:20 +0200 Subject: [PATCH 6/8] code formatting --- routingpy/client_base.py | 58 +++++++++++++++++++++------ routingpy/client_default.py | 35 +++------------- routingpy/routers/google.py | 26 ++++-------- routingpy/routers/graphhopper.py | 21 +++------- routingpy/routers/heremaps.py | 20 ++------- routingpy/routers/mapbox_osrm.py | 21 +++------- routingpy/routers/mapbox_valhalla.py | 20 ++------- routingpy/routers/openrouteservice.py | 20 ++------- routingpy/routers/osrm.py | 20 ++------- routingpy/routers/valhalla.py | 20 ++------- 10 files changed, 88 insertions(+), 173 deletions(-) diff --git a/routingpy/client_base.py b/routingpy/client_base.py index 00c0326..46fccb6 100644 --- a/routingpy/client_base.py +++ b/routingpy/client_base.py @@ -115,17 +115,6 @@ def __init__( seconds. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. - - Example: - - >>> from routingpy.routers import ORS - >>> router = ORS(my_key, requests_kwargs={'proxies': {'https': '129.125.12.0'}}) - >>> print(router.proxies) - {'https': '129.125.12.0'} - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. @@ -134,6 +123,9 @@ def __init__( :param skip_api_error: Continue with batch processing if a :class:`routingpy.exceptions.RouterApiError` is encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default False. :type skip_api_error: bool + + :param **kwargs: Additional keyword arguments + :type **kwargs: dict """ self.base_url = base_url @@ -158,8 +150,48 @@ def __init__( self._req = None @abstractmethod - def _request(self): - """Must be implemented for inheriting client classes.""" + def _request( + self, + url, + get_params={}, + post_params=None, + first_request_time=None, + retry_counter=0, + dry_run=None, + ): + """Performs HTTP GET/POST with credentials, returning the body as + JSON. + + :param url: URL path for the request. Should begin with a slash. + :type url: string + + :param get_params: HTTP GET parameters. + :type get_params: dict or list of tuples + + :param post_params: HTTP POST parameters. Only specified by calling method. + :type post_params: dict + + :param first_request_time: The time of the first request (None if no + retries have occurred). + :type first_request_time: :class:`datetime.datetime` + + :param retry_counter: The number of this retry, or zero for first attempt. + :type retry_counter: int + + :param dry_run: If 'true', only prints URL and parameters. 'true' or 'false'. + :type dry_run: string + + :raises routingpy.exceptions.RouterApiError: when the API returns an error due to faulty configuration. + :raises routingpy.exceptions.RouterServerError: when the API returns a server error. + :raises routingpy.exceptions.RouterError: when anything else happened while requesting. + :raises routingpy.exceptions.JSONParseError: when the JSON response can't be parsed. + :raises routingpy.exceptions.Timeout: when the request timed out. + :raises routingpy.exceptions.TransportError: when something went wrong while trying to + execute a request. + + :returns: raw JSON response. + :rtype: dict + """ pass @staticmethod diff --git a/routingpy/client_default.py b/routingpy/client_default.py index a687a9a..5dcb2d0 100644 --- a/routingpy/client_default.py +++ b/routingpy/client_default.py @@ -57,19 +57,6 @@ def __init__( seconds. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. - - Example: - - >>> from routingpy.routers import ORS - >>> router = ORS(my_key, requests_kwargs={'proxies': {'https': '129.125.12.0'}}) - >>> print(router.client.proxies) - {'https': '129.125.12.0'} - - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. @@ -78,6 +65,9 @@ def __init__( :param skip_api_error: Continue with batch processing if a :class:`routingpy.exceptions.RouterApiError` is encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default False. :type skip_api_error: bool + + :param **kwargs: Additional arguments, such as headers or proxies. + :type **kwargs: dict """ self._session = requests.Session() @@ -112,7 +102,6 @@ def _request( first_request_time=None, retry_counter=0, dry_run=None, - **client_kwargs ): """Performs HTTP GET/POST with credentials, returning the body as JSON. @@ -133,11 +122,6 @@ def _request( :param retry_counter: The number of this retry, or zero for first attempt. :type retry_counter: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. - :type requests_kwargs: dict - :param dry_run: If 'true', only prints URL and parameters. 'true' or 'false'. :type dry_run: string @@ -171,10 +155,7 @@ def _request( authed_url = self._generate_auth_url(url, get_params) - # Default to the client-level self.kwargs, with method-level - # client_kwargs arg overriding. - client_kwargs = client_kwargs or {} - final_requests_kwargs = dict(self.kwargs, **client_kwargs) + final_requests_kwargs = self.kwargs # Determine GET/POST. requests_method = self._session.get @@ -211,9 +192,7 @@ def _request( UserWarning, ) - return self._request( - url, get_params, post_params, first_request_time, retry_counter + 1, **client_kwargs - ) + return self._request(url, get_params, post_params, first_request_time, retry_counter + 1) try: result = self._get_body(response) @@ -239,9 +218,7 @@ def _request( UserWarning, ) # Retry request. - return self._request( - url, get_params, post_params, first_request_time, retry_counter + 1, **client_kwargs - ) + return self._request(url, get_params, post_params, first_request_time, retry_counter + 1) @property def req(self): diff --git a/routingpy/routers/google.py b/routingpy/routers/google.py index 6534bf9..1ac8ca9 100644 --- a/routingpy/routers/google.py +++ b/routingpy/routers/google.py @@ -43,8 +43,8 @@ def __init__( """ Initializes a Google client. - :param key: API key. - :type key: str + :param api_key: API key. + :type api_key: str :param user_agent: User Agent to be used when requesting. Default :attr:`routingpy.routers.options.default_user_agent`. @@ -58,21 +58,6 @@ def __init__( seconds. Default :attr:`routingpy.routers.options.default_retry_timeout`. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. **Note**, that ``proxies`` can be set globally - in :attr:`routingpy.routers.options.default_proxies`. - - Example: - - >>> from routingpy.routers import Google - >>> router = Google(my_key, requests_kwargs={ - >>> 'proxies': {'https': '129.125.12.0'} - >>> }) - >>> print(router.client.proxies) - {'https': '129.125.12.0'} - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. @@ -86,6 +71,9 @@ def __init__( :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` :type client: abc.ABCMeta + + :param **client_kwargs: Additional arguments passed to the client, such as headers or proxies. + :type **client_kwargs: dict """ self.key = api_key @@ -185,7 +173,7 @@ def directions( # noqa: C901 :param avoid: Indicates that the calculated route(s) should avoid the indicated features. One or more of ['tolls', 'highways', 'ferries', 'indoor']. Default None. - :param avoid: list of str + :type avoid: list of str :param optimize: Optimize the given order of via waypoints (i.e. between first and last location). Default False. :type optimize: bool @@ -223,7 +211,7 @@ def directions( # noqa: C901 :type transit_routing_preference: str :param dry_run: Print URL and parameters without sending the request. - :param dry_run: bool + :type dry_run: bool :returns: One or multiple route(s) from provided coordinates and restrictions. :rtype: :class:`routingpy.direction.Direction` or :class:`routingpy.direction.Directions` diff --git a/routingpy/routers/graphhopper.py b/routingpy/routers/graphhopper.py index 79e1ff0..02e7e28 100644 --- a/routingpy/routers/graphhopper.py +++ b/routingpy/routers/graphhopper.py @@ -65,25 +65,10 @@ def __init__( seconds. Default :attr:`routingpy.routers.options.default_retry_timeout`. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. **Note**, that ``proxies`` can be set globally - in :attr:`routingpy.routers.options.default_proxies`. - - Example: - - >>> from routingpy.routers import Graphhopper - >>> router = Graphhopper(my_key, requests_kwargs={ - >>> 'proxies': {'https': '129.125.12.0'} - >>> }) - >>> print(router.client.proxies) - {'https': '129.125.12.0'} - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. - Default :attr:`routingpy.routers.options.default_over_query_limit`. + Default :attr:`routingpy.routers.options.default_retry_over_query_limit`. :type retry_over_query_limit: bool :param skip_api_error: Continue with batch processing if a :class:`routingpy.exceptions.RouterApiError` is @@ -93,6 +78,10 @@ def __init__( :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` :type client: abc.ABCMeta + + :param **client_kwargs: Additional arguments passed to the client, such as headers or proxies. + :type **client_kwargs: dict + """ if base_url == self._DEFAULT_BASE_URL and api_key is None: diff --git a/routingpy/routers/heremaps.py b/routingpy/routers/heremaps.py index 358005d..e02514e 100644 --- a/routingpy/routers/heremaps.py +++ b/routingpy/routers/heremaps.py @@ -65,25 +65,10 @@ def __init__( seconds. Default :attr:`routingpy.routers.options.default_retry_timeout`. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. **Note**, that ``proxies`` can be set globally - in :attr:`routingpy.routers.options.default_proxies`. - - Example: - - >>> from routingpy.routers import HereMaps - >>> router = HereMaps(my_key, requests_kwargs={ - >>> 'proxies': {'https': '129.125.12.0'} - >>> }) - >>> print(router.client.proxies) - {'https': '129.125.12.0'} - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. - Default :attr:`routingpy.routers.options.default_over_query_limit`. + Default :attr:`routingpy.routers.options.default_retry_over_query_limit`. :type retry_over_query_limit: bool :param skip_api_error: Continue with batch processing if a :class:`routingpy.exceptions.RouterApiError` is @@ -93,6 +78,9 @@ def __init__( :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` :type client: abc.ABCMeta + + :param **client_kwargs: Additional arguments passed to the client, such as headers or proxies. + :type **client_kwargs: dict """ if app_id is None and app_code is None and api_key is None: diff --git a/routingpy/routers/mapbox_osrm.py b/routingpy/routers/mapbox_osrm.py index 73f298b..245e181 100644 --- a/routingpy/routers/mapbox_osrm.py +++ b/routingpy/routers/mapbox_osrm.py @@ -60,21 +60,6 @@ def __init__( seconds. Default :attr:`routingpy.routers.options.default_retry_timeout`. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. **Note**, that ``proxies`` can be set globally - in :attr:`routingpy.routers.options.default_proxies`. - - Example: - - >>> from routingpy.routers import MapboxOSRM - >>> router = MapboxOSRM(my_key, requests_kwargs={ - >>> 'proxies': {'https': '129.125.12.0'} - >>> }) - >>> print(router.client.proxies) - {'https': '129.125.12.0'} - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. @@ -88,10 +73,15 @@ def __init__( :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` :type client: abc.ABCMeta + + :param **client_kwargs: Additional arguments passed to the client, such as headers or proxies. + :type **client_kwargs: dict """ self.api_key = api_key + client_kwargs.update({"headers": {"Content-Type": "application/x-www-form-urlencoded"}}) + self.client = client( self._base_url, user_agent, @@ -304,7 +294,6 @@ def directions( # noqa: C901 get_params=get_params, post_params=params, dry_run=dry_run, - headers={"Content-Type": "application/x-www-form-urlencoded"}, ), alternatives, geometries, diff --git a/routingpy/routers/mapbox_valhalla.py b/routingpy/routers/mapbox_valhalla.py index ccce107..6bea1e8 100644 --- a/routingpy/routers/mapbox_valhalla.py +++ b/routingpy/routers/mapbox_valhalla.py @@ -53,25 +53,10 @@ def __init__( seconds. Default :attr:`routingpy.routers.options.default_retry_timeout`. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. **Note**, that ``proxies`` can be set globally - in :attr:`routingpy.routers.options.default_proxies`. - - Example: - - >>> from routingpy.routers import MapboxValhalla - >>> router = MapboxValhalla(my_key, requests_kwargs={ - >>> 'proxies': {'https': '129.125.12.0'} - >>> }) - >>> print(router.client.proxies) - {'https': '129.125.12.0'} - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. - Default :attr:`routingpy.routers.options.default_over_query_limit`. + Default :attr:`routingpy.routers.options.default_retry_over_query_limit`. :type retry_over_query_limit: bool :param skip_api_error: Continue with batch processing if a :class:`routingpy.exceptions.RouterApiError` is @@ -81,6 +66,9 @@ def __init__( :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` :type client: abc.ABCMeta + + :param **client_kwargs: Additional arguments passed to the client, such as headers or proxies. + :type **client_kwargs: dict """ super(MapboxValhalla, self).__init__( diff --git a/routingpy/routers/openrouteservice.py b/routingpy/routers/openrouteservice.py index dddab0a..1737162 100644 --- a/routingpy/routers/openrouteservice.py +++ b/routingpy/routers/openrouteservice.py @@ -62,25 +62,10 @@ def __init__( seconds. Default :attr:`routingpy.routers.options.default_retry_timeout`. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. **Note**, that ``proxies`` can be set globally - in :attr:`routingpy.routers.options.default_proxies`. - - Example: - - >>> from routingpy.routers import ORS - >>> router = ORS(my_key, requests_kwargs={ - >>> 'proxies': {'https': '129.125.12.0'} - >>> }) - >>> print(router.client.proxies) - {'https': '129.125.12.0'} - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. - Default :attr:`routingpy.routers.options.default_over_query_limit`. + Default :attr:`routingpy.routers.options.default_retry_over_query_limit`. :type retry_over_query_limit: bool :param skip_api_error: Continue with batch processing if a :class:`routingpy.exceptions.RouterApiError` is @@ -90,6 +75,9 @@ def __init__( :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` :type client: abc.ABCMeta + + :param **client_kwargs: Additional arguments passed to the client, such as headers or proxies. + :type **client_kwargs: dict """ if base_url == self._DEFAULT_BASE_URL and api_key is None: diff --git a/routingpy/routers/osrm.py b/routingpy/routers/osrm.py index d8e44c7..f41b09c 100644 --- a/routingpy/routers/osrm.py +++ b/routingpy/routers/osrm.py @@ -59,25 +59,10 @@ def __init__( seconds. Default :attr:`routingpy.routers.options.default_retry_timeout`. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. **Note**, that ``proxies`` can be set globally - in :attr:`routingpy.routers.options.default_proxies`. - - Example: - - >>> from routingpy.routers import OSRM - >>> router = OSRM(my_key, requests_kwargs={ - >>> 'proxies': {'https': '129.125.12.0'} - >>> }) - >>> print(router.client.proxies) - {'https': '129.125.12.0'} - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. - Default :attr:`routingpy.routers.options.default_over_query_limit`. + Default :attr:`routingpy.routers.options.default_retry_over_query_limit`. :type retry_over_query_limit: bool :param skip_api_error: Continue with batch processing if a :class:`routingpy.exceptions.RouterApiError` is @@ -87,6 +72,9 @@ def __init__( :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` :type client: abc.ABCMeta + + :param **client_kwargs: Additional arguments passed to the client, such as headers or proxies. + :type **client_kwargs: dict """ self.client = client( diff --git a/routingpy/routers/valhalla.py b/routingpy/routers/valhalla.py index a60366a..4701fb4 100644 --- a/routingpy/routers/valhalla.py +++ b/routingpy/routers/valhalla.py @@ -67,25 +67,10 @@ def __init__( seconds. Default :attr:`routingpy.routers.options.default_retry_timeout`. :type retry_timeout: int - :param requests_kwargs: Extra keyword arguments for the requests - library, which among other things allow for proxy auth to be - implemented. **Note**, that ``proxies`` can be set globally - in :attr:`routingpy.routers.options.default_proxies`. - - Example: - - >>> from routingpy.routers import Valhalla - >>> router = Valhalla(my_key, requests_kwargs={ - >>> 'proxies': {'https': '129.125.12.0'} - >>> }) - >>> print(router.client.proxies) - {'https': '129.125.12.0'} - :type requests_kwargs: dict - :param retry_over_query_limit: If True, client will not raise an exception on HTTP 429, but instead jitter a sleeping timer to pause between requests until HTTP 200 or retry_timeout is reached. - Default :attr:`routingpy.routers.options.default_over_query_limit`. + Default :attr:`routingpy.routers.options.default_retry_over_query_limit`. :type retry_over_query_limit: bool :param skip_api_error: Continue with batch processing if a :class:`routingpy.exceptions.RouterApiError` is @@ -95,6 +80,9 @@ def __init__( :param client: A client class for request handling. Needs to be derived from :class:`routingpy.base.BaseClient` :type client: abc.ABCMeta + + :param **client_kwargs: Additional arguments passed to the client, such as headers or proxies. + :type **client_kwargs: dict """ self.api_key = api_key From 335926d4842e49cd6de592a2520d955902366642 Mon Sep 17 00:00:00 2001 From: nilsnolde Date: Mon, 21 Jun 2021 18:59:27 +0200 Subject: [PATCH 7/8] fix tests: apparently there was both a bug in the here tests and a bug in responses (since it didn't pick up on our bug before the last PR's packages updates) --- tests/test_heremaps.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_heremaps.py b/tests/test_heremaps.py index 8687949..f3446bf 100644 --- a/tests/test_heremaps.py +++ b/tests/test_heremaps.py @@ -148,7 +148,7 @@ def test_full_isochrones_response_object(self): responses.add( responses.GET, - "https://isoline.route.api.here.com/routing/7.2/calculateisoline.json", + "https://route.api.here.com/routing/7.2/calculateisoline.json", status=200, json=ENDPOINTS_RESPONSES[self.name]["isochrones"], content_type="application/json", @@ -158,7 +158,7 @@ def test_full_isochrones_response_object(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual( - "https://isoline.route.api.here.com/routing/7.2/calculateisoline.json?app_code=sample_app_code&" + "https://route.api.here.com/routing/7.2/calculateisoline.json?app_code=sample_app_code&" "app_id=sample_app_id&mode=fastest%3Bcar&quality=1&range=1000%2C2000%2C3000&rangeType=distance&" "singleComponent=false&start=geo%2148.23424%2C8.34234", responses.calls[0].request.url, @@ -178,7 +178,7 @@ def test_full_matrix(self): responses.add( responses.GET, - "https://matrix.route.api.here.com/routing/7.2/calculatematrix.json", + "https://route.api.here.com/routing/7.2/calculatematrix.json", status=200, json=ENDPOINTS_RESPONSES[self.name]["matrix"], content_type="application/json", @@ -188,7 +188,7 @@ def test_full_matrix(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual( - "https://matrix.route.api.here.com/routing/7.2/calculatematrix.json?app_code=sample_app_code&" + "https://route.api.here.com/routing/7.2/calculatematrix.json?app_code=sample_app_code&" "app_id=sample_app_id&destination0=geo%2149.445776%2C8.780916&height=20&length=10&limitedWeight=10&" "mode=fastest%3Bcar&shippedHazardousGoods=gas%2Cflammable&start0=geo%2149.420577%2C8.688641&" "start1=geo%2149.415776%2C8.680916&summaryAttributes=traveltime%2Ccostfactor&trailersCount=3&truckType=truck&" @@ -333,7 +333,7 @@ def test_full_isochrones_response_object(self): responses.add( responses.GET, - "https://isoline.route.ls.hereapi.com/routing/7.2/calculateisoline.json", + "https://route.ls.hereapi.com/routing/7.2/calculateisoline.json", status=200, json=ENDPOINTS_RESPONSES[self.name]["isochrones"], content_type="application/json", @@ -343,7 +343,7 @@ def test_full_isochrones_response_object(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual( - "https://isoline.route.ls.hereapi.com/routing/7.2/calculateisoline.json?apikey=sample_api_key&" + "https://route.ls.hereapi.com/routing/7.2/calculateisoline.json?apikey=sample_api_key&" "mode=fastest%3Bcar&quality=1&range=1000%2C2000%2C3000&rangeType=distance&" "singleComponent=false&start=geo%2148.23424%2C8.34234", responses.calls[0].request.url, @@ -363,7 +363,7 @@ def test_full_matrix(self): responses.add( responses.GET, - "https://matrix.route.ls.hereapi.com/routing/7.2/calculatematrix.json", + "https://route.ls.hereapi.com/routing/7.2/calculatematrix.json", status=200, json=ENDPOINTS_RESPONSES[self.name]["matrix"], content_type="application/json", @@ -373,7 +373,7 @@ def test_full_matrix(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual( - "https://matrix.route.ls.hereapi.com/routing/7.2/calculatematrix.json?apikey=sample_api_key&" + "https://route.ls.hereapi.com/routing/7.2/calculatematrix.json?apikey=sample_api_key&" "destination0=geo%2149.445776%2C8.780916&height=20&length=10&limitedWeight=10&" "mode=fastest%3Bcar&shippedHazardousGoods=gas%2Cflammable&start0=geo%2149.420577%2C8.688641&" "start1=geo%2149.415776%2C8.680916&summaryAttributes=traveltime%2Ccostfactor&trailersCount=3&truckType=truck&" From 7c445862faebde6367c7f14cb04d23d7e4804466 Mon Sep 17 00:00:00 2001 From: nilsnolde Date: Mon, 21 Jun 2021 19:01:55 +0200 Subject: [PATCH 8/8] add requests URL to docstring --- routingpy/client_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routingpy/client_base.py b/routingpy/client_base.py index 46fccb6..087f585 100644 --- a/routingpy/client_base.py +++ b/routingpy/client_base.py @@ -124,7 +124,7 @@ def __init__( encountered (e.g. no route found). If False, processing will discontinue and raise an error. Default False. :type skip_api_error: bool - :param **kwargs: Additional keyword arguments + :param **kwargs: Additional keyword arguments for the `requests library `_ :type **kwargs: dict """ self.base_url = base_url