Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix#22 #24

Merged
merged 9 commits into from
Jun 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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