From d489b7832e429e0e6494ebfbb506f75727df4982 Mon Sep 17 00:00:00 2001 From: Sean Wohltman Date: Thu, 24 Sep 2015 14:59:09 -0400 Subject: [PATCH 001/260] Add traffic_model parameter This new parameter allows customers with Client IDs to utilize "Predictive Time in Traffic" in Directions queries which allows them to specify a travel_model (best_guess, optimistic, pessimistic) for a Directions result set to a departure time in the future and returns an estimated durration_in_traffic based on Google's historical traffic data for that day and time. --- googlemaps/directions.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/googlemaps/directions.py b/googlemaps/directions.py index 4864e967..05680f3e 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -24,7 +24,7 @@ def directions(client, origin, destination, mode=None, waypoints=None, alternatives=False, avoid=None, language=None, units=None, region=None, departure_time=None, arrival_time=None, optimize_waypoints=False, transit_mode=None, - transit_routing_preference=None): + transit_routing_preference=None, traffic_model=None): """Get directions between an origin point and a destination point. :param origin: The address or latitude/longitude value from which you wish @@ -82,6 +82,10 @@ def directions(client, origin, destination, :param transit_routing_preference: Specifies preferences for transit requests. Valid values are "less_walking" or "fewer_transfers" :type transit_routing_preference: string + + :param traffic_model: Specifies the predictive travel time model to use. + "best_guess" or "optimistic" or "pessimistic" + :type units: string :rtype: list of routes """ @@ -137,6 +141,9 @@ def directions(client, origin, destination, if transit_routing_preference: params["transit_routing_preference"] = transit_routing_preference + + if traffic_model: + params["traffic_model"] = traffic_model return client._get("/maps/api/directions/json", params)["routes"] From 94b85ae52eb4c7854aade6c88ca59ebdff3a6f58 Mon Sep 17 00:00:00 2001 From: Florian Demmer Date: Mon, 5 Oct 2015 14:59:28 +0200 Subject: [PATCH 002/260] Use utcnow to generate timestamp --- googlemaps/timezone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/timezone.py b/googlemaps/timezone.py index 593a76e7..0c48f0eb 100644 --- a/googlemaps/timezone.py +++ b/googlemaps/timezone.py @@ -45,7 +45,7 @@ def timezone(client, location, timestamp=None, language=None): location = convert.latlng(location) - timestamp = convert.time(timestamp or datetime.now()) + timestamp = convert.time(timestamp or datetime.utcnow()) params = { "location": location, From 865249d527c0b0e77292076966ab3be629ad3158 Mon Sep 17 00:00:00 2001 From: Florian Demmer Date: Mon, 5 Oct 2015 15:07:16 +0200 Subject: [PATCH 003/260] Update test mocking --- test/test_timezone.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_timezone.py b/test/test_timezone.py index d5db4952..11127478 100644 --- a/test/test_timezone.py +++ b/test/test_timezone.py @@ -55,6 +55,7 @@ class MockDatetime(object): def now(self): return datetime.datetime.fromtimestamp(1608) + utcnow = now @responses.activate @mock.patch("googlemaps.timezone.datetime", MockDatetime()) From a86d3f8c7ca6e4f2207cb89f7d4ac032d1b52afa Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 6 Oct 2015 11:00:03 +1100 Subject: [PATCH 004/260] Fix docstring. Change-Id: I6e0bf79b61d4135767770ff07f142104722a00ee --- googlemaps/timezone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/timezone.py b/googlemaps/timezone.py index 0c48f0eb..84372acf 100644 --- a/googlemaps/timezone.py +++ b/googlemaps/timezone.py @@ -34,7 +34,7 @@ def timezone(client, location, timestamp=None, language=None): midnight, January 1, 1970 UTC. The Time Zone API uses the timestamp to determine whether or not Daylight Savings should be applied. Times before 1970 can be expressed as negative values. Optional. Defaults to - ``datetime.now()``. + ``datetime.utcnow()``. :type timestamp: int or datetime.datetime :param language: The language in which to return results. From b31ae99821a1fd04ff14da57492a5c309efc7b10 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 8 Oct 2015 14:16:33 +1100 Subject: [PATCH 005/260] Update gitignore Change-Id: Iab21f38827e70afa2f53f8c679791de8844f32c2 --- .gitignore | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c115db24..78c58e12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Compiled source # ################### *.pyc - + # OS generated files # ###################### .DS_Store @@ -15,7 +15,13 @@ Thumbs.db # Sphinx documentation # ######################## docs/_build/ +generated_docs/ +# Release generated files # +########################### +.eggs/ +build/ +dist/ # vim *.swp From aa4e5be46c73e6e15342dca9612cc840993ffd6f Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 8 Oct 2015 14:17:47 +1100 Subject: [PATCH 006/260] Grammar Change-Id: I1c83238b4ce215b7bdc3d42b85fc8e2a20f6094f --- googlemaps/convert.py | 2 +- googlemaps/geocoding.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/googlemaps/convert.py b/googlemaps/convert.py index c9cfe350..b720b3e5 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -81,7 +81,7 @@ def join_list(sep, arg): :param sep: Separator string. :type sep: string :param arg: Value to coerce into a list. - :type arg: string or list of string + :type arg: string or list of strings :rtype: string """ return sep.join(as_list(arg)) diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index fe37fc92..833fc13e 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -77,14 +77,14 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, human-readable address. :param latlng: The latitude/longitude value for which you wish to obtain the - closest, human-readable address + closest, human-readable address. :type latlng: dict or list or tuple :param result_type: One or more address types to restrict results to. - :type result_type: string or list of string + :type result_type: string or list of strings :param location_type: One or more location types to restrict results to. - :type location_type: list of string + :type location_type: list of strings :param language: The language in which to return results. :type langauge: string From 9878ce79d2ced1b100927f6940b66e265ad2c786 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 8 Oct 2015 14:18:31 +1100 Subject: [PATCH 007/260] Added Places API. Change-Id: Id1cfac8613b888571a09890c68e751d9ee44ee17 --- README.md | 5 +- googlemaps/client.py | 33 +++- googlemaps/places.py | 392 +++++++++++++++++++++++++++++++++++++++++++ test/test_places.py | 158 +++++++++++++++++ 4 files changed, 585 insertions(+), 3 deletions(-) create mode 100644 googlemaps/places.py create mode 100644 test/test_places.py diff --git a/README.md b/README.md index f0f65cc7..44237c60 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ APIs: - [Geocoding API] - [Time Zone API] - [Roads API] + - [Places API] Keep in mind that the same [terms and conditions](https://developers.google.com/maps/terms) apply to usage of the APIs when they're accessed through this library. @@ -87,6 +88,7 @@ https://developers.google.com/maps/. - [Geocoding API] - [Time Zone API] - [Roads API] + - [Places API] ## Usage @@ -139,7 +141,7 @@ customers can use their [API key][apikey], too. # Generating documentation $ tox -e docs - + # Uploading a new release $ easy_install wheel twine $ python setup.py sdist bdist_wheel @@ -159,6 +161,7 @@ customers can use their [API key][apikey], too. [Geocoding API]: https://developers.google.com/maps/documentation/geocoding/ [Time Zone API]: https://developers.google.com/maps/documentation/timezone/ [Roads API]: https://developers.google.com/maps/documentation/roads/ +[Places API]: https://developers.google.com/places/ [issues]: https://github.com/googlemaps/google-maps-services-python/issues [contrib]: https://github.com/googlemaps/google-maps-services-python/blob/master/CONTRIB.md diff --git a/googlemaps/client.py b/googlemaps/client.py index 8e66ccc6..937ce8da 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -133,30 +133,42 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.sent_times = collections.deque("", queries_per_second) def _get(self, url, params, first_request_time=None, retry_counter=0, - base_url=_DEFAULT_BASE_URL, accepts_clientid=True, extract_body=None): + base_url=_DEFAULT_BASE_URL, accepts_clientid=True, + extract_body=None, requests_kwargs=None): """Performs HTTP GET request with credentials, returning the body as JSON. :param url: URL path for the request. Should begin with a slash. :type url: string + :param params: HTTP GET parameters. :type params: dict or list of key/value tuples + :param first_request_time: The time of the first request (None if no retries have occurred). :type first_request_time: datetime.datetime + :param retry_counter: The number of this retry, or zero for first attempt. :type retry_counter: int + :param base_url: The base URL for the request. Defaults to the Maps API server. Should not have a trailing slash. :type base_url: string + :param accepts_clientid: Whether this call supports the client/signature params. Some APIs require API keys (e.g. Roads). :type accepts_clientid: bool + :param extract_body: A function that extracts the body from the request. If the request was not successful, the function should raise a googlemaps.HTTPError or googlemaps.ApiError as appropriate. :type extract_body: function + :param requests_kwargs: Same extra keywords arg for requests as per + __init__, but provided here to allow overriding internally + on a per-request basis. + :type requests_kwargs: dict + :raises ApiError: when the API returns an error. :raises Timeout: if the request timed out. :raises TransportError: when something went wrong while trying to @@ -181,8 +193,11 @@ def _get(self, url, params, first_request_time=None, retry_counter=0, authed_url = self._generate_auth_url(url, params, accepts_clientid) + # Default to the client-level self.requests_kwargs, with method-level + # requests_kwargs arg overriding. + requests_kwargs = dict(self.requests_kwargs, **(requests_kwargs or {})) try: - resp = requests.get(base_url + authed_url, **self.requests_kwargs) + resp = requests.get(base_url + authed_url, **requests_kwargs) except requests.exceptions.Timeout: raise googlemaps.exceptions.Timeout() except Exception as e: @@ -272,6 +287,13 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.roads import snap_to_roads from googlemaps.roads import speed_limits from googlemaps.roads import snapped_speed_limits +from googlemaps.places import places +from googlemaps.places import places_nearby +from googlemaps.places import places_radar +from googlemaps.places import place +from googlemaps.places import photo +from googlemaps.places import autocomplete +from googlemaps.places import autocomplete_query Client.directions = directions Client.distance_matrix = distance_matrix @@ -283,6 +305,13 @@ def _generate_auth_url(self, path, params, accepts_clientid): Client.snap_to_roads = snap_to_roads Client.speed_limits = speed_limits Client.snapped_speed_limits = snapped_speed_limits +Client.places = places +Client.places_nearby = places_nearby +Client.places_radar = places_radar +Client.place = place +Client.photo = photo +Client.autocomplete = autocomplete +Client.autocomplete_query = autocomplete_query def sign_hmac(secret, payload): diff --git a/googlemaps/places.py b/googlemaps/places.py new file mode 100644 index 00000000..7dfd77f4 --- /dev/null +++ b/googlemaps/places.py @@ -0,0 +1,392 @@ +# +# Copyright 2015 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Performs requests to the Google Maps Places API.""" + +from googlemaps import convert + + +def places(client, query, location=None, radius=None, language=None, + min_price=None, max_price=None, open_now=False, types=None, + page_token=None): + """ + Performs text search for places. + + :param query: The text string on which to search, for example: "restaurant". + :type query: string + + :param location: The latitude/longitude value for which you wish to obtain the + closest, human-readable address. + :type location: dict or list or tuple + + :param radius: Distance in meters within which to bias results. + :type radius: int + + :param language: The language in which to return results. + :type langauge: string + + :param min_price: Restricts results to only those places with no less than + this price level. Valid values are in the range from 0 + (most affordable) to 4 (most expensive). + :type min_price: int + + :param max_price: Restricts results to only those places with no greater + than this price level. Valid values are in the range + from 0 (most affordable) to 4 (most expensive). + :type max_price: int + + :param open_now: Return only those places that are open for business at + the time the query is sent. + :type open_now: bool + + :param types: Restricts the results to places matching at least one of the + specified types. + :type types: string or list of strings + + :param page_token: Token from a previous search that when provided will + returns the next page of results for the same search. + :type page_token: string + + :rtype: result dict with the following keys: + status: status code + results: list of places + html_attributions: set of attributions which must be displayed + next_page_token: token for retrieving the next page of results + + """ + return _places(client, "text", query=query, location=location, + radius=radius, language=language, min_price=min_price, + max_price=min_price, open_now=open_now, types=types, + page_token=page_token) + + +def places_nearby(client, location, radius=None, keyword=None, language=None, + min_price=None, max_price=None, name=None, open_now=False, + rank_by=None, types=None, page_token=None): + """ + Performs nearby search for places. + + :param location: The latitude/longitude value for which you wish to obtain the + closest, human-readable address. + :type location: dict or list or tuple + + :param radius: Distance in meters within which to bias results. + :type radius: int + + :param keyword: A term to be matched against all content that Google has + indexed for this place. + :type keyword: string + + :param language: The language in which to return results. + :type langauge: string + + :param min_price: Restricts results to only those places with no less than + this price level. Valid values are in the range from 0 + (most affordable) to 4 (most expensive). + :type min_price: int + + :param max_price: Restricts results to only those places with no greater + than this price level. Valid values are in the range + from 0 (most affordable) to 4 (most expensive). + :type max_price: int + + :param name: One or more terms to be matched against the names of places. + :type name: string or list of strings + + :param open_now: Return only those places that are open for business at + the time the query is sent. + :type open_now: bool + + :param rank_by: Specifies the order in which results are listed. + Possible values are: prominence (default), distance + :type rank_by: string + + :param types: Restricts the results to places matching at least one of the + specified types. + :type types: string or list of strings + + :param page_token: Token from a previous search that when provided will + returns the next page of results for the same search. + :type page_token: string + + :rtype: result dict with the following keys: + status: status code + results: list of places + html_attributions: set of attributions which must be displayed + next_page_token: token for retrieving the next page of results + + """ + if rank_by == "distance": + if not (keyword or name or types): + raise ValueError("either a keyword, name, or types arg is required " + "when rank_by is set to distance") + elif radius is not None: + raise ValueError("radius cannot be specified when rank_by is set to " + "distance") + + return _places(client, "nearby", location=location, radius=radius, + keyword=keyword, language=language, min_price=min_price, + max_price=max_price, name=name, open_now=open_now, + rank_by=rank_by, types=types, page_token=page_token) + + +def places_radar(client, location, radius, keyword=None, min_price=None, + max_price=None, name=None, open_now=False, types=None): + + """ + Performs radar search for places. + + :param location: The latitude/longitude value for which you wish to obtain the + closest, human-readable address. + :type location: dict or list or tuple + + :param radius: Distance in meters within which to bias results. + :type radius: int + + :param keyword: A term to be matched against all content that Google has + indexed for this place. + :type keyword: string + + :param min_price: Restricts results to only those places with no less than + this price level. Valid values are in the range from 0 + (most affordable) to 4 (most expensive). + :type min_price: int + + :param max_price: Restricts results to only those places with no greater + than this price level. Valid values are in the range + from 0 (most affordable) to 4 (most expensive). + :type max_price: int + + :param name: One or more terms to be matched against the names of places. + :type name: string or list of strings + + :param open_now: Return only those places that are open for business at + the time the query is sent. + :type open_now: bool + + :param types: Restricts the results to places matching at least one of the + specified types. + :type types: string or list of strings + + :rtype: result dict with the following keys: + status: status code + results: list of places + html_attributions: set of attributions which must be displayed + + """ + if not (keyword or name or types): + raise ValueError("either a keyword, name, or types arg is required") + + return _places(client, "radar", location=location, radius=radius, + keyword=keyword, min_price=min_price, max_price=min_price, + name=name, open_now=open_now, types=types) + + +def _places(client, url_part, query=None, location=None, radius=None, + keyword=None, language=None, min_price=0, max_price=4, name=None, + open_now=False, rank_by=None, types=None, page_token=None): + """ + Internal handler for ``places``, ``places_nearby``, and ``places_radar``. + See each method's docs for arg details. + """ + + params = {"minprice": min_price, "maxprice": max_price} + + if query: + params["query"] = query + if location: + params["location"] = convert.latlng(location) + if radius: + params["radius"] = radius + if keyword: + params["keyword"] = keyword + if language: + params["language"] = language + if name: + params["name"] = convert.join_list(" ", name) + if open_now: + params["opennow"] = "true" + if rank_by: + params["rankby"] = rank_by + if types: + params["types"] = convert.join_list("|", types) + if page_token: + params["pagetoken"] = page_token + + url = "/maps/api/place/%ssearch/json" % url_part + return client._get(url, params) + + +def place(client, place_id, language=None): + """ + Comprehensive details for an individual place. + + :param place_id: A textual identifier that uniquely identifies a place, + returned from a Places search. + :type place_id: string + + :param language: The language in which to return results. + :type langauge: string + + :rtype: result dict with the following keys: + status: status code + result: dict containing place details + html_attributions: set of attributions which must be displayed + + """ + params = {"placeid": place_id} + if language: + params["language"] = language + return client._get("/maps/api/place/details/json", params) + + +def photo(client, photo_reference, max_width=None, max_height=None): + """ + Downloads a places photo. + + :param photo_reference: A string identifier that uniquely identifies a + photo, as provided by either a Places search or + Places detail request. + :type photo_reference: string + + :param max_width: Specifies the maximum desired width, in pixels. + :type max_width: int + + :param max_height: Specifies the maximum desired height, in pixels. + :type max_height: int + + :rtype: iterator containing the raw image data. + + """ + + if not (max_width or max_height): + raise ValueError("a max_width or max_height arg is required") + + params = {"photoreference": photo_reference} + + if max_width: + params["maxwidth"] = max_width + if max_height: + params["maxheight"] = max_height + + # "extract_body" and "stream" args here are used to return an iterable + # response containing the image file data, rather than converting from + # json. + response = client._get("/maps/api/place/photo", params, + extract_body=lambda response: response, + requests_kwargs={"stream": True}) + return response.iter_content() + + +def autocomplete(client, input_text, offset=None, location=None, radius=None, + language=None, types=None, components=None): + """ + Returns Place predictions given a textual search string and optional + geographic bounds. + + :param input_text: The text string on which to search. + :type input_text: string + + :param offset: The position, in the input term, of the last character + that the service uses to match predictions. For example, + if the input is 'Google' and the offset is 3, the + service will match on 'Goo'. + :type offset: int + + :param location: The latitude/longitude value for which you wish to obtain the + closest, human-readable address. + :type location: dict or list or tuple + + :param radius: Distance in meters within which to bias results. + :type radius: int + + :param language: The language in which to return results. + :type langauge: string + + :param types: Restricts the results to places matching at least one of the + specified types. + :type types: string or list of strings + + :param components: A component filter for which you wish to obtain a geocode, + for example: + ``{'administrative_area': 'TX','country': 'US'}`` + :type components: dict + + :rtype: list of predictions + + """ + return _autocomplete(client, "", input_text, offset=offset, + location=location, radius=radius, language=language, + types=types, components=components) + + +def autocomplete_query(client, input_text, offset=None, location=None, + radius=None, language=None): + """ + Returns Place predictions given a textual search query, such as + "pizza near New York", and optional geographic bounds. + + :param input_text: The text query on which to search. + :type input_text: string + + :param offset: The position, in the input term, of the last character + that the service uses to match predictions. For example, + if the input is 'Google' and the offset is 3, the + service will match on 'Goo'. + :type offset: int + + :param location: The latitude/longitude value for which you wish to obtain the + closest, human-readable address. + :type location: dict or list or tuple + + :param radius: Distance in meters within which to bias results. + :type radius: int + + :param language: The language in which to return results. + :type langauge: string + + :rtype: list of predictions + + """ + return _autocomplete(client, "query", input_text, offset=offset, + location=location, radius=radius, language=language) + + +def _autocomplete(client, url_part, input_text, offset=None, location=None, + radius=None, language=None, types=None, components=None): + """ + Internal handler for ``autocomplete`` and ``autocomplete_query``. + See each method's docs for arg details. + """ + + params = {"input": input_text} + + if offset: + params["offset"] = offset + if location: + params["location"] = convert.latlng(location) + if radius: + params["radius"] = radius + if language: + params["language"] = language + if types: + params["types"] = types + if components: + params["components"] = convert.components(components) + + url = "/maps/api/place/%sautocomplete/json" % url_part + return client._get(url, params)["predictions"] diff --git a/test/test_places.py b/test/test_places.py new file mode 100644 index 00000000..4166132b --- /dev/null +++ b/test/test_places.py @@ -0,0 +1,158 @@ +# This Python file uses the following encoding: utf-8 +# +# Copyright 2015 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the places module.""" + +from types import GeneratorType + +import responses + +import test as _test +import googlemaps + +class PlacesTest(_test.TestCase): + + def setUp(self): + self.key = 'AIzaasdf' + self.client = googlemaps.Client(self.key) + self.location = (-33.86746, 151.207090) + self.types = ('liquor_store', 'mosque') + self.language = 'en-AU' + self.radius = 100 + + @responses.activate + def test_places_text_search(self): + url = 'https://maps.googleapis.com/maps/api/place/textsearch/json' + responses.add(responses.GET, url, + body='{"status": "OK", "results": [], "html_attributions": []}', + status=200, content_type='application/json') + + self.client.places('restaurant', location=self.location, + radius=self.radius, language=self.language, + min_price=1, max_price=4, open_now=True, + types=self.types) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?language=en-AU&location=-33.867460%%2C151.207090&' + 'maxprice=1&minprice=1&opennow=true&query=restaurant&' + 'radius=100&types=liquor_store%%7Cmosque&key=%s' + % (url, self.key), responses.calls[0].request.url) + + @responses.activate + def test_places_nearby_search(self): + url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json' + responses.add(responses.GET, url, + body='{"status": "OK", "results": [], "html_attributions": []}', + status=200, content_type='application/json') + + self.client.places_nearby(self.location, keyword='foo', + language=self.language, min_price=1, + max_price=4, name='bar', open_now=True, + rank_by='distance', types=self.types) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?keyword=foo&language=en-AU&location=-33.867460%%2C151.207090&' + 'maxprice=4&minprice=1&name=bar&opennow=true&rankby=distance&' + 'types=liquor_store%%7Cmosque&key=%s' + % (url, self.key), responses.calls[0].request.url) + + def distance_missing_extra_args(): + self.client.places_nearby(self.location, rank_by="distance") + self.assertRaises(ValueError, distance_missing_extra_args) + + def distance_and_radius(): + self.client.places_nearby(self.location, rank_by="distance", + keyword='foo', radius=self.radius) + self.assertRaises(ValueError, distance_and_radius) + + @responses.activate + def test_places_radar_search(self): + url = 'https://maps.googleapis.com/maps/api/place/radarsearch/json' + responses.add(responses.GET, url, + body='{"status": "OK", "results": [], "html_attributions": []}', + status=200, content_type='application/json') + + self.client.places_radar(self.location, self.radius, keyword='foo', + min_price=1, max_price=4, name='bar', + open_now=True, types=self.types) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?keyword=foo&location=-33.867460%%2C151.207090&' + 'maxprice=1&minprice=1&name=bar&opennow=true&radius=100&' + 'types=liquor_store%%7Cmosque&key=%s' + % (url, self.key), responses.calls[0].request.url) + + def missing_extra_args(): + self.client.places_radar(self.location, self.radius) + self.assertRaises(ValueError, missing_extra_args) + + @responses.activate + def test_place_detail(self): + url = 'https://maps.googleapis.com/maps/api/place/details/json' + responses.add(responses.GET, url, + body='{"status": "OK", "result": {}, "html_attributions": []}', + status=200, content_type='application/json') + + self.client.place('ChIJN1t_tDeuEmsRUsoyG83frY4', language=self.language) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4&key=%s' + % (url, self.key), responses.calls[0].request.url) + + @responses.activate + def test_photo(self): + url = 'https://maps.googleapis.com/maps/api/place/photo' + responses.add(responses.GET, url, status=200) + + ref = 'CnRvAAAAwMpdHeWlXl-lH0vp7lez4znKPIWSWvgvZFISdKx45AwJVP1Qp37YOrH7sqHMJ8C-vBDC546decipPHchJhHZL94RcTUfPa1jWzo-rSHaTlbNtjh-N68RkcToUCuY9v2HNpo5mziqkir37WU8FJEqVBIQ4k938TI3e7bf8xq-uwDZcxoUbO_ZJzPxremiQurAYzCTwRhE_V0' + response = self.client.photo(ref, max_width=100) + + self.assertTrue(isinstance(response, GeneratorType)) + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?maxwidth=100&photoreference=%s&key=%s' + % (url, ref, self.key), responses.calls[0].request.url) + + @responses.activate + def test_autocomplete(self): + url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json' + responses.add(responses.GET, url, + body='{"status": "OK", "predictions": []}', + status=200, content_type='application/json') + + self.client.autocomplete('Google', offset=3, location=self.location, + radius=self.radius, language=self.language, + types='geocode', components={'country': 'au'}) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?components=country%%3Aau&input=Google&language=en-AU&' + 'location=-33.867460%%2C151.207090&offset=3&radius=100&' + 'types=geocode&key=%s' % (url, self.key), + responses.calls[0].request.url) + + @responses.activate + def test_autocomplete_query(self): + url = 'https://maps.googleapis.com/maps/api/place/queryautocomplete/json' + responses.add(responses.GET, url, + body='{"status": "OK", "predictions": []}', + status=200, content_type='application/json') + + self.client.autocomplete_query('pizza near New York') + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?input=pizza+near+New+York&key=%s' % + (url, self.key), responses.calls[0].request.url) From c4694adf36a92f37eab84b7450bb6c17a0e7903e Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 8 Oct 2015 17:13:20 +1100 Subject: [PATCH 008/260] Unify lat/lng conversions. Change-Id: I66fb76af4e697357af6ec2e224cf1a7a999a6e39 --- googlemaps/convert.py | 30 ++++++++++++++++++++++++++++-- googlemaps/directions.py | 25 +++++++++---------------- googlemaps/distance_matrix.py | 24 +++++++----------------- googlemaps/elevation.py | 31 +++++++++++++------------------ googlemaps/geocoding.py | 6 ++---- googlemaps/places.py | 10 +++++----- googlemaps/roads.py | 33 ++++++++++----------------------- googlemaps/timezone.py | 10 +++------- test/test_convert.py | 24 ++++++++++++++++++++---- test/test_places.py | 10 ++++------ 10 files changed, 101 insertions(+), 102 deletions(-) diff --git a/googlemaps/convert.py b/googlemaps/convert.py index b720b3e5..83b52ddd 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -44,10 +44,14 @@ def latlng(arg): convert.latlng(sydney) # '-33.8674869,151.2069902' + For convenience, also accepts lat/lon pair as a string, in + which case it's returned unchanged. + :param arg: The lat/lon pair. - :type arg: dict or list or tuple + :type arg: string or dict or list or tuple """ - return "%f,%f" % normalize_lat_lng(arg) + return arg if is_string(arg) else "%f,%f" % normalize_lat_lng(arg) + def normalize_lat_lng(arg): """Take the various lat/lng representations and return a tuple. @@ -76,6 +80,26 @@ def normalize_lat_lng(arg): "but got %s" % type(arg).__name__) +def location_list(arg): + """Joins a list of locations into a pipe separated string, handling + the various formats supported for lat/lng values. + + For example: + p = [{"lat" : -33.867486, "lng" : 151.206990}, "Sydney"] + convert.waypoint(p) + # '-33.867486,151.206990|Sydney' + + :param arg: The lat/lng list. + :type arg: list + :rtype: string + """ + if isinstance(arg, tuple): + # Handle the single-tuple lat/lng case. + return latlng(arg) + else: + return "|".join([latlng(location) for location in as_list(arg)]) + + def join_list(sep, arg): """If arg is list-like, then joins it with sep. :param sep: Separator string. @@ -107,6 +131,7 @@ def _is_list(arg): and _has_method(arg, "__getitem__") or _has_method(arg, "__iter__")) + def is_string(val): """Determines whether the passed value is a string, safe for 2/3.""" try: @@ -115,6 +140,7 @@ def is_string(val): return isinstance(val, str) return isinstance(val, basestring) + def time(arg): """Converts the value into a unix time (seconds since unix epoch). diff --git a/googlemaps/directions.py b/googlemaps/directions.py index 4864e967..58199a1c 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -29,11 +29,11 @@ def directions(client, origin, destination, :param origin: The address or latitude/longitude value from which you wish to calculate directions. - :type origin: string or dict or tuple + :type origin: string, dict, list, or tuple :param destination: The address or latitude/longitude value from which you wish to calculate directions. - :type destination: string or dict or tuple + :type destination: string, dict, list, or tuple :param mode: Specifies the mode of transport to use when calculating directions. One of "driving", "walking", "bicycling" or "transit" @@ -41,6 +41,8 @@ def directions(client, origin, destination, :param waypoints: Specifies an array of waypoints. Waypoints alter a route by routing it through the specified location(s). + :type waypoints: a single location, or a list of locations, where a + location is a string, dict, list, or tuple :param alternatives: If True, more than one route may be returned in the response. @@ -87,8 +89,8 @@ def directions(client, origin, destination, """ params = { - "origin": _convert_waypoint(origin), - "destination": _convert_waypoint(destination) + "origin": convert.latlng(origin), + "destination": convert.latlng(destination) } if mode: @@ -99,13 +101,10 @@ def directions(client, origin, destination, params["mode"] = mode if waypoints: - waypoints = convert.as_list(waypoints) - waypoints = [_convert_waypoint(waypoint) for waypoint in waypoints] - + waypoints = convert.location_list(waypoints) if optimize_waypoints: - waypoints = ["optimize:true"] + waypoints - - params["waypoints"] = convert.join_list("|", waypoints) + waypoints = "optimize:true|" + waypoints + params["waypoints"] = waypoints if alternatives: params["alternatives"] = "true" @@ -139,9 +138,3 @@ def directions(client, origin, destination, params["transit_routing_preference"] = transit_routing_preference return client._get("/maps/api/directions/json", params)["routes"] - -def _convert_waypoint(waypoint): - if not convert.is_string(waypoint): - return convert.latlng(waypoint) - - return waypoint diff --git a/googlemaps/distance_matrix.py b/googlemaps/distance_matrix.py index 6d62ef0b..bb0e8a20 100644 --- a/googlemaps/distance_matrix.py +++ b/googlemaps/distance_matrix.py @@ -26,17 +26,19 @@ def distance_matrix(client, origins, destinations, transit_routing_preference=None): """ Gets travel distance and time for a matrix of origins and destinations. - :param origins: One or more addresses and/or latitude/longitude values, + :param origins: One or more locations and/or latitude/longitude values, from which to calculate distance and time. If you pass an address as a string, the service will geocode the string and convert it to a latitude/longitude coordinate to calculate directions. - :type origins: list of strings, dicts or tuples + :type origins: a single location, or a list of locations, where a + location is a string, dict, list, or tuple :param destinations: One or more addresses and/or lat/lng values, to which to calculate distance and time. If you pass an address as a string, the service will geocode the string and convert it to a latitude/longitude coordinate to calculate directions. - :type destinations: list of strings, dicts or tuples + :type destinations: a single location, or a list of locations, where a + location is a string, dict, list, or tuple :param mode: Specifies the mode of transport to use when calculating directions. Valid values are "driving", "walking", "transit" or @@ -77,8 +79,8 @@ def distance_matrix(client, origins, destinations, """ params = { - "origins": _convert_path(origins), - "destinations": _convert_path(destinations) + "origins": convert.location_list(origins), + "destinations": convert.location_list(destinations) } if mode: @@ -116,15 +118,3 @@ def distance_matrix(client, origins, destinations, params["transit_routing_preference"] = transit_routing_preference return client._get("/maps/api/distancematrix/json", params) - - -def _convert_path(waypoints): - # Handle the single-tuple case - if type(waypoints) is tuple: - waypoints = [waypoints] - else: - waypoints = as_list(waypoints) - - return convert.join_list("|", - [(k if convert.is_string(k) else convert.latlng(k)) - for k in waypoints]) diff --git a/googlemaps/elevation.py b/googlemaps/elevation.py index 70ecf01e..599011af 100644 --- a/googlemaps/elevation.py +++ b/googlemaps/elevation.py @@ -16,41 +16,37 @@ # """Performs requests to the Google Maps Elevation API.""" + from googlemaps import convert + def elevation(client, locations): """ Provides elevation data for locations provided on the surface of the earth, including depth locations on the ocean floor (which return negative values) - :param locations: A single latitude/longitude tuple or dict, or a list of - latitude/longitude tuples or dicts from which you wish to calculate - elevation data. - :type locations: list or tuple + :param locations: Array of latitude/longitude values from which you wish + to calculate elevation data. + :type locations: a single location, or a list of locations, where a + location is a string, dict, list, or tuple :rtype: list of elevation data responses """ - params = {} - if type(locations) is tuple: - locations = [locations] - - params["locations"] = convert.join_list("|", - [convert.latlng(k) for k in convert.as_list(locations)]) - + params = {"locations": convert.location_list(locations)} return client._get("/maps/api/elevation/json", params)["results"] + def elevation_along_path(client, path, samples): """ Provides elevation data sampled along a path on the surface of the earth. - :param path: A encoded polyline string, or a list of - latitude/longitude tuples from which you wish to calculate - elevation data. - :type path: str or list + :param path: A encoded polyline string, or a list of latitude/longitude + values from which you wish to calculate elevation data. + :type path: string, dict, list, or tuple :param samples: The number of sample points along a path for which to - return elevation data. + return elevation data. :type samples: int :rtype: list of elevation data responses @@ -59,8 +55,7 @@ def elevation_along_path(client, path, samples): if type(path) is str: path = "enc:%s" % path else: - path = convert.join_list("|", - [convert.latlng(k) for k in convert.as_list(path)]) + path = convert.location_list(path) params = { "path": path, diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index 833fc13e..c721c8f5 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -78,7 +78,7 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :param latlng: The latitude/longitude value for which you wish to obtain the closest, human-readable address. - :type latlng: dict or list or tuple + :type latlng: string, dict, list, or tuple :param result_type: One or more address types to restrict results to. :type result_type: string or list of strings @@ -93,9 +93,7 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, """ - params = { - "latlng": convert.latlng(latlng) - } + params = {"latlng": convert.latlng(latlng)} if result_type: params["result_type"] = convert.join_list("|", result_type) diff --git a/googlemaps/places.py b/googlemaps/places.py index 7dfd77f4..4a2ff65e 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -31,7 +31,7 @@ def places(client, query, location=None, radius=None, language=None, :param location: The latitude/longitude value for which you wish to obtain the closest, human-readable address. - :type location: dict or list or tuple + :type location: string, dict, list, or tuple :param radius: Distance in meters within which to bias results. :type radius: int @@ -82,7 +82,7 @@ def places_nearby(client, location, radius=None, keyword=None, language=None, :param location: The latitude/longitude value for which you wish to obtain the closest, human-readable address. - :type location: dict or list or tuple + :type location: string, dict, list, or tuple :param radius: Distance in meters within which to bias results. :type radius: int @@ -152,7 +152,7 @@ def places_radar(client, location, radius, keyword=None, min_price=None, :param location: The latitude/longitude value for which you wish to obtain the closest, human-readable address. - :type location: dict or list or tuple + :type location: string, dict, list, or tuple :param radius: Distance in meters within which to bias results. :type radius: int @@ -309,7 +309,7 @@ def autocomplete(client, input_text, offset=None, location=None, radius=None, :param location: The latitude/longitude value for which you wish to obtain the closest, human-readable address. - :type location: dict or list or tuple + :type location: string, dict, list, or tuple :param radius: Distance in meters within which to bias results. :type radius: int @@ -351,7 +351,7 @@ def autocomplete_query(client, input_text, offset=None, location=None, :param location: The latitude/longitude value for which you wish to obtain the closest, human-readable address. - :type location: dict or list or tuple + :type location: string, dict, list, or tuple :param radius: Distance in meters within which to bias results. :type radius: int diff --git a/googlemaps/roads.py b/googlemaps/roads.py index 0e659a9f..c47f17b2 100644 --- a/googlemaps/roads.py +++ b/googlemaps/roads.py @@ -20,8 +20,10 @@ import googlemaps from googlemaps import convert + _ROADS_BASE_URL = "https://roads.googleapis.com" + def snap_to_roads(client, path, interpolate=False): """Snaps a path to the most likely roads travelled. @@ -29,8 +31,9 @@ def snap_to_roads(client, path, interpolate=False): set of data with the points snapped to the most likely roads the vehicle was traveling along. - :param path: The path to be snapped. A list of latitude/longitude tuples. - :type path: list + :param path: The path to be snapped. + :type path: a single location, or a list of locations, where a + location is a string, dict, list, or tuple :param interpolate: Whether to interpolate a path to include all points forming the full road-geometry. When true, additional interpolated @@ -43,15 +46,7 @@ def snap_to_roads(client, path, interpolate=False): :rtype: A list of snapped points. """ - if type(path) is tuple: - path = [path] - - path = convert.join_list("|", - [convert.latlng(k) for k in convert.as_list(path)]) - - params = { - "path": path - } + params = {"path": convert.location_list(path)} if interpolate: params["interpolate"] = "true" @@ -86,23 +81,15 @@ def snapped_speed_limits(client, path): The provided points will first be snapped to the most likely roads the vehicle was traveling along. - :param path: The path of points to be snapped. A list of (or single) - latitude/longitude tuples. - :type path: list or tuple + :param path: The path of points to be snapped. + :type path: a single location, or a list of locations, where a + location is a string, dict, list, or tuple :rtype: a dict with both a list of speed limits and a list of the snapped points. """ - if type(path) is tuple: - path = [path] - - path = convert.join_list("|", - [convert.latlng(k) for k in convert.as_list(path)]) - - params = { - "path": path - } + params = {"path": convert.location_list(path)} return client._get("/v1/speedLimits", params, base_url=_ROADS_BASE_URL, diff --git a/googlemaps/timezone.py b/googlemaps/timezone.py index 84372acf..339c5e6b 100644 --- a/googlemaps/timezone.py +++ b/googlemaps/timezone.py @@ -28,7 +28,7 @@ def timezone(client, location, timestamp=None, language=None): :param location: The latitude/longitude value representing the location to look up. - :type location: dict or tuple + :type location: string, dict, list, or tuple :param timestamp: Timestamp specifies the desired time as seconds since midnight, January 1, 1970 UTC. The Time Zone API uses the timestamp to @@ -43,13 +43,9 @@ def timezone(client, location, timestamp=None, language=None): :rtype: dict """ - location = convert.latlng(location) - - timestamp = convert.time(timestamp or datetime.utcnow()) - params = { - "location": location, - "timestamp": timestamp + "location": convert.latlng(location), + "timestamp": convert.time(timestamp or datetime.utcnow()) } if language: diff --git a/test/test_convert.py b/test/test_convert.py index d1941915..90d9c0f3 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -26,20 +26,36 @@ class ConvertTest(unittest.TestCase): def test_latlng(self): + expected = "1.000000,2.000000" ll = {"lat": 1, "lng": 2} - self.assertEqual("1.000000,2.000000", convert.latlng(ll)) + self.assertEqual(expected, convert.latlng(ll)) ll = [1, 2] - self.assertEqual("1.000000,2.000000", convert.latlng(ll)) + self.assertEqual(expected, convert.latlng(ll)) ll = (1, 2) - self.assertEqual("1.000000,2.000000", convert.latlng(ll)) + self.assertEqual(expected, convert.latlng(ll)) + + self.assertEqual(expected, convert.latlng(expected)) with self.assertRaises(TypeError): convert.latlng(1) + def test_location_list(self): + expected = "1.000000,2.000000|1.000000,2.000000" + ll = [{"lat": 1, "lng": 2}, {"lat": 1, "lng": 2}] + self.assertEqual(expected, convert.location_list(ll)) + + ll = [[1, 2], [1, 2]] + self.assertEqual(expected, convert.location_list(ll)) + + ll = [(1, 2), [1, 2]] + self.assertEqual(expected, convert.location_list(ll)) + + self.assertEqual(expected, convert.location_list(expected)) + with self.assertRaises(TypeError): - convert.latlng("test") + convert.latlng(1) def test_join_list(self): self.assertEqual("asdf", convert.join_list("|", "asdf")) diff --git a/test/test_places.py b/test/test_places.py index 4166132b..3b000ec4 100644 --- a/test/test_places.py +++ b/test/test_places.py @@ -25,6 +25,7 @@ import test as _test import googlemaps + class PlacesTest(_test.TestCase): def setUp(self): @@ -71,14 +72,12 @@ def test_places_nearby_search(self): 'types=liquor_store%%7Cmosque&key=%s' % (url, self.key), responses.calls[0].request.url) - def distance_missing_extra_args(): + with self.assertRaises(ValueError): self.client.places_nearby(self.location, rank_by="distance") - self.assertRaises(ValueError, distance_missing_extra_args) - def distance_and_radius(): + with self.assertRaises(ValueError): self.client.places_nearby(self.location, rank_by="distance", keyword='foo', radius=self.radius) - self.assertRaises(ValueError, distance_and_radius) @responses.activate def test_places_radar_search(self): @@ -97,9 +96,8 @@ def test_places_radar_search(self): 'types=liquor_store%%7Cmosque&key=%s' % (url, self.key), responses.calls[0].request.url) - def missing_extra_args(): + with self.assertRaises(ValueError): self.client.places_radar(self.location, self.radius) - self.assertRaises(ValueError, missing_extra_args) @responses.activate def test_place_detail(self): From 74a07b2d1c3da70159fc25f81ca71f4b22060395 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 8 Oct 2015 17:28:54 +1100 Subject: [PATCH 009/260] Remove some extraneous features. Change-Id: I44e40ca662b84f2a77386c83ead4a0da8024faaf --- googlemaps/client.py | 6 - googlemaps/distance_matrix.py | 1 + googlemaps/places.py | 215 ++-------------------------------- test/test_places.py | 71 +---------- 4 files changed, 14 insertions(+), 279 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 937ce8da..88acc1cb 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -288,12 +288,9 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.roads import speed_limits from googlemaps.roads import snapped_speed_limits from googlemaps.places import places -from googlemaps.places import places_nearby -from googlemaps.places import places_radar from googlemaps.places import place from googlemaps.places import photo from googlemaps.places import autocomplete -from googlemaps.places import autocomplete_query Client.directions = directions Client.distance_matrix = distance_matrix @@ -306,12 +303,9 @@ def _generate_auth_url(self, path, params, accepts_clientid): Client.speed_limits = speed_limits Client.snapped_speed_limits = snapped_speed_limits Client.places = places -Client.places_nearby = places_nearby -Client.places_radar = places_radar Client.place = place Client.photo = photo Client.autocomplete = autocomplete -Client.autocomplete_query = autocomplete_query def sign_hmac(secret, payload): diff --git a/googlemaps/distance_matrix.py b/googlemaps/distance_matrix.py index bb0e8a20..9bbdb09a 100644 --- a/googlemaps/distance_matrix.py +++ b/googlemaps/distance_matrix.py @@ -20,6 +20,7 @@ from googlemaps import convert from googlemaps.convert import as_list + def distance_matrix(client, origins, destinations, mode=None, language=None, avoid=None, units=None, departure_time=None, arrival_time=None, transit_mode=None, diff --git a/googlemaps/places.py b/googlemaps/places.py index 4a2ff65e..199b4976 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -68,143 +68,7 @@ def places(client, query, location=None, radius=None, language=None, next_page_token: token for retrieving the next page of results """ - return _places(client, "text", query=query, location=location, - radius=radius, language=language, min_price=min_price, - max_price=min_price, open_now=open_now, types=types, - page_token=page_token) - - -def places_nearby(client, location, radius=None, keyword=None, language=None, - min_price=None, max_price=None, name=None, open_now=False, - rank_by=None, types=None, page_token=None): - """ - Performs nearby search for places. - - :param location: The latitude/longitude value for which you wish to obtain the - closest, human-readable address. - :type location: string, dict, list, or tuple - - :param radius: Distance in meters within which to bias results. - :type radius: int - - :param keyword: A term to be matched against all content that Google has - indexed for this place. - :type keyword: string - - :param language: The language in which to return results. - :type langauge: string - - :param min_price: Restricts results to only those places with no less than - this price level. Valid values are in the range from 0 - (most affordable) to 4 (most expensive). - :type min_price: int - - :param max_price: Restricts results to only those places with no greater - than this price level. Valid values are in the range - from 0 (most affordable) to 4 (most expensive). - :type max_price: int - - :param name: One or more terms to be matched against the names of places. - :type name: string or list of strings - - :param open_now: Return only those places that are open for business at - the time the query is sent. - :type open_now: bool - - :param rank_by: Specifies the order in which results are listed. - Possible values are: prominence (default), distance - :type rank_by: string - - :param types: Restricts the results to places matching at least one of the - specified types. - :type types: string or list of strings - - :param page_token: Token from a previous search that when provided will - returns the next page of results for the same search. - :type page_token: string - - :rtype: result dict with the following keys: - status: status code - results: list of places - html_attributions: set of attributions which must be displayed - next_page_token: token for retrieving the next page of results - - """ - if rank_by == "distance": - if not (keyword or name or types): - raise ValueError("either a keyword, name, or types arg is required " - "when rank_by is set to distance") - elif radius is not None: - raise ValueError("radius cannot be specified when rank_by is set to " - "distance") - - return _places(client, "nearby", location=location, radius=radius, - keyword=keyword, language=language, min_price=min_price, - max_price=max_price, name=name, open_now=open_now, - rank_by=rank_by, types=types, page_token=page_token) - - -def places_radar(client, location, radius, keyword=None, min_price=None, - max_price=None, name=None, open_now=False, types=None): - - """ - Performs radar search for places. - - :param location: The latitude/longitude value for which you wish to obtain the - closest, human-readable address. - :type location: string, dict, list, or tuple - - :param radius: Distance in meters within which to bias results. - :type radius: int - - :param keyword: A term to be matched against all content that Google has - indexed for this place. - :type keyword: string - - :param min_price: Restricts results to only those places with no less than - this price level. Valid values are in the range from 0 - (most affordable) to 4 (most expensive). - :type min_price: int - - :param max_price: Restricts results to only those places with no greater - than this price level. Valid values are in the range - from 0 (most affordable) to 4 (most expensive). - :type max_price: int - - :param name: One or more terms to be matched against the names of places. - :type name: string or list of strings - - :param open_now: Return only those places that are open for business at - the time the query is sent. - :type open_now: bool - - :param types: Restricts the results to places matching at least one of the - specified types. - :type types: string or list of strings - - :rtype: result dict with the following keys: - status: status code - results: list of places - html_attributions: set of attributions which must be displayed - - """ - if not (keyword or name or types): - raise ValueError("either a keyword, name, or types arg is required") - - return _places(client, "radar", location=location, radius=radius, - keyword=keyword, min_price=min_price, max_price=min_price, - name=name, open_now=open_now, types=types) - - -def _places(client, url_part, query=None, location=None, radius=None, - keyword=None, language=None, min_price=0, max_price=4, name=None, - open_now=False, rank_by=None, types=None, page_token=None): - """ - Internal handler for ``places``, ``places_nearby``, and ``places_radar``. - See each method's docs for arg details. - """ - - params = {"minprice": min_price, "maxprice": max_price} + params = {} if query: params["query"] = query @@ -212,23 +76,18 @@ def _places(client, url_part, query=None, location=None, radius=None, params["location"] = convert.latlng(location) if radius: params["radius"] = radius - if keyword: - params["keyword"] = keyword if language: params["language"] = language - if name: - params["name"] = convert.join_list(" ", name) + if min_price: + params["minprice"] = min_price + if max_price: + params["maxprice"] = max_price if open_now: params["opennow"] = "true" - if rank_by: - params["rankby"] = rank_by - if types: - params["types"] = convert.join_list("|", types) if page_token: params["pagetoken"] = page_token - url = "/maps/api/place/%ssearch/json" % url_part - return client._get(url, params) + return client._get("/maps/api/place/textsearch/json", params) def place(client, place_id, language=None): @@ -292,49 +151,7 @@ def photo(client, photo_reference, max_width=None, max_height=None): return response.iter_content() -def autocomplete(client, input_text, offset=None, location=None, radius=None, - language=None, types=None, components=None): - """ - Returns Place predictions given a textual search string and optional - geographic bounds. - - :param input_text: The text string on which to search. - :type input_text: string - - :param offset: The position, in the input term, of the last character - that the service uses to match predictions. For example, - if the input is 'Google' and the offset is 3, the - service will match on 'Goo'. - :type offset: int - - :param location: The latitude/longitude value for which you wish to obtain the - closest, human-readable address. - :type location: string, dict, list, or tuple - - :param radius: Distance in meters within which to bias results. - :type radius: int - - :param language: The language in which to return results. - :type langauge: string - - :param types: Restricts the results to places matching at least one of the - specified types. - :type types: string or list of strings - - :param components: A component filter for which you wish to obtain a geocode, - for example: - ``{'administrative_area': 'TX','country': 'US'}`` - :type components: dict - - :rtype: list of predictions - - """ - return _autocomplete(client, "", input_text, offset=offset, - location=location, radius=radius, language=language, - types=types, components=components) - - -def autocomplete_query(client, input_text, offset=None, location=None, +def autocomplete(client, input_text, offset=None, location=None, radius=None, language=None): """ Returns Place predictions given a textual search query, such as @@ -361,16 +178,6 @@ def autocomplete_query(client, input_text, offset=None, location=None, :rtype: list of predictions - """ - return _autocomplete(client, "query", input_text, offset=offset, - location=location, radius=radius, language=language) - - -def _autocomplete(client, url_part, input_text, offset=None, location=None, - radius=None, language=None, types=None, components=None): - """ - Internal handler for ``autocomplete`` and ``autocomplete_query``. - See each method's docs for arg details. """ params = {"input": input_text} @@ -383,10 +190,6 @@ def _autocomplete(client, url_part, input_text, offset=None, location=None, params["radius"] = radius if language: params["language"] = language - if types: - params["types"] = types - if components: - params["components"] = convert.components(components) - url = "/maps/api/place/%sautocomplete/json" % url_part - return client._get(url, params)["predictions"] + response = client._get("/maps/api/place/queryautocomplete/json", params) + return response["predictions"] diff --git a/test/test_places.py b/test/test_places.py index 3b000ec4..83910b6d 100644 --- a/test/test_places.py +++ b/test/test_places.py @@ -45,60 +45,14 @@ def test_places_text_search(self): self.client.places('restaurant', location=self.location, radius=self.radius, language=self.language, - min_price=1, max_price=4, open_now=True, - types=self.types) + min_price=1, max_price=4, open_now=True) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?language=en-AU&location=-33.867460%%2C151.207090&' - 'maxprice=1&minprice=1&opennow=true&query=restaurant&' - 'radius=100&types=liquor_store%%7Cmosque&key=%s' + 'maxprice=4&minprice=1&opennow=true&query=restaurant&' + 'radius=100&key=%s' % (url, self.key), responses.calls[0].request.url) - @responses.activate - def test_places_nearby_search(self): - url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json' - responses.add(responses.GET, url, - body='{"status": "OK", "results": [], "html_attributions": []}', - status=200, content_type='application/json') - - self.client.places_nearby(self.location, keyword='foo', - language=self.language, min_price=1, - max_price=4, name='bar', open_now=True, - rank_by='distance', types=self.types) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?keyword=foo&language=en-AU&location=-33.867460%%2C151.207090&' - 'maxprice=4&minprice=1&name=bar&opennow=true&rankby=distance&' - 'types=liquor_store%%7Cmosque&key=%s' - % (url, self.key), responses.calls[0].request.url) - - with self.assertRaises(ValueError): - self.client.places_nearby(self.location, rank_by="distance") - - with self.assertRaises(ValueError): - self.client.places_nearby(self.location, rank_by="distance", - keyword='foo', radius=self.radius) - - @responses.activate - def test_places_radar_search(self): - url = 'https://maps.googleapis.com/maps/api/place/radarsearch/json' - responses.add(responses.GET, url, - body='{"status": "OK", "results": [], "html_attributions": []}', - status=200, content_type='application/json') - - self.client.places_radar(self.location, self.radius, keyword='foo', - min_price=1, max_price=4, name='bar', - open_now=True, types=self.types) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?keyword=foo&location=-33.867460%%2C151.207090&' - 'maxprice=1&minprice=1&name=bar&opennow=true&radius=100&' - 'types=liquor_store%%7Cmosque&key=%s' - % (url, self.key), responses.calls[0].request.url) - - with self.assertRaises(ValueError): - self.client.places_radar(self.location, self.radius) - @responses.activate def test_place_detail(self): url = 'https://maps.googleapis.com/maps/api/place/details/json' @@ -127,29 +81,12 @@ def test_photo(self): @responses.activate def test_autocomplete(self): - url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json' - responses.add(responses.GET, url, - body='{"status": "OK", "predictions": []}', - status=200, content_type='application/json') - - self.client.autocomplete('Google', offset=3, location=self.location, - radius=self.radius, language=self.language, - types='geocode', components={'country': 'au'}) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?components=country%%3Aau&input=Google&language=en-AU&' - 'location=-33.867460%%2C151.207090&offset=3&radius=100&' - 'types=geocode&key=%s' % (url, self.key), - responses.calls[0].request.url) - - @responses.activate - def test_autocomplete_query(self): url = 'https://maps.googleapis.com/maps/api/place/queryautocomplete/json' responses.add(responses.GET, url, body='{"status": "OK", "predictions": []}', status=200, content_type='application/json') - self.client.autocomplete_query('pizza near New York') + self.client.autocomplete('pizza near New York') self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?input=pizza+near+New+York&key=%s' % From 3d152eaca4c0f9e51e5bd39140f871b7cc3b3454 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 8 Oct 2015 17:33:10 +1100 Subject: [PATCH 010/260] Add an example showing how to save a photo from the places API. Change-Id: I561fcfaa4b67d995340b306daaaab8d1af3f6c29 --- googlemaps/places.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 199b4976..24f41445 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -115,11 +115,10 @@ def place(client, place_id, language=None): def photo(client, photo_reference, max_width=None, max_height=None): """ - Downloads a places photo. + Downloads a photo from the Places API. :param photo_reference: A string identifier that uniquely identifies a - photo, as provided by either a Places search or - Places detail request. + photo, as provided by either a Places search or Places detail request. :type photo_reference: string :param max_width: Specifies the maximum desired width, in pixels. @@ -128,7 +127,16 @@ def photo(client, photo_reference, max_width=None, max_height=None): :param max_height: Specifies the maximum desired height, in pixels. :type max_height: int - :rtype: iterator containing the raw image data. + :rtype: iterator containing the raw image data, which typically can be + used to save an image file locally, eg: + + ``` + f = open(local_filename, 'wb') + for chunk in client.photo(photo_reference, max_width=100): + if chunk: + f.write(chunk) + f.close() + ``` """ From fcac33deadf6ad99e1440dad49a86ea9d01c969f Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 8 Oct 2015 17:45:43 +1100 Subject: [PATCH 011/260] Fix handling of mandatory query arg in places search. Change-Id: Ifd4f4cd7335cb44f964eff188e1fe0344a4e1a9e --- googlemaps/places.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 24f41445..b72e683c 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -68,10 +68,8 @@ def places(client, query, location=None, radius=None, language=None, next_page_token: token for retrieving the next page of results """ - params = {} + params = {"query": query} - if query: - params["query"] = query if location: params["location"] = convert.latlng(location) if radius: From ea670bcf5a0703a05ecc5167300f9303d2c81253 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Oct 2015 09:41:31 +1100 Subject: [PATCH 012/260] Give some of the ambiguous places method names more meaningful names. Change-Id: I631d79346a5c420d3f9e5f72416d48e8fc593104 --- googlemaps/client.py | 8 ++++---- googlemaps/places.py | 6 +++--- test/test_places.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 88acc1cb..a036661e 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -289,8 +289,8 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.roads import snapped_speed_limits from googlemaps.places import places from googlemaps.places import place -from googlemaps.places import photo -from googlemaps.places import autocomplete +from googlemaps.places import places_photo +from googlemaps.places import places_autocomplete Client.directions = directions Client.distance_matrix = distance_matrix @@ -304,8 +304,8 @@ def _generate_auth_url(self, path, params, accepts_clientid): Client.snapped_speed_limits = snapped_speed_limits Client.places = places Client.place = place -Client.photo = photo -Client.autocomplete = autocomplete +Client.places_photo = places_photo +Client.places_autocomplete = places_autocomplete def sign_hmac(secret, payload): diff --git a/googlemaps/places.py b/googlemaps/places.py index b72e683c..5cdb01cf 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -111,7 +111,7 @@ def place(client, place_id, language=None): return client._get("/maps/api/place/details/json", params) -def photo(client, photo_reference, max_width=None, max_height=None): +def places_photo(client, photo_reference, max_width=None, max_height=None): """ Downloads a photo from the Places API. @@ -157,8 +157,8 @@ def photo(client, photo_reference, max_width=None, max_height=None): return response.iter_content() -def autocomplete(client, input_text, offset=None, location=None, - radius=None, language=None): +def places_autocomplete(client, input_text, offset=None, location=None, + radius=None, language=None): """ Returns Place predictions given a textual search query, such as "pizza near New York", and optional geographic bounds. diff --git a/test/test_places.py b/test/test_places.py index 83910b6d..874dfdcf 100644 --- a/test/test_places.py +++ b/test/test_places.py @@ -72,7 +72,7 @@ def test_photo(self): responses.add(responses.GET, url, status=200) ref = 'CnRvAAAAwMpdHeWlXl-lH0vp7lez4znKPIWSWvgvZFISdKx45AwJVP1Qp37YOrH7sqHMJ8C-vBDC546decipPHchJhHZL94RcTUfPa1jWzo-rSHaTlbNtjh-N68RkcToUCuY9v2HNpo5mziqkir37WU8FJEqVBIQ4k938TI3e7bf8xq-uwDZcxoUbO_ZJzPxremiQurAYzCTwRhE_V0' - response = self.client.photo(ref, max_width=100) + response = self.client.places_photo(ref, max_width=100) self.assertTrue(isinstance(response, GeneratorType)) self.assertEqual(1, len(responses.calls)) @@ -86,7 +86,7 @@ def test_autocomplete(self): body='{"status": "OK", "predictions": []}', status=200, content_type='application/json') - self.client.autocomplete('pizza near New York') + self.client.places_autocomplete('pizza near New York') self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?input=pizza+near+New+York&key=%s' % From c2de2426e842475a6fb8e9a37b6f38be3d477e87 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Oct 2015 09:46:23 +1100 Subject: [PATCH 013/260] Don't document status keys in responses. Change-Id: I9749eef95c5b7ed00e8d76122cf84921e2a6d995 --- googlemaps/places.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 5cdb01cf..ecbfe935 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -62,7 +62,6 @@ def places(client, query, location=None, radius=None, language=None, :type page_token: string :rtype: result dict with the following keys: - status: status code results: list of places html_attributions: set of attributions which must be displayed next_page_token: token for retrieving the next page of results @@ -100,7 +99,6 @@ def place(client, place_id, language=None): :type langauge: string :rtype: result dict with the following keys: - status: status code result: dict containing place details html_attributions: set of attributions which must be displayed From 195019fa51df0d4275b8eec06cf049e3457783b5 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Oct 2015 09:48:03 +1100 Subject: [PATCH 014/260] Radius isn't strictly int Change-Id: I8dff39dc5456a89f23cdc178588d0326bf0c9b36 --- googlemaps/places.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index ecbfe935..55ca6364 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -175,7 +175,7 @@ def places_autocomplete(client, input_text, offset=None, location=None, :type location: string, dict, list, or tuple :param radius: Distance in meters within which to bias results. - :type radius: int + :type radius: number :param language: The language in which to return results. :type langauge: string From cf33210f9fa7c6a5d57b3f0de77c619dab47e65d Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Oct 2015 09:54:24 +1100 Subject: [PATCH 015/260] Array -> List Change-Id: I2b7818549c2449ae31319f15b9e13c1b536f6f10 --- googlemaps/elevation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/elevation.py b/googlemaps/elevation.py index 599011af..10995a87 100644 --- a/googlemaps/elevation.py +++ b/googlemaps/elevation.py @@ -26,7 +26,7 @@ def elevation(client, locations): earth, including depth locations on the ocean floor (which return negative values) - :param locations: Array of latitude/longitude values from which you wish + :param locations: List of latitude/longitude values from which you wish to calculate elevation data. :type locations: a single location, or a list of locations, where a location is a string, dict, list, or tuple From 1b09b79d4e642aea1cc6f075b17b662d8a9da699 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Oct 2015 09:54:58 +1100 Subject: [PATCH 016/260] A -> An Change-Id: Iddec7060a69f759155c35bed9a8891451b3f6ba2 --- googlemaps/elevation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/elevation.py b/googlemaps/elevation.py index 10995a87..b6a111e7 100644 --- a/googlemaps/elevation.py +++ b/googlemaps/elevation.py @@ -41,7 +41,7 @@ def elevation_along_path(client, path, samples): """ Provides elevation data sampled along a path on the surface of the earth. - :param path: A encoded polyline string, or a list of latitude/longitude + :param path: An encoded polyline string, or a list of latitude/longitude values from which you wish to calculate elevation data. :type path: string, dict, list, or tuple From a58c0ed18fd6d1105aaafd1e73bd46133297b085 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Oct 2015 09:55:50 +1100 Subject: [PATCH 017/260] Google Maps Places -> Google Places Change-Id: I243939826f471fdf4d6275348852c1ca98d3a51f --- googlemaps/places.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 55ca6364..e025dbd7 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -15,7 +15,7 @@ # the License. # -"""Performs requests to the Google Maps Places API.""" +"""Performs requests to the Google Places API.""" from googlemaps import convert From d514dd025b4ddce6b691169c610f76a14e942bfc Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Oct 2015 10:14:22 +1100 Subject: [PATCH 018/260] Consistent formatting in all docstrings. Change-Id: Id471901337f0a19de9523141db0481b78dd30d47 --- googlemaps/client.py | 28 ++++++++++------ googlemaps/convert.py | 8 +++++ googlemaps/directions.py | 2 +- googlemaps/distance_matrix.py | 22 ++++++------- googlemaps/geocoding.py | 13 +++----- googlemaps/places.py | 61 ++++++++++++++++------------------- googlemaps/roads.py | 14 ++++---- 7 files changed, 78 insertions(+), 70 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index a036661e..2ac9d0a8 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -144,35 +144,35 @@ def _get(self, url, params, first_request_time=None, retry_counter=0, :param params: HTTP GET parameters. :type params: dict or list of key/value tuples - :param first_request_time: The time of the first request (None if no retries - have occurred). + :param first_request_time: The time of the first request (None if no + retries have occurred). :type first_request_time: datetime.datetime :param retry_counter: The number of this retry, or zero for first attempt. :type retry_counter: int :param base_url: The base URL for the request. Defaults to the Maps API - server. Should not have a trailing slash. + server. Should not have a trailing slash. :type base_url: string :param accepts_clientid: Whether this call supports the client/signature - params. Some APIs require API keys (e.g. Roads). + params. Some APIs require API keys (e.g. Roads). :type accepts_clientid: bool :param extract_body: A function that extracts the body from the request. - If the request was not successful, the function should raise a - googlemaps.HTTPError or googlemaps.ApiError as appropriate. + If the request was not successful, the function should raise a + googlemaps.HTTPError or googlemaps.ApiError as appropriate. :type extract_body: function :param requests_kwargs: Same extra keywords arg for requests as per - __init__, but provided here to allow overriding internally - on a per-request basis. + __init__, but provided here to allow overriding internally on a + per-request basis. :type requests_kwargs: dict :raises ApiError: when the API returns an error. :raises Timeout: if the request timed out. :raises TransportError: when something went wrong while trying to - exceute a request. + exceute a request. """ if not first_request_time: @@ -249,11 +249,15 @@ def _get_body(self, resp): def _generate_auth_url(self, path, params, accepts_clientid): """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 + """ # Deterministic ordering through sorting by key. # Useful for tests, and in the future, any caching. @@ -310,10 +314,13 @@ def _generate_auth_url(self, path, params, accepts_clientid): def sign_hmac(secret, payload): """Returns a base64-encoded HMAC-SHA1 signature of a given string. + :param secret: The key used for the signature, base64 encoded. :type secret: string + :param payload: The payload to sign. :type payload: string + :rtype: string """ payload = payload.encode('ascii', 'strict') @@ -325,8 +332,11 @@ def sign_hmac(secret, payload): def urlencode_params(params): """URL encodes the parameters. + :param params: The parameters :type params: list of key/value tuples. + + :rtype: string """ # urlencode does not handle unicode strings in Python 2. # Firstly, normalize the values so they get encoded correctly. diff --git a/googlemaps/convert.py b/googlemaps/convert.py index 83b52ddd..df7b32d8 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -91,6 +91,7 @@ def location_list(arg): :param arg: The lat/lng list. :type arg: list + :rtype: string """ if isinstance(arg, tuple): @@ -102,10 +103,13 @@ def location_list(arg): def join_list(sep, arg): """If arg is list-like, then joins it with sep. + :param sep: Separator string. :type sep: string + :param arg: Value to coerce into a list. :type arg: string or list of strings + :rtype: string """ return sep.join(as_list(arg)) @@ -114,6 +118,7 @@ def join_list(sep, arg): def as_list(arg): """Coerces arg into a list. If arg is already list-like, returns arg. Otherwise, returns a one-element list containing arg. + :rtype: list """ if _is_list(arg): @@ -165,8 +170,10 @@ def _has_method(arg, method): """Returns true if the given object has a method with the given name. :param arg: the object + :param method: the method name :type method: string + :rtype: bool """ return hasattr(arg, method) and callable(getattr(arg, method)) @@ -183,6 +190,7 @@ def components(arg): :param arg: The component filter. :type arg: dict + :rtype: basestring """ if isinstance(arg, dict): diff --git a/googlemaps/directions.py b/googlemaps/directions.py index 58199a1c..b808060c 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -28,7 +28,7 @@ def directions(client, origin, destination, """Get directions between an origin point and a destination point. :param origin: The address or latitude/longitude value from which you wish - to calculate directions. + to calculate directions. :type origin: string, dict, list, or tuple :param destination: The address or latitude/longitude value from which diff --git a/googlemaps/distance_matrix.py b/googlemaps/distance_matrix.py index 9bbdb09a..20076c52 100644 --- a/googlemaps/distance_matrix.py +++ b/googlemaps/distance_matrix.py @@ -28,33 +28,33 @@ def distance_matrix(client, origins, destinations, """ Gets travel distance and time for a matrix of origins and destinations. :param origins: One or more locations and/or latitude/longitude values, - from which to calculate distance and time. If you pass an address - as a string, the service will geocode the string and convert it to - a latitude/longitude coordinate to calculate directions. + from which to calculate distance and time. If you pass an address as + a string, the service will geocode the string and convert it to a + latitude/longitude coordinate to calculate directions. :type origins: a single location, or a list of locations, where a location is a string, dict, list, or tuple :param destinations: One or more addresses and/or lat/lng values, to - which to calculate distance and time. If you pass an address as a - string, the service will geocode the string and convert it to a - latitude/longitude coordinate to calculate directions. + which to calculate distance and time. If you pass an address as a + string, the service will geocode the string and convert it to a + latitude/longitude coordinate to calculate directions. :type destinations: a single location, or a list of locations, where a location is a string, dict, list, or tuple :param mode: Specifies the mode of transport to use when calculating - directions. Valid values are "driving", "walking", "transit" or - "bicycling". + directions. Valid values are "driving", "walking", "transit" or + "bicycling". :type mode: string :param language: The language in which to return results. :type language: string :param avoid: Indicates that the calculated route(s) should avoid the - indicated features. Valid values are "tolls", "highways" or "ferries" + indicated features. Valid values are "tolls", "highways" or "ferries". :type avoid: string :param units: Specifies the unit system to use when displaying results. - Valid values are "metric" or "imperial" + Valid values are "metric" or "imperial". :type units: string :param departure_time: Specifies the desired time of departure. @@ -72,7 +72,7 @@ def distance_matrix(client, origins, destinations, :type transit_mode: string or list of strings :param transit_routing_preference: Specifies preferences for transit - requests. Valid values are "less_walking" or "fewer_transfers" + requests. Valid values are "less_walking" or "fewer_transfers". :type transit_routing_preference: string :rtype: matrix of distances. Results are returned in rows, each row diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index c721c8f5..7152d69e 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -30,24 +30,22 @@ def geocode(client, address=None, components=None, bounds=None, region=None, :param address: The address to geocode. :type address: string - :param components: A component filter for which you wish to obtain a geocode, - for example: - ``{'administrative_area': 'TX','country': 'US'}`` + :param components: A component filter for which you wish to obtain a + geocode, for example: ``{'administrative_area': 'TX','country': 'US'}`` :type components: dict :param bounds: The bounding box of the viewport within which to bias geocode - results more prominently. + results more prominently. :type bounds: string or dict with northeast and southwest keys. :param region: The region code, specified as a ccTLD ("top-level domain") - two-character value. + two-character value. :type region: string :param language: The language in which to return results. :type langauge: string :rtype: list of geocoding results. - """ params = {} @@ -77,7 +75,7 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, human-readable address. :param latlng: The latitude/longitude value for which you wish to obtain the - closest, human-readable address. + closest, human-readable address. :type latlng: string, dict, list, or tuple :param result_type: One or more address types to restrict results to. @@ -90,7 +88,6 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :type langauge: string :rtype: list of reverse geocoding results. - """ params = {"latlng": convert.latlng(latlng)} diff --git a/googlemaps/places.py b/googlemaps/places.py index e025dbd7..265dd7ed 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -24,13 +24,13 @@ def places(client, query, location=None, radius=None, language=None, min_price=None, max_price=None, open_now=False, types=None, page_token=None): """ - Performs text search for places. + Places search. :param query: The text string on which to search, for example: "restaurant". :type query: string :param location: The latitude/longitude value for which you wish to obtain the - closest, human-readable address. + closest, human-readable address. :type location: string, dict, list, or tuple :param radius: Distance in meters within which to bias results. @@ -40,32 +40,31 @@ def places(client, query, location=None, radius=None, language=None, :type langauge: string :param min_price: Restricts results to only those places with no less than - this price level. Valid values are in the range from 0 - (most affordable) to 4 (most expensive). + this price level. Valid values are in the range from 0 (most affordable) + to 4 (most expensive). :type min_price: int :param max_price: Restricts results to only those places with no greater - than this price level. Valid values are in the range - from 0 (most affordable) to 4 (most expensive). + than this price level. Valid values are in the range from 0 (most + affordable) to 4 (most expensive). :type max_price: int :param open_now: Return only those places that are open for business at - the time the query is sent. + the time the query is sent. :type open_now: bool :param types: Restricts the results to places matching at least one of the - specified types. + specified types. :type types: string or list of strings :param page_token: Token from a previous search that when provided will - returns the next page of results for the same search. + returns the next page of results for the same search. :type page_token: string :rtype: result dict with the following keys: - results: list of places - html_attributions: set of attributions which must be displayed - next_page_token: token for retrieving the next page of results - + results: list of places + html_attributions: set of attributions which must be displayed + next_page_token: token for retrieving the next page of results """ params = {"query": query} @@ -92,16 +91,15 @@ def place(client, place_id, language=None): Comprehensive details for an individual place. :param place_id: A textual identifier that uniquely identifies a place, - returned from a Places search. + returned from a Places search. :type place_id: string :param language: The language in which to return results. :type langauge: string :rtype: result dict with the following keys: - result: dict containing place details - html_attributions: set of attributions which must be displayed - + result: dict containing place details + html_attributions: set of attributions which must be displayed """ params = {"placeid": place_id} if language: @@ -114,7 +112,7 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): Downloads a photo from the Places API. :param photo_reference: A string identifier that uniquely identifies a - photo, as provided by either a Places search or Places detail request. + photo, as provided by either a Places search or Places detail request. :type photo_reference: string :param max_width: Specifies the maximum desired width, in pixels. @@ -124,16 +122,15 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): :type max_height: int :rtype: iterator containing the raw image data, which typically can be - used to save an image file locally, eg: - - ``` - f = open(local_filename, 'wb') - for chunk in client.photo(photo_reference, max_width=100): - if chunk: - f.write(chunk) - f.close() - ``` - + used to save an image file locally, eg: + + ``` + f = open(local_filename, 'wb') + for chunk in client.photo(photo_reference, max_width=100): + if chunk: + f.write(chunk) + f.close() + ``` """ if not (max_width or max_height): @@ -165,13 +162,12 @@ def places_autocomplete(client, input_text, offset=None, location=None, :type input_text: string :param offset: The position, in the input term, of the last character - that the service uses to match predictions. For example, - if the input is 'Google' and the offset is 3, the - service will match on 'Goo'. + that the service uses to match predictions. For example, if the input + is 'Google' and the offset is 3, the service will match on 'Goo'. :type offset: int :param location: The latitude/longitude value for which you wish to obtain the - closest, human-readable address. + closest, human-readable address. :type location: string, dict, list, or tuple :param radius: Distance in meters within which to bias results. @@ -181,7 +177,6 @@ def places_autocomplete(client, input_text, offset=None, location=None, :type langauge: string :rtype: list of predictions - """ params = {"input": input_text} diff --git a/googlemaps/roads.py b/googlemaps/roads.py index c47f17b2..da1850e5 100644 --- a/googlemaps/roads.py +++ b/googlemaps/roads.py @@ -36,11 +36,10 @@ def snap_to_roads(client, path, interpolate=False): location is a string, dict, list, or tuple :param interpolate: Whether to interpolate a path to include all points - forming the full road-geometry. When true, additional interpolated - points will also be returned, resulting in a path that smoothly - follows the geometry of the road, even around corners and through - tunnels. Interpolated paths may contain more points than the - original path. + forming the full road-geometry. When true, additional interpolated + points will also be returned, resulting in a path that smoothly follows + the geometry of the road, even around corners and through tunnels. + Interpolated paths may contain more points than the original path. :type interpolate: bool :rtype: A list of snapped points. @@ -61,7 +60,7 @@ def speed_limits(client, place_ids): """Returns the posted speed limit (in km/h) for given road segments. :param place_ids: The Place ID of the road segment. Place IDs are returned - by the snap_to_roads function. You can pass up to 100 Place IDs. + by the snap_to_roads function. You can pass up to 100 Place IDs. :type place_ids: str or list :rtype: list of speed limits. @@ -85,8 +84,7 @@ def snapped_speed_limits(client, path): :type path: a single location, or a list of locations, where a location is a string, dict, list, or tuple - :rtype: a dict with both a list of speed limits and a list of the snapped - points. + :rtype: dict with a list of speed limits and a list of the snapped points. """ params = {"path": convert.location_list(path)} From 06f74f2e84416ea3f8dd5e803091b94c47a7e61b Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Oct 2015 10:18:43 +1100 Subject: [PATCH 019/260] Link to the list of supported types values for Places API. Change-Id: I1731c421d971d66c2dcd96bd349ea1aec919f912 --- googlemaps/places.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 265dd7ed..349b92f2 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -54,7 +54,8 @@ def places(client, query, location=None, radius=None, language=None, :type open_now: bool :param types: Restricts the results to places matching at least one of the - specified types. + specified types. The full list of supported types is available here: + https://developers.google.com/places/supported_types :type types: string or list of strings :param page_token: Token from a previous search that when provided will From 606fcbad96d3f41ea0db8557753999cf6aeda7e5 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Oct 2015 10:21:05 +1100 Subject: [PATCH 020/260] eg -> For example Change-Id: Id1abf1542104b35cf40b3e10ca2dddbd702f2735 --- googlemaps/places.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 349b92f2..6cbf0347 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -123,7 +123,7 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): :type max_height: int :rtype: iterator containing the raw image data, which typically can be - used to save an image file locally, eg: + used to save an image file locally. For example: ``` f = open(local_filename, 'wb') From 559f546f3971eef88abfcf6d6978cb524ef9f113 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 11 Nov 2015 10:00:20 +1100 Subject: [PATCH 021/260] Added Sean Wohltman to CONTRIBUTORS. Change-Id: I119169c1cc219f4c03b0fccdd8e0dcb6fdada9ac --- CONTRIBUTORS | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 42728d64..10a36b0f 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -12,4 +12,5 @@ Dave Holmes Luke Mahe Mark McDonald Sam Thorogood +Sean Wohltman Stephen McDonald From 7e3f59f8bc110872e74c420d5cae1a4e4b3adec4 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 11 Nov 2015 11:11:47 +1100 Subject: [PATCH 022/260] Tag 2.4 Change-Id: I1102e8825eadb829b9ffe731975ed4dda4c07c8c --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 63cfe578..8c4ee086 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.3-dev" +__version__ = "2.4" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 672c09e8..335d5647 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.3-dev', + version='2.4', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 5c08aebb5e6198ea333953dcdd627b6551381ff3 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 11 Nov 2015 11:20:12 +1100 Subject: [PATCH 023/260] Start next dev version (2.4-dev) Change-Id: Ia8a927dad756808f3624f72664e0e19efbf1d00b --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 8c4ee086..eb92915f 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4" +__version__ = "2.4-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 335d5647..1a08b4d5 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4', + version='2.4-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 7f2536cc5b18d7c679725df9a567c2659271f169 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 11 Nov 2015 15:10:28 +1100 Subject: [PATCH 024/260] Update docs link to latest lib version. Change-Id: I5e401d99698434458ff7066237a6e0ad32f9d7b9 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 44237c60..697e9fcc 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Note that you will need requests 2.4.0 or higher if you want to specify connect/ ## Developer Documentation -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.2/) +View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4/) Additional documentation for the included web services is available at https://developers.google.com/maps/. From 7e899ee83784049a1628abefad082c28f7dfa05d Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 24 Nov 2015 10:38:33 +1100 Subject: [PATCH 025/260] Added traffic_model param to distance matrix API, plus some traffic_model tests. Change-Id: I85a61b62e2be043db7714701094b30a37cdf5c30 --- googlemaps/directions.py | 4 ++-- googlemaps/distance_matrix.py | 9 ++++++++- test/test_directions.py | 5 +++-- test/test_distance_matrix.py | 12 ++++++++++-- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/googlemaps/directions.py b/googlemaps/directions.py index 024bf54e..b3938ea7 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -84,7 +84,7 @@ def directions(client, origin, destination, :param transit_routing_preference: Specifies preferences for transit requests. Valid values are "less_walking" or "fewer_transfers" :type transit_routing_preference: string - + :param traffic_model: Specifies the predictive travel time model to use. "best_guess" or "optimistic" or "pessimistic" :type units: string @@ -140,7 +140,7 @@ def directions(client, origin, destination, if transit_routing_preference: params["transit_routing_preference"] = transit_routing_preference - + if traffic_model: params["traffic_model"] = traffic_model diff --git a/googlemaps/distance_matrix.py b/googlemaps/distance_matrix.py index 20076c52..89896ccc 100644 --- a/googlemaps/distance_matrix.py +++ b/googlemaps/distance_matrix.py @@ -24,7 +24,7 @@ def distance_matrix(client, origins, destinations, mode=None, language=None, avoid=None, units=None, departure_time=None, arrival_time=None, transit_mode=None, - transit_routing_preference=None): + transit_routing_preference=None, traffic_model=None): """ Gets travel distance and time for a matrix of origins and destinations. :param origins: One or more locations and/or latitude/longitude values, @@ -75,6 +75,10 @@ def distance_matrix(client, origins, destinations, requests. Valid values are "less_walking" or "fewer_transfers". :type transit_routing_preference: string + :param traffic_model: Specifies the predictive travel time model to use. + "best_guess" or "optimistic" or "pessimistic" + :type units: string + :rtype: matrix of distances. Results are returned in rows, each row containing one origin paired with each destination. """ @@ -118,4 +122,7 @@ def distance_matrix(client, origins, destinations, if transit_routing_preference: params["transit_routing_preference"] = transit_routing_preference + if traffic_model: + params["traffic_model"] = traffic_model + return client._get("/maps/api/distancematrix/json", params) diff --git a/test/test_directions.py b/test/test_directions.py index 2de5e0bc..d7d8eb42 100644 --- a/test/test_directions.py +++ b/test/test_directions.py @@ -89,12 +89,13 @@ def test_transit_with_departure_time(self): now = datetime.now() routes = self.client.directions("Sydney Town Hall", "Parramatta, NSW", mode="transit", + traffic_model="optimistic", departure_time=now) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?origin=' 'Sydney+Town+Hall&key=%s&destination=Parramatta%%2C+NSW&' - 'mode=transit&departure_time=%d' % + 'mode=transit&departure_time=%d&traffic_model=optimistic' % (self.key, time.mktime(now.timetuple())), responses.calls[0].request.url) @@ -120,7 +121,7 @@ def test_transit_with_arrival_time(self): responses.calls[0].request.url) - def test_crazy_travel_mode(self): + def test_invalid_travel_mode(self): with self.assertRaises(ValueError): self.client.directions("48 Pirrama Road, Pyrmont, NSW", "Sydney Town Hall", diff --git a/test/test_distance_matrix.py b/test/test_distance_matrix.py index d0848f59..992cea83 100644 --- a/test/test_distance_matrix.py +++ b/test/test_distance_matrix.py @@ -17,6 +17,9 @@ """Tests for the distance matrix module.""" +from datetime import datetime +import time + import googlemaps import responses import test as _test @@ -97,11 +100,14 @@ def test_all_params(self): "Bungle Bungles, Australia", "The Pinnacles, Australia"] + now = datetime.now() matrix = self.client.distance_matrix(origins, destinations, mode="driving", language="en-AU", avoid="tolls", - units="imperial") + units="imperial", + departure_time=now, + traffic_model="optimistic") self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/distancematrix/json?' @@ -112,7 +118,9 @@ def test_all_params(self): 'avoid=tolls&mode=driving&key=%s&units=imperial&' 'destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7C' 'Blue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia' - '%%7CThe+Pinnacles%%2C+Australia' % self.key, + '%%7CThe+Pinnacles%%2C+Australia&departure_time=%d' + '&traffic_model=optimistic' % + (self.key, time.mktime(now.timetuple())), responses.calls[0].request.url) From 57465e7bb3e11964494ef261be587e9588edce99 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 24 Nov 2015 10:41:58 +1100 Subject: [PATCH 026/260] Tag 2.4.1 Change-Id: I231bac9de2a6650fa0cc3b425e0f5cd1c5f84401 --- README.md | 2 +- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 697e9fcc..bbc46669 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Note that you will need requests 2.4.0 or higher if you want to specify connect/ ## Developer Documentation -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4/) +View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.1/) Additional documentation for the included web services is available at https://developers.google.com/maps/. diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index eb92915f..2ec54c94 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4-dev" +__version__ = "2.4.1" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 1a08b4d5..aee2b879 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4-dev', + version='2.4.1', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From c9ea1ee7a02fd42d24967d60a2457b33b2894d0e Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 24 Nov 2015 11:22:36 +1100 Subject: [PATCH 027/260] Better traffic_model docstrings. Change-Id: I42c4b840bfb3ba5c074d1f836ebcb8f543efa32f --- googlemaps/directions.py | 5 ++++- googlemaps/distance_matrix.py | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/googlemaps/directions.py b/googlemaps/directions.py index b3938ea7..cb939109 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -86,7 +86,10 @@ def directions(client, origin, destination, :type transit_routing_preference: string :param traffic_model: Specifies the predictive travel time model to use. - "best_guess" or "optimistic" or "pessimistic" + Valid values are "best_guess" or "optimistic" or "pessimistic". + The traffic_model parameter may only be specified for requests where + the travel mode is driving, and where the request includes a + departure_time. :type units: string :rtype: list of routes diff --git a/googlemaps/distance_matrix.py b/googlemaps/distance_matrix.py index 89896ccc..89fb23a1 100644 --- a/googlemaps/distance_matrix.py +++ b/googlemaps/distance_matrix.py @@ -76,8 +76,10 @@ def distance_matrix(client, origins, destinations, :type transit_routing_preference: string :param traffic_model: Specifies the predictive travel time model to use. - "best_guess" or "optimistic" or "pessimistic" - :type units: string + Valid values are "best_guess" or "optimistic" or "pessimistic". + The traffic_model parameter may only be specified for requests where + the travel mode is driving, and where the request includes a + departure_time. :rtype: matrix of distances. Results are returned in rows, each row containing one origin paired with each destination. From a851690bce1e033b575709ddb5eaf223ead3f176 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 24 Nov 2015 14:44:32 +1100 Subject: [PATCH 028/260] Start next dev version (2.4.1-dev) Change-Id: Ic363dd5081e2cdc8c662b45f59219729d520ee27 --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 2ec54c94..0b8f708a 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.1" +__version__ = "2.4.1-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index aee2b879..b389869e 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.1', + version='2.4.1-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From b453ed5b350093a3ac65d7853da61effb08f34c2 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 3 Dec 2015 14:46:59 +1100 Subject: [PATCH 029/260] Fix rate limiting in Python 2.6 Change-Id: Id404089468d602145eff2ee33828fe7af229211d --- googlemaps/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 2ac9d0a8..d8aa3925 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -130,6 +130,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, "verify": True, # NOTE(cbro): verify SSL certs. }) + self.queries_per_second = queries_per_second self.sent_times = collections.deque("", queries_per_second) def _get(self, url, params, first_request_time=None, retry_counter=0, @@ -210,7 +211,7 @@ def _get(self, url, params, first_request_time=None, retry_counter=0, # Check if the time of the nth previous query (where n is queries_per_second) # is under a second ago - if so, sleep for the difference. - if self.sent_times and len(self.sent_times) == self.sent_times.maxlen: + if self.sent_times and len(self.sent_times) == self.queries_per_second: elapsed_since_earliest = time.time() - self.sent_times[0] if elapsed_since_earliest < 1: time.sleep(1 - elapsed_since_earliest) From 40081e24653b5d2726eb88ac974c710cb088338f Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 3 Dec 2015 14:47:48 +1100 Subject: [PATCH 030/260] Note correct min Python version. Change-Id: I5dc62d00852979240d946687f1001fcf60ec52e4 --- README.md | 8 +++----- setup.py | 3 --- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bbc46669..7f87345a 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ contribute, please read [How to Contribute][contrib]. ## Requirements - - Python 2.5 or later. + - Python 2.7 or later. - A Google Maps API key. ### API keys @@ -92,15 +92,13 @@ https://developers.google.com/maps/. ## Usage -This example uses the [Geocoding API]. - - +This example uses the [Geocoding API] and the [Directions API]. ```python gmaps = googlemaps.Client(key='Add Your Key here') -# Geocoding and address +# Geocoding an address geocode_result = gmaps.geocode('1600 Amphitheatre Parkway, Mountain View, CA') # Look up an address with reverse geocoding diff --git a/setup.py b/setup.py index b389869e..1f8786ca 100644 --- a/setup.py +++ b/setup.py @@ -31,9 +31,6 @@ 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.4', From 2aeedcda9571e4b4c3d571e6ded8e025ec20e276 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 3 Dec 2015 14:52:12 +1100 Subject: [PATCH 031/260] Tag 2.4.2 Change-Id: I411d41e894dcb595a0c7053665c289a45043e61b --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 0b8f708a..55f9b318 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.1-dev" +__version__ = "2.4.2" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 1f8786ca..d248c1f9 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.1-dev', + version='2.4.2', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From b224b4c3f3998780fc2155b726fd9106b042fda7 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 3 Dec 2015 16:01:31 +1100 Subject: [PATCH 032/260] Start next dev version (2.4.2-dev) Change-Id: I4350430c6e9126dd83be2e95776a0bb3f65d300b --- README.md | 2 +- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f87345a..a20780b3 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Note that you will need requests 2.4.0 or higher if you want to specify connect/ ## Developer Documentation -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.1/) +View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.2/) Additional documentation for the included web services is available at https://developers.google.com/maps/. diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 55f9b318..8dbe0bf4 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.2" +__version__ = "2.4.2-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index d248c1f9..551b131c 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.2', + version='2.4.2-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 7c9a1584aaa3a41008b182df45df8bf74cb26613 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 4 Jan 2016 09:21:54 +1100 Subject: [PATCH 033/260] Update to latest version of requests. Closes #89. Change-Id: Ia274160fc9a1d00d187998dfa9db2c67f3dad70c --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 551b131c..25cad6a1 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ requirements = [ - 'requests<=2.6', + 'requests<=2.9.1', ] setup(name='googlemaps', From 8284b1160ebe25bc8e59420c399a001cbd5bbe26 Mon Sep 17 00:00:00 2001 From: Matthew Wilkens Date: Tue, 12 Jan 2016 18:32:10 -0500 Subject: [PATCH 034/260] Add reverse geocoding my place_id --- googlemaps/geocoding.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index 7152d69e..3c4d9b43 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -68,8 +68,8 @@ def geocode(client, address=None, components=None, bounds=None, region=None, return client._get("/maps/api/geocode/json", params)["results"] -def reverse_geocode(client, latlng, result_type=None, location_type=None, - language=None): +def reverse_geocode(client, latlng, result_type=None, + location_type=None, language=None): """ Reverse geocoding is the process of converting geographic coordinates into a human-readable address. @@ -77,6 +77,10 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :param latlng: The latitude/longitude value for which you wish to obtain the closest, human-readable address. :type latlng: string, dict, list, or tuple + + :param place_id: The place_id for which you wish to obtain the + closest, human-readable address. + :type place_id: string :param result_type: One or more address types to restrict results to. :type result_type: string or list of strings @@ -90,7 +94,10 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :rtype: list of reverse geocoding results. """ - params = {"latlng": convert.latlng(latlng)} + if (type(latlng) == str) & (latlng[0] in ("-0123456789")): + params = {"place_id": latlng} + else: + params = {"latlng": convert.latlng(latlng)} if result_type: params["result_type"] = convert.join_list("|", result_type) From c86e898f2cdded4cd41b1c0c2622182fc1acd5ad Mon Sep 17 00:00:00 2001 From: Matthew Wilkens Date: Tue, 12 Jan 2016 20:36:48 -0500 Subject: [PATCH 035/260] Support reverse_geocode by place_id If reverse_geocode is passed a string that does not begin with a digit or a + or - sign, assume it's a place_id rather than a latlng coordinate and treat appropriately. --- googlemaps/geocoding.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index 3c4d9b43..a664da69 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -68,20 +68,16 @@ def geocode(client, address=None, components=None, bounds=None, region=None, return client._get("/maps/api/geocode/json", params)["results"] -def reverse_geocode(client, latlng, result_type=None, - location_type=None, language=None): +def reverse_geocode(client, latlng, result_type=None, location_type=None, + language=None): """ Reverse geocoding is the process of converting geographic coordinates into a human-readable address. - :param latlng: The latitude/longitude value for which you wish to obtain the - closest, human-readable address. + :param latlng: The latitude/longitude value or place_id for which you wish + to obtain the closest, human-readable address. :type latlng: string, dict, list, or tuple - :param place_id: The place_id for which you wish to obtain the - closest, human-readable address. - :type place_id: string - :param result_type: One or more address types to restrict results to. :type result_type: string or list of strings @@ -94,10 +90,10 @@ def reverse_geocode(client, latlng, result_type=None, :rtype: list of reverse geocoding results. """ - if (type(latlng) == str) & (latlng[0] in ("-0123456789")): - params = {"place_id": latlng} + if (type(latlng) in (str, unicode)) & (latlng[0] not in ("-+0123456789")): + params = {"place_id": latlng} else: - params = {"latlng": convert.latlng(latlng)} + params = {"latlng": convert.latlng(latlng)} if result_type: params["result_type"] = convert.join_list("|", result_type) From 8077f07c3948169ab91ebd347c80e2bb20348759 Mon Sep 17 00:00:00 2001 From: Matthew Wilkens Date: Wed, 13 Jan 2016 22:33:50 -0500 Subject: [PATCH 036/260] reverse_geocode back to Python3 only Python3 lacks 'unicode' type. Need different approach to catch unicode strings in Python2. --- googlemaps/geocoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index a664da69..6bd9c5b2 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -90,7 +90,7 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :rtype: list of reverse geocoding results. """ - if (type(latlng) in (str, unicode)) & (latlng[0] not in ("-+0123456789")): + if (type(latlng) == str) & (latlng[0] not in ("-+0123456789")): params = {"place_id": latlng} else: params = {"latlng": convert.latlng(latlng)} From ec0e3564ea9fbf7fabd194bb81c2651ad6e64cfe Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 5 Feb 2016 13:29:21 +1100 Subject: [PATCH 037/260] Update analytics --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a20780b3..b33b81cf 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Python Client for Google Maps Services Use Python? Want to [geocode][Geocoding API] something? Looking for [directions][Directions API]? Maybe [matrices of directions][Distance Matrix API]? This library brings the [Google Maps API Web Services] to your Python application. -![Analytics](https://ga-beacon.appspot.com/UA-12846745-20/google-maps-services-python/readme?pixel) +![Analytics](https://maps-ga-beacon.appspot.com/UA-12846745-20/google-maps-services-java/readme?pixel) The Python Client for Google Maps Services is a Python Client library for the following Google Maps APIs: From d6bc87907cea98a65f28b441cda0d332c0c3be00 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 5 Feb 2016 13:47:44 +1100 Subject: [PATCH 038/260] Update analytics --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b33b81cf..a5f2bcfc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Python Client for Google Maps Services Use Python? Want to [geocode][Geocoding API] something? Looking for [directions][Directions API]? Maybe [matrices of directions][Distance Matrix API]? This library brings the [Google Maps API Web Services] to your Python application. -![Analytics](https://maps-ga-beacon.appspot.com/UA-12846745-20/google-maps-services-java/readme?pixel) +![Analytics](https://maps-ga-beacon.appspot.com/UA-12846745-20/google-maps-services-python/readme?pixel) The Python Client for Google Maps Services is a Python Client library for the following Google Maps APIs: From a02d1f69a0371686cd97993a2588d127dadf3ec7 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 5 Feb 2016 13:59:22 +1100 Subject: [PATCH 039/260] pip no longer supports Python 3.2, so we can't test it in CI. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cdd7714b..f003fdf3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,5 @@ script: env: - TOXENV=py27 - - TOXENV=py32 - TOXENV=py34 - TOXENV=docs From 86e38dd2a760f6236354fd0d750563d58a5d54db Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 29 Feb 2016 16:45:29 +1100 Subject: [PATCH 040/260] In elevations API, automatically use encoded polylines if shorter in length. Change-Id: I807f9211c397286f88f708b04b62b12e109e9285 --- googlemaps/convert.py | 23 +++++++++++++++++++++++ googlemaps/elevation.py | 4 ++-- test/test_elevation.py | 10 ++++------ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/googlemaps/convert.py b/googlemaps/convert.py index df7b32d8..40b44aa9 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -314,3 +314,26 @@ def encode_polyline(points): last_lng = lng return result + + +def shortest_path(locations): + """Returns the shortest representation of the given locations. + + The Elevations API limits requests to 2000 characters, and accepts + multiple locations either as pipe-delimited lat/lng values, or + an encoded polyline, so we determine which is shortest and use it. + + :param locations: The lat/lng list. + :type locations: list + + :rtype: string + """ + if isinstance(locations, tuple): + # Handle the single-tuple lat/lng case. + locations = [locations] + encoded = "enc:%s" % encode_polyline(locations) + unencoded = location_list(locations) + if len(encoded) < len(unencoded): + return encoded + else: + return unencoded diff --git a/googlemaps/elevation.py b/googlemaps/elevation.py index b6a111e7..4b286f06 100644 --- a/googlemaps/elevation.py +++ b/googlemaps/elevation.py @@ -33,7 +33,7 @@ def elevation(client, locations): :rtype: list of elevation data responses """ - params = {"locations": convert.location_list(locations)} + params = {"locations": convert.shortest_path(locations)} return client._get("/maps/api/elevation/json", params)["results"] @@ -55,7 +55,7 @@ def elevation_along_path(client, path, samples): if type(path) is str: path = "enc:%s" % path else: - path = convert.location_list(path) + path = convert.shortest_path(path) params = { "path": path, diff --git a/test/test_elevation.py b/test/test_elevation.py index 73b31a93..478e1499 100644 --- a/test/test_elevation.py +++ b/test/test_elevation.py @@ -41,7 +41,7 @@ def test_elevation_single(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' - 'locations=40.714728%%2C-73.998672&key=%s' % self.key, + 'locations=enc:abowFtzsbM&key=%s' % self.key, responses.calls[0].request.url) @responses.activate @@ -56,7 +56,7 @@ def test_elevation_single_list(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' - 'locations=40.714728%%2C-73.998672&key=%s' % self.key, + 'locations=enc:abowFtzsbM&key=%s' % self.key, responses.calls[0].request.url) @responses.activate @@ -72,8 +72,7 @@ def test_elevation_multiple(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' - 'locations=40.714728%%2C-73.998672%%7C-34.397000%%2C' - '150.644000&key=%s' % self.key, + 'locations=enc:abowFtzsbMhgmiMuobzi@&key=%s' % self.key, responses.calls[0].request.url) def test_elevation_along_path_single(self): @@ -95,7 +94,6 @@ def test_elevation_along_path(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' - 'path=40.714728%%2C-73.998672%%7C-34.397000%%2C150.644000&' + 'path=enc:abowFtzsbMhgmiMuobzi@&' 'key=%s&samples=5' % self.key, responses.calls[0].request.url) - From f19d5fab82af451f937209bb389ccb88a616f287 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 1 Mar 2016 14:21:05 +1100 Subject: [PATCH 041/260] Trim extraneous chars from latlngs. Change-Id: I0d296840cea5665a70654b9de60754c35e82e4de --- googlemaps/convert.py | 29 ++++++++++++++++++++++++++++- test/test_convert.py | 6 +++--- test/test_distance_matrix.py | 2 +- test/test_elevation.py | 15 +++++++++++++++ test/test_geocoding.py | 2 +- test/test_places.py | 2 +- test/test_roads.py | 2 +- 7 files changed, 50 insertions(+), 8 deletions(-) diff --git a/googlemaps/convert.py b/googlemaps/convert.py index 40b44aa9..1c2264e4 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -31,6 +31,29 @@ import time as _time +def format_float(arg): + """Formats a float value to be as short as possible. + + Trims extraneous trailing zeros and period to give API + args the best possible chance of fitting within 2000 char + URL length restrictions. + + For example: + + format_float(40) -> "40" + format_float(40.0) -> "40" + format_float(40.1) -> "40.1" + format_float(40.001) -> "40.001" + format_float(40.0010) -> "40.001" + + :param arg: The lat or lng float. + :type arg: float + + :rtype: string + """ + return ("%f" % float(arg)).rstrip("0").rstrip(".") + + def latlng(arg): """Converts a lat/lon pair to a comma-separated string. @@ -50,7 +73,11 @@ def latlng(arg): :param arg: The lat/lon pair. :type arg: string or dict or list or tuple """ - return arg if is_string(arg) else "%f,%f" % normalize_lat_lng(arg) + if is_string(arg): + return arg + + normalized = normalize_lat_lng(arg) + return "%s,%s" % (format_float(normalized[0]), format_float(normalized[1])) def normalize_lat_lng(arg): diff --git a/test/test_convert.py b/test/test_convert.py index 90d9c0f3..851eda14 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -26,7 +26,7 @@ class ConvertTest(unittest.TestCase): def test_latlng(self): - expected = "1.000000,2.000000" + expected = "1,2" ll = {"lat": 1, "lng": 2} self.assertEqual(expected, convert.latlng(ll)) @@ -42,7 +42,7 @@ def test_latlng(self): convert.latlng(1) def test_location_list(self): - expected = "1.000000,2.000000|1.000000,2.000000" + expected = "1,2|1,2" ll = [{"lat": 1, "lng": 2}, {"lat": 1, "lng": 2}] self.assertEqual(expected, convert.location_list(ll)) @@ -104,7 +104,7 @@ def test_bounds(self): ne = {"lat": 1, "lng": 2} sw = (3, 4) b = {"northeast": ne, "southwest": sw} - self.assertEqual("3.000000,4.000000|1.000000,2.000000", + self.assertEqual("3,4|1,2", convert.bounds(b)) with self.assertRaises(TypeError): diff --git a/test/test_distance_matrix.py b/test/test_distance_matrix.py index 992cea83..966abef8 100644 --- a/test/test_distance_matrix.py +++ b/test/test_distance_matrix.py @@ -77,7 +77,7 @@ def test_mixed_params(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/distancematrix/json?' - 'key=%s&origins=Bobcaygeon+ON%%7C41.432060%%2C-81.389920&' + 'key=%s&origins=Bobcaygeon+ON%%7C41.43206%%2C-81.38992&' 'destinations=43.012486%%2C-83.696415%%7C42.886386%%2C' '-78.878163' % self.key, responses.calls[0].request.url) diff --git a/test/test_elevation.py b/test/test_elevation.py index 478e1499..6fc990ba 100644 --- a/test/test_elevation.py +++ b/test/test_elevation.py @@ -97,3 +97,18 @@ def test_elevation_along_path(self): 'path=enc:abowFtzsbMhgmiMuobzi@&' 'key=%s&samples=5' % self.key, responses.calls[0].request.url) + + @responses.activate + def test_short_latlng(self): + responses.add(responses.GET, + 'https://maps.googleapis.com/maps/api/elevation/json', + body='{"status":"OK","results":[]}', + status=200, + content_type='application/json') + + results = self.client.elevation((40, -73)) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' + 'locations=40,-73&key=%s' % self.key, + responses.calls[0].request.url) diff --git a/test/test_geocoding.py b/test/test_geocoding.py index 2aba3f9d..e074c034 100644 --- a/test/test_geocoding.py +++ b/test/test_geocoding.py @@ -57,7 +57,7 @@ def test_reverse_geocode(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'latlng=-33.867487%%2C151.206990&key=%s' % self.key, + 'latlng=-33.867487%%2C151.20699&key=%s' % self.key, responses.calls[0].request.url) @responses.activate diff --git a/test/test_places.py b/test/test_places.py index 874dfdcf..a401e7a1 100644 --- a/test/test_places.py +++ b/test/test_places.py @@ -48,7 +48,7 @@ def test_places_text_search(self): min_price=1, max_price=4, open_now=True) self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?language=en-AU&location=-33.867460%%2C151.207090&' + self.assertURLEqual('%s?language=en-AU&location=-33.86746%%2C151.20709&' 'maxprice=4&minprice=1&opennow=true&query=restaurant&' 'radius=100&key=%s' % (url, self.key), responses.calls[0].request.url) diff --git a/test/test_roads.py b/test/test_roads.py index 9a357289..62b379f2 100644 --- a/test/test_roads.py +++ b/test/test_roads.py @@ -58,7 +58,7 @@ def test_path(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual("https://roads.googleapis.com/v1/speedLimits?" - "path=1.000000%%2C2.000000|3.000000%%2C4.000000" + "path=1%%2C2|3%%2C4" "&key=%s" % self.key, responses.calls[0].request.url) From 5756119d98e79e679a07e62df126424cc589f393 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 2 Mar 2016 15:42:59 +1100 Subject: [PATCH 042/260] Added missing Places API features: nearby/radar search, autocomplete queries, various params. Change-Id: I55b859d5a570e7c979bcc6c9cd7468645429ce83 --- googlemaps/client.py | 6 ++ googlemaps/places.py | 230 ++++++++++++++++++++++++++++++++++++++++--- test/test_places.py | 75 +++++++++++++- 3 files changed, 292 insertions(+), 19 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index d8aa3925..97037a27 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -293,9 +293,12 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.roads import speed_limits from googlemaps.roads import snapped_speed_limits from googlemaps.places import places +from googlemaps.places import places_nearby +from googlemaps.places import places_radar from googlemaps.places import place from googlemaps.places import places_photo from googlemaps.places import places_autocomplete +from googlemaps.places import places_autocomplete_query Client.directions = directions Client.distance_matrix = distance_matrix @@ -308,9 +311,12 @@ def _generate_auth_url(self, path, params, accepts_clientid): Client.speed_limits = speed_limits Client.snapped_speed_limits = snapped_speed_limits Client.places = places +Client.places_nearby = places_nearby +Client.places_radar = places_radar Client.place = place Client.places_photo = places_photo Client.places_autocomplete = places_autocomplete +Client.places_autocomplete_query = places_autocomplete_query def sign_hmac(secret, payload): diff --git a/googlemaps/places.py b/googlemaps/places.py index 6cbf0347..52f19809 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -21,7 +21,7 @@ def places(client, query, location=None, radius=None, language=None, - min_price=None, max_price=None, open_now=False, types=None, + min_price=None, max_price=None, open_now=False, type=None, page_token=None): """ Places search. @@ -53,10 +53,10 @@ def places(client, query, location=None, radius=None, language=None, the time the query is sent. :type open_now: bool - :param types: Restricts the results to places matching at least one of the - specified types. The full list of supported types is available here: - https://developers.google.com/places/supported_types - :type types: string or list of strings + :param type: Restricts the results to places matching the specified type. + The full list of supported types is available here: + https://developers.google.com/places/supported_type + :type type: string :param page_token: Token from a previous search that when provided will returns the next page of results for the same search. @@ -67,24 +67,168 @@ def places(client, query, location=None, radius=None, language=None, html_attributions: set of attributions which must be displayed next_page_token: token for retrieving the next page of results """ - params = {"query": query} + return _places(client, "text", query=query, location=location, + radius=radius, language=language, min_price=min_price, + max_price=max_price, open_now=open_now, type=type, + page_token=page_token) + +def places_nearby(client, location, radius=None, keyword=None, language=None, + min_price=None, max_price=None, name=None, open_now=False, + rank_by=None, type=None, page_token=None): + """ + Performs nearby search for places. + + :param location: The latitude/longitude value for which you wish to obtain the + closest, human-readable address. + :type location: string, dict, list, or tuple + + :param radius: Distance in meters within which to bias results. + :type radius: int + + :param keyword: A term to be matched against all content that Google has + indexed for this place. + :type keyword: string + + :param language: The language in which to return results. + :type langauge: string + + :param min_price: Restricts results to only those places with no less than + this price level. Valid values are in the range from 0 + (most affordable) to 4 (most expensive). + :type min_price: int + + :param max_price: Restricts results to only those places with no greater + than this price level. Valid values are in the range + from 0 (most affordable) to 4 (most expensive). + :type max_price: int + + :param name: One or more terms to be matched against the names of places. + :type name: string or list of strings + + :param open_now: Return only those places that are open for business at + the time the query is sent. + :type open_now: bool + + :param rank_by: Specifies the order in which results are listed. + Possible values are: prominence (default), distance + :type rank_by: string + + :param type: Restricts the results to places matching the specified type. + The full list of supported types is available here: + https://developers.google.com/places/supported_type + :type type: string + + :param page_token: Token from a previous search that when provided will + returns the next page of results for the same search. + :type page_token: string + + :rtype: result dict with the following keys: + status: status code + results: list of places + html_attributions: set of attributions which must be displayed + next_page_token: token for retrieving the next page of results + + """ + if rank_by == "distance": + if not (keyword or name or type): + raise ValueError("either a keyword, name, or type arg is required " + "when rank_by is set to distance") + elif radius is not None: + raise ValueError("radius cannot be specified when rank_by is set to " + "distance") + + return _places(client, "nearby", location=location, radius=radius, + keyword=keyword, language=language, min_price=min_price, + max_price=max_price, name=name, open_now=open_now, + rank_by=rank_by, type=type, page_token=page_token) + + +def places_radar(client, location, radius, keyword=None, min_price=None, + max_price=None, name=None, open_now=False, type=None): + """ + Performs radar search for places. + + :param location: The latitude/longitude value for which you wish to obtain the + closest, human-readable address. + :type location: string, dict, list, or tuple + + :param radius: Distance in meters within which to bias results. + :type radius: int + + :param keyword: A term to be matched against all content that Google has + indexed for this place. + :type keyword: string + + :param min_price: Restricts results to only those places with no less than + this price level. Valid values are in the range from 0 + (most affordable) to 4 (most expensive). + :type min_price: int + + :param max_price: Restricts results to only those places with no greater + than this price level. Valid values are in the range + from 0 (most affordable) to 4 (most expensive). + :type max_price: int + + :param name: One or more terms to be matched against the names of places. + :type name: string or list of strings + + :param open_now: Return only those places that are open for business at + the time the query is sent. + :type open_now: bool + + :param type: Restricts the results to places matching the specified type. + The full list of supported types is available here: + https://developers.google.com/places/supported_type + :type type: string + + :rtype: result dict with the following keys: + status: status code + results: list of places + html_attributions: set of attributions which must be displayed + + """ + if not (keyword or name or type): + raise ValueError("either a keyword, name, or type arg is required") + + return _places(client, "radar", location=location, radius=radius, + keyword=keyword, min_price=min_price, max_price=max_price, + name=name, open_now=open_now, type=type) + + +def _places(client, url_part, query=None, location=None, radius=None, + keyword=None, language=None, min_price=0, max_price=4, name=None, + open_now=False, rank_by=None, type=None, page_token=None): + """ + Internal handler for ``places``, ``places_nearby``, and ``places_radar``. + See each method's docs for arg details. + """ + + params = {"minprice": min_price, "maxprice": max_price} + + if query: + params["query"] = query if location: params["location"] = convert.latlng(location) if radius: params["radius"] = radius + if keyword: + params["keyword"] = keyword if language: params["language"] = language - if min_price: - params["minprice"] = min_price - if max_price: - params["maxprice"] = max_price + if name: + params["name"] = convert.join_list(" ", name) if open_now: params["opennow"] = "true" + if rank_by: + params["rankby"] = rank_by + if type: + params["type"] = type if page_token: params["pagetoken"] = page_token - return client._get("/maps/api/place/textsearch/json", params) + url = "/maps/api/place/%ssearch/json" % url_part + return client._get(url, params) def place(client, place_id, language=None): @@ -154,7 +298,51 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): def places_autocomplete(client, input_text, offset=None, location=None, - radius=None, language=None): + radius=None, language=None, type=None, + components=None): + """ + Returns Place predictions given a textual search string and optional + geographic bounds. + + :param input_text: The text string on which to search. + :type input_text: string + + :param offset: The position, in the input term, of the last character + that the service uses to match predictions. For example, + if the input is 'Google' and the offset is 3, the + service will match on 'Goo'. + :type offset: int + + :param location: The latitude/longitude value for which you wish to obtain the + closest, human-readable address. + :type location: string, dict, list, or tuple + + :param radius: Distance in meters within which to bias results. + :type radius: int + + :param language: The language in which to return results. + :type langauge: string + + :param type: Restricts the results to places matching the specified type. + The full list of supported types is available here: + https://developers.google.com/places/web-service/autocomplete#place_types + :type type: string + + :param components: A component filter for which you wish to obtain a geocode, + for example: + ``{'administrative_area': 'TX','country': 'US'}`` + :type components: dict + + :rtype: list of predictions + + """ + return _autocomplete(client, "", input_text, offset=offset, + location=location, radius=radius, language=language, + type=type, components=components) + + +def places_autocomplete_query(client, input_text, offset=None, location=None, + radius=None, language=None): """ Returns Place predictions given a textual search query, such as "pizza near New York", and optional geographic bounds. @@ -179,6 +367,16 @@ def places_autocomplete(client, input_text, offset=None, location=None, :rtype: list of predictions """ + return _autocomplete(client, "query", input_text, offset=offset, + location=location, radius=radius, language=language) + + +def _autocomplete(client, url_part, input_text, offset=None, location=None, + radius=None, language=None, type=None, components=None): + """ + Internal handler for ``autocomplete`` and ``autocomplete_query``. + See each method's docs for arg details. + """ params = {"input": input_text} @@ -190,6 +388,10 @@ def places_autocomplete(client, input_text, offset=None, location=None, params["radius"] = radius if language: params["language"] = language + if type: + params["type"] = type + if components: + params["components"] = convert.components(components) - response = client._get("/maps/api/place/queryautocomplete/json", params) - return response["predictions"] + url = "/maps/api/place/%sautocomplete/json" % url_part + return client._get(url, params)["predictions"] diff --git a/test/test_places.py b/test/test_places.py index a401e7a1..188ee2d4 100644 --- a/test/test_places.py +++ b/test/test_places.py @@ -1,6 +1,6 @@ # This Python file uses the following encoding: utf-8 # -# Copyright 2015 Google Inc. All rights reserved. +# Copyright 2016 Google Inc. All rights reserved. # # # Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -32,7 +32,7 @@ def setUp(self): self.key = 'AIzaasdf' self.client = googlemaps.Client(self.key) self.location = (-33.86746, 151.207090) - self.types = ('liquor_store', 'mosque') + self.type = 'liquor_store' self.language = 'en-AU' self.radius = 100 @@ -45,14 +45,60 @@ def test_places_text_search(self): self.client.places('restaurant', location=self.location, radius=self.radius, language=self.language, - min_price=1, max_price=4, open_now=True) + min_price=1, max_price=4, open_now=True, + type=self.type) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?language=en-AU&location=-33.86746%%2C151.20709&' 'maxprice=4&minprice=1&opennow=true&query=restaurant&' - 'radius=100&key=%s' + 'radius=100&type=liquor_store&key=%s' % (url, self.key), responses.calls[0].request.url) + @responses.activate + def test_places_nearby_search(self): + url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json' + responses.add(responses.GET, url, + body='{"status": "OK", "results": [], "html_attributions": []}', + status=200, content_type='application/json') + + self.client.places_nearby(self.location, keyword='foo', + language=self.language, min_price=1, + max_price=4, name='bar', open_now=True, + rank_by='distance', type=self.type) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?keyword=foo&language=en-AU&location=-33.86746%%2C151.20709&' + 'maxprice=4&minprice=1&name=bar&opennow=true&rankby=distance&' + 'type=liquor_store&key=%s' + % (url, self.key), responses.calls[0].request.url) + + with self.assertRaises(ValueError): + self.client.places_nearby(self.location, rank_by="distance") + + with self.assertRaises(ValueError): + self.client.places_nearby(self.location, rank_by="distance", + keyword='foo', radius=self.radius) + + @responses.activate + def test_places_radar_search(self): + url = 'https://maps.googleapis.com/maps/api/place/radarsearch/json' + responses.add(responses.GET, url, + body='{"status": "OK", "results": [], "html_attributions": []}', + status=200, content_type='application/json') + + self.client.places_radar(self.location, self.radius, keyword='foo', + min_price=1, max_price=4, name='bar', + open_now=True, type=self.type) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?keyword=foo&location=-33.86746%%2C151.20709&' + 'maxprice=4&minprice=1&name=bar&opennow=true&radius=100&' + 'type=liquor_store&key=%s' + % (url, self.key), responses.calls[0].request.url) + + with self.assertRaises(ValueError): + self.client.places_radar(self.location, self.radius) + @responses.activate def test_place_detail(self): url = 'https://maps.googleapis.com/maps/api/place/details/json' @@ -81,12 +127,31 @@ def test_photo(self): @responses.activate def test_autocomplete(self): + url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json' + responses.add(responses.GET, url, + body='{"status": "OK", "predictions": []}', + status=200, content_type='application/json') + + self.client.places_autocomplete('Google', offset=3, + location=self.location, + radius=self.radius, + language=self.language, type='geocode', + components={'country': 'au'}) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?components=country%%3Aau&input=Google&language=en-AU&' + 'location=-33.86746%%2C151.20709&offset=3&radius=100&' + 'type=geocode&key=%s' % (url, self.key), + responses.calls[0].request.url) + + @responses.activate + def test_autocomplete_query(self): url = 'https://maps.googleapis.com/maps/api/place/queryautocomplete/json' responses.add(responses.GET, url, body='{"status": "OK", "predictions": []}', status=200, content_type='application/json') - self.client.places_autocomplete('pizza near New York') + self.client.places_autocomplete_query('pizza near New York') self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?input=pizza+near+New+York&key=%s' % From e63269f26b4f1d9ed7bf7f58152193a17a159200 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 14 Mar 2016 12:06:53 +1100 Subject: [PATCH 043/260] Tag 2.4.3 Change-Id: I680e0f59a2fb6e6ad9d464bb3caca3674a49fade --- README.md | 2 +- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a5f2bcfc..88a30c79 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Note that you will need requests 2.4.0 or higher if you want to specify connect/ ## Developer Documentation -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.2/) +View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.3/) Additional documentation for the included web services is available at https://developers.google.com/maps/. diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 8dbe0bf4..359d6f5e 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.2-dev" +__version__ = "2.4.3" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 25cad6a1..9bed1f86 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.2-dev', + version='2.4.3', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 8968200fab354132af97ddf5c56900062d9bdaf0 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 14 Mar 2016 12:49:30 +1100 Subject: [PATCH 044/260] Start next dev version (2.4.3-dev) Change-Id: Ie96d01919694738173c0c23871372d39a6eca19a --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 359d6f5e..70d8fb05 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.3" +__version__ = "2.4.3-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 9bed1f86..cf303fb4 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.3', + version='2.4.3-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 40a5d453bf50b44582ef7466d0be817c1be974df Mon Sep 17 00:00:00 2001 From: Matthew Wilkens Date: Mon, 14 Mar 2016 22:27:36 -0400 Subject: [PATCH 045/260] Fix place_id detection in reverse_geocode Use convert.is_string to do 2/3-safe string detection If string, test for comma; if no comma, then place_id --- googlemaps/geocoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index 6bd9c5b2..e76380d9 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -90,7 +90,7 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :rtype: list of reverse geocoding results. """ - if (type(latlng) == str) & (latlng[0] not in ("-+0123456789")): + if (convert.is_string(latlng) and (',' not in latlng)): params = {"place_id": latlng} else: params = {"latlng": convert.latlng(latlng)} From 765a370753448e9c80c89351fe02581f8be64d40 Mon Sep 17 00:00:00 2001 From: Matthew Wilkens Date: Mon, 14 Mar 2016 23:34:19 -0400 Subject: [PATCH 046/260] reverse_geocode() cleanup Remove extraneous parens. Add comment on logic detecting place_id as parameter. --- googlemaps/geocoding.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index e76380d9..d0a789a8 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -90,7 +90,9 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :rtype: list of reverse geocoding results. """ - if (convert.is_string(latlng) and (',' not in latlng)): + # Check if latlng param is a place_id string. + # place_id strings do not contain commas; latlng strings do. + if convert.is_string(latlng) and ',' not in latlng: params = {"place_id": latlng} else: params = {"latlng": convert.latlng(latlng)} From 32bc457308ed587ba9294700d64e00c38a3b4da5 Mon Sep 17 00:00:00 2001 From: Alykhan Tejani Date: Thu, 17 Mar 2016 15:39:56 +0000 Subject: [PATCH 047/260] Updated README to include relevant imports for example usage added `import googlemaps` and `from datetime import datetime` for the example to be complete --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 88a30c79..478bf251 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ https://developers.google.com/maps/. This example uses the [Geocoding API] and the [Directions API]. ```python +import googlemaps +from datetime import datetime gmaps = googlemaps.Client(key='Add Your Key here') From 2573fb94559eea4f866756776628acb4f9e2e08f Mon Sep 17 00:00:00 2001 From: Remi Bouchez Date: Thu, 24 Mar 2016 13:59:49 +0900 Subject: [PATCH 048/260] Adding channel support for Maps for Work users of this library (including tests). Cf. https://developers.google.com/maps/premium/reports/usage-reports#channels --- googlemaps/client.py | 23 +++++++++++++++++++++-- test/test_client.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 97037a27..b812d7e5 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -26,6 +26,7 @@ from datetime import timedelta import hashlib import hmac +import re import requests import random import time @@ -49,7 +50,7 @@ class Client(object): 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=10): + queries_per_second=10, channel=None): """ :param key: Maps API key. Required, unless "client_id" and "client_secret" are set. @@ -62,6 +63,12 @@ def __init__(self, key=None, client_id=None, client_secret=None, secret (base64 encoded). :type client_secret: string + :param channel: (for Maps API for Work customers) When set, a channel + parameter with this value will be added to the requests. + This can be used for tracking purpose. + Can not be used with a Maps API key. + :type channel: str + :param timeout: Combined connect and read timeout for HTTP requests, in seconds. Specify "None" for no timeout. :type timeout: int @@ -104,10 +111,19 @@ def __init__(self, key=None, client_id=None, client_secret=None, if key and not key.startswith("AIza"): raise ValueError("Invalid API key provided.") + if channel: + if key: + raise ValueError("The channel argument can not be used with an " + "API key") + if not re.match("^[a-zA-Z0-9._-]*$", channel): + raise ValueError("The channel argument must be an ASCII " + "alphanumeric string. The period (.), underscore (_)" + "and hyphen (-) characters are allowed.") + self.key = key if timeout and (connect_timeout or read_timeout): - raise ValueError("Specify either timeout, or connect_timeout " + + raise ValueError("Specify either timeout, or connect_timeout " "and read_timeout") if connect_timeout and read_timeout: @@ -122,6 +138,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.client_id = client_id self.client_secret = client_secret + self.channel = channel self.retry_timeout = timedelta(seconds=retry_timeout) self.requests_kwargs = requests_kwargs or {} self.requests_kwargs.update({ @@ -268,6 +285,8 @@ def _generate_auth_url(self, path, params, accepts_clientid): params = params[:] # Take a copy. if accepts_clientid and self.client_id and self.client_secret: + if self.channel: + params.append(("channel", self.channel)) params.append(("client", self.client_id)) path = "?".join([path, urlencode_params(params)]) diff --git a/test/test_client.py b/test/test_client.py index 5489e547..5a5ce329 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -216,3 +216,32 @@ def __call__(self, req): client.geocode("Sesame St.") self.assertEqual(2, len(responses.calls)) + + def test_channel_with_api_key(self): + with self.assertRaises(ValueError): + client = googlemaps.Client(key="AIzaasdf", channel="mychannel") + + def test_invalid_channel(self): + with self.assertRaises(ValueError): + client = googlemaps.Client(client_id="foo", client_secret="a2V5", channel="auieauie$? ") + + @responses.activate + def test_channel_with_client_id(self): + responses.add(responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json") + + client = googlemaps.Client(client_id="foo", client_secret="a2V5", channel="MyChannel_1") + client.geocode("Sesame St.") + + self.assertEqual(1, len(responses.calls)) + + # Check ordering of parameters. + self.assertIn("address=Sesame+St.&channel=MyChannel_1&client=foo&signature", + responses.calls[0].request.url) + self.assertURLEqual("https://maps.googleapis.com/maps/api/geocode/json?" + "address=Sesame+St.&channel=MyChannel_1&client=foo&" + "signature=5s4Hw2AitGZlkipugXkjPuxhmME=", + responses.calls[0].request.url) From 8f1c47ee3b8ab5870a45d0f3dba9b22459107bc7 Mon Sep 17 00:00:00 2001 From: rbouchez Date: Sun, 22 May 2016 13:01:42 +0900 Subject: [PATCH 049/260] [Fix] Changed the requirement from "no API key" to "has client ID" for the channel argument. --- googlemaps/client.py | 8 ++++---- test/test_client.py | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index b812d7e5..fe7f5bf0 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -66,7 +66,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, :param channel: (for Maps API for Work customers) When set, a channel parameter with this value will be added to the requests. This can be used for tracking purpose. - Can not be used with a Maps API key. + Can only be used with a Maps API client ID. :type channel: str :param timeout: Combined connect and read timeout for HTTP requests, in @@ -112,9 +112,9 @@ def __init__(self, key=None, client_id=None, client_secret=None, raise ValueError("Invalid API key provided.") if channel: - if key: - raise ValueError("The channel argument can not be used with an " - "API key") + if not client_id: + raise ValueError("The channel argument must be used with a " + "client ID") if not re.match("^[a-zA-Z0-9._-]*$", channel): raise ValueError("The channel argument must be an ASCII " "alphanumeric string. The period (.), underscore (_)" diff --git a/test/test_client.py b/test/test_client.py index 5a5ce329..c880ba7d 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -217,13 +217,14 @@ def __call__(self, req): self.assertEqual(2, len(responses.calls)) - def test_channel_with_api_key(self): + def test_channel_without_client_id(self): with self.assertRaises(ValueError): client = googlemaps.Client(key="AIzaasdf", channel="mychannel") def test_invalid_channel(self): with self.assertRaises(ValueError): - client = googlemaps.Client(client_id="foo", client_secret="a2V5", channel="auieauie$? ") + client = googlemaps.Client(client_id="foo", client_secret="a2V5", + channel="auieauie$? ") @responses.activate def test_channel_with_client_id(self): @@ -233,7 +234,8 @@ def test_channel_with_client_id(self): status=200, content_type="application/json") - client = googlemaps.Client(client_id="foo", client_secret="a2V5", channel="MyChannel_1") + client = googlemaps.Client(key="AIzaasdf", client_id="foo", client_secret="a2V5", + channel="MyChannel_1") client.geocode("Sesame St.") self.assertEqual(1, len(responses.calls)) From 5479484e7e2f4da759a8488e1b427ec008975b06 Mon Sep 17 00:00:00 2001 From: Ivan Skorokhodov Date: Tue, 24 May 2016 12:53:41 +0300 Subject: [PATCH 050/260] Fixed broken links in documentation Google has changed (or it was originally broken) links to their place types from https://developers.google.com/places/supported_type to https://developers.google.com/places/supported_types (Now it is with the letter "s" on the end) --- googlemaps/places.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 52f19809..38f2c389 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -55,7 +55,7 @@ def places(client, query, location=None, radius=None, language=None, :param type: Restricts the results to places matching the specified type. The full list of supported types is available here: - https://developers.google.com/places/supported_type + https://developers.google.com/places/supported_types :type type: string :param page_token: Token from a previous search that when provided will @@ -116,7 +116,7 @@ def places_nearby(client, location, radius=None, keyword=None, language=None, :param type: Restricts the results to places matching the specified type. The full list of supported types is available here: - https://developers.google.com/places/supported_type + https://developers.google.com/places/supported_types :type type: string :param page_token: Token from a previous search that when provided will @@ -179,7 +179,7 @@ def places_radar(client, location, radius, keyword=None, min_price=None, :param type: Restricts the results to places matching the specified type. The full list of supported types is available here: - https://developers.google.com/places/supported_type + https://developers.google.com/places/supported_types :type type: string :rtype: result dict with the following keys: From 2b6f433af63c008bdb24a6b21ab7018a6db1915c Mon Sep 17 00:00:00 2001 From: rbouchez Date: Wed, 25 May 2016 16:51:56 +0900 Subject: [PATCH 051/260] Fix on googlemaps.Client: the 'channel' argument can now be given whenever there is a client_id (instead of only when there is no API key). --- googlemaps/client.py | 2 +- test/test_client.py | 40 +++++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index fe7f5bf0..3d0d9d68 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -114,7 +114,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, if channel: if not client_id: raise ValueError("The channel argument must be used with a " - "client ID") + "client ID") if not re.match("^[a-zA-Z0-9._-]*$", channel): raise ValueError("The channel argument must be an ASCII " "alphanumeric string. The period (.), underscore (_)" diff --git a/test/test_client.py b/test/test_client.py index c880ba7d..9e873718 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -222,28 +222,30 @@ def test_channel_without_client_id(self): client = googlemaps.Client(key="AIzaasdf", channel="mychannel") def test_invalid_channel(self): + # Cf. limitations here: + # https://developers.google.com/maps/premium/reports + # /usage-reports#channels with self.assertRaises(ValueError): client = googlemaps.Client(client_id="foo", client_secret="a2V5", channel="auieauie$? ") - @responses.activate - def test_channel_with_client_id(self): - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - body='{"status":"OK","results":[]}', - status=200, - content_type="application/json") - - client = googlemaps.Client(key="AIzaasdf", client_id="foo", client_secret="a2V5", + def test_auth_url_with_channel(self): + client = googlemaps.Client(key="AIzaasdf", + client_id="foo", + client_secret="a2V5", channel="MyChannel_1") - client.geocode("Sesame St.") - - self.assertEqual(1, len(responses.calls)) - # Check ordering of parameters. - self.assertIn("address=Sesame+St.&channel=MyChannel_1&client=foo&signature", - responses.calls[0].request.url) - self.assertURLEqual("https://maps.googleapis.com/maps/api/geocode/json?" - "address=Sesame+St.&channel=MyChannel_1&client=foo&" - "signature=5s4Hw2AitGZlkipugXkjPuxhmME=", - responses.calls[0].request.url) + # Check ordering of parameters + signature. + auth_url = client._generate_auth_url("/test", + {"param": "param"}, + accepts_clientid=True) + self.assertEqual(auth_url, "/test?param=param" + "&channel=MyChannel_1" + "&client=foo" + "&signature=OH18GuQto_mEpxj99UimKskvo4k=") + + # Check if added to requests to API with accepts_clientid=False + auth_url = client._generate_auth_url("/test", + {"param": "param"}, + accepts_clientid=False) + self.assertEqual(auth_url, "/test?param=param&key=AIzaasdf") From 50baefbc937d98378fdcede2b2827ed8cd4218fd Mon Sep 17 00:00:00 2001 From: rbouchez Date: Thu, 9 Jun 2016 15:55:11 +0900 Subject: [PATCH 052/260] Bump requests max version requirement to 2.10.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cf303fb4..a4cea497 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ requirements = [ - 'requests<=2.9.1', + 'requests<=2.10.0', ] setup(name='googlemaps', From 0cacef1792b1712d8027209ef88a5a9df40e0d82 Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Tue, 14 Jun 2016 15:58:56 +0200 Subject: [PATCH 053/260] Fixed bug where requests version was incorrectly interpreted. * Also adds test for the future. --- googlemaps/client.py | 2 +- test/test_client.py | 39 +++++++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 3d0d9d68..b9452034 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -129,7 +129,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, if connect_timeout and read_timeout: # Check that the version of requests is >= 2.4.0 chunks = requests.__version__.split(".") - if chunks[0] < 2 or (chunks[0] == 2 and chunks[1] < 4): + if int(chunks[0]) < 2 or (int(chunks[0]) == 2 and int(chunks[1]) < 4): raise NotImplementedError("Connect/Read timeouts require " "requests v2.4.0 or higher") self.timeout = (connect_timeout, read_timeout) diff --git a/test/test_client.py b/test/test_client.py index 9e873718..725327b5 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -24,6 +24,7 @@ import googlemaps from googlemaps import client as _client import test as _test +import requests class ClientTest(_test.TestCase): @@ -226,18 +227,18 @@ def test_invalid_channel(self): # https://developers.google.com/maps/premium/reports # /usage-reports#channels with self.assertRaises(ValueError): - client = googlemaps.Client(client_id="foo", client_secret="a2V5", + client = googlemaps.Client(client_id="foo", client_secret="a2V5", channel="auieauie$? ") def test_auth_url_with_channel(self): - client = googlemaps.Client(key="AIzaasdf", - client_id="foo", - client_secret="a2V5", + client = googlemaps.Client(key="AIzaasdf", + client_id="foo", + client_secret="a2V5", channel="MyChannel_1") # Check ordering of parameters + signature. - auth_url = client._generate_auth_url("/test", - {"param": "param"}, + auth_url = client._generate_auth_url("/test", + {"param": "param"}, accepts_clientid=True) self.assertEqual(auth_url, "/test?param=param" "&channel=MyChannel_1" @@ -245,7 +246,29 @@ def test_auth_url_with_channel(self): "&signature=OH18GuQto_mEpxj99UimKskvo4k=") # Check if added to requests to API with accepts_clientid=False - auth_url = client._generate_auth_url("/test", - {"param": "param"}, + auth_url = client._generate_auth_url("/test", + {"param": "param"}, accepts_clientid=False) self.assertEqual(auth_url, "/test?param=param&key=AIzaasdf") + + def test_requests_version(self): + client_args_timeout = { + "key": "AIzaasdf", + "client_id": "foo", + "client_secret": "a2V5", + "channel": "MyChannel_1", + "connect_timeout": 5, + "read_timeout": 5 + } + client_args = client_args_timeout.copy() + del client_args["connect_timeout"] + del client_args["read_timeout"] + + requests.__version__ = '2.3.0' + with self.assertRaises(NotImplementedError): + googlemaps.Client(**client_args_timeout) + googlemaps.Client(**client_args) + + requests.__version__ = '2.4.0' + googlemaps.Client(**client_args_timeout) + googlemaps.Client(**client_args) From 70d93b16905e9d315f140d86a8751437b20ae0c4 Mon Sep 17 00:00:00 2001 From: Zoltan Szalai Date: Thu, 16 Jun 2016 20:06:04 +0200 Subject: [PATCH 054/260] roads: handle {} response --- googlemaps/roads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/roads.py b/googlemaps/roads.py index da1850e5..7244f264 100644 --- a/googlemaps/roads.py +++ b/googlemaps/roads.py @@ -53,7 +53,7 @@ def snap_to_roads(client, path, interpolate=False): return client._get("/v1/snapToRoads", params, base_url=_ROADS_BASE_URL, accepts_clientid=False, - extract_body=_roads_extract)["snappedPoints"] + extract_body=_roads_extract).get("snappedPoints", []) def speed_limits(client, place_ids): From c28e73840d82b0914c02347de7113803428fff01 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 1 Jul 2016 09:55:54 +1000 Subject: [PATCH 055/260] Tag 2.4.4 Change-Id: I729b249a4d8bb2c4c59efc7504febc796bb03632 --- README.md | 2 +- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 478bf251..0a61abe4 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Note that you will need requests 2.4.0 or higher if you want to specify connect/ ## Developer Documentation -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.3/) +View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.4/) Additional documentation for the included web services is available at https://developers.google.com/maps/. diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 70d8fb05..8cc7bf1e 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.3-dev" +__version__ = "2.4.4" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index a4cea497..911f8787 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.3-dev', + version='2.4.4', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 8013de5d7c1b4867dcafb4449b97c1cebab33127 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 1 Jul 2016 10:18:09 +1000 Subject: [PATCH 056/260] Start next dev version (2.4.4-dev) Change-Id: I4e77b4802cdc6a535d8d106bff6db67f38028d30 --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 8cc7bf1e..2ce8e115 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.4" +__version__ = "2.4.4-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 911f8787..3a562c37 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.4', + version='2.4.4-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 674762a821ba81aaf409c795de7f9ac9aa8274cc Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 13 Jul 2016 10:31:25 +1000 Subject: [PATCH 057/260] Allow multiple values per type in components. Closes #138. Change-Id: Ia2e286fb97c41a3f555b6f68b7c243f1b6ed65aa --- googlemaps/convert.py | 12 ++++++++++-- test/test_convert.py | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/googlemaps/convert.py b/googlemaps/convert.py index 1c2264e4..6206cfa8 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -220,9 +220,17 @@ def components(arg): :rtype: basestring """ + + # Components may have multiple values per type, here we + # expand them into individual key/value items, eg: + # {"country": ["US", "AU"], "foo": 1} -> "country:AU", "country:US", "foo:1" + def expand(arg): + for k, v in arg.items(): + for item in as_list(v): + yield "%s:%s" % (k, item) + if isinstance(arg, dict): - arg = sorted(["%s:%s" % (k, arg[k]) for k in arg]) - return "|".join(arg) + return "|".join(sorted(expand(arg))) raise TypeError( "Expected a dict for components, " diff --git a/test/test_convert.py b/test/test_convert.py index 851eda14..090a95fc 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -91,6 +91,9 @@ def test_components(self): c = {"country": "US", "foo": 1} self.assertEqual("country:US|foo:1", convert.components(c)) + c = {"country": ["US", "AU"], "foo": 1} + self.assertEqual("country:AU|country:US|foo:1", convert.components(c)) + with self.assertRaises(TypeError): convert.components("test") From f50c04287550a8f59f7762730ad9dd5813a9b8b5 Mon Sep 17 00:00:00 2001 From: Sarah Maddox Date: Mon, 1 Aug 2016 15:55:24 +1000 Subject: [PATCH 058/260] Update README.md --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a61abe4..3449ab7e 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ https://developers.google.com/maps/. ## Usage -This example uses the [Geocoding API] and the [Directions API]. +This example uses the [Geocoding API] and the [Directions API] with an API key: ```python import googlemaps @@ -114,7 +114,30 @@ directions_result = gmaps.directions("Sydney Town Hall", departure_time=now) ``` +Below is the same example, using client ID and client secret (digital signature) +for authentication. This code assumes you have previously loaded the `client_id` +and `client_secret` variables with appropriate values. +For a guide on how to generate the `client_secret` (digital signature), see the +documentation for the API you're using. For example, see the guide for the +[Directions API][directions-client-id]. + +```python +gmaps = googlemaps.Client(client_id=client_id, client_secret=client_secret) + +# Geocoding and address +geocode_result = gmaps.geocode('1600 Amphitheatre Parkway, Mountain View, CA') + +# Look up an address with reverse geocoding +reverse_geocode_result = gmaps.reverse_geocode((40.714224, -73.961452)) + +# Request directions via public transit +now = datetime.now() +directions_result = gmaps.directions("Sydney Town Hall", + "Parramatta, NSW", + mode="transit", + departure_time=now) +``` For more usage examples, check out [the tests](test/). @@ -156,6 +179,7 @@ customers can use their [API key][apikey], too. [Google Maps API Web Services]: https://developers.google.com/maps/documentation/webservices/ [Directions API]: https://developers.google.com/maps/documentation/directions/ +[directions-client-id]: https://developers.google.com/maps/documentation/directions/get-api-key#client-id [Distance Matrix API]: https://developers.google.com/maps/documentation/distancematrix/ [Elevation API]: https://developers.google.com/maps/documentation/elevation/ [Geocoding API]: https://developers.google.com/maps/documentation/geocoding/ From d1d779e272e0fd1213a6861b93067e93152c2c0d Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 1 Aug 2016 16:18:16 +1000 Subject: [PATCH 059/260] Update instructions for API keys and client IDs. --- README.md | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3449ab7e..5c06c381 100644 --- a/README.md +++ b/README.md @@ -42,30 +42,31 @@ contribute, please read [How to Contribute][contrib]. ### API keys -Each Google Maps Web Service requires an API key or Client ID. API keys are -freely available with a Google Account at https://developers.google.com/console. -To generate a server key for your project: +Each Google Maps Web Service request requires an API key or client ID. API keys +are freely available with a Google Account at +https://developers.google.com/console. The type of API key you need is a +**Server key**. + +To get an API key: 1. Visit https://developers.google.com/console and log in with a Google Account. - 1. Select an existing project, or create a new project. - 1. Click **Enable an API**. - 1. Browse for the API, and set its status to "On". The Python Client for Google Maps Services + 1. Select one of your existing projects, or create a new project. + 1. Enable the API(s) you want to use. The Python Client for Google Maps Services accesses the following APIs: * Directions API * Distance Matrix API * Elevation API * Geocoding API - * Time Zone API + * Places API * Roads API - 1. Once you've enabled the APIs, click **Credentials** from the left navigation of the Developer - Console. - 1. In the "Public API access", click **Create new Key**. - 1. Choose **Server Key**. + * Time Zone API + 1. Create a new **Server key**. 1. If you'd like to restrict requests to a specific IP address, do so now. - 1. Click **Create**. - -Your API key should be 40 characters long, and begin with `AIza`. + +For guided help, follow the instructions for the [Directions API][directions-key]. +You only need one API key, but remember to enable all the APIs you need. +For even more information, see the guide to [API keys][apikey]. **Important:** This key should be kept secret on your server. @@ -148,10 +149,10 @@ For more usage examples, check out [the tests](test/). Automatically retry when intermittent failures occur. That is, when any of the retriable 5xx errors are returned from the API. -### Keys *and* Client IDs +### Client IDs -Maps API for Work customers can use their [client ID and secret][clientid] to authenticate. Free -customers can use their [API key][apikey], too. +Google Maps APIs Premium Plan customers can use their [client ID and secret][clientid] to authenticate, +instead of an API key. ## Building the Project @@ -179,6 +180,7 @@ customers can use their [API key][apikey], too. [Google Maps API Web Services]: https://developers.google.com/maps/documentation/webservices/ [Directions API]: https://developers.google.com/maps/documentation/directions/ +[directions-key]: https://developers.google.com/maps/documentation/directions/get-api-key#key [directions-client-id]: https://developers.google.com/maps/documentation/directions/get-api-key#client-id [Distance Matrix API]: https://developers.google.com/maps/documentation/distancematrix/ [Elevation API]: https://developers.google.com/maps/documentation/elevation/ From 4c0dd0f6099f9531b1e40ca1300640dcd9649bbd Mon Sep 17 00:00:00 2001 From: Chris Broadfoot Date: Sat, 13 Aug 2016 21:34:39 -0700 Subject: [PATCH 060/260] README: add travis badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5c06c381..397f642e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Python Client for Google Maps Services ==================================== +[![Build Status](https://travis-ci.org/googlemaps/google-maps-services-python.svg?branch=master)](https://travis-ci.org/googlemaps/google-maps-services-python) + ## Description Use Python? Want to [geocode][Geocoding API] something? Looking for [directions][Directions API]? From b9c68ad0bf4895d5bb492380e07031a1491f449e Mon Sep 17 00:00:00 2001 From: Andrew Naoum Date: Wed, 31 Aug 2016 07:49:00 +1000 Subject: [PATCH 061/260] Implement nearestRoads in the Google Maps Roads API --- googlemaps/client.py | 2 ++ googlemaps/roads.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/googlemaps/client.py b/googlemaps/client.py index b9452034..2445a1db 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -309,6 +309,7 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.geocoding import reverse_geocode from googlemaps.timezone import timezone from googlemaps.roads import snap_to_roads +from googlemaps.roads import nearest_roads from googlemaps.roads import speed_limits from googlemaps.roads import snapped_speed_limits from googlemaps.places import places @@ -327,6 +328,7 @@ def _generate_auth_url(self, path, params, accepts_clientid): Client.reverse_geocode = reverse_geocode Client.timezone = timezone Client.snap_to_roads = snap_to_roads +Client.nearest_roads = nearest_roads Client.speed_limits = speed_limits Client.snapped_speed_limits = snapped_speed_limits Client.places = places diff --git a/googlemaps/roads.py b/googlemaps/roads.py index 7244f264..1ab07534 100644 --- a/googlemaps/roads.py +++ b/googlemaps/roads.py @@ -55,6 +55,27 @@ def snap_to_roads(client, path, interpolate=False): accepts_clientid=False, extract_body=_roads_extract).get("snappedPoints", []) +def nearest_roads(client, points): + """Find the closest road segments for each point + + Takes up to 100 independent coordinates, and returns the closest road + segment for each point. The points passed do not need to be part of a + continuous path. + + :param points: The points for which the nearest road segments are to be + located. + :type points: a single location, or a list of locations, where a + location is a string, dict, list, or tuple + + :rtype: A list of snapped points. + """ + + params = {"points": convert.location_list(points)} + + return client._get("/v1/nearestRoads", params, + base_url=_ROADS_BASE_URL, + accepts_clientid=False, + extract_body=_roads_extract).get("snappedPoints", []) def speed_limits(client, place_ids): """Returns the posted speed limit (in km/h) for given road segments. From 2ccf0b2912019341aa60aeb65fc36ca6d9d02a56 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 1 Sep 2016 11:51:39 +1000 Subject: [PATCH 062/260] Add test for nearest roads. Change-Id: I4c20fe8acbd8ac23e74533b637042ea48765499a --- test/test_roads.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_roads.py b/test/test_roads.py index 62b379f2..2d0b9c65 100644 --- a/test/test_roads.py +++ b/test/test_roads.py @@ -45,6 +45,22 @@ def test_snap(self): "path=40.714728%%2C-73.998672&key=%s" % self.key, responses.calls[0].request.url) + @responses.activate + def test_nearest_roads(self): + responses.add(responses.GET, + "https://roads.googleapis.com/v1/nearestRoads", + body='{"snappedPoints":["foo"]}', + status=200, + content_type="application/json") + + results = self.client.nearest_roads((40.714728, -73.998672)) + self.assertEqual("foo", results[0]) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual("https://roads.googleapis.com/v1/nearestRoads?" + "points=40.714728%%2C-73.998672&key=%s" % self.key, + responses.calls[0].request.url) + @responses.activate def test_path(self): responses.add(responses.GET, From 59572ad589fd42035c6b9c555289cda32314e512 Mon Sep 17 00:00:00 2001 From: Tim Sampson Date: Wed, 26 Oct 2016 14:27:44 +0300 Subject: [PATCH 063/260] bump requests requirement to 2.11.1. Fixes #151 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3a562c37..50d6ca4f 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ requirements = [ - 'requests<=2.10.0', + 'requests<=2.11.1', ] setup(name='googlemaps', From 32d07dfa75a9d8be791670c64d67a007728e29c7 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 30 Nov 2016 16:02:30 +1100 Subject: [PATCH 064/260] Provide a way to add extra params to any request. Change-Id: I123b0a6f4044d732cd2721539f4ec1fec5b34f37 --- googlemaps/client.py | 60 +++++++++++++++++++++++++++++--------------- test/test_client.py | 16 ++++++++++++ tox.ini | 2 +- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 2445a1db..dd4c9a0b 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -24,6 +24,7 @@ import collections from datetime import datetime from datetime import timedelta +import functools import hashlib import hmac import re @@ -279,10 +280,11 @@ def _generate_auth_url(self, path, params, accepts_clientid): """ # Deterministic ordering through sorting by key. # Useful for tests, and in the future, any caching. + extra_params = getattr(self, "_extra_params", None) or {} if type(params) is dict: - params = sorted(params.items()) + params = sorted(dict(extra_params, **params).items()) else: - params = params[:] # Take a copy. + params = sorted(extra_params.items()) + params[:] # Take a copy. if accepts_clientid and self.client_id and self.client_secret: if self.channel: @@ -320,24 +322,42 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.places import places_autocomplete from googlemaps.places import places_autocomplete_query -Client.directions = directions -Client.distance_matrix = distance_matrix -Client.elevation = elevation -Client.elevation_along_path = elevation_along_path -Client.geocode = geocode -Client.reverse_geocode = reverse_geocode -Client.timezone = timezone -Client.snap_to_roads = snap_to_roads -Client.nearest_roads = nearest_roads -Client.speed_limits = speed_limits -Client.snapped_speed_limits = snapped_speed_limits -Client.places = places -Client.places_nearby = places_nearby -Client.places_radar = places_radar -Client.place = place -Client.places_photo = places_photo -Client.places_autocomplete = places_autocomplete -Client.places_autocomplete_query = places_autocomplete_query + +def make_api_method(func): + """ + Provides a single entry point for modifying all API methods. + For now this is limited to allowing the client object to be modified + with an `extra_params` keyword arg to each method, that is then used + as the params for each web service request. Please note that this is + an unsupported feature for advanced use only. + """ + @functools.wraps(func) + def wrapper(*args, **kwargs): + args[0]._extra_params = kwargs.pop("extra_params", None) + result = func(*args, **kwargs) + del args[0]._extra_params + return result + return wrapper + + +Client.directions = make_api_method(directions) +Client.distance_matrix = make_api_method(distance_matrix) +Client.elevation = make_api_method(elevation) +Client.elevation_along_path = make_api_method(elevation_along_path) +Client.geocode = make_api_method(geocode) +Client.reverse_geocode = make_api_method(reverse_geocode) +Client.timezone = make_api_method(timezone) +Client.snap_to_roads = make_api_method(snap_to_roads) +Client.nearest_roads = make_api_method(nearest_roads) +Client.speed_limits = make_api_method(speed_limits) +Client.snapped_speed_limits = make_api_method(snapped_speed_limits) +Client.places = make_api_method(places) +Client.places_nearby = make_api_method(places_nearby) +Client.places_radar = make_api_method(places_radar) +Client.place = make_api_method(place) +Client.places_photo = make_api_method(places_photo) +Client.places_autocomplete = make_api_method(places_autocomplete) +Client.places_autocomplete_query = make_api_method(places_autocomplete_query) def sign_hmac(secret, payload): diff --git a/test/test_client.py b/test/test_client.py index 725327b5..f2d424a4 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -81,6 +81,22 @@ def test_key_sent(self): "key=AIzaasdf&address=Sesame+St.", responses.calls[0].request.url) + @responses.activate + def test_extra_params(self): + responses.add(responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json") + + client = googlemaps.Client(key="AIzaasdf") + client.geocode("Sesame St.", extra_params={"foo": "bar"}) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual("https://maps.googleapis.com/maps/api/geocode/json?" + "key=AIzaasdf&address=Sesame+St.&foo=bar", + responses.calls[0].request.url) + def test_hmac(self): """ From http://en.wikipedia.org/wiki/Hash-based_message_authentication_code diff --git a/tox.ini b/tox.ini index ff3a0f44..33ea7416 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py27,py32,py34,docs + py27,py32,py34,py35,docs [testenv] commands = From 77b9baa150542f9faa7851dd3abfb26d2e276488 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 1 Dec 2016 11:20:57 +1100 Subject: [PATCH 065/260] Version 2.4.5 Change-Id: I2742b74aaef8dd4883e90243f932ac0050ddbb9b --- README.md | 10 +++++----- googlemaps/__init__.py | 2 +- setup.py | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 397f642e..f34ec8a7 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ contribute, please read [How to Contribute][contrib]. Each Google Maps Web Service request requires an API key or client ID. API keys are freely available with a Google Account at -https://developers.google.com/console. The type of API key you need is a -**Server key**. +https://developers.google.com/console. The type of API key you need is a +**Server key**. To get an API key: @@ -65,8 +65,8 @@ To get an API key: * Time Zone API 1. Create a new **Server key**. 1. If you'd like to restrict requests to a specific IP address, do so now. - -For guided help, follow the instructions for the [Directions API][directions-key]. + +For guided help, follow the instructions for the [Directions API][directions-key]. You only need one API key, but remember to enable all the APIs you need. For even more information, see the guide to [API keys][apikey]. @@ -80,7 +80,7 @@ Note that you will need requests 2.4.0 or higher if you want to specify connect/ ## Developer Documentation -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.4/) +View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.5/) Additional documentation for the included web services is available at https://developers.google.com/maps/. diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 2ce8e115..faabbe57 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.4-dev" +__version__ = "2.4.5" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 50d6ca4f..22097058 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.4-dev', + version='2.4.5', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', @@ -34,6 +34,7 @@ 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Internet', ] ) From ee43a420b085d7baa24227e0d86dddf9f4210d35 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 1 Dec 2016 11:42:11 +1100 Subject: [PATCH 066/260] Version 2.4.5-dev Change-Id: I56a497c39d6585533aa12ebb0ef4370953747110 --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index faabbe57..f7013b11 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.5" +__version__ = "2.4.5-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 22097058..2b86159e 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.5', + version='2.4.5-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 6d57a5080f8c147bfb6c26cdd25af38f31cbb201 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 5 Jan 2017 10:15:42 +1100 Subject: [PATCH 067/260] Update .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f003fdf3..1efd5c74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,4 +11,5 @@ script: env: - TOXENV=py27 - TOXENV=py34 + - TOXENV=py35 - TOXENV=docs From f779f5327fee9f8b0c26a44cd5418c84155fd861 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 6 Jan 2017 15:47:55 +1100 Subject: [PATCH 068/260] Fix issue with extra_params + threading. Closes #160. Change-Id: Ic9bd0acc8c394bd734bd13b7d10263b2f63915a3 --- googlemaps/client.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index dd4c9a0b..f1cd1296 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -328,14 +328,19 @@ def make_api_method(func): Provides a single entry point for modifying all API methods. For now this is limited to allowing the client object to be modified with an `extra_params` keyword arg to each method, that is then used - as the params for each web service request. Please note that this is - an unsupported feature for advanced use only. + as the params for each web service request. + + Please note that this is an unsupported feature for advanced use only. + It's also currently incompatibile with multiple threads, see GH #160. """ @functools.wraps(func) def wrapper(*args, **kwargs): args[0]._extra_params = kwargs.pop("extra_params", None) result = func(*args, **kwargs) - del args[0]._extra_params + try: + del args[0]._extra_params + except AttributeError: + pass return result return wrapper From 15db2f982601682c13341ee24f8f6491a18f36f8 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 6 Feb 2017 15:19:13 +1100 Subject: [PATCH 069/260] Support Python 3.6 Change-Id: I325c20bfa53679da3f717bb6c6429bbbf39489e9 --- .travis.yml | 1 + setup.py | 1 + tox.ini | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1efd5c74..0c49ceeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,5 @@ env: - TOXENV=py27 - TOXENV=py34 - TOXENV=py35 + - TOXENV=py36 - TOXENV=docs diff --git a/setup.py b/setup.py index 2b86159e..45d2eed0 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Internet', ] ) diff --git a/tox.ini b/tox.ini index 33ea7416..67b23a7f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py27,py32,py34,py35,docs + py27,py32,py34,py35,py36,docs [testenv] commands = From 61901d14bcf217d9cb546f79560ac70d45885eb2 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 6 Feb 2017 15:19:47 +1100 Subject: [PATCH 070/260] Support POST requests in client. Change-Id: I03e50b82ab1849dfcf63d6b71fdd01888f85576d --- googlemaps/client.py | 51 ++++++++++++++++++++++------------- googlemaps/directions.py | 2 +- googlemaps/distance_matrix.py | 2 +- googlemaps/elevation.py | 4 +-- googlemaps/geocoding.py | 6 ++--- googlemaps/places.py | 8 +++--- googlemaps/roads.py | 8 +++--- googlemaps/timezone.py | 2 +- 8 files changed, 49 insertions(+), 34 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index f1cd1296..1b21bb88 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -151,10 +151,10 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.queries_per_second = queries_per_second self.sent_times = collections.deque("", queries_per_second) - def _get(self, url, params, first_request_time=None, retry_counter=0, + def _request(self, url, params, first_request_time=None, retry_counter=0, base_url=_DEFAULT_BASE_URL, accepts_clientid=True, - extract_body=None, requests_kwargs=None): - """Performs HTTP GET request with credentials, returning the body as + extract_body=None, requests_kwargs=None, post_body=None): + """Performs HTTP GET/POST with credentials, returning the body as JSON. :param url: URL path for the request. Should begin with a slash. @@ -214,21 +214,32 @@ def _get(self, url, params, first_request_time=None, retry_counter=0, # Default to the client-level self.requests_kwargs, with method-level # requests_kwargs arg overriding. - requests_kwargs = dict(self.requests_kwargs, **(requests_kwargs or {})) + requests_kwargs = requests_kwargs or {} + final_requests_kwargs = dict(self.requests_kwargs, **requests_kwargs) + + # Determine GET/POST. + requests_method = requests.get + if post_body is not None: + requests_method = requests.post + final_requests_kwargs["data"] = post_body + try: - resp = requests.get(base_url + authed_url, **requests_kwargs) + response = requests_method(base_url + authed_url, + **final_requests_kwargs) except requests.exceptions.Timeout: raise googlemaps.exceptions.Timeout() except Exception as e: raise googlemaps.exceptions.TransportError(e) - if resp.status_code in _RETRIABLE_STATUSES: + if response.status_code in _RETRIABLE_STATUSES: # Retry request. - return self._get(url, params, first_request_time, retry_counter + 1, - base_url, accepts_clientid, extract_body) + return self._request(url, params, first_request_time, + retry_counter + 1, base_url, accepts_clientid, + extract_body, requests_kwargs, post_body) - # Check if the time of the nth previous query (where n is queries_per_second) - # is under a second ago - if so, sleep for the difference. + # Check if the time of the nth previous query (where n is + # queries_per_second) is under a second ago - if so, sleep for + # the difference. if self.sent_times and len(self.sent_times) == self.queries_per_second: elapsed_since_earliest = time.time() - self.sent_times[0] if elapsed_since_earliest < 1: @@ -236,21 +247,25 @@ def _get(self, url, params, first_request_time=None, retry_counter=0, try: if extract_body: - result = extract_body(resp) + result = extract_body(response) else: - result = self._get_body(resp) + result = self._get_body(response) self.sent_times.append(time.time()) return result except googlemaps.exceptions._RetriableRequest: # Retry request. - return self._get(url, params, first_request_time, retry_counter + 1, - base_url, accepts_clientid, extract_body) + return self._request(url, params, first_request_time, + retry_counter + 1, base_url, accepts_clientid, + extract_body, requests_kwargs, post_body) + + def _get(self, *args, **kwargs): + return self._request(*args, **kwargs) - def _get_body(self, resp): - if resp.status_code != 200: - raise googlemaps.exceptions.HTTPError(resp.status_code) + def _get_body(self, response): + if response.status_code != 200: + raise googlemaps.exceptions.HTTPError(response.status_code) - body = resp.json() + body = response.json() api_status = body["status"] if api_status == "OK" or api_status == "ZERO_RESULTS": diff --git a/googlemaps/directions.py b/googlemaps/directions.py index cb939109..236dc96a 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -147,4 +147,4 @@ def directions(client, origin, destination, if traffic_model: params["traffic_model"] = traffic_model - return client._get("/maps/api/directions/json", params)["routes"] + return client._request("/maps/api/directions/json", params)["routes"] diff --git a/googlemaps/distance_matrix.py b/googlemaps/distance_matrix.py index 89fb23a1..6c1f0626 100644 --- a/googlemaps/distance_matrix.py +++ b/googlemaps/distance_matrix.py @@ -127,4 +127,4 @@ def distance_matrix(client, origins, destinations, if traffic_model: params["traffic_model"] = traffic_model - return client._get("/maps/api/distancematrix/json", params) + return client._request("/maps/api/distancematrix/json", params) diff --git a/googlemaps/elevation.py b/googlemaps/elevation.py index 4b286f06..4546679c 100644 --- a/googlemaps/elevation.py +++ b/googlemaps/elevation.py @@ -34,7 +34,7 @@ def elevation(client, locations): :rtype: list of elevation data responses """ params = {"locations": convert.shortest_path(locations)} - return client._get("/maps/api/elevation/json", params)["results"] + return client._request("/maps/api/elevation/json", params)["results"] def elevation_along_path(client, path, samples): @@ -62,4 +62,4 @@ def elevation_along_path(client, path, samples): "samples": samples } - return client._get("/maps/api/elevation/json", params)["results"] + return client._request("/maps/api/elevation/json", params)["results"] diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index d0a789a8..99ceeeff 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -65,7 +65,7 @@ def geocode(client, address=None, components=None, bounds=None, region=None, if language: params["language"] = language - return client._get("/maps/api/geocode/json", params)["results"] + return client._request("/maps/api/geocode/json", params)["results"] def reverse_geocode(client, latlng, result_type=None, location_type=None, @@ -77,7 +77,7 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :param latlng: The latitude/longitude value or place_id for which you wish to obtain the closest, human-readable address. :type latlng: string, dict, list, or tuple - + :param result_type: One or more address types to restrict results to. :type result_type: string or list of strings @@ -106,4 +106,4 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, if language: params["language"] = language - return client._get("/maps/api/geocode/json", params)["results"] + return client._request("/maps/api/geocode/json", params)["results"] diff --git a/googlemaps/places.py b/googlemaps/places.py index 38f2c389..a40f06c6 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -228,7 +228,7 @@ def _places(client, url_part, query=None, location=None, radius=None, params["pagetoken"] = page_token url = "/maps/api/place/%ssearch/json" % url_part - return client._get(url, params) + return client._request(url, params) def place(client, place_id, language=None): @@ -249,7 +249,7 @@ def place(client, place_id, language=None): params = {"placeid": place_id} if language: params["language"] = language - return client._get("/maps/api/place/details/json", params) + return client._request("/maps/api/place/details/json", params) def places_photo(client, photo_reference, max_width=None, max_height=None): @@ -291,7 +291,7 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): # "extract_body" and "stream" args here are used to return an iterable # response containing the image file data, rather than converting from # json. - response = client._get("/maps/api/place/photo", params, + response = client._request("/maps/api/place/photo", params, extract_body=lambda response: response, requests_kwargs={"stream": True}) return response.iter_content() @@ -394,4 +394,4 @@ def _autocomplete(client, url_part, input_text, offset=None, location=None, params["components"] = convert.components(components) url = "/maps/api/place/%sautocomplete/json" % url_part - return client._get(url, params)["predictions"] + return client._request(url, params)["predictions"] diff --git a/googlemaps/roads.py b/googlemaps/roads.py index 1ab07534..120cc245 100644 --- a/googlemaps/roads.py +++ b/googlemaps/roads.py @@ -50,7 +50,7 @@ def snap_to_roads(client, path, interpolate=False): if interpolate: params["interpolate"] = "true" - return client._get("/v1/snapToRoads", params, + return client._request("/v1/snapToRoads", params, base_url=_ROADS_BASE_URL, accepts_clientid=False, extract_body=_roads_extract).get("snappedPoints", []) @@ -72,7 +72,7 @@ def nearest_roads(client, points): params = {"points": convert.location_list(points)} - return client._get("/v1/nearestRoads", params, + return client._request("/v1/nearestRoads", params, base_url=_ROADS_BASE_URL, accepts_clientid=False, extract_body=_roads_extract).get("snappedPoints", []) @@ -89,7 +89,7 @@ def speed_limits(client, place_ids): params = [("placeId", place_id) for place_id in convert.as_list(place_ids)] - return client._get("/v1/speedLimits", params, + return client._request("/v1/speedLimits", params, base_url=_ROADS_BASE_URL, accepts_clientid=False, extract_body=_roads_extract)["speedLimits"] @@ -110,7 +110,7 @@ def snapped_speed_limits(client, path): params = {"path": convert.location_list(path)} - return client._get("/v1/speedLimits", params, + return client._request("/v1/speedLimits", params, base_url=_ROADS_BASE_URL, accepts_clientid=False, extract_body=_roads_extract) diff --git a/googlemaps/timezone.py b/googlemaps/timezone.py index 339c5e6b..0b6370dc 100644 --- a/googlemaps/timezone.py +++ b/googlemaps/timezone.py @@ -51,4 +51,4 @@ def timezone(client, location, timestamp=None, language=None): if language: params["language"] = language - return client._get( "/maps/api/timezone/json", params) + return client._request( "/maps/api/timezone/json", params) From d7f52da7146d0149fea8ef7c5905f4f300dbd2bb Mon Sep 17 00:00:00 2001 From: Cris Bettis Date: Thu, 9 Feb 2017 20:14:39 -0500 Subject: [PATCH 071/260] Allow a requests.Session object to be passed into Client This will allow the client to re-use an existing session and not have re-establish the connection over subsequent calls. --- googlemaps/client.py | 11 +++++++++-- test/test_client.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index f1cd1296..2874f649 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -47,11 +47,12 @@ class Client(object): """Performs requests to the Google Maps API web services.""" + session = requests.Session() 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=10, channel=None): + queries_per_second=10, channel=None, requests_session=None): """ :param key: Maps API key. Required, unless "client_id" and "client_secret" are set. @@ -104,6 +105,9 @@ def __init__(self, key=None, client_id=None, client_secret=None, http://docs.python-requests.org/en/latest/api/#main-interface :type requests_kwargs: dict + :param requests_session: Re-usable requests.session object for re-using connections + :type requests_session: request.Session + """ if not key and not (client_secret and client_id): raise ValueError("Must provide API key or enterprise credentials " @@ -112,6 +116,9 @@ def __init__(self, key=None, client_id=None, client_secret=None, if key and not key.startswith("AIza"): raise ValueError("Invalid API key provided.") + if requests_session is not None: + self.session = requests_session + if channel: if not client_id: raise ValueError("The channel argument must be used with a " @@ -216,7 +223,7 @@ def _get(self, url, params, first_request_time=None, retry_counter=0, # requests_kwargs arg overriding. requests_kwargs = dict(self.requests_kwargs, **(requests_kwargs or {})) try: - resp = requests.get(base_url + authed_url, **requests_kwargs) + resp = self.session.get(base_url + authed_url, **requests_kwargs) except requests.exceptions.Timeout: raise googlemaps.exceptions.Timeout() except Exception as e: diff --git a/test/test_client.py b/test/test_client.py index f2d424a4..df6a70d5 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -43,6 +43,20 @@ def test_urlencode(self): encoded_params = _client.urlencode_params([("address", "=Sydney ~")]) self.assertEqual("address=%3DSydney+~", encoded_params) + @responses.activate + def test_query_with_session(self): + session = requests.Session() + responses.add(responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json") + client = googlemaps.Client(key="AIzaasdf", + queries_per_second=3, + requests_session=session) + client.geocode("Sesame St.") + + @responses.activate def test_queries_per_second(self): # This test assumes that the time to run a mocked query is From 573cc880c9e74d072312b61584ae473d7dfd5e0b Mon Sep 17 00:00:00 2001 From: Cris Bettis Date: Thu, 9 Feb 2017 22:05:52 -0500 Subject: [PATCH 072/260] fixup! Allow a requests.Session object to be passed into Client --- googlemaps/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 2874f649..0d1f17d8 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -47,7 +47,6 @@ class Client(object): """Performs requests to the Google Maps API web services.""" - session = requests.Session() def __init__(self, key=None, client_id=None, client_secret=None, timeout=None, connect_timeout=None, read_timeout=None, @@ -116,7 +115,9 @@ def __init__(self, key=None, client_id=None, client_secret=None, if key and not key.startswith("AIza"): raise ValueError("Invalid API key provided.") - if requests_session is not None: + if requests_session is None: + self.session = requests.Session() + else: self.session = requests_session if channel: From c778419c779c398a3347fe49d8f4199fd8901b5e Mon Sep 17 00:00:00 2001 From: Cris Bettis Date: Thu, 9 Feb 2017 22:11:04 -0500 Subject: [PATCH 073/260] fixup! Allow a requests.Session object to be passed into Client --- googlemaps/client.py | 11 ++--------- test/test_client.py | 4 ++-- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 0d1f17d8..26bd17b2 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -51,7 +51,7 @@ class Client(object): 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=10, channel=None, requests_session=None): + queries_per_second=10, channel=None): """ :param key: Maps API key. Required, unless "client_id" and "client_secret" are set. @@ -104,9 +104,6 @@ def __init__(self, key=None, client_id=None, client_secret=None, http://docs.python-requests.org/en/latest/api/#main-interface :type requests_kwargs: dict - :param requests_session: Re-usable requests.session object for re-using connections - :type requests_session: request.Session - """ if not key and not (client_secret and client_id): raise ValueError("Must provide API key or enterprise credentials " @@ -115,11 +112,6 @@ def __init__(self, key=None, client_id=None, client_secret=None, if key and not key.startswith("AIza"): raise ValueError("Invalid API key provided.") - if requests_session is None: - self.session = requests.Session() - else: - self.session = requests_session - if channel: if not client_id: raise ValueError("The channel argument must be used with a " @@ -129,6 +121,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, "alphanumeric string. The period (.), underscore (_)" "and hyphen (-) characters are allowed.") + self.session = requests.Session() self.key = key if timeout and (connect_timeout or read_timeout): diff --git a/test/test_client.py b/test/test_client.py index df6a70d5..41b90b26 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -52,8 +52,8 @@ def test_query_with_session(self): status=200, content_type="application/json") client = googlemaps.Client(key="AIzaasdf", - queries_per_second=3, - requests_session=session) + queries_per_second=3) + client.session = session client.geocode("Sesame St.") From 25940c4c165f7cdd1ebabdc0c4337742f6354298 Mon Sep 17 00:00:00 2001 From: Cris Bettis Date: Fri, 10 Feb 2017 07:53:49 -0500 Subject: [PATCH 074/260] fixup! Allow a requests.Session object to be passed into Client --- test/test_client.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/test_client.py b/test/test_client.py index 41b90b26..f2d424a4 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -43,20 +43,6 @@ def test_urlencode(self): encoded_params = _client.urlencode_params([("address", "=Sydney ~")]) self.assertEqual("address=%3DSydney+~", encoded_params) - @responses.activate - def test_query_with_session(self): - session = requests.Session() - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - body='{"status":"OK","results":[]}', - status=200, - content_type="application/json") - client = googlemaps.Client(key="AIzaasdf", - queries_per_second=3) - client.session = session - client.geocode("Sesame St.") - - @responses.activate def test_queries_per_second(self): # This test assumes that the time to run a mocked query is From 904f1b460297cb857ba15e1abe88ef293c5bcd92 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 14 Feb 2017 15:40:23 +1100 Subject: [PATCH 075/260] Added geolocation API Change-Id: I20f01473ddc741fad713d471469c49e14aaf9f49 --- googlemaps/client.py | 14 ++--- googlemaps/geolocation.py | 105 ++++++++++++++++++++++++++++++++++++++ test/test_geolocation.py | 44 ++++++++++++++++ 3 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 googlemaps/geolocation.py create mode 100644 test/test_geolocation.py diff --git a/googlemaps/client.py b/googlemaps/client.py index 1b21bb88..da6db3d8 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -153,7 +153,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, def _request(self, url, params, first_request_time=None, retry_counter=0, base_url=_DEFAULT_BASE_URL, accepts_clientid=True, - extract_body=None, requests_kwargs=None, post_body=None): + extract_body=None, requests_kwargs=None, post_json=None): """Performs HTTP GET/POST with credentials, returning the body as JSON. @@ -219,9 +219,9 @@ def _request(self, url, params, first_request_time=None, retry_counter=0, # Determine GET/POST. requests_method = requests.get - if post_body is not None: + if post_json is not None: requests_method = requests.post - final_requests_kwargs["data"] = post_body + final_requests_kwargs["json"] = post_json try: response = requests_method(base_url + authed_url, @@ -235,7 +235,7 @@ def _request(self, url, params, first_request_time=None, retry_counter=0, # Retry request. return self._request(url, params, first_request_time, retry_counter + 1, base_url, accepts_clientid, - extract_body, requests_kwargs, post_body) + extract_body, requests_kwargs, post_json) # Check if the time of the nth previous query (where n is # queries_per_second) is under a second ago - if so, sleep for @@ -256,9 +256,9 @@ def _request(self, url, params, first_request_time=None, retry_counter=0, # Retry request. return self._request(url, params, first_request_time, retry_counter + 1, base_url, accepts_clientid, - extract_body, requests_kwargs, post_body) + extract_body, requests_kwargs, post_json) - def _get(self, *args, **kwargs): + def _get(self, *args, **kwargs): # Backwards compatibility. return self._request(*args, **kwargs) def _get_body(self, response): @@ -324,6 +324,7 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.elevation import elevation_along_path from googlemaps.geocoding import geocode from googlemaps.geocoding import reverse_geocode +from googlemaps.geolocation import geolocate from googlemaps.timezone import timezone from googlemaps.roads import snap_to_roads from googlemaps.roads import nearest_roads @@ -366,6 +367,7 @@ def wrapper(*args, **kwargs): Client.elevation_along_path = make_api_method(elevation_along_path) Client.geocode = make_api_method(geocode) Client.reverse_geocode = make_api_method(reverse_geocode) +Client.geolocate = make_api_method(geolocate) Client.timezone = make_api_method(timezone) Client.snap_to_roads = make_api_method(snap_to_roads) Client.nearest_roads = make_api_method(nearest_roads) diff --git a/googlemaps/geolocation.py b/googlemaps/geolocation.py new file mode 100644 index 00000000..9cbfb6b5 --- /dev/null +++ b/googlemaps/geolocation.py @@ -0,0 +1,105 @@ +# +# Copyright 2017 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Performs requests to the Google Maps Geolocation API.""" +from googlemaps import exceptions + + +_GEOLOCATION_BASE_URL = "https://www.googleapis.com" + + +def _geolocation_extract(response): + """ + Mimics the exception handling logic in ``client._get_body``, but + for geolocation which uses a different response format. + """ + body = response.json() + if response.status_code in (200, 404): + return body + elif response.status_code == 403: + raise exceptions._RetriableRequest() + else: + try: + error = body["error"]["errors"][0]["reason"] + except KeyError: + error = None + raise exceptions.ApiError(response.status_code, error) + + +def geolocate(client, home_mobile_country_code=None, + home_mobile_network_code=None, radio_type=None, carrier=None, + consider_ip=None, cell_towers=None, wifi_access_points=None): + """ + The Google Maps Geolocation API returns a location and accuracy + radius based on information about cell towers and WiFi nodes given. + + See https://developers.google.com/maps/documentation/geolocation/intro + for more info, including more detail for each parameter below. + + :param home_mobile_country_code: The mobile country code (MCC) for + the device's home network. + :type home_mobile_country_code: string + + :param home_mobile_network_code: The mobile network code (MCC) for + the device's home network. + :type home_mobile_network_code: string + + :param radio_type: The mobile radio type. Supported values are + lte, gsm, cdma, and wcdma. While this field is optional, it + should be included if a value is available, for more accurate + results. + :type radio_type: string + + :param carrier: The carrier name. + :type carrier: string + + :param consider_ip: Specifies whether to fall back to IP geolocation + if wifi and cell tower signals are not available. Note that the + IP address in the request header may not be the IP of the device. + :type consider_ip: bool + + :param cell_towers: A list of cell tower dicts. See + https://developers.google.com/maps/documentation/geolocation/intro#cell_tower_object + for more detail. + :type cell_towers: list of dicts + + :param wifi_access_points: A list of WiFi access point dicts. See + https://developers.google.com/maps/documentation/geolocation/intro#wifi_access_point_object + for more detail. + :type wifi_access_points: list of dicts + """ + + params = {} + if home_mobile_country_code is not None: + params["homeMobileCountryCode"] = home_mobile_country_code + if home_mobile_network_code is not None: + params["homeMobileNetworkCode"] = home_mobile_network_code + if radio_type is not None: + params["radioType"] = radio_type + if carrier is not None: + params["carrier"] = carrier + if consider_ip is not None: + params["considerIp"] = consider_ip + if cell_towers is not None: + params["cellTowers"] = cell_towers + if wifi_access_points is not None: + params["wifiAccessPoints"] = wifi_access_points + + return client._request("/geolocation/v1/geolocate", {}, # No GET params + base_url=_GEOLOCATION_BASE_URL, + extract_body=_geolocation_extract, + post_json=params) diff --git a/test/test_geolocation.py b/test/test_geolocation.py new file mode 100644 index 00000000..8b6e1918 --- /dev/null +++ b/test/test_geolocation.py @@ -0,0 +1,44 @@ +# This Python file uses the following encoding: utf-8 +# +# Copyright 2017 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the geocolocation module.""" + +import responses + +import test as _test +import googlemaps + +class GeolocationTest(_test.TestCase): + + def setUp(self): + self.key = 'AIzaasdf' + self.client = googlemaps.Client(self.key) + + @responses.activate + def test_simple_geolocate(self): + responses.add(responses.POST, + 'https://www.googleapis.com/geolocation/v1/geolocate', + body='{"location": {"lat": 51.0,"lng": -0.1},"accuracy": 1200.4}', + status=200, + content_type='application/json') + + results = self.client.geolocate() + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('https://www.googleapis.com/geolocation/v1/geolocate?' + 'key=%s' % self.key, responses.calls[0].request.url) From a53acc7f60a743550052d0a8db7fa7a1d23fa174 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 14 Feb 2017 16:06:12 +1100 Subject: [PATCH 076/260] Travis doesn't yet support 3.6 Change-Id: I903847cddebcbd8da5aa060384b93753a679c3c7 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0c49ceeb..1efd5c74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,5 +12,4 @@ env: - TOXENV=py27 - TOXENV=py34 - TOXENV=py35 - - TOXENV=py36 - TOXENV=docs From e090f85047ae559fe4eaba449187f06fb9d4b06c Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 27 Feb 2017 13:09:22 +1100 Subject: [PATCH 077/260] Note geolocation API in README Change-Id: I071b252fa36799567c49d0e4799c2b9f03699f2e --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f34ec8a7..949f0564 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ APIs: - [Distance Matrix API] - [Elevation API] - [Geocoding API] + - [Geolocation API] - [Time Zone API] - [Roads API] - [Places API] @@ -187,6 +188,7 @@ instead of an API key. [Distance Matrix API]: https://developers.google.com/maps/documentation/distancematrix/ [Elevation API]: https://developers.google.com/maps/documentation/elevation/ [Geocoding API]: https://developers.google.com/maps/documentation/geocoding/ +[Geolocation API]: https://developers.google.com/maps/documentation/geolocation/ [Time Zone API]: https://developers.google.com/maps/documentation/timezone/ [Roads API]: https://developers.google.com/maps/documentation/roads/ [Places API]: https://developers.google.com/places/ From 571b93bb7b3dfb4df86c48c1f7ae9fb020da3122 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 27 Feb 2017 13:11:26 +1100 Subject: [PATCH 078/260] Version 2.4.6 Change-Id: I58b10a7d6f830020b54dc760e2c5517d0feb5be4 --- README.md | 2 +- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 949f0564..06f4d0ef 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Note that you will need requests 2.4.0 or higher if you want to specify connect/ ## Developer Documentation -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.5/) +View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.6/) Additional documentation for the included web services is available at https://developers.google.com/maps/. diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index f7013b11..6805fdd9 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.5-dev" +__version__ = "2.4.6" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 45d2eed0..d355afee 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.5-dev', + version='2.4.6', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 0e0a52303ae80111e842a645fbdfee5d4b4a0493 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 27 Feb 2017 14:00:14 +1100 Subject: [PATCH 079/260] Version 2.4.6-dev Change-Id: I1f905ae79f843e28330f4b4a11c95fd5a9a4ed76 --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 6805fdd9..a8591eb4 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.6" +__version__ = "2.4.6-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index d355afee..2b61e152 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.6', + version='2.4.6-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 09028184ea7ed8809875575dd04ff377ba38aeb5 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 27 Feb 2017 14:05:09 +1100 Subject: [PATCH 080/260] Update README Change-Id: Ie6cbfee050d884836bbcbe6fa31d9662ff335445 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 06f4d0ef..1907563f 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ To get an API key: * Distance Matrix API * Elevation API * Geocoding API + * Geolocation API * Places API * Roads API * Time Zone API @@ -90,6 +91,7 @@ https://developers.google.com/maps/. - [Distance Matrix API] - [Elevation API] - [Geocoding API] + - [Geolocation API] - [Time Zone API] - [Roads API] - [Places API] From 889ca0dcdb836c7c82b9fd361b9c6d454f4bcd22 Mon Sep 17 00:00:00 2001 From: Paul Traina Date: Fri, 12 May 2017 17:26:32 -0700 Subject: [PATCH 081/260] Relax requirements to allow requests >=2.11 and <3.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2b61e152..f15539dd 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ requirements = [ - 'requests<=2.11.1', + 'requests>=2.11.1,<3.0', ] setup(name='googlemaps', From eaae89b634e1c6412e9634efebbc2254f5cd420f Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 30 May 2017 12:47:53 +1000 Subject: [PATCH 082/260] Add strictbounds param to places API. Closes #179. Change-Id: I7a639bdf026b68f39962c4480f040d2dd4c3c15d --- googlemaps/places.py | 11 +++++++++-- test/test_places.py | 7 ++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index a40f06c6..5da0aabb 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -299,7 +299,7 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): def places_autocomplete(client, input_text, offset=None, location=None, radius=None, language=None, type=None, - components=None): + components=None, strict_bounds=False): """ Returns Place predictions given a textual search string and optional geographic bounds. @@ -333,6 +333,10 @@ def places_autocomplete(client, input_text, offset=None, location=None, ``{'administrative_area': 'TX','country': 'US'}`` :type components: dict + :param strict_bounds: Returns only those places that are strictly within + the region defined by location and radius. + :type strict_bounds: bool + :rtype: list of predictions """ @@ -372,7 +376,8 @@ def places_autocomplete_query(client, input_text, offset=None, location=None, def _autocomplete(client, url_part, input_text, offset=None, location=None, - radius=None, language=None, type=None, components=None): + radius=None, language=None, type=None, components=None, + strict_bounds=False): """ Internal handler for ``autocomplete`` and ``autocomplete_query``. See each method's docs for arg details. @@ -392,6 +397,8 @@ def _autocomplete(client, url_part, input_text, offset=None, location=None, params["type"] = type if components: params["components"] = convert.components(components) + if strict_bounds: + params["strictbounds"] = "true" url = "/maps/api/place/%sautocomplete/json" % url_part return client._request(url, params)["predictions"] diff --git a/test/test_places.py b/test/test_places.py index 188ee2d4..a42247b4 100644 --- a/test/test_places.py +++ b/test/test_places.py @@ -136,13 +136,14 @@ def test_autocomplete(self): location=self.location, radius=self.radius, language=self.language, type='geocode', - components={'country': 'au'}) + components={'country': 'au'}, + strict_bounds=True) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?components=country%%3Aau&input=Google&language=en-AU&' 'location=-33.86746%%2C151.20709&offset=3&radius=100&' - 'type=geocode&key=%s' % (url, self.key), - responses.calls[0].request.url) + 'strictbounds=true&type=geocode&key=%s' % + (url, self.key), responses.calls[0].request.url) @responses.activate def test_autocomplete_query(self): From 1cb70d15c5a0bec44ce8eb1c38e7a547e37db276 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 30 May 2017 13:04:12 +1000 Subject: [PATCH 083/260] Fix missing param Change-Id: Ibe469a37de595c1747da2559efd057ce09e78c68 --- googlemaps/places.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 5da0aabb..8de5ae7e 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -342,7 +342,8 @@ def places_autocomplete(client, input_text, offset=None, location=None, """ return _autocomplete(client, "", input_text, offset=offset, location=location, radius=radius, language=language, - type=type, components=components) + type=type, components=components, + strict_bounds=strict_bounds) def places_autocomplete_query(client, input_text, offset=None, location=None, From 85b81274a5db1bf877aa391bcccfd1456ccce2f0 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 8 Jun 2017 11:39:26 +1000 Subject: [PATCH 084/260] Fix places autocomplete param, type -> types. Change-Id: I9af476525056a6d651f56bfff3ebd9b42f602353 --- googlemaps/places.py | 12 ++++++------ test/test_places.py | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 8de5ae7e..78c04fda 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -298,7 +298,7 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): def places_autocomplete(client, input_text, offset=None, location=None, - radius=None, language=None, type=None, + radius=None, language=None, types=None, components=None, strict_bounds=False): """ Returns Place predictions given a textual search string and optional @@ -323,7 +323,7 @@ def places_autocomplete(client, input_text, offset=None, location=None, :param language: The language in which to return results. :type langauge: string - :param type: Restricts the results to places matching the specified type. + :param types: Restricts the results to places matching the specified type. The full list of supported types is available here: https://developers.google.com/places/web-service/autocomplete#place_types :type type: string @@ -342,7 +342,7 @@ def places_autocomplete(client, input_text, offset=None, location=None, """ return _autocomplete(client, "", input_text, offset=offset, location=location, radius=radius, language=language, - type=type, components=components, + types=types, components=components, strict_bounds=strict_bounds) @@ -377,7 +377,7 @@ def places_autocomplete_query(client, input_text, offset=None, location=None, def _autocomplete(client, url_part, input_text, offset=None, location=None, - radius=None, language=None, type=None, components=None, + radius=None, language=None, types=None, components=None, strict_bounds=False): """ Internal handler for ``autocomplete`` and ``autocomplete_query``. @@ -394,8 +394,8 @@ def _autocomplete(client, url_part, input_text, offset=None, location=None, params["radius"] = radius if language: params["language"] = language - if type: - params["type"] = type + if types: + params["types"] = types if components: params["components"] = convert.components(components) if strict_bounds: diff --git a/test/test_places.py b/test/test_places.py index a42247b4..71e3a546 100644 --- a/test/test_places.py +++ b/test/test_places.py @@ -135,14 +135,15 @@ def test_autocomplete(self): self.client.places_autocomplete('Google', offset=3, location=self.location, radius=self.radius, - language=self.language, type='geocode', + language=self.language, + types='geocode', components={'country': 'au'}, strict_bounds=True) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?components=country%%3Aau&input=Google&language=en-AU&' 'location=-33.86746%%2C151.20709&offset=3&radius=100&' - 'strictbounds=true&type=geocode&key=%s' % + 'strictbounds=true&types=geocode&key=%s' % (url, self.key), responses.calls[0].request.url) @responses.activate From b20a68f603bbee92b44baad26eb6b0f9cfd7b7d2 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Jun 2017 12:27:32 +1000 Subject: [PATCH 085/260] Version 2.5 Change-Id: I82272e067fd304b4a41ad4b4c5393e373264ba0e --- README.md | 2 +- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1907563f..f628e202 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Note that you will need requests 2.4.0 or higher if you want to specify connect/ ## Developer Documentation -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.4.6/) +View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.5/) Additional documentation for the included web services is available at https://developers.google.com/maps/. diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index a8591eb4..d0076614 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.4.6-dev" +__version__ = "2.5.0" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index f15539dd..84eec08a 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.4.6-dev', + version='2.5.0', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From c9851e0cd26f5eda2b7a006631a2954fd4bcd657 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 9 Jun 2017 12:39:12 +1000 Subject: [PATCH 086/260] 2.5.0-dev Change-Id: I0130f91173f3b950288ebd327b667557b020654b --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index d0076614..7def0726 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.5.0" +__version__ = "2.5.0-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 84eec08a..8cf6d55b 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.5.0', + version='2.5.0-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From fa3fdfa7fdc6a54eae9f610b8742d1028008b170 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 3 Jul 2017 10:21:01 +1000 Subject: [PATCH 087/260] Mark places radar search as deprecated. Change-Id: Ib7f18344f0516866fc8a34f9f760371c3d2dcd31 --- googlemaps/places.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/googlemaps/places.py b/googlemaps/places.py index 78c04fda..a4800012 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -191,6 +191,10 @@ def places_radar(client, location, radius, keyword=None, min_price=None, if not (keyword or name or type): raise ValueError("either a keyword, name, or type arg is required") + from warnings import warn + warn("places_radar is deprecated, see http://goo.gl/BGiumE", + DeprecationWarning) + return _places(client, "radar", location=location, radius=radius, keyword=keyword, min_price=min_price, max_price=max_price, name=name, open_now=open_now, type=type) From 78fdaab8ccb3f894e94f028cd7bac7648b9e50b5 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 3 Jul 2017 10:23:17 +1000 Subject: [PATCH 088/260] Version 2.5.1 Change-Id: Id8a35e480a8340e95ece8447337eaeb64adce4f5 --- README.md | 2 +- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f628e202..80d9f468 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Note that you will need requests 2.4.0 or higher if you want to specify connect/ ## Developer Documentation -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/2.5/) +View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/) Additional documentation for the included web services is available at https://developers.google.com/maps/. diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 7def0726..99c48df1 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.5.0-dev" +__version__ = "2.5.1" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 8cf6d55b..8b61560e 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.5.0-dev', + version='2.5.1', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From d75f1c5ef878ed826a51b4083a9699c48dcf24db Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 3 Jul 2017 10:29:32 +1000 Subject: [PATCH 089/260] Version 2.5.1-dev Change-Id: Id04308736f952cb5cd0d662540dae3b13695140e --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 99c48df1..e9a0b779 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.5.1" +__version__ = "2.5.1-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 8b61560e..ec7b2e38 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.5.1', + version='2.5.1-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From de0ddab13e423b3ab4708e5b21b23206a3887dd3 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Fri, 18 Aug 2017 15:00:23 +1000 Subject: [PATCH 090/260] Return empty result on APIs when key missing from response. Change-Id: I79e0e606bca6538ac2a679dbf53d80b014e7940f --- googlemaps/directions.py | 2 +- googlemaps/elevation.py | 4 ++-- googlemaps/geocoding.py | 4 ++-- googlemaps/places.py | 2 +- googlemaps/roads.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/googlemaps/directions.py b/googlemaps/directions.py index 236dc96a..5a826807 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -147,4 +147,4 @@ def directions(client, origin, destination, if traffic_model: params["traffic_model"] = traffic_model - return client._request("/maps/api/directions/json", params)["routes"] + return client._request("/maps/api/directions/json", params).get("routes", []) diff --git a/googlemaps/elevation.py b/googlemaps/elevation.py index 4546679c..8eb6b14a 100644 --- a/googlemaps/elevation.py +++ b/googlemaps/elevation.py @@ -34,7 +34,7 @@ def elevation(client, locations): :rtype: list of elevation data responses """ params = {"locations": convert.shortest_path(locations)} - return client._request("/maps/api/elevation/json", params)["results"] + return client._request("/maps/api/elevation/json", params).get("results", []) def elevation_along_path(client, path, samples): @@ -62,4 +62,4 @@ def elevation_along_path(client, path, samples): "samples": samples } - return client._request("/maps/api/elevation/json", params)["results"] + return client._request("/maps/api/elevation/json", params).get("results", []) diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index 99ceeeff..a2913cf9 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -65,7 +65,7 @@ def geocode(client, address=None, components=None, bounds=None, region=None, if language: params["language"] = language - return client._request("/maps/api/geocode/json", params)["results"] + return client._request("/maps/api/geocode/json", params).get("results", []) def reverse_geocode(client, latlng, result_type=None, location_type=None, @@ -106,4 +106,4 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, if language: params["language"] = language - return client._request("/maps/api/geocode/json", params)["results"] + return client._request("/maps/api/geocode/json", params).get("results", []) diff --git a/googlemaps/places.py b/googlemaps/places.py index a4800012..63e79305 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -406,4 +406,4 @@ def _autocomplete(client, url_part, input_text, offset=None, location=None, params["strictbounds"] = "true" url = "/maps/api/place/%sautocomplete/json" % url_part - return client._request(url, params)["predictions"] + return client._request(url, params).get("predictions", []) diff --git a/googlemaps/roads.py b/googlemaps/roads.py index 120cc245..5bf2276d 100644 --- a/googlemaps/roads.py +++ b/googlemaps/roads.py @@ -92,7 +92,7 @@ def speed_limits(client, place_ids): return client._request("/v1/speedLimits", params, base_url=_ROADS_BASE_URL, accepts_clientid=False, - extract_body=_roads_extract)["speedLimits"] + extract_body=_roads_extract).get("speedLimits", []) def snapped_speed_limits(client, path): From dc2f487d7c7a5c68b9ccb8b0bd7f6d171627b11c Mon Sep 17 00:00:00 2001 From: Apostolos Spanos Date: Fri, 1 Sep 2017 14:46:01 +0100 Subject: [PATCH 091/260] Use place_id as destination Make clear that using a place_id as a destination is possible. --- googlemaps/directions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/googlemaps/directions.py b/googlemaps/directions.py index 5a826807..f3a6b803 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -32,7 +32,8 @@ def directions(client, origin, destination, :type origin: string, dict, list, or tuple :param destination: The address or latitude/longitude value from which - you wish to calculate directions. + you wish to calculate directions. You can use a place_id as destination + using 'place_id' as a preffix in the passing parameter. :type destination: string, dict, list, or tuple :param mode: Specifies the mode of transport to use when calculating From 6643949a0bb0e68f999d685e72cfa6dcc80c0ad6 Mon Sep 17 00:00:00 2001 From: Apostolos Spanos Date: Mon, 4 Sep 2017 16:41:39 +0100 Subject: [PATCH 092/260] Update directions.py --- googlemaps/directions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/directions.py b/googlemaps/directions.py index f3a6b803..f1713dfb 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -33,7 +33,7 @@ def directions(client, origin, destination, :param destination: The address or latitude/longitude value from which you wish to calculate directions. You can use a place_id as destination - using 'place_id' as a preffix in the passing parameter. + by putting 'place_id:' as a preffix in the passing parameter. :type destination: string, dict, list, or tuple :param mode: Specifies the mode of transport to use when calculating From 45ced91e3c4733f67d0527456e406e317274b6d1 Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Fri, 15 Sep 2017 13:11:26 +0200 Subject: [PATCH 093/260] =?UTF-8?q?Small=20documentation=20fix:=20photo(?= =?UTF-8?q?=E2=80=A6)=20=E2=86=92=20places=5Fphoto(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- googlemaps/places.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 63e79305..3eb5cbea 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -275,7 +275,7 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): ``` f = open(local_filename, 'wb') - for chunk in client.photo(photo_reference, max_width=100): + for chunk in client.places_photo(photo_reference, max_width=100): if chunk: f.write(chunk) f.close() From 363df85ab7c8dd3f0f413fa31861cd2e824a1bf2 Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Sun, 17 Sep 2017 23:05:03 +0200 Subject: [PATCH 094/260] Update Travis config: Fix Python versions This runs each Tox env with the right base Python version. --- .travis.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1efd5c74..ea1fa63d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,12 @@ language: python -python: 2.7 + +matrix: + include: + - { python: '2.7', env: TOXENV=py27 } + - { python: '3.4', env: TOXENV=py34 } + - { python: '3.5', env: TOXENV=py35 } + - { python: '3.6', env: TOXENV=py36 } + - { python: '3.6', env: TOXENV=docs } install: - pip install requests @@ -7,9 +14,3 @@ install: script: - tox - -env: - - TOXENV=py27 - - TOXENV=py34 - - TOXENV=py35 - - TOXENV=docs From c8053b61a83ee94410e0fb024a0ed5037a2f68fd Mon Sep 17 00:00:00 2001 From: Matthew Laney Date: Mon, 27 Nov 2017 14:58:42 -0800 Subject: [PATCH 095/260] Implement region parameter in distance matrix to fix issue #209 --- googlemaps/distance_matrix.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) mode change 100644 => 100755 googlemaps/distance_matrix.py diff --git a/googlemaps/distance_matrix.py b/googlemaps/distance_matrix.py old mode 100644 new mode 100755 index 6c1f0626..f6a85e8c --- a/googlemaps/distance_matrix.py +++ b/googlemaps/distance_matrix.py @@ -24,7 +24,7 @@ def distance_matrix(client, origins, destinations, mode=None, language=None, avoid=None, units=None, departure_time=None, arrival_time=None, transit_mode=None, - transit_routing_preference=None, traffic_model=None): + transit_routing_preference=None, traffic_model=None, region=None): """ Gets travel distance and time for a matrix of origins and destinations. :param origins: One or more locations and/or latitude/longitude values, @@ -81,6 +81,11 @@ def distance_matrix(client, origins, destinations, the travel mode is driving, and where the request includes a departure_time. + :param region: Specifies the prefered region the geocoder should search + first, but it will not restrict the results to only this region. Valid + values are a ccTLD code. + :type region: string + :rtype: matrix of distances. Results are returned in rows, each row containing one origin paired with each destination. """ @@ -127,4 +132,7 @@ def distance_matrix(client, origins, destinations, if traffic_model: params["traffic_model"] = traffic_model + if region: + params["region"] = region + return client._request("/maps/api/distancematrix/json", params) From 8671ecd2bb301f8f82d5574f027204ceb6dfe9d5 Mon Sep 17 00:00:00 2001 From: Kerrick Staley Date: Thu, 4 Jan 2018 16:36:51 -0800 Subject: [PATCH 096/260] Add option to not retry when over query limit --- googlemaps/client.py | 16 +++++++++++++--- googlemaps/exceptions.py | 8 ++++++++ googlemaps/geolocation.py | 2 +- googlemaps/roads.py | 2 +- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 72b59ed0..bcef125e 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -51,7 +51,8 @@ class Client(object): 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=10, channel=None): + queries_per_second=10, channel=None, + retry_over_query_limit=True): """ :param key: Maps API key. Required, unless "client_id" and "client_secret" are set. @@ -93,6 +94,11 @@ def __init__(self, key=None, client_id=None, client_secret=None, appropriate amount of time before it runs the current query. :type queries_per_second: int + :param retry_over_query_limit: If True, requests that result in a + response indicating the query rate limit was exceeded will be + retried. Defaults to True. + :type retry_over_query_limit: bool + :raises ValueError: when either credentials are missing, incomplete or invalid. :raises NotImplementedError: if connect_timeout and read_timeout are @@ -150,6 +156,7 @@ 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) def _request(self, url, params, first_request_time=None, retry_counter=0, @@ -253,7 +260,10 @@ def _request(self, url, params, first_request_time=None, retry_counter=0, result = self._get_body(response) self.sent_times.append(time.time()) return result - except googlemaps.exceptions._RetriableRequest: + except googlemaps.exceptions._RetriableRequest as e: + if isinstance(e, googlemaps.exceptions._OverQueryLimit) and not self.retry_over_query_limit: + raise + # Retry request. return self._request(url, params, first_request_time, retry_counter + 1, base_url, accepts_clientid, @@ -273,7 +283,7 @@ def _get_body(self, response): return body if api_status == "OVER_QUERY_LIMIT": - raise googlemaps.exceptions._RetriableRequest() + raise googlemaps.exceptions._OverQueryLimit() if "error_message" in body: raise googlemaps.exceptions.ApiError(api_status, diff --git a/googlemaps/exceptions.py b/googlemaps/exceptions.py index 4197a650..8aa73e77 100644 --- a/googlemaps/exceptions.py +++ b/googlemaps/exceptions.py @@ -58,3 +58,11 @@ class Timeout(Exception): class _RetriableRequest(Exception): """Signifies that the request can be retried.""" pass + +class _OverQueryLimit(_RetriableRequest): + """Signifies that the request failed because the client exceeded its query rate limit. + + Normally we treat this as a retriable condition, but we allow the calling code to specify that these requests should + not be retried. + """ + pass diff --git a/googlemaps/geolocation.py b/googlemaps/geolocation.py index 9cbfb6b5..e4b57082 100644 --- a/googlemaps/geolocation.py +++ b/googlemaps/geolocation.py @@ -31,7 +31,7 @@ def _geolocation_extract(response): if response.status_code in (200, 404): return body elif response.status_code == 403: - raise exceptions._RetriableRequest() + raise exceptions._OverQueryLimit() else: try: error = body["error"]["errors"][0]["reason"] diff --git a/googlemaps/roads.py b/googlemaps/roads.py index 5bf2276d..138dc391 100644 --- a/googlemaps/roads.py +++ b/googlemaps/roads.py @@ -133,7 +133,7 @@ def _roads_extract(resp): status = error["status"] if status == "RESOURCE_EXHAUSTED": - raise googlemaps.exceptions._RetriableRequest() + raise googlemaps.exceptions._OverQueryLimit() if "message" in error: raise googlemaps.exceptions.ApiError(status, error["message"]) From 20d1b7b4dba2942b09750b534b2652d6d52cb10c Mon Sep 17 00:00:00 2001 From: Kerrick Staley Date: Thu, 4 Jan 2018 17:00:16 -0800 Subject: [PATCH 097/260] Make it subclass ApiError --- googlemaps/client.py | 10 ++++------ googlemaps/exceptions.py | 2 +- googlemaps/geolocation.py | 14 ++++++++------ googlemaps/roads.py | 8 +++----- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index bcef125e..a1cac991 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -283,13 +283,11 @@ def _get_body(self, response): return body if api_status == "OVER_QUERY_LIMIT": - raise googlemaps.exceptions._OverQueryLimit() + raise googlemaps.exceptions._OverQueryLimit( + api_status, body.get("error_message")) - if "error_message" in body: - raise googlemaps.exceptions.ApiError(api_status, - body["error_message"]) - else: - raise googlemaps.exceptions.ApiError(api_status) + raise googlemaps.exceptions.ApiError(api_status, + body.get("error_message")) def _generate_auth_url(self, path, params, accepts_clientid): """Returns the path and query string portion of the request URL, first diff --git a/googlemaps/exceptions.py b/googlemaps/exceptions.py index 8aa73e77..679b26c3 100644 --- a/googlemaps/exceptions.py +++ b/googlemaps/exceptions.py @@ -59,7 +59,7 @@ class _RetriableRequest(Exception): """Signifies that the request can be retried.""" pass -class _OverQueryLimit(_RetriableRequest): +class _OverQueryLimit(ApiError, _RetriableRequest): """Signifies that the request failed because the client exceeded its query rate limit. Normally we treat this as a retriable condition, but we allow the calling code to specify that these requests should diff --git a/googlemaps/geolocation.py b/googlemaps/geolocation.py index e4b57082..c8db15ec 100644 --- a/googlemaps/geolocation.py +++ b/googlemaps/geolocation.py @@ -30,13 +30,15 @@ def _geolocation_extract(response): body = response.json() if response.status_code in (200, 404): return body - elif response.status_code == 403: - raise exceptions._OverQueryLimit() + + try: + error = body["error"]["errors"][0]["reason"] + except KeyError: + error = None + + if response.status_code == 403: + raise exceptions._OverQueryLimit(response.status_code, error) else: - try: - error = body["error"]["errors"][0]["reason"] - except KeyError: - error = None raise exceptions.ApiError(response.status_code, error) diff --git a/googlemaps/roads.py b/googlemaps/roads.py index 138dc391..edfb8ecb 100644 --- a/googlemaps/roads.py +++ b/googlemaps/roads.py @@ -133,12 +133,10 @@ def _roads_extract(resp): status = error["status"] if status == "RESOURCE_EXHAUSTED": - raise googlemaps.exceptions._OverQueryLimit() + raise googlemaps.exceptions._OverQueryLimit(status, + error.get("message")) - if "message" in error: - raise googlemaps.exceptions.ApiError(status, error["message"]) - else: - raise googlemaps.exceptions.ApiError(status) + raise googlemaps.exceptions.ApiError(status, error.get("message")) if resp.status_code != 200: raise googlemaps.exceptions.HTTPError(resp.status_code) From 91e6fb99d2902946b706327336948c6d008e9cc4 Mon Sep 17 00:00:00 2001 From: Kerrick Staley Date: Thu, 4 Jan 2018 17:02:12 -0800 Subject: [PATCH 098/260] Add unit test --- test/test_client.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_client.py b/test/test_client.py index f2d424a4..79019d36 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -288,3 +288,19 @@ def test_requests_version(self): requests.__version__ = '2.4.0' googlemaps.Client(**client_args_timeout) googlemaps.Client(**client_args) + + @responses.activate + def test_no_retry_over_query_limit(self): + responses.add(responses.GET, + "https://maps.googleapis.com/foo", + body='{"status":"OVER_QUERY_LIMIT"}', + status=200, + content_type="application/json") + + client = googlemaps.Client(key="AIzaasdf", + retry_over_query_limit=False) + + with self.assertRaises(googlemaps.exceptions.ApiError): + client._request("/foo", {}) + + self.assertEqual(1, len(responses.calls)) From ed23c0451e1140ea0f6312650ce3d56cca8ed772 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 9 Jan 2018 11:01:10 +1100 Subject: [PATCH 099/260] Increase default QPS from 10 to 50 Change-Id: I14979c04d1c1c314e007a9eb5858ee1db52a74e7 --- googlemaps/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 72b59ed0..d6820ab5 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -51,7 +51,7 @@ class Client(object): 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=10, channel=None): + queries_per_second=50, channel=None): """ :param key: Maps API key. Required, unless "client_id" and "client_secret" are set. From 6a7be65017073f2593ae0679f3eabcdb36b10b03 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 12 Feb 2018 12:24:24 +1100 Subject: [PATCH 100/260] Validate country components in places autocomplete. Closes #216. Change-Id: Ie88f23b7b0a22d1ed8e07b74d027f16252a8e80d --- googlemaps/places.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 3eb5cbea..8cdacf27 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -332,9 +332,9 @@ def places_autocomplete(client, input_text, offset=None, location=None, https://developers.google.com/places/web-service/autocomplete#place_types :type type: string - :param components: A component filter for which you wish to obtain a geocode, - for example: - ``{'administrative_area': 'TX','country': 'US'}`` + :param components: A component filter for which you wish to obtain a geocode. + Currently, you can use components to filter by up to 5 countries for + example: ``{'country': ['US', 'AU']}`` :type components: dict :param strict_bounds: Returns only those places that are strictly within @@ -401,6 +401,8 @@ def _autocomplete(client, url_part, input_text, offset=None, location=None, if types: params["types"] = types if components: + if len(components) != 1 or components.keys()[0] != "country": + raise ValueError("Only country components are supported") params["components"] = convert.components(components) if strict_bounds: params["strictbounds"] = "true" From a91c1753ef6c81755c05b9e8d778d0ccc0488e74 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 12 Feb 2018 12:32:31 +1100 Subject: [PATCH 101/260] Python3 fix Change-Id: I687ddf1045a4e68445172212d83c016bc31443df --- googlemaps/places.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 8cdacf27..a5cf8dac 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -401,7 +401,7 @@ def _autocomplete(client, url_part, input_text, offset=None, location=None, if types: params["types"] = types if components: - if len(components) != 1 or components.keys()[0] != "country": + if len(components) != 1 or list(components.keys())[0] != "country": raise ValueError("Only country components are supported") params["components"] = convert.components(components) if strict_bounds: From 881bfdf0bc17176c82c9631b381836022b277744 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 12 Feb 2018 12:41:29 +1100 Subject: [PATCH 102/260] Allow string version of bounds. Closes #188. Change-Id: I648d10cd69be1f7069b5f0391a0ca68d337f467d --- googlemaps/convert.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/googlemaps/convert.py b/googlemaps/convert.py index 6206cfa8..a20e3a32 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -265,7 +265,9 @@ def bounds(arg): :type arg: dict """ - if isinstance(arg, dict): + if is_string(arg) and arg.count("|") == 1 and arg.count(",") == 2: + return arg + elif isinstance(arg, dict): if "southwest" in arg and "northeast" in arg: return "%s|%s" % (latlng(arg["southwest"]), latlng(arg["northeast"])) From c1c6affdc15a6be7ea264d7aca06a07fd64d74be Mon Sep 17 00:00:00 2001 From: Stvan Date: Mon, 26 Mar 2018 01:59:25 +0200 Subject: [PATCH 103/260] Places service region parameter appending (#228) --- googlemaps/places.py | 16 +++++++++++++--- test/test_places.py | 5 +++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index a5cf8dac..6b02d2ec 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -21,7 +21,7 @@ def places(client, query, location=None, radius=None, language=None, - min_price=None, max_price=None, open_now=False, type=None, + min_price=None, max_price=None, open_now=False, type=None, region=None, page_token=None): """ Places search. @@ -57,6 +57,10 @@ def places(client, query, location=None, radius=None, language=None, The full list of supported types is available here: https://developers.google.com/places/supported_types :type type: string + + :param region: The region code, optional parameter. + See more @ https://developers.google.com/places/web-service/search + :type region: string :param page_token: Token from a previous search that when provided will returns the next page of results for the same search. @@ -69,7 +73,7 @@ def places(client, query, location=None, radius=None, language=None, """ return _places(client, "text", query=query, location=location, radius=radius, language=language, min_price=min_price, - max_price=max_price, open_now=open_now, type=type, + max_price=max_price, open_now=open_now, type=type, region=region, page_token=page_token) @@ -85,6 +89,10 @@ def places_nearby(client, location, radius=None, keyword=None, language=None, :param radius: Distance in meters within which to bias results. :type radius: int + + :param region: The region code, optional parameter. + See more @ https://developers.google.com/places/web-service/search + :type region: string :param keyword: A term to be matched against all content that Google has indexed for this place. @@ -202,7 +210,7 @@ def places_radar(client, location, radius, keyword=None, min_price=None, def _places(client, url_part, query=None, location=None, radius=None, keyword=None, language=None, min_price=0, max_price=4, name=None, - open_now=False, rank_by=None, type=None, page_token=None): + open_now=False, rank_by=None, type=None, region=None, page_token=None): """ Internal handler for ``places``, ``places_nearby``, and ``places_radar``. See each method's docs for arg details. @@ -228,6 +236,8 @@ def _places(client, url_part, query=None, location=None, radius=None, params["rankby"] = rank_by if type: params["type"] = type + if region: + params["region"] = region if page_token: params["pagetoken"] = page_token diff --git a/test/test_places.py b/test/test_places.py index 71e3a546..f9e033c8 100644 --- a/test/test_places.py +++ b/test/test_places.py @@ -34,6 +34,7 @@ def setUp(self): self.location = (-33.86746, 151.207090) self.type = 'liquor_store' self.language = 'en-AU' + self.region = 'AU' self.radius = 100 @responses.activate @@ -44,14 +45,14 @@ def test_places_text_search(self): status=200, content_type='application/json') self.client.places('restaurant', location=self.location, - radius=self.radius, language=self.language, + radius=self.radius, region=self.region, language=self.language, min_price=1, max_price=4, open_now=True, type=self.type) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?language=en-AU&location=-33.86746%%2C151.20709&' 'maxprice=4&minprice=1&opennow=true&query=restaurant&' - 'radius=100&type=liquor_store&key=%s' + 'radius=100®ion=AU&type=liquor_store&key=%s' % (url, self.key), responses.calls[0].request.url) @responses.activate From 86cfa452a07a039f942df5e51a410756797b2ac3 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 16 Apr 2018 15:49:37 +1000 Subject: [PATCH 104/260] location param for places nearby is not required if pagetoken provided Change-Id: I70b63549ba8850694425a743f747952db6964d2d --- googlemaps/places.py | 16 +++++++++------- test/test_convert.py | 3 +-- test/test_places.py | 6 ++++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index a5cf8dac..b5a59d6d 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -73,9 +73,9 @@ def places(client, query, location=None, radius=None, language=None, page_token=page_token) -def places_nearby(client, location, radius=None, keyword=None, language=None, - min_price=None, max_price=None, name=None, open_now=False, - rank_by=None, type=None, page_token=None): +def places_nearby(client, location=None, radius=None, keyword=None, + language=None, min_price=None, max_price=None, name=None, + open_now=False, rank_by=None, type=None, page_token=None): """ Performs nearby search for places. @@ -130,13 +130,15 @@ def places_nearby(client, location, radius=None, keyword=None, language=None, next_page_token: token for retrieving the next page of results """ + if not location and not page_token: + raise ValueError("either a location or page_token arg is required") if rank_by == "distance": if not (keyword or name or type): - raise ValueError("either a keyword, name, or type arg is required " - "when rank_by is set to distance") + raise ValueError("either a keyword, name, or type arg is required " + "when rank_by is set to distance") elif radius is not None: - raise ValueError("radius cannot be specified when rank_by is set to " - "distance") + raise ValueError("radius cannot be specified when rank_by is set to " + "distance") return _places(client, "nearby", location=location, radius=radius, keyword=keyword, language=language, min_price=min_price, diff --git a/test/test_convert.py b/test/test_convert.py index 090a95fc..ed08c84b 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -107,8 +107,7 @@ def test_bounds(self): ne = {"lat": 1, "lng": 2} sw = (3, 4) b = {"northeast": ne, "southwest": sw} - self.assertEqual("3,4|1,2", - convert.bounds(b)) + self.assertEqual("3,4|1,2", convert.bounds(b)) with self.assertRaises(TypeError): convert.bounds("test") diff --git a/test/test_places.py b/test/test_places.py index 71e3a546..49418d42 100644 --- a/test/test_places.py +++ b/test/test_places.py @@ -61,7 +61,7 @@ def test_places_nearby_search(self): body='{"status": "OK", "results": [], "html_attributions": []}', status=200, content_type='application/json') - self.client.places_nearby(self.location, keyword='foo', + self.client.places_nearby(location=self.location, keyword='foo', language=self.language, min_price=1, max_price=4, name='bar', open_now=True, rank_by='distance', type=self.type) @@ -72,11 +72,13 @@ def test_places_nearby_search(self): 'type=liquor_store&key=%s' % (url, self.key), responses.calls[0].request.url) + with self.assertRaises(ValueError): + self.client.places_nearby(radius=self.radius) with self.assertRaises(ValueError): self.client.places_nearby(self.location, rank_by="distance") with self.assertRaises(ValueError): - self.client.places_nearby(self.location, rank_by="distance", + self.client.places_nearby(location=self.location, rank_by="distance", keyword='foo', radius=self.radius) @responses.activate From 42e7e99d46b3b8aec7d5994e674f366d36fcc5e1 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 21 May 2018 03:08:17 +0200 Subject: [PATCH 105/260] Fix type annotation in places_autocomplete() docs (#231) This was broken by the rename of the parameter from `type` to `types` in commit 85b81274a5db1bf877aa391bcccfd1456ccce2f0. --- googlemaps/places.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 0b035bdb..4c95dcbd 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -342,7 +342,7 @@ def places_autocomplete(client, input_text, offset=None, location=None, :param types: Restricts the results to places matching the specified type. The full list of supported types is available here: https://developers.google.com/places/web-service/autocomplete#place_types - :type type: string + :type types: string :param components: A component filter for which you wish to obtain a geocode. Currently, you can use components to filter by up to 5 countries for From bca4a9bfd187fbc2324d328633ed682f88bd56c0 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 21 May 2018 11:13:38 +1000 Subject: [PATCH 106/260] Add manifest Change-Id: Ib37b4d808c60d103a9852943b7e1ba58171d9426 --- MANIFEST.in | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..4e833047 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE README.md +global-exclude __pycache__ +global-exclude *.py[co] From 82f71d81e5da3511a539879336401e570974bd11 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Thu, 31 May 2018 11:19:05 +1000 Subject: [PATCH 107/260] Move test module into package. Closes #238. Change-Id: Ic32abf0f4c734c2ec8f582d3318b271c7c4df653 --- {test => googlemaps/test}/__init__.py | 0 {test => googlemaps/test}/test_client.py | 10 ++++++---- {test => googlemaps/test}/test_convert.py | 0 {test => googlemaps/test}/test_directions.py | 6 ++++-- {test => googlemaps/test}/test_distance_matrix.py | 6 ++++-- {test => googlemaps/test}/test_elevation.py | 4 +++- {test => googlemaps/test}/test_geocoding.py | 4 +++- {test => googlemaps/test}/test_geolocation.py | 3 ++- {test => googlemaps/test}/test_places.py | 2 +- {test => googlemaps/test}/test_roads.py | 5 +++-- {test => googlemaps/test}/test_timezone.py | 5 +++-- setup.py | 1 + tox.ini | 2 +- 13 files changed, 31 insertions(+), 17 deletions(-) rename {test => googlemaps/test}/__init__.py (100%) rename {test => googlemaps/test}/test_client.py (99%) rename {test => googlemaps/test}/test_convert.py (100%) rename {test => googlemaps/test}/test_directions.py (99%) rename {test => googlemaps/test}/test_distance_matrix.py (99%) rename {test => googlemaps/test}/test_elevation.py (99%) rename {test => googlemaps/test}/test_geocoding.py (99%) rename {test => googlemaps/test}/test_geolocation.py (97%) rename {test => googlemaps/test}/test_places.py (99%) rename {test => googlemaps/test}/test_roads.py (99%) rename {test => googlemaps/test}/test_timezone.py (98%) diff --git a/test/__init__.py b/googlemaps/test/__init__.py similarity index 100% rename from test/__init__.py rename to googlemaps/test/__init__.py diff --git a/test/test_client.py b/googlemaps/test/test_client.py similarity index 99% rename from test/test_client.py rename to googlemaps/test/test_client.py index 79019d36..90d83b3e 100644 --- a/test/test_client.py +++ b/googlemaps/test/test_client.py @@ -18,14 +18,16 @@ """Tests for client module.""" -import responses import time -import googlemaps -from googlemaps import client as _client -import test as _test +import responses import requests +import googlemaps +import googlemaps.client as _client +import googlemaps.test as _test + + class ClientTest(_test.TestCase): def test_no_api_key(self): diff --git a/test/test_convert.py b/googlemaps/test/test_convert.py similarity index 100% rename from test/test_convert.py rename to googlemaps/test/test_convert.py diff --git a/test/test_directions.py b/googlemaps/test/test_directions.py similarity index 99% rename from test/test_directions.py rename to googlemaps/test/test_directions.py index d7d8eb42..de21dc1e 100644 --- a/test/test_directions.py +++ b/googlemaps/test/test_directions.py @@ -19,11 +19,13 @@ from datetime import datetime from datetime import timedelta -import responses import time +import responses + import googlemaps -import test as _test +import googlemaps.test as _test + class DirectionsTest(_test.TestCase): diff --git a/test/test_distance_matrix.py b/googlemaps/test/test_distance_matrix.py similarity index 99% rename from test/test_distance_matrix.py rename to googlemaps/test/test_distance_matrix.py index 966abef8..5e929b24 100644 --- a/test/test_distance_matrix.py +++ b/googlemaps/test/test_distance_matrix.py @@ -20,9 +20,11 @@ from datetime import datetime import time -import googlemaps import responses -import test as _test + +import googlemaps +import googlemaps.test as _test + class DistanceMatrixTest(_test.TestCase): diff --git a/test/test_elevation.py b/googlemaps/test/test_elevation.py similarity index 99% rename from test/test_elevation.py rename to googlemaps/test/test_elevation.py index 6fc990ba..41146939 100644 --- a/test/test_elevation.py +++ b/googlemaps/test/test_elevation.py @@ -18,10 +18,12 @@ """Tests for the elevation module.""" import datetime + import responses import googlemaps -import test as _test +import googlemaps.test as _test + class ElevationTest(_test.TestCase): diff --git a/test/test_geocoding.py b/googlemaps/test/test_geocoding.py similarity index 99% rename from test/test_geocoding.py rename to googlemaps/test/test_geocoding.py index e074c034..511e6d8f 100644 --- a/test/test_geocoding.py +++ b/googlemaps/test/test_geocoding.py @@ -19,10 +19,12 @@ """Tests for the geocoding module.""" import datetime + import responses -import test as _test import googlemaps +import googlemaps.test as _test + class GeocodingTest(_test.TestCase): diff --git a/test/test_geolocation.py b/googlemaps/test/test_geolocation.py similarity index 97% rename from test/test_geolocation.py rename to googlemaps/test/test_geolocation.py index 8b6e1918..64b6f36a 100644 --- a/test/test_geolocation.py +++ b/googlemaps/test/test_geolocation.py @@ -20,8 +20,9 @@ import responses -import test as _test import googlemaps +import googlemaps.test as _test + class GeolocationTest(_test.TestCase): diff --git a/test/test_places.py b/googlemaps/test/test_places.py similarity index 99% rename from test/test_places.py rename to googlemaps/test/test_places.py index 8ae5f221..6bb4aa1e 100644 --- a/test/test_places.py +++ b/googlemaps/test/test_places.py @@ -22,8 +22,8 @@ import responses -import test as _test import googlemaps +import googlemaps.test as _test class PlacesTest(_test.TestCase): diff --git a/test/test_roads.py b/googlemaps/test/test_roads.py similarity index 99% rename from test/test_roads.py rename to googlemaps/test/test_roads.py index 2d0b9c65..6e11b604 100644 --- a/test/test_roads.py +++ b/googlemaps/test/test_roads.py @@ -18,10 +18,11 @@ """Tests for the roads module.""" +import responses + import googlemaps +import googlemaps.test as _test -import responses -import test as _test class RoadsTest(_test.TestCase): diff --git a/test/test_timezone.py b/googlemaps/test/test_timezone.py similarity index 98% rename from test/test_timezone.py rename to googlemaps/test/test_timezone.py index 11127478..23daeca3 100644 --- a/test/test_timezone.py +++ b/googlemaps/test/test_timezone.py @@ -18,12 +18,13 @@ """Tests for the timezone module.""" +import datetime + import responses import mock -import datetime import googlemaps -import test as _test +import googlemaps.test as _test class TimezoneTest(_test.TestCase): diff --git a/setup.py b/setup.py index ec7b2e38..6ab70806 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ platforms='Posix; MacOS X; Windows', setup_requires=requirements, install_requires=requirements, + test_suite='googlemaps.test', classifiers=['Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', diff --git a/tox.ini b/tox.ini index 67b23a7f..77ca5b7e 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = [testenv] commands = - nosetests + nosetests googlemaps/test deps = -rtest_requirements.txt [testenv:docs] From bf153940b56a4cfb34a4fcd9c9b40767c4715c5c Mon Sep 17 00:00:00 2001 From: Nicola Cammillini Date: Wed, 20 Jun 2018 01:26:44 +0200 Subject: [PATCH 108/260] Broken test link (#242) After moving the directory containing tests inside package, link in README is broken. Link now points one directory down, to googlemaps/test. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80d9f468..f71721a4 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ directions_result = gmaps.directions("Sydney Town Hall", departure_time=now) ``` -For more usage examples, check out [the tests](test/). +For more usage examples, check out [the tests](googlemaps/test/). ## Features From ce498cbba072d7acb3b480d73d96ebea02627715 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 25 Jun 2018 16:11:35 +1000 Subject: [PATCH 109/260] Add findplacebytext method and fields params (#234) * Add findplacebytext method and fields params Change-Id: I76c3393a57b0286336cc5c871e24a9f5297ce21d * Add autocomplete session token Change-Id: I70dd571a4442b0c7e4959aaa27094932d7c9ea8d * Add locationbias field to findplacefromtext Change-Id: I8ce8f08acdb7437ee75e7c44a28cb477e84d01ec * Remove alt_id Change-Id: I192d5d6e74977734e406acb0590b426ee227f52e * find_places -> find_place Change-Id: Ibcd2fb904c08f9d3eee8926dac3b4906f4fe241d --- googlemaps/client.py | 2 + googlemaps/places.py | 117 ++++++++++++++++++++++++++++++--- googlemaps/test/test_places.py | 47 +++++++++++-- 3 files changed, 153 insertions(+), 13 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 11d15071..89ad179c 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -339,6 +339,7 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.roads import nearest_roads from googlemaps.roads import speed_limits from googlemaps.roads import snapped_speed_limits +from googlemaps.places import find_place from googlemaps.places import places from googlemaps.places import places_nearby from googlemaps.places import places_radar @@ -382,6 +383,7 @@ def wrapper(*args, **kwargs): Client.nearest_roads = make_api_method(nearest_roads) Client.speed_limits = make_api_method(speed_limits) Client.snapped_speed_limits = make_api_method(snapped_speed_limits) +Client.find_place = make_api_method(find_place) Client.places = make_api_method(places) Client.places_nearby = make_api_method(places_nearby) Client.places_radar = make_api_method(places_radar) diff --git a/googlemaps/places.py b/googlemaps/places.py index 4c95dcbd..70e17c10 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -17,9 +17,87 @@ """Performs requests to the Google Places API.""" +from uuid import uuid4 as places_autocomplete_session_token from googlemaps import convert +PLACES_FIND_FIELDS = set([ + "formatted_address", "geometry", "icon", "id", "name", + "permanently_closed", "photos", "place_id", "scope", "types", + "vicinity", "opening_hours", "price_level", "rating", +]) + +PLACES_DETAIL_FIELDS = set([ + "address_component", "adr_address", "alt_id", "formatted_address", + "geometry", "icon", "id", "name", "permanently_closed", "photo", + "place_id", "scope", "type", "url", "utc_offset", "vicinity", + "formatted_phone_number", "international_phone_number", "opening_hours", + "website", "price_level", "rating", "review", +]) + + +def find_place(client, input, input_type, fields=None, location_bias=None, + language=None): + """ + A Find Place request takes a text input, and returns a place. + The text input can be any kind of Places data, for example, + a name, address, or phone number. + + :param input: The text input specifying which place to search for (for + example, a name, address, or phone number). + :type input: string + + :param input_type: The type of input. This can be one of either 'textquery' + or 'phonenumber'. + :type input_type: string + + :param fields: The fields specifying the types of place data to return, + separated by a comma. For full details see: + https://developers.google.com/places/web-service/search#FindPlaceRequests + :type input: list + + :param location_bias: Prefer results in a specified area, by specifying + either a radius plus lat/lng, or two lat/lng pairs + representing the points of a rectangle. See: + https://developers.google.com/places/web-service/search#FindPlaceRequests + :type location_bias: string + + :param language: The language in which to return results. + :type langauge: string + + :rtype: result dict with the following keys: + status: status code + candidates: list of places + """ + params = {"input": input, "inputtype": input_type} + + if input_type != "textquery" and input_type != "phonenumber": + raise ValueError("Valid values for the `input_type` param for " + "`find_place` are 'textquery' or 'phonenumber', " + "the given value is invalid: '%s'" % input_type) + + if fields: + invalid_fields = set(fields) - PLACES_FIND_FIELDS + if invalid_fields: + raise ValueError("Valid values for the `fields` param for " + "`find_place` are '%s', these given field(s) " + "are invalid: '%s'" % ( + "', '".join(PLACES_FIND_FIELDS), + "', '".join(invalid_fields))) + params["fields"] = convert.join_list(",", fields) + + if location_bias: + valid = ["ipbias", "point", "circle", "rectangle"] + if location_bias.split(":")[0] not in valid: + raise ValueError("location_bias should be prefixed with one of: %s" + % valid) + params["locationbias"] = location_bias + if language: + params["language"] = language + + return client._request("/maps/api/place/findplacefromtext/json", params) + + def places(client, query, location=None, radius=None, language=None, min_price=None, max_price=None, open_now=False, type=None, region=None, page_token=None): @@ -57,7 +135,7 @@ def places(client, query, location=None, radius=None, language=None, The full list of supported types is available here: https://developers.google.com/places/supported_types :type type: string - + :param region: The region code, optional parameter. See more @ https://developers.google.com/places/web-service/search :type region: string @@ -89,7 +167,7 @@ def places_nearby(client, location=None, radius=None, keyword=None, :param radius: Distance in meters within which to bias results. :type radius: int - + :param region: The region code, optional parameter. See more @ https://developers.google.com/places/web-service/search :type region: string @@ -247,7 +325,7 @@ def _places(client, url_part, query=None, location=None, radius=None, return client._request(url, params) -def place(client, place_id, language=None): +def place(client, place_id, fields=None, language=None): """ Comprehensive details for an individual place. @@ -255,6 +333,11 @@ def place(client, place_id, language=None): returned from a Places search. :type place_id: string + :param fields: The fields specifying the types of place data to return, + separated by a comma. For full details see: + https://cloud.google.com/maps-platform/user-guide/product-changes/#places + :type input: list + :param language: The language in which to return results. :type langauge: string @@ -263,8 +346,20 @@ def place(client, place_id, language=None): html_attributions: set of attributions which must be displayed """ params = {"placeid": place_id} + + if fields: + invalid_fields = set(fields) - PLACES_DETAIL_FIELDS + if invalid_fields: + raise ValueError("Valid values for the `fields` param for " + "`place` are '%s', these given field(s) " + "are invalid: '%s'" % ( + "', '".join(PLACES_DETAIL_FIELDS), + "', '".join(invalid_fields))) + params["fields"] = convert.join_list(",", fields) + if language: params["language"] = language + return client._request("/maps/api/place/details/json", params) @@ -313,8 +408,8 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): return response.iter_content() -def places_autocomplete(client, input_text, offset=None, location=None, - radius=None, language=None, types=None, +def places_autocomplete(client, input_text, session_token, offset=None, + location=None, radius=None, language=None, types=None, components=None, strict_bounds=False): """ Returns Place predictions given a textual search string and optional @@ -323,6 +418,10 @@ def places_autocomplete(client, input_text, offset=None, location=None, :param input_text: The text string on which to search. :type input_text: string + :param session_token: A random string which identifies an autocomplete + session for billing purposes. + :type session_token: string + :param offset: The position, in the input term, of the last character that the service uses to match predictions. For example, if the input is 'Google' and the offset is 3, the @@ -392,9 +491,9 @@ def places_autocomplete_query(client, input_text, offset=None, location=None, location=location, radius=radius, language=language) -def _autocomplete(client, url_part, input_text, offset=None, location=None, - radius=None, language=None, types=None, components=None, - strict_bounds=False): +def _autocomplete(client, url_part, input_text, session_token=None, + offset=None, location=None, radius=None, language=None, + types=None, components=None, strict_bounds=False): """ Internal handler for ``autocomplete`` and ``autocomplete_query``. See each method's docs for arg details. @@ -402,6 +501,8 @@ def _autocomplete(client, url_part, input_text, offset=None, location=None, params = {"input": input_text} + if session_token: + params["sessiontoken"] = session_token if offset: params["offset"] = offset if location: diff --git a/googlemaps/test/test_places.py b/googlemaps/test/test_places.py index 6bb4aa1e..37d1e76f 100644 --- a/googlemaps/test/test_places.py +++ b/googlemaps/test/test_places.py @@ -23,6 +23,7 @@ import responses import googlemaps +from googlemaps.places import places_autocomplete_session_token import googlemaps.test as _test @@ -37,6 +38,33 @@ def setUp(self): self.region = 'AU' self.radius = 100 + @responses.activate + def test_places_find(self): + url = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json' + responses.add(responses.GET, url, + body='{"status": "OK", "candidates": []}', + status=200, content_type='application/json') + + self.client.find_place('restaurant', 'textquery', + fields=['geometry', 'id'], + location_bias='point:90,90', + language=self.language) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual('%s?language=en-AU&inputtype=textquery&' + 'locationbias=point:90,90&input=restaurant' + '&fields=geometry,id&key=%s' + % (url, self.key), responses.calls[0].request.url) + + with self.assertRaises(ValueError): + self.client.find_place('restaurant', 'invalid') + with self.assertRaises(ValueError): + self.client.find_place('restaurant', 'textquery', + fields=['geometry', 'invalid']) + with self.assertRaises(ValueError): + self.client.find_place('restaurant', 'textquery', + location_bias='invalid') + @responses.activate def test_places_text_search(self): url = 'https://maps.googleapis.com/maps/api/place/textsearch/json' @@ -109,12 +137,18 @@ def test_place_detail(self): body='{"status": "OK", "result": {}, "html_attributions": []}', status=200, content_type='application/json') - self.client.place('ChIJN1t_tDeuEmsRUsoyG83frY4', language=self.language) + self.client.place('ChIJN1t_tDeuEmsRUsoyG83frY4', + fields=['geometry', 'id'], language=self.language) self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4&key=%s' + self.assertURLEqual('%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4' + '&key=%s&fields=geometry,id' % (url, self.key), responses.calls[0].request.url) + with self.assertRaises(ValueError): + self.client.place('ChIJN1t_tDeuEmsRUsoyG83frY4', + fields=['geometry', 'invalid']) + @responses.activate def test_photo(self): url = 'https://maps.googleapis.com/maps/api/place/photo' @@ -135,7 +169,9 @@ def test_autocomplete(self): body='{"status": "OK", "predictions": []}', status=200, content_type='application/json') - self.client.places_autocomplete('Google', offset=3, + session_token = places_autocomplete_session_token() + + self.client.places_autocomplete('Google', session_token, offset=3, location=self.location, radius=self.radius, language=self.language, @@ -146,8 +182,9 @@ def test_autocomplete(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?components=country%%3Aau&input=Google&language=en-AU&' 'location=-33.86746%%2C151.20709&offset=3&radius=100&' - 'strictbounds=true&types=geocode&key=%s' % - (url, self.key), responses.calls[0].request.url) + 'strictbounds=true&types=geocode&key=%s&sessiontoken=' % + (url, self.key), responses.calls[0].request.url, + session_token) @responses.activate def test_autocomplete_query(self): From d0d00eaf0b6656e4ead15c4c011436cb60660ca6 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 25 Jun 2018 16:19:46 +1000 Subject: [PATCH 110/260] Version 3.0.0 Change-Id: I3b31dba8aa1f8f5ebffb9a35cf4b8d3ede041616 --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index e9a0b779..e658de7a 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "2.5.1-dev" +__version__ = "3.0.0" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 6ab70806..ee98ba4a 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='2.5.1-dev', + version='3.0.0', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 864f1184a8cec56698f4f1019699eabda0f4a158 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 25 Jun 2018 16:26:29 +1000 Subject: [PATCH 111/260] Dev version Change-Id: Ibbe5633812db7e881c91e8fc52385e0161521db9 --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index e658de7a..51286c07 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "3.0.0" +__version__ = "3.0.0-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index ee98ba4a..d1bd6fad 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='3.0.0', + version='3.0.0-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 93d47cb23b317a25d68708b29c08c20b74dc8c43 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 27 Jun 2018 09:54:15 +1000 Subject: [PATCH 112/260] Fix autocomplete sessiontoken. Change-Id: I3de2df5783b3b43b7e11bb02ffb7ec75065a197c --- googlemaps/places.py | 6 +++--- googlemaps/test/test_places.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 70e17c10..e2c38c56 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -455,9 +455,9 @@ def places_autocomplete(client, input_text, session_token, offset=None, :rtype: list of predictions """ - return _autocomplete(client, "", input_text, offset=offset, - location=location, radius=radius, language=language, - types=types, components=components, + return _autocomplete(client, "", input_text, session_token=session_token, + offset=offset, location=location, radius=radius, + language=language, types=types, components=components, strict_bounds=strict_bounds) diff --git a/googlemaps/test/test_places.py b/googlemaps/test/test_places.py index 37d1e76f..77d05afd 100644 --- a/googlemaps/test/test_places.py +++ b/googlemaps/test/test_places.py @@ -182,9 +182,8 @@ def test_autocomplete(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?components=country%%3Aau&input=Google&language=en-AU&' 'location=-33.86746%%2C151.20709&offset=3&radius=100&' - 'strictbounds=true&types=geocode&key=%s&sessiontoken=' % - (url, self.key), responses.calls[0].request.url, - session_token) + 'strictbounds=true&types=geocode&key=%s&sessiontoken=%s' % + (url, self.key, session_token), responses.calls[0].request.url) @responses.activate def test_autocomplete_query(self): From fe7cb74b317de29f46d95fd3ddaf456ae9794d6f Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 27 Jun 2018 11:32:01 +1000 Subject: [PATCH 113/260] Add constants for places fields param by category. Change-Id: Ifa9d792eb7f04083d5f398d0522b33f9bd2b9170 --- googlemaps/places.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index e2c38c56..5fe22815 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -21,20 +21,37 @@ from googlemaps import convert -PLACES_FIND_FIELDS = set([ +PLACES_FIND_FIELDS_BASIC = set([ "formatted_address", "geometry", "icon", "id", "name", - "permanently_closed", "photos", "place_id", "scope", "types", - "vicinity", "opening_hours", "price_level", "rating", + "permanently_closed", "photos", "place_id", "plus_code", "scope", + "types", ]) -PLACES_DETAIL_FIELDS = set([ +PLACES_FIND_FIELDS_CONTACT = set(["opening_hours",]) + +PLACES_FIND_FIELDS_ATMOSPHERE = set(["price_level", "rating"]) + +PLACES_FIND_FIELDS = (PLACES_FIND_FIELDS_BASIC ^ + PLACES_FIND_FIELDS_CONTACT ^ + PLACES_FIND_FIELDS_ATMOSPHERE) + +PLACES_DETAIL_FIELDS_BASIC = set([ "address_component", "adr_address", "alt_id", "formatted_address", "geometry", "icon", "id", "name", "permanently_closed", "photo", - "place_id", "scope", "type", "url", "utc_offset", "vicinity", + "place_id", "plus_code", "scope", "type", "url", "utc_offset", "vicinity", +]) + +PLACES_DETAIL_FIELDS_CONTACT = set([ "formatted_phone_number", "international_phone_number", "opening_hours", - "website", "price_level", "rating", "review", + "website", ]) +PLACES_DETAIL_FIELDS_ATMOSPHERE = set(["price_level", "rating", "review",]) + +PLACES_DETAIL_FIELDS = (PLACES_DETAIL_FIELDS_BASIC ^ + PLACES_DETAIL_FIELDS_CONTACT ^ + PLACES_DETAIL_FIELDS_ATMOSPHERE) + def find_place(client, input, input_type, fields=None, location_bias=None, language=None): From 49f834c4a4f606d3f4a9b0285e7ffd9c90e03785 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 27 Jun 2018 11:34:30 +1000 Subject: [PATCH 114/260] Version 3.0.1 Change-Id: I36de197ecbf52927d409417d2ba7748f97f6b448 --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 51286c07..80330067 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "3.0.0-dev" +__version__ = "3.0.1" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index d1bd6fad..974fecef 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='3.0.0-dev', + version='3.0.1', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 216588d10512e720a7ced134ada5f4075dff51a2 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Wed, 27 Jun 2018 11:38:16 +1000 Subject: [PATCH 115/260] Version 3.0.1-dev Change-Id: Idf1369704fdf3e78abb29c1cdaabed48582bccca --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 80330067..fe54f896 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "3.0.1" +__version__ = "3.0.11-dev" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 974fecef..08530b45 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='3.0.1', + version='3.0.1-dev', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From aa7dc9ce7427dbbb76fc1107a309087e020d2f38 Mon Sep 17 00:00:00 2001 From: Pyglouthon Date: Mon, 2 Jul 2018 01:48:59 +0200 Subject: [PATCH 116/260] Add sessiontoken to place details request to get free request in an autocomplete session (#244) --- googlemaps/places.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 5fe22815..7324c253 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -342,7 +342,7 @@ def _places(client, url_part, query=None, location=None, radius=None, return client._request(url, params) -def place(client, place_id, fields=None, language=None): +def place(client, place_id, session_token=None, fields=None, language=None): """ Comprehensive details for an individual place. @@ -350,13 +350,17 @@ def place(client, place_id, fields=None, language=None): returned from a Places search. :type place_id: string + :param session_token: A random string which identifies an autocomplete + session for billing purposes. + :type session_token: string + :param fields: The fields specifying the types of place data to return, separated by a comma. For full details see: https://cloud.google.com/maps-platform/user-guide/product-changes/#places :type input: list :param language: The language in which to return results. - :type langauge: string + :type language: string :rtype: result dict with the following keys: result: dict containing place details @@ -376,6 +380,8 @@ def place(client, place_id, fields=None, language=None): if language: params["language"] = language + if session_token: + params["sessiontoken"] = session_token return client._request("/maps/api/place/details/json", params) From afdec6575592d7808594473206107060467b437a Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 2 Jul 2018 10:53:39 +1000 Subject: [PATCH 117/260] Version 3.0.2 Change-Id: Ie14e3489fa932fa6ba4f90a79776a8a73b3a1f19 --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index fe54f896..63f904c8 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "3.0.11-dev" +__version__ = "3.0.2" from googlemaps.client import Client import googlemaps.exceptions diff --git a/setup.py b/setup.py index 08530b45..2cad3805 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ] setup(name='googlemaps', - version='3.0.1-dev', + version='3.0.2', description='Python client library for Google Maps API Web Services', scripts=[], url='https://github.com/googlemaps/google-maps-services-python', From 8c4c0c2253e635d2db52bf8762073d029024e17d Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Mon, 20 Aug 2018 09:52:16 +1000 Subject: [PATCH 118/260] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f71721a4..e9a2e9c7 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ instead of an API key. [apikey]: https://developers.google.com/maps/faq#keysystem [clientid]: https://developers.google.com/maps/documentation/business/webservices/auth -[Google Maps API Web Services]: https://developers.google.com/maps/documentation/webservices/ +[Google Maps API Web Services]: https://developers.google.com/maps/apis-by-platform#web_service_apis [Directions API]: https://developers.google.com/maps/documentation/directions/ [directions-key]: https://developers.google.com/maps/documentation/directions/get-api-key#key [directions-client-id]: https://developers.google.com/maps/documentation/directions/get-api-key#client-id From a3d79ab523c5f1a92bde255ca38ea822307886d2 Mon Sep 17 00:00:00 2001 From: Craig Date: Mon, 10 Sep 2018 16:36:12 -0400 Subject: [PATCH 119/260] Do not override headers passed in request_kwargs If headers are passed in as part of the request_kwargs, then make sure they are not overridden as part of the load. This is required if you are limiting your API by using referer. --- googlemaps/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 89ad179c..c4e1d5c1 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -149,8 +149,10 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.channel = channel self.retry_timeout = timedelta(seconds=retry_timeout) self.requests_kwargs = requests_kwargs or {} + headers = self.request_kwargs.pop('headers', {}) + headers.update({"User-Agent": _USER_AGENT}) self.requests_kwargs.update({ - "headers": {"User-Agent": _USER_AGENT}, + "headers": headers, "timeout": self.timeout, "verify": True, # NOTE(cbro): verify SSL certs. }) From f6e02a5bfa2971b0cb56cf7c12f7d9f9e5dc03ca Mon Sep 17 00:00:00 2001 From: Craig Date: Mon, 10 Sep 2018 16:50:26 -0400 Subject: [PATCH 120/260] Update client.py --- googlemaps/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index c4e1d5c1..6c695f31 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -149,7 +149,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.channel = channel self.retry_timeout = timedelta(seconds=retry_timeout) self.requests_kwargs = requests_kwargs or {} - headers = self.request_kwargs.pop('headers', {}) + headers = self.requests_kwargs.pop('headers', {}) headers.update({"User-Agent": _USER_AGENT}) self.requests_kwargs.update({ "headers": headers, From 2b580ca70672c6003125ec926003dafc74f516be Mon Sep 17 00:00:00 2001 From: Yevhen Amelin Date: Sat, 27 Oct 2018 12:07:20 +0300 Subject: [PATCH 121/260] Fix typos in docstrings --- googlemaps/geocoding.py | 4 ++-- googlemaps/places.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index a2913cf9..b665d776 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -43,7 +43,7 @@ def geocode(client, address=None, components=None, bounds=None, region=None, :type region: string :param language: The language in which to return results. - :type langauge: string + :type language: string :rtype: list of geocoding results. """ @@ -85,7 +85,7 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :type location_type: list of strings :param language: The language in which to return results. - :type langauge: string + :type language: string :rtype: list of reverse geocoding results. """ diff --git a/googlemaps/places.py b/googlemaps/places.py index 7324c253..7c2277a9 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -80,7 +80,7 @@ def find_place(client, input, input_type, fields=None, location_bias=None, :type location_bias: string :param language: The language in which to return results. - :type langauge: string + :type language: string :rtype: result dict with the following keys: status: status code @@ -132,7 +132,7 @@ def places(client, query, location=None, radius=None, language=None, :type radius: int :param language: The language in which to return results. - :type langauge: string + :type language: string :param min_price: Restricts results to only those places with no less than this price level. Valid values are in the range from 0 (most affordable) @@ -194,7 +194,7 @@ def places_nearby(client, location=None, radius=None, keyword=None, :type keyword: string :param language: The language in which to return results. - :type langauge: string + :type language: string :param min_price: Restricts results to only those places with no less than this price level. Valid values are in the range from 0 @@ -459,7 +459,7 @@ def places_autocomplete(client, input_text, session_token, offset=None, :type radius: int :param language: The language in which to return results. - :type langauge: string + :type language: string :param types: Restricts the results to places matching the specified type. The full list of supported types is available here: @@ -506,7 +506,7 @@ def places_autocomplete_query(client, input_text, offset=None, location=None, :type radius: number :param language: The language in which to return results. - :type langauge: string + :type language: string :rtype: list of predictions """ From d6c514d6282415d243b990375e2582ed8530be04 Mon Sep 17 00:00:00 2001 From: engstrom Date: Mon, 12 Nov 2018 14:19:14 -0700 Subject: [PATCH 122/260] Increase the required version of requests. Versions of the requests library prior to 2.20.0 have a known security vulnerability (CVE-2018-18074). --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2cad3805..e3bf4286 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ requirements = [ - 'requests>=2.11.1,<3.0', + 'requests>=2.20.0,<3.0', ] setup(name='googlemaps', From 7f7d302b97e05dd12c4a9a687b03781c136101b6 Mon Sep 17 00:00:00 2001 From: Alexander Polekha Date: Thu, 30 May 2019 15:56:56 +0300 Subject: [PATCH 123/260] user_ratings_total field added to place atmosphere fields --- googlemaps/places.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 7c2277a9..534319dd 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -29,7 +29,9 @@ PLACES_FIND_FIELDS_CONTACT = set(["opening_hours",]) -PLACES_FIND_FIELDS_ATMOSPHERE = set(["price_level", "rating"]) +PLACES_FIND_FIELDS_ATMOSPHERE = set([ + "price_level", "rating", "user_ratings_total", +]) PLACES_FIND_FIELDS = (PLACES_FIND_FIELDS_BASIC ^ PLACES_FIND_FIELDS_CONTACT ^ @@ -46,7 +48,9 @@ "website", ]) -PLACES_DETAIL_FIELDS_ATMOSPHERE = set(["price_level", "rating", "review",]) +PLACES_DETAIL_FIELDS_ATMOSPHERE = set([ + "price_level", "rating", "review", "user_ratings_total", +]) PLACES_DETAIL_FIELDS = (PLACES_DETAIL_FIELDS_BASIC ^ PLACES_DETAIL_FIELDS_CONTACT ^ From 6e60b3c0a3e794403b8a7f94bbd52d1f29da15f0 Mon Sep 17 00:00:00 2001 From: David Robles Date: Wed, 7 Aug 2019 13:04:30 -0700 Subject: [PATCH 124/260] Parameter session_token in places_autocomplete should be optional The official docs of the Places API has the session_token parameter as optional. This change makes it optional too in the library to keep it in sync. --- googlemaps/places.py | 2 +- googlemaps/test/test_places.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 534319dd..7f43b13c 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -435,7 +435,7 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): return response.iter_content() -def places_autocomplete(client, input_text, session_token, offset=None, +def places_autocomplete(client, input_text, session_token=None, offset=None, location=None, radius=None, language=None, types=None, components=None, strict_bounds=False): """ diff --git a/googlemaps/test/test_places.py b/googlemaps/test/test_places.py index 77d05afd..4080b55f 100644 --- a/googlemaps/test/test_places.py +++ b/googlemaps/test/test_places.py @@ -171,7 +171,7 @@ def test_autocomplete(self): session_token = places_autocomplete_session_token() - self.client.places_autocomplete('Google', session_token, offset=3, + self.client.places_autocomplete('Google', session_token=session_token, offset=3, location=self.location, radius=self.radius, language=self.language, From 5d8d0061be6bcfef162d4b7bc8caff8b37bf2695 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 19 Aug 2019 15:07:07 -0700 Subject: [PATCH 125/260] remove deprecated places radar (#288) --- googlemaps/client.py | 2 -- googlemaps/places.py | 58 +--------------------------------- googlemaps/test/test_places.py | 20 ------------ 3 files changed, 1 insertion(+), 79 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 6c695f31..9a195c3c 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -344,7 +344,6 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.places import find_place from googlemaps.places import places from googlemaps.places import places_nearby -from googlemaps.places import places_radar from googlemaps.places import place from googlemaps.places import places_photo from googlemaps.places import places_autocomplete @@ -388,7 +387,6 @@ def wrapper(*args, **kwargs): Client.find_place = make_api_method(find_place) Client.places = make_api_method(places) Client.places_nearby = make_api_method(places_nearby) -Client.places_radar = make_api_method(places_radar) Client.place = make_api_method(place) Client.places_photo = make_api_method(places_photo) Client.places_autocomplete = make_api_method(places_autocomplete) diff --git a/googlemaps/places.py b/googlemaps/places.py index 7f43b13c..2daf61aa 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -253,67 +253,11 @@ def places_nearby(client, location=None, radius=None, keyword=None, rank_by=rank_by, type=type, page_token=page_token) -def places_radar(client, location, radius, keyword=None, min_price=None, - max_price=None, name=None, open_now=False, type=None): - """ - Performs radar search for places. - - :param location: The latitude/longitude value for which you wish to obtain the - closest, human-readable address. - :type location: string, dict, list, or tuple - - :param radius: Distance in meters within which to bias results. - :type radius: int - - :param keyword: A term to be matched against all content that Google has - indexed for this place. - :type keyword: string - - :param min_price: Restricts results to only those places with no less than - this price level. Valid values are in the range from 0 - (most affordable) to 4 (most expensive). - :type min_price: int - - :param max_price: Restricts results to only those places with no greater - than this price level. Valid values are in the range - from 0 (most affordable) to 4 (most expensive). - :type max_price: int - - :param name: One or more terms to be matched against the names of places. - :type name: string or list of strings - - :param open_now: Return only those places that are open for business at - the time the query is sent. - :type open_now: bool - - :param type: Restricts the results to places matching the specified type. - The full list of supported types is available here: - https://developers.google.com/places/supported_types - :type type: string - - :rtype: result dict with the following keys: - status: status code - results: list of places - html_attributions: set of attributions which must be displayed - - """ - if not (keyword or name or type): - raise ValueError("either a keyword, name, or type arg is required") - - from warnings import warn - warn("places_radar is deprecated, see http://goo.gl/BGiumE", - DeprecationWarning) - - return _places(client, "radar", location=location, radius=radius, - keyword=keyword, min_price=min_price, max_price=max_price, - name=name, open_now=open_now, type=type) - - def _places(client, url_part, query=None, location=None, radius=None, keyword=None, language=None, min_price=0, max_price=4, name=None, open_now=False, rank_by=None, type=None, region=None, page_token=None): """ - Internal handler for ``places``, ``places_nearby``, and ``places_radar``. + Internal handler for ``places`` and ``places_nearby``. See each method's docs for arg details. """ diff --git a/googlemaps/test/test_places.py b/googlemaps/test/test_places.py index 4080b55f..4a32000a 100644 --- a/googlemaps/test/test_places.py +++ b/googlemaps/test/test_places.py @@ -110,26 +110,6 @@ def test_places_nearby_search(self): self.client.places_nearby(location=self.location, rank_by="distance", keyword='foo', radius=self.radius) - @responses.activate - def test_places_radar_search(self): - url = 'https://maps.googleapis.com/maps/api/place/radarsearch/json' - responses.add(responses.GET, url, - body='{"status": "OK", "results": [], "html_attributions": []}', - status=200, content_type='application/json') - - self.client.places_radar(self.location, self.radius, keyword='foo', - min_price=1, max_price=4, name='bar', - open_now=True, type=self.type) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?keyword=foo&location=-33.86746%%2C151.20709&' - 'maxprice=4&minprice=1&name=bar&opennow=true&radius=100&' - 'type=liquor_store&key=%s' - % (url, self.key), responses.calls[0].request.url) - - with self.assertRaises(ValueError): - self.client.places_radar(self.location, self.radius) - @responses.activate def test_place_detail(self): url = 'https://maps.googleapis.com/maps/api/place/details/json' From 681dabe23ab44c91025b140603e81ef61a57e2c0 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Tue, 20 Aug 2019 10:22:17 -0700 Subject: [PATCH 126/260] fix top level import and remove unused imports (#289) * fix imports and remove unused * use uuid4 directly for session token in test --- googlemaps/__init__.py | 6 +++--- googlemaps/distance_matrix.py | 1 - googlemaps/places.py | 1 - googlemaps/test/test_places.py | 5 +++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 63f904c8..1c94fae5 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -18,7 +18,7 @@ __version__ = "3.0.2" from googlemaps.client import Client -import googlemaps.exceptions +from googlemaps import exceptions -# Allow sphinx to pick up these symbols for the documentation. -__all__ = ["Client"] + +__all__ = ["Client", "exceptions"] diff --git a/googlemaps/distance_matrix.py b/googlemaps/distance_matrix.py index f6a85e8c..1d848253 100755 --- a/googlemaps/distance_matrix.py +++ b/googlemaps/distance_matrix.py @@ -18,7 +18,6 @@ """Performs requests to the Google Maps Distance Matrix API.""" from googlemaps import convert -from googlemaps.convert import as_list def distance_matrix(client, origins, destinations, diff --git a/googlemaps/places.py b/googlemaps/places.py index 2daf61aa..53ca3889 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -17,7 +17,6 @@ """Performs requests to the Google Places API.""" -from uuid import uuid4 as places_autocomplete_session_token from googlemaps import convert diff --git a/googlemaps/test/test_places.py b/googlemaps/test/test_places.py index 4a32000a..17bf03e8 100644 --- a/googlemaps/test/test_places.py +++ b/googlemaps/test/test_places.py @@ -18,12 +18,13 @@ """Tests for the places module.""" +import uuid + from types import GeneratorType import responses import googlemaps -from googlemaps.places import places_autocomplete_session_token import googlemaps.test as _test @@ -149,7 +150,7 @@ def test_autocomplete(self): body='{"status": "OK", "predictions": []}', status=200, content_type='application/json') - session_token = places_autocomplete_session_token() + session_token = uuid.uuid4().hex self.client.places_autocomplete('Google', session_token=session_token, offset=3, location=self.location, From eef6f7cade6325b35e44cd0045c45c29d75b7091 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 22 Aug 2019 09:42:29 -0700 Subject: [PATCH 127/260] add badges for pypi and number of contributors and download (#293) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e9a2e9c7..e17ccbad 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ Python Client for Google Maps Services ==================================== [![Build Status](https://travis-ci.org/googlemaps/google-maps-services-python.svg?branch=master)](https://travis-ci.org/googlemaps/google-maps-services-python) +[![PyPI version](https://badge.fury.io/py/googlemaps.svg)](https://badge.fury.io/py/googlemaps) +![PyPI - Downloads](https://img.shields.io/pypi/dd/googlemaps) +![GitHub contributors](https://img.shields.io/github/contributors/googlemaps/google-maps-services-python) ## Description From e063ee077160ceba93c15e7e718a50bb764195ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Chv=C3=A1tal?= Date: Thu, 22 Aug 2019 19:00:52 +0200 Subject: [PATCH 128/260] Include tests in sdist pypi tarball (#273) --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 4e833047..20b3a1ad 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include LICENSE README.md +recursive-include googlemaps/test *.py global-exclude __pycache__ global-exclude *.py[co] From 8297fd9f1dfd221cb1dd22187bcf410b5c8475cc Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 22 Aug 2019 10:36:23 -0700 Subject: [PATCH 129/260] add long description to setup.py (#292) Add `long_description` to setup.py from the readme and set the `long_description_content_type` so that pypi renders it correctly. --- setup.py | 71 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/setup.py b/setup.py index e3bf4286..31e3edab 100644 --- a/setup.py +++ b/setup.py @@ -1,42 +1,43 @@ import sys - - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup +import io +from setuptools import setup if sys.version_info <= (2, 4): - error = 'Requires Python Version 2.5 or above... exiting.' - print >> sys.stderr, error - sys.exit(1) + error = "Requires Python Version 2.5 or above... exiting." + print >>sys.stderr, error + sys.exit(1) + +requirements = ["requests>=2.20.0,<3.0"] -requirements = [ - 'requests>=2.20.0,<3.0', -] +# use io.open until python2.7 support is dropped +with io.open("README.md", encoding="utf8") as f: + readme = f.read() -setup(name='googlemaps', - version='3.0.2', - description='Python client library for Google Maps API Web Services', - scripts=[], - url='https://github.com/googlemaps/google-maps-services-python', - packages=['googlemaps'], - license='Apache 2.0', - platforms='Posix; MacOS X; Windows', - setup_requires=requirements, - install_requires=requirements, - test_suite='googlemaps.test', - classifiers=['Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Topic :: Internet', - ] - ) +setup( + name="googlemaps", + version="3.0.2", + description="Python client library for Google Maps Platform", + long_description=readme, + long_description_content_type="text/markdown", + scripts=[], + url="https://github.com/googlemaps/google-maps-services-python", + packages=["googlemaps"], + license="Apache 2.0", + platforms="Posix; MacOS X; Windows", + setup_requires=requirements, + install_requires=requirements, + test_suite="googlemaps.test", + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Internet", + ], +) From e2e92382e7ae166112ef80a57c9b76198e821e7d Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 22 Aug 2019 10:38:22 -0700 Subject: [PATCH 130/260] upgrade to pytest and nox with coverage reporting to codecov (#291) - pytest replaces the unmaintained nose - nox replaces tox - coverage reported to codecov.io --- .gitignore | 5 ++-- .travis.yml | 22 +++++++++++------ .travis/install.sh | 11 +++++++++ README.md | 13 +++++----- noxfile.py | 57 +++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 12 +++++++++ test_requirements.txt | 6 +++-- tox.ini | 16 ------------ 8 files changed, 107 insertions(+), 35 deletions(-) create mode 100755 .travis/install.sh create mode 100644 noxfile.py create mode 100644 setup.cfg delete mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 78c58e12..0d2efbbc 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,8 @@ dist/ # python testing things etc .coverage -.tox +.nox env -nosetests.xml googlemaps.egg-info *.egg - +.vscode/ diff --git a/.travis.yml b/.travis.yml index ea1fa63d..87a0fece 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,22 @@ language: python +dist: xenial matrix: include: - - { python: '2.7', env: TOXENV=py27 } - - { python: '3.4', env: TOXENV=py34 } - - { python: '3.5', env: TOXENV=py35 } - - { python: '3.6', env: TOXENV=py36 } - - { python: '3.6', env: TOXENV=docs } + - python: '2.7' + env: NOXSESSION="tests-2.7" + - python: '3.5' + env: NOXSESSION="tests-3.5" + - python: '3.6' + env: NOXSESSION="tests-3.6" + - python: '3.7' + env: NOXSESSION="tests-3.7" + sudo: required # required for Python 3.7 (github.com/travis-ci/travis-ci#9069) + - python: '2.7' + env: NOXSESSION="docs" install: - - pip install requests - - pip install tox +- ./.travis/install.sh script: - - tox +- python3 -m nox --session "$NOXSESSION" diff --git a/.travis/install.sh b/.travis/install.sh new file mode 100755 index 00000000..9dd84bde --- /dev/null +++ b/.travis/install.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -exo pipefail + +if ! python3 -m pip --version; then + curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py + sudo python3 get-pip.py + sudo python3 -m pip install nox +else + python3 -m pip install nox +fi diff --git a/README.md b/README.md index e17ccbad..45e2de6d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Python Client for Google Maps Services ==================================== [![Build Status](https://travis-ci.org/googlemaps/google-maps-services-python.svg?branch=master)](https://travis-ci.org/googlemaps/google-maps-services-python) +[![codecov](https://codecov.io/gh/googlemaps/google-maps-services-python/branch/master/graph/badge.svg)](https://codecov.io/gh/googlemaps/google-maps-services-python) [![PyPI version](https://badge.fury.io/py/googlemaps.svg)](https://badge.fury.io/py/googlemaps) ![PyPI - Downloads](https://img.shields.io/pypi/dd/googlemaps) ![GitHub contributors](https://img.shields.io/github/contributors/googlemaps/google-maps-services-python) @@ -165,14 +166,14 @@ instead of an API key. ## Building the Project - # Installing tox - $ pip install tox + # Installing nox + $ pip install nox # Running tests - $ tox + $ nox # Generating documentation - $ tox -e docs + $ nox -e docs # Uploading a new release $ easy_install wheel twine @@ -180,13 +181,13 @@ instead of an API key. $ twine upload dist/* # Copy docs to gh-pages - $ tox -e docs && mv docs/_build/html generated_docs && git clean -Xdi && git checkout gh-pages + $ nox -e docs && mv docs/_build/html generated_docs && git clean -Xdi && git checkout gh-pages [apikey]: https://developers.google.com/maps/faq#keysystem [clientid]: https://developers.google.com/maps/documentation/business/webservices/auth -[Google Maps API Web Services]: https://developers.google.com/maps/apis-by-platform#web_service_apis +[Google Maps Platform web services]: https://developers.google.com/maps/apis-by-platform#web_service_apis [Directions API]: https://developers.google.com/maps/documentation/directions/ [directions-key]: https://developers.google.com/maps/documentation/directions/get-api-key#key [directions-client-id]: https://developers.google.com/maps/documentation/directions/get-api-key#client-id diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 00000000..f1170918 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,57 @@ +import nox + + +def _install_dev_packages(session): + session.install("-e", ".") + + +def _install_test_dependencies(session): + session.install("-r", "test_requirements.txt") + + +def _install_doc_dependencies(session): + session.install("sphinx") + + +@nox.session(python=["2.7", "3.5", "3.6", "3.7"]) +def tests(session): + _install_dev_packages(session) + _install_test_dependencies(session) + + session.install("pytest") + session.run("pytest") + + session.notify("cover") + + +@nox.session +def cover(session): + """Coverage analysis.""" + session.install("coverage") + session.install("codecov") + session.run("coverage", "report", "--show-missing") + session.run("codecov") + session.run("coverage", "erase") + + +@nox.session(python="2.7") +def docs(session): + _install_dev_packages(session) + _install_doc_dependencies(session) + + session.run("rm", "-rf", "docs/_build", external=True) + + sphinx_args = [ + "-a", + "-E", + "-b", + "html", + "-d", + "docs/_build/doctrees", + "docs", + "docs/_build/html", + ] + + sphinx_cmd = "sphinx-build" + + session.run(sphinx_cmd, *sphinx_args) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..399af724 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,12 @@ +[tool:pytest] +addopts = -rsxX --cov=googlemaps --cov-report= + +[coverage:run] +omit = + googlemaps/test/* + +[coverage:report] +exclude_lines = + pragma: no cover + def __repr__ + raise NotImplementedError \ No newline at end of file diff --git a/test_requirements.txt b/test_requirements.txt index 5646081a..e5a18030 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,2 +1,4 @@ -nose -responses==0.3 +pytest +pytest-cov +responses +mock diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 77ca5b7e..00000000 --- a/tox.ini +++ /dev/null @@ -1,16 +0,0 @@ -[tox] -envlist = - py27,py32,py34,py35,py36,docs - -[testenv] -commands = - nosetests googlemaps/test -deps = -rtest_requirements.txt - -[testenv:docs] -basepython = - python2.7 -commands = - sphinx-build -a -E -b html -d docs/_build/doctrees docs docs/_build/html -deps = - Sphinx From 102dae3936d4678224f3221345be2f7e2b8554e3 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 23 Aug 2019 13:35:47 -0700 Subject: [PATCH 131/260] update readme and direct to documentation for api key (#297) --- README.md | 116 +++++++++++++++++++----------------------------------- 1 file changed, 40 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 45e2de6d..10f9d50f 100644 --- a/README.md +++ b/README.md @@ -9,22 +9,22 @@ Python Client for Google Maps Services ## Description -Use Python? Want to [geocode][Geocoding API] something? Looking for [directions][Directions API]? -Maybe [matrices of directions][Distance Matrix API]? This library brings the [Google Maps API Web -Services] to your Python application. +Use Python? Want to geocode something? Looking for directions? +Maybe matrices of directions? This library brings the Google Maps Platform Web +Services to your Python application. ![Analytics](https://maps-ga-beacon.appspot.com/UA-12846745-20/google-maps-services-python/readme?pixel) The Python Client for Google Maps Services is a Python Client library for the following Google Maps APIs: - - [Directions API] - - [Distance Matrix API] - - [Elevation API] - - [Geocoding API] - - [Geolocation API] - - [Time Zone API] - - [Roads API] - - [Places API] + - Directions API + - Distance Matrix API + - Elevation API + - Geocoding API + - Geolocation API + - Time Zone API + - Roads API + - Places API Keep in mind that the same [terms and conditions](https://developers.google.com/maps/terms) apply to usage of the APIs when they're accessed through this library. @@ -39,42 +39,20 @@ to make backwards-incompatible changes. If we do remove some functionality (typi better functionality exists or if the feature proved infeasible), our intention is to deprecate and give developers a year to update their code. -If you find a bug, or have a feature suggestion, please [log an issue][issues]. If you'd like to -contribute, please read [How to Contribute][contrib]. +If you find a bug, or have a feature suggestion, please log an issue. If you'd like to +contribute, please read contribute. ## Requirements - Python 2.7 or later. - A Google Maps API key. -### API keys +## API Keys Each Google Maps Web Service request requires an API key or client ID. API keys -are freely available with a Google Account at -https://developers.google.com/console. The type of API key you need is a -**Server key**. - -To get an API key: - - 1. Visit https://developers.google.com/console and log in with - a Google Account. - 1. Select one of your existing projects, or create a new project. - 1. Enable the API(s) you want to use. The Python Client for Google Maps Services - accesses the following APIs: - * Directions API - * Distance Matrix API - * Elevation API - * Geocoding API - * Geolocation API - * Places API - * Roads API - * Time Zone API - 1. Create a new **Server key**. - 1. If you'd like to restrict requests to a specific IP address, do so now. - -For guided help, follow the instructions for the [Directions API][directions-key]. -You only need one API key, but remember to enable all the APIs you need. -For even more information, see the guide to [API keys][apikey]. +are generated in the 'Credentials' page of the 'APIs & Services' tab of [Google Cloud console](https://console.cloud.google.com/apis/credentials). + +For even more information on getting started with Google Maps Platform and generating/restricting an API key, see [Get Started with Google Maps Platform](https://developers.google.com/maps/gmp-get-started) in our docs. **Important:** This key should be kept secret on your server. @@ -84,25 +62,9 @@ For even more information, see the guide to [API keys][apikey]. Note that you will need requests 2.4.0 or higher if you want to specify connect/read timeouts. -## Developer Documentation - -View the [reference documentation](https://googlemaps.github.io/google-maps-services-python/docs/) - -Additional documentation for the included web services is available at -https://developers.google.com/maps/. - - - [Directions API] - - [Distance Matrix API] - - [Elevation API] - - [Geocoding API] - - [Geolocation API] - - [Time Zone API] - - [Roads API] - - [Places API] - ## Usage -This example uses the [Geocoding API] and the [Directions API] with an API key: +This example uses the Geocoding API and the Directions API with an API key: ```python import googlemaps @@ -130,7 +92,7 @@ and `client_secret` variables with appropriate values. For a guide on how to generate the `client_secret` (digital signature), see the documentation for the API you're using. For example, see the guide for the -[Directions API][directions-client-id]. +[Directions API](https://developers.google.com/maps/documentation/directions/get-api-key#client-id). ```python gmaps = googlemaps.Client(client_id=client_id, client_secret=client_secret) @@ -160,7 +122,7 @@ are returned from the API. ### Client IDs -Google Maps APIs Premium Plan customers can use their [client ID and secret][clientid] to authenticate, +Google Maps APIs Premium Plan customers can use their client ID and secret to authenticate, instead of an API key. ## Building the Project @@ -183,21 +145,23 @@ instead of an API key. # Copy docs to gh-pages $ nox -e docs && mv docs/_build/html generated_docs && git clean -Xdi && git checkout gh-pages - -[apikey]: https://developers.google.com/maps/faq#keysystem -[clientid]: https://developers.google.com/maps/documentation/business/webservices/auth - -[Google Maps Platform web services]: https://developers.google.com/maps/apis-by-platform#web_service_apis -[Directions API]: https://developers.google.com/maps/documentation/directions/ -[directions-key]: https://developers.google.com/maps/documentation/directions/get-api-key#key -[directions-client-id]: https://developers.google.com/maps/documentation/directions/get-api-key#client-id -[Distance Matrix API]: https://developers.google.com/maps/documentation/distancematrix/ -[Elevation API]: https://developers.google.com/maps/documentation/elevation/ -[Geocoding API]: https://developers.google.com/maps/documentation/geocoding/ -[Geolocation API]: https://developers.google.com/maps/documentation/geolocation/ -[Time Zone API]: https://developers.google.com/maps/documentation/timezone/ -[Roads API]: https://developers.google.com/maps/documentation/roads/ -[Places API]: https://developers.google.com/places/ - -[issues]: https://github.com/googlemaps/google-maps-services-python/issues -[contrib]: https://github.com/googlemaps/google-maps-services-python/blob/master/CONTRIB.md +## Documentation & resources +### Getting started +- [Get Started with Google Maps Platform](https://developers.google.com/maps/gmp-get-started) +- [Generating/restricting an API key](https://developers.google.com/maps/gmp-get-started#api-key) +- [Authenticating with a client ID](https://developers.google.com/maps/documentation/directions/get-api-key#client-id) + +### API docs +- [Google Maps Platform web services](https://developers.google.com/maps/apis-by-platform#web_service_apis) +- [Directions API](https://developers.google.com/maps/documentation/directions/) +- [Distance Matrix API](https://developers.google.com/maps/documentation/distancematrix/) +- [Elevation API](https://developers.google.com/maps/documentation/elevation/) +- [Geocoding API](https://developers.google.com/maps/documentation/geocoding/) +- [Geolocation API](https://developers.google.com/maps/documentation/geolocation/) +- [Time Zone API](https://developers.google.com/maps/documentation/timezone/) +- [Roads API](https://developers.google.com/maps/documentation/roads/) +- [Places API](https://developers.google.com/places/) + +### Support +- [Report an issue](https://github.com/googlemaps/google-maps-services-python/issues) +- [Contribute](https://github.com/googlemaps/google-maps-services-python/blob/master/CONTRIB.md) From 35a0f10d1e43f7de11612f5f954df0546a47fab0 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 23 Aug 2019 13:47:01 -0700 Subject: [PATCH 132/260] automate upload to pypi with travis deploy (#296) --- .travis.yml | 12 ++++++++++++ README.md | 7 +------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 87a0fece..0b7d50fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,15 @@ install: script: - python3 -m nox --session "$NOXSESSION" + +deploy: + on: + repo: googlemaps/google-maps-services-python + tag: true + python: '3.6' # only run this deploy once with python 3.6 + provider: pypi + distributions: 'sdist bdist_wheel' + user: __token__ # api token encrypted within travis + +notifications: + email: false diff --git a/README.md b/README.md index 10f9d50f..b4669df7 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ directions_result = gmaps.directions("Sydney Town Hall", departure_time=now) ``` -For more usage examples, check out [the tests](googlemaps/test/). +For more usage examples, check out [the tests](https://github.com/googlemaps/google-maps-services-python/tree/master/googlemaps/test). ## Features @@ -137,11 +137,6 @@ instead of an API key. # Generating documentation $ nox -e docs - # Uploading a new release - $ easy_install wheel twine - $ python setup.py sdist bdist_wheel - $ twine upload dist/* - # Copy docs to gh-pages $ nox -e docs && mv docs/_build/html generated_docs && git clean -Xdi && git checkout gh-pages From 5439986a94b8f372fd1f697d5dafa9eef7dfe61e Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 26 Aug 2019 11:43:57 -0700 Subject: [PATCH 133/260] explicitly truncate float precision in format_float to 8 decimals, closes #277 (#301) --- googlemaps/convert.py | 11 +++++++---- googlemaps/test/test_convert.py | 16 ++++++++++++++++ googlemaps/test/test_distance_matrix.py | 4 ++-- googlemaps/test/test_geocoding.py | 2 +- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/googlemaps/convert.py b/googlemaps/convert.py index a20e3a32..b5823f67 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -34,9 +34,10 @@ def format_float(arg): """Formats a float value to be as short as possible. - Trims extraneous trailing zeros and period to give API - args the best possible chance of fitting within 2000 char - URL length restrictions. + Truncates float to 8 decimal places and trims extraneous + trailing zeros and period to give API args the best + possible chance of fitting within 2000 char URL length + restrictions. For example: @@ -45,13 +46,15 @@ def format_float(arg): format_float(40.1) -> "40.1" format_float(40.001) -> "40.001" format_float(40.0010) -> "40.001" + format_float(40.000000001) -> "40" + format_float(40.000000009) -> "40.00000001" :param arg: The lat or lng float. :type arg: float :rtype: string """ - return ("%f" % float(arg)).rstrip("0").rstrip(".") + return ("%.8f" % float(arg)).rstrip("0").rstrip(".") def latlng(arg): diff --git a/googlemaps/test/test_convert.py b/googlemaps/test/test_convert.py index ed08c84b..9cbde9ea 100644 --- a/googlemaps/test/test_convert.py +++ b/googlemaps/test/test_convert.py @@ -19,6 +19,7 @@ import datetime import unittest +import pytest from googlemaps import convert @@ -152,3 +153,18 @@ def test_polyline_round_trip(self): points = convert.decode_polyline(test_polyline) actual_polyline = convert.encode_polyline(points) self.assertEqual(test_polyline, actual_polyline) + + +@pytest.mark.parametrize( + "value, expected", + [ + (40, "40"), + (40.0, "40"), + (40.1, "40.1"), + (40.00000001, "40.00000001"), + (40.000000009, "40.00000001"), + (40.000000001, "40"), + ], +) +def test_format_float(value, expected): + assert convert.format_float(value) == expected diff --git a/googlemaps/test/test_distance_matrix.py b/googlemaps/test/test_distance_matrix.py index 5e929b24..83ecfaf3 100644 --- a/googlemaps/test/test_distance_matrix.py +++ b/googlemaps/test/test_distance_matrix.py @@ -80,8 +80,8 @@ def test_mixed_params(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/distancematrix/json?' 'key=%s&origins=Bobcaygeon+ON%%7C41.43206%%2C-81.38992&' - 'destinations=43.012486%%2C-83.696415%%7C42.886386%%2C' - '-78.878163' % self.key, + 'destinations=43.012486%%2C-83.6964149%%7C42.8863855%%2C' + '-78.8781627' % self.key, responses.calls[0].request.url) @responses.activate diff --git a/googlemaps/test/test_geocoding.py b/googlemaps/test/test_geocoding.py index 511e6d8f..0f946850 100644 --- a/googlemaps/test/test_geocoding.py +++ b/googlemaps/test/test_geocoding.py @@ -59,7 +59,7 @@ def test_reverse_geocode(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'latlng=-33.867487%%2C151.20699&key=%s' % self.key, + 'latlng=-33.8674869,151.2069902&key=%s' % self.key, responses.calls[0].request.url) @responses.activate From 393536c4e66a64d1b451c50770ec8a77749c3c24 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Tue, 27 Aug 2019 12:03:11 -0600 Subject: [PATCH 134/260] release version 3.1.0 (#298) --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ setup.py | 8 ++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..a70f1b86 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog +All notable changes to this project will be documented in this file. + +## [Unreleased] +### Changed +### Added +### Removed + +## [v3.1.0] +### Changed +- Switched build system to use [nox](https://nox.thea.codes/en/stable/), pytest, and codecov. Added Python 3.7 to test framework. +- Set precision of truncated latitude and longitude floats [to 8 decimals](https://github.com/googlemaps/google-maps-services-python/pull/301) instead of 6. +- Minimum version of requests increased. +- Session token parameter [added](https://github.com/googlemaps/google-maps-services-python/pull/244) to `place()`. +- Fixed issue where headers in `request_kwargs` were being overridden. +### Added +- Automation for PyPi uploads. +- Long description to package. +- Added tests to manifest and tarball. +### Removed +- Removed places `places_autocomplete_session_token` which can be replaced with `uuid.uuid4().hex`. +- Removed deprecated `places_radar`. + + +**Note:** Start of changelog is 2019-08-27, [v3.0.2]. + +[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.0...HEAD +[v3.1.0]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.2...3.1.0 +[v3.0.2]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.1...3.0.2 +[v3.0.1]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.0...3.0.1 +[v3.0.0]: https://github.com/googlemaps/google-maps-services-python/compare/2.5.1...3.0.0 diff --git a/setup.py b/setup.py index 31e3edab..5d51adab 100644 --- a/setup.py +++ b/setup.py @@ -15,11 +15,15 @@ with io.open("README.md", encoding="utf8") as f: readme = f.read() +with io.open("CHANGELOG.md", encoding="utf8") as f: + changelog = f.read() + + setup( name="googlemaps", - version="3.0.2", + version="3.1.0", description="Python client library for Google Maps Platform", - long_description=readme, + long_description=readme + changelog, long_description_content_type="text/markdown", scripts=[], url="https://github.com/googlemaps/google-maps-services-python", From 20da29d6d29328ca6c3391268d68d119f70aeabf Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Tue, 27 Aug 2019 13:37:11 -0600 Subject: [PATCH 135/260] fix travis deploy (#305) --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0b7d50fa..2dd6c9be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,9 +26,11 @@ deploy: repo: googlemaps/google-maps-services-python tag: true python: '3.6' # only run this deploy once with python 3.6 + branch: env(tag) # branch is equal to tag name provider: pypi distributions: 'sdist bdist_wheel' user: __token__ # api token encrypted within travis + skip_existing: true notifications: email: false From 5c4440c637010f50a912f205c91906e2e298c42a Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Wed, 28 Aug 2019 20:07:36 +0530 Subject: [PATCH 136/260] fix(manifest): include changelog in distribution (#308) currently manifest.in does not include changelog.md, which causes pip install to fail --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 20b3a1ad..fdf9f786 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include LICENSE README.md +include CHANGELOG.md LICENSE README.md recursive-include googlemaps/test *.py global-exclude __pycache__ global-exclude *.py[co] From 15491753130d1eb3911de34bf025433c30339286 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 28 Aug 2019 10:01:58 -0600 Subject: [PATCH 137/260] set version to 3.1.1 (#310) --- CHANGELOG.md | 7 ++++++- setup.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a70f1b86..8f955e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file. ### Added ### Removed +## [v3.1.1] +### Changed +- Added changelog to manifest + ## [v3.1.0] ### Changed - Switched build system to use [nox](https://nox.thea.codes/en/stable/), pytest, and codecov. Added Python 3.7 to test framework. @@ -24,7 +28,8 @@ All notable changes to this project will be documented in this file. **Note:** Start of changelog is 2019-08-27, [v3.0.2]. -[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.0...HEAD +[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.1...HEAD +[v3.1.1]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.0...3.1.1 [v3.1.0]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.2...3.1.0 [v3.0.2]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.1...3.0.2 [v3.0.1]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.0...3.0.1 diff --git a/setup.py b/setup.py index 5d51adab..76ffcc9f 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup( name="googlemaps", - version="3.1.0", + version="3.1.1", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From f79a1eea6ed4bc2414e068336137e5d013f00de5 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 29 Aug 2019 11:46:37 -0600 Subject: [PATCH 138/260] test distribution tar as part of ci (#311) --- .travis.yml | 1 + .travis/distribution.sh | 6 ++++++ CHANGELOG.md | 1 + noxfile.py | 10 +++++++++- 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100755 .travis/distribution.sh diff --git a/.travis.yml b/.travis.yml index 2dd6c9be..e1c65e95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ install: script: - python3 -m nox --session "$NOXSESSION" +- python3 -m nox -e distribution deploy: on: diff --git a/.travis/distribution.sh b/.travis/distribution.sh new file mode 100755 index 00000000..f7c08690 --- /dev/null +++ b/.travis/distribution.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +rm -rf dist + +python setup.py sdist +pip install $(find dist -name googlemaps-*.tar.gz) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f955e0f..05ea6a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Changed ### Added +- Tests for distribution tar as part of CI ### Removed ## [v3.1.1] diff --git a/noxfile.py b/noxfile.py index f1170918..e6fa230a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,5 +1,7 @@ import nox +SUPPORTED_PY_VERSIONS = ["2.7", "3.5", "3.6", "3.7"] + def _install_dev_packages(session): session.install("-e", ".") @@ -13,7 +15,7 @@ def _install_doc_dependencies(session): session.install("sphinx") -@nox.session(python=["2.7", "3.5", "3.6", "3.7"]) +@nox.session(python=SUPPORTED_PY_VERSIONS) def tests(session): _install_dev_packages(session) _install_test_dependencies(session) @@ -55,3 +57,9 @@ def docs(session): sphinx_cmd = "sphinx-build" session.run(sphinx_cmd, *sphinx_args) + + +@nox.session() +def distribution(session): + session.run("bash", ".travis/distribution.sh", external=True) + session.run("python", "-c", "import googlemaps") From d3cd4c07247fcd71d9c1f29c37138aba1f8ddae8 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 30 Aug 2019 10:46:57 -0600 Subject: [PATCH 139/260] add github issue templates (#306) --- .github/ISSUE_TEMPLATE/bug_report.md | 47 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 32 +++++++++++++++ .github/ISSUE_TEMPLATE/support_request.md | 16 ++++++++ README.md | 1 + 4 files changed, 96 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/support_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..b1a36c32 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,47 @@ +--- +name: Bug report +about: Create a report to help us improve +label: type: bug, triage me +--- + +Thanks for stopping by to let us know something could be better! + +--- +**PLEASE READ** + +If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. + +Discover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform [support resources page](https://developers.google.com/maps/support/). + +If your bug or feature request is not related to this particular library, please visit the Google Maps Platform [issue trackers](https://developers.google.com/maps/support/#issue_tracker). + +Check for answers on StackOverflow with the [google-maps](http://stackoverflow.com/questions/tagged/google-maps) tag. + +--- + +Please be sure to include as much information as possible: + +#### Environment details + +1. Specify the API at the beginning of the title (for example, "Places: ...") +2. OS type and version +3. Library version and other environment information + +#### Steps to reproduce + + 1. ? + +#### Code example + +```python +# example +``` + +#### Stack trace +``` +# example +``` + +Following these steps will guarantee the quickest resolution possible. + +Thanks! diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..54fcee5e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,32 @@ +--- +name: Feature request +about: Suggest an idea for this library +label: type: feature request, triage me +--- + +Thanks for stopping by to let us know something could be better! + +--- +**PLEASE READ** + +If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. + +Discover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform [support resources page](https://developers.google.com/maps/support/). + +If your bug or feature request is not related to this particular library, please visit the Google Maps Platform [issue trackers](https://developers.google.com/maps/support/#issue_tracker). + +Check for answers on StackOverflow with the [google-maps](http://stackoverflow.com/questions/tagged/google-maps) tag. + +--- + + **Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + + **Describe the solution you'd like** +A clear and concise description of what you want to happen. + + **Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + + **Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/support_request.md b/.github/ISSUE_TEMPLATE/support_request.md new file mode 100644 index 00000000..4eb13391 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support_request.md @@ -0,0 +1,16 @@ +--- +name: Support request +about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. +label: triage me, type: question +--- +**PLEASE READ** + +If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. + +Discover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform [support resources page](https://developers.google.com/maps/support/). + +If your bug or feature request is not related to this particular library, please visit the Google Maps Platform [issue trackers](https://developers.google.com/maps/support/#issue_tracker). + +Check for answers on StackOverflow with the [google-maps](http://stackoverflow.com/questions/tagged/google-maps) tag. + +--- diff --git a/README.md b/README.md index b4669df7..fdc7d9b2 100644 --- a/README.md +++ b/README.md @@ -160,3 +160,4 @@ instead of an API key. ### Support - [Report an issue](https://github.com/googlemaps/google-maps-services-python/issues) - [Contribute](https://github.com/googlemaps/google-maps-services-python/blob/master/CONTRIB.md) +- [StackOverflow](http://stackoverflow.com/questions/tagged/google-maps) From 91efdd11fc98d36566ec2a32c17c72c305b1b3a1 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 30 Aug 2019 11:07:19 -0600 Subject: [PATCH 140/260] add missing line to fix github templates (#312) --- .github/ISSUE_TEMPLATE/bug_report.md | 1 + .github/ISSUE_TEMPLATE/feature_request.md | 1 + .github/ISSUE_TEMPLATE/support_request.md | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b1a36c32..ad65bce3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,6 +2,7 @@ name: Bug report about: Create a report to help us improve label: type: bug, triage me + --- Thanks for stopping by to let us know something could be better! diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 54fcee5e..8b40f036 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,6 +2,7 @@ name: Feature request about: Suggest an idea for this library label: type: feature request, triage me + --- Thanks for stopping by to let us know something could be better! diff --git a/.github/ISSUE_TEMPLATE/support_request.md b/.github/ISSUE_TEMPLATE/support_request.md index 4eb13391..81602d23 100644 --- a/.github/ISSUE_TEMPLATE/support_request.md +++ b/.github/ISSUE_TEMPLATE/support_request.md @@ -2,6 +2,7 @@ name: Support request about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. label: triage me, type: question + --- **PLEASE READ** From 3afe6586698523ff96272d01c4845e150abe0e53 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 30 Aug 2019 11:26:48 -0600 Subject: [PATCH 141/260] escape github template labels (#313) --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/ISSUE_TEMPLATE/support_request.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ad65bce3..1539cc67 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: Bug report about: Create a report to help us improve -label: type: bug, triage me +label: 'type: bug, triage me' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 8b40f036..557f2315 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,7 @@ --- name: Feature request about: Suggest an idea for this library -label: type: feature request, triage me +label: 'type: feature request, triage me' --- diff --git a/.github/ISSUE_TEMPLATE/support_request.md b/.github/ISSUE_TEMPLATE/support_request.md index 81602d23..f8cade51 100644 --- a/.github/ISSUE_TEMPLATE/support_request.md +++ b/.github/ISSUE_TEMPLATE/support_request.md @@ -1,7 +1,7 @@ --- name: Support request about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. -label: triage me, type: question +label: 'triage me, type: question' --- **PLEASE READ** From b70730aa0490214d8eefec11630463582ee00fb2 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 9 Sep 2019 10:35:12 -0700 Subject: [PATCH 142/260] add support for subfields in mask (#302) --- googlemaps/places.py | 349 +++++++++++++++++++++++---------- googlemaps/test/test_places.py | 8 +- 2 files changed, 253 insertions(+), 104 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 53ca3889..a77f2d85 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -20,44 +20,92 @@ from googlemaps import convert -PLACES_FIND_FIELDS_BASIC = set([ - "formatted_address", "geometry", "icon", "id", "name", - "permanently_closed", "photos", "place_id", "plus_code", "scope", - "types", -]) - -PLACES_FIND_FIELDS_CONTACT = set(["opening_hours",]) - -PLACES_FIND_FIELDS_ATMOSPHERE = set([ - "price_level", "rating", "user_ratings_total", -]) - -PLACES_FIND_FIELDS = (PLACES_FIND_FIELDS_BASIC ^ - PLACES_FIND_FIELDS_CONTACT ^ - PLACES_FIND_FIELDS_ATMOSPHERE) - -PLACES_DETAIL_FIELDS_BASIC = set([ - "address_component", "adr_address", "alt_id", "formatted_address", - "geometry", "icon", "id", "name", "permanently_closed", "photo", - "place_id", "plus_code", "scope", "type", "url", "utc_offset", "vicinity", -]) - -PLACES_DETAIL_FIELDS_CONTACT = set([ - "formatted_phone_number", "international_phone_number", "opening_hours", - "website", -]) - -PLACES_DETAIL_FIELDS_ATMOSPHERE = set([ - "price_level", "rating", "review", "user_ratings_total", -]) - -PLACES_DETAIL_FIELDS = (PLACES_DETAIL_FIELDS_BASIC ^ - PLACES_DETAIL_FIELDS_CONTACT ^ - PLACES_DETAIL_FIELDS_ATMOSPHERE) - - -def find_place(client, input, input_type, fields=None, location_bias=None, - language=None): +PLACES_FIND_FIELDS_BASIC = set( + [ + "formatted_address", + "geometry", + "geometry/location", + "geometry/location/lat", + "geometry/location/lng", + "geometry/viewport", + "geometry/viewport/northeast", + "geometry/viewport/northeast/lat", + "geometry/viewport/northeast/lng", + "geometry/viewport/southwest", + "geometry/viewport/southwest/lat", + "geometry/viewport/southwest/lng", + "icon", + "id", + "name", + "permanently_closed", + "photos", + "place_id", + "plus_code", + "scope", + "types", + ] +) + +PLACES_FIND_FIELDS_CONTACT = set(["opening_hours"]) + +PLACES_FIND_FIELDS_ATMOSPHERE = set(["price_level", "rating", "user_ratings_total"]) + +PLACES_FIND_FIELDS = ( + PLACES_FIND_FIELDS_BASIC + ^ PLACES_FIND_FIELDS_CONTACT + ^ PLACES_FIND_FIELDS_ATMOSPHERE +) + +PLACES_DETAIL_FIELDS_BASIC = set( + [ + "address_component", + "adr_address", + "alt_id", + "formatted_address", + "geometry", + "geometry/location", + "geometry/location/lat", + "geometry/location/lng", + "geometry/viewport", + "geometry/viewport/northeast", + "geometry/viewport/northeast/lat", + "geometry/viewport/northeast/lng", + "geometry/viewport/southwest", + "geometry/viewport/southwest/lat", + "geometry/viewport/southwest/lng", + "icon", + "id", + "name", + "permanently_closed", + "photo", + "place_id", + "plus_code", + "scope", + "type", + "url", + "utc_offset", + "vicinity", + ] +) + +PLACES_DETAIL_FIELDS_CONTACT = set( + ["formatted_phone_number", "international_phone_number", "opening_hours", "website"] +) + +PLACES_DETAIL_FIELDS_ATMOSPHERE = set( + ["price_level", "rating", "review", "user_ratings_total"] +) + +PLACES_DETAIL_FIELDS = ( + PLACES_DETAIL_FIELDS_BASIC + ^ PLACES_DETAIL_FIELDS_CONTACT + ^ PLACES_DETAIL_FIELDS_ATMOSPHERE +) + + +def find_place( + client, input, input_type, fields=None, location_bias=None, language=None +): """ A Find Place request takes a text input, and returns a place. The text input can be any kind of Places data, for example, @@ -92,25 +140,27 @@ def find_place(client, input, input_type, fields=None, location_bias=None, params = {"input": input, "inputtype": input_type} if input_type != "textquery" and input_type != "phonenumber": - raise ValueError("Valid values for the `input_type` param for " - "`find_place` are 'textquery' or 'phonenumber', " - "the given value is invalid: '%s'" % input_type) + raise ValueError( + "Valid values for the `input_type` param for " + "`find_place` are 'textquery' or 'phonenumber', " + "the given value is invalid: '%s'" % input_type + ) if fields: invalid_fields = set(fields) - PLACES_FIND_FIELDS if invalid_fields: - raise ValueError("Valid values for the `fields` param for " - "`find_place` are '%s', these given field(s) " - "are invalid: '%s'" % ( - "', '".join(PLACES_FIND_FIELDS), - "', '".join(invalid_fields))) + raise ValueError( + "Valid values for the `fields` param for " + "`find_place` are '%s', these given field(s) " + "are invalid: '%s'" + % ("', '".join(PLACES_FIND_FIELDS), "', '".join(invalid_fields)) + ) params["fields"] = convert.join_list(",", fields) if location_bias: valid = ["ipbias", "point", "circle", "rectangle"] if location_bias.split(":")[0] not in valid: - raise ValueError("location_bias should be prefixed with one of: %s" - % valid) + raise ValueError("location_bias should be prefixed with one of: %s" % valid) params["locationbias"] = location_bias if language: params["language"] = language @@ -118,9 +168,19 @@ def find_place(client, input, input_type, fields=None, location_bias=None, return client._request("/maps/api/place/findplacefromtext/json", params) -def places(client, query, location=None, radius=None, language=None, - min_price=None, max_price=None, open_now=False, type=None, region=None, - page_token=None): +def places( + client, + query, + location=None, + radius=None, + language=None, + min_price=None, + max_price=None, + open_now=False, + type=None, + region=None, + page_token=None, +): """ Places search. @@ -169,15 +229,36 @@ def places(client, query, location=None, radius=None, language=None, html_attributions: set of attributions which must be displayed next_page_token: token for retrieving the next page of results """ - return _places(client, "text", query=query, location=location, - radius=radius, language=language, min_price=min_price, - max_price=max_price, open_now=open_now, type=type, region=region, - page_token=page_token) - - -def places_nearby(client, location=None, radius=None, keyword=None, - language=None, min_price=None, max_price=None, name=None, - open_now=False, rank_by=None, type=None, page_token=None): + return _places( + client, + "text", + query=query, + location=location, + radius=radius, + language=language, + min_price=min_price, + max_price=max_price, + open_now=open_now, + type=type, + region=region, + page_token=page_token, + ) + + +def places_nearby( + client, + location=None, + radius=None, + keyword=None, + language=None, + min_price=None, + max_price=None, + name=None, + open_now=False, + rank_by=None, + type=None, + page_token=None, +): """ Performs nearby search for places. @@ -240,21 +321,49 @@ def places_nearby(client, location=None, radius=None, keyword=None, raise ValueError("either a location or page_token arg is required") if rank_by == "distance": if not (keyword or name or type): - raise ValueError("either a keyword, name, or type arg is required " - "when rank_by is set to distance") + raise ValueError( + "either a keyword, name, or type arg is required " + "when rank_by is set to distance" + ) elif radius is not None: - raise ValueError("radius cannot be specified when rank_by is set to " - "distance") - - return _places(client, "nearby", location=location, radius=radius, - keyword=keyword, language=language, min_price=min_price, - max_price=max_price, name=name, open_now=open_now, - rank_by=rank_by, type=type, page_token=page_token) - - -def _places(client, url_part, query=None, location=None, radius=None, - keyword=None, language=None, min_price=0, max_price=4, name=None, - open_now=False, rank_by=None, type=None, region=None, page_token=None): + raise ValueError( + "radius cannot be specified when rank_by is set to " "distance" + ) + + return _places( + client, + "nearby", + location=location, + radius=radius, + keyword=keyword, + language=language, + min_price=min_price, + max_price=max_price, + name=name, + open_now=open_now, + rank_by=rank_by, + type=type, + page_token=page_token, + ) + + +def _places( + client, + url_part, + query=None, + location=None, + radius=None, + keyword=None, + language=None, + min_price=0, + max_price=4, + name=None, + open_now=False, + rank_by=None, + type=None, + region=None, + page_token=None, +): """ Internal handler for ``places`` and ``places_nearby``. See each method's docs for arg details. @@ -318,11 +427,12 @@ def place(client, place_id, session_token=None, fields=None, language=None): if fields: invalid_fields = set(fields) - PLACES_DETAIL_FIELDS if invalid_fields: - raise ValueError("Valid values for the `fields` param for " - "`place` are '%s', these given field(s) " - "are invalid: '%s'" % ( - "', '".join(PLACES_DETAIL_FIELDS), - "', '".join(invalid_fields))) + raise ValueError( + "Valid values for the `fields` param for " + "`place` are '%s', these given field(s) " + "are invalid: '%s'" + % ("', '".join(PLACES_DETAIL_FIELDS), "', '".join(invalid_fields)) + ) params["fields"] = convert.join_list(",", fields) if language: @@ -372,15 +482,27 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): # "extract_body" and "stream" args here are used to return an iterable # response containing the image file data, rather than converting from # json. - response = client._request("/maps/api/place/photo", params, - extract_body=lambda response: response, - requests_kwargs={"stream": True}) + response = client._request( + "/maps/api/place/photo", + params, + extract_body=lambda response: response, + requests_kwargs={"stream": True}, + ) return response.iter_content() -def places_autocomplete(client, input_text, session_token=None, offset=None, - location=None, radius=None, language=None, types=None, - components=None, strict_bounds=False): +def places_autocomplete( + client, + input_text, + session_token=None, + offset=None, + location=None, + radius=None, + language=None, + types=None, + components=None, + strict_bounds=False, +): """ Returns Place predictions given a textual search string and optional geographic bounds. @@ -425,14 +547,24 @@ def places_autocomplete(client, input_text, session_token=None, offset=None, :rtype: list of predictions """ - return _autocomplete(client, "", input_text, session_token=session_token, - offset=offset, location=location, radius=radius, - language=language, types=types, components=components, - strict_bounds=strict_bounds) - - -def places_autocomplete_query(client, input_text, offset=None, location=None, - radius=None, language=None): + return _autocomplete( + client, + "", + input_text, + session_token=session_token, + offset=offset, + location=location, + radius=radius, + language=language, + types=types, + components=components, + strict_bounds=strict_bounds, + ) + + +def places_autocomplete_query( + client, input_text, offset=None, location=None, radius=None, language=None +): """ Returns Place predictions given a textual search query, such as "pizza near New York", and optional geographic bounds. @@ -457,13 +589,30 @@ def places_autocomplete_query(client, input_text, offset=None, location=None, :rtype: list of predictions """ - return _autocomplete(client, "query", input_text, offset=offset, - location=location, radius=radius, language=language) - - -def _autocomplete(client, url_part, input_text, session_token=None, - offset=None, location=None, radius=None, language=None, - types=None, components=None, strict_bounds=False): + return _autocomplete( + client, + "query", + input_text, + offset=offset, + location=location, + radius=radius, + language=language, + ) + + +def _autocomplete( + client, + url_part, + input_text, + session_token=None, + offset=None, + location=None, + radius=None, + language=None, + types=None, + components=None, + strict_bounds=False, +): """ Internal handler for ``autocomplete`` and ``autocomplete_query``. See each method's docs for arg details. diff --git a/googlemaps/test/test_places.py b/googlemaps/test/test_places.py index 17bf03e8..f6fa9f8a 100644 --- a/googlemaps/test/test_places.py +++ b/googlemaps/test/test_places.py @@ -47,14 +47,14 @@ def test_places_find(self): status=200, content_type='application/json') self.client.find_place('restaurant', 'textquery', - fields=['geometry', 'id'], + fields=['geometry/location', 'id'], location_bias='point:90,90', language=self.language) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?language=en-AU&inputtype=textquery&' 'locationbias=point:90,90&input=restaurant' - '&fields=geometry,id&key=%s' + '&fields=geometry/location,id&key=%s' % (url, self.key), responses.calls[0].request.url) with self.assertRaises(ValueError): @@ -119,11 +119,11 @@ def test_place_detail(self): status=200, content_type='application/json') self.client.place('ChIJN1t_tDeuEmsRUsoyG83frY4', - fields=['geometry', 'id'], language=self.language) + fields=['geometry/location', 'id'], language=self.language) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4' - '&key=%s&fields=geometry,id' + '&key=%s&fields=geometry/location,id' % (url, self.key), responses.calls[0].request.url) with self.assertRaises(ValueError): From 5817524ca256066e0d1eb5f48855b9f36d309874 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 9 Sep 2019 11:12:06 -0700 Subject: [PATCH 143/260] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05ea6a96..0223b95b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. ### Changed ### Added - Tests for distribution tar as part of CI +- Support for subfields such as `geometry/location` or `geometry/viewport` in Places. + ### Removed ## [v3.1.1] From 32a6a9846f860f0d69fabab7a66afdf7265750be Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 11 Sep 2019 14:15:47 -0700 Subject: [PATCH 144/260] set version to 3.1.2 (#315) --- CHANGELOG.md | 11 +++++++---- setup.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0223b95b..92a9f4f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,13 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Changed ### Added -- Tests for distribution tar as part of CI -- Support for subfields such as `geometry/location` or `geometry/viewport` in Places. - ### Removed +## [v3.1.2] +### Added +- Tests for distribution tar as part of CI +- Support for subfields such as `geometry/location` and `geometry/viewport` in Places. + ## [v3.1.1] ### Changed - Added changelog to manifest @@ -31,7 +33,8 @@ All notable changes to this project will be documented in this file. **Note:** Start of changelog is 2019-08-27, [v3.0.2]. -[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.1...HEAD +[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.2...HEAD +[v3.1.2]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.1...3.1.2 [v3.1.1]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.0...3.1.1 [v3.1.0]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.2...3.1.0 [v3.0.2]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.1...3.0.2 diff --git a/setup.py b/setup.py index 76ffcc9f..812e43f2 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup( name="googlemaps", - version="3.1.1", + version="3.1.2", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From e10f05d8e1b73ddd920b4eeba74d471039c04d30 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 11 Sep 2019 15:16:50 -0700 Subject: [PATCH 145/260] travis conditional: tag to tags (#316) --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e1c65e95..75d8e4c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,10 +24,8 @@ script: deploy: on: - repo: googlemaps/google-maps-services-python - tag: true + tags: true python: '3.6' # only run this deploy once with python 3.6 - branch: env(tag) # branch is equal to tag name provider: pypi distributions: 'sdist bdist_wheel' user: __token__ # api token encrypted within travis From cdf75c73f7a5d21b25049e4de7a99ea69b03be57 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 20 Sep 2019 09:42:57 -0700 Subject: [PATCH 146/260] add deprecation warning for alt_id, id, reference, and scope (#319) --- CHANGELOG.md | 5 ++++- googlemaps/places.py | 23 ++++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92a9f4f3..6ae61f5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,11 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Changed +- deprecation warning for place fields: `alt_id`, `id`, `reference`, and `scope`. Read more about this at https://developers.google.com/maps/deprecations. + +## [v3.1.2] ### Added -### Removed +- Tests for distribution tar as part of CI ## [v3.1.2] ### Added diff --git a/googlemaps/places.py b/googlemaps/places.py index a77f2d85..87110cc1 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -16,6 +16,7 @@ # """Performs requests to the Google Places API.""" +import warnings from googlemaps import convert @@ -35,13 +36,13 @@ "geometry/viewport/southwest/lat", "geometry/viewport/southwest/lng", "icon", - "id", + "id", # deprecated: https://developers.google.com/maps/deprecations "name", "permanently_closed", "photos", "place_id", "plus_code", - "scope", + "scope", # deprecated: https://developers.google.com/maps/deprecations "types", ] ) @@ -60,7 +61,7 @@ [ "address_component", "adr_address", - "alt_id", + "alt_id", # deprecated: https://developers.google.com/maps/deprecations "formatted_address", "geometry", "geometry/location", @@ -74,13 +75,13 @@ "geometry/viewport/southwest/lat", "geometry/viewport/southwest/lng", "icon", - "id", + "id", # deprecated: https://developers.google.com/maps/deprecations "name", "permanently_closed", "photo", "place_id", "plus_code", - "scope", + "scope", # deprecated: https://developers.google.com/maps/deprecations "type", "url", "utc_offset", @@ -102,6 +103,11 @@ ^ PLACES_DETAIL_FIELDS_ATMOSPHERE ) +DEPRECATED_FIELDS = {"alt_id", "id", "reference", "scope"} +DEPRECATED_FIELDS_MESSAGE = ( + "Fields, %s, are deprecated. " + "Read more at https://developers.google.com/maps/deprecations." +) def find_place( client, input, input_type, fields=None, location_bias=None, language=None @@ -147,6 +153,13 @@ def find_place( ) if fields: + deprecated_fields = set(fields) & DEPRECATED_FIELDS + if deprecated_fields: + warnings.warn( + DEPRECATED_FIELDS_MESSAGE % str(list(deprecated_fields)), + DeprecationWarning + ) + invalid_fields = set(fields) - PLACES_FIND_FIELDS if invalid_fields: raise ValueError( From c5e450bc1fc38e0fd607ff6befec7c51812c8ace Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 20 Sep 2019 09:57:30 -0700 Subject: [PATCH 147/260] release 3.1.3 (#320) --- CHANGELOG.md | 8 +++----- setup.py | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ae61f5f..4c322577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v3.1.3] ### Changed - deprecation warning for place fields: `alt_id`, `id`, `reference`, and `scope`. Read more about this at https://developers.google.com/maps/deprecations. -## [v3.1.2] -### Added -- Tests for distribution tar as part of CI - ## [v3.1.2] ### Added - Tests for distribution tar as part of CI @@ -36,7 +33,8 @@ All notable changes to this project will be documented in this file. **Note:** Start of changelog is 2019-08-27, [v3.0.2]. -[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.2...HEAD +[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.3...HEAD +[v3.1.3]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.2...3.1.3 [v3.1.2]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.1...3.1.2 [v3.1.1]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.0...3.1.1 [v3.1.0]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.2...3.1.0 diff --git a/setup.py b/setup.py index 812e43f2..9a901068 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup( name="googlemaps", - version="3.1.2", + version="3.1.3", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 4b1721f09bbf1bc6cd10610306e017bb995bd416 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 2 Oct 2019 23:01:22 -0700 Subject: [PATCH 148/260] add stale config --- .github/stale.yml | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..3adea285 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,59 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 30 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 30 + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: [] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: + - pinned + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: "status: will not fix" + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +# closeComment: > +# Your comment here. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 10 + +# Limit to only `issues` or `pulls` +# only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +# pulls: +# daysUntilStale: 30 +# markComment: > +# This pull request has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. + +# issues: +# exemptLabels: +# - confirmed From 704cbb4a65fc61a0dfcd010d7e42df7ad3de2949 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 2 Oct 2019 23:17:48 -0700 Subject: [PATCH 149/260] modify stale config --- .github/stale.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 3adea285..aadf0f18 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -24,21 +24,21 @@ exemptMilestones: false exemptAssignees: false # Label to use when marking as stale -staleLabel: "status: will not fix" +staleLabel: "stale" # Comment to post when marking as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. + recent activity. Please comment here if it is still valid so that we can + reprioritize. Thank you! # Comment to post when removing the stale label. # unmarkComment: > # Your comment here. # Comment to post when closing a stale Issue or Pull Request. -# closeComment: > -# Your comment here. +closeComment: > + Closing this. Please reopen if you believe it should be addressed. Thank you for your contribution. # Limit the number of actions per hour, from 1-30. Default is 30 limitPerRun: 10 From 866bb21c2dda761bb54aed115d82b01951f08d0f Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 2 Oct 2019 23:34:49 -0700 Subject: [PATCH 150/260] modify stale config --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index aadf0f18..876e12a7 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 30 +daysUntilStale: 90 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. From a3fe2deb7823671e8b48a065637014fb94fee1e0 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 2 Oct 2019 23:53:43 -0700 Subject: [PATCH 151/260] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 4 +++- .github/ISSUE_TEMPLATE/feature_request.md | 4 +++- .github/ISSUE_TEMPLATE/support_request.md | 8 ++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1539cc67..4782add5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,9 @@ --- name: Bug report about: Create a report to help us improve -label: 'type: bug, triage me' +title: '' +labels: 'type: bug, triage me' +assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 557f2315..39c3c5ab 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,9 @@ --- name: Feature request about: Suggest an idea for this library -label: 'type: feature request, triage me' +title: '' +labels: 'type: feature request, triage me' +assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/support_request.md b/.github/ISSUE_TEMPLATE/support_request.md index f8cade51..495a5c99 100644 --- a/.github/ISSUE_TEMPLATE/support_request.md +++ b/.github/ISSUE_TEMPLATE/support_request.md @@ -1,9 +1,13 @@ --- name: Support request -about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. -label: 'triage me, type: question' +about: If you have a support contract with Google, please create an issue in the Google + Cloud Support console. +title: '' +labels: 'triage me, type: question' +assignees: '' --- + **PLEASE READ** If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. From 8cff8e5b1ca9724c72c955345524552c48eca3a6 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 14 Oct 2019 11:22:02 -0600 Subject: [PATCH 152/260] add pr templates --- .github/PULL_REQUEST_TEMPLATE/pull_request_template.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..2bbfe499 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,6 @@ +--- +name: Pull request +about: Create a pull request +label: 'triage me' + +--- From 2947d6123fa94cae9cd27ba931c1ac600c974e6f Mon Sep 17 00:00:00 2001 From: David Robles Date: Fri, 15 Nov 2019 15:31:17 -0800 Subject: [PATCH 153/260] fix: APIError.__str__ should always return a str (#328) --- googlemaps/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/exceptions.py b/googlemaps/exceptions.py index 679b26c3..0a0f116a 100644 --- a/googlemaps/exceptions.py +++ b/googlemaps/exceptions.py @@ -27,7 +27,7 @@ def __init__(self, status, message=None): def __str__(self): if self.message is None: - return self.status + return str(self.status) else: return "%s (%s)" % (self.status, self.message) From 147e7dc5f2fe4ca530b5be4e9abb86f65421e38a Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 15 Nov 2019 16:39:04 -0700 Subject: [PATCH 154/260] chore: release v3.1.4 (#329) --- CHANGELOG.md | 7 ++++++- setup.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c322577..3f4d9f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v3.1.4] +### Changed +- `APIError.__str__` should always return a str (#328) + ## [v3.1.3] ### Changed - deprecation warning for place fields: `alt_id`, `id`, `reference`, and `scope`. Read more about this at https://developers.google.com/maps/deprecations. @@ -33,7 +37,8 @@ All notable changes to this project will be documented in this file. **Note:** Start of changelog is 2019-08-27, [v3.0.2]. -[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.3...HEAD +[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.4...HEAD +[v3.1.4]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.3...3.1.4 [v3.1.3]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.2...3.1.3 [v3.1.2]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.1...3.1.2 [v3.1.1]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.0...3.1.1 diff --git a/setup.py b/setup.py index 9a901068..4fc840b2 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup( name="googlemaps", - version="3.1.3", + version="3.1.4", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 81640b0a76fb741f228996f260a05c6e4a2cb27c Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 19 Dec 2019 14:22:58 -0600 Subject: [PATCH 155/260] fix: remove deprecated place fields (#332) --- CHANGELOG.md | 3 +++ googlemaps/places.py | 20 +------------------- googlemaps/test/test_places.py | 8 ++++---- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4d9f0e..8e3e5558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Removed +- Removed place fields: `alt_id`, `id`, `reference`, and `scope`. Read more about this at https://developers.google.com/maps/deprecations. + ## [v3.1.4] ### Changed - `APIError.__str__` should always return a str (#328) diff --git a/googlemaps/places.py b/googlemaps/places.py index 87110cc1..eb748b35 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -36,13 +36,11 @@ "geometry/viewport/southwest/lat", "geometry/viewport/southwest/lng", "icon", - "id", # deprecated: https://developers.google.com/maps/deprecations "name", "permanently_closed", "photos", "place_id", "plus_code", - "scope", # deprecated: https://developers.google.com/maps/deprecations "types", ] ) @@ -61,7 +59,6 @@ [ "address_component", "adr_address", - "alt_id", # deprecated: https://developers.google.com/maps/deprecations "formatted_address", "geometry", "geometry/location", @@ -75,13 +72,11 @@ "geometry/viewport/southwest/lat", "geometry/viewport/southwest/lng", "icon", - "id", # deprecated: https://developers.google.com/maps/deprecations "name", "permanently_closed", "photo", "place_id", "plus_code", - "scope", # deprecated: https://developers.google.com/maps/deprecations "type", "url", "utc_offset", @@ -103,12 +98,6 @@ ^ PLACES_DETAIL_FIELDS_ATMOSPHERE ) -DEPRECATED_FIELDS = {"alt_id", "id", "reference", "scope"} -DEPRECATED_FIELDS_MESSAGE = ( - "Fields, %s, are deprecated. " - "Read more at https://developers.google.com/maps/deprecations." -) - def find_place( client, input, input_type, fields=None, location_bias=None, language=None ): @@ -152,14 +141,7 @@ def find_place( "the given value is invalid: '%s'" % input_type ) - if fields: - deprecated_fields = set(fields) & DEPRECATED_FIELDS - if deprecated_fields: - warnings.warn( - DEPRECATED_FIELDS_MESSAGE % str(list(deprecated_fields)), - DeprecationWarning - ) - + if fields: invalid_fields = set(fields) - PLACES_FIND_FIELDS if invalid_fields: raise ValueError( diff --git a/googlemaps/test/test_places.py b/googlemaps/test/test_places.py index f6fa9f8a..a21cd8ee 100644 --- a/googlemaps/test/test_places.py +++ b/googlemaps/test/test_places.py @@ -47,14 +47,14 @@ def test_places_find(self): status=200, content_type='application/json') self.client.find_place('restaurant', 'textquery', - fields=['geometry/location', 'id'], + fields=['geometry/location', 'place_id'], location_bias='point:90,90', language=self.language) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?language=en-AU&inputtype=textquery&' 'locationbias=point:90,90&input=restaurant' - '&fields=geometry/location,id&key=%s' + '&fields=geometry/location,place_id&key=%s' % (url, self.key), responses.calls[0].request.url) with self.assertRaises(ValueError): @@ -119,11 +119,11 @@ def test_place_detail(self): status=200, content_type='application/json') self.client.place('ChIJN1t_tDeuEmsRUsoyG83frY4', - fields=['geometry/location', 'id'], language=self.language) + fields=['geometry/location', 'place_id'], language=self.language) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4' - '&key=%s&fields=geometry/location,id' + '&key=%s&fields=geometry/location,place_id' % (url, self.key), responses.calls[0].request.url) with self.assertRaises(ValueError): From 19f6e53dc2ede5852bcb1ae3df9941b6d1e81136 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 19 Dec 2019 14:37:26 -0600 Subject: [PATCH 156/260] build: test and build for python 3 only (#333) --- .travis.yml | 6 +++--- CHANGELOG.md | 3 ++- noxfile.py | 2 +- setup.py | 8 +------- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 75d8e4c4..dc90fb60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ dist: xenial matrix: include: - - python: '2.7' - env: NOXSESSION="tests-2.7" - python: '3.5' env: NOXSESSION="tests-3.5" - python: '3.6' @@ -12,7 +10,9 @@ matrix: - python: '3.7' env: NOXSESSION="tests-3.7" sudo: required # required for Python 3.7 (github.com/travis-ci/travis-ci#9069) - - python: '2.7' + - python: '3.8' + env: NOXSESSION="tests-3.8" + - python: '3.6' env: NOXSESSION="docs" install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e3e5558..40a8a3c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -### Removed +### Changed +- Python 2 is no longer supported - Removed place fields: `alt_id`, `id`, `reference`, and `scope`. Read more about this at https://developers.google.com/maps/deprecations. ## [v3.1.4] diff --git a/noxfile.py b/noxfile.py index e6fa230a..088ce82c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,6 +1,6 @@ import nox -SUPPORTED_PY_VERSIONS = ["2.7", "3.5", "3.6", "3.7"] +SUPPORTED_PY_VERSIONS = ["3.5", "3.6", "3.7", "3.8"] def _install_dev_packages(session): diff --git a/setup.py b/setup.py index 4fc840b2..d5880608 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,6 @@ from setuptools import setup -if sys.version_info <= (2, 4): - error = "Requires Python Version 2.5 or above... exiting." - print >>sys.stderr, error - sys.exit(1) - - requirements = ["requests>=2.20.0,<3.0"] # use io.open until python2.7 support is dropped @@ -38,10 +32,10 @@ "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Topic :: Internet", ], ) From 06f3a1473f6345bf953d7e0e3e7ed9b4ad30675f Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 19 Dec 2019 16:29:15 -0600 Subject: [PATCH 157/260] chore(release): 4.0.0 (#334) --- CHANGELOG.md | 2 +- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40a8a3c7..2ace6be4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog All notable changes to this project will be documented in this file. -## [Unreleased] +## [v4.0.0] ### Changed - Python 2 is no longer supported - Removed place fields: `alt_id`, `id`, `reference`, and `scope`. Read more about this at https://developers.google.com/maps/deprecations. diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 1c94fae5..89660007 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "3.0.2" +__version__ = "4.0.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index d5880608..3f17dd13 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="googlemaps", - version="3.1.4", + version="4.0.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 3a871b06126ed23b13ae710ad63da499b4e55287 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 19 Dec 2019 15:30:47 -0700 Subject: [PATCH 158/260] docs: fix changelog for v4.0.0 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ace6be4..b2ca9736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,8 @@ All notable changes to this project will be documented in this file. **Note:** Start of changelog is 2019-08-27, [v3.0.2]. -[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.4...HEAD +[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/4.0.0...HEAD +[v4.0.0]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.4...4.0.0 [v3.1.4]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.3...3.1.4 [v3.1.3]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.2...3.1.3 [v3.1.2]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.1...3.1.2 From 94c757e28e631fbbe198eb838a8ab55ea8d9dc37 Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Mon, 23 Dec 2019 17:11:12 -0800 Subject: [PATCH 159/260] style: Update pull request template. (#335) * style: Update pull request template. * Add back name, about and label. --- .github/PULL_REQUEST_TEMPLATE/pull_request_template.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index 2bbfe499..4d7d59e9 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -2,5 +2,11 @@ name: Pull request about: Create a pull request label: 'triage me' - --- +Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: +- [ ] Make sure to open a GitHub issue as a bug/feature request before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea +- [ ] Ensure the tests and linter pass +- [ ] Code coverage does not decrease (if any source code was changed) +- [ ] Appropriate docs were updated (if necessary) + +Fixes # 🦕 From e3dcfc3076ca41dc86f1c916cf467aa9bc3df408 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 8 Jan 2020 14:10:20 -0800 Subject: [PATCH 160/260] fix: increase stale bot window --- .github/stale.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 876e12a7..8ed0e080 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,11 +1,11 @@ # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 90 +daysUntilStale: 120 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 30 +daysUntilClose: 180 # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) onlyLabels: [] @@ -13,6 +13,7 @@ onlyLabels: [] # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - pinned + - "type: bug" # Set to true to ignore issues in a project (defaults to false) exemptProjects: false @@ -44,7 +45,7 @@ closeComment: > limitPerRun: 10 # Limit to only `issues` or `pulls` -# only: issues +only: issues # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': # pulls: From 2d6d4a9b1733860daacf421e71e894a1123e8e3a Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Tue, 21 Jan 2020 09:52:02 -0800 Subject: [PATCH 161/260] feat: Adding experience_id support to Client class. (#338) * feat: Adding experience_id support to Client class. * Writing tests. * Adding sample tags. * Use underscore. --- .gitignore | 1 + googlemaps/client.py | 47 +++++++++++++++++-- googlemaps/test/test_client.py | 83 ++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 3 deletions(-) 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, From 700cfeca112d43700697db04a8f981903e9ff222 Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Tue, 21 Jan 2020 18:04:01 -0800 Subject: [PATCH 162/260] chore(release): 4.1.0 (#339) --- CHANGELOG.md | 7 ++++++- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ca9736..ea74f8c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog All notable changes to this project will be documented in this file. +## [v4.1.0] +### Added +- Adding support for passing in `experience_id` to Client class (#338) + ## [v4.0.0] ### Changed - Python 2 is no longer supported @@ -41,7 +45,8 @@ All notable changes to this project will be documented in this file. **Note:** Start of changelog is 2019-08-27, [v3.0.2]. -[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/4.0.0...HEAD +[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/4.1.0...HEAD +[v4.1.0]: https://github.com/googlemaps/google-maps-services-python/compare/4.0.0...4.1.0 [v4.0.0]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.4...4.0.0 [v3.1.4]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.3...3.1.4 [v3.1.3]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.2...3.1.3 diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 89660007..8061142a 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.0.0" +__version__ = "4.1.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 3f17dd13..fb616993 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="googlemaps", - version="4.0.0", + version="4.1.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 7f70f0c0f32e0cbbdd7c1e1ccbbbe60c1c473fc3 Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Fri, 24 Jan 2020 14:06:37 -0800 Subject: [PATCH 163/260] docs(Template): update location of PR template (#340) --- .github/pull_request_template.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..009707d4 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +Thank you for opening a Pull Request! + +--- + +Before submitting your PR, there are a few things you can do to make sure it goes smoothly: +- [ ] Make sure to open a GitHub issue as a bug/feature request before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea +- [ ] Ensure the tests and linter pass +- [ ] Code coverage does not decrease (if any source code was changed) +- [ ] Appropriate docs were updated (if necessary) + +Fixes # 🦕 From 034411ec862b9234767176a8838794f865f532fb Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Thu, 30 Jan 2020 14:25:02 -0800 Subject: [PATCH 164/260] docs(Code of Conduct): adding CODE_OF_CONDUCT.md file. (#341) --- CODE_OF_CONDUCT.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f8b12cb5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,63 @@ +# Google Open Source Community Guidelines + +At Google, we recognize and celebrate the creativity and collaboration of open +source contributors and the diversity of skills, experiences, cultures, and +opinions they bring to the projects and communities they participate in. + +Every one of Google's open source projects and communities are inclusive +environments, based on treating all individuals respectfully, regardless of +gender identity and expression, sexual orientation, disabilities, +neurodiversity, physical appearance, body size, ethnicity, nationality, race, +age, religion, or similar personal characteristic. + +We value diverse opinions, but we value respectful behavior more. + +Respectful behavior includes: + +* Being considerate, kind, constructive, and helpful. +* Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or + physically threatening behavior, speech, and imagery. +* Not engaging in unwanted physical contact. + +Some Google open source projects [may adopt][] an explicit project code of +conduct, which may have additional detailed expectations for participants. Most +of those projects will use our [modified Contributor Covenant][]. + +[may adopt]: https://opensource.google/docs/releasing/preparing/#conduct +[modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/ + +## Resolve peacefully + +We do not believe that all conflict is necessarily bad; healthy debate and +disagreement often yields positive results. However, it is never okay to be +disrespectful. + +If you see someone behaving disrespectfully, you are encouraged to address the +behavior directly with those involved. Many issues can be resolved quickly and +easily, and this gives people more control over the outcome of their dispute. +If you are unable to resolve the matter for any reason, or if the behavior is +threatening or harassing, report it. We are dedicated to providing an +environment where participants feel welcome and safe. + +## Reporting problems + +Some Google open source projects may adopt a project-specific code of conduct. +In those cases, a Google employee will be identified as the Project Steward, +who will receive and handle reports of code of conduct violations. In the event +that a project hasn’t identified a Project Steward, you can report problems by +emailing opensource@google.com. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is +taken. The identity of the reporter will be omitted from the details of the +report supplied to the accused. In potentially harmful situations, such as +ongoing harassment or threats to anyone's safety, we may take action without +notice. + +*This document was adapted from the [IndieWeb Code of Conduct][] and can also +be found at .* + +[IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct From 553e86109b2e5ab04f8ead91b0ec712d8341d03d Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 30 Jan 2020 15:40:29 -0800 Subject: [PATCH 165/260] docs: minimize mention of client id (#342) --- README.md | 29 ----------------------------- googlemaps/client.py | 5 +++-- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index fdc7d9b2..cb222b68 100644 --- a/README.md +++ b/README.md @@ -86,31 +86,6 @@ directions_result = gmaps.directions("Sydney Town Hall", departure_time=now) ``` -Below is the same example, using client ID and client secret (digital signature) -for authentication. This code assumes you have previously loaded the `client_id` -and `client_secret` variables with appropriate values. - -For a guide on how to generate the `client_secret` (digital signature), see the -documentation for the API you're using. For example, see the guide for the -[Directions API](https://developers.google.com/maps/documentation/directions/get-api-key#client-id). - -```python -gmaps = googlemaps.Client(client_id=client_id, client_secret=client_secret) - -# Geocoding and address -geocode_result = gmaps.geocode('1600 Amphitheatre Parkway, Mountain View, CA') - -# Look up an address with reverse geocoding -reverse_geocode_result = gmaps.reverse_geocode((40.714224, -73.961452)) - -# Request directions via public transit -now = datetime.now() -directions_result = gmaps.directions("Sydney Town Hall", - "Parramatta, NSW", - mode="transit", - departure_time=now) -``` - For more usage examples, check out [the tests](https://github.com/googlemaps/google-maps-services-python/tree/master/googlemaps/test). ## Features @@ -120,10 +95,6 @@ For more usage examples, check out [the tests](https://github.com/googlemaps/goo Automatically retry when intermittent failures occur. That is, when any of the retriable 5xx errors are returned from the API. -### Client IDs - -Google Maps APIs Premium Plan customers can use their client ID and secret to authenticate, -instead of an API key. ## Building the Project diff --git a/googlemaps/client.py b/googlemaps/client.py index ac054917..d1136ce4 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -56,14 +56,15 @@ def __init__(self, key=None, client_id=None, client_secret=None, retry_over_query_limit=True, experience_id=None): """ :param key: Maps API key. Required, unless "client_id" and - "client_secret" are set. + "client_secret" are set. Most users should use an API key. :type key: string :param client_id: (for Maps API for Work customers) Your client ID. + Most users should use an API key instead. :type client_id: string :param client_secret: (for Maps API for Work customers) Your client - secret (base64 encoded). + secret (base64 encoded). Most users should use an API key instead. :type client_secret: string :param channel: (for Maps API for Work customers) When set, a channel From 06754323575d6c157ec1ba72f8429bf74344f29e Mon Sep 17 00:00:00 2001 From: romavlasov Date: Wed, 12 Feb 2020 17:23:44 +0300 Subject: [PATCH 166/260] feat: Add support of Maps Static API (#344) --- README.md | 2 + googlemaps/client.py | 17 ++- googlemaps/convert.py | 11 ++ googlemaps/maps.py | 252 ++++++++++++++++++++++++++++++++ googlemaps/test/test_convert.py | 8 + googlemaps/test/test_maps.py | 117 +++++++++++++++ 6 files changed, 404 insertions(+), 3 deletions(-) create mode 100644 googlemaps/maps.py create mode 100644 googlemaps/test/test_maps.py diff --git a/README.md b/README.md index cb222b68..9f221f64 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ APIs: - Time Zone API - Roads API - Places API + - Maps Static API Keep in mind that the same [terms and conditions](https://developers.google.com/maps/terms) apply to usage of the APIs when they're accessed through this library. @@ -127,6 +128,7 @@ are returned from the API. - [Time Zone API](https://developers.google.com/maps/documentation/timezone/) - [Roads API](https://developers.google.com/maps/documentation/roads/) - [Places API](https://developers.google.com/places/) +- [Maps Static API](https://developers.google.com/maps/documentation/maps-static/) ### Support - [Report an issue](https://github.com/googlemaps/google-maps-services-python/issues) diff --git a/googlemaps/client.py b/googlemaps/client.py index d1136ce4..ae2b4891 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -390,6 +390,7 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.places import places_photo from googlemaps.places import places_autocomplete from googlemaps.places import places_autocomplete_query +from googlemaps.maps import static_map def make_api_method(func): @@ -433,6 +434,7 @@ def wrapper(*args, **kwargs): Client.places_photo = make_api_method(places_photo) Client.places_autocomplete = make_api_method(places_autocomplete) Client.places_autocomplete_query = make_api_method(places_autocomplete_query) +Client.static_map = make_api_method(static_map) def sign_hmac(secret, payload): @@ -463,11 +465,17 @@ def urlencode_params(params): """ # urlencode does not handle unicode strings in Python 2. # Firstly, normalize the values so they get encoded correctly. - params = [(key, normalize_for_urlencode(val)) for key, val in params] + extended = [] + for key, val in params: + if isinstance(val, (list, tuple)): + for v in val: + extended.append((key, normalize_for_urlencode(v))) + else: + extended.append((key, normalize_for_urlencode(val))) # Secondly, unquote unreserved chars which are incorrectly quoted # by urllib.urlencode, causing invalid auth signatures. See GH #72 # for more info. - return requests.utils.unquote_unreserved(urlencode(params)) + return requests.utils.unquote_unreserved(urlencode(extended)) try: @@ -489,4 +497,7 @@ def normalize_for_urlencode(value): def normalize_for_urlencode(value): """(Python 3) No-op.""" # urlencode in Python 3 handles all the types we are passing it. - return value + if isinstance(value, str): + return value + + return normalize_for_urlencode(str(value)) diff --git a/googlemaps/convert.py b/googlemaps/convert.py index b5823f67..602cf4d0 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -280,6 +280,17 @@ def bounds(arg): "but got %s" % type(arg).__name__) +def size(arg): + if isinstance(arg, int): + return "%sx%s" % (arg, arg) + elif _is_list(arg): + return "%sx%s" % (arg[0], arg[1]) + + raise TypeError( + "Expected a size int or list, " + "but got %s" % type(arg).__name__) + + def decode_polyline(polyline): """Decodes a Polyline string into a list of lat/lng dicts. diff --git a/googlemaps/maps.py b/googlemaps/maps.py new file mode 100644 index 00000000..eedcc422 --- /dev/null +++ b/googlemaps/maps.py @@ -0,0 +1,252 @@ +# +# Copyright 2020 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Performs requests to the Google Maps Static API.""" + +from googlemaps import convert + + +MAPS_IMAGE_FORMATS = set( + ['png8', 'png', 'png32', 'gif', 'jpg', 'jpg-baseline'] +) + +MAPS_MAP_TYPES = set( + ['roadmap', 'satellite', 'terrain', 'hybrid'] +) + + +class StaticMapParam(object): + """Base class to handle parameters for Maps Static API.""" + + def __init__(self): + self.params = [] + + def __str__(self): + """Converts a list of parameters to the format expected by + the Google Maps server. + + :rtype: str + + """ + return convert.join_list('|', self.params) + + +class StaticMapMarker(StaticMapParam): + """Handles marker parameters for Maps Static API.""" + + def __init__(self, locations, + size=None, color=None, label=None): + """ + :param locations: Specifies the locations of the markers on + the map. + :type locations: list + + :param size: Specifies the size of the marker. + :type size: str + + :param color: Specifies a color of the marker. + :type color: str + + :param label: Specifies a single uppercase alphanumeric + character to be displaied on marker. + :type label: str + """ + + super(StaticMapMarker, self).__init__() + + if size: + self.params.append("size:%s" % size) + + if color: + self.params.append("color:%s" % color) + + if label: + if len(label) != 1 or not label.isupper() or not label.isalnum(): + raise ValueError("Invalid label") + self.params.append("label:%s" % label) + + self.params.append(convert.location_list(locations)) + + +class StaticMapPath(StaticMapParam): + """Handles path parameters for Maps Static API.""" + + def __init__(self, points, + weight=None, color=None, + fillcolor=None, geodesic=None): + """ + :param points: Specifies the point through which the path + will be built. + :type points: list + + :param weight: Specifies the thickness of the path in pixels. + :type weight: int + + :param color: Specifies a color of the path. + :type color: str + + :param fillcolor: Indicates both that the path marks off a + polygonal area and specifies the fill color to use as an + overlay within that area. + :type fillcolor: str + + :param geodesic: Indicates that the requested path should be + interpreted as a geodesic line that follows the curvature + of the earth. + :type geodesic: bool + """ + + super(StaticMapPath, self).__init__() + + if weight: + self.params.append("weight:%s" % weight) + + if color: + self.params.append("color:%s" % color) + + if fillcolor: + self.params.append("fillcolor:%s" % fillcolor) + + if geodesic: + self.params.append("geodesic:%s" % geodesic) + + self.params.append(convert.location_list(points)) + + +def static_map(client, size, + center=None, zoom=None, scale=None, + format=None, maptype=None, language=None, region=None, + markers=None, path=None, visible=None, style=None): + """ + Downloads a map image from the Maps Static API. + + See https://developers.google.com/maps/documentation/maps-static/intro + for more info, including more detail for each parameter below. + + :param size: Defines the rectangular dimensions of the map image. + :type param: int or list + + :param center: Defines the center of the map, equidistant from all edges + of the map. + :type center: dict or list or string + + :param zoom: Defines the zoom level of the map, which determines the + magnification level of the map. + :type zoom: int + + :param scale: Affects the number of pixels that are returned. + :type scale: int + + :param format: Defines the format of the resulting image. + :type format: string + + :param maptype: defines the type of map to construct. There are several + possible maptype values, including roadmap, satellite, hybrid, + and terrain. + :type maptype: string + + :param language: defines the language to use for display of labels on + map tiles. + :type language: string + + :param region: defines the appropriate borders to display, based on + geo-political sensitivities. + :type region: string + + :param markers: define one or more markers to attach to the image at + specified locations. + :type markers: StaticMapMarker + + :param path: defines a single path of two or more connected points to + overlay on the image at specified locations. + :type path: StaticMapPath + + :param visible: specifies one or more locations that should remain visible + on the map, though no markers or other indicators will be displayed. + :type visible: list of dict + + :param style: defines a custom style to alter the presentation of + a specific feature (roads, parks, and other features) of the map. + :type style: list of dict + + :rtype: iterator containing the raw image data, which typically can be + used to save an image file locally. For example: + + ``` + f = open(local_filename, 'wb') + for chunk in client.static_map(size=(400, 400), + center=(52.520103, 13.404871), + zoom=15): + if chunk: + f.write(chunk) + f.close() + ``` + """ + + params = {"size": convert.size(size)} + + if not markers: + if not (center or zoom is not None): + raise ValueError( + "both center and zoom are required" + "when markers is not specifed" + ) + + if center: + params["center"] = convert.latlng(center) + + if zoom is not None: + params["zoom"] = zoom + + if scale is not None: + params["scale"] = scale + + if format: + if format not in MAPS_IMAGE_FORMATS: + raise ValueError("Invalid image format") + params['format'] = format + + if maptype: + if maptype not in MAPS_MAP_TYPES: + raise ValueError("Invalid maptype") + params["maptype"] = maptype + + if language: + params["language"] = language + + if region: + params["region"] = region + + if markers: + params["markers"] = markers + + if path: + params["path"] = path + + if visible: + params["visible"] = convert.location_list(visible) + + if style: + params["style"] = convert.components(style) + + response = client._request( + "/maps/api/staticmap", + params, + extract_body=lambda response: response, + requests_kwargs={"stream": True}, + ) + return response.iter_content() diff --git a/googlemaps/test/test_convert.py b/googlemaps/test/test_convert.py index 9cbde9ea..dc0fe2b1 100644 --- a/googlemaps/test/test_convert.py +++ b/googlemaps/test/test_convert.py @@ -113,6 +113,14 @@ def test_bounds(self): with self.assertRaises(TypeError): convert.bounds("test") + def test_size(self): + self.assertEqual("1x1", convert.size(1)) + + self.assertEqual("2x3", convert.size((2, 3))) + + with self.assertRaises(TypeError): + convert.size("test") + def test_polyline_decode(self): syd_mel_route = ("rvumEis{y[`NsfA~tAbF`bEj^h{@{KlfA~eA~`AbmEghAt~D|e@j" "lRpO~yH_\\v}LjbBh~FdvCxu@`nCplDbcBf_B|wBhIfhCnqEb~D~" diff --git a/googlemaps/test/test_maps.py b/googlemaps/test/test_maps.py new file mode 100644 index 00000000..a20b84fe --- /dev/null +++ b/googlemaps/test/test_maps.py @@ -0,0 +1,117 @@ +# +# Copyright 2020 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the maps module.""" + +from types import GeneratorType + +import responses + +import googlemaps +import googlemaps.test as _test + +from googlemaps.maps import StaticMapMarker +from googlemaps.maps import StaticMapPath + + +class MapsTest(_test.TestCase): + + def setUp(self): + self.key = "AIzaasdf" + self.client = googlemaps.Client(self.key) + + @responses.activate + def test_static_map_marker(self): + marker = StaticMapMarker( + locations=[{"lat": -33.867486, "lng": 151.206990}, "Sydney"], + size='small', color='blue', label="S" + ) + + self.assertEqual( + "size:small|color:blue|label:S|" + "-33.867486,151.20699|Sydney", + str(marker) + ) + + with self.assertRaises(ValueError): + StaticMapMarker(locations=["Sydney"], label="XS") + + @responses.activate + def test_static_map_path(self): + path = StaticMapPath( + points=[{"lat": -33.867486, "lng": 151.206990}, "Sydney"], + weight=5, color="red", geodesic=True, fillcolor="Red" + ) + + self.assertEqual( + "weight:5|color:red|fillcolor:Red|""geodesic:True|" + "-33.867486,151.20699|Sydney", + str(path) + ) + + @responses.activate + def test_download(self): + url = 'https://maps.googleapis.com/maps/api/staticmap' + responses.add(responses.GET, url, status=200) + + path = StaticMapPath( + points=[(62.107733,-145.541936), 'Delta+Junction,AK'], + weight=5, color="red" + ) + + m1 = StaticMapMarker( + locations=[(62.107733,-145.541936)], + color="blue", label="S" + ) + + m2 = StaticMapMarker( + locations=['Delta+Junction,AK'], + size="tiny", color="green" + ) + + m3 = StaticMapMarker( + locations=["Tok,AK"], + size="mid", color="0xFFFF00", label="C" + ) + + response = self.client.static_map( + size=(400, 400), zoom=6, center=(63.259591,-144.667969), + maptype="hybrid", format="png", scale=2, visible=["Tok,AK"], + path=path, markers=[m1, m2, m3] + ) + + self.assertTrue(isinstance(response, GeneratorType)) + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + '%s?center=63.259591%%2C-144.667969&format=png&maptype=hybrid&' + 'markers=color%%3Ablue%%7Clabel%%3AS%%7C62.107733%%2C-145.541936&' + 'markers=size%%3Atiny%%7Ccolor%%3Agreen%%7CDelta%%2BJunction%%2CAK&' + 'markers=size%%3Amid%%7Ccolor%%3A0xFFFF00%%7Clabel%%3AC%%7CTok%%2CAK&' + 'path=weight%%3A5%%7Ccolor%%3Ared%%7C62.107733%%2C-145.541936%%7CDelta%%2BJunction%%2CAK&' + 'scale=2&size=400x400&visible=Tok%%2CAK&zoom=6&key=%s' + % (url, self.key), responses.calls[0].request.url) + + with self.assertRaises(ValueError): + self.client.static_map(size=(400, 400)) + + with self.assertRaises(ValueError): + self.client.static_map(size=(400, 400), center=(63.259591,-144.667969), + zoom=6, format='test') + + with self.assertRaises(ValueError): + self.client.static_map(size=(400, 400), center=(63.259591,-144.667969), + zoom=6, maptype='test') From e27988ecef8139f45c93a306a4b83834ebb35f37 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 12 Feb 2020 06:38:34 -0800 Subject: [PATCH 167/260] docs(README): update requirement to Python 3.5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f221f64..88fb5fe5 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ contribute, please read contribute. ## Requirements - - Python 2.7 or later. + - Python 3.5 or later. - A Google Maps API key. ## API Keys From 3371e100791b017aa55a0c52b5b18d2aee3fea6d Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 12 Feb 2020 07:03:00 -0800 Subject: [PATCH 168/260] chore(release): 4.2.0 (#346) --- CHANGELOG.md | 7 ++++++- googlemaps/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea74f8c1..35deb20a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog All notable changes to this project will be documented in this file. +## [v4.2.0] +### Added +- Add support for Maps Static API (#344) + ## [v4.1.0] ### Added - Adding support for passing in `experience_id` to Client class (#338) @@ -45,7 +49,8 @@ All notable changes to this project will be documented in this file. **Note:** Start of changelog is 2019-08-27, [v3.0.2]. -[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/4.1.0...HEAD +[Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/4.2.0...HEAD +[v4.2.0]: https://github.com/googlemaps/google-maps-services-python/compare/4.1.0...4.2.0 [v4.1.0]: https://github.com/googlemaps/google-maps-services-python/compare/4.0.0...4.1.0 [v4.0.0]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.4...4.0.0 [v3.1.4]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.3...3.1.4 diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 8061142a..d81cb6e9 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.1.0" +__version__ = "4.2.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index fb616993..b65cb9a8 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="googlemaps", - version="4.1.0", + version="4.2.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 6682591dda6f987b193bb9b3bdb8e9d50397d651 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 12 Feb 2020 07:10:08 -0800 Subject: [PATCH 169/260] docs(directions): add note about via in waypoints param closes #239. see https://developers.google.com/maps/documentation/directions/intro#Waypoints for more information. --- googlemaps/directions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/googlemaps/directions.py b/googlemaps/directions.py index f1713dfb..b38a4cdf 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -41,7 +41,9 @@ def directions(client, origin, destination, :type mode: string :param waypoints: Specifies an array of waypoints. Waypoints alter a - route by routing it through the specified location(s). + route by routing it through the specified location(s). To influence + route without adding stop prefix the waypoint with `via`, similar to + `waypoints = ["via:San Francisco", "via:Mountain View"]`. :type waypoints: a single location, or a list of locations, where a location is a string, dict, list, or tuple From c61a1e306f72bcbae365f3487c04d2368a22d30a Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 12 Feb 2020 10:02:19 -0800 Subject: [PATCH 170/260] fix: add python requires attribute to setup.py closes #345 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b65cb9a8..301cd743 100644 --- a/setup.py +++ b/setup.py @@ -38,4 +38,5 @@ "Programming Language :: Python :: 3.8", "Topic :: Internet", ], + python_requires='>=3.5' ) From 03a61f2f0bfe0d5604eefc64400166150f7c3688 Mon Sep 17 00:00:00 2001 From: FredaXin <48503813+FredaXin@users.noreply.github.com> Date: Mon, 24 Feb 2020 17:13:44 -0500 Subject: [PATCH 171/260] docs(README): added link to the github page doc (#347) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 88fb5fe5..2b6ad08d 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,9 @@ are returned from the API. $ nox -e docs && mv docs/_build/html generated_docs && git clean -Xdi && git checkout gh-pages ## Documentation & resources + +[Documentation for the `google-maps-services-python` library](https://googlemaps.github.io/google-maps-services-python/docs/index.html) + ### Getting started - [Get Started with Google Maps Platform](https://developers.google.com/maps/gmp-get-started) - [Generating/restricting an API key](https://developers.google.com/maps/gmp-get-started#api-key) From 9c5bec199a35d12016b7e088d278c5d0cb16e44c Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Tue, 24 Mar 2020 02:38:49 +0530 Subject: [PATCH 172/260] build: test and build docs with python3 (#351) --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 088ce82c..9f4024d4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -36,7 +36,7 @@ def cover(session): session.run("coverage", "erase") -@nox.session(python="2.7") +@nox.session(python="3.6") def docs(session): _install_dev_packages(session) _install_doc_dependencies(session) From 7b5648bfaf1be2d951b6b761a2ed05246399435f Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Thu, 23 Apr 2020 16:45:21 -0700 Subject: [PATCH 173/260] build: Adding test, release, and publish workflows (#359) * build: Adding build and release workflows. * Adding .releaserc * Adding publish workflow. * Rename to test.yml * s/__version/__version__ * Remove travis. --- {.travis => .github/scripts}/distribution.sh | 0 {.travis => .github/scripts}/install.sh | 0 .github/workflows/publish.yml | 48 ++++++++++++++++++++ .github/workflows/release.yml | 37 +++++++++++++++ .github/workflows/test.yml | 46 +++++++++++++++++++ .releaserc | 22 +++++++++ .travis.yml | 35 -------------- noxfile.py | 2 +- 8 files changed, 154 insertions(+), 36 deletions(-) rename {.travis => .github/scripts}/distribution.sh (100%) rename {.travis => .github/scripts}/install.sh (100%) create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 .releaserc delete mode 100644 .travis.yml diff --git a/.travis/distribution.sh b/.github/scripts/distribution.sh similarity index 100% rename from .travis/distribution.sh rename to .github/scripts/distribution.sh diff --git a/.travis/install.sh b/.github/scripts/install.sh similarity index 100% rename from .travis/install.sh rename to .github/scripts/install.sh diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..a0ea83af --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,48 @@ +# Copyright 2020 Google LLC +# +# 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. + +# A workflow that pushes artifacts to Sonatype +name: Publish + +on: + push: + tags: + - '*' + repository_dispatch: + types: [publish] + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Python + uses: "actions/setup-python@v1" + with: + python-version: "3.6" + + - name: Install dependencies + run: ./.github/scripts/install.sh + + - name: Run distribution + run: python3 -m nox -e distribution + + - name: Deploy to PyPi + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..bebedf46 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,37 @@ +# Copyright 2020 Google LLC +# +# 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. + +name: Release +on: + push: + branches: [ master ] +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} + - name: Semantic Release + uses: cycjimmy/semantic-release-action@v2 + with: + extra_plugins: | + "@semantic-release/commit-analyzer" + "@semantic-release/release-notes-generator" + "@google/semantic-release-replace-plugin" + "@semantic-release/git + "@semantic-release/github + env: + GH_TOKEN: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..53ec5a12 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,46 @@ +# Copyright 2020 Google LLC +# +# 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. + +# A workflow that runs tests on every new pull request +name: Run tests + +on: + repository_dispatch: + types: [test] + pull_request: + branches: ['*'] + push: + branches: ['*'] + +jobs: + test: + name: "Run tests on Python ${{ matrix.python-version }}" + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup Python + uses: "actions/setup-python@v1" + with: + python-version: "${{ matrix.python-version }}" + + - name: Install dependencies + run: ./.github/scripts/install.sh + + - name: Run tests + run: | + python3 -m nox --session "tests-${{ matrix.python-version }}" + python3 -m nox -e distribution diff --git a/.releaserc b/.releaserc new file mode 100644 index 00000000..981e4031 --- /dev/null +++ b/.releaserc @@ -0,0 +1,22 @@ +branches: + - master +plugins: + - "@semantic-release/commit-analyzer" + - "@semantic-release/release-notes-generator" + - - "@google/semantic-release-replace-plugin" + - replacements: + - files: + - "./googlemaps/__init__.py" + from: "__version__ = \".*\"" + to: "__version__ = \"${nextRelease.version}\"" + - files: + - "./setup.py" + from: "version=\".*\"" + to: "version=\"${nextRelease.version}\"" + - - "@semantic-release/git" + - assets: + - "./googlemaps/__init__.py" + - "./setup.py" + - "@semantic-release/github" +options: + debug: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index dc90fb60..00000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -language: python -dist: xenial - -matrix: - include: - - python: '3.5' - env: NOXSESSION="tests-3.5" - - python: '3.6' - env: NOXSESSION="tests-3.6" - - python: '3.7' - env: NOXSESSION="tests-3.7" - sudo: required # required for Python 3.7 (github.com/travis-ci/travis-ci#9069) - - python: '3.8' - env: NOXSESSION="tests-3.8" - - python: '3.6' - env: NOXSESSION="docs" - -install: -- ./.travis/install.sh - -script: -- python3 -m nox --session "$NOXSESSION" -- python3 -m nox -e distribution - -deploy: - on: - tags: true - python: '3.6' # only run this deploy once with python 3.6 - provider: pypi - distributions: 'sdist bdist_wheel' - user: __token__ # api token encrypted within travis - skip_existing: true - -notifications: - email: false diff --git a/noxfile.py b/noxfile.py index 9f4024d4..bdb7477e 100644 --- a/noxfile.py +++ b/noxfile.py @@ -61,5 +61,5 @@ def docs(session): @nox.session() def distribution(session): - session.run("bash", ".travis/distribution.sh", external=True) + session.run("bash", ".github/scripts/distribution.sh", external=True) session.run("python", "-c", "import googlemaps") From 0f7c01066d1dfc7492fb4d54ef8d3c2155e79d36 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 23 Apr 2020 23:46:16 +0000 Subject: [PATCH 174/260] chore(release): 4.2.1 [skip ci] ## [4.2.1](https://github.com/googlemaps/google-maps-services-python/compare/v4.2.0...v4.2.1) (2020-04-23) ### Bug Fixes * add python requires attribute to setup.py ([c61a1e3](https://github.com/googlemaps/google-maps-services-python/commit/c61a1e306f72bcbae365f3487c04d2368a22d30a)), closes [#345](https://github.com/googlemaps/google-maps-services-python/issues/345) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index d81cb6e9..64b606a9 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.2.0" +__version__ = "4.2.1" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 301cd743..73bc6a20 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="googlemaps", - version="4.2.0", + version="4.2.1", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 68d35e6adb23799b262f46ac9ad2ba1bcd4473f8 Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Thu, 23 Apr 2020 16:56:53 -0700 Subject: [PATCH 175/260] chore: Fix test.yml spacing. --- .github/workflows/test.yml | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 53ec5a12..527b8db8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,20 +27,19 @@ jobs: test: name: "Run tests on Python ${{ matrix.python-version }}" runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Setup Python - uses: "actions/setup-python@v1" - with: - python-version: "${{ matrix.python-version }}" - - - name: Install dependencies - run: ./.github/scripts/install.sh - - - name: Run tests - run: | - python3 -m nox --session "tests-${{ matrix.python-version }}" - python3 -m nox -e distribution + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup Python + uses: "actions/setup-python@v1" + with: + python-version: "${{ matrix.python-version }}" + + - name: Install dependencies + run: ./.github/scripts/install.sh + + - name: Run tests + run: | + python3 -m nox --session "tests-${{ matrix.python-version }}" + python3 -m nox -e distribution From fc69668c5c28395a4935f212cb67c43f2a0eb566 Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Fri, 24 Apr 2020 20:11:05 +0530 Subject: [PATCH 176/260] fix: use timezone-aware timestamp() instead of timetuple() (#350) fixes issue where convert.time ignores timezone in the datetime object. current implementation: times = [ datetime(2020, 3, 14, 8, 0, tzinfo=timezone.utc), datetime(2020, 3, 14, 9, 0, tzinfo=timezone(timedelta(hours=1))), datetime(2020, 3, 14, 8, 0, tzinfo=timezone(timedelta(hours=1))) ] for time in times: print(convert.time(time)) output: 1584153000 1584156600 1584153000 technically, as @atollena describes, the first two times should be considered to be the same, whereas the third datetime instance is expected be an hour behind the first two instances. but by using timetuple(), the specified timezone is ignored. so, using the timestamp() method instead of timetuple() ensures that the specified timezone is respected: new implementation (assuming the same list of times from before): for time in times: print(convert.time(time)) output: 1584172800 1584172800 1584169200 here, the specified timezone data is respected, and as expected the third datetime instance is 3600 seconds behind the first two instances. since python2 support has now been dropped, this can safely be included in the package. Signed-off-by: Chinmay D. Pai --- googlemaps/convert.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/googlemaps/convert.py b/googlemaps/convert.py index 602cf4d0..7dfa9882 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -28,8 +28,6 @@ # '-33.8674869,151.2069902' """ -import time as _time - def format_float(arg): """Formats a float value to be as short as possible. @@ -187,8 +185,8 @@ def time(arg): :type arg: datetime.datetime or int """ # handle datetime instances. - if _has_method(arg, "timetuple"): - arg = _time.mktime(arg.timetuple()) + if _has_method(arg, "timestamp"): + arg = arg.timestamp() if isinstance(arg, float): arg = int(arg) From 0c4aa3ccbb0bd142c3ee82466eef9248085211a7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 24 Apr 2020 14:41:58 +0000 Subject: [PATCH 177/260] chore(release): 4.2.2 [skip ci] ## [4.2.2](https://github.com/googlemaps/google-maps-services-python/compare/v4.2.1...v4.2.2) (2020-04-24) ### Bug Fixes * use timezone-aware timestamp() instead of timetuple() ([#350](https://github.com/googlemaps/google-maps-services-python/issues/350)) ([fc69668](https://github.com/googlemaps/google-maps-services-python/commit/fc69668c5c28395a4935f212cb67c43f2a0eb566)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 64b606a9..fa073a41 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.2.1" +__version__ = "4.2.2" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 73bc6a20..27680192 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="googlemaps", - version="4.2.1", + version="4.2.2", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 5c3fedb2e3993b822db26b2fa5cab3c031d4d21e Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Fri, 24 Apr 2020 09:17:35 -0700 Subject: [PATCH 178/260] chore: Install setuptools --- .github/scripts/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/install.sh b/.github/scripts/install.sh index 9dd84bde..4d7872ec 100755 --- a/.github/scripts/install.sh +++ b/.github/scripts/install.sh @@ -5,7 +5,7 @@ set -exo pipefail if ! python3 -m pip --version; then curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py sudo python3 get-pip.py - sudo python3 -m pip install nox + sudo python3 -m pip install setuptools nox else - python3 -m pip install nox + python3 -m pip install setuptools nox fi From e0cfe650731c969dedde0326a0fe40e578d397bd Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Fri, 24 Apr 2020 09:24:11 -0700 Subject: [PATCH 179/260] chore: Upgrade setuptools. --- .github/scripts/install.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/scripts/install.sh b/.github/scripts/install.sh index 4d7872ec..5cd76712 100755 --- a/.github/scripts/install.sh +++ b/.github/scripts/install.sh @@ -5,7 +5,9 @@ set -exo pipefail if ! python3 -m pip --version; then curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py sudo python3 get-pip.py - sudo python3 -m pip install setuptools nox + sudo python3 -m pip install --upgrade setuptools + sudo python3 -m pip install nox else - python3 -m pip install setuptools nox + sudo python3 -m pip install --upgrade setuptools + python3 -m pip install nox fi From 4609fce5b80a18182732af93b0c9322c76552f9f Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Fri, 24 Apr 2020 09:35:17 -0700 Subject: [PATCH 180/260] chore: Adding job strategy. --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 527b8db8..58839686 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,6 +27,9 @@ jobs: test: name: "Run tests on Python ${{ matrix.python-version }}" runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.5", "3.6", "3.7", "3.8"] steps: - name: Checkout repository uses: actions/checkout@v2 From 6eeb48a7d89575a1f8da68c56fef0e717f9737e5 Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Mon, 27 Apr 2020 08:52:18 -0700 Subject: [PATCH 181/260] feat: Add business_status (#356) --- googlemaps/places.py | 2 ++ googlemaps/test/test_places.py | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index eb748b35..1ead5fe4 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -23,6 +23,7 @@ PLACES_FIND_FIELDS_BASIC = set( [ + "business_status", "formatted_address", "geometry", "geometry/location", @@ -59,6 +60,7 @@ [ "address_component", "adr_address", + "business_status", "formatted_address", "geometry", "geometry/location", diff --git a/googlemaps/test/test_places.py b/googlemaps/test/test_places.py index a21cd8ee..c2ce4ec9 100644 --- a/googlemaps/test/test_places.py +++ b/googlemaps/test/test_places.py @@ -47,14 +47,14 @@ def test_places_find(self): status=200, content_type='application/json') self.client.find_place('restaurant', 'textquery', - fields=['geometry/location', 'place_id'], + fields=['business_status', 'geometry/location', 'place_id'], location_bias='point:90,90', language=self.language) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?language=en-AU&inputtype=textquery&' 'locationbias=point:90,90&input=restaurant' - '&fields=geometry/location,place_id&key=%s' + '&fields=business_status,geometry/location,place_id&key=%s' % (url, self.key), responses.calls[0].request.url) with self.assertRaises(ValueError): @@ -119,11 +119,12 @@ def test_place_detail(self): status=200, content_type='application/json') self.client.place('ChIJN1t_tDeuEmsRUsoyG83frY4', - fields=['geometry/location', 'place_id'], language=self.language) + fields=['business_status', 'geometry/location', 'place_id'], + language=self.language) self.assertEqual(1, len(responses.calls)) self.assertURLEqual('%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4' - '&key=%s&fields=geometry/location,place_id' + '&key=%s&fields=business_status,geometry/location,place_id' % (url, self.key), responses.calls[0].request.url) with self.assertRaises(ValueError): From 15ca2c7314dfd280dbd7e5d318ba0bce15c67eaa Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 27 Apr 2020 15:53:09 +0000 Subject: [PATCH 182/260] chore(release): 4.3.0 [skip ci] # [4.3.0](https://github.com/googlemaps/google-maps-services-python/compare/v4.2.2...v4.3.0) (2020-04-27) ### Features * Add business_status ([#356](https://github.com/googlemaps/google-maps-services-python/issues/356)) ([6eeb48a](https://github.com/googlemaps/google-maps-services-python/commit/6eeb48a7d89575a1f8da68c56fef0e717f9737e5)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index fa073a41..49ad240f 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.2.2" +__version__ = "4.3.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 27680192..5be33d49 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="googlemaps", - version="4.2.2", + version="4.3.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 428b5c9d76d0c5c5e5000cbec8fc9ace810ac856 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 27 Apr 2020 10:11:54 -0700 Subject: [PATCH 183/260] fix: cleanup test and remove from package (#361) --- MANIFEST.in | 1 - googlemaps/test/test_directions.py | 278 -------------- googlemaps/test/test_distance_matrix.py | 150 -------- googlemaps/test/test_elevation.py | 116 ------ googlemaps/test/test_geocoding.py | 283 -------------- googlemaps/test/test_places.py | 181 --------- googlemaps/test/test_roads.py | 137 ------- noxfile.py | 4 +- setup.cfg | 2 +- setup.py | 8 +- test_requirements.txt | 4 - {googlemaps/test => tests}/__init__.py | 6 +- {googlemaps/test => tests}/test_client.py | 234 +++++++----- {googlemaps/test => tests}/test_convert.py | 59 +-- tests/test_directions.py | 325 ++++++++++++++++ tests/test_distance_matrix.py | 183 +++++++++ tests/test_elevation.py | 134 +++++++ tests/test_geocoding.py | 348 ++++++++++++++++++ .../test => tests}/test_geolocation.py | 25 +- {googlemaps/test => tests}/test_maps.py | 76 ++-- tests/test_places.py | 249 +++++++++++++ tests/test_roads.py | 158 ++++++++ {googlemaps/test => tests}/test_timezone.py | 56 +-- 23 files changed, 1654 insertions(+), 1363 deletions(-) delete mode 100644 googlemaps/test/test_directions.py delete mode 100644 googlemaps/test/test_distance_matrix.py delete mode 100644 googlemaps/test/test_elevation.py delete mode 100644 googlemaps/test/test_geocoding.py delete mode 100644 googlemaps/test/test_places.py delete mode 100644 googlemaps/test/test_roads.py delete mode 100644 test_requirements.txt rename {googlemaps/test => tests}/__init__.py (90%) rename {googlemaps/test => tests}/test_client.py (65%) rename {googlemaps/test => tests}/test_convert.py (68%) create mode 100644 tests/test_directions.py create mode 100644 tests/test_distance_matrix.py create mode 100644 tests/test_elevation.py create mode 100644 tests/test_geocoding.py rename {googlemaps/test => tests}/test_geolocation.py (63%) rename {googlemaps/test => tests}/test_maps.py (52%) create mode 100644 tests/test_places.py create mode 100644 tests/test_roads.py rename {googlemaps/test => tests}/test_timezone.py (56%) diff --git a/MANIFEST.in b/MANIFEST.in index fdf9f786..b248a6b8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ include CHANGELOG.md LICENSE README.md -recursive-include googlemaps/test *.py global-exclude __pycache__ global-exclude *.py[co] diff --git a/googlemaps/test/test_directions.py b/googlemaps/test/test_directions.py deleted file mode 100644 index de21dc1e..00000000 --- a/googlemaps/test/test_directions.py +++ /dev/null @@ -1,278 +0,0 @@ -# -# Copyright 2014 Google Inc. All rights reserved. -# -# -# 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. -# - -"""Tests for the directions module.""" - -from datetime import datetime -from datetime import timedelta -import time - -import responses - -import googlemaps -import googlemaps.test as _test - - -class DirectionsTest(_test.TestCase): - - def setUp(self): - self.key = 'AIzaasdf' - self.client = googlemaps.Client(self.key) - - @responses.activate - def test_simple_directions(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - # Simplest directions request. Driving directions by default. - routes = self.client.directions("Sydney", "Melbourne") - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json' - '?origin=Sydney&destination=Melbourne&key=%s' % - self.key, - responses.calls[0].request.url) - - @responses.activate - def test_complex_request(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - routes = self.client.directions("Sydney", "Melbourne", - mode="bicycling", - avoid=["highways", "tolls", "ferries"], - units="metric", - region="us") - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?' - 'origin=Sydney&avoid=highways%%7Ctolls%%7Cferries&' - 'destination=Melbourne&mode=bicycling&key=%s' - '&units=metric®ion=us' % - self.key, - responses.calls[0].request.url) - - - def test_transit_without_time(self): - # With mode of transit, we need a departure_time or an - # arrival_time specified - with self.assertRaises(googlemaps.exceptions.ApiError): - self.client.directions("Sydney Town Hall", "Parramatta, NSW", - mode="transit") - - @responses.activate - def test_transit_with_departure_time(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - now = datetime.now() - routes = self.client.directions("Sydney Town Hall", "Parramatta, NSW", - mode="transit", - traffic_model="optimistic", - departure_time=now) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?origin=' - 'Sydney+Town+Hall&key=%s&destination=Parramatta%%2C+NSW&' - 'mode=transit&departure_time=%d&traffic_model=optimistic' % - (self.key, time.mktime(now.timetuple())), - responses.calls[0].request.url) - - @responses.activate - def test_transit_with_arrival_time(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - an_hour_from_now = datetime.now() + timedelta(hours=1) - routes = self.client.directions("Sydney Town Hall", - "Parramatta, NSW", - mode="transit", - arrival_time=an_hour_from_now) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?' - 'origin=Sydney+Town+Hall&arrival_time=%d&' - 'destination=Parramatta%%2C+NSW&mode=transit&key=%s' % - (time.mktime(an_hour_from_now.timetuple()), self.key), - responses.calls[0].request.url) - - - def test_invalid_travel_mode(self): - with self.assertRaises(ValueError): - self.client.directions("48 Pirrama Road, Pyrmont, NSW", - "Sydney Town Hall", - mode="crawling") - - @responses.activate - def test_travel_mode_round_trip(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - routes = self.client.directions("Town Hall, Sydney", - "Parramatta, NSW", - mode="bicycling") - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?' - 'origin=Town+Hall%%2C+Sydney&destination=Parramatta%%2C+NSW&' - 'mode=bicycling&key=%s' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_brooklyn_to_queens_by_transit(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - now = datetime.now() - routes = self.client.directions("Brooklyn", - "Queens", - mode="transit", - departure_time=now) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?' - 'origin=Brooklyn&key=%s&destination=Queens&mode=transit&' - 'departure_time=%d' % (self.key, time.mktime(now.timetuple())), - responses.calls[0].request.url) - - @responses.activate - def test_boston_to_concord_via_charlestown_and_lexington(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - routes = self.client.directions("Boston, MA", - "Concord, MA", - waypoints=["Charlestown, MA", - "Lexington, MA"]) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?' - 'origin=Boston%%2C+MA&destination=Concord%%2C+MA&' - 'waypoints=Charlestown%%2C+MA%%7CLexington%%2C+MA&' - 'key=%s' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_adelaide_wine_tour(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - routes = self.client.directions("Adelaide, SA", - "Adelaide, SA", - waypoints=["Barossa Valley, SA", - "Clare, SA", - "Connawarra, SA", - "McLaren Vale, SA"], - optimize_waypoints=True) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?' - 'origin=Adelaide%%2C+SA&destination=Adelaide%%2C+SA&' - 'waypoints=optimize%%3Atrue%%7CBarossa+Valley%%2C+' - 'SA%%7CClare%%2C+SA%%7CConnawarra%%2C+SA%%7CMcLaren+' - 'Vale%%2C+SA&key=%s' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_toledo_to_madrid_in_spain(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - routes = self.client.directions("Toledo", "Madrid", - region="es") - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?' - 'origin=Toledo®ion=es&destination=Madrid&key=%s' % - self.key, - responses.calls[0].request.url) - - @responses.activate - def test_zero_results_returns_response(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"ZERO_RESULTS","routes":[]}', - status=200, - content_type='application/json') - - routes = self.client.directions("Toledo", "Madrid") - self.assertIsNotNone(routes) - self.assertEqual(0, len(routes)) - - @responses.activate - def test_language_parameter(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - routes = self.client.directions("Toledo", "Madrid", - region="es", - language="es") - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?' - 'origin=Toledo®ion=es&destination=Madrid&key=%s&' - 'language=es' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_alternatives(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/directions/json', - body='{"status":"OK","routes":[]}', - status=200, - content_type='application/json') - - routes = self.client.directions("Sydney Town Hall", - "Parramatta Town Hall", - alternatives=True) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/directions/json?' - 'origin=Sydney+Town+Hall&destination=Parramatta+Town+Hall&' - 'alternatives=true&key=%s' % self.key, - responses.calls[0].request.url) - diff --git a/googlemaps/test/test_distance_matrix.py b/googlemaps/test/test_distance_matrix.py deleted file mode 100644 index 83ecfaf3..00000000 --- a/googlemaps/test/test_distance_matrix.py +++ /dev/null @@ -1,150 +0,0 @@ -# -# Copyright 2014 Google Inc. All rights reserved. -# -# -# 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. -# - -"""Tests for the distance matrix module.""" - -from datetime import datetime -import time - -import responses - -import googlemaps -import googlemaps.test as _test - - -class DistanceMatrixTest(_test.TestCase): - - def setUp(self): - self.key = 'AIzaasdf' - self.client = googlemaps.Client(self.key) - - @responses.activate - def test_basic_params(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/distancematrix/json', - body='{"status":"OK","rows":[]}', - status=200, - content_type='application/json') - - origins = ["Perth, Australia", "Sydney, Australia", - "Melbourne, Australia", "Adelaide, Australia", - "Brisbane, Australia", "Darwin, Australia", - "Hobart, Australia", "Canberra, Australia"] - destinations = ["Uluru, Australia", - "Kakadu, Australia", - "Blue Mountains, Australia", - "Bungle Bungles, Australia", - "The Pinnacles, Australia"] - - matrix = self.client.distance_matrix(origins, destinations) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/distancematrix/json?' - 'key=%s&origins=Perth%%2C+Australia%%7CSydney%%2C+' - 'Australia%%7CMelbourne%%2C+Australia%%7CAdelaide%%2C+' - 'Australia%%7CBrisbane%%2C+Australia%%7CDarwin%%2C+' - 'Australia%%7CHobart%%2C+Australia%%7CCanberra%%2C+Australia&' - 'destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7C' - 'Blue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia' - '%%7CThe+Pinnacles%%2C+Australia' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_mixed_params(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/distancematrix/json', - body='{"status":"OK","rows":[]}', - status=200, - content_type='application/json') - - origins = ["Bobcaygeon ON", [41.43206, -81.38992]] - destinations = [(43.012486, -83.6964149), - {"lat": 42.8863855, "lng": -78.8781627}] - - matrix = self.client.distance_matrix(origins, destinations) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/distancematrix/json?' - 'key=%s&origins=Bobcaygeon+ON%%7C41.43206%%2C-81.38992&' - 'destinations=43.012486%%2C-83.6964149%%7C42.8863855%%2C' - '-78.8781627' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_all_params(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/distancematrix/json', - body='{"status":"OK","rows":[]}', - status=200, - content_type='application/json') - - origins = ["Perth, Australia", "Sydney, Australia", - "Melbourne, Australia", "Adelaide, Australia", - "Brisbane, Australia", "Darwin, Australia", - "Hobart, Australia", "Canberra, Australia"] - destinations = ["Uluru, Australia", - "Kakadu, Australia", - "Blue Mountains, Australia", - "Bungle Bungles, Australia", - "The Pinnacles, Australia"] - - now = datetime.now() - matrix = self.client.distance_matrix(origins, destinations, - mode="driving", - language="en-AU", - avoid="tolls", - units="imperial", - departure_time=now, - traffic_model="optimistic") - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/distancematrix/json?' - 'origins=Perth%%2C+Australia%%7CSydney%%2C+Australia%%7C' - 'Melbourne%%2C+Australia%%7CAdelaide%%2C+Australia%%7C' - 'Brisbane%%2C+Australia%%7CDarwin%%2C+Australia%%7CHobart%%2C+' - 'Australia%%7CCanberra%%2C+Australia&language=en-AU&' - 'avoid=tolls&mode=driving&key=%s&units=imperial&' - 'destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7C' - 'Blue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia' - '%%7CThe+Pinnacles%%2C+Australia&departure_time=%d' - '&traffic_model=optimistic' % - (self.key, time.mktime(now.timetuple())), - responses.calls[0].request.url) - - - @responses.activate - def test_lang_param(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/distancematrix/json', - body='{"status":"OK","rows":[]}', - status=200, - content_type='application/json') - - origins = ["Vancouver BC", "Seattle"] - destinations = ["San Francisco", "Victoria BC"] - - matrix = self.client.distance_matrix(origins, destinations, - language="fr-FR", - mode="bicycling") - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/distancematrix/json?' - 'key=%s&language=fr-FR&mode=bicycling&' - 'origins=Vancouver+BC%%7CSeattle&' - 'destinations=San+Francisco%%7CVictoria+BC' % - self.key, - responses.calls[0].request.url) diff --git a/googlemaps/test/test_elevation.py b/googlemaps/test/test_elevation.py deleted file mode 100644 index 41146939..00000000 --- a/googlemaps/test/test_elevation.py +++ /dev/null @@ -1,116 +0,0 @@ -# -# Copyright 2014 Google Inc. All rights reserved. -# -# -# 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. -# - -"""Tests for the elevation module.""" - -import datetime - -import responses - -import googlemaps -import googlemaps.test as _test - - -class ElevationTest(_test.TestCase): - - def setUp(self): - self.key = 'AIzaasdf' - self.client = googlemaps.Client(self.key) - - @responses.activate - def test_elevation_single(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/elevation/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.elevation((40.714728, -73.998672)) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' - 'locations=enc:abowFtzsbM&key=%s' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_elevation_single_list(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/elevation/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.elevation([(40.714728, -73.998672)]) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' - 'locations=enc:abowFtzsbM&key=%s' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_elevation_multiple(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/elevation/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - locations = [(40.714728, -73.998672), (-34.397, 150.644)] - results = self.client.elevation(locations) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' - 'locations=enc:abowFtzsbMhgmiMuobzi@&key=%s' % self.key, - responses.calls[0].request.url) - - def test_elevation_along_path_single(self): - with self.assertRaises(googlemaps.exceptions.ApiError): - results = self.client.elevation_along_path( - [(40.714728, -73.998672)], 5) - - @responses.activate - def test_elevation_along_path(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/elevation/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - path = [(40.714728, -73.998672), (-34.397, 150.644)] - - results = self.client.elevation_along_path(path, 5) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' - 'path=enc:abowFtzsbMhgmiMuobzi@&' - 'key=%s&samples=5' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_short_latlng(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/elevation/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.elevation((40, -73)) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/elevation/json?' - 'locations=40,-73&key=%s' % self.key, - responses.calls[0].request.url) diff --git a/googlemaps/test/test_geocoding.py b/googlemaps/test/test_geocoding.py deleted file mode 100644 index 0f946850..00000000 --- a/googlemaps/test/test_geocoding.py +++ /dev/null @@ -1,283 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Copyright 2014 Google Inc. All rights reserved. -# -# -# 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. -# - -"""Tests for the geocoding module.""" - -import datetime - -import responses - -import googlemaps -import googlemaps.test as _test - - -class GeocodingTest(_test.TestCase): - - def setUp(self): - self.key = 'AIzaasdf' - self.client = googlemaps.Client(self.key) - - @responses.activate - def test_simple_geocode(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.geocode('Sydney') - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'key=%s&address=Sydney' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_reverse_geocode(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.reverse_geocode((-33.8674869, 151.2069902)) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'latlng=-33.8674869,151.2069902&key=%s' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_geocoding_the_googleplex(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.geocode('1600 Amphitheatre Parkway, ' - 'Mountain View, CA') - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'key=%s&address=1600+Amphitheatre+Parkway%%2C+Mountain' - '+View%%2C+CA' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_geocode_with_bounds(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.geocode('Winnetka', - bounds={'southwest': (34.172684, -118.604794), - 'northeast':(34.236144, -118.500938)}) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'bounds=34.172684%%2C-118.604794%%7C34.236144%%2C' - '-118.500938&key=%s&address=Winnetka' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_geocode_with_region_biasing(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.geocode('Toledo', region='es') - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'region=es&key=%s&address=Toledo' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_geocode_with_component_filter(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.geocode('santa cruz', - components={'country': 'ES'}) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'key=%s&components=country%%3AES&address=santa+cruz' % - self.key, - responses.calls[0].request.url) - - @responses.activate - def test_geocode_with_multiple_component_filters(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.geocode('Torun', - components={'administrative_area': 'TX','country': 'US'}) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'key=%s&components=administrative_area%%3ATX%%7C' - 'country%%3AUS&address=Torun' % self.key, - responses.calls[0].request.url) - - - @responses.activate - def test_geocode_with_just_components(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.geocode( - components={'route': 'Annegatan', - 'administrative_area': 'Helsinki', - 'country': 'Finland'}) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'key=%s&components=administrative_area%%3AHelsinki' - '%%7Ccountry%%3AFinland%%7Croute%%3AAnnegatan' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_simple_reverse_geocode(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.reverse_geocode((40.714224, -73.961452)) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'latlng=40.714224%%2C-73.961452&key=%s' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_reverse_geocode_restricted_by_type(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.reverse_geocode((40.714224, -73.961452), - location_type='ROOFTOP', - result_type='street_address') - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'latlng=40.714224%%2C-73.961452&result_type=street_address&' - 'key=%s&location_type=ROOFTOP' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_reverse_geocode_multiple_location_types(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.reverse_geocode((40.714224, -73.961452), - location_type=['ROOFTOP', - 'RANGE_INTERPOLATED'], - result_type='street_address') - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'latlng=40.714224%%2C-73.961452&result_type=street_address&' - 'key=%s&location_type=ROOFTOP%%7CRANGE_INTERPOLATED' % - self.key, - responses.calls[0].request.url) - - @responses.activate - def test_reverse_geocode_multiple_result_types(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.reverse_geocode((40.714224, -73.961452), - location_type='ROOFTOP', - result_type=['street_address', - 'route']) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'latlng=40.714224%%2C-73.961452&result_type=street_address' - '%%7Croute&key=%s&location_type=ROOFTOP' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_partial_match(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.geocode('Pirrama Pyrmont') - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'key=%s&address=Pirrama+Pyrmont' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_utf_results(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - results = self.client.geocode(components={'postal_code': '96766'}) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://maps.googleapis.com/maps/api/geocode/json?' - 'key=%s&components=postal_code%%3A96766' % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_utf8_request(self): - responses.add(responses.GET, - 'https://maps.googleapis.com/maps/api/geocode/json', - body='{"status":"OK","results":[]}', - status=200, - content_type='application/json') - - self.client.geocode(self.u('\\u4e2d\\u56fd')) # China - self.assertURLEqual( - 'https://maps.googleapis.com/maps/api/geocode/json?' - 'key=%s&address=%s' % (self.key, '%E4%B8%AD%E5%9B%BD'), - responses.calls[0].request.url) diff --git a/googlemaps/test/test_places.py b/googlemaps/test/test_places.py deleted file mode 100644 index c2ce4ec9..00000000 --- a/googlemaps/test/test_places.py +++ /dev/null @@ -1,181 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Copyright 2016 Google Inc. All rights reserved. -# -# -# 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. -# - -"""Tests for the places module.""" - -import uuid - -from types import GeneratorType - -import responses - -import googlemaps -import googlemaps.test as _test - - -class PlacesTest(_test.TestCase): - - def setUp(self): - self.key = 'AIzaasdf' - self.client = googlemaps.Client(self.key) - self.location = (-33.86746, 151.207090) - self.type = 'liquor_store' - self.language = 'en-AU' - self.region = 'AU' - self.radius = 100 - - @responses.activate - def test_places_find(self): - url = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json' - responses.add(responses.GET, url, - body='{"status": "OK", "candidates": []}', - status=200, content_type='application/json') - - self.client.find_place('restaurant', 'textquery', - fields=['business_status', 'geometry/location', 'place_id'], - location_bias='point:90,90', - language=self.language) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?language=en-AU&inputtype=textquery&' - 'locationbias=point:90,90&input=restaurant' - '&fields=business_status,geometry/location,place_id&key=%s' - % (url, self.key), responses.calls[0].request.url) - - with self.assertRaises(ValueError): - self.client.find_place('restaurant', 'invalid') - with self.assertRaises(ValueError): - self.client.find_place('restaurant', 'textquery', - fields=['geometry', 'invalid']) - with self.assertRaises(ValueError): - self.client.find_place('restaurant', 'textquery', - location_bias='invalid') - - @responses.activate - def test_places_text_search(self): - url = 'https://maps.googleapis.com/maps/api/place/textsearch/json' - responses.add(responses.GET, url, - body='{"status": "OK", "results": [], "html_attributions": []}', - status=200, content_type='application/json') - - self.client.places('restaurant', location=self.location, - radius=self.radius, region=self.region, language=self.language, - min_price=1, max_price=4, open_now=True, - type=self.type) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?language=en-AU&location=-33.86746%%2C151.20709&' - 'maxprice=4&minprice=1&opennow=true&query=restaurant&' - 'radius=100®ion=AU&type=liquor_store&key=%s' - % (url, self.key), responses.calls[0].request.url) - - @responses.activate - def test_places_nearby_search(self): - url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json' - responses.add(responses.GET, url, - body='{"status": "OK", "results": [], "html_attributions": []}', - status=200, content_type='application/json') - - self.client.places_nearby(location=self.location, keyword='foo', - language=self.language, min_price=1, - max_price=4, name='bar', open_now=True, - rank_by='distance', type=self.type) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?keyword=foo&language=en-AU&location=-33.86746%%2C151.20709&' - 'maxprice=4&minprice=1&name=bar&opennow=true&rankby=distance&' - 'type=liquor_store&key=%s' - % (url, self.key), responses.calls[0].request.url) - - with self.assertRaises(ValueError): - self.client.places_nearby(radius=self.radius) - with self.assertRaises(ValueError): - self.client.places_nearby(self.location, rank_by="distance") - - with self.assertRaises(ValueError): - self.client.places_nearby(location=self.location, rank_by="distance", - keyword='foo', radius=self.radius) - - @responses.activate - def test_place_detail(self): - url = 'https://maps.googleapis.com/maps/api/place/details/json' - responses.add(responses.GET, url, - body='{"status": "OK", "result": {}, "html_attributions": []}', - status=200, content_type='application/json') - - self.client.place('ChIJN1t_tDeuEmsRUsoyG83frY4', - fields=['business_status', 'geometry/location', 'place_id'], - language=self.language) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4' - '&key=%s&fields=business_status,geometry/location,place_id' - % (url, self.key), responses.calls[0].request.url) - - with self.assertRaises(ValueError): - self.client.place('ChIJN1t_tDeuEmsRUsoyG83frY4', - fields=['geometry', 'invalid']) - - @responses.activate - def test_photo(self): - url = 'https://maps.googleapis.com/maps/api/place/photo' - responses.add(responses.GET, url, status=200) - - ref = 'CnRvAAAAwMpdHeWlXl-lH0vp7lez4znKPIWSWvgvZFISdKx45AwJVP1Qp37YOrH7sqHMJ8C-vBDC546decipPHchJhHZL94RcTUfPa1jWzo-rSHaTlbNtjh-N68RkcToUCuY9v2HNpo5mziqkir37WU8FJEqVBIQ4k938TI3e7bf8xq-uwDZcxoUbO_ZJzPxremiQurAYzCTwRhE_V0' - response = self.client.places_photo(ref, max_width=100) - - self.assertTrue(isinstance(response, GeneratorType)) - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?maxwidth=100&photoreference=%s&key=%s' - % (url, ref, self.key), responses.calls[0].request.url) - - @responses.activate - def test_autocomplete(self): - url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json' - responses.add(responses.GET, url, - body='{"status": "OK", "predictions": []}', - status=200, content_type='application/json') - - session_token = uuid.uuid4().hex - - self.client.places_autocomplete('Google', session_token=session_token, offset=3, - location=self.location, - radius=self.radius, - language=self.language, - types='geocode', - components={'country': 'au'}, - strict_bounds=True) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?components=country%%3Aau&input=Google&language=en-AU&' - 'location=-33.86746%%2C151.20709&offset=3&radius=100&' - 'strictbounds=true&types=geocode&key=%s&sessiontoken=%s' % - (url, self.key, session_token), responses.calls[0].request.url) - - @responses.activate - def test_autocomplete_query(self): - url = 'https://maps.googleapis.com/maps/api/place/queryautocomplete/json' - responses.add(responses.GET, url, - body='{"status": "OK", "predictions": []}', - status=200, content_type='application/json') - - self.client.places_autocomplete_query('pizza near New York') - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('%s?input=pizza+near+New+York&key=%s' % - (url, self.key), responses.calls[0].request.url) diff --git a/googlemaps/test/test_roads.py b/googlemaps/test/test_roads.py deleted file mode 100644 index 6e11b604..00000000 --- a/googlemaps/test/test_roads.py +++ /dev/null @@ -1,137 +0,0 @@ -# -# Copyright 2015 Google Inc. All rights reserved. -# -# -# 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. -# - -"""Tests for the roads module.""" - - -import responses - -import googlemaps -import googlemaps.test as _test - - -class RoadsTest(_test.TestCase): - - def setUp(self): - self.key = "AIzaasdf" - self.client = googlemaps.Client(self.key) - - @responses.activate - def test_snap(self): - responses.add(responses.GET, - "https://roads.googleapis.com/v1/snapToRoads", - body='{"snappedPoints":["foo"]}', - status=200, - content_type="application/json") - - results = self.client.snap_to_roads((40.714728, -73.998672)) - self.assertEqual("foo", results[0]) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual("https://roads.googleapis.com/v1/snapToRoads?" - "path=40.714728%%2C-73.998672&key=%s" % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_nearest_roads(self): - responses.add(responses.GET, - "https://roads.googleapis.com/v1/nearestRoads", - body='{"snappedPoints":["foo"]}', - status=200, - content_type="application/json") - - results = self.client.nearest_roads((40.714728, -73.998672)) - self.assertEqual("foo", results[0]) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual("https://roads.googleapis.com/v1/nearestRoads?" - "points=40.714728%%2C-73.998672&key=%s" % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_path(self): - responses.add(responses.GET, - "https://roads.googleapis.com/v1/speedLimits", - body='{"speedLimits":["foo"]}', - status=200, - content_type="application/json") - - results = self.client.snapped_speed_limits([(1, 2),(3, 4)]) - self.assertEqual("foo", results["speedLimits"][0]) - - self.assertEqual(1, len(responses.calls)) - self.assertURLEqual("https://roads.googleapis.com/v1/speedLimits?" - "path=1%%2C2|3%%2C4" - "&key=%s" % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_speedlimits(self): - responses.add(responses.GET, - "https://roads.googleapis.com/v1/speedLimits", - body='{"speedLimits":["foo"]}', - status=200, - content_type="application/json") - - results = self.client.speed_limits("id1") - self.assertEqual("foo", results[0]) - self.assertEqual("https://roads.googleapis.com/v1/speedLimits?" - "placeId=id1&key=%s" % self.key, - responses.calls[0].request.url) - - @responses.activate - def test_speedlimits_multiple(self): - responses.add(responses.GET, - "https://roads.googleapis.com/v1/speedLimits", - body='{"speedLimits":["foo"]}', - status=200, - content_type="application/json") - - results = self.client.speed_limits(["id1", "id2", "id3"]) - self.assertEqual("foo", results[0]) - self.assertEqual("https://roads.googleapis.com/v1/speedLimits?" - "placeId=id1&placeId=id2&placeId=id3" - "&key=%s" % self.key, - responses.calls[0].request.url) - - def test_clientid_not_accepted(self): - client = googlemaps.Client(client_id="asdf", client_secret="asdf") - - with self.assertRaises(ValueError): - client.speed_limits("foo") - - @responses.activate - def test_retry(self): - class request_callback: - def __init__(self): - self.first_req = True - - def __call__(self, req): - if self.first_req: - self.first_req = False - return (500, {}, 'Internal Server Error.') - return (200, {}, '{"speedLimits":[]}') - - responses.add_callback(responses.GET, - "https://roads.googleapis.com/v1/speedLimits", - content_type="application/json", - callback=request_callback()) - - self.client.speed_limits([]) - - self.assertEqual(2, len(responses.calls)) - self.assertEqual(responses.calls[0].request.url, responses.calls[1].request.url) diff --git a/noxfile.py b/noxfile.py index bdb7477e..06eefe58 100644 --- a/noxfile.py +++ b/noxfile.py @@ -8,7 +8,9 @@ def _install_dev_packages(session): def _install_test_dependencies(session): - session.install("-r", "test_requirements.txt") + session.install("pytest") + session.install("pytest-cov") + session.install("responses") def _install_doc_dependencies(session): diff --git a/setup.cfg b/setup.cfg index 399af724..56320971 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ addopts = -rsxX --cov=googlemaps --cov-report= [coverage:run] omit = - googlemaps/test/* + tests/* [coverage:report] exclude_lines = diff --git a/setup.py b/setup.py index 5be33d49..198d283e 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,12 @@ -import sys -import io from setuptools import setup requirements = ["requests>=2.20.0,<3.0"] -# use io.open until python2.7 support is dropped -with io.open("README.md", encoding="utf8") as f: +with open("README.md") as f: readme = f.read() -with io.open("CHANGELOG.md", encoding="utf8") as f: +with open("CHANGELOG.md") as f: changelog = f.read() @@ -26,7 +23,6 @@ platforms="Posix; MacOS X; Windows", setup_requires=requirements, install_requires=requirements, - test_suite="googlemaps.test", classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", diff --git a/test_requirements.txt b/test_requirements.txt deleted file mode 100644 index e5a18030..00000000 --- a/test_requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -pytest -pytest-cov -responses -mock diff --git a/googlemaps/test/__init__.py b/tests/__init__.py similarity index 90% rename from googlemaps/test/__init__.py rename to tests/__init__.py index 38c70917..8a32b1ed 100644 --- a/googlemaps/test/__init__.py +++ b/tests/__init__.py @@ -18,14 +18,10 @@ import unittest import codecs -try: # Python 3 - from urllib.parse import urlparse, parse_qsl -except ImportError: # Python 2 - from urlparse import urlparse, parse_qsl +from urllib.parse import urlparse, parse_qsl class TestCase(unittest.TestCase): - def assertURLEqual(self, first, second, msg=None): """Check that two arguments are equivalent URLs. Ignores the order of query arguments. diff --git a/googlemaps/test/test_client.py b/tests/test_client.py similarity index 65% rename from googlemaps/test/test_client.py rename to tests/test_client.py index 9c689b67..3ad92772 100644 --- a/googlemaps/test/test_client.py +++ b/tests/test_client.py @@ -26,12 +26,11 @@ import googlemaps import googlemaps.client as _client -import googlemaps.test as _test +from . import TestCase from googlemaps.client import _X_GOOG_MAPS_EXPERIENCE_ID -class ClientTest(_test.TestCase): - +class ClientTest(TestCase): def test_no_api_key(self): with self.assertRaises(Exception): client = googlemaps.Client() @@ -56,13 +55,16 @@ def test_queries_per_second(self): queries_per_second = 3 query_range = range(queries_per_second * 2) for _ in query_range: - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - body='{"status":"OK","results":[]}', - status=200, - content_type="application/json") - client = googlemaps.Client(key="AIzaasdf", - queries_per_second=queries_per_second) + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + client = googlemaps.Client( + key="AIzaasdf", queries_per_second=queries_per_second + ) start = time.time() for _ in query_range: client.geocode("Sesame St.") @@ -71,35 +73,43 @@ def test_queries_per_second(self): @responses.activate def test_key_sent(self): - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - body='{"status":"OK","results":[]}', - status=200, - content_type="application/json") + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) client = googlemaps.Client(key="AIzaasdf") client.geocode("Sesame St.") self.assertEqual(1, len(responses.calls)) - self.assertURLEqual("https://maps.googleapis.com/maps/api/geocode/json?" - "key=AIzaasdf&address=Sesame+St.", - responses.calls[0].request.url) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=AIzaasdf&address=Sesame+St.", + responses.calls[0].request.url, + ) @responses.activate def test_extra_params(self): - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - body='{"status":"OK","results":[]}', - status=200, - content_type="application/json") + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) client = googlemaps.Client(key="AIzaasdf") client.geocode("Sesame St.", extra_params={"foo": "bar"}) self.assertEqual(1, len(responses.calls)) - self.assertURLEqual("https://maps.googleapis.com/maps/api/geocode/json?" - "key=AIzaasdf&address=Sesame+St.&foo=bar", - responses.calls[0].request.url) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=AIzaasdf&address=Sesame+St.&foo=bar", + responses.calls[0].request.url, + ) def test_hmac(self): """ @@ -110,18 +120,20 @@ def test_hmac(self): """ message = "The quick brown fox jumps over the lazy dog" - key = "a2V5" # "key" -> base64 + key = "a2V5" # "key" -> base64 signature = "3nybhbi3iqa8ino29wqQcBydtNk=" self.assertEqual(signature, _client.sign_hmac(key, message)) @responses.activate def test_url_signed(self): - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - body='{"status":"OK","results":[]}', - status=200, - content_type="application/json") + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) client = googlemaps.Client(client_id="foo", client_secret="a2V5") client.geocode("Sesame St.") @@ -129,20 +141,25 @@ def test_url_signed(self): self.assertEqual(1, len(responses.calls)) # Check ordering of parameters. - self.assertIn("address=Sesame+St.&client=foo&signature", - responses.calls[0].request.url) - self.assertURLEqual("https://maps.googleapis.com/maps/api/geocode/json?" - "address=Sesame+St.&client=foo&" - "signature=fxbWUIcNPZSekVOhp2ul9LW5TpY=", - responses.calls[0].request.url) + self.assertIn( + "address=Sesame+St.&client=foo&signature", responses.calls[0].request.url + ) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "address=Sesame+St.&client=foo&" + "signature=fxbWUIcNPZSekVOhp2ul9LW5TpY=", + responses.calls[0].request.url, + ) @responses.activate def test_ua_sent(self): - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - body='{"status":"OK","results":[]}', - status=200, - content_type="application/json") + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) client = googlemaps.Client(key="AIzaasdf") client.geocode("Sesame St.") @@ -163,10 +180,12 @@ def __call__(self, req): return (200, {}, '{"status":"OVER_QUERY_LIMIT"}') return (200, {}, '{"status":"OK","results":[]}') - responses.add_callback(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - content_type='application/json', - callback=request_callback()) + responses.add_callback( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + content_type="application/json", + callback=request_callback(), + ) client = googlemaps.Client(key="AIzaasdf") client.geocode("Sesame St.") @@ -176,10 +195,12 @@ def __call__(self, req): @responses.activate def test_transport_error(self): - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - status=404, - content_type='application/json') + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + status=404, + content_type="application/json", + ) client = googlemaps.Client(key="AIzaasdf") with self.assertRaises(googlemaps.exceptions.HTTPError) as e: @@ -189,11 +210,13 @@ def test_transport_error(self): @responses.activate def test_host_override(self): - responses.add(responses.GET, - "https://foo.com/bar", - body='{"status":"OK","results":[]}', - status=200, - content_type="application/json") + responses.add( + responses.GET, + "https://foo.com/bar", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) client = googlemaps.Client(key="AIzaasdf") client._get("/bar", {}, base_url="https://foo.com") @@ -205,11 +228,13 @@ def test_custom_extract(self): def custom_extract(resp): return resp.json() - responses.add(responses.GET, - "https://maps.googleapis.com/bar", - body='{"error":"errormessage"}', - status=403, - content_type="application/json") + responses.add( + responses.GET, + "https://maps.googleapis.com/bar", + body='{"error":"errormessage"}', + status=403, + content_type="application/json", + ) client = googlemaps.Client(key="AIzaasdf") b = client._get("/bar", {}, extract_body=custom_extract) @@ -225,13 +250,15 @@ def __init__(self): def __call__(self, req): if self.first_req: self.first_req = False - return (500, {}, 'Internal Server Error.') + return (500, {}, "Internal Server Error.") return (200, {}, '{"status":"OK","results":[]}') - responses.add_callback(responses.GET, - "https://maps.googleapis.com/maps/api/geocode/json", - content_type="application/json", - callback=request_callback()) + responses.add_callback( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + content_type="application/json", + callback=request_callback(), + ) client = googlemaps.Client(key="AIzaasdf") client.geocode("Sesame St.") @@ -247,28 +274,31 @@ def test_invalid_channel(self): # https://developers.google.com/maps/premium/reports # /usage-reports#channels with self.assertRaises(ValueError): - client = googlemaps.Client(client_id="foo", client_secret="a2V5", - channel="auieauie$? ") + client = googlemaps.Client( + client_id="foo", client_secret="a2V5", channel="auieauie$? " + ) def test_auth_url_with_channel(self): - client = googlemaps.Client(key="AIzaasdf", - client_id="foo", - client_secret="a2V5", - channel="MyChannel_1") + client = googlemaps.Client( + key="AIzaasdf", client_id="foo", client_secret="a2V5", channel="MyChannel_1" + ) # Check ordering of parameters + signature. - auth_url = client._generate_auth_url("/test", - {"param": "param"}, - accepts_clientid=True) - self.assertEqual(auth_url, "/test?param=param" - "&channel=MyChannel_1" - "&client=foo" - "&signature=OH18GuQto_mEpxj99UimKskvo4k=") + auth_url = client._generate_auth_url( + "/test", {"param": "param"}, accepts_clientid=True + ) + self.assertEqual( + auth_url, + "/test?param=param" + "&channel=MyChannel_1" + "&client=foo" + "&signature=OH18GuQto_mEpxj99UimKskvo4k=", + ) # Check if added to requests to API with accepts_clientid=False - auth_url = client._generate_auth_url("/test", - {"param": "param"}, - accepts_clientid=False) + auth_url = client._generate_auth_url( + "/test", {"param": "param"}, accepts_clientid=False + ) self.assertEqual(auth_url, "/test?param=param&key=AIzaasdf") def test_requests_version(self): @@ -278,18 +308,18 @@ def test_requests_version(self): "client_secret": "a2V5", "channel": "MyChannel_1", "connect_timeout": 5, - "read_timeout": 5 + "read_timeout": 5, } client_args = client_args_timeout.copy() del client_args["connect_timeout"] del client_args["read_timeout"] - requests.__version__ = '2.3.0' + requests.__version__ = "2.3.0" with self.assertRaises(NotImplementedError): googlemaps.Client(**client_args_timeout) googlemaps.Client(**client_args) - requests.__version__ = '2.4.0' + requests.__version__ = "2.4.0" googlemaps.Client(**client_args_timeout) googlemaps.Client(**client_args) @@ -327,10 +357,7 @@ def test_experience_id_sample(self): experience_id = str(uuid.uuid4()) # instantiate client with experience id - client = googlemaps.Client( - key="AIza-Maps-API-Key", - experience_id=experience_id - ) + client = googlemaps.Client(key="AIza-Maps-API-Key", experience_id=experience_id) # clear the current experience id client.clear_experience_id() @@ -352,11 +379,13 @@ def test_experience_id_sample(self): @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") + 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") @@ -376,14 +405,15 @@ def test_experience_id_no_in_header(self): @responses.activate def test_no_retry_over_query_limit(self): - responses.add(responses.GET, - "https://maps.googleapis.com/foo", - body='{"status":"OVER_QUERY_LIMIT"}', - status=200, - content_type="application/json") - - client = googlemaps.Client(key="AIzaasdf", - retry_over_query_limit=False) + responses.add( + responses.GET, + "https://maps.googleapis.com/foo", + body='{"status":"OVER_QUERY_LIMIT"}', + status=200, + content_type="application/json", + ) + + client = googlemaps.Client(key="AIzaasdf", retry_over_query_limit=False) with self.assertRaises(googlemaps.exceptions.ApiError): client._request("/foo", {}) diff --git a/googlemaps/test/test_convert.py b/tests/test_convert.py similarity index 68% rename from googlemaps/test/test_convert.py rename to tests/test_convert.py index dc0fe2b1..39546aee 100644 --- a/googlemaps/test/test_convert.py +++ b/tests/test_convert.py @@ -25,7 +25,6 @@ class ConvertTest(unittest.TestCase): - def test_latlng(self): expected = "1,2" ll = {"lat": 1, "lng": 2} @@ -122,31 +121,33 @@ def test_size(self): convert.size("test") def test_polyline_decode(self): - syd_mel_route = ("rvumEis{y[`NsfA~tAbF`bEj^h{@{KlfA~eA~`AbmEghAt~D|e@j" - "lRpO~yH_\\v}LjbBh~FdvCxu@`nCplDbcBf_B|wBhIfhCnqEb~D~" - "jCn_EngApdEtoBbfClf@t_CzcCpoEr_Gz_DxmAphDjjBxqCviEf}" - "B|pEvsEzbE~qGfpExjBlqCx}BvmLb`FbrQdpEvkAbjDllD|uDldD" - "j`Ef|AzcEx_Gtm@vuI~xArwD`dArlFnhEzmHjtC~eDluAfkC|eAd" - "hGpJh}N_mArrDlr@h|HzjDbsAvy@~~EdTxpJje@jlEltBboDjJdv" - "KyZpzExrAxpHfg@pmJg[tgJuqBnlIarAh}DbN`hCeOf_IbxA~uFt" - "|A|xEt_ArmBcN|sB|h@b_DjOzbJ{RlxCcfAp~AahAbqG~Gr}AerA" - "`dCwlCbaFo]twKt{@bsG|}A~fDlvBvz@tw@rpD_r@rqB{PvbHek@" - "vsHlh@ptNtm@fkD[~xFeEbyKnjDdyDbbBtuA|~Br|Gx_AfxCt}Cj" - "nHv`Ew\\lnBdrBfqBraD|{BldBxpG|]jqC`mArcBv]rdAxgBzdEb" - "{InaBzyC}AzaEaIvrCzcAzsCtfD~qGoPfeEh]h`BxiB`e@`kBxfA" - "v^pyA`}BhkCdoCtrC~bCxhCbgEplKrk@tiAteBwAxbCwuAnnCc]b" - "{FjrDdjGhhGzfCrlDruBzSrnGhvDhcFzw@n{@zxAf}Fd{IzaDnbD" - "joAjqJjfDlbIlzAraBxrB}K~`GpuD~`BjmDhkBp{@r_AxCrnAjrC" - "x`AzrBj{B|r@~qBbdAjtDnvCtNzpHxeApyC|GlfM`fHtMvqLjuEt" - "lDvoFbnCt|@xmAvqBkGreFm~@hlHw|AltC}NtkGvhBfaJ|~@riAx" - "uC~gErwCttCzjAdmGuF`iFv`AxsJftD|nDr_QtbMz_DheAf~Buy@" - "rlC`i@d_CljC`gBr|H|nAf_Fh{G|mE~kAhgKviEpaQnu@zwAlrA`" - "G~gFnvItz@j{Cng@j{D{]`tEftCdcIsPz{DddE~}PlnE|dJnzG`e" - "G`mF|aJdqDvoAwWjzHv`H`wOtjGzeXhhBlxErfCf{BtsCjpEjtD|" - "}Aja@xnAbdDt|ErMrdFh{CzgAnlCnr@`wEM~mE`bA`uD|MlwKxmB" - "vuFlhB|sN`_@fvBp`CxhCt_@loDsS|eDlmChgFlqCbjCxk@vbGxm" - "CjbMba@rpBaoClcCk_DhgEzYdzBl\\vsA_JfGztAbShkGtEhlDzh" - "C~w@hnB{e@yF}`D`_Ayx@~vGqn@l}CafC") + syd_mel_route = ( + "rvumEis{y[`NsfA~tAbF`bEj^h{@{KlfA~eA~`AbmEghAt~D|e@j" + "lRpO~yH_\\v}LjbBh~FdvCxu@`nCplDbcBf_B|wBhIfhCnqEb~D~" + "jCn_EngApdEtoBbfClf@t_CzcCpoEr_Gz_DxmAphDjjBxqCviEf}" + "B|pEvsEzbE~qGfpExjBlqCx}BvmLb`FbrQdpEvkAbjDllD|uDldD" + "j`Ef|AzcEx_Gtm@vuI~xArwD`dArlFnhEzmHjtC~eDluAfkC|eAd" + "hGpJh}N_mArrDlr@h|HzjDbsAvy@~~EdTxpJje@jlEltBboDjJdv" + "KyZpzExrAxpHfg@pmJg[tgJuqBnlIarAh}DbN`hCeOf_IbxA~uFt" + "|A|xEt_ArmBcN|sB|h@b_DjOzbJ{RlxCcfAp~AahAbqG~Gr}AerA" + "`dCwlCbaFo]twKt{@bsG|}A~fDlvBvz@tw@rpD_r@rqB{PvbHek@" + "vsHlh@ptNtm@fkD[~xFeEbyKnjDdyDbbBtuA|~Br|Gx_AfxCt}Cj" + "nHv`Ew\\lnBdrBfqBraD|{BldBxpG|]jqC`mArcBv]rdAxgBzdEb" + "{InaBzyC}AzaEaIvrCzcAzsCtfD~qGoPfeEh]h`BxiB`e@`kBxfA" + "v^pyA`}BhkCdoCtrC~bCxhCbgEplKrk@tiAteBwAxbCwuAnnCc]b" + "{FjrDdjGhhGzfCrlDruBzSrnGhvDhcFzw@n{@zxAf}Fd{IzaDnbD" + "joAjqJjfDlbIlzAraBxrB}K~`GpuD~`BjmDhkBp{@r_AxCrnAjrC" + "x`AzrBj{B|r@~qBbdAjtDnvCtNzpHxeApyC|GlfM`fHtMvqLjuEt" + "lDvoFbnCt|@xmAvqBkGreFm~@hlHw|AltC}NtkGvhBfaJ|~@riAx" + "uC~gErwCttCzjAdmGuF`iFv`AxsJftD|nDr_QtbMz_DheAf~Buy@" + "rlC`i@d_CljC`gBr|H|nAf_Fh{G|mE~kAhgKviEpaQnu@zwAlrA`" + "G~gFnvItz@j{Cng@j{D{]`tEftCdcIsPz{DddE~}PlnE|dJnzG`e" + "G`mF|aJdqDvoAwWjzHv`H`wOtjGzeXhhBlxErfCf{BtsCjpEjtD|" + "}Aja@xnAbdDt|ErMrdFh{CzgAnlCnr@`wEM~mE`bA`uD|MlwKxmB" + "vuFlhB|sN`_@fvBp`CxhCt_@loDsS|eDlmChgFlqCbjCxk@vbGxm" + "CjbMba@rpBaoClcCk_DhgEzYdzBl\\vsA_JfGztAbShkGtEhlDzh" + "C~w@hnB{e@yF}`D`_Ayx@~vGqn@l}CafC" + ) points = convert.decode_polyline(syd_mel_route) self.assertAlmostEqual(-33.86746, points[0]["lat"]) @@ -155,8 +156,10 @@ def test_polyline_decode(self): self.assertAlmostEqual(144.963180, points[-1]["lng"]) def test_polyline_round_trip(self): - test_polyline = ("gcneIpgxzRcDnBoBlEHzKjBbHlG`@`IkDxIi" - "KhKoMaLwTwHeIqHuAyGXeB~Ew@fFjAtIzExF") + test_polyline = ( + "gcneIpgxzRcDnBoBlEHzKjBbHlG`@`IkDxIi" + "KhKoMaLwTwHeIqHuAyGXeB~Ew@fFjAtIzExF" + ) points = convert.decode_polyline(test_polyline) actual_polyline = convert.encode_polyline(points) diff --git a/tests/test_directions.py b/tests/test_directions.py new file mode 100644 index 00000000..5a3c477a --- /dev/null +++ b/tests/test_directions.py @@ -0,0 +1,325 @@ +# +# Copyright 2014 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the directions module.""" + +from datetime import datetime +from datetime import timedelta +import time + +import responses + +import googlemaps +from . import TestCase + + +class DirectionsTest(TestCase): + def setUp(self): + self.key = "AIzaasdf" + self.client = googlemaps.Client(self.key) + + @responses.activate + def test_simple_directions(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + # Simplest directions request. Driving directions by default. + routes = self.client.directions("Sydney", "Melbourne") + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json" + "?origin=Sydney&destination=Melbourne&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_complex_request(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + routes = self.client.directions( + "Sydney", + "Melbourne", + mode="bicycling", + avoid=["highways", "tolls", "ferries"], + units="metric", + region="us", + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?" + "origin=Sydney&avoid=highways%%7Ctolls%%7Cferries&" + "destination=Melbourne&mode=bicycling&key=%s" + "&units=metric®ion=us" % self.key, + responses.calls[0].request.url, + ) + + def test_transit_without_time(self): + # With mode of transit, we need a departure_time or an + # arrival_time specified + with self.assertRaises(googlemaps.exceptions.ApiError): + self.client.directions( + "Sydney Town Hall", "Parramatta, NSW", mode="transit" + ) + + @responses.activate + def test_transit_with_departure_time(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + now = datetime.now() + routes = self.client.directions( + "Sydney Town Hall", + "Parramatta, NSW", + mode="transit", + traffic_model="optimistic", + departure_time=now, + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?origin=" + "Sydney+Town+Hall&key=%s&destination=Parramatta%%2C+NSW&" + "mode=transit&departure_time=%d&traffic_model=optimistic" + % (self.key, time.mktime(now.timetuple())), + responses.calls[0].request.url, + ) + + @responses.activate + def test_transit_with_arrival_time(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + an_hour_from_now = datetime.now() + timedelta(hours=1) + routes = self.client.directions( + "Sydney Town Hall", + "Parramatta, NSW", + mode="transit", + arrival_time=an_hour_from_now, + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?" + "origin=Sydney+Town+Hall&arrival_time=%d&" + "destination=Parramatta%%2C+NSW&mode=transit&key=%s" + % (time.mktime(an_hour_from_now.timetuple()), self.key), + responses.calls[0].request.url, + ) + + def test_invalid_travel_mode(self): + with self.assertRaises(ValueError): + self.client.directions( + "48 Pirrama Road, Pyrmont, NSW", "Sydney Town Hall", mode="crawling" + ) + + @responses.activate + def test_travel_mode_round_trip(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + routes = self.client.directions( + "Town Hall, Sydney", "Parramatta, NSW", mode="bicycling" + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?" + "origin=Town+Hall%%2C+Sydney&destination=Parramatta%%2C+NSW&" + "mode=bicycling&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_brooklyn_to_queens_by_transit(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + now = datetime.now() + routes = self.client.directions( + "Brooklyn", "Queens", mode="transit", departure_time=now + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?" + "origin=Brooklyn&key=%s&destination=Queens&mode=transit&" + "departure_time=%d" % (self.key, time.mktime(now.timetuple())), + responses.calls[0].request.url, + ) + + @responses.activate + def test_boston_to_concord_via_charlestown_and_lexington(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + routes = self.client.directions( + "Boston, MA", "Concord, MA", waypoints=["Charlestown, MA", "Lexington, MA"] + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?" + "origin=Boston%%2C+MA&destination=Concord%%2C+MA&" + "waypoints=Charlestown%%2C+MA%%7CLexington%%2C+MA&" + "key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_adelaide_wine_tour(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + routes = self.client.directions( + "Adelaide, SA", + "Adelaide, SA", + waypoints=[ + "Barossa Valley, SA", + "Clare, SA", + "Connawarra, SA", + "McLaren Vale, SA", + ], + optimize_waypoints=True, + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?" + "origin=Adelaide%%2C+SA&destination=Adelaide%%2C+SA&" + "waypoints=optimize%%3Atrue%%7CBarossa+Valley%%2C+" + "SA%%7CClare%%2C+SA%%7CConnawarra%%2C+SA%%7CMcLaren+" + "Vale%%2C+SA&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_toledo_to_madrid_in_spain(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + routes = self.client.directions("Toledo", "Madrid", region="es") + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?" + "origin=Toledo®ion=es&destination=Madrid&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_zero_results_returns_response(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"ZERO_RESULTS","routes":[]}', + status=200, + content_type="application/json", + ) + + routes = self.client.directions("Toledo", "Madrid") + self.assertIsNotNone(routes) + self.assertEqual(0, len(routes)) + + @responses.activate + def test_language_parameter(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + routes = self.client.directions("Toledo", "Madrid", region="es", language="es") + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?" + "origin=Toledo®ion=es&destination=Madrid&key=%s&" + "language=es" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_alternatives(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/directions/json", + body='{"status":"OK","routes":[]}', + status=200, + content_type="application/json", + ) + + routes = self.client.directions( + "Sydney Town Hall", "Parramatta Town Hall", alternatives=True + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/directions/json?" + "origin=Sydney+Town+Hall&destination=Parramatta+Town+Hall&" + "alternatives=true&key=%s" % self.key, + responses.calls[0].request.url, + ) diff --git a/tests/test_distance_matrix.py b/tests/test_distance_matrix.py new file mode 100644 index 00000000..a906f8ce --- /dev/null +++ b/tests/test_distance_matrix.py @@ -0,0 +1,183 @@ +# +# Copyright 2014 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the distance matrix module.""" + +from datetime import datetime +import time + +import responses + +import googlemaps +from . import TestCase + + +class DistanceMatrixTest(TestCase): + def setUp(self): + self.key = "AIzaasdf" + self.client = googlemaps.Client(self.key) + + @responses.activate + def test_basic_params(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/distancematrix/json", + body='{"status":"OK","rows":[]}', + status=200, + content_type="application/json", + ) + + origins = [ + "Perth, Australia", + "Sydney, Australia", + "Melbourne, Australia", + "Adelaide, Australia", + "Brisbane, Australia", + "Darwin, Australia", + "Hobart, Australia", + "Canberra, Australia", + ] + destinations = [ + "Uluru, Australia", + "Kakadu, Australia", + "Blue Mountains, Australia", + "Bungle Bungles, Australia", + "The Pinnacles, Australia", + ] + + matrix = self.client.distance_matrix(origins, destinations) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/distancematrix/json?" + "key=%s&origins=Perth%%2C+Australia%%7CSydney%%2C+" + "Australia%%7CMelbourne%%2C+Australia%%7CAdelaide%%2C+" + "Australia%%7CBrisbane%%2C+Australia%%7CDarwin%%2C+" + "Australia%%7CHobart%%2C+Australia%%7CCanberra%%2C+Australia&" + "destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7C" + "Blue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia" + "%%7CThe+Pinnacles%%2C+Australia" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_mixed_params(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/distancematrix/json", + body='{"status":"OK","rows":[]}', + status=200, + content_type="application/json", + ) + + origins = ["Bobcaygeon ON", [41.43206, -81.38992]] + destinations = [ + (43.012486, -83.6964149), + {"lat": 42.8863855, "lng": -78.8781627}, + ] + + matrix = self.client.distance_matrix(origins, destinations) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/distancematrix/json?" + "key=%s&origins=Bobcaygeon+ON%%7C41.43206%%2C-81.38992&" + "destinations=43.012486%%2C-83.6964149%%7C42.8863855%%2C" + "-78.8781627" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_all_params(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/distancematrix/json", + body='{"status":"OK","rows":[]}', + status=200, + content_type="application/json", + ) + + origins = [ + "Perth, Australia", + "Sydney, Australia", + "Melbourne, Australia", + "Adelaide, Australia", + "Brisbane, Australia", + "Darwin, Australia", + "Hobart, Australia", + "Canberra, Australia", + ] + destinations = [ + "Uluru, Australia", + "Kakadu, Australia", + "Blue Mountains, Australia", + "Bungle Bungles, Australia", + "The Pinnacles, Australia", + ] + + now = datetime.now() + matrix = self.client.distance_matrix( + origins, + destinations, + mode="driving", + language="en-AU", + avoid="tolls", + units="imperial", + departure_time=now, + traffic_model="optimistic", + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/distancematrix/json?" + "origins=Perth%%2C+Australia%%7CSydney%%2C+Australia%%7C" + "Melbourne%%2C+Australia%%7CAdelaide%%2C+Australia%%7C" + "Brisbane%%2C+Australia%%7CDarwin%%2C+Australia%%7CHobart%%2C+" + "Australia%%7CCanberra%%2C+Australia&language=en-AU&" + "avoid=tolls&mode=driving&key=%s&units=imperial&" + "destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7C" + "Blue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia" + "%%7CThe+Pinnacles%%2C+Australia&departure_time=%d" + "&traffic_model=optimistic" % (self.key, time.mktime(now.timetuple())), + responses.calls[0].request.url, + ) + + @responses.activate + def test_lang_param(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/distancematrix/json", + body='{"status":"OK","rows":[]}', + status=200, + content_type="application/json", + ) + + origins = ["Vancouver BC", "Seattle"] + destinations = ["San Francisco", "Victoria BC"] + + matrix = self.client.distance_matrix( + origins, destinations, language="fr-FR", mode="bicycling" + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/distancematrix/json?" + "key=%s&language=fr-FR&mode=bicycling&" + "origins=Vancouver+BC%%7CSeattle&" + "destinations=San+Francisco%%7CVictoria+BC" % self.key, + responses.calls[0].request.url, + ) diff --git a/tests/test_elevation.py b/tests/test_elevation.py new file mode 100644 index 00000000..165f95b3 --- /dev/null +++ b/tests/test_elevation.py @@ -0,0 +1,134 @@ +# +# Copyright 2014 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the elevation module.""" + +import datetime + +import responses + +import googlemaps +from . import TestCase + + +class ElevationTest(TestCase): + def setUp(self): + self.key = "AIzaasdf" + self.client = googlemaps.Client(self.key) + + @responses.activate + def test_elevation_single(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/elevation/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.elevation((40.714728, -73.998672)) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/elevation/json?" + "locations=enc:abowFtzsbM&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_elevation_single_list(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/elevation/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.elevation([(40.714728, -73.998672)]) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/elevation/json?" + "locations=enc:abowFtzsbM&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_elevation_multiple(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/elevation/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + locations = [(40.714728, -73.998672), (-34.397, 150.644)] + results = self.client.elevation(locations) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/elevation/json?" + "locations=enc:abowFtzsbMhgmiMuobzi@&key=%s" % self.key, + responses.calls[0].request.url, + ) + + def test_elevation_along_path_single(self): + with self.assertRaises(googlemaps.exceptions.ApiError): + results = self.client.elevation_along_path([(40.714728, -73.998672)], 5) + + @responses.activate + def test_elevation_along_path(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/elevation/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + path = [(40.714728, -73.998672), (-34.397, 150.644)] + + results = self.client.elevation_along_path(path, 5) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/elevation/json?" + "path=enc:abowFtzsbMhgmiMuobzi@&" + "key=%s&samples=5" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_short_latlng(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/elevation/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.elevation((40, -73)) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/elevation/json?" + "locations=40,-73&key=%s" % self.key, + responses.calls[0].request.url, + ) diff --git a/tests/test_geocoding.py b/tests/test_geocoding.py new file mode 100644 index 00000000..dfd9376d --- /dev/null +++ b/tests/test_geocoding.py @@ -0,0 +1,348 @@ +# This Python file uses the following encoding: utf-8 +# +# Copyright 2014 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the geocoding module.""" + +import datetime + +import responses + +import googlemaps +from . import TestCase + + +class GeocodingTest(TestCase): + def setUp(self): + self.key = "AIzaasdf" + self.client = googlemaps.Client(self.key) + + @responses.activate + def test_simple_geocode(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode("Sydney") + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=%s&address=Sydney" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_reverse_geocode(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.reverse_geocode((-33.8674869, 151.2069902)) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "latlng=-33.8674869,151.2069902&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_geocoding_the_googleplex(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode("1600 Amphitheatre Parkway, " "Mountain View, CA") + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=%s&address=1600+Amphitheatre+Parkway%%2C+Mountain" + "+View%%2C+CA" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_geocode_with_bounds(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode( + "Winnetka", + bounds={ + "southwest": (34.172684, -118.604794), + "northeast": (34.236144, -118.500938), + }, + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "bounds=34.172684%%2C-118.604794%%7C34.236144%%2C" + "-118.500938&key=%s&address=Winnetka" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_geocode_with_region_biasing(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode("Toledo", region="es") + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "region=es&key=%s&address=Toledo" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_geocode_with_component_filter(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode("santa cruz", components={"country": "ES"}) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=%s&components=country%%3AES&address=santa+cruz" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_geocode_with_multiple_component_filters(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode( + "Torun", components={"administrative_area": "TX", "country": "US"} + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=%s&components=administrative_area%%3ATX%%7C" + "country%%3AUS&address=Torun" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_geocode_with_just_components(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode( + components={ + "route": "Annegatan", + "administrative_area": "Helsinki", + "country": "Finland", + } + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=%s&components=administrative_area%%3AHelsinki" + "%%7Ccountry%%3AFinland%%7Croute%%3AAnnegatan" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_simple_reverse_geocode(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.reverse_geocode((40.714224, -73.961452)) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "latlng=40.714224%%2C-73.961452&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_reverse_geocode_restricted_by_type(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.reverse_geocode( + (40.714224, -73.961452), + location_type="ROOFTOP", + result_type="street_address", + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "latlng=40.714224%%2C-73.961452&result_type=street_address&" + "key=%s&location_type=ROOFTOP" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_reverse_geocode_multiple_location_types(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.reverse_geocode( + (40.714224, -73.961452), + location_type=["ROOFTOP", "RANGE_INTERPOLATED"], + result_type="street_address", + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "latlng=40.714224%%2C-73.961452&result_type=street_address&" + "key=%s&location_type=ROOFTOP%%7CRANGE_INTERPOLATED" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_reverse_geocode_multiple_result_types(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.reverse_geocode( + (40.714224, -73.961452), + location_type="ROOFTOP", + result_type=["street_address", "route"], + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "latlng=40.714224%%2C-73.961452&result_type=street_address" + "%%7Croute&key=%s&location_type=ROOFTOP" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_partial_match(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode("Pirrama Pyrmont") + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=%s&address=Pirrama+Pyrmont" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_utf_results(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode(components={"postal_code": "96766"}) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=%s&components=postal_code%%3A96766" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_utf8_request(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + self.client.geocode(self.u("\\u4e2d\\u56fd")) # China + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=%s&address=%s" % (self.key, "%E4%B8%AD%E5%9B%BD"), + responses.calls[0].request.url, + ) diff --git a/googlemaps/test/test_geolocation.py b/tests/test_geolocation.py similarity index 63% rename from googlemaps/test/test_geolocation.py rename to tests/test_geolocation.py index 64b6f36a..8eeb2cbc 100644 --- a/googlemaps/test/test_geolocation.py +++ b/tests/test_geolocation.py @@ -21,25 +21,28 @@ import responses import googlemaps -import googlemaps.test as _test +from . import TestCase -class GeolocationTest(_test.TestCase): - +class GeolocationTest(TestCase): def setUp(self): - self.key = 'AIzaasdf' + self.key = "AIzaasdf" self.client = googlemaps.Client(self.key) @responses.activate def test_simple_geolocate(self): - responses.add(responses.POST, - 'https://www.googleapis.com/geolocation/v1/geolocate', - body='{"location": {"lat": 51.0,"lng": -0.1},"accuracy": 1200.4}', - status=200, - content_type='application/json') + responses.add( + responses.POST, + "https://www.googleapis.com/geolocation/v1/geolocate", + body='{"location": {"lat": 51.0,"lng": -0.1},"accuracy": 1200.4}', + status=200, + content_type="application/json", + ) results = self.client.geolocate() self.assertEqual(1, len(responses.calls)) - self.assertURLEqual('https://www.googleapis.com/geolocation/v1/geolocate?' - 'key=%s' % self.key, responses.calls[0].request.url) + self.assertURLEqual( + "https://www.googleapis.com/geolocation/v1/geolocate?" "key=%s" % self.key, + responses.calls[0].request.url, + ) diff --git a/googlemaps/test/test_maps.py b/tests/test_maps.py similarity index 52% rename from googlemaps/test/test_maps.py rename to tests/test_maps.py index a20b84fe..db83ee04 100644 --- a/googlemaps/test/test_maps.py +++ b/tests/test_maps.py @@ -22,14 +22,13 @@ import responses import googlemaps -import googlemaps.test as _test +from . import TestCase from googlemaps.maps import StaticMapMarker from googlemaps.maps import StaticMapPath -class MapsTest(_test.TestCase): - +class MapsTest(TestCase): def setUp(self): self.key = "AIzaasdf" self.client = googlemaps.Client(self.key) @@ -38,13 +37,13 @@ def setUp(self): def test_static_map_marker(self): marker = StaticMapMarker( locations=[{"lat": -33.867486, "lng": 151.206990}, "Sydney"], - size='small', color='blue', label="S" + size="small", + color="blue", + label="S", ) self.assertEqual( - "size:small|color:blue|label:S|" - "-33.867486,151.20699|Sydney", - str(marker) + "size:small|color:blue|label:S|" "-33.867486,151.20699|Sydney", str(marker) ) with self.assertRaises(ValueError): @@ -54,64 +53,75 @@ def test_static_map_marker(self): def test_static_map_path(self): path = StaticMapPath( points=[{"lat": -33.867486, "lng": 151.206990}, "Sydney"], - weight=5, color="red", geodesic=True, fillcolor="Red" + weight=5, + color="red", + geodesic=True, + fillcolor="Red", ) self.assertEqual( - "weight:5|color:red|fillcolor:Red|""geodesic:True|" + "weight:5|color:red|fillcolor:Red|" + "geodesic:True|" "-33.867486,151.20699|Sydney", - str(path) + str(path), ) @responses.activate def test_download(self): - url = 'https://maps.googleapis.com/maps/api/staticmap' + url = "https://maps.googleapis.com/maps/api/staticmap" responses.add(responses.GET, url, status=200) path = StaticMapPath( - points=[(62.107733,-145.541936), 'Delta+Junction,AK'], - weight=5, color="red" + points=[(62.107733, -145.541936), "Delta+Junction,AK"], + weight=5, + color="red", ) m1 = StaticMapMarker( - locations=[(62.107733,-145.541936)], - color="blue", label="S" + locations=[(62.107733, -145.541936)], color="blue", label="S" ) m2 = StaticMapMarker( - locations=['Delta+Junction,AK'], - size="tiny", color="green" + locations=["Delta+Junction,AK"], size="tiny", color="green" ) m3 = StaticMapMarker( - locations=["Tok,AK"], - size="mid", color="0xFFFF00", label="C" + locations=["Tok,AK"], size="mid", color="0xFFFF00", label="C" ) response = self.client.static_map( - size=(400, 400), zoom=6, center=(63.259591,-144.667969), - maptype="hybrid", format="png", scale=2, visible=["Tok,AK"], - path=path, markers=[m1, m2, m3] + size=(400, 400), + zoom=6, + center=(63.259591, -144.667969), + maptype="hybrid", + format="png", + scale=2, + visible=["Tok,AK"], + path=path, + markers=[m1, m2, m3], ) self.assertTrue(isinstance(response, GeneratorType)) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( - '%s?center=63.259591%%2C-144.667969&format=png&maptype=hybrid&' - 'markers=color%%3Ablue%%7Clabel%%3AS%%7C62.107733%%2C-145.541936&' - 'markers=size%%3Atiny%%7Ccolor%%3Agreen%%7CDelta%%2BJunction%%2CAK&' - 'markers=size%%3Amid%%7Ccolor%%3A0xFFFF00%%7Clabel%%3AC%%7CTok%%2CAK&' - 'path=weight%%3A5%%7Ccolor%%3Ared%%7C62.107733%%2C-145.541936%%7CDelta%%2BJunction%%2CAK&' - 'scale=2&size=400x400&visible=Tok%%2CAK&zoom=6&key=%s' - % (url, self.key), responses.calls[0].request.url) + "%s?center=63.259591%%2C-144.667969&format=png&maptype=hybrid&" + "markers=color%%3Ablue%%7Clabel%%3AS%%7C62.107733%%2C-145.541936&" + "markers=size%%3Atiny%%7Ccolor%%3Agreen%%7CDelta%%2BJunction%%2CAK&" + "markers=size%%3Amid%%7Ccolor%%3A0xFFFF00%%7Clabel%%3AC%%7CTok%%2CAK&" + "path=weight%%3A5%%7Ccolor%%3Ared%%7C62.107733%%2C-145.541936%%7CDelta%%2BJunction%%2CAK&" + "scale=2&size=400x400&visible=Tok%%2CAK&zoom=6&key=%s" % (url, self.key), + responses.calls[0].request.url, + ) with self.assertRaises(ValueError): self.client.static_map(size=(400, 400)) with self.assertRaises(ValueError): - self.client.static_map(size=(400, 400), center=(63.259591,-144.667969), - zoom=6, format='test') + self.client.static_map( + size=(400, 400), center=(63.259591, -144.667969), zoom=6, format="test" + ) with self.assertRaises(ValueError): - self.client.static_map(size=(400, 400), center=(63.259591,-144.667969), - zoom=6, maptype='test') + self.client.static_map( + size=(400, 400), center=(63.259591, -144.667969), zoom=6, maptype="test" + ) diff --git a/tests/test_places.py b/tests/test_places.py new file mode 100644 index 00000000..5316fe8e --- /dev/null +++ b/tests/test_places.py @@ -0,0 +1,249 @@ +# This Python file uses the following encoding: utf-8 +# +# Copyright 2016 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the places module.""" + +import uuid + +from types import GeneratorType + +import responses + +import googlemaps +from . import TestCase + + +class PlacesTest(TestCase): + def setUp(self): + self.key = "AIzaasdf" + self.client = googlemaps.Client(self.key) + self.location = (-33.86746, 151.207090) + self.type = "liquor_store" + self.language = "en-AU" + self.region = "AU" + self.radius = 100 + + @responses.activate + def test_places_find(self): + url = "https://maps.googleapis.com/maps/api/place/findplacefromtext/json" + responses.add( + responses.GET, + url, + body='{"status": "OK", "candidates": []}', + status=200, + content_type="application/json", + ) + + self.client.find_place( + "restaurant", + "textquery", + fields=["business_status", "geometry/location", "place_id"], + location_bias="point:90,90", + language=self.language, + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "%s?language=en-AU&inputtype=textquery&" + "locationbias=point:90,90&input=restaurant" + "&fields=business_status,geometry/location,place_id&key=%s" + % (url, self.key), + responses.calls[0].request.url, + ) + + with self.assertRaises(ValueError): + self.client.find_place("restaurant", "invalid") + with self.assertRaises(ValueError): + self.client.find_place( + "restaurant", "textquery", fields=["geometry", "invalid"] + ) + with self.assertRaises(ValueError): + self.client.find_place("restaurant", "textquery", location_bias="invalid") + + @responses.activate + def test_places_text_search(self): + url = "https://maps.googleapis.com/maps/api/place/textsearch/json" + responses.add( + responses.GET, + url, + body='{"status": "OK", "results": [], "html_attributions": []}', + status=200, + content_type="application/json", + ) + + self.client.places( + "restaurant", + location=self.location, + radius=self.radius, + region=self.region, + language=self.language, + min_price=1, + max_price=4, + open_now=True, + type=self.type, + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "%s?language=en-AU&location=-33.86746%%2C151.20709&" + "maxprice=4&minprice=1&opennow=true&query=restaurant&" + "radius=100®ion=AU&type=liquor_store&key=%s" % (url, self.key), + responses.calls[0].request.url, + ) + + @responses.activate + def test_places_nearby_search(self): + url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" + responses.add( + responses.GET, + url, + body='{"status": "OK", "results": [], "html_attributions": []}', + status=200, + content_type="application/json", + ) + + self.client.places_nearby( + location=self.location, + keyword="foo", + language=self.language, + min_price=1, + max_price=4, + name="bar", + open_now=True, + rank_by="distance", + type=self.type, + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "%s?keyword=foo&language=en-AU&location=-33.86746%%2C151.20709&" + "maxprice=4&minprice=1&name=bar&opennow=true&rankby=distance&" + "type=liquor_store&key=%s" % (url, self.key), + responses.calls[0].request.url, + ) + + with self.assertRaises(ValueError): + self.client.places_nearby(radius=self.radius) + with self.assertRaises(ValueError): + self.client.places_nearby(self.location, rank_by="distance") + + with self.assertRaises(ValueError): + self.client.places_nearby( + location=self.location, + rank_by="distance", + keyword="foo", + radius=self.radius, + ) + + @responses.activate + def test_place_detail(self): + url = "https://maps.googleapis.com/maps/api/place/details/json" + responses.add( + responses.GET, + url, + body='{"status": "OK", "result": {}, "html_attributions": []}', + status=200, + content_type="application/json", + ) + + self.client.place( + "ChIJN1t_tDeuEmsRUsoyG83frY4", + fields=["business_status", "geometry/location", "place_id"], + language=self.language, + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4" + "&key=%s&fields=business_status,geometry/location,place_id" + % (url, self.key), + responses.calls[0].request.url, + ) + + with self.assertRaises(ValueError): + self.client.place( + "ChIJN1t_tDeuEmsRUsoyG83frY4", fields=["geometry", "invalid"] + ) + + @responses.activate + def test_photo(self): + url = "https://maps.googleapis.com/maps/api/place/photo" + responses.add(responses.GET, url, status=200) + + ref = "CnRvAAAAwMpdHeWlXl-lH0vp7lez4znKPIWSWvgvZFISdKx45AwJVP1Qp37YOrH7sqHMJ8C-vBDC546decipPHchJhHZL94RcTUfPa1jWzo-rSHaTlbNtjh-N68RkcToUCuY9v2HNpo5mziqkir37WU8FJEqVBIQ4k938TI3e7bf8xq-uwDZcxoUbO_ZJzPxremiQurAYzCTwRhE_V0" + response = self.client.places_photo(ref, max_width=100) + + self.assertTrue(isinstance(response, GeneratorType)) + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "%s?maxwidth=100&photoreference=%s&key=%s" % (url, ref, self.key), + responses.calls[0].request.url, + ) + + @responses.activate + def test_autocomplete(self): + url = "https://maps.googleapis.com/maps/api/place/autocomplete/json" + responses.add( + responses.GET, + url, + body='{"status": "OK", "predictions": []}', + status=200, + content_type="application/json", + ) + + session_token = uuid.uuid4().hex + + self.client.places_autocomplete( + "Google", + session_token=session_token, + offset=3, + location=self.location, + radius=self.radius, + language=self.language, + types="geocode", + components={"country": "au"}, + strict_bounds=True, + ) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "%s?components=country%%3Aau&input=Google&language=en-AU&" + "location=-33.86746%%2C151.20709&offset=3&radius=100&" + "strictbounds=true&types=geocode&key=%s&sessiontoken=%s" + % (url, self.key, session_token), + responses.calls[0].request.url, + ) + + @responses.activate + def test_autocomplete_query(self): + url = "https://maps.googleapis.com/maps/api/place/queryautocomplete/json" + responses.add( + responses.GET, + url, + body='{"status": "OK", "predictions": []}', + status=200, + content_type="application/json", + ) + + self.client.places_autocomplete_query("pizza near New York") + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "%s?input=pizza+near+New+York&key=%s" % (url, self.key), + responses.calls[0].request.url, + ) diff --git a/tests/test_roads.py b/tests/test_roads.py new file mode 100644 index 00000000..bfea2bf7 --- /dev/null +++ b/tests/test_roads.py @@ -0,0 +1,158 @@ +# +# Copyright 2015 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the roads module.""" + + +import responses + +import googlemaps +from . import TestCase + + +class RoadsTest(TestCase): + def setUp(self): + self.key = "AIzaasdf" + self.client = googlemaps.Client(self.key) + + @responses.activate + def test_snap(self): + responses.add( + responses.GET, + "https://roads.googleapis.com/v1/snapToRoads", + body='{"snappedPoints":["foo"]}', + status=200, + content_type="application/json", + ) + + results = self.client.snap_to_roads((40.714728, -73.998672)) + self.assertEqual("foo", results[0]) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://roads.googleapis.com/v1/snapToRoads?" + "path=40.714728%%2C-73.998672&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_nearest_roads(self): + responses.add( + responses.GET, + "https://roads.googleapis.com/v1/nearestRoads", + body='{"snappedPoints":["foo"]}', + status=200, + content_type="application/json", + ) + + results = self.client.nearest_roads((40.714728, -73.998672)) + self.assertEqual("foo", results[0]) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://roads.googleapis.com/v1/nearestRoads?" + "points=40.714728%%2C-73.998672&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_path(self): + responses.add( + responses.GET, + "https://roads.googleapis.com/v1/speedLimits", + body='{"speedLimits":["foo"]}', + status=200, + content_type="application/json", + ) + + results = self.client.snapped_speed_limits([(1, 2), (3, 4)]) + self.assertEqual("foo", results["speedLimits"][0]) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://roads.googleapis.com/v1/speedLimits?" + "path=1%%2C2|3%%2C4" + "&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_speedlimits(self): + responses.add( + responses.GET, + "https://roads.googleapis.com/v1/speedLimits", + body='{"speedLimits":["foo"]}', + status=200, + content_type="application/json", + ) + + results = self.client.speed_limits("id1") + self.assertEqual("foo", results[0]) + self.assertEqual( + "https://roads.googleapis.com/v1/speedLimits?" + "placeId=id1&key=%s" % self.key, + responses.calls[0].request.url, + ) + + @responses.activate + def test_speedlimits_multiple(self): + responses.add( + responses.GET, + "https://roads.googleapis.com/v1/speedLimits", + body='{"speedLimits":["foo"]}', + status=200, + content_type="application/json", + ) + + results = self.client.speed_limits(["id1", "id2", "id3"]) + self.assertEqual("foo", results[0]) + self.assertEqual( + "https://roads.googleapis.com/v1/speedLimits?" + "placeId=id1&placeId=id2&placeId=id3" + "&key=%s" % self.key, + responses.calls[0].request.url, + ) + + def test_clientid_not_accepted(self): + client = googlemaps.Client(client_id="asdf", client_secret="asdf") + + with self.assertRaises(ValueError): + client.speed_limits("foo") + + @responses.activate + def test_retry(self): + class request_callback: + def __init__(self): + self.first_req = True + + def __call__(self, req): + if self.first_req: + self.first_req = False + return (500, {}, "Internal Server Error.") + return (200, {}, '{"speedLimits":[]}') + + responses.add_callback( + responses.GET, + "https://roads.googleapis.com/v1/speedLimits", + content_type="application/json", + callback=request_callback(), + ) + + self.client.speed_limits([]) + + self.assertEqual(2, len(responses.calls)) + self.assertEqual(responses.calls[0].request.url, responses.calls[1].request.url) diff --git a/googlemaps/test/test_timezone.py b/tests/test_timezone.py similarity index 56% rename from googlemaps/test/test_timezone.py rename to tests/test_timezone.py index 23daeca3..9d2edc1c 100644 --- a/googlemaps/test/test_timezone.py +++ b/tests/test_timezone.py @@ -21,58 +21,62 @@ import datetime import responses -import mock - +from unittest import mock import googlemaps -import googlemaps.test as _test - +from . import TestCase -class TimezoneTest(_test.TestCase): +class TimezoneTest(TestCase): def setUp(self): self.key = "AIzaasdf" self.client = googlemaps.Client(self.key) @responses.activate def test_los_angeles(self): - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/timezone/json", - body='{"status":"OK"}', - status=200, - content_type="application/json") + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/timezone/json", + body='{"status":"OK"}', + status=200, + content_type="application/json", + ) ts = 1331766000 timezone = self.client.timezone((39.603481, -119.682251), ts) self.assertIsNotNone(timezone) self.assertEqual(1, len(responses.calls)) - self.assertURLEqual("https://maps.googleapis.com/maps/api/timezone/json" - "?location=39.603481,-119.682251×tamp=%d" - "&key=%s" % - (ts, self.key), - responses.calls[0].request.url) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/timezone/json" + "?location=39.603481,-119.682251×tamp=%d" + "&key=%s" % (ts, self.key), + responses.calls[0].request.url, + ) class MockDatetime(object): - def now(self): return datetime.datetime.fromtimestamp(1608) + utcnow = now @responses.activate @mock.patch("googlemaps.timezone.datetime", MockDatetime()) def test_los_angeles_with_no_timestamp(self): - responses.add(responses.GET, - "https://maps.googleapis.com/maps/api/timezone/json", - body='{"status":"OK"}', - status=200, - content_type="application/json") + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/timezone/json", + body='{"status":"OK"}', + status=200, + content_type="application/json", + ) timezone = self.client.timezone((39.603481, -119.682251)) self.assertIsNotNone(timezone) self.assertEqual(1, len(responses.calls)) - self.assertURLEqual("https://maps.googleapis.com/maps/api/timezone/json" - "?location=39.603481,-119.682251×tamp=%d" - "&key=%s" % - (1608, self.key), - responses.calls[0].request.url) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/timezone/json" + "?location=39.603481,-119.682251×tamp=%d" + "&key=%s" % (1608, self.key), + responses.calls[0].request.url, + ) From 5fd6f0e962a2afbff4e6190e4ae91841b756ff4f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 27 Apr 2020 17:12:45 +0000 Subject: [PATCH 184/260] chore(release): 4.3.1 [skip ci] ## [4.3.1](https://github.com/googlemaps/google-maps-services-python/compare/v4.3.0...v4.3.1) (2020-04-27) ### Bug Fixes * cleanup test and remove from package ([#361](https://github.com/googlemaps/google-maps-services-python/issues/361)) ([428b5c9](https://github.com/googlemaps/google-maps-services-python/commit/428b5c9d76d0c5c5e5000cbec8fc9ace810ac856)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 49ad240f..d5730529 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.3.0" +__version__ = "4.3.1" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 198d283e..8771dec9 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.3.0", + version="4.3.1", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From c6292185c1e2722119ea576c4a22b26b8d6c9d02 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 4 May 2020 09:54:39 -0700 Subject: [PATCH 185/260] docs: removed old image from readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2b6ad08d..e2c8d1d4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ Python Client for Google Maps Services Use Python? Want to geocode something? Looking for directions? Maybe matrices of directions? This library brings the Google Maps Platform Web Services to your Python application. -![Analytics](https://maps-ga-beacon.appspot.com/UA-12846745-20/google-maps-services-python/readme?pixel) The Python Client for Google Maps Services is a Python Client library for the following Google Maps APIs: From cc13049aa2490351da78cb088793e8565d25e9a3 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 8 May 2020 09:11:25 -0700 Subject: [PATCH 186/260] docs: update broken link in readme closes #363 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2c8d1d4..31840082 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ directions_result = gmaps.directions("Sydney Town Hall", departure_time=now) ``` -For more usage examples, check out [the tests](https://github.com/googlemaps/google-maps-services-python/tree/master/googlemaps/test). +For more usage examples, check out [the tests](https://github.com/googlemaps/google-maps-services-python/tree/master/tests). ## Features From c875f3561c040c9b16c2d5c2b58d75cb0a7793cf Mon Sep 17 00:00:00 2001 From: thatguysimon Date: Thu, 14 May 2020 19:14:23 +0300 Subject: [PATCH 187/260] feat: Allow overriding base_url on Client object initialization (#364) --- googlemaps/client.py | 13 +++++++++++-- tests/test_client.py | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index ae2b4891..546f7796 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -53,7 +53,8 @@ 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, experience_id=None): + retry_over_query_limit=True, experience_id=None, + base_url=_DEFAULT_BASE_URL): """ :param key: Maps API key. Required, unless "client_id" and "client_secret" are set. Most users should use an API key. @@ -115,6 +116,10 @@ def __init__(self, key=None, client_id=None, client_secret=None, implemented. See the official requests docs for more info: http://docs.python-requests.org/en/latest/api/#main-interface :type requests_kwargs: dict + + :param base_url: The base URL for all requests. Defaults to the Maps API + server. Should not have a trailing slash. + :type base_url: string """ if not key and not (client_secret and client_id): @@ -167,6 +172,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.retry_over_query_limit = retry_over_query_limit self.sent_times = collections.deque("", queries_per_second) self.set_experience_id(experience_id) + self.base_url = base_url def set_experience_id(self, *experience_id_args): """Sets the value for the HTTP header field name @@ -204,7 +210,7 @@ def clear_experience_id(self): 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, + base_url=None, accepts_clientid=True, extract_body=None, requests_kwargs=None, post_json=None): """Performs HTTP GET/POST with credentials, returning the body as JSON. @@ -246,6 +252,9 @@ def _request(self, url, params, first_request_time=None, retry_counter=0, exceute a request. """ + if base_url is None: + base_url = self.base_url + if not first_request_time: first_request_time = datetime.now() diff --git a/tests/test_client.py b/tests/test_client.py index 3ad92772..a18221fa 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -209,7 +209,22 @@ def test_transport_error(self): self.assertEqual(e.exception.status_code, 404) @responses.activate - def test_host_override(self): + def test_host_override_on_init(self): + responses.add( + responses.GET, + "https://foo.com/bar", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + client = googlemaps.Client(key="AIzaasdf", base_url="https://foo.com") + client._get("/bar", {}) + + self.assertEqual(1, len(responses.calls)) + + @responses.activate + def test_host_override_per_request(self): responses.add( responses.GET, "https://foo.com/bar", From 865cf78caa46aaa8d1e5918e8469bb3b2ac06314 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 14 May 2020 16:18:36 +0000 Subject: [PATCH 188/260] chore(release): 4.4.0 [skip ci] # [4.4.0](https://github.com/googlemaps/google-maps-services-python/compare/v4.3.1...v4.4.0) (2020-05-14) ### Features * Allow overriding base_url on Client object initialization ([#364](https://github.com/googlemaps/google-maps-services-python/issues/364)) ([c875f35](https://github.com/googlemaps/google-maps-services-python/commit/c875f3561c040c9b16c2d5c2b58d75cb0a7793cf)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index d5730529..3255e2c1 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.3.1" +__version__ = "4.4.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 8771dec9..be72bd82 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.3.1", + version="4.4.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 5b289d7484ae6dcf15575bdd2b5c2bd32cd68aca Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Tue, 26 May 2020 08:28:46 -0700 Subject: [PATCH 189/260] fix: mark permanently_closed as deprecated (#365) --- googlemaps/places.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 1ead5fe4..0456b461 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -100,6 +100,13 @@ ^ PLACES_DETAIL_FIELDS_ATMOSPHERE ) +DEPRECATED_FIELDS = {"permanently_closed"} +DEPRECATED_FIELDS_MESSAGE = ( + "Fields, %s, are deprecated. " + "Read more at https://developers.google.com/maps/deprecations." +) + + def find_place( client, input, input_type, fields=None, location_bias=None, language=None ): @@ -143,7 +150,14 @@ def find_place( "the given value is invalid: '%s'" % input_type ) - if fields: + if fields: + deprecated_fields = set(fields) & DEPRECATED_FIELDS + if deprecated_fields: + warnings.warn( + DEPRECATED_FIELDS_MESSAGE % str(list(deprecated_fields)), + DeprecationWarning, + ) + invalid_fields = set(fields) - PLACES_FIND_FIELDS if invalid_fields: raise ValueError( @@ -422,6 +436,13 @@ def place(client, place_id, session_token=None, fields=None, language=None): params = {"placeid": place_id} if fields: + deprecated_fields = set(fields) & DEPRECATED_FIELDS + if deprecated_fields: + warnings.warn( + DEPRECATED_FIELDS_MESSAGE % str(list(deprecated_fields)), + DeprecationWarning, + ) + invalid_fields = set(fields) - PLACES_DETAIL_FIELDS if invalid_fields: raise ValueError( From 104ab3466d40a1061f511ad689a56fefa9fbdc87 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 26 May 2020 15:29:57 +0000 Subject: [PATCH 190/260] chore(release): 4.4.1 [skip ci] ## [4.4.1](https://github.com/googlemaps/google-maps-services-python/compare/v4.4.0...v4.4.1) (2020-05-26) ### Bug Fixes * mark permanently_closed as deprecated ([#365](https://github.com/googlemaps/google-maps-services-python/issues/365)) ([5b289d7](https://github.com/googlemaps/google-maps-services-python/commit/5b289d7484ae6dcf15575bdd2b5c2bd32cd68aca)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 3255e2c1..f98dfcc0 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.4.0" +__version__ = "4.4.1" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index be72bd82..038f0e37 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.4.0", + version="4.4.1", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From d39ff32820a8e77e2f789af81590fcc3a3770797 Mon Sep 17 00:00:00 2001 From: Mariatta Date: Tue, 14 Jul 2020 08:09:19 -0700 Subject: [PATCH 191/260] docs: fix params for find_place. (#369) --- googlemaps/places.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 0456b461..a4ab63ed 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -123,10 +123,9 @@ def find_place( or 'phonenumber'. :type input_type: string - :param fields: The fields specifying the types of place data to return, - separated by a comma. For full details see: + :param fields: The fields specifying the types of place data to return. For full details see: https://developers.google.com/places/web-service/search#FindPlaceRequests - :type input: list + :type fields: list :param location_bias: Prefer results in a specified area, by specifying either a radius plus lat/lng, or two lat/lng pairs From adf9cdeeb8eae5e1c07716b2138be2174e5972dd Mon Sep 17 00:00:00 2001 From: Bharat Raghunathan Date: Wed, 22 Jul 2020 17:52:21 +0000 Subject: [PATCH 192/260] docs: add proper code block formatting to the docs (#371) --- googlemaps/places.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index a4ab63ed..af070c4c 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -477,13 +477,13 @@ def places_photo(client, photo_reference, max_width=None, max_height=None): :rtype: iterator containing the raw image data, which typically can be used to save an image file locally. For example: - ``` + .. code-block:: python + f = open(local_filename, 'wb') for chunk in client.places_photo(photo_reference, max_width=100): if chunk: f.write(chunk) f.close() - ``` """ if not (max_width or max_height): From c942b865c2247a6dcdffd3dd5e20a367d8d705d2 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Tue, 28 Jul 2020 11:45:09 -0600 Subject: [PATCH 193/260] fix: static map label (#374) --- googlemaps/maps.py | 4 ++-- tests/test_maps.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/googlemaps/maps.py b/googlemaps/maps.py index eedcc422..763e0126 100644 --- a/googlemaps/maps.py +++ b/googlemaps/maps.py @@ -75,8 +75,8 @@ def __init__(self, locations, self.params.append("color:%s" % color) if label: - if len(label) != 1 or not label.isupper() or not label.isalnum(): - raise ValueError("Invalid label") + if len(label) != 1 or (label.isalpha() and not label.isupper()) or not label.isalnum(): + raise ValueError("Marker label must be alphanumeric and uppercase.") self.params.append("label:%s" % label) self.params.append(convert.location_list(locations)) diff --git a/tests/test_maps.py b/tests/test_maps.py index db83ee04..8db6298f 100644 --- a/tests/test_maps.py +++ b/tests/test_maps.py @@ -49,6 +49,8 @@ def test_static_map_marker(self): with self.assertRaises(ValueError): StaticMapMarker(locations=["Sydney"], label="XS") + self.assertEqual("label:1|Sydney", str(StaticMapMarker(locations=["Sydney"], label="1"))) + @responses.activate def test_static_map_path(self): path = StaticMapPath( From 5253cbc8b7e47bb1f5c1774b2a30705b158fcd8c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 28 Jul 2020 17:45:55 +0000 Subject: [PATCH 194/260] chore(release): 4.4.2 [skip ci] ## [4.4.2](https://github.com/googlemaps/google-maps-services-python/compare/v4.4.1...v4.4.2) (2020-07-28) ### Bug Fixes * static map label ([#374](https://github.com/googlemaps/google-maps-services-python/issues/374)) ([c942b86](https://github.com/googlemaps/google-maps-services-python/commit/c942b865c2247a6dcdffd3dd5e20a367d8d705d2)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index f98dfcc0..b6f45f8c 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.4.1" +__version__ = "4.4.2" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 038f0e37..b9b3aeea 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.4.1", + version="4.4.2", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 44cafa059f0d76599525001fc39f448fc0c722c8 Mon Sep 17 00:00:00 2001 From: Harsh Mishra Date: Mon, 1 Mar 2021 23:48:50 +0530 Subject: [PATCH 195/260] fix: fixed code quality issues (#398) * Add .deepsource.toml * Fixed Object Inheritance * Replace ternary syntax with if expression * Use literal syntax to create data structure --- .deepsource.toml | 10 ++++++++++ googlemaps/client.py | 4 ++-- googlemaps/convert.py | 4 +--- googlemaps/maps.py | 11 +++-------- googlemaps/places.py | 29 +++++++++-------------------- tests/test_timezone.py | 2 +- 6 files changed, 26 insertions(+), 34 deletions(-) create mode 100644 .deepsource.toml diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 00000000..f772ce8c --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,10 @@ +version = 1 + +test_patterns = ["tests/**"] + +[[analyzers]] +name = "python" +enabled = true + + [analyzers.meta] + runtime_version = "3.x.x" diff --git a/googlemaps/client.py b/googlemaps/client.py index 546f7796..980509e2 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -43,10 +43,10 @@ _USER_AGENT = "GoogleGeoApiClientPython/%s" % googlemaps.__version__ _DEFAULT_BASE_URL = "https://maps.googleapis.com" -_RETRIABLE_STATUSES = set([500, 503, 504]) +_RETRIABLE_STATUSES = {500, 503, 504} -class Client(object): +class Client: """Performs requests to the Google Maps API web services.""" def __init__(self, key=None, client_id=None, client_secret=None, diff --git a/googlemaps/convert.py b/googlemaps/convert.py index 7dfa9882..2b3d056e 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -160,9 +160,7 @@ def _is_list(arg): return False if isinstance(arg, str): # Python 3-only, as str has __iter__ return False - return (not _has_method(arg, "strip") - and _has_method(arg, "__getitem__") - or _has_method(arg, "__iter__")) + return _has_method(arg, "__getitem__") if not _has_method(arg, "strip") else _has_method(arg, "__iter__") def is_string(val): diff --git a/googlemaps/maps.py b/googlemaps/maps.py index 763e0126..cc1a054e 100644 --- a/googlemaps/maps.py +++ b/googlemaps/maps.py @@ -20,16 +20,11 @@ from googlemaps import convert -MAPS_IMAGE_FORMATS = set( - ['png8', 'png', 'png32', 'gif', 'jpg', 'jpg-baseline'] -) +MAPS_IMAGE_FORMATS = {'png8', 'png', 'png32', 'gif', 'jpg', 'jpg-baseline'} -MAPS_MAP_TYPES = set( - ['roadmap', 'satellite', 'terrain', 'hybrid'] -) +MAPS_MAP_TYPES = {'roadmap', 'satellite', 'terrain', 'hybrid'} - -class StaticMapParam(object): +class StaticMapParam: """Base class to handle parameters for Maps Static API.""" def __init__(self): diff --git a/googlemaps/places.py b/googlemaps/places.py index af070c4c..f84bb84d 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -21,9 +21,7 @@ from googlemaps import convert -PLACES_FIND_FIELDS_BASIC = set( - [ - "business_status", +PLACES_FIND_FIELDS_BASIC = {"business_status", "formatted_address", "geometry", "geometry/location", @@ -42,13 +40,11 @@ "photos", "place_id", "plus_code", - "types", - ] -) + "types",} -PLACES_FIND_FIELDS_CONTACT = set(["opening_hours"]) +PLACES_FIND_FIELDS_CONTACT = {"opening_hours"} -PLACES_FIND_FIELDS_ATMOSPHERE = set(["price_level", "rating", "user_ratings_total"]) +PLACES_FIND_FIELDS_ATMOSPHERE = {"price_level", "rating", "user_ratings_total"} PLACES_FIND_FIELDS = ( PLACES_FIND_FIELDS_BASIC @@ -56,9 +52,7 @@ ^ PLACES_FIND_FIELDS_ATMOSPHERE ) -PLACES_DETAIL_FIELDS_BASIC = set( - [ - "address_component", +PLACES_DETAIL_FIELDS_BASIC = {"address_component", "adr_address", "business_status", "formatted_address", @@ -82,17 +76,11 @@ "type", "url", "utc_offset", - "vicinity", - ] -) + "vicinity",} -PLACES_DETAIL_FIELDS_CONTACT = set( - ["formatted_phone_number", "international_phone_number", "opening_hours", "website"] -) +PLACES_DETAIL_FIELDS_CONTACT = {"formatted_phone_number", "international_phone_number", "opening_hours", "website"} -PLACES_DETAIL_FIELDS_ATMOSPHERE = set( - ["price_level", "rating", "review", "user_ratings_total"] -) +PLACES_DETAIL_FIELDS_ATMOSPHERE = {"price_level", "rating", "review", "user_ratings_total"} PLACES_DETAIL_FIELDS = ( PLACES_DETAIL_FIELDS_BASIC @@ -658,3 +646,4 @@ def _autocomplete( url = "/maps/api/place/%sautocomplete/json" % url_part return client._request(url, params).get("predictions", []) + \ No newline at end of file diff --git a/tests/test_timezone.py b/tests/test_timezone.py index 9d2edc1c..a1d7394e 100644 --- a/tests/test_timezone.py +++ b/tests/test_timezone.py @@ -53,7 +53,7 @@ def test_los_angeles(self): responses.calls[0].request.url, ) - class MockDatetime(object): + class MockDatetime: def now(self): return datetime.datetime.fromtimestamp(1608) From f9c8febd3d2705a9a0ac329dd8e0ce6dcef4d0c2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 1 Mar 2021 18:19:45 +0000 Subject: [PATCH 196/260] chore(release): 4.4.3 [skip ci] ## [4.4.3](https://github.com/googlemaps/google-maps-services-python/compare/v4.4.2...v4.4.3) (2021-03-01) ### Bug Fixes * fixed code quality issues ([#398](https://github.com/googlemaps/google-maps-services-python/issues/398)) ([44cafa0](https://github.com/googlemaps/google-maps-services-python/commit/44cafa059f0d76599525001fc39f448fc0c722c8)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index b6f45f8c..f0568e3d 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.4.2" +__version__ = "4.4.3" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index b9b3aeea..11e0dc62 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.4.2", + version="4.4.3", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 6b29efd1ac377e7fe9d5d552bf3bc85894fa959a Mon Sep 17 00:00:00 2001 From: Jake <1993773+jengel3@users.noreply.github.com> Date: Fri, 12 Mar 2021 12:54:39 -0600 Subject: [PATCH 197/260] fix: make Places textsearch query parameter optional (#399) --- googlemaps/places.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index f84bb84d..e288dc91 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -168,7 +168,7 @@ def find_place( def places( client, - query, + query=None, location=None, radius=None, language=None, From dfd09e647f03edc107d16e3e051a22e62250dbfc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 12 Mar 2021 19:00:18 +0000 Subject: [PATCH 198/260] chore(release): 4.4.4 [skip ci] ## [4.4.4](https://github.com/googlemaps/google-maps-services-python/compare/v4.4.3...v4.4.4) (2021-03-12) ### Bug Fixes * make Places textsearch query parameter optional ([#399](https://github.com/googlemaps/google-maps-services-python/issues/399)) ([6b29efd](https://github.com/googlemaps/google-maps-services-python/commit/6b29efd1ac377e7fe9d5d552bf3bc85894fa959a)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index f0568e3d..2bf13650 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.4.3" +__version__ = "4.4.4" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 11e0dc62..f7089df2 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.4.3", + version="4.4.4", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 14a8f0b92b55405a3853682c155c1d9329a9823b Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 15 Mar 2021 11:49:10 -0600 Subject: [PATCH 199/260] chore: run publish in release exec (#401) --- .github/scripts/install.sh | 2 +- .github/workflows/publish.yml | 48 ----------------------------------- .github/workflows/release.yml | 10 ++++++++ .releaserc | 11 ++++++-- 4 files changed, 20 insertions(+), 51 deletions(-) delete mode 100644 .github/workflows/publish.yml diff --git a/.github/scripts/install.sh b/.github/scripts/install.sh index 5cd76712..91235742 100755 --- a/.github/scripts/install.sh +++ b/.github/scripts/install.sh @@ -6,7 +6,7 @@ if ! python3 -m pip --version; then curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py sudo python3 get-pip.py sudo python3 -m pip install --upgrade setuptools - sudo python3 -m pip install nox + sudo python3 -m pip install nox twine else sudo python3 -m pip install --upgrade setuptools python3 -m pip install nox diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index a0ea83af..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2020 Google LLC -# -# 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. - -# A workflow that pushes artifacts to Sonatype -name: Publish - -on: - push: - tags: - - '*' - repository_dispatch: - types: [publish] - -jobs: - publish: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup Python - uses: "actions/setup-python@v1" - with: - python-version: "3.6" - - - name: Install dependencies - run: ./.github/scripts/install.sh - - - name: Run distribution - run: python3 -m nox -e distribution - - - name: Deploy to PyPi - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bebedf46..d27f41d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,10 +20,18 @@ jobs: release: runs-on: ubuntu-latest steps: + - name: Setup Python + uses: "actions/setup-python@v1" + with: + python-version: "3.6" - name: Checkout uses: actions/checkout@v2 with: token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} + - name: Install dependencies + run: ./.github/scripts/install.sh + - name: Run distribution + run: python3 -m nox -e distribution - name: Semantic Release uses: cycjimmy/semantic-release-action@v2 with: @@ -35,3 +43,5 @@ jobs: "@semantic-release/github env: GH_TOKEN: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/.releaserc b/.releaserc index 981e4031..7daf2b19 100644 --- a/.releaserc +++ b/.releaserc @@ -11,8 +11,15 @@ plugins: to: "__version__ = \"${nextRelease.version}\"" - files: - "./setup.py" - from: "version=\".*\"" - to: "version=\"${nextRelease.version}\"" + from: 'version=".*"' + to: 'version="${nextRelease.version}"' + - - "@semantic-release/exec" + - prepareCmd: + - rm -rf dist + - python3 setup.py sdist + - python3 -m twine check dist/* + - publishCmd: + - python3 -m twine upload dist/* - - "@semantic-release/git" - assets: - "./googlemaps/__init__.py" From bd0788f617679663e3dd7dbafe88ddb05421f9f7 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 15 Mar 2021 18:07:46 +0000 Subject: [PATCH 200/260] chore: include exec plugin --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d27f41d4..c4708c48 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,7 @@ jobs: "@semantic-release/commit-analyzer" "@semantic-release/release-notes-generator" "@google/semantic-release-replace-plugin" + "@semantic-release/exec" "@semantic-release/git "@semantic-release/github env: From fc0d02660f56a4a4a3d2edd7656e2a881701ea0c Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 15 Mar 2021 18:19:03 +0000 Subject: [PATCH 201/260] chore: fix exec command --- .releaserc | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.releaserc b/.releaserc index 7daf2b19..6d2b9617 100644 --- a/.releaserc +++ b/.releaserc @@ -14,12 +14,8 @@ plugins: from: 'version=".*"' to: 'version="${nextRelease.version}"' - - "@semantic-release/exec" - - prepareCmd: - - rm -rf dist - - python3 setup.py sdist - - python3 -m twine check dist/* - - publishCmd: - - python3 -m twine upload dist/* + - prepareCmd: rm -rf dist && python3 setup.py sdist && python3 -m twine check dist/* + - publishCmd: python3 -m twine upload dist/* - - "@semantic-release/git" - assets: - "./googlemaps/__init__.py" From 5df4f403ed87fa963fb43b405744ca531a2f6881 Mon Sep 17 00:00:00 2001 From: Andrew Gomez Date: Mon, 15 Mar 2021 13:24:58 -0500 Subject: [PATCH 202/260] docs: specify support for place ID parameter in distance_matrix.py (#400) Updated param comments to specify support for place ID via 'place_id:' Per API documentation: "using place IDs is preferred over using addresses or latitude/longitude coordinates" Created test_place_id_param() to test case when origin/destination consist solely of place IDs. Updated test_mixed_params() to include a place ID in the origin. --- googlemaps/distance_matrix.py | 16 +++++++------- tests/test_distance_matrix.py | 39 +++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/googlemaps/distance_matrix.py b/googlemaps/distance_matrix.py index 1d848253..a30cbe09 100755 --- a/googlemaps/distance_matrix.py +++ b/googlemaps/distance_matrix.py @@ -26,17 +26,19 @@ def distance_matrix(client, origins, destinations, transit_routing_preference=None, traffic_model=None, region=None): """ Gets travel distance and time for a matrix of origins and destinations. - :param origins: One or more locations and/or latitude/longitude values, - from which to calculate distance and time. If you pass an address as - a string, the service will geocode the string and convert it to a + :param origins: One or more addresses, Place IDs, and/or latitude/longitude + values, from which to calculate distance and time. Each Place ID string + must be prepended with 'place_id:'. If you pass an address as a string, + the service will geocode the string and convert it to a latitude/longitude coordinate to calculate directions. :type origins: a single location, or a list of locations, where a location is a string, dict, list, or tuple - :param destinations: One or more addresses and/or lat/lng values, to - which to calculate distance and time. If you pass an address as a - string, the service will geocode the string and convert it to a - latitude/longitude coordinate to calculate directions. + :param destinations: One or more addresses, Place IDs, and/or lat/lng values + , to which to calculate distance and time. Each Place ID string must be + prepended with 'place_id:'. If you pass an address as a string, the + service will geocode the string and convert it to a latitude/longitude + coordinate to calculate directions. :type destinations: a single location, or a list of locations, where a location is a string, dict, list, or tuple diff --git a/tests/test_distance_matrix.py b/tests/test_distance_matrix.py index a906f8ce..6946782e 100644 --- a/tests/test_distance_matrix.py +++ b/tests/test_distance_matrix.py @@ -84,7 +84,10 @@ def test_mixed_params(self): content_type="application/json", ) - origins = ["Bobcaygeon ON", [41.43206, -81.38992]] + origins = [ + "Bobcaygeon ON", [41.43206, -81.38992], + "place_id:ChIJ7cv00DwsDogRAMDACa2m4K8" + ] destinations = [ (43.012486, -83.6964149), {"lat": 42.8863855, "lng": -78.8781627}, @@ -95,7 +98,8 @@ def test_mixed_params(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual( "https://maps.googleapis.com/maps/api/distancematrix/json?" - "key=%s&origins=Bobcaygeon+ON%%7C41.43206%%2C-81.38992&" + "key=%s&origins=Bobcaygeon+ON%%7C41.43206%%2C-81.38992%%7C" + "place_id%%3AChIJ7cv00DwsDogRAMDACa2m4K8&" "destinations=43.012486%%2C-83.6964149%%7C42.8863855%%2C" "-78.8781627" % self.key, responses.calls[0].request.url, @@ -181,3 +185,34 @@ def test_lang_param(self): "destinations=San+Francisco%%7CVictoria+BC" % self.key, responses.calls[0].request.url, ) + @responses.activate + def test_place_id_param(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/distancematrix/json", + body='{"status":"OK","rows":[]}', + status=200, + content_type="application/json", + ) + + origins = [ + 'place_id:ChIJ7cv00DwsDogRAMDACa2m4K8', + 'place_id:ChIJzxcfI6qAa4cR1jaKJ_j0jhE', + ] + destinations = [ + 'place_id:ChIJPZDrEzLsZIgRoNrpodC5P30', + 'place_id:ChIJjQmTaV0E9YgRC2MLmS_e_mY', + ] + + matrix = self.client.distance_matrix(origins, destinations) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/distancematrix/json?" + "key=%s&" + "origins=place_id%%3AChIJ7cv00DwsDogRAMDACa2m4K8%%7C" + "place_id%%3AChIJzxcfI6qAa4cR1jaKJ_j0jhE&" + "destinations=place_id%%3AChIJPZDrEzLsZIgRoNrpodC5P30%%7C" + "place_id%%3AChIJjQmTaV0E9YgRC2MLmS_e_mY" % self.key, + responses.calls[0].request.url, + ) From 4ad0c4905972e35d58dce6a9091f84db4640bd3f Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 15 Mar 2021 18:31:10 +0000 Subject: [PATCH 203/260] build: fix escape --- .github/workflows/release.yml | 4 ++-- .releaserc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4708c48..8e149580 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,8 +40,8 @@ jobs: "@semantic-release/release-notes-generator" "@google/semantic-release-replace-plugin" "@semantic-release/exec" - "@semantic-release/git - "@semantic-release/github + "@semantic-release/git" + "@semantic-release/github" env: GH_TOKEN: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} TWINE_USERNAME: __token__ diff --git a/.releaserc b/.releaserc index 6d2b9617..d34a775c 100644 --- a/.releaserc +++ b/.releaserc @@ -14,8 +14,8 @@ plugins: from: 'version=".*"' to: 'version="${nextRelease.version}"' - - "@semantic-release/exec" - - prepareCmd: rm -rf dist && python3 setup.py sdist && python3 -m twine check dist/* - - publishCmd: python3 -m twine upload dist/* + - prepareCmd: "rm -rf dist && python3 setup.py sdist && python3 -m twine check dist/*" + - publishCmd: "python3 -m twine upload dist/*" - - "@semantic-release/git" - assets: - "./googlemaps/__init__.py" From 6d2ee9642a1849ed64cdce76692f68a753fc286a Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 15 Mar 2021 19:04:38 +0000 Subject: [PATCH 204/260] chore: options is not an array --- .releaserc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.releaserc b/.releaserc index d34a775c..db70f6e2 100644 --- a/.releaserc +++ b/.releaserc @@ -14,8 +14,8 @@ plugins: from: 'version=".*"' to: 'version="${nextRelease.version}"' - - "@semantic-release/exec" - - prepareCmd: "rm -rf dist && python3 setup.py sdist && python3 -m twine check dist/*" - - publishCmd: "python3 -m twine upload dist/*" + - prepareCmd: "rm -rf dist && python3 setup.py sdist && python3 -m twine check dist/*" + publishCmd: "python3 -m twine upload dist/*" - - "@semantic-release/git" - assets: - "./googlemaps/__init__.py" From b963ff945c2343d34fa9d3b3e6acec02f987ca95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Vasi=C4=87?= Date: Mon, 15 Mar 2021 20:14:29 +0100 Subject: [PATCH 205/260] fix: Add parameter 'origin' to places autocomplete (#392) --- googlemaps/places.py | 11 +++++++++++ tests/test_places.py | 2 ++ 2 files changed, 13 insertions(+) diff --git a/googlemaps/places.py b/googlemaps/places.py index e288dc91..91c14dfe 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -501,6 +501,7 @@ def places_autocomplete( input_text, session_token=None, offset=None, + origin=None, location=None, radius=None, language=None, @@ -525,6 +526,12 @@ def places_autocomplete( service will match on 'Goo'. :type offset: int + :param origin: The origin point from which to calculate straight-line distance + to the destination (returned as distance_meters). + If this value is omitted, straight-line distance will + not be returned. + :type origin: string, dict, list, or tuple + :param location: The latitude/longitude value for which you wish to obtain the closest, human-readable address. :type location: string, dict, list, or tuple @@ -558,6 +565,7 @@ def places_autocomplete( input_text, session_token=session_token, offset=offset, + origin=origin, location=location, radius=radius, language=language, @@ -611,6 +619,7 @@ def _autocomplete( input_text, session_token=None, offset=None, + origin=None, location=None, radius=None, language=None, @@ -629,6 +638,8 @@ def _autocomplete( params["sessiontoken"] = session_token if offset: params["offset"] = offset + if origin: + params["origin"] = convert.latlng(origin) if location: params["location"] = convert.latlng(location) if radius: diff --git a/tests/test_places.py b/tests/test_places.py index 5316fe8e..50781773 100644 --- a/tests/test_places.py +++ b/tests/test_places.py @@ -212,6 +212,7 @@ def test_autocomplete(self): "Google", session_token=session_token, offset=3, + origin=self.location, location=self.location, radius=self.radius, language=self.language, @@ -223,6 +224,7 @@ def test_autocomplete(self): self.assertEqual(1, len(responses.calls)) self.assertURLEqual( "%s?components=country%%3Aau&input=Google&language=en-AU&" + "origin=-33.86746%%2C151.20709&" "location=-33.86746%%2C151.20709&offset=3&radius=100&" "strictbounds=true&types=geocode&key=%s&sessiontoken=%s" % (url, self.key, session_token), From 975071f2d68e4847feae78b9dfe07e9895f2eaef Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Tue, 16 Mar 2021 06:15:19 +1100 Subject: [PATCH 206/260] docs: fix simple typo, preffix -> prefix (#389) There is a small typo in googlemaps/directions.py. Should read `prefix` rather than `preffix`. --- googlemaps/directions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/directions.py b/googlemaps/directions.py index b38a4cdf..353145cc 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -33,7 +33,7 @@ def directions(client, origin, destination, :param destination: The address or latitude/longitude value from which you wish to calculate directions. You can use a place_id as destination - by putting 'place_id:' as a preffix in the passing parameter. + by putting 'place_id:' as a prefix in the passing parameter. :type destination: string, dict, list, or tuple :param mode: Specifies the mode of transport to use when calculating From 923f8f3149d673c163a0b3a65b08b30ef7c9c827 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Mon, 15 Mar 2021 19:39:01 +0000 Subject: [PATCH 207/260] chore: update release to match pypa/gh-action-pypi-publish --- .github/scripts/install.sh | 1 + .github/workflows/release.yml | 4 +++- .releaserc | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/scripts/install.sh b/.github/scripts/install.sh index 91235742..a585594c 100755 --- a/.github/scripts/install.sh +++ b/.github/scripts/install.sh @@ -10,4 +10,5 @@ if ! python3 -m pip --version; then else sudo python3 -m pip install --upgrade setuptools python3 -m pip install nox + python3 -m pip install --prefer-binary twine fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8e149580..f5704229 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,11 +19,13 @@ on: jobs: release: runs-on: ubuntu-latest + env: + PYTHONDONTWRITEBYTECODE: 1 steps: - name: Setup Python uses: "actions/setup-python@v1" with: - python-version: "3.6" + python-version: "3.9" - name: Checkout uses: actions/checkout@v2 with: diff --git a/.releaserc b/.releaserc index db70f6e2..15fa5beb 100644 --- a/.releaserc +++ b/.releaserc @@ -14,7 +14,7 @@ plugins: from: 'version=".*"' to: 'version="${nextRelease.version}"' - - "@semantic-release/exec" - - prepareCmd: "rm -rf dist && python3 setup.py sdist && python3 -m twine check dist/*" + - prepareCmd: "python3 setup.py sdist && python3 -m twine check dist/*" publishCmd: "python3 -m twine upload dist/*" - - "@semantic-release/git" - assets: From 760bbce63e4ea4ad0c300020bad3f53a4159f028 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 15 Mar 2021 19:47:20 +0000 Subject: [PATCH 208/260] chore(release): 4.4.5 [skip ci] ## [4.4.5](https://github.com/googlemaps/google-maps-services-python/compare/v4.4.4...v4.4.5) (2021-03-15) ### Bug Fixes * Add parameter 'origin' to places autocomplete ([#392](https://github.com/googlemaps/google-maps-services-python/issues/392)) ([b963ff9](https://github.com/googlemaps/google-maps-services-python/commit/b963ff945c2343d34fa9d3b3e6acec02f987ca95)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 2bf13650..937db7ee 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.4.4" +__version__ = "4.4.5" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index f7089df2..bda02c1f 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.4.4", + version="4.4.5", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From a9460c0138ea95d4eacfe4ed2dcd1528ad7c4b54 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 15:29:09 -0600 Subject: [PATCH 209/260] chore: Upgrade to GitHub-native Dependabot (#405) Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..491deae0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 From 68276459f1d9cb3317c0c6c81b5ef9d6f45cd6b8 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 14 Jul 2021 09:40:33 -0600 Subject: [PATCH 210/260] fix: allow channel without client_id (#411) --- googlemaps/client.py | 6 ++---- tests/test_client.py | 4 ---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 980509e2..7b84b7b4 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -130,13 +130,11 @@ def __init__(self, key=None, client_id=None, client_secret=None, raise ValueError("Invalid API key provided.") if channel: - if not client_id: - raise ValueError("The channel argument must be used with a " - "client ID") if not re.match("^[a-zA-Z0-9._-]*$", channel): raise ValueError("The channel argument must be an ASCII " "alphanumeric string. The period (.), underscore (_)" - "and hyphen (-) characters are allowed.") + "and hyphen (-) characters are allowed. If used without " + "client_id, it must be 0-999.") self.session = requests.Session() self.key = key diff --git a/tests/test_client.py b/tests/test_client.py index a18221fa..4f01e397 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -280,10 +280,6 @@ def __call__(self, req): self.assertEqual(2, len(responses.calls)) - def test_channel_without_client_id(self): - with self.assertRaises(ValueError): - client = googlemaps.Client(key="AIzaasdf", channel="mychannel") - def test_invalid_channel(self): # Cf. limitations here: # https://developers.google.com/maps/premium/reports From cca7a307774c35cfae24d0311e8211d3220f3fb4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 14 Jul 2021 15:41:47 +0000 Subject: [PATCH 211/260] chore(release): 4.4.6 [skip ci] ## [4.4.6](https://github.com/googlemaps/google-maps-services-python/compare/v4.4.5...v4.4.6) (2021-07-14) ### Bug Fixes * allow channel without client_id ([#411](https://github.com/googlemaps/google-maps-services-python/issues/411)) ([6827645](https://github.com/googlemaps/google-maps-services-python/commit/68276459f1d9cb3317c0c6c81b5ef9d6f45cd6b8)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 937db7ee..a0b81a69 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.4.5" +__version__ = "4.4.6" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index bda02c1f..4bdae609 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.4.5", + version="4.4.6", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From a8afb86892ed06857099aecf10ca63c2271ce569 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 14 Jul 2021 09:50:58 -0600 Subject: [PATCH 212/260] fix: run sdist after tag created --- .releaserc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.releaserc b/.releaserc index 15fa5beb..78e982ac 100644 --- a/.releaserc +++ b/.releaserc @@ -15,7 +15,7 @@ plugins: to: 'version="${nextRelease.version}"' - - "@semantic-release/exec" - prepareCmd: "python3 setup.py sdist && python3 -m twine check dist/*" - publishCmd: "python3 -m twine upload dist/*" + publishCmd: "python3 setup.py sdist && python3 -m twine upload dist/*" - - "@semantic-release/git" - assets: - "./googlemaps/__init__.py" From 8ad82645782a3d7417ca82b1a0cb31ab7930238b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 14 Jul 2021 15:52:17 +0000 Subject: [PATCH 213/260] chore(release): 4.4.7 [skip ci] ## [4.4.7](https://github.com/googlemaps/google-maps-services-python/compare/v4.4.6...v4.4.7) (2021-07-14) ### Bug Fixes * run sdist after tag created ([a8afb86](https://github.com/googlemaps/google-maps-services-python/commit/a8afb86892ed06857099aecf10ca63c2271ce569)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index a0b81a69..6649d28a 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.4.6" +__version__ = "4.4.7" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 4bdae609..99273db0 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.4.6", + version="4.4.7", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 891321c3209e13130b760661d23b2e5cd741e39f Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 14 Jul 2021 10:55:13 -0600 Subject: [PATCH 214/260] chore: delete unnecessary file --- .deepsource.toml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .deepsource.toml diff --git a/.deepsource.toml b/.deepsource.toml deleted file mode 100644 index f772ce8c..00000000 --- a/.deepsource.toml +++ /dev/null @@ -1,10 +0,0 @@ -version = 1 - -test_patterns = ["tests/**"] - -[[analyzers]] -name = "python" -enabled = true - - [analyzers.meta] - runtime_version = "3.x.x" From a93b3c0fe1ec4680fbb34fc29ac8714b9a8c7da4 Mon Sep 17 00:00:00 2001 From: northtree Date: Thu, 29 Jul 2021 23:42:53 +1000 Subject: [PATCH 215/260] feat: allow passings requests session (#414) --- googlemaps/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 7b84b7b4..1334571d 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -54,6 +54,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, retry_timeout=60, requests_kwargs=None, queries_per_second=50, channel=None, retry_over_query_limit=True, experience_id=None, + requests_session=None, base_url=_DEFAULT_BASE_URL): """ :param key: Maps API key. Required, unless "client_id" and @@ -116,6 +117,9 @@ def __init__(self, key=None, client_id=None, client_secret=None, implemented. See the official requests docs for more info: http://docs.python-requests.org/en/latest/api/#main-interface :type requests_kwargs: dict + + :param requests_session: Reused persistent session for flexibility. + :type requests_session: requests.Session :param base_url: The base URL for all requests. Defaults to the Maps API server. Should not have a trailing slash. @@ -136,7 +140,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, "and hyphen (-) characters are allowed. If used without " "client_id, it must be 0-999.") - self.session = requests.Session() + self.session = requests_session or requests.Session() self.key = key if timeout and (connect_timeout or read_timeout): From 0717028106b0b9ba7f2fc607f7b2cbabdf25dcd6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 29 Jul 2021 13:44:22 +0000 Subject: [PATCH 216/260] chore(release): 4.5.0 [skip ci] # [4.5.0](https://github.com/googlemaps/google-maps-services-python/compare/v4.4.7...v4.5.0) (2021-07-29) ### Features * allow passings requests session ([#414](https://github.com/googlemaps/google-maps-services-python/issues/414)) ([a93b3c0](https://github.com/googlemaps/google-maps-services-python/commit/a93b3c0fe1ec4680fbb34fc29ac8714b9a8c7da4)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 6649d28a..803bf5a9 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.4.7" +__version__ = "4.5.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 99273db0..c2a605ba 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.4.7", + version="4.5.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From c7c32368811ada02a96b05480cbdb595639c44fa Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 29 Jul 2021 08:16:33 -0600 Subject: [PATCH 217/260] fix: only run publishCmd --- .releaserc | 1 - 1 file changed, 1 deletion(-) diff --git a/.releaserc b/.releaserc index 78e982ac..469c01f0 100644 --- a/.releaserc +++ b/.releaserc @@ -14,7 +14,6 @@ plugins: from: 'version=".*"' to: 'version="${nextRelease.version}"' - - "@semantic-release/exec" - - prepareCmd: "python3 setup.py sdist && python3 -m twine check dist/*" publishCmd: "python3 setup.py sdist && python3 -m twine upload dist/*" - - "@semantic-release/git" - assets: From 3502f8afdee1ba14b9acba7d8693225ca05bb80f Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 29 Jul 2021 08:22:46 -0600 Subject: [PATCH 218/260] chore: fix formatting --- .releaserc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.releaserc b/.releaserc index 469c01f0..5f53e6f5 100644 --- a/.releaserc +++ b/.releaserc @@ -13,8 +13,7 @@ plugins: - "./setup.py" from: 'version=".*"' to: 'version="${nextRelease.version}"' - - - "@semantic-release/exec" - publishCmd: "python3 setup.py sdist && python3 -m twine upload dist/*" + - [ "@semantic-release/exec", { publishCmd: "python3 setup.py sdist && python3 -m twine upload dist/*" }] - - "@semantic-release/git" - assets: - "./googlemaps/__init__.py" From 5045dce3eaf793ff20ab5b33c78402b1c501f329 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 29 Jul 2021 14:24:08 +0000 Subject: [PATCH 219/260] chore(release): 4.5.1 [skip ci] ## [4.5.1](https://github.com/googlemaps/google-maps-services-python/compare/v4.5.0...v4.5.1) (2021-07-29) ### Bug Fixes * only run publishCmd ([c7c3236](https://github.com/googlemaps/google-maps-services-python/commit/c7c32368811ada02a96b05480cbdb595639c44fa)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 803bf5a9..593f4ba8 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.5.0" +__version__ = "4.5.1" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index c2a605ba..369cb0b5 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.5.0", + version="4.5.1", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From dc8e116992666cac83bea7d41aac864eeea1d882 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 29 Jul 2021 08:34:30 -0600 Subject: [PATCH 220/260] fix: cleanup old dist --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f5704229..869f8d65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,6 +34,8 @@ jobs: run: ./.github/scripts/install.sh - name: Run distribution run: python3 -m nox -e distribution + - name: Cleanup old dist + run: rm -rf googlemaps-* - name: Semantic Release uses: cycjimmy/semantic-release-action@v2 with: From 5420d7d1f7bc1945e50e62d6c8a910657249a34d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 29 Jul 2021 14:35:53 +0000 Subject: [PATCH 221/260] chore(release): 4.5.2 [skip ci] ## [4.5.2](https://github.com/googlemaps/google-maps-services-python/compare/v4.5.1...v4.5.2) (2021-07-29) ### Bug Fixes * cleanup old dist ([dc8e116](https://github.com/googlemaps/google-maps-services-python/commit/dc8e116992666cac83bea7d41aac864eeea1d882)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 593f4ba8..b272c645 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.5.1" +__version__ = "4.5.2" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 369cb0b5..bff54133 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.5.1", + version="4.5.2", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 964cecd5ce2df29ac5cd91e62560cd0a69c7125d Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 29 Jul 2021 08:38:49 -0600 Subject: [PATCH 222/260] fix: cleanup dist --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 869f8d65..ce78e642 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: - name: Run distribution run: python3 -m nox -e distribution - name: Cleanup old dist - run: rm -rf googlemaps-* + run: rm -rf googlemaps-* dist/ - name: Semantic Release uses: cycjimmy/semantic-release-action@v2 with: From 00a87a0f623c00286f20e3f4c576a62a73284e5c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 29 Jul 2021 14:39:57 +0000 Subject: [PATCH 223/260] chore(release): 4.5.3 [skip ci] ## [4.5.3](https://github.com/googlemaps/google-maps-services-python/compare/v4.5.2...v4.5.3) (2021-07-29) ### Bug Fixes * cleanup dist ([964cecd](https://github.com/googlemaps/google-maps-services-python/commit/964cecd5ce2df29ac5cd91e62560cd0a69c7125d)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index b272c645..63665644 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.5.2" +__version__ = "4.5.3" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index bff54133..be29f9b1 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.5.2", + version="4.5.3", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 25e26092cb19e998764cae37474dd4c35ff42473 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Fri, 1 Oct 2021 11:15:52 -0600 Subject: [PATCH 224/260] chore: Created local 'SECURITY.md' from remote 'SECURITY.md' (#418) --- SECURITY.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..6d19135d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,10 @@ +# Report a security issue + +To report a security issue, please use https://g.co/vulnz. We use +https://g.co/vulnz for our intake, and do coordination and disclosure here on +GitHub (including using GitHub Security Advisory). The Google Security Team will +respond within 5 working days of your report on g.co/vulnz. + +To contact us about other bugs, please open an issue on GitHub. + +> **Note**: This file is synchronized from the https://github.com/googlemaps/.github repository. From db1292f267897d1bd5db34655f1745e61d1a96c6 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Wed, 2 Feb 2022 13:52:31 -0700 Subject: [PATCH 225/260] chore: update test matrix python versions (#428) --- .github/workflows/test.yml | 2 +- noxfile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 58839686..f8123e23 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.5", "3.6", "3.7", "3.8"] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - name: Checkout repository uses: actions/checkout@v2 diff --git a/noxfile.py b/noxfile.py index 06eefe58..d3357fe4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,6 +1,6 @@ import nox -SUPPORTED_PY_VERSIONS = ["3.5", "3.6", "3.7", "3.8"] +SUPPORTED_PY_VERSIONS = ["3.7", "3.8", "3.9", "3.10"] def _install_dev_packages(session): From 1364711c1557b44e72d1ea60d53a7f134f892de6 Mon Sep 17 00:00:00 2001 From: Andy Klimczak Date: Wed, 2 Feb 2022 16:15:53 -0500 Subject: [PATCH 226/260] feat: Geocode by place id (#427) Geocode endpoint accepts a `place_id` param as an alternative to geocode Google docs: https://developers.google.com/maps/documentation/geocoding/requests-places-geocoding --- googlemaps/geocoding.py | 9 ++++++++- tests/test_geocoding.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index b665d776..e409a49e 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -19,7 +19,7 @@ from googlemaps import convert -def geocode(client, address=None, components=None, bounds=None, region=None, +def geocode(client, address=None, place_id=None, components=None, bounds=None, region=None, language=None): """ Geocoding is the process of converting addresses @@ -30,6 +30,10 @@ def geocode(client, address=None, components=None, bounds=None, region=None, :param address: The address to geocode. :type address: string + :param place_id: A textual identifier that uniquely identifies a place, + returned from a Places search. + :type place_id: string + :param components: A component filter for which you wish to obtain a geocode, for example: ``{'administrative_area': 'TX','country': 'US'}`` :type components: dict @@ -53,6 +57,9 @@ def geocode(client, address=None, components=None, bounds=None, region=None, if address: params["address"] = address + if place_id: + params["place_id"] = place_id + if components: params["components"] = convert.components(components) diff --git a/tests/test_geocoding.py b/tests/test_geocoding.py index dfd9376d..813241f6 100644 --- a/tests/test_geocoding.py +++ b/tests/test_geocoding.py @@ -201,6 +201,25 @@ def test_geocode_with_just_components(self): responses.calls[0].request.url, ) + @responses.activate + def test_geocode_place_id(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[]}', + status=200, + content_type="application/json", + ) + + results = self.client.geocode(place_id="ChIJeRpOeF67j4AR9ydy_PIzPuM") + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "key=%s&place_id=ChIJeRpOeF67j4AR9ydy_PIzPuM" % self.key, + responses.calls[0].request.url, + ) + @responses.activate def test_simple_reverse_geocode(self): responses.add( From 4dd8db6b53049869cf98f2fed3ba8e56676d1709 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 2 Feb 2022 21:17:04 +0000 Subject: [PATCH 227/260] chore(release): 4.6.0 [skip ci] # [4.6.0](https://github.com/googlemaps/google-maps-services-python/compare/v4.5.3...v4.6.0) (2022-02-02) ### Features * Geocode by place id ([#427](https://github.com/googlemaps/google-maps-services-python/issues/427)) ([1364711](https://github.com/googlemaps/google-maps-services-python/commit/1364711c1557b44e72d1ea60d53a7f134f892de6)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 63665644..ffde203b 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.5.3" +__version__ = "4.6.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index be29f9b1..8826822d 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="googlemaps", - version="4.5.3", + version="4.6.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 2723079fce5a677ae068568018900c391b1a1f69 Mon Sep 17 00:00:00 2001 From: googlemaps-bot Date: Mon, 9 May 2022 14:54:46 -0600 Subject: [PATCH 228/260] chore: Synced file(s) with googlemaps/.github (#432) * chore: Created local '.github/CODEOWNERS' from remote '.github/CODEOWNERS' * chore: Created local '.github/sync-repo-settings.yaml' from remote '.github/sync-repo-settings.yaml' --- .github/CODEOWNERS | 17 +++++++++++++++ .github/sync-repo-settings.yaml | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/sync-repo-settings.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..e95e611e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,17 @@ +# Copyright 2022 Google LLC +# +# 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. + +# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners + +.github/ @googlemaps/admin diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml new file mode 100644 index 00000000..84693ce8 --- /dev/null +++ b/.github/sync-repo-settings.yaml @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# 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. + +# https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings + +rebaseMergeAllowed: true +squashMergeAllowed: true +mergeCommitAllowed: false +deleteBranchOnMerge: true +branchProtectionRules: +- pattern: main + isAdminEnforced: false + requiresStrictStatusChecks: false + requiredStatusCheckContexts: + - 'cla/google' + requiredApprovingReviewCount: 1 + requiresCodeOwnerReviews: true +- pattern: master + isAdminEnforced: false + requiresStrictStatusChecks: false + requiredStatusCheckContexts: + - 'cla/google' + requiredApprovingReviewCount: 1 + requiresCodeOwnerReviews: true +permissionRules: + - team: admin + permission: admin From 628ab311f8416a2b49a4acbccb8d27747bf0a4a5 Mon Sep 17 00:00:00 2001 From: googlemaps-bot Date: Mon, 9 May 2022 15:36:13 -0600 Subject: [PATCH 229/260] chore: Synced local '.github/sync-repo-settings.yaml' with remote '.github/sync-repo-settings.yaml' (#433) --- .github/sync-repo-settings.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 84693ce8..98d0b463 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -24,6 +24,7 @@ branchProtectionRules: requiresStrictStatusChecks: false requiredStatusCheckContexts: - 'cla/google' + - 'test' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true - pattern: master @@ -31,6 +32,7 @@ branchProtectionRules: requiresStrictStatusChecks: false requiredStatusCheckContexts: - 'cla/google' + - 'test' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true permissionRules: From e710ba7b5aeb9156b5112dbd55bd71903128f8c4 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Tue, 10 May 2022 12:15:23 -0600 Subject: [PATCH 230/260] build: update required checks (#435) --- .github/sync-repo-settings.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 98d0b463..746f8e52 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -25,6 +25,8 @@ branchProtectionRules: requiredStatusCheckContexts: - 'cla/google' - 'test' + - 'snippet-bot-check' + - 'header-check' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true - pattern: master @@ -33,6 +35,8 @@ branchProtectionRules: requiredStatusCheckContexts: - 'cla/google' - 'test' + - 'snippet-bot-check' + - 'header-check' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true permissionRules: From c67d453305fb103aa6ccc3639cf1733854ea480b Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Tue, 10 May 2022 12:47:51 -0600 Subject: [PATCH 231/260] chore: fix typo in check name (#436) --- .github/sync-repo-settings.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 746f8e52..a7b2d39c 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -25,7 +25,7 @@ branchProtectionRules: requiredStatusCheckContexts: - 'cla/google' - 'test' - - 'snippet-bot-check' + - 'snippet-bot check' - 'header-check' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true @@ -35,7 +35,7 @@ branchProtectionRules: requiredStatusCheckContexts: - 'cla/google' - 'test' - - 'snippet-bot-check' + - 'snippet-bot check' - 'header-check' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true From 1d995b4420ab369d8c23fcdef8b55e284f80dde0 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Tue, 10 May 2022 14:06:01 -0600 Subject: [PATCH 232/260] build: update workflow and standardize check name --- .github/workflows/test.yml | 8 +++++++- README.md | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8123e23..ddcc2e6c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ on: branches: ['*'] jobs: - test: + matrix: name: "Run tests on Python ${{ matrix.python-version }}" runs-on: ubuntu-latest strategy: @@ -46,3 +46,9 @@ jobs: run: | python3 -m nox --session "tests-${{ matrix.python-version }}" python3 -m nox -e distribution + test: + name: Wait for matrix to finish + needs: [matrix] + runs-on: ubuntu-latest + steps: + - run: echo "Test matrix finished" diff --git a/README.md b/README.md index 31840082..21564486 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ Python Client for Google Maps Services ==================================== -[![Build Status](https://travis-ci.org/googlemaps/google-maps-services-python.svg?branch=master)](https://travis-ci.org/googlemaps/google-maps-services-python) +![Test](https://github.com/googlemaps/google-maps-services-js/workflows/test/badge.svg) +![Release](https://github.com/googlemaps/google-maps-services-js/workflows/release/badge.svg) [![codecov](https://codecov.io/gh/googlemaps/google-maps-services-python/branch/master/graph/badge.svg)](https://codecov.io/gh/googlemaps/google-maps-services-python) [![PyPI version](https://badge.fury.io/py/googlemaps.svg)](https://badge.fury.io/py/googlemaps) ![PyPI - Downloads](https://img.shields.io/pypi/dd/googlemaps) From f59f66b81326f8f2b9aeadd41df8bba4ac58ff9b Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Tue, 10 May 2022 14:34:42 -0600 Subject: [PATCH 233/260] docs: fix badges --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 21564486..54c024b3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ Python Client for Google Maps Services ==================================== -![Test](https://github.com/googlemaps/google-maps-services-js/workflows/test/badge.svg) -![Release](https://github.com/googlemaps/google-maps-services-js/workflows/release/badge.svg) +![Test](https://github.com/googlemaps/google-maps-services-js/workflows/Test/badge.svg) +![Release](https://github.com/googlemaps/google-maps-services-js/workflows/Release/badge.svg) [![codecov](https://codecov.io/gh/googlemaps/google-maps-services-python/branch/master/graph/badge.svg)](https://codecov.io/gh/googlemaps/google-maps-services-python) [![PyPI version](https://badge.fury.io/py/googlemaps.svg)](https://badge.fury.io/py/googlemaps) ![PyPI - Downloads](https://img.shields.io/pypi/dd/googlemaps) From dfa5e7f2e5f5a0371f607b07538b6c084d8c5b42 Mon Sep 17 00:00:00 2001 From: googlemaps-bot Date: Tue, 10 May 2022 15:31:35 -0600 Subject: [PATCH 234/260] chore: Created local '.github/workflows/dependabot.yml' from remote '.github/workflows/dependabot.yml' (#434) --- .github/workflows/dependabot.yml | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/dependabot.yml diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 00000000..597e7636 --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,36 @@ +# Copyright 2022 Google LLC +# +# 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. + +name: Dependabot +on: pull_request + +permissions: + contents: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}} + steps: + - name: approve + run: gh pr review --comment -b "Automatically approved since dependabot is [configured](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#labels) with label `automatic`." + if: ${{ github.event.label.name == 'automatic' }} + - name: approve-instructions + run: echo "configure dependabot with label 'automatic' to have it automatically approved and merged. https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#labels" + if: ${{ github.event.label.name != 'automatic' }} + - name: merge + run: gh pr merge --auto --squash --delete-branch "$PR_URL" From 381563b0ea97d28beb1a0a54299549536fe6ce9c Mon Sep 17 00:00:00 2001 From: googlemaps-bot Date: Wed, 11 May 2022 15:35:06 -0600 Subject: [PATCH 235/260] chore: Synced local '.github/workflows/dependabot.yml' with remote '.github/workflows/dependabot.yml' (#437) --- .github/workflows/dependabot.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index 597e7636..12013710 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -27,10 +27,10 @@ jobs: GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}} steps: - name: approve - run: gh pr review --comment -b "Automatically approved since dependabot is [configured](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#labels) with label `automatic`." - if: ${{ github.event.label.name == 'automatic' }} + run: gh pr review --comment -b "Automatically approved since dependabot is not [configured](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#labels) with label `do not merge`." + if: ${{ github.event.label.name != 'do not merge' }} - name: approve-instructions - run: echo "configure dependabot with label 'automatic' to have it automatically approved and merged. https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#labels" - if: ${{ github.event.label.name != 'automatic' }} + run: echo "Configure dependabot with label 'do not merge' to prevent it from being automatically approved and merged. https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#labels" + if: ${{ github.event.label.name == 'do not merge' }} - name: merge run: gh pr merge --auto --squash --delete-branch "$PR_URL" From 8d334a87946442e6d786f9eb0a80a5840eb3cd72 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 12 May 2022 09:49:55 -0600 Subject: [PATCH 236/260] chore: simplify dependabot workflow --- .github/workflows/dependabot.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index 12013710..c4c66544 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -28,9 +28,5 @@ jobs: steps: - name: approve run: gh pr review --comment -b "Automatically approved since dependabot is not [configured](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#labels) with label `do not merge`." - if: ${{ github.event.label.name != 'do not merge' }} - - name: approve-instructions - run: echo "Configure dependabot with label 'do not merge' to prevent it from being automatically approved and merged. https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#labels" - if: ${{ github.event.label.name == 'do not merge' }} - name: merge run: gh pr merge --auto --squash --delete-branch "$PR_URL" From fd2933fc8c23e9caa3bb4a5bc74ae0692babde05 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 12 May 2022 09:52:52 -0600 Subject: [PATCH 237/260] chore: update pull request approval comment --- .github/workflows/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index c4c66544..b9853abf 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -27,6 +27,6 @@ jobs: GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}} steps: - name: approve - run: gh pr review --comment -b "Automatically approved since dependabot is not [configured](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#labels) with label `do not merge`." + run: gh pr review --comment -b "Automatically approved dependabot pull request." - name: merge run: gh pr merge --auto --squash --delete-branch "$PR_URL" From 407e81be1ded5026752c1412f0c8330ca0caa6bd Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 12 May 2022 09:59:57 -0600 Subject: [PATCH 238/260] chore: fix approval by providing url to pr --- .github/workflows/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index b9853abf..aacd8097 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -27,6 +27,6 @@ jobs: GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}} steps: - name: approve - run: gh pr review --comment -b "Automatically approved dependabot pull request." + run: gh pr review --comment -b "Automatically approved dependabot pull request." "$PR_URL" - name: merge run: gh pr merge --auto --squash --delete-branch "$PR_URL" From 7548bb2487d17e93149c40015c9e8b76576180e3 Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 12 May 2022 10:09:50 -0600 Subject: [PATCH 239/260] chore: add --approve --- .github/workflows/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index aacd8097..904a1990 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -27,6 +27,6 @@ jobs: GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}} steps: - name: approve - run: gh pr review --comment -b "Automatically approved dependabot pull request." "$PR_URL" + run: gh pr review --approve --comment -b "Automatically approved dependabot pull request." "$PR_URL" - name: merge run: gh pr merge --auto --squash --delete-branch "$PR_URL" From 62e1a306059296b7fb9034b709253e2ed6c6ca9c Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 12 May 2022 10:21:04 -0600 Subject: [PATCH 240/260] chore: only approve --- .github/workflows/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index 904a1990..6b46ebbb 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -27,6 +27,6 @@ jobs: GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}} steps: - name: approve - run: gh pr review --approve --comment -b "Automatically approved dependabot pull request." "$PR_URL" + run: gh pr review --approve "$PR_URL" - name: merge run: gh pr merge --auto --squash --delete-branch "$PR_URL" From 8f7209439db457c38da2ea1b6e9115cb727ee39d Mon Sep 17 00:00:00 2001 From: Justin Poehnelt Date: Thu, 19 May 2022 13:24:17 -0600 Subject: [PATCH 241/260] build: change dependabot interval to weekly (#439) --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 491deae0..55a9ccdd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,5 +3,5 @@ updates: - package-ecosystem: pip directory: "/" schedule: - interval: daily + interval: "weekly" open-pull-requests-limit: 10 From 3bcb05f56ec0745d9ecaac677c31963cac8ece18 Mon Sep 17 00:00:00 2001 From: anglarett Date: Wed, 9 Nov 2022 17:19:51 +0100 Subject: [PATCH 242/260] feat: Adds support for Address Validation API (#448) --- .github/dependabot.yml | 14 + .github/scripts/distribution.sh | 14 + .github/scripts/install.sh | 14 + .github/stale.yml | 14 + .gitignore | 2 + README.md | 7 + coverage.xml | 849 ++++++++++++++++++++++++++++++++ docs/conf.py | 14 + googlemaps/addressvalidation.py | 80 +++ googlemaps/client.py | 36 +- noxfile.py | 14 + setup.py | 14 + tests/test_addressvalidation.py | 48 ++ text.py | 19 + 14 files changed, 1133 insertions(+), 6 deletions(-) create mode 100644 coverage.xml create mode 100644 googlemaps/addressvalidation.py create mode 100644 tests/test_addressvalidation.py create mode 100644 text.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 55a9ccdd..36f9436b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,17 @@ +# Copyright 2022 Google LLC +# +# 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. + version: 2 updates: - package-ecosystem: pip diff --git a/.github/scripts/distribution.sh b/.github/scripts/distribution.sh index f7c08690..e779cd1d 100755 --- a/.github/scripts/distribution.sh +++ b/.github/scripts/distribution.sh @@ -1,4 +1,18 @@ #!/bin/bash +# Copyright 2022 Google LLC +# +# 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. + rm -rf dist diff --git a/.github/scripts/install.sh b/.github/scripts/install.sh index a585594c..39e7f9f0 100755 --- a/.github/scripts/install.sh +++ b/.github/scripts/install.sh @@ -1,4 +1,18 @@ #!/bin/bash +# Copyright 2022 Google LLC +# +# 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. + set -exo pipefail diff --git a/.github/stale.yml b/.github/stale.yml index 8ed0e080..1d39e65d 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,3 +1,17 @@ +# Copyright 2022 Google LLC +# +# 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. + # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale diff --git a/.gitignore b/.gitignore index c4477ba5..74d89080 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ googlemaps.egg-info *.egg .vscode/ .idea/ +index.py +test.py diff --git a/README.md b/README.md index 54c024b3..cb5f7821 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ APIs: - Roads API - Places API - Maps Static API + - Address Validation API Keep in mind that the same [terms and conditions](https://developers.google.com/maps/terms) apply to usage of the APIs when they're accessed through this library. @@ -85,6 +86,12 @@ directions_result = gmaps.directions("Sydney Town Hall", "Parramatta, NSW", mode="transit", departure_time=now) + +# Validate an address with address validation +addressvalidation_result = gmaps.addressvalidation(['1600 Amphitheatre Pk'], + regionCode='US', + locality='Mountain View', + enableUspsCass=True) ``` For more usage examples, check out [the tests](https://github.com/googlemaps/google-maps-services-python/tree/master/tests). diff --git a/coverage.xml b/coverage.xml new file mode 100644 index 00000000..1c38ca3d --- /dev/null +++ b/coverage.xml @@ -0,0 +1,849 @@ + + + + + + /Users/anglarett/Public/Drop Box/dev-se-git/google-maps-services-python + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/conf.py b/docs/conf.py index 19cdfef5..0d8314fb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,17 @@ +# Copyright 2022 Google LLC +# +# 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. + # -*- coding: utf-8 -*- # # Maps API documentation build configuration file, created by diff --git a/googlemaps/addressvalidation.py b/googlemaps/addressvalidation.py new file mode 100644 index 00000000..45b74655 --- /dev/null +++ b/googlemaps/addressvalidation.py @@ -0,0 +1,80 @@ +# +# Copyright 2014 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Performs requests to the Google Maps Address Validation API.""" +from googlemaps import exceptions + + +_ADDRESSVALIDATION_BASE_URL = "https://addressvalidation.googleapis.com" + + +def _addressvalidation_extract(response): + """ + Mimics the exception handling logic in ``client._get_body``, but + for addressvalidation which uses a different response format. + """ + body = response.json() + return body + + # if response.status_code in (200, 404): + # return body + + # try: + # error = body["error"]["errors"][0]["reason"] + # except KeyError: + # error = None + + # if response.status_code == 403: + # raise exceptions._OverQueryLimit(response.status_code, error) + # else: + # raise exceptions.ApiError(response.status_code, error) + + +def addressvalidation(client, addressLines, regionCode=None , locality=None, enableUspsCass=None): + """ + The Google Maps Address Validation API returns a verification of an address + See https://developers.google.com/maps/documentation/address-validation/overview + request must include parameters below. + :param addressLines: The address to validate + :type addressLines: array + :param regionCode: (optional) The country code + :type regionCode: string + :param locality: (optional) Restrict to a locality, ie:Mountain View + :type locality: string + :param enableUspsCass For the "US" and "PR" regions only, you can optionally enable the Coding Accuracy Support System (CASS) from the United States Postal Service (USPS) + :type locality: boolean + """ + + params = { + "address":{ + "addressLines": addressLines + } + } + + if regionCode is not None: + params["address"]["regionCode"] = regionCode + + if locality is not None: + params["address"]["locality"] = locality + + if enableUspsCass is not False or enableUspsCass is not None: + params["enableUspsCass"] = enableUspsCass + + return client._request("/v1:validateAddress", {}, # No GET params + base_url=_ADDRESSVALIDATION_BASE_URL, + extract_body=_addressvalidation_extract, + post_json=params) \ No newline at end of file diff --git a/googlemaps/client.py b/googlemaps/client.py index 1334571d..54838fa0 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -31,6 +31,8 @@ import requests import random import time +import math +import sys import googlemaps @@ -52,7 +54,7 @@ class Client: 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, + queries_per_second=60, queries_per_minute=6000,channel=None, retry_over_query_limit=True, experience_id=None, requests_session=None, base_url=_DEFAULT_BASE_URL): @@ -93,11 +95,16 @@ def __init__(self, key=None, client_id=None, client_secret=None, seconds. :type retry_timeout: int - :param queries_per_second: Number of queries per second permitted. + :param queries_per_second: Number of queries per second permitted. Unset queries_per_minute to None. If set smaller number will be used. If the rate limit is reached, the client will sleep for the appropriate amount of time before it runs the current query. :type queries_per_second: int + :param queries_per_minute: Number of queries per minute permitted. Unset queries_per_second to None. If set smaller number will be used. + If the rate limit is reached, the client will sleep for the + appropriate amount of time before it runs the current query. + :type queries_per_minute: int + :param retry_over_query_limit: If True, requests that result in a response indicating the query rate limit was exceeded will be retried. Defaults to True. @@ -169,10 +176,26 @@ def __init__(self, key=None, client_id=None, client_secret=None, "timeout": self.timeout, "verify": True, # NOTE(cbro): verify SSL certs. }) - + + self.queries_quota : int self.queries_per_second = queries_per_second + self.queries_per_minute = queries_per_minute + try: + if (type(self.queries_per_second) == int and type(self.queries_per_minute) == int ): + self.queries_quota = math.floor(min(self.queries_per_second, self.queries_per_minute/60)) + elif (self.queries_per_second and type(self.queries_per_second) == int ): + self.queries_quota = math.floor(self.queries_per_second) + elif (self.queries_per_minute and type(self.queries_per_minute) == int ): + self.queries_quota = math.floor(self.queries_per_minute/60) + else: + sys.exit("MISSING VALID NUMBER for queries_per_second or queries_per_minute") + print("\n","API queries_quota:", self.queries_quota,"\n") + + except NameError: + sys.exit("MISSING VALUE for queries_per_second or queries_per_minute") + self.retry_over_query_limit = retry_over_query_limit - self.sent_times = collections.deque("", queries_per_second) + self.sent_times = collections.deque("", self.queries_quota) self.set_experience_id(experience_id) self.base_url = base_url @@ -303,7 +326,7 @@ def _request(self, url, params, first_request_time=None, retry_counter=0, # Check if the time of the nth previous query (where n is # queries_per_second) is under a second ago - if so, sleep for # the difference. - if self.sent_times and len(self.sent_times) == self.queries_per_second: + if self.sent_times and len(self.sent_times) == self.queries_quota: elapsed_since_earliest = time.time() - self.sent_times[0] if elapsed_since_earliest < 1: time.sleep(1 - elapsed_since_earliest) @@ -402,7 +425,7 @@ def _generate_auth_url(self, path, params, accepts_clientid): from googlemaps.places import places_autocomplete from googlemaps.places import places_autocomplete_query from googlemaps.maps import static_map - +from googlemaps.addressvalidation import addressvalidation def make_api_method(func): """ @@ -446,6 +469,7 @@ def wrapper(*args, **kwargs): Client.places_autocomplete = make_api_method(places_autocomplete) Client.places_autocomplete_query = make_api_method(places_autocomplete_query) Client.static_map = make_api_method(static_map) +Client.addressvalidation = make_api_method(addressvalidation) def sign_hmac(secret, payload): diff --git a/noxfile.py b/noxfile.py index d3357fe4..2bc3b2a1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,3 +1,17 @@ +# Copyright 2022 Google LLC +# +# 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. + import nox SUPPORTED_PY_VERSIONS = ["3.7", "3.8", "3.9", "3.10"] diff --git a/setup.py b/setup.py index 8826822d..b2536fc3 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,17 @@ +# Copyright 2022 Google LLC +# +# 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. + from setuptools import setup diff --git a/tests/test_addressvalidation.py b/tests/test_addressvalidation.py new file mode 100644 index 00000000..69ad8b70 --- /dev/null +++ b/tests/test_addressvalidation.py @@ -0,0 +1,48 @@ +# This Python file uses the following encoding: utf-8 +# +# Copyright 2017 Google Inc. All rights reserved. +# +# +# 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. +# + +"""Tests for the addressvalidation module.""" + +import responses + +import googlemaps +from . import TestCase + + +class AddressValidationTest(TestCase): + def setUp(self): + self.key = "AIzaasdf" + self.client = googlemaps.Client(self.key) + + @responses.activate + def test_simple_addressvalidation(self): + responses.add( + responses.POST, + "https://addressvalidation.googleapis.com/v1:validateAddress", + body='{"address": {"regionCode": "US","locality": "Mountain View","addressLines": "1600 Amphitheatre Pkwy"},"enableUspsCass":true}', + status=200, + content_type="application/json", + ) + + results = self.client.addressvalidation('1600 Amphitheatre Pk', regionCode='US', locality='Mountain View', enableUspsCass=True) + + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://addressvalidation.googleapis.com/v1:validateAddress?" "key=%s" % self.key, + responses.calls[0].request.url, + ) \ No newline at end of file diff --git a/text.py b/text.py new file mode 100644 index 00000000..13734488 --- /dev/null +++ b/text.py @@ -0,0 +1,19 @@ +import math + +queries_quota : int +queries_per_second = 60 # None or 60 +queries_per_minute = None # None or 6000 + +try: + if (type(queries_per_second) == int and type(queries_per_minute) == int ): + queries_quota = math.floor(min(queries_per_second, queries_per_minute/60)) + elif (queries_per_second): + queries_quota = math.floor(queries_per_second) + elif (queries_per_minute): + queries_quota = math.floor(queries_per_minute/60) + else: + print("MISSING VALID NUMBER for queries_per_second or queries_per_minute") + print(queries_quota) + +except NameError: + print("MISSING VALUE for queries_per_second or queries_per_minute") \ No newline at end of file From 6d7a993d49c090c3e06fd1e3bf3019d184279b47 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 9 Nov 2022 16:20:57 +0000 Subject: [PATCH 243/260] chore(release): 4.7.0 [skip ci] # [4.7.0](https://github.com/googlemaps/google-maps-services-python/compare/v4.6.0...v4.7.0) (2022-11-09) ### Features * Adds support for Address Validation API ([#448](https://github.com/googlemaps/google-maps-services-python/issues/448)) ([3bcb05f](https://github.com/googlemaps/google-maps-services-python/commit/3bcb05f56ec0745d9ecaac677c31963cac8ece18)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index ffde203b..4a3b3b91 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.6.0" +__version__ = "4.7.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index b2536fc3..51ccabf8 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( name="googlemaps", - version="4.6.0", + version="4.7.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 83ad82772fcfba2ebe798ae544d7f01df1f6ded7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Mon, 21 Nov 2022 22:59:47 +0100 Subject: [PATCH 244/260] fix: Convert print statement to info log (#455) --- googlemaps/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 54838fa0..6684ed1e 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -22,6 +22,7 @@ import base64 import collections +import logging from datetime import datetime from datetime import timedelta import functools @@ -41,6 +42,8 @@ except ImportError: # Python 2 from urllib import urlencode +logger = logging.getLogger(__name__) + _X_GOOG_MAPS_EXPERIENCE_ID = "X-Goog-Maps-Experience-ID" _USER_AGENT = "GoogleGeoApiClientPython/%s" % googlemaps.__version__ _DEFAULT_BASE_URL = "https://maps.googleapis.com" @@ -189,7 +192,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.queries_quota = math.floor(self.queries_per_minute/60) else: sys.exit("MISSING VALID NUMBER for queries_per_second or queries_per_minute") - print("\n","API queries_quota:", self.queries_quota,"\n") + logger.info("API queries_quota:", self.queries_quota) except NameError: sys.exit("MISSING VALUE for queries_per_second or queries_per_minute") From 8fde9501b4df675a1ae9f763c29344b407aacf6f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 21 Nov 2022 22:00:51 +0000 Subject: [PATCH 245/260] chore(release): 4.7.1 [skip ci] ## [4.7.1](https://github.com/googlemaps/google-maps-services-python/compare/v4.7.0...v4.7.1) (2022-11-21) ### Bug Fixes * Convert print statement to info log ([#455](https://github.com/googlemaps/google-maps-services-python/issues/455)) ([83ad827](https://github.com/googlemaps/google-maps-services-python/commit/83ad82772fcfba2ebe798ae544d7f01df1f6ded7)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 4a3b3b91..226be765 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.7.0" +__version__ = "4.7.1" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 51ccabf8..053cd4d8 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( name="googlemaps", - version="4.7.0", + version="4.7.1", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 5b952d73f8374baac876b4d845fd46cebec6ed7e Mon Sep 17 00:00:00 2001 From: anglarett Date: Tue, 22 Nov 2022 00:09:25 +0100 Subject: [PATCH 246/260] fix: fixes broken support for python 3.5 (#453) * fix tests * updates after review --- .github/workflows/test.yml | 4 ++-- .gitignore | 1 - googlemaps/addressvalidation.py | 5 +++-- googlemaps/client.py | 1 - tests/test_addressvalidation.py | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ddcc2e6c..286ff75a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,10 +32,10 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup Python - uses: "actions/setup-python@v1" + uses: "actions/setup-python@v3" with: python-version: "${{ matrix.python-version }}" diff --git a/.gitignore b/.gitignore index 74d89080..93e7a2fc 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,3 @@ googlemaps.egg-info .vscode/ .idea/ index.py -test.py diff --git a/googlemaps/addressvalidation.py b/googlemaps/addressvalidation.py index 45b74655..149f3b48 100644 --- a/googlemaps/addressvalidation.py +++ b/googlemaps/addressvalidation.py @@ -1,5 +1,5 @@ # -# Copyright 2014 Google Inc. All rights reserved. +# Copyright 2022 Google Inc. All rights reserved. # # # Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -77,4 +77,5 @@ def addressvalidation(client, addressLines, regionCode=None , locality=None, ena return client._request("/v1:validateAddress", {}, # No GET params base_url=_ADDRESSVALIDATION_BASE_URL, extract_body=_addressvalidation_extract, - post_json=params) \ No newline at end of file + post_json=params) + \ No newline at end of file diff --git a/googlemaps/client.py b/googlemaps/client.py index 6684ed1e..301d6cab 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -180,7 +180,6 @@ def __init__(self, key=None, client_id=None, client_secret=None, "verify": True, # NOTE(cbro): verify SSL certs. }) - self.queries_quota : int self.queries_per_second = queries_per_second self.queries_per_minute = queries_per_minute try: diff --git a/tests/test_addressvalidation.py b/tests/test_addressvalidation.py index 69ad8b70..d1f2f589 100644 --- a/tests/test_addressvalidation.py +++ b/tests/test_addressvalidation.py @@ -26,7 +26,7 @@ class AddressValidationTest(TestCase): def setUp(self): - self.key = "AIzaasdf" + self.key = "AIzaSyD_sJl0qMA65CYHMBokVfMNA7AKyt5ERYs" self.client = googlemaps.Client(self.key) @responses.activate From c5982e7eadd8ded7bfcef862fa6182516125d19b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 21 Nov 2022 23:10:43 +0000 Subject: [PATCH 247/260] chore(release): 4.7.2 [skip ci] ## [4.7.2](https://github.com/googlemaps/google-maps-services-python/compare/v4.7.1...v4.7.2) (2022-11-21) ### Bug Fixes * fixes broken support for python 3.5 ([#453](https://github.com/googlemaps/google-maps-services-python/issues/453)) ([5b952d7](https://github.com/googlemaps/google-maps-services-python/commit/5b952d73f8374baac876b4d845fd46cebec6ed7e)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 226be765..ca3b7518 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.7.1" +__version__ = "4.7.2" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 053cd4d8..89c10a0f 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( name="googlemaps", - version="4.7.1", + version="4.7.2", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From dcebf77cb8dc4e45a2bd8e95f8851d86811322a7 Mon Sep 17 00:00:00 2001 From: sandre35 <65041823+sandre35@users.noreply.github.com> Date: Tue, 22 Nov 2022 20:29:15 +0100 Subject: [PATCH 248/260] fix: correct lazy attribute inside logger (#457) --- googlemaps/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/client.py b/googlemaps/client.py index 301d6cab..d1f4ab6a 100644 --- a/googlemaps/client.py +++ b/googlemaps/client.py @@ -191,7 +191,7 @@ def __init__(self, key=None, client_id=None, client_secret=None, self.queries_quota = math.floor(self.queries_per_minute/60) else: sys.exit("MISSING VALID NUMBER for queries_per_second or queries_per_minute") - logger.info("API queries_quota:", self.queries_quota) + logger.info("API queries_quota: %s", self.queries_quota) except NameError: sys.exit("MISSING VALUE for queries_per_second or queries_per_minute") From ca042f50ad8c2a68ed99dd89dad12bfe9b31cdf3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 22 Nov 2022 19:30:32 +0000 Subject: [PATCH 249/260] chore(release): 4.7.3 [skip ci] ## [4.7.3](https://github.com/googlemaps/google-maps-services-python/compare/v4.7.2...v4.7.3) (2022-11-22) ### Bug Fixes * correct lazy attribute inside logger ([#457](https://github.com/googlemaps/google-maps-services-python/issues/457)) ([dcebf77](https://github.com/googlemaps/google-maps-services-python/commit/dcebf77cb8dc4e45a2bd8e95f8851d86811322a7)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index ca3b7518..f25d9359 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.7.2" +__version__ = "4.7.3" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 89c10a0f..239eceb0 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( name="googlemaps", - version="4.7.2", + version="4.7.3", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 2c30f3a61d6632a6c7e7ec91718b97d6a1051379 Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Mon, 23 Jan 2023 15:09:05 -0800 Subject: [PATCH 250/260] docs: Adding docs.yml workflow to update docs on a new tag. (#360) * docs: Adding docs.yml workflow to update docs on a new tag. Once the docs have been generated, a new PR will be opened against the `gh-pages` branch. Resolves #348 Co-authored-by: Angela Yu <5506675+wangela@users.noreply.github.com> --- .github/workflows/docs.yml | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..c83d6c38 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,63 @@ +# Copyright 2023 Google LLC +# +# 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. + +# A workflow that pushes artifacts to Sonatype +name: Publish + +on: + push: + tags: + - '*' + repository_dispatch: + types: [docs] + +jobs: + docs: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Python + uses: "actions/setup-python@v3" + with: + python-version: "3.9" + + - name: Install dependencies + run: ./.github/scripts/install.sh + + - name: Generate docs + run: python3 -m nox --session docs + + - name: Update gh-pages branch with docs + run: | + echo "Creating tar for generated docs" + cd ./docs/_build/html && tar cvf ~/docs.tar . + + echo "Unpacking tar into gh-pages branch" + git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* + cd $GITHUB_WORKSPACE && git checkout gh-pages && tar xvf ~/docs.tar + + - name: PR Changes + uses: peter-evans/create-pull-request@v4 + with: + token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} + commit-message: 'docs: Update docs' + committer: googlemaps-bot + author: googlemaps-bot + title: 'docs: Update docs' + body: | + Updated GitHub pages with latest from `python3 -m nox --session docs`. + branch: googlemaps-bot/update_gh_pages From 88268363f4612fa97a525c149f7dce3e36f293ef Mon Sep 17 00:00:00 2001 From: Angela Yu <5506675+wangela@users.noreply.github.com> Date: Mon, 23 Jan 2023 23:34:06 -0800 Subject: [PATCH 251/260] chore: update release action dependencies (#473) * chore: update release action dependencies * chore: set semantic release version --- .github/workflows/release.yml | 7 ++++--- .github/workflows/test.yml | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce78e642..a0a28dce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,11 +23,11 @@ jobs: PYTHONDONTWRITEBYTECODE: 1 steps: - name: Setup Python - uses: "actions/setup-python@v1" + uses: "actions/setup-python@v3" with: python-version: "3.9" - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} - name: Install dependencies @@ -37,8 +37,9 @@ jobs: - name: Cleanup old dist run: rm -rf googlemaps-* dist/ - name: Semantic Release - uses: cycjimmy/semantic-release-action@v2 + uses: cycjimmy/semantic-release-action@v3 with: + semantic_version: 19 extra_plugins: | "@semantic-release/commit-analyzer" "@semantic-release/release-notes-generator" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 286ff75a..570a087a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -51,4 +51,6 @@ jobs: needs: [matrix] runs-on: ubuntu-latest steps: - - run: echo "Test matrix finished" + - run: | + echo "Test matrix finished"; + exit 0; From 9f09ccbaecef7c1244f07f8cccc39024ad5fddee Mon Sep 17 00:00:00 2001 From: Joe Loffredo <60896458+jloffredo2@users.noreply.github.com> Date: Tue, 24 Jan 2023 14:11:59 -0500 Subject: [PATCH 252/260] feat: add `editorial_summary` to Place Details atmosphere fields (#472) --- googlemaps/places.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 91c14dfe..ac3bf53e 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -80,7 +80,7 @@ PLACES_DETAIL_FIELDS_CONTACT = {"formatted_phone_number", "international_phone_number", "opening_hours", "website"} -PLACES_DETAIL_FIELDS_ATMOSPHERE = {"price_level", "rating", "review", "user_ratings_total"} +PLACES_DETAIL_FIELDS_ATMOSPHERE = {"editorial_summary","price_level", "rating", "review", "user_ratings_total"} PLACES_DETAIL_FIELDS = ( PLACES_DETAIL_FIELDS_BASIC From 65d5a263e683e3763a6e1c076d1c5778211fd745 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 24 Jan 2023 19:13:04 +0000 Subject: [PATCH 253/260] chore(release): 4.8.0 [skip ci] # [4.8.0](https://github.com/googlemaps/google-maps-services-python/compare/v4.7.3...v4.8.0) (2023-01-24) ### Features * add `editorial_summary` to Place Details atmosphere fields ([#472](https://github.com/googlemaps/google-maps-services-python/issues/472)) ([9f09ccb](https://github.com/googlemaps/google-maps-services-python/commit/9f09ccbaecef7c1244f07f8cccc39024ad5fddee)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index f25d9359..be61bb0e 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.7.3" +__version__ = "4.8.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 239eceb0..3958aed2 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( name="googlemaps", - version="4.7.3", + version="4.8.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 8afe62898f1733316fea705e8daa0313add7b93c Mon Sep 17 00:00:00 2001 From: nnolan <36713193+nnolan@users.noreply.github.com> Date: Tue, 24 Jan 2023 16:23:43 -0600 Subject: [PATCH 254/260] feat: add support for sorting reviews in Place Details requests (#468) --- googlemaps/places.py | 16 ++++++++++++++-- tests/test_places.py | 4 +++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index ac3bf53e..35b818f2 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -396,7 +396,14 @@ def _places( return client._request(url, params) -def place(client, place_id, session_token=None, fields=None, language=None): +def place( + client, + place_id, + session_token=None, + fields=None, + language=None, + reviews_sort="most_relevant" +): """ Comprehensive details for an individual place. @@ -416,6 +423,10 @@ def place(client, place_id, session_token=None, fields=None, language=None): :param language: The language in which to return results. :type language: string + :param reviews_sort: The sorting method to use when returning reviews. + Can be set to most_relevant (default) or newest. + :type reviews_sort: string + :rtype: result dict with the following keys: result: dict containing place details html_attributions: set of attributions which must be displayed @@ -444,6 +455,8 @@ def place(client, place_id, session_token=None, fields=None, language=None): params["language"] = language if session_token: params["sessiontoken"] = session_token + if reviews_sort: + params["reviews_sort"] = reviews_sort return client._request("/maps/api/place/details/json", params) @@ -657,4 +670,3 @@ def _autocomplete( url = "/maps/api/place/%sautocomplete/json" % url_part return client._request(url, params).get("predictions", []) - \ No newline at end of file diff --git a/tests/test_places.py b/tests/test_places.py index 50781773..dd29935d 100644 --- a/tests/test_places.py +++ b/tests/test_places.py @@ -36,6 +36,7 @@ def setUp(self): self.type = "liquor_store" self.language = "en-AU" self.region = "AU" + self.reviews_sort="newest" self.radius = 100 @responses.activate @@ -165,11 +166,12 @@ def test_place_detail(self): "ChIJN1t_tDeuEmsRUsoyG83frY4", fields=["business_status", "geometry/location", "place_id"], language=self.language, + reviews_sort=self.reviews_sort, ) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( - "%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4" + "%s?reviews_sort=newest&language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4" "&key=%s&fields=business_status,geometry/location,place_id" % (url, self.key), responses.calls[0].request.url, From 72c482c78d1cbb86eb9847e09e0976b16c0f49a6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 24 Jan 2023 22:24:47 +0000 Subject: [PATCH 255/260] chore(release): 4.9.0 [skip ci] # [4.9.0](https://github.com/googlemaps/google-maps-services-python/compare/v4.8.0...v4.9.0) (2023-01-24) ### Features * add support for sorting reviews in Place Details requests ([#468](https://github.com/googlemaps/google-maps-services-python/issues/468)) ([8afe628](https://github.com/googlemaps/google-maps-services-python/commit/8afe62898f1733316fea705e8daa0313add7b93c)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index be61bb0e..6aabb596 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.8.0" +__version__ = "4.9.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 3958aed2..634cfb42 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( name="googlemaps", - version="4.8.0", + version="4.9.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 6c69310997e2e82edb5ee3e7235a3057945e7c76 Mon Sep 17 00:00:00 2001 From: Angela Yu <5506675+wangela@users.noreply.github.com> Date: Thu, 26 Jan 2023 08:43:40 -0800 Subject: [PATCH 256/260] feat: add new place details fields and reviews request modifiers (#474) * feat: add new place details fields and support for reviews sorting and translation * feat: support reviews (plural) field --- googlemaps/places.py | 97 ++++++++++++++++++++++++++++++-------------- tests/test_places.py | 12 +++--- 2 files changed, 73 insertions(+), 36 deletions(-) diff --git a/googlemaps/places.py b/googlemaps/places.py index 35b818f2..269a17fa 100644 --- a/googlemaps/places.py +++ b/googlemaps/places.py @@ -52,35 +52,64 @@ ^ PLACES_FIND_FIELDS_ATMOSPHERE ) -PLACES_DETAIL_FIELDS_BASIC = {"address_component", - "adr_address", - "business_status", - "formatted_address", - "geometry", - "geometry/location", - "geometry/location/lat", - "geometry/location/lng", - "geometry/viewport", - "geometry/viewport/northeast", - "geometry/viewport/northeast/lat", - "geometry/viewport/northeast/lng", - "geometry/viewport/southwest", - "geometry/viewport/southwest/lat", - "geometry/viewport/southwest/lng", - "icon", - "name", - "permanently_closed", - "photo", - "place_id", - "plus_code", - "type", - "url", - "utc_offset", - "vicinity",} - -PLACES_DETAIL_FIELDS_CONTACT = {"formatted_phone_number", "international_phone_number", "opening_hours", "website"} - -PLACES_DETAIL_FIELDS_ATMOSPHERE = {"editorial_summary","price_level", "rating", "review", "user_ratings_total"} +PLACES_DETAIL_FIELDS_BASIC = { + "address_component", + "adr_address", + "business_status", + "formatted_address", + "geometry", + "geometry/location", + "geometry/location/lat", + "geometry/location/lng", + "geometry/viewport", + "geometry/viewport/northeast", + "geometry/viewport/northeast/lat", + "geometry/viewport/northeast/lng", + "geometry/viewport/southwest", + "geometry/viewport/southwest/lat", + "geometry/viewport/southwest/lng", + "icon", + "name", + "permanently_closed", + "photo", + "place_id", + "plus_code", + "type", + "url", + "utc_offset", + "vicinity", + "wheelchair_accessible_entrance" +} + +PLACES_DETAIL_FIELDS_CONTACT = { + "formatted_phone_number", + "international_phone_number", + "opening_hours", + "current_opening_hours", + "secondary_opening_hours", + "website", +} + +PLACES_DETAIL_FIELDS_ATMOSPHERE = { + "curbside_pickup", + "delivery", + "dine_in", + "editorial_summary", + "price_level", + "rating", + "reservable", + "review", # prefer "reviews" to match API documentation + "reviews", + "serves_beer", + "serves_breakfast", + "serves_brunch", + "serves_dinner", + "serves_lunch", + "serves_vegetarian_food", + "serves_wine", + "takeout", + "user_ratings_total" +} PLACES_DETAIL_FIELDS = ( PLACES_DETAIL_FIELDS_BASIC @@ -88,7 +117,7 @@ ^ PLACES_DETAIL_FIELDS_ATMOSPHERE ) -DEPRECATED_FIELDS = {"permanently_closed"} +DEPRECATED_FIELDS = {"permanently_closed", "review"} DEPRECATED_FIELDS_MESSAGE = ( "Fields, %s, are deprecated. " "Read more at https://developers.google.com/maps/deprecations." @@ -402,7 +431,8 @@ def place( session_token=None, fields=None, language=None, - reviews_sort="most_relevant" + reviews_no_translations=False, + reviews_sort="most_relevant", ): """ Comprehensive details for an individual place. @@ -423,6 +453,9 @@ def place( :param language: The language in which to return results. :type language: string + :param reviews_no_translations: Specify reviews_no_translations=True to disable translation of reviews; reviews_no_translations=False (default) enables translation of reviews. + :type reviews_no_translations: bool + :param reviews_sort: The sorting method to use when returning reviews. Can be set to most_relevant (default) or newest. :type reviews_sort: string @@ -455,6 +488,8 @@ def place( params["language"] = language if session_token: params["sessiontoken"] = session_token + if reviews_no_translations: + params["reviews_no_translations"] = "true" if reviews_sort: params["reviews_sort"] = reviews_sort diff --git a/tests/test_places.py b/tests/test_places.py index dd29935d..32f03d16 100644 --- a/tests/test_places.py +++ b/tests/test_places.py @@ -36,7 +36,6 @@ def setUp(self): self.type = "liquor_store" self.language = "en-AU" self.region = "AU" - self.reviews_sort="newest" self.radius = 100 @responses.activate @@ -164,15 +163,18 @@ def test_place_detail(self): self.client.place( "ChIJN1t_tDeuEmsRUsoyG83frY4", - fields=["business_status", "geometry/location", "place_id"], + fields=["business_status", "geometry/location", + "place_id", "reviews"], language=self.language, - reviews_sort=self.reviews_sort, + reviews_no_translations=True, + reviews_sort="newest", ) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( - "%s?reviews_sort=newest&language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4" - "&key=%s&fields=business_status,geometry/location,place_id" + "%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4" + "&reviews_no_translations=true&reviews_sort=newest" + "&key=%s&fields=business_status,geometry/location,place_id,reviews" % (url, self.key), responses.calls[0].request.url, ) From 82674b7fccb00d611f36b11cab6f281827174f8b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 26 Jan 2023 16:44:56 +0000 Subject: [PATCH 257/260] chore(release): 4.10.0 [skip ci] # [4.10.0](https://github.com/googlemaps/google-maps-services-python/compare/v4.9.0...v4.10.0) (2023-01-26) ### Features * add new place details fields and reviews request modifiers ([#474](https://github.com/googlemaps/google-maps-services-python/issues/474)) ([6c69310](https://github.com/googlemaps/google-maps-services-python/commit/6c69310997e2e82edb5ee3e7235a3057945e7c76)) --- googlemaps/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 6aabb596..61ec45d0 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -15,7 +15,7 @@ # the License. # -__version__ = "4.9.0" +__version__ = "4.10.0" from googlemaps.client import Client from googlemaps import exceptions diff --git a/setup.py b/setup.py index 634cfb42..df9f32f1 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( name="googlemaps", - version="4.9.0", + version="4.10.0", description="Python client library for Google Maps Platform", long_description=readme + changelog, long_description_content_type="text/markdown", From 80cb54e16964e686d24cfd536ab8db329395e274 Mon Sep 17 00:00:00 2001 From: Angela Yu <5506675+wangela@users.noreply.github.com> Date: Thu, 26 Jan 2023 08:52:42 -0800 Subject: [PATCH 258/260] chore: update nox docs python version --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 2bc3b2a1..e5b92961 100644 --- a/noxfile.py +++ b/noxfile.py @@ -52,7 +52,7 @@ def cover(session): session.run("coverage", "erase") -@nox.session(python="3.6") +@nox.session(python="3.7") def docs(session): _install_dev_packages(session) _install_doc_dependencies(session) From 645e07de5a27c4c858b2c0673f0dd6f23ca62d28 Mon Sep 17 00:00:00 2001 From: Angela Yu <5506675+wangela@users.noreply.github.com> Date: Thu, 26 Jan 2023 17:01:18 -0800 Subject: [PATCH 259/260] docs: fix code format in static maps docs (#475) --- googlemaps/maps.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/googlemaps/maps.py b/googlemaps/maps.py index cc1a054e..746223d6 100644 --- a/googlemaps/maps.py +++ b/googlemaps/maps.py @@ -123,7 +123,7 @@ def __init__(self, points, def static_map(client, size, - center=None, zoom=None, scale=None, + center=None, zoom=None, scale=None, format=None, maptype=None, language=None, region=None, markers=None, path=None, visible=None, style=None): """ @@ -181,7 +181,8 @@ def static_map(client, size, :rtype: iterator containing the raw image data, which typically can be used to save an image file locally. For example: - ``` + .. code-block:: python + f = open(local_filename, 'wb') for chunk in client.static_map(size=(400, 400), center=(52.520103, 13.404871), @@ -189,7 +190,6 @@ def static_map(client, size, if chunk: f.write(chunk) f.close() - ``` """ params = {"size": convert.size(size)} From 9ec69cb66eec929d08ca90a82081b8ee4eef8b54 Mon Sep 17 00:00:00 2001 From: Thomas Clifford Date: Tue, 16 Jul 2024 10:47:22 -0700 Subject: [PATCH 260/260] =?UTF-8?q?feat!:=20Added=20Address=20Descriptors?= =?UTF-8?q?=20to=20Geocoding=20response.=20Refactored=20Geo=E2=80=A6=20(#5?= =?UTF-8?q?16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat! Added Address Descriptors to Geocoding response. Refactored Geocoding response to allow fields outside the geocoding result to be exposed through the client. --------- Co-authored-by: Tom Clifford --- README.md | 4 +++ googlemaps/geocoding.py | 18 ++++++++++---- tests/test_geocoding.py | 54 +++++++++++++++++++++++++++++------------ 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index cb5f7821..40823790 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,10 @@ addressvalidation_result = gmaps.addressvalidation(['1600 Amphitheatre Pk'], regionCode='US', locality='Mountain View', enableUspsCass=True) + +# Get an Address Descriptor of a location in the reverse geocoding response +address_descriptor_result = gmaps.reverse_geocode((40.714224, -73.961452), enable_address_descriptor=True) + ``` For more usage examples, check out [the tests](https://github.com/googlemaps/google-maps-services-python/tree/master/tests). diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index e409a49e..590bb627 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -49,7 +49,9 @@ def geocode(client, address=None, place_id=None, components=None, bounds=None, r :param language: The language in which to return results. :type language: string - :rtype: list of geocoding results. + :rtype: result dict with the following keys: + status: status code + results: list of geocoding results """ params = {} @@ -72,11 +74,11 @@ def geocode(client, address=None, place_id=None, components=None, bounds=None, r if language: params["language"] = language - return client._request("/maps/api/geocode/json", params).get("results", []) + return client._request("/maps/api/geocode/json", params) def reverse_geocode(client, latlng, result_type=None, location_type=None, - language=None): + language=None, enable_address_descriptor=False): """ Reverse geocoding is the process of converting geographic coordinates into a human-readable address. @@ -94,7 +96,10 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, :param language: The language in which to return results. :type language: string - :rtype: list of reverse geocoding results. + :rtype: result dict with the following keys: + status: status code + results: list of reverse geocoding results + address_descriptor: address descriptor for the target """ # Check if latlng param is a place_id string. @@ -113,4 +118,7 @@ def reverse_geocode(client, latlng, result_type=None, location_type=None, if language: params["language"] = language - return client._request("/maps/api/geocode/json", params).get("results", []) + if enable_address_descriptor: + params["enable_address_descriptor"] = "true" + + return client._request("/maps/api/geocode/json", params) diff --git a/tests/test_geocoding.py b/tests/test_geocoding.py index 813241f6..8734c8b8 100644 --- a/tests/test_geocoding.py +++ b/tests/test_geocoding.py @@ -41,7 +41,7 @@ def test_simple_geocode(self): content_type="application/json", ) - results = self.client.geocode("Sydney") + results = self.client.geocode("Sydney").get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -60,7 +60,7 @@ def test_reverse_geocode(self): content_type="application/json", ) - results = self.client.reverse_geocode((-33.8674869, 151.2069902)) + results = self.client.reverse_geocode((-33.8674869, 151.2069902)).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -79,7 +79,7 @@ def test_geocoding_the_googleplex(self): content_type="application/json", ) - results = self.client.geocode("1600 Amphitheatre Parkway, " "Mountain View, CA") + results = self.client.geocode("1600 Amphitheatre Parkway, " "Mountain View, CA").get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -105,7 +105,7 @@ def test_geocode_with_bounds(self): "southwest": (34.172684, -118.604794), "northeast": (34.236144, -118.500938), }, - ) + ).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -125,7 +125,7 @@ def test_geocode_with_region_biasing(self): content_type="application/json", ) - results = self.client.geocode("Toledo", region="es") + results = self.client.geocode("Toledo", region="es").get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -144,7 +144,7 @@ def test_geocode_with_component_filter(self): content_type="application/json", ) - results = self.client.geocode("santa cruz", components={"country": "ES"}) + results = self.client.geocode("santa cruz", components={"country": "ES"}).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -165,7 +165,7 @@ def test_geocode_with_multiple_component_filters(self): results = self.client.geocode( "Torun", components={"administrative_area": "TX", "country": "US"} - ) + ).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -191,7 +191,7 @@ def test_geocode_with_just_components(self): "administrative_area": "Helsinki", "country": "Finland", } - ) + ).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -211,7 +211,7 @@ def test_geocode_place_id(self): content_type="application/json", ) - results = self.client.geocode(place_id="ChIJeRpOeF67j4AR9ydy_PIzPuM") + results = self.client.geocode(place_id="ChIJeRpOeF67j4AR9ydy_PIzPuM").get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -230,7 +230,7 @@ def test_simple_reverse_geocode(self): content_type="application/json", ) - results = self.client.reverse_geocode((40.714224, -73.961452)) + results = self.client.reverse_geocode((40.714224, -73.961452)).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -253,7 +253,7 @@ def test_reverse_geocode_restricted_by_type(self): (40.714224, -73.961452), location_type="ROOFTOP", result_type="street_address", - ) + ).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -277,7 +277,7 @@ def test_reverse_geocode_multiple_location_types(self): (40.714224, -73.961452), location_type=["ROOFTOP", "RANGE_INTERPOLATED"], result_type="street_address", - ) + ).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -301,7 +301,7 @@ def test_reverse_geocode_multiple_result_types(self): (40.714224, -73.961452), location_type="ROOFTOP", result_type=["street_address", "route"], - ) + ).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -311,6 +311,28 @@ def test_reverse_geocode_multiple_result_types(self): responses.calls[0].request.url, ) + @responses.activate + def test_reverse_geocode_with_address_descriptors(self): + responses.add( + responses.GET, + "https://maps.googleapis.com/maps/api/geocode/json", + body='{"status":"OK","results":[], "address_descriptor":{ "landmarks": [ { "placeId": "id" } ] } }', + status=200, + content_type="application/json", + ) + + response = self.client.reverse_geocode((-33.8674869, 151.2069902), enable_address_descriptor=True) + + address_descriptor = response.get("address_descriptor", []) + + self.assertEqual(1, len(address_descriptor["landmarks"])) + self.assertEqual(1, len(responses.calls)) + self.assertURLEqual( + "https://maps.googleapis.com/maps/api/geocode/json?" + "latlng=-33.8674869,151.2069902&enable_address_descriptor=true&key=%s" % self.key, + responses.calls[0].request.url, + ) + @responses.activate def test_partial_match(self): responses.add( @@ -321,7 +343,7 @@ def test_partial_match(self): content_type="application/json", ) - results = self.client.geocode("Pirrama Pyrmont") + results = self.client.geocode("Pirrama Pyrmont").get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -340,7 +362,7 @@ def test_utf_results(self): content_type="application/json", ) - results = self.client.geocode(components={"postal_code": "96766"}) + results = self.client.geocode(components={"postal_code": "96766"}).get("results", []) self.assertEqual(1, len(responses.calls)) self.assertURLEqual( @@ -359,7 +381,7 @@ def test_utf8_request(self): content_type="application/json", ) - self.client.geocode(self.u("\\u4e2d\\u56fd")) # China + self.client.geocode(self.u("\\u4e2d\\u56fd")).get("results", []) # China self.assertURLEqual( "https://maps.googleapis.com/maps/api/geocode/json?" "key=%s&address=%s" % (self.key, "%E4%B8%AD%E5%9B%BD"),