Skip to content

Commit

Permalink
Merge pull request #24 from gis-ops/fix#22
Browse files Browse the repository at this point in the history
Fix#22
  • Loading branch information
nilsnolde authored Jun 21, 2021
2 parents 838d328 + 7c44586 commit e6add3d
Show file tree
Hide file tree
Showing 13 changed files with 482 additions and 437 deletions.
217 changes: 217 additions & 0 deletions routingpy/client_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# -*- 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.
#
"""
Core client functionality, common across all routers.
"""


from routingpy.__version__ import __version__

from abc import ABCMeta, abstractmethod
from datetime import timedelta
import requests
from urllib.parse import urlencode


_DEFAULT_USER_AGENT = "routingpy/v{}".format(__version__)
_RETRIABLE_STATUSES = set([503])


class options(object):
"""
Contains default configuration options for all routers, e.g. `timeout` and `proxies`. Each router can take the same
configuration values, which will override the values set in `options` object.
Example for overriding default values for `user_agent` and proxies:
>>> from routingpy.routers import options
>>> from routingpy.routers import MapboxValhalla
>>> options.default_user_agent = 'amazing_routing_app'
>>> options.default_proxies = {'https': '129.125.12.0'}
>>> router = MapboxValhalla(my_key)
>>> print(router.headers)
{'User-Agent': 'amazing_routing_app', 'Content-Type': 'application/json'}
>>> print(router.proxies)
{'https': '129.125.12.0'}
Attributes:
self.default_timeout:
Combined connect and read timeout for HTTP requests, in
seconds. Specify "None" for no timeout. Integer.
self.default_retry_timeout:
Timeout across multiple retriable requests, in
seconds. Integer.
self.default_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. Boolean.
self.default_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. Boolean.
self.default_user_agent:
User-Agent to send with the requests to routing API. String.
self.default_proxies:
Proxies passed to the requests library. Dictionary.
"""

default_timeout = 60
default_retry_timeout = 60
default_retry_over_query_limit = True
default_skip_api_error = False
default_user_agent = _DEFAULT_USER_AGENT
default_proxies = None


# 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"})()


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,
**kwargs
):
"""
: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 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
:param **kwargs: Additional keyword arguments for the `requests library <https://docs.python-requests.org/en/master/api/#requests.request>`_
:type **kwargs: dict
"""
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_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",
}

self.timeout = timeout if timeout != DEFAULT else options.default_timeout

self.kwargs = kwargs

self._req = None

@abstractmethod
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
def _generate_auth_url(path, params):
"""Returns the path and query string portion of the request URL, first
adding any necessary parameters.
:param path: The path portion of the URL.
:type path: string
:param params: URL parameters.
:type params: dict or list of key/value tuples
:rtype: string
"""

if isinstance(params, dict):
params = sorted(dict(**params).items())
elif isinstance(params, (list, tuple)):
params = params

return path + "?" + requests.utils.unquote_unreserved(urlencode(params))
Loading

0 comments on commit e6add3d

Please sign in to comment.