Repository: geopy/geopy
Pull request #416 (2020-06-21 17:59:01)

geopy 2.0: misc cleanup
(This PR contains a subset of commits from #335).

Miscellaneous cleanup: 
- introduce kwarg-only args, 
- remove `None` defaults for authentication key args which are later validated to be non-falsy,
- incapsulate urllib-specific params in the base Geocoder class (this is a preparation for the Adapters).


Patch:
diff --git a/docs/changelog_2xx.rst b/docs/changelog_2xx.rst
index 578dba0..0a8af58 100644
--- a/docs/changelog_2xx.rst
+++ b/docs/changelog_2xx.rst
@@ -9,6 +9,14 @@ Changelog
 -----
 2020-XXX
 
+geopy 2.0 is a major release with lots of cleanup and inner refactorings.
+The public interface of the library is mostly the same, and the set
+of supported geocoders didn't change.
+
+If you have checked your code on the latest 1.x release with enabled
+warnings (i.e. with ``-Wd`` key of the ``python`` command) and fixed
+all of them, then it should be safe to upgrade.
+
 Packaging changes
 ~~~~~~~~~~~~~~~~~
 
@@ -45,3 +53,9 @@ Chores
   used with a default or sample user-agent.
 - :class:`.Point` now raises a ``ValueError`` if constructed from a single number.
   A zero longitude must be explicitly passed to avoid the error.
+- Most of the service-specific arguments of geocoders now must be passed
+  as kwargs, positional arguments are not accepted.
+- Removed default value ``None`` for authentication key arguments of
+  :class:`.GeoNames`, :class:`.OpenMapQuest` and :class:`.Yandex`.
+- ``parse_*`` methods in geocoders have been prefixed with ``_``
+  to explicitly mark that they are private.
diff --git a/geopy/distance.py b/geopy/distance.py
index e4fce24..ea205b9 100644
--- a/geopy/distance.py
+++ b/geopy/distance.py
@@ -115,6 +115,7 @@ An attempt to calculate distances between points with different altitudes
 would result in a ``ValueError`` exception.
 
 """
+import abc
 from math import asin, atan2, cos, sin, sqrt
 
 from geographiclib.geodesic import Geodesic
@@ -186,7 +187,7 @@ def _ensure_same_altitude(a, b):
     # it won't give much error.
 
 
-class Distance:
+class Distance(abc.ABC):
 
     def __init__(self, *args, **kwargs):
         kilometers = kwargs.pop('kilometers', 0)
@@ -235,10 +236,8 @@ class Distance:
 
     __bool__ = __nonzero__
 
+    @abc.abstractmethod
     def measure(self, a, b):
-        """
-        Abstract method for measure
-        """
         raise NotImplementedError()
 
     def __repr__(self):  # pragma: no cover
diff --git a/geopy/geocoders/algolia.py b/geopy/geocoders/algolia.py
index 734a230..513bde5 100644
--- a/geopy/geocoders/algolia.py
+++ b/geopy/geocoders/algolia.py
@@ -1,6 +1,5 @@
 import collections.abc
 from urllib.parse import urlencode
-from urllib.request import Request
 
 from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder
 from geopy.location import Location
@@ -22,6 +21,7 @@ class AlgoliaPlaces(Geocoder):
 
     def __init__(
             self,
+            *,
             app_id=None,
             api_key=None,
             domain='places-dsn.algolia.net',
@@ -29,7 +29,7 @@ class AlgoliaPlaces(Geocoder):
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
         :param str app_id: Unique application identifier. It's used to
@@ -80,6 +80,7 @@ class AlgoliaPlaces(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             type=None,
@@ -90,7 +91,7 @@ class AlgoliaPlaces(Geocoder):
             around=None,
             around_via_ip=None,
             around_radius=None,
-            x_forwarded_for=None,
+            x_forwarded_for=None
     ):
         """
         Return a location point by address.
@@ -184,18 +185,18 @@ class AlgoliaPlaces(Geocoder):
             params['aroundRadius'] = around_radius
 
         url = '?'.join((self.geocode_api, urlencode(params)))
-        request = Request(url)
+        headers = {}
 
         if x_forwarded_for is not None:
-            request.add_header('X-Forwarded-For', x_forwarded_for)
+            headers['X-Forwarded-For'] = x_forwarded_for
 
         if self.app_id is not None and self.api_key is not None:
-            request.add_header('X-Algolia-Application-Id', self.app_id)
-            request.add_header('X-Algolia-API-Key', self.api_key)
+            headers['X-Algolia-Application-Id'] = self.app_id
+            headers['X-Algolia-API-Key'] = self.api_key
 
         logger.debug('%s.geocode: %s', self.__class__.__name__, url)
         return self._parse_json(
-            self._call_geocoder(request, timeout=timeout),
+            self._call_geocoder(url, headers=headers, timeout=timeout),
             exactly_one,
             language=language,
         )
@@ -203,10 +204,11 @@ class AlgoliaPlaces(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             limit=None,
-            language=None,
+            language=None
     ):
         """
         Return an address by location point.
@@ -249,21 +251,20 @@ class AlgoliaPlaces(Geocoder):
             params['language'] = language
 
         url = '?'.join((self.reverse_api, urlencode(params)))
-        request = Request(url)
+        headers = {}
 
         if self.app_id is not None and self.api_key is not None:
-            request.add_header('X-Algolia-Application-Id', self.app_id)
-            request.add_header('X-Algolia-API-Key', self.api_key)
+            headers['X-Algolia-Application-Id'] = self.app_id
+            headers['X-Algolia-API-Key'] = self.api_key
 
         logger.debug("%s.reverse: %s", self.__class__.__name__, url)
         return self._parse_json(
-            self._call_geocoder(request, timeout=timeout),
+            self._call_geocoder(url, headers=headers, timeout=timeout),
             exactly_one,
             language=language,
         )
 
-    @staticmethod
-    def _parse_feature(feature, language):
+    def _parse_feature(self, feature, language):
         # Parse each resource.
         latitude = feature.get('_geoloc', {}).get('lat')
         longitude = feature.get('_geoloc', {}).get('lng')
@@ -278,7 +279,6 @@ class AlgoliaPlaces(Geocoder):
 
         return Location(placename, (latitude, longitude), feature)
 
-    @classmethod
     def _parse_json(self, response, exactly_one, language):
         if response is None or 'hits' not in response:
             return None
diff --git a/geopy/geocoders/arcgis.py b/geopy/geocoders/arcgis.py
index 9a452ab..bd1eafa 100644
--- a/geopy/geocoders/arcgis.py
+++ b/geopy/geocoders/arcgis.py
@@ -1,7 +1,6 @@
 import json
 from time import time
 from urllib.parse import urlencode
-from urllib.request import Request
 
 from geopy.exc import (
     ConfigurationError,
@@ -35,6 +34,7 @@ class ArcGIS(Geocoder):
             self,
             username=None,
             password=None,
+            *,
             referer=None,
             token_lifetime=60,
             scheme=None,
@@ -43,7 +43,7 @@ class ArcGIS(Geocoder):
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
             auth_domain='www.arcgis.com',
-            domain='geocode.arcgis.com',
+            domain='geocode.arcgis.com'
     ):
         """
 
@@ -133,13 +133,11 @@ class ArcGIS(Geocoder):
         """
         if self.token is None or int(time()) > self.token_expiry:
             self._refresh_authentication_token()
-        request = Request(
-            "&".join((url, urlencode({"token": self.token}))),
-            headers={"Referer": self.referer}
-        )
-        return self._base_call_geocoder(request, timeout=timeout)
+        url = "&".join((url, urlencode({"token": self.token})))
+        headers = {"Referer": self.referer}
+        return self._base_call_geocoder(url, timeout=timeout, headers=headers)
 
-    def geocode(self, query, exactly_one=True, timeout=DEFAULT_SENTINEL,
+    def geocode(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL,
                 out_fields=None):
         """
         Return a location point by address.
@@ -202,7 +200,7 @@ class ArcGIS(Geocoder):
             return geocoded[0]
         return geocoded
 
-    def reverse(self, query, exactly_one=True, timeout=DEFAULT_SENTINEL,
+    def reverse(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL,
                 distance=None):
         """
         Return an address by location point.
diff --git a/geopy/geocoders/azure.py b/geopy/geocoders/azure.py
index f9e8137..828982a 100644
--- a/geopy/geocoders/azure.py
+++ b/geopy/geocoders/azure.py
@@ -17,12 +17,13 @@ class AzureMaps(TomTom):
     def __init__(
             self,
             subscription_key,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
-            domain='atlas.microsoft.com',
+            domain='atlas.microsoft.com'
     ):
         """
         :param str subscription_key: Azure Maps subscription key.
diff --git a/geopy/geocoders/baidu.py b/geopy/geocoders/baidu.py
index f26f250..c157ae5 100644
--- a/geopy/geocoders/baidu.py
+++ b/geopy/geocoders/baidu.py
@@ -31,12 +31,13 @@ class Baidu(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
-            security_key=None,
+            security_key=None
     ):
         """
 
@@ -76,8 +77,7 @@ class Baidu(Geocoder):
         self.reverse_api = '%s://api.map.baidu.com%s' % (self.scheme, self.reverse_path)
         self.security_key = security_key
 
-    @staticmethod
-    def _format_components_param(components):
+    def _format_components_param(self, components):
         """
         Format the components dict to something Baidu understands.
         """
@@ -88,8 +88,9 @@ class Baidu(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return a location point by address.
@@ -121,7 +122,7 @@ class Baidu(Geocoder):
             self._call_geocoder(url, timeout=timeout), exactly_one=exactly_one
         )
 
-    def reverse(self, query, exactly_one=True, timeout=DEFAULT_SENTINEL):
+    def reverse(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL):
         """
         Return an address by location point.
 
@@ -200,8 +201,7 @@ class Baidu(Geocoder):
         else:
             return [parse_place(item) for item in place]
 
-    @staticmethod
-    def _check_status(status):
+    def _check_status(self, status):
         """
         Validates error statuses.
         """
diff --git a/geopy/geocoders/banfrance.py b/geopy/geocoders/banfrance.py
index 2300c3b..de43e0a 100644
--- a/geopy/geocoders/banfrance.py
+++ b/geopy/geocoders/banfrance.py
@@ -19,12 +19,13 @@ class BANFrance(Geocoder):
 
     def __init__(
             self,
+            *,
             domain='api-adresse.data.gouv.fr',
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -67,9 +68,10 @@ class BANFrance(Geocoder):
     def geocode(
             self,
             query,
+            *,
             limit=None,
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return a location point by address.
@@ -111,8 +113,9 @@ class BANFrance(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return an address by location point.
@@ -151,8 +154,7 @@ class BANFrance(Geocoder):
             self._call_geocoder(url, timeout=timeout), exactly_one
         )
 
-    @staticmethod
-    def _parse_feature(feature):
+    def _parse_feature(self, feature):
         # Parse each resource.
         latitude = feature.get('geometry', {}).get('coordinates', [])[1]
         longitude = feature.get('geometry', {}).get('coordinates', [])[0]
@@ -160,7 +162,6 @@ class BANFrance(Geocoder):
 
         return Location(placename, (latitude, longitude), feature)
 
-    @classmethod
     def _parse_json(self, response, exactly_one):
         if response is None or 'features' not in response:
             return None
diff --git a/geopy/geocoders/base.py b/geopy/geocoders/base.py
index 36a04f5..ffa235a 100644
--- a/geopy/geocoders/base.py
+++ b/geopy/geocoders/base.py
@@ -1,4 +1,3 @@
-import functools
 import json
 from socket import timeout as SocketTimeout
 from ssl import SSLError
@@ -176,11 +175,12 @@ class Geocoder:
 
     def __init__(
             self,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         self.scheme = scheme or options.default_scheme
         if self.scheme not in ('http', 'https'):
@@ -211,8 +211,7 @@ class Geocoder:
         )
         self.urlopen = opener.open
 
-    @staticmethod
-    def _coerce_point_to_string(point, output_format="%(lat)s,%(lon)s"):
+    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.
@@ -230,8 +229,9 @@ class Geocoder:
         return output_format % dict(lat=point.latitude,
                                     lon=point.longitude)
 
-    @staticmethod
-    def _format_bounding_box(bbox, output_format="%(lat1)s,%(lon1)s,%(lat2)s,%(lon2)s"):
+    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:
@@ -265,43 +265,25 @@ class Geocoder:
     def _call_geocoder(
             self,
             url,
+            *,
             timeout=DEFAULT_SENTINEL,
-            raw=False,
-            requester=None,
-            deserializer=json.loads,
-            **kwargs
+            is_json=True,
+            headers=None
     ):
         """
         For a generated query URL, get the results.
         """
 
-        if requester:
-            req = url  # Don't construct an urllib's Request for a custom requester.
-
-            # `requester` might be anything which can issue an HTTP request.
-            # Assume that `requester` is a method of the `requests` library.
-            # Requests, however, doesn't accept SSL context in its HTTP
-            # request methods. A custom HTTP adapter has to be created for that.
-            # So the current usage is not directly compatible with `requests`.
-            requester = functools.partial(requester, context=self.ssl_context,
-                                          proxies=self.proxies,
-                                          headers=self.headers)
-        else:
-            if isinstance(url, Request):
-                # copy Request
-                headers = self.headers.copy()
-                headers.update(url.header_items())
-                req = Request(url=url.get_full_url(), headers=headers)
-            else:
-                req = Request(url=url, headers=self.headers)
-
-        requester = requester or self.urlopen
+        req_headers = self.headers.copy()
+        if headers:
+            req_headers.update(headers)
+        req = Request(url=url, headers=req_headers)
 
         timeout = (timeout if timeout is not DEFAULT_SENTINEL
                    else self.timeout)
 
         try:
-            page = requester(req, timeout=timeout, **kwargs)
+            page = self.urlopen(req, timeout=timeout)
         except Exception as error:
             message = (
                 str(error.args[0])
@@ -344,14 +326,11 @@ class Geocoder:
         if status_code in ERROR_CODE_MAP:
             raise ERROR_CODE_MAP[page.status_code]("\n%s" % decode_page(page))
 
-        if raw:
-            return page
-
         page = decode_page(page)
 
-        if deserializer is not None:
+        if is_json:
             try:
-                return deserializer(page)
+                return json.loads(page)
             except ValueError:
                 raise GeocoderParseError(
                     "Could not deserialize using deserializer:\n%s" % page
@@ -367,14 +346,8 @@ class Geocoder:
                          exc_info=True)
             return None
 
-    def geocode(self, query, exactly_one=True, timeout=DEFAULT_SENTINEL):
-        """
-        Implemented in subclasses.
-        """
-        raise NotImplementedError()
+    # def geocode(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL):
+    #     raise NotImplementedError()
 
-    def reverse(self, query, exactly_one=True, timeout=DEFAULT_SENTINEL):
-        """
-        Implemented in subclasses.
-        """
-        raise NotImplementedError()
+    # def reverse(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL):
+    #     raise NotImplementedError()
diff --git a/geopy/geocoders/bing.py b/geopy/geocoders/bing.py
index aa67783..256c73e 100644
--- a/geopy/geocoders/bing.py
+++ b/geopy/geocoders/bing.py
@@ -36,11 +36,12 @@ class Bing(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -78,6 +79,7 @@ class Bing(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             user_location=None,
             timeout=DEFAULT_SENTINEL,
@@ -155,6 +157,7 @@ class Bing(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             culture=None,
@@ -203,8 +206,7 @@ class Bing(Geocoder):
             exactly_one
         )
 
-    @staticmethod
-    def _parse_json(doc, exactly_one=True):
+    def _parse_json(self, doc, exactly_one=True):
         """
         Parse a location name, latitude, and longitude from an JSON response.
         """
diff --git a/geopy/geocoders/databc.py b/geopy/geocoders/databc.py
index 8d1a5bd..2f10dbc 100644
--- a/geopy/geocoders/databc.py
+++ b/geopy/geocoders/databc.py
@@ -19,11 +19,12 @@ class DataBC(Geocoder):
 
     def __init__(
             self,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -56,11 +57,12 @@ class DataBC(Geocoder):
     def geocode(
             self,
             query,
+            *,
             max_results=25,
             set_back=0,
             location_descriptor='any',
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return a location point by address.
@@ -122,8 +124,7 @@ class DataBC(Geocoder):
             return geocoded[0]
         return geocoded
 
-    @staticmethod
-    def _parse_feature(feature):
+    def _parse_feature(self, feature):
         properties = feature['properties']
         coordinates = feature['geometry']['coordinates']
         return Location(
diff --git a/geopy/geocoders/geocodeearth.py b/geopy/geocoders/geocodeearth.py
index 7ef7dc8..76ab3da 100644
--- a/geopy/geocoders/geocodeearth.py
+++ b/geopy/geocoders/geocodeearth.py
@@ -12,12 +12,13 @@ class GeocodeEarth(Pelias):
     def __init__(
             self,
             api_key,
+            *,
             domain='api.geocode.earth',
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             scheme=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
         :param str api_key: Geocode.earth API key, required.
diff --git a/geopy/geocoders/geocodefarm.py b/geopy/geocoders/geocodefarm.py
index f879ab2..5713c20 100644
--- a/geopy/geocoders/geocodefarm.py
+++ b/geopy/geocoders/geocodefarm.py
@@ -25,11 +25,12 @@ class GeocodeFarm(Geocoder):
     def __init__(
             self,
             api_key=None,
+            *,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
-            scheme=None,
+            scheme=None
     ):
         """
 
@@ -68,7 +69,7 @@ class GeocodeFarm(Geocoder):
             "%s://%s%s" % (self.scheme, domain, self.reverse_path)
         )
 
-    def geocode(self, query, exactly_one=True, timeout=DEFAULT_SENTINEL):
+    def geocode(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL):
         """
         Return a location point by address.
 
@@ -96,7 +97,7 @@ class GeocodeFarm(Geocoder):
             self._call_geocoder(url, timeout=timeout), exactly_one
         )
 
-    def reverse(self, query, exactly_one=True, timeout=DEFAULT_SENTINEL):
+    def reverse(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL):
         """
         Return an address by location point.
 
@@ -133,9 +134,7 @@ class GeocodeFarm(Geocoder):
             self._call_geocoder(url, timeout=timeout), exactly_one
         )
 
-    @staticmethod
-    def parse_code(results):
-        # TODO make this a private API
+    def _parse_code(self, results):
         # Parse each resource.
         places = []
         for result in results.get('RESULTS'):
@@ -163,14 +162,13 @@ class GeocodeFarm(Geocoder):
         if "NO_RESULTS" in geocoding_results.get("STATUS", {}).get("status", ""):
             return None
 
-        places = self.parse_code(geocoding_results)
+        places = self._parse_code(geocoding_results)
         if exactly_one:
             return places[0]
         else:
             return places
 
-    @staticmethod
-    def _check_for_api_errors(geocoding_results):
+    def _check_for_api_errors(self, geocoding_results):
         """
         Raise any exceptions if there were problems reported
         in the api response.
diff --git a/geopy/geocoders/geolake.py b/geopy/geocoders/geolake.py
index 4d345bd..cbb821d 100644
--- a/geopy/geocoders/geolake.py
+++ b/geopy/geocoders/geolake.py
@@ -34,12 +34,13 @@ class Geolake(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             domain='api.geolake.com',
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -82,9 +83,10 @@ class Geolake(Geocoder):
     def geocode(
             self,
             query,
+            *,
             country_codes=None,
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return a location point by address.
diff --git a/geopy/geocoders/geonames.py b/geopy/geocoders/geonames.py
index 446b095..bbaa3a2 100644
--- a/geopy/geocoders/geonames.py
+++ b/geopy/geocoders/geonames.py
@@ -1,7 +1,6 @@
 from urllib.parse import urlencode
 
 from geopy.exc import (
-    ConfigurationError,
     GeocoderAuthenticationFailure,
     GeocoderInsufficientPrivileges,
     GeocoderQueryError,
@@ -37,12 +36,13 @@ class GeoNames(Geocoder):
 
     def __init__(
             self,
-            username=None,
+            username,
+            *,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
-            scheme='http',
+            scheme='http'
     ):
         """
 
@@ -77,12 +77,6 @@ class GeoNames(Geocoder):
             user_agent=user_agent,
             ssl_context=ssl_context,
         )
-        if username is None:
-            raise ConfigurationError(
-                'No username given, required for api access.  If you do not '
-                'have a GeoNames username, sign up here: '
-                'http://www.geonames.org/login'
-            )
         self.username = username
 
         domain = 'api.geonames.org'
@@ -102,10 +96,11 @@ class GeoNames(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             country=None,
-            country_bias=None,
+            country_bias=None
     ):
         """
         Return a location point by address.
@@ -158,11 +153,12 @@ class GeoNames(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             feature_code=None,
             lang=None,
-            find_nearby_type='findNearbyPlaceName',
+            find_nearby_type='findNearbyPlaceName'
     ):
         """
         Return an address by location point.
@@ -257,7 +253,7 @@ class GeoNames(Geocoder):
             params['lang'] = lang
         return params
 
-    def reverse_timezone(self, query, timeout=DEFAULT_SENTINEL):
+    def reverse_timezone(self, query, *, timeout=DEFAULT_SENTINEL):
         """
         Find the timezone for a point in `query`.
 
diff --git a/geopy/geocoders/googlev3.py b/geopy/geocoders/googlev3.py
index f078894..f38f8f9 100644
--- a/geopy/geocoders/googlev3.py
+++ b/geopy/geocoders/googlev3.py
@@ -33,6 +33,7 @@ class GoogleV3(Geocoder):
     def __init__(
             self,
             api_key=None,
+            *,
             domain='maps.googleapis.com',
             scheme=None,
             client_id=None,
@@ -41,7 +42,7 @@ class GoogleV3(Geocoder):
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
-            channel='',
+            channel=''
     ):
         """
 
@@ -134,8 +135,7 @@ class GoogleV3(Geocoder):
             self.scheme, self.domain, path, signature
         )
 
-    @staticmethod
-    def _format_components_param(components):
+    def _format_components_param(self, components):
         """
         Format the components dict to something Google understands.
         """
@@ -159,6 +159,7 @@ class GoogleV3(Geocoder):
     def geocode(
             self,
             query=None,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             bounds=None,
@@ -166,7 +167,7 @@ class GoogleV3(Geocoder):
             components=None,
             place_id=None,
             language=None,
-            sensor=False,
+            sensor=False
     ):
         """
         Return a location point by address.
@@ -260,10 +261,11 @@ class GoogleV3(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             language=None,
-            sensor=False,
+            sensor=False
     ):
         """
         Return an address by location point.
@@ -309,7 +311,7 @@ class GoogleV3(Geocoder):
             self._call_geocoder(url, timeout=timeout), exactly_one
         )
 
-    def reverse_timezone(self, query, at_time=None, timeout=DEFAULT_SENTINEL):
+    def reverse_timezone(self, query, *, at_time=None, timeout=DEFAULT_SENTINEL):
         """
         Find the timezone a point in `query` was in for a specified `at_time`.
 
@@ -399,8 +401,7 @@ class GoogleV3(Geocoder):
         else:
             return [parse_place(place) for place in places]
 
-    @staticmethod
-    def _check_status(status):
+    def _check_status(self, status):
         """
         Validates error statuses.
         """
diff --git a/geopy/geocoders/here.py b/geopy/geocoders/here.py
index 390c7cb..7301f39 100644
--- a/geopy/geocoders/here.py
+++ b/geopy/geocoders/here.py
@@ -40,6 +40,7 @@ class Here(Geocoder):
 
     def __init__(
             self,
+            *,
             app_id=None,
             app_code=None,
             apikey=None,
@@ -47,7 +48,7 @@ class Here(Geocoder):
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -130,6 +131,7 @@ class Here(Geocoder):
     def geocode(
             self,
             query,
+            *,
             bbox=None,
             mapview=None,
             exactly_one=True,
@@ -237,6 +239,7 @@ class Here(Geocoder):
     def reverse(
             self,
             query,
+            *,
             radius=None,
             exactly_one=True,
             maxresults=None,
@@ -314,8 +317,7 @@ class Here(Geocoder):
             exactly_one
         )
 
-    @staticmethod
-    def _parse_json(doc, exactly_one=True):
+    def _parse_json(self, doc, exactly_one=True):
         """
         Parse a location name, latitude, and longitude from an JSON response.
         """
diff --git a/geopy/geocoders/ignfrance.py b/geopy/geocoders/ignfrance.py
index 1271860..64d7fb7 100644
--- a/geopy/geocoders/ignfrance.py
+++ b/geopy/geocoders/ignfrance.py
@@ -1,7 +1,6 @@
 import base64
 import xml.etree.ElementTree as ET
 from urllib.parse import urlencode
-from urllib.request import Request
 
 from geopy.exc import ConfigurationError, GeocoderQueryError
 from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder
@@ -39,6 +38,7 @@ class IGNFrance(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             username=None,
             password=None,
             referer=None,
@@ -47,7 +47,7 @@ class IGNFrance(Geocoder):
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -121,12 +121,13 @@ class IGNFrance(Geocoder):
     def geocode(
             self,
             query,
+            *,
             query_type='StreetAddress',
             maximum_responses=25,
             is_freeform=False,
             filtering=None,
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return a location point by address.
@@ -226,11 +227,12 @@ class IGNFrance(Geocoder):
     def reverse(
             self,
             query,
+            *,
             reverse_geocode_preference=('StreetAddress', ),
             maximum_responses=25,
             filtering='',
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return an address by location point.
@@ -356,8 +358,7 @@ class IGNFrance(Geocoder):
                 ) for place in places
             ]
 
-    @staticmethod
-    def _xml_to_json_places(tree, is_reverse=False):
+    def _xml_to_json_places(self, tree, is_reverse=False):
         """
         Transform the xml ElementTree due to XML webservice return to json
         """
@@ -446,29 +447,25 @@ class IGNFrance(Geocoder):
         """
         Send the request to get raw content.
         """
-
-        request = Request(url)
-
+        headers = {}
         if self.referer is not None:
-            request.add_header('Referer', self.referer)
+            headers['Referer'] = self.referer
 
         if self.username and self.password and self.referer is None:
             credentials = '{0}:{1}'.format(self.username, self.password).encode()
             auth_str = base64.standard_b64encode(credentials).decode()
-            request.add_unredirected_header(
-                'Authorization',
-                'Basic {}'.format(auth_str.strip()))
+            headers['Authorization'] = 'Basic {}'.format(auth_str.strip())
 
         raw_xml = self._call_geocoder(
-            request,
+            url,
+            headers=headers,
             timeout=timeout,
-            deserializer=None
+            is_json=False,
         )
 
         return raw_xml
 
-    @staticmethod
-    def _parse_place(place, is_freeform=None):
+    def _parse_place(self, place, is_freeform=None):
         """
         Get the location, lat, lng and place from a single json place.
         """
diff --git a/geopy/geocoders/mapbox.py b/geopy/geocoders/mapbox.py
index 29db554..35751be 100644
--- a/geopy/geocoders/mapbox.py
+++ b/geopy/geocoders/mapbox.py
@@ -20,12 +20,13 @@ class MapBox(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
-            domain='api.mapbox.com',
+            domain='api.mapbox.com'
     ):
         """
         :param str api_key: The API key required by Mapbox to perform
@@ -80,11 +81,12 @@ class MapBox(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             proximity=None,
             country=None,
-            bbox=None,
+            bbox=None
     ):
         """
         Return a location point by address.
@@ -149,8 +151,9 @@ class MapBox(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return an address by location point.
diff --git a/geopy/geocoders/mapquest.py b/geopy/geocoders/mapquest.py
index fc9020c..31d76d0 100644
--- a/geopy/geocoders/mapquest.py
+++ b/geopy/geocoders/mapquest.py
@@ -27,12 +27,13 @@ class MapQuest(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
-            domain='www.mapquestapi.com',
+            domain='www.mapquestapi.com'
     ):
         """
         :param str api_key: The API key required by Mapquest to perform
@@ -111,6 +112,7 @@ class MapQuest(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             limit=None,
@@ -167,6 +169,7 @@ class MapQuest(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL
     ):
diff --git a/geopy/geocoders/maptiler.py b/geopy/geocoders/maptiler.py
index f3fa861..fc56e9d 100644
--- a/geopy/geocoders/maptiler.py
+++ b/geopy/geocoders/maptiler.py
@@ -20,12 +20,13 @@ class MapTiler(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
-            domain='api.maptiler.com',
+            domain='api.maptiler.com'
     ):
         """
         :param str api_key: The API key required by Maptiler to perform
@@ -81,11 +82,12 @@ class MapTiler(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             proximity=None,
             language=None,
-            bbox=None,
+            bbox=None
     ):
         """
         Return a location point by address.
@@ -145,9 +147,10 @@ class MapTiler(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
-            language=None,
+            language=None
     ):
         """
         Return an address by location point.
diff --git a/geopy/geocoders/opencage.py b/geopy/geocoders/opencage.py
index 0996aa1..58eb31e 100644
--- a/geopy/geocoders/opencage.py
+++ b/geopy/geocoders/opencage.py
@@ -20,12 +20,13 @@ class OpenCage(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             domain='api.opencagedata.com',
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -67,11 +68,12 @@ class OpenCage(Geocoder):
     def geocode(
             self,
             query,
+            *,
             bounds=None,
             country=None,
             language=None,
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return a location point by address.
@@ -137,9 +139,10 @@ class OpenCage(Geocoder):
     def reverse(
             self,
             query,
+            *,
             language=None,
             exactly_one=True,
-            timeout=DEFAULT_SENTINEL,
+            timeout=DEFAULT_SENTINEL
     ):
         """
         Return an address by location point.
@@ -197,8 +200,7 @@ class OpenCage(Geocoder):
         else:
             return [parse_place(place) for place in places]
 
-    @staticmethod
-    def _check_status(status):
+    def _check_status(self, status):
         """
         Validates error statuses.
         """
diff --git a/geopy/geocoders/openmapquest.py b/geopy/geocoders/openmapquest.py
index 8f11984..87164c2 100644
--- a/geopy/geocoders/openmapquest.py
+++ b/geopy/geocoders/openmapquest.py
@@ -1,4 +1,3 @@
-from geopy.exc import ConfigurationError
 from geopy.geocoders.base import DEFAULT_SENTINEL
 from geopy.geocoders.osm import Nominatim
 
@@ -24,13 +23,14 @@ class OpenMapQuest(Nominatim):
 
     def __init__(
             self,
-            api_key=None,
+            api_key,
+            *,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             domain='open.mapquestapi.com',
             scheme=None,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -63,8 +63,6 @@ class OpenMapQuest(Nominatim):
             user_agent=user_agent,
             ssl_context=ssl_context,
         )
-        if not api_key:
-            raise ConfigurationError('OpenMapQuest requires an API key')
         self.api_key = api_key
 
     def _construct_url(self, base_api, params):
diff --git a/geopy/geocoders/osm.py b/geopy/geocoders/osm.py
index b96be54..ed19c8e 100644
--- a/geopy/geocoders/osm.py
+++ b/geopy/geocoders/osm.py
@@ -51,12 +51,13 @@ class Nominatim(Geocoder):
 
     def __init__(
             self,
+            *,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             domain=_DEFAULT_NOMINATIM_DOMAIN,
             scheme=None,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
             # Make sure to synchronize the changes of this signature in the
             # inheriting classes (e.g. PickPoint).
     ):
@@ -126,6 +127,7 @@ class Nominatim(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             limit=None,
@@ -137,7 +139,7 @@ class Nominatim(Geocoder):
             viewbox=None,
             bounded=False,
             featuretype=None,
-            namedetails=False,
+            namedetails=False
     ):
         """
         Return a location point by address.
@@ -288,6 +290,7 @@ class Nominatim(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             language=False,
@@ -351,9 +354,7 @@ class Nominatim(Geocoder):
             self._call_geocoder(url, timeout=timeout), exactly_one
         )
 
-    @staticmethod
-    def parse_code(place):
-        # TODO make this a private API
+    def _parse_code(self, place):
         # Parse each resource.
         latitude = place.get('lat', None)
         longitude = place.get('lon', None)
@@ -369,6 +370,6 @@ class Nominatim(Geocoder):
         if not isinstance(places, collections.abc.Sequence):
             places = [places]
         if exactly_one:
-            return self.parse_code(places[0])
+            return self._parse_code(places[0])
         else:
-            return [self.parse_code(place) for place in places]
+            return [self._parse_code(place) for place in places]
diff --git a/geopy/geocoders/pelias.py b/geopy/geocoders/pelias.py
index 674395e..01544d3 100644
--- a/geopy/geocoders/pelias.py
+++ b/geopy/geocoders/pelias.py
@@ -24,11 +24,12 @@ class Pelias(Geocoder):
             self,
             domain,
             api_key=None,
+            *,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             scheme=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
             # Make sure to synchronize the changes of this signature in the
             # inheriting classes (e.g. GeocodeEarth).
     ):
@@ -75,6 +76,7 @@ class Pelias(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             boundary_rect=None,
@@ -141,6 +143,7 @@ class Pelias(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             language=None
@@ -193,9 +196,7 @@ class Pelias(Geocoder):
             self._call_geocoder(url, timeout=timeout), exactly_one
         )
 
-    @staticmethod
-    def parse_code(feature):
-        # TODO make this a private API
+    def _parse_code(self, feature):
         # Parse each resource.
         latitude = feature.get('geometry', {}).get('coordinates', [])[1]
         longitude = feature.get('geometry', {}).get('coordinates', [])[0]
@@ -209,6 +210,6 @@ class Pelias(Geocoder):
         if not len(features):
             return None
         if exactly_one:
-            return self.parse_code(features[0])
+            return self._parse_code(features[0])
         else:
-            return [self.parse_code(feature) for feature in features]
+            return [self._parse_code(feature) for feature in features]
diff --git a/geopy/geocoders/photon.py b/geopy/geocoders/photon.py
index e210a15..3677d7e 100644
--- a/geopy/geocoders/photon.py
+++ b/geopy/geocoders/photon.py
@@ -24,12 +24,13 @@ class Photon(Geocoder):
 
     def __init__(
             self,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             domain='photon.komoot.de',
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -67,6 +68,7 @@ class Photon(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             location_bias=None,
@@ -140,10 +142,11 @@ class Photon(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             language=False,
-            limit=None,
+            limit=None
     ):
         """
         Return an address by location point.
@@ -189,22 +192,19 @@ class Photon(Geocoder):
             self._call_geocoder(url, timeout=timeout), exactly_one
         )
 
-    @classmethod
-    def _parse_json(cls, resources, exactly_one=True):
+    def _parse_json(self, resources, exactly_one=True):
         """
         Parse display name, latitude, and longitude from a JSON response.
         """
         if not len(resources['features']):  # pragma: no cover
             return None
         if exactly_one:
-            return cls.parse_resource(resources['features'][0])
+            return self._parse_resource(resources['features'][0])
         else:
-            return [cls.parse_resource(resource) for resource
+            return [self._parse_resource(resource) for resource
                     in resources['features']]
 
-    @classmethod
-    def parse_resource(cls, resource):
-        # TODO make this a private API
+    def _parse_resource(self, resource):
         # Return location and coordinates tuple from dict.
         name_elements = ['name', 'housenumber', 'street',
                          'postcode', 'street', 'city',
diff --git a/geopy/geocoders/pickpoint.py b/geopy/geocoders/pickpoint.py
index d14e7b2..c6f597f 100644
--- a/geopy/geocoders/pickpoint.py
+++ b/geopy/geocoders/pickpoint.py
@@ -17,12 +17,13 @@ class PickPoint(Nominatim):
     def __init__(
             self,
             api_key,
+            *,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             domain='api.pickpoint.io',
             scheme=None,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
diff --git a/geopy/geocoders/smartystreets.py b/geopy/geocoders/smartystreets.py
index d57867e..879c4bd 100644
--- a/geopy/geocoders/smartystreets.py
+++ b/geopy/geocoders/smartystreets.py
@@ -21,10 +21,11 @@ class LiveAddress(Geocoder):
             self,
             auth_id,
             auth_token,
+            *,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -61,9 +62,10 @@ class LiveAddress(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
-            candidates=1,
+            candidates=1
     ):
         """
         Return a location point by address.
@@ -121,8 +123,7 @@ class LiveAddress(Geocoder):
         else:
             return [self._format_structured_address(c) for c in response]
 
-    @staticmethod
-    def _format_structured_address(address):
+    def _format_structured_address(self, address):
         """
         Pretty-print address and return lat, lon tuple.
         """
diff --git a/geopy/geocoders/tomtom.py b/geopy/geocoders/tomtom.py
index ea3007f..6c27d63 100644
--- a/geopy/geocoders/tomtom.py
+++ b/geopy/geocoders/tomtom.py
@@ -21,12 +21,13 @@ class TomTom(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             scheme=None,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             ssl_context=DEFAULT_SENTINEL,
-            domain='api.tomtom.com',
+            domain='api.tomtom.com'
     ):
         """
         :param str api_key: TomTom API key.
@@ -64,11 +65,12 @@ class TomTom(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             limit=None,
             typeahead=False,
-            language=None,
+            language=None
     ):
         """
         Return a location point by address.
@@ -122,9 +124,10 @@ class TomTom(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
-            language=None,
+            language=None
     ):
         """
         Return an address by location point.
@@ -166,8 +169,7 @@ class TomTom(Geocoder):
             self._call_geocoder(url, timeout=timeout), exactly_one
         )
 
-    @staticmethod
-    def _boolean_value(bool_value):
+    def _boolean_value(self, bool_value):
         return 'true' if bool_value else 'false'
 
     def _geocode_params(self, formatted_query):
diff --git a/geopy/geocoders/what3words.py b/geopy/geocoders/what3words.py
index 7c3c9a6..80ddbd9 100644
--- a/geopy/geocoders/what3words.py
+++ b/geopy/geocoders/what3words.py
@@ -26,10 +26,11 @@ class What3Words(Geocoder):
     def __init__(
             self,
             api_key,
+            *,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -71,11 +72,14 @@ class What3Words(Geocoder):
         else:
             return True
 
-    def geocode(self,
-                query,
-                lang='en',
-                exactly_one=True,
-                timeout=DEFAULT_SENTINEL):
+    def geocode(
+            self,
+            query,
+            *,
+            lang='en',
+            exactly_one=True,
+            timeout=DEFAULT_SENTINEL
+    ):
 
         """
         Return a location point for a `3 words` query. If the `3 words` address
@@ -158,8 +162,14 @@ class What3Words(Geocoder):
         else:
             return [location]
 
-    def reverse(self, query, lang='en', exactly_one=True,
-                timeout=DEFAULT_SENTINEL):
+    def reverse(
+            self,
+            query,
+            *,
+            lang='en',
+            exactly_one=True,
+            timeout=DEFAULT_SENTINEL
+    ):
         """
         Return a `3 words` address by location point. Each point on surface has
         a `3 words` address, so there's always a non-empty response.
diff --git a/geopy/geocoders/yandex.py b/geopy/geocoders/yandex.py
index 74b2a1e..6d5d958 100644
--- a/geopy/geocoders/yandex.py
+++ b/geopy/geocoders/yandex.py
@@ -1,4 +1,3 @@
-import warnings
 from urllib.parse import urlencode
 
 from geopy.exc import GeocoderParseError, GeocoderServiceError
@@ -24,12 +23,13 @@ class Yandex(Geocoder):
 
     def __init__(
             self,
-            api_key=None,
+            api_key,
+            *,
             timeout=DEFAULT_SENTINEL,
             proxies=DEFAULT_SENTINEL,
             user_agent=None,
             scheme=None,
-            ssl_context=DEFAULT_SENTINEL,
+            ssl_context=DEFAULT_SENTINEL
     ):
         """
 
@@ -59,14 +59,6 @@ class Yandex(Geocoder):
             user_agent=user_agent,
             ssl_context=ssl_context,
         )
-        if not api_key:
-            warnings.warn(
-                'Since September 2019 Yandex requires each request to have an API key. '
-                'Pass a valid `api_key` to Yandex geocoder to hide this warning. '
-                'API keys can be created at https://developer.tech.yandex.ru/',
-                UserWarning,
-                stacklevel=2
-            )
         self.api_key = api_key
         domain = 'geocode-maps.yandex.ru'
         self.api = '%s://%s%s' % (self.scheme, domain, self.api_path)
@@ -74,9 +66,10 @@ class Yandex(Geocoder):
     def geocode(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
-            lang=None,
+            lang=None
     ):
         """
         Return a location point by address.
@@ -108,8 +101,7 @@ class Yandex(Geocoder):
             'geocode': query,
             'format': 'json'
         }
-        if self.api_key:
-            params['apikey'] = self.api_key
+        params['apikey'] = self.api_key
         if lang:
             params['lang'] = lang
         if exactly_one:
@@ -124,10 +116,11 @@ class Yandex(Geocoder):
     def reverse(
             self,
             query,
+            *,
             exactly_one=True,
             timeout=DEFAULT_SENTINEL,
             kind=None,
-            lang=None,
+            lang=None
     ):
         """
         Return an address by location point.
@@ -170,8 +163,7 @@ class Yandex(Geocoder):
             'geocode': point,
             'format': 'json'
         }
-        if self.api_key:
-            params['apikey'] = self.api_key
+        params['apikey'] = self.api_key
         if lang:
             params['lang'] = lang
         if kind:


Test patch:
diff --git a/test/geocoders/base.py b/test/geocoders/base.py
index fcf346d..64f6e9d 100644
--- a/test/geocoders/base.py
+++ b/test/geocoders/base.py
@@ -96,16 +96,21 @@ class GeocoderTestCase(unittest.TestCase):
         g = Geocoder()
         assert g.timeout == 12
 
-        with patch.object(g, 'urlopen') as mock_urlopen:
-            g._call_geocoder(url, raw=True)
+        with ExitStack() as stack:
+            mock_urlopen = stack.enter_context(patch.object(g, 'urlopen'))
+            stack.enter_context(
+                patch.object(geopy.geocoders.base, 'decode_page', return_value='{}')
+            )
+
+            g._call_geocoder(url)
             args, kwargs = mock_urlopen.call_args
             assert kwargs['timeout'] == 12
 
-            g._call_geocoder(url, timeout=7, raw=True)
+            g._call_geocoder(url, timeout=7)
             args, kwargs = mock_urlopen.call_args
             assert kwargs['timeout'] == 7
 
-            g._call_geocoder(url, timeout=None, raw=True)
+            g._call_geocoder(url, timeout=None)
             args, kwargs = mock_urlopen.call_args
             assert kwargs['timeout'] is None
 
@@ -139,7 +144,7 @@ class GeocoderPointCoercionTestCase(unittest.TestCase):
     coordinates_address = "175 5th Avenue, NYC, USA"
 
     def setUp(self):
-        self.method = Geocoder._coerce_point_to_string
+        self.method = Geocoder()._coerce_point_to_string
 
     def test_point(self):
         latlon = self.method(Point(*self.coordinates))
@@ -171,7 +176,7 @@ class GeocoderPointCoercionTestCase(unittest.TestCase):
 class GeocoderFormatBoundingBoxTestCase(unittest.TestCase):
 
     def setUp(self):
-        self.method = Geocoder._format_bounding_box
+        self.method = Geocoder()._format_bounding_box
 
     def test_string_raises(self):
         with pytest.raises(GeocoderQueryError):
diff --git a/test/geocoders/googlev3.py b/test/geocoders/googlev3.py
index 768a441..e6b888d 100644
--- a/test/geocoders/googlev3.py
+++ b/test/geocoders/googlev3.py
@@ -105,7 +105,7 @@ class GoogleV3TestCase(GeocoderTestBase):
         assert 'client' in params
 
     def test_format_components_param(self):
-        f = GoogleV3._format_components_param
+        f = self.geocoder._format_components_param
         assert f({}) == ''
         assert f([]) == ''
 
@@ -262,7 +262,7 @@ class GoogleV3TestCase(GeocoderTestBase):
 
     def test_timezone_invalid_at_time(self):
         with pytest.raises(exc.GeocoderQueryError):
-            self.geocoder.reverse_timezone(self.new_york_point, "eek")
+            self.geocoder.reverse_timezone(self.new_york_point, at_time="eek")
 
     def test_reverse_timezone_unknown(self):
         self.reverse_timezone_run(
diff --git a/test/geocoders/openmapquest.py b/test/geocoders/openmapquest.py
index 375fb05..7569618 100644
--- a/test/geocoders/openmapquest.py
+++ b/test/geocoders/openmapquest.py
@@ -1,8 +1,5 @@
 import unittest
 
-import pytest
-
-from geopy.exc import ConfigurationError
 from geopy.geocoders import OpenMapQuest
 from test.geocoders.nominatim import BaseNominatimTestCase
 from test.geocoders.util import GeocoderTestBase, env
@@ -17,10 +14,6 @@ class OpenMapQuestNoNetTestCase(GeocoderTestBase):
         )
         assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0'
 
-    def test_raises_without_apikey(self):
-        with pytest.raises(ConfigurationError):
-            OpenMapQuest()
-
 
 @unittest.skipUnless(
     bool(env.get('OPENMAPQUEST_APIKEY')),
diff --git a/test/geocoders/yandex.py b/test/geocoders/yandex.py
index b6e1840..8579d51 100644
--- a/test/geocoders/yandex.py
+++ b/test/geocoders/yandex.py
@@ -1,5 +1,4 @@
 import unittest
-import warnings
 
 import pytest
 
@@ -26,11 +25,6 @@ class YandexTestCase(GeocoderTestBase):
         )
         assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0'
 
-    def test_warning_with_no_api_key(self):
-        with warnings.catch_warnings(record=True) as w:
-            Yandex()
-        assert len(w) == 1
-
     def test_unicode_name(self):
         self.geocode_run(
             {"query": "площадь Ленина Донецк"},
diff --git a/test/test_distance.py b/test/test_distance.py
index 874b1da..c8242c9 100644
--- a/test/test_distance.py
+++ b/test/test_distance.py
@@ -260,9 +260,9 @@ class CommonDistanceCases(CommonDistanceComputationCases,
 
 
 class TestWhenInstantiatingBaseDistanceClass(unittest.TestCase):
-    def test_should_not_be_able_to_give_multiple_points(self):
-        with self.assertRaises(NotImplementedError):
-            Distance(1, 2, 3, 4)
+    def test_should_not_be_able_to_instantiate(self):
+        with self.assertRaises(TypeError):
+            Distance((0, 0), (0, 180))
 
 
 class TestDefaultDistanceClass(unittest.TestCase):
diff --git a/test/test_proxy.py b/test/test_proxy.py
index a773d2f..149abe3 100644
--- a/test/test_proxy.py
+++ b/test/test_proxy.py
@@ -1,8 +1,10 @@
 import os
 import ssl
 import unittest
+from unittest.mock import patch
 from urllib.request import getproxies, urlopen
 
+import geopy.geocoders.base
 from geopy.exc import GeocoderServiceError
 from geopy.geocoders.base import Geocoder
 from test.proxy_server import HttpServerThread, ProxyServerThread
@@ -18,7 +20,8 @@ WITH_SYSTEM_PROXIES = bool(getproxies())
 
 class DummyGeocoder(Geocoder):
     def geocode(self, location):
-        geo_request = self._call_geocoder(location, raw=True)
+        with patch.object(geopy.geocoders.base, 'decode_page', lambda page: page):
+            geo_request = self._call_geocoder(location, is_json=False)
         geo_html = geo_request.read()
         return geo_html if geo_html else None