class AdapterHTTPError(IOError):
    """An exception which must be raised by adapters when an HTTP response
    with a non-successful status code has been received.

    Base Geocoder class translates this exception to an instance of
    :class:`geopy.exc.GeocoderServiceError`.

    """

    def __init__(self, message, *, status_code, headers, text):
        """

        :param str message: Standard exception message.
        :param int status_code: HTTP status code.
        :param dict headers: HTTP response readers. A mapping object
            with lowercased or case-insensitive keys.

            .. versionadded:: 2.2
        :param str text: HTTP body text.
        """
        self.status_code = status_code
        self.headers = headers
        self.text = text
        super().__init__(message) class ConfigurationError(GeopyError, ValueError):
    """
    When instantiating a geocoder, the arguments given were invalid. See
    the documentation of each geocoder's ``__init__`` for more details.
    """ class GeocoderAuthenticationFailure(GeocoderServiceError):
    """
    The remote geocoding service rejected the API key or account
    credentials this geocoder was instantiated with.
    """ class GeocoderInsufficientPrivileges(GeocoderServiceError):
    """
    The remote geocoding service refused to fulfill a request using the
    account credentials given.
    """ class GeocoderQueryError(GeocoderServiceError, ValueError):
    """
    Either geopy detected input that would cause a request to fail,
    or a request was made and the remote geocoding service responded
    that the request was bad.
    """ class GeocoderRateLimited(GeocoderQuotaExceeded, IOError):
    """
    The remote geocoding service has rate-limited the request.
    Retrying later might help.

    Exception of this type has a ``retry_after`` attribute,
    which contains amount of time (in seconds) the service
    has asked to wait. Might be ``None`` if there were no such
    data in response.

    .. versionadded:: 2.2
    """

    def __init__(self, message, *, retry_after=None):
        super().__init__(message)
        self.retry_after = retry_after class GeocoderServiceError(GeopyError):
    """
    There was an exception caused when calling the remote geocoding service,
    and no more specific exception could be raised by geopy. When calling
    geocoders' ``geocode`` or `reverse` methods, this is the most generic
    exception that can be raised, and any non-geopy exception will be caught
    and turned into this. The exception's message will be that of the
    original exception.
    """ class GeocoderUnavailable(GeocoderServiceError, IOError):
    """
    Either it was not possible to establish a connection to the remote
    geocoding service, or the service responded with a code indicating
    it was unavailable.
    """ DEFAULT_SENTINEL = type('object', (object,),
                        {'__repr__': lambda self: 'DEFAULT_SENTINEL'})() ERROR_CODE_MAP = {
    400: GeocoderQueryError,
    401: GeocoderAuthenticationFailure,
    402: GeocoderQuotaExceeded,
    403: GeocoderInsufficientPrivileges,
    407: GeocoderAuthenticationFailure,
    408: GeocoderTimedOut,
    412: GeocoderQueryError,
    413: GeocoderQueryError,
    414: GeocoderQueryError,
    429: GeocoderRateLimited,
    502: GeocoderServiceError,
    503: GeocoderTimedOut,
    504: GeocoderTimedOut
} class Geocoder:
    """
    Template object for geocoders.
    """

    def __init__(
            self,
            *,
            scheme=None,
            timeout=DEFAULT_SENTINEL,
            proxies=DEFAULT_SENTINEL,
            user_agent=None,
            ssl_context=DEFAULT_SENTINEL,
            adapter_factory=None
    ):
        self.scheme = scheme or options.default_scheme
        if self.scheme not in ('http', 'https'):
            raise ConfigurationError(
                'Supported schemes are `http` and `https`.'
            )
        self.timeout = (timeout if timeout is not DEFAULT_SENTINEL
                        else options.default_timeout)
        self.proxies = (proxies if proxies is not DEFAULT_SENTINEL
                        else options.default_proxies)
        self.headers = {'User-Agent': user_agent or options.default_user_agent}
        self.ssl_context = (ssl_context if ssl_context is not DEFAULT_SENTINEL
                            else options.default_ssl_context)

        if isinstance(self.proxies, str):
            self.proxies = {'http': self.proxies, 'https': self.proxies}

        if adapter_factory is None:
            adapter_factory = options.default_adapter_factory
        self.adapter = adapter_factory(
            proxies=self.proxies,
            ssl_context=self.ssl_context,
        )
        if isinstance(self.adapter, BaseSyncAdapter):
            self.__run_async = False
        elif isinstance(self.adapter, BaseAsyncAdapter):
            self.__run_async = True
        else:
            raise ConfigurationError(
                "Adapter %r must extend either BaseSyncAdapter or BaseAsyncAdapter"
                % (type(self.adapter),)
            )

    def __enter__(self):
        """Context manager for synchronous adapters. At exit all
        open connections will be closed.

        In synchronous mode context manager usage is not required,
        and connections will be automatically closed by garbage collection.
        """
        if self.__run_async:
            raise TypeError("`async with` must be used with async adapters")
        res = self.adapter.__enter__()
        assert res is self.adapter, "adapter's __enter__ must return `self`"
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.adapter.__exit__(exc_type, exc_val, exc_tb)

    async def __aenter__(self):
        """Context manager for asynchronous adapters. At exit all
        open connections will be closed.

        In asynchronous mode context manager usage is not required,
        however, it is strongly advised to avoid warnings about
        resources leaks.
        """
        if not self.__run_async:
            raise TypeError("`async with` cannot be used with sync adapters")
        res = await self.adapter.__aenter__()
        assert res is self.adapter, "adapter's __enter__ must return `self`"
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.adapter.__aexit__(exc_type, exc_val, exc_tb)

    def _coerce_point_to_string(self, point, output_format="%(lat)s,%(lon)s"):
        """
        Do the right thing on "point" input. For geocoders with reverse
        methods.
        """
        if not isinstance(point, Point):
            point = Point(point)

        # Altitude is silently dropped.
        #
        # Geocoding services (almost?) always consider only lat and lon
        # in queries, so altitude doesn't affect the request.
        # A non-zero altitude should not raise an exception
        # though, because PoIs are assumed to span the whole
        # altitude axis (i.e. not just the 0km plane).
        return output_format % dict(lat=point.latitude,
                                    lon=point.longitude)

    def _format_bounding_box(
        self, bbox, output_format="%(lat1)s,%(lon1)s,%(lat2)s,%(lon2)s"
    ):
        """
        Transform bounding box boundaries to a string matching
        `output_format` from the following formats:

            - [Point(lat1, lon1), Point(lat2, lon2)]
            - [[lat1, lon1], [lat2, lon2]]
            - ["lat1,lon1", "lat2,lon2"]

        It is guaranteed that lat1 <= lat2 and lon1 <= lon2.
        """
        if len(bbox) != 2:
            raise GeocoderQueryError("Unsupported format for a bounding box")
        p1, p2 = bbox
        p1, p2 = Point(p1), Point(p2)
        return output_format % dict(lat1=min(p1.latitude, p2.latitude),
                                    lon1=min(p1.longitude, p2.longitude),
                                    lat2=max(p1.latitude, p2.latitude),
                                    lon2=max(p1.longitude, p2.longitude))

    def _geocoder_exception_handler(self, error):
        """
        Geocoder-specific exceptions handler.
        Override if custom exceptions processing is needed.
        For example, raising an appropriate GeocoderQuotaExceeded on non-200
        response with a textual message in the body about the exceeded quota.

        Return `NONE_RESULT` to have the geocoding call return `None` (meaning
        empty result).
        """
        pass

    def _call_geocoder(
            self,
            url,
            callback,
            *,
            timeout=DEFAULT_SENTINEL,
            is_json=True,
            headers=None
    ):
        """
        For a generated query URL, get the results.
        """

        req_headers = self.headers.copy()
        if headers:
            req_headers.update(headers)

        timeout = (timeout if timeout is not DEFAULT_SENTINEL
                   else self.timeout)

        try:
            if is_json:
                result = self.adapter.get_json(url, timeout=timeout, headers=req_headers)
            else:
                result = self.adapter.get_text(url, timeout=timeout, headers=req_headers)
            if self.__run_async:
                async def fut():
                    try:
                        res = callback(await result)
                        if inspect.isawaitable(res):
                            res = await res
                        return res
                    except Exception as error:
                        res = self._adapter_error_handler(error)
                        if res is NONE_RESULT:
                            return None
                        raise

                return fut()
            else:
                return callback(result)
        except Exception as error:
            res = self._adapter_error_handler(error)
            if res is NONE_RESULT:
                return None
            raise

    def _adapter_error_handler(self, error):
        if isinstance(error, AdapterHTTPError):
            if error.text:
                logger.info(
                    'Received an HTTP error (%s): %s',
                    error.status_code,
                    error.text,
                    exc_info=False,
                )
            res = self._geocoder_exception_handler(error)
            if res is NONE_RESULT:
                return NONE_RESULT
            exc_cls = ERROR_CODE_MAP.get(error.status_code, GeocoderServiceError)
            if issubclass(exc_cls, GeocoderRateLimited):
                raise exc_cls(
                    str(error), retry_after=get_retry_after(error.headers)
                ) from error
            else:
                raise exc_cls(str(error)) from error
        else:
            res = self._geocoder_exception_handler(error)
            if res is NONE_RESULT:
                return NONE_RESULT

    # def geocode(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL):
    #     raise NotImplementedError()

    # def reverse(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL):
    #     raise NotImplementedError() class Location:
    """
    Contains a parsed geocoder response. Can be iterated over as
    ``(location<String>, (latitude<float>, longitude<Float))``.
    Or one can access the properties ``address``, ``latitude``,
    ``longitude``, or ``raw``. The last
    is a dictionary of the geocoder's response for this item.
    """

    __slots__ = ("_address", "_point", "_tuple", "_raw")

    def __init__(self, address, point, raw):
        if address is None:
            raise TypeError("`address` must not be None")
        self._address = address

        if isinstance(point, Point):
            self._point = point
        elif isinstance(point, str):
            self._point = Point(point)
        elif isinstance(point, collections.abc.Sequence):
            self._point = Point(point)
        else:
            raise TypeError(
                "`point` is of unsupported type: %r" % type(point)
            )
        self._tuple = _location_tuple(self)

        if raw is None:
            raise TypeError("`raw` must not be None")
        self._raw = raw

    @property
    def address(self):
        """
        Location as a formatted string returned by the geocoder or constructed
        by geopy, depending on the service.

        :rtype: str
        """
        return self._address

    @property
    def latitude(self):
        """
        Location's latitude.

        :rtype: float
        """
        return self._point[0]

    @property
    def longitude(self):
        """
        Location's longitude.

        :rtype: float
        """
        return self._point[1]

    @property
    def altitude(self):
        """
        Location's altitude.

        .. note::
            Geocoding services usually don't consider altitude neither in
            requests nor in responses, so almost always the value of this
            property would be zero.

        :rtype: float
        """
        return self._point[2]

    @property
    def point(self):
        """
        :class:`geopy.point.Point` instance representing the location's
        latitude, longitude, and altitude.

        :rtype: :class:`geopy.point.Point`
        """
        return self._point

    @property
    def raw(self):
        """
        Location's raw, unparsed geocoder response. For details on this,
        consult the service's documentation.

        :rtype: dict
        """
        return self._raw

    def __getitem__(self, index):
        """
        Backwards compatibility with geopy<0.98 tuples.
        """
        return self._tuple[index]

    def __str__(self):
        return self._address

    def __repr__(self):
        return "Location(%s, (%s, %s, %s))" % (
            self._address, self.latitude, self.longitude, self.altitude
        )

    def __iter__(self):
        return iter(self._tuple)

    def __getstate__(self):
        return self._address, self._point, self._raw

    def __setstate__(self, state):
        self._address, self._point, self._raw = state
        self._tuple = _location_tuple(self)

    def __eq__(self, other):
        return (
            isinstance(other, Location) and
            self._address == other._address and
            self._point == other._point and
            self.raw == other.raw
        )

    def __ne__(self, other):
        return not (self == other)

    def __len__(self):
        return len(self._tuple) NUMBER_TYPES = (int, float, Decimal)
