-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #24 from gis-ops/fix#22
Fix#22
- Loading branch information
Showing
13 changed files
with
482 additions
and
437 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
Oops, something went wrong.