diff --git a/.gitignore b/.gitignore index 0d2efbbc..c4477ba5 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ env googlemaps.egg-info *.egg .vscode/ +.idea/ diff --git a/googlemaps/client.py b/googlemaps/client.py index 9a195c3c..ac054917 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -39,12 +39,13 @@ except ImportError: # Python 2 from urllib import urlencode - +_X_GOOG_MAPS_EXPERIENCE_ID = "X-Goog-Maps-Experience-ID" _USER_AGENT = "GoogleGeoApiClientPython/%s" % googlemaps.__version__ _DEFAULT_BASE_URL = "https://maps.googleapis.com" _RETRIABLE_STATUSES = set([500, 503, 504]) + class Client(object): """Performs requests to the Google Maps API web services.""" @@ -52,7 +53,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, timeout=None, connect_timeout=None, read_timeout=None, retry_timeout=60, requests_kwargs=None, queries_per_second=50, channel=None, - retry_over_query_limit=True): + retry_over_query_limit=True, experience_id=None): """ :param key: Maps API key. Required, unless "client_id" and "client_secret" are set. @@ -99,6 +100,10 @@ def __init__(self, key=None, client_id=None, client_secret=None, retried. Defaults to True. :type retry_over_query_limit: bool + :param experience_id: The value for the HTTP header field name + 'X-Goog-Maps-Experience-ID'. + :type experience_id: str + :raises ValueError: when either credentials are missing, incomplete or invalid. :raises NotImplementedError: if connect_timeout and read_timeout are @@ -150,7 +155,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.retry_timeout = timedelta(seconds=retry_timeout) self.requests_kwargs = requests_kwargs or {} headers = self.requests_kwargs.pop('headers', {}) - headers.update({"User-Agent": _USER_AGENT}) + headers.update({"User-Agent": _USER_AGENT}) self.requests_kwargs.update({ "headers": headers, "timeout": self.timeout, @@ -160,6 +165,42 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.queries_per_second = queries_per_second self.retry_over_query_limit = retry_over_query_limit self.sent_times = collections.deque("", queries_per_second) + self.set_experience_id(experience_id) + + def set_experience_id(self, *experience_id_args): + """Sets the value for the HTTP header field name + 'X-Goog-Maps-Experience-ID' to be used on subsequent API calls. + + :param experience_id_args: the experience ID + :type experience_id_args: string varargs + """ + if len(experience_id_args) == 0 or experience_id_args[0] is None: + self.clear_experience_id() + return + + headers = self.requests_kwargs.pop("headers", {}) + headers[_X_GOOG_MAPS_EXPERIENCE_ID] = ",".join(experience_id_args) + self.requests_kwargs["headers"] = headers + + def get_experience_id(self): + """Gets the experience ID for the HTTP header field name + 'X-Goog-Maps-Experience-ID' + + :return: The experience ID if set + :rtype: str + """ + headers = self.requests_kwargs.get("headers", {}) + return headers.get(_X_GOOG_MAPS_EXPERIENCE_ID, None) + + def clear_experience_id(self): + """Clears the experience ID for the HTTP header field name + 'X-Goog-Maps-Experience-ID' if set. + """ + headers = self.requests_kwargs.get("headers") + if headers is None: + return + headers.pop(_X_GOOG_MAPS_EXPERIENCE_ID, {}) + self.requests_kwargs["headers"] = headers def _request(self, url, params, first_request_time=None, retry_counter=0, base_url=_DEFAULT_BASE_URL, accepts_clientid=True, diff --git a/googlemaps/test/test_client.py b/googlemaps/test/test_client.py index 90d83b3e..9c689b67 100644 --- a/googlemaps/test/test_client.py +++ b/googlemaps/test/test_client.py @@ -22,10 +22,12 @@ import responses import requests +import uuid import googlemaps import googlemaps.client as _client import googlemaps.test as _test +from googlemaps.client import _X_GOOG_MAPS_EXPERIENCE_ID class ClientTest(_test.TestCase): @@ -291,6 +293,87 @@ def test_requests_version(self): googlemaps.Client(**client_args_timeout) googlemaps.Client(**client_args) + def test_single_experience_id(self): + experience_id1 = "Exp1" + client = googlemaps.Client(key="AIzaasdf", experience_id=experience_id1) + self.assertEqual(experience_id1, client.get_experience_id()) + + experience_id2 = "Exp2" + client.set_experience_id(experience_id2) + self.assertEqual(experience_id2, client.get_experience_id()) + + def test_multiple_experience_id(self): + client = googlemaps.Client(key="AIzaasdf") + + experience_id1 = "Exp1" + experience_id2 = "Exp2" + client.set_experience_id(experience_id1, experience_id2) + + result = "%s,%s" % (experience_id1, experience_id2) + self.assertEqual(result, client.get_experience_id()) + + def test_no_experience_id(self): + client = googlemaps.Client(key="AIzaasdf") + self.assertIsNone(client.get_experience_id()) + + def test_clearing_experience_id(self): + client = googlemaps.Client(key="AIzaasdf") + client.set_experience_id("ExpId") + client.clear_experience_id() + self.assertIsNone(client.get_experience_id()) + + def test_experience_id_sample(self): + # [START maps_experience_id] + experience_id = str(uuid.uuid4()) + + # instantiate client with experience id + client = googlemaps.Client( + key="AIza-Maps-API-Key", + experience_id=experience_id + ) + + # clear the current experience id + client.clear_experience_id() + + # set a new experience id + other_experience_id = str(uuid.uuid4()) + client.set_experience_id(experience_id, other_experience_id) + + # make API request, the client will set the header + # X-GOOG-MAPS-EXPERIENCE-ID: experience_id,other_experience_id + + # get current experience id + ids = client.get_experience_id() + # [END maps_experience_id] + + result = "%s,%s" % (experience_id, other_experience_id) + self.assertEqual(result, ids) + + @responses.activate + def _perform_mock_request(self, experience_id=None): + # Mock response + responses.add(responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json") + + # Perform network call + client = googlemaps.Client(key="AIzaasdf") + client.set_experience_id(experience_id) + client.geocode("Sesame St.") + return responses.calls[0].request + + def test_experience_id_in_header(self): + experience_id = "Exp1" + request = self._perform_mock_request(experience_id) + header_value = request.headers[_X_GOOG_MAPS_EXPERIENCE_ID] + self.assertEqual(experience_id, header_value) + + def test_experience_id_no_in_header(self): + request = self._perform_mock_request() + self.assertIsNone(request.headers.get(_X_GOOG_MAPS_EXPERIENCE_ID)) + @responses.activate def test_no_retry_over_query_limit(self): responses.add(responses.GET,