def pairwise(seq):
def join_filter(sep, seq, pred=bool):
def get_version():
import collections.abc
import json
import warnings
from functools import partial
from urllib.parse import urlencode
from geopy.adapters import AdapterHTTPError
from geopy.exc import (
    ConfigurationError,
    GeocoderAuthenticationFailure,
    GeocoderInsufficientPrivileges,
    GeocoderQueryError,
    GeocoderRateLimited,
    GeocoderServiceError,
    GeocoderUnavailable,
)
from geopy.geocoders.base import DEFAULT_SENTINEL, ERROR_CODE_MAP, Geocoder
from geopy.location import Location
from geopy.util import join_filter, logger
                in components.items()
                if key in self.structured_query_params
            ]
            if not parts:
                raise GeocoderQueryError("`components` dict must not be empty")
            for pair in parts:
                if ';' in pair:
                    raise GeocoderQueryError(
                        "';' must not be used in values of the structured query. "
                        "Offending pair: {!r}".format(pair)
                    )
            params['qq'] = ';'.join(parts)

        if at:
            point = self._coerce_point_to_string(at, output_format="%(lat)s,%(lon)s")
            params['at'] = point

        if countries:
            params['in'] = 'countryCode:' + ','.join(countries)

        if language:
            params['lang'] = language

        if limit:
            params['limit'] = limit
        if exactly_one:
            params['limit'] = 1

        url = "?".join((self.api, urlencode(params)))
        logger.debug("%s.geocode: %s", self.__class__.__name__, url)
        callback = partial(self._parse_json, exactly_one=exactly_one)
        return self._call_geocoder(url, callback, timeout=timeout)

    def reverse(
            self,
            query,
            *,
            language=None,
            limit=None,
            exactly_one=True,
            timeout=DEFAULT_SENTINEL
    ):
        """
        Return an address by location point.

        :param query: The coordinates for which you wish to obtain the
            closest human-readable addresses.
        :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude,
            longitude)``, or string as ``"%(latitude)s, %(longitude)s"``.

        :param str language: Affects the language of the response,
            must be a BCP 47 compliant language code, e.g. ``en-US``.

        :param int limit: Maximum number of results to be returned.
            This will be reset to one if ``exactly_one`` is True.

        :param bool exactly_one: Return one result or a list of results, if
            available.

        :param int timeout: Time, in seconds, to wait for the geocoding service
            to respond before raising a :class:`geopy.exc.GeocoderTimedOut`
            exception. Set this only if you wish to override, on this call
            only, the value set during the geocoder's initialization.

        :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if
            ``exactly_one=False``.
        """

        params = {
            'at': self._coerce_point_to_string(query, output_format="%(lat)s,%(lon)s"),
            'apiKey': self.apikey,
        }

        if language:
            params['lang'] = language

        if limit:
            params['limit'] = limit
        if exactly_one:
            params['limit'] = 1

        url = "%s?%s" % (self.reverse_api, urlencode(params))
        logger.debug("%s.reverse: %s", self.__class__.__name__, url)
        callback = partial(self._parse_json, exactly_one=exactly_one)
        return self._call_geocoder(url, callback, timeout=timeout)

    def _parse_json(self, doc, exactly_one=True):
        resources = doc['items']
        if not resources:
            return None

        def parse_resource(resource):
            """
            Parse each return object.
            """
            location = resource['title']
            position = resource['position']

            latitude, longitude = position['lat'], position['lng']

            return Location(location, (latitude, longitude), resource)

        if exactly_one:
            return parse_resource(resources[0])
        else:
            return [parse_resource(resource) for resource in resources]

    def _geocoder_exception_handler(self, error):
        if not isinstance(error, AdapterHTTPError):
            return
        if error.status_code is None or error.text is None:
            return
        try:
            body = json.loads(error.text)
        except ValueError:
            message = error.text
        else:
            # `title`: https://developer.here.com/documentation/geocoding-search-api/api-reference-swagger.html  # noqa
            # `error_description`: returned for queries without apiKey.
            message = body.get('title') or body.get('error_description') or error.text

        exc_cls = ERROR_CODE_MAP.get(error.status_code, GeocoderServiceError)