From 94bf3b722234361868a06cc9ccc93f627932fd98 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Wed, 3 Oct 2018 07:17:12 -0700 Subject: [PATCH 01/13] Dependency: Update to Python3 [private] --- ggeocoder.py | 33 ++++++++++++++------------------- setup.py | 8 ++++---- test.py | 26 ++++++++++++++------------ 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/ggeocoder.py b/ggeocoder.py index 1ece7fd..4786e88 100755 --- a/ggeocoder.py +++ b/ggeocoder.py @@ -8,9 +8,9 @@ import base64 import hashlib import hmac -import urllib -import urllib2 -import urlparse +import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.error, urllib.parse +import urllib.parse try: import json @@ -18,10 +18,11 @@ import simplejson as json -VERSION = '1.0.2' +VERSION = '2.0.0' __all__ = ['Geocoder', 'GeocoderResult', 'GeoResult', 'GeocodeError',] + class GeocodeError(Exception): """ Base class for errors in the :mod:`ggeocoder` module. @@ -58,10 +59,7 @@ def __init__(self, status, url=None, response=None): def __str__(self): """Return a string representation of this :exc:`GeocoderError`.""" return 'Error: {0}\nQuery: {1}'.format(self.status, self.url) - - def __unicode__(self, *args, **kwargs): - """Return a unicode representation of this :exc:`GeocoderError`.""" - return unicode(self.__str__()) + class GeoResult(object): """ @@ -84,9 +82,6 @@ def __eq__(self, other): return False def __str__(self): - return unicode(self).encode('utf-8') - - def __unicode__(self): return self.formatted_address def __getattr__(self, name): @@ -225,7 +220,7 @@ def _get_geocoder_result(self, result_class=GeoResult, **params): def _get_results(self, params=None): url = self._get_request_url(params or {}) - response = urllib2.urlopen(url, timeout=self.TIMEOUT_SECONDS) + response = urllib.request.urlopen(url, timeout=self.TIMEOUT_SECONDS) return self._process_response(response, url) def _process_response(self, response, url): @@ -235,7 +230,7 @@ def _process_response(self, response, url): return j['results'] def _get_request_url(self, params): - encoded_params = urllib.urlencode(params) + encoded_params = urllib.parse.urlencode(params) url = self.GOOGLE_API_URL + encoded_params if self.use_premier_key: url = self._get_premier_url(url) @@ -252,11 +247,11 @@ def _generate_signature(self, base_url, private_key): """ http://code.google.com/apis/maps/documentation/webservices/index.html#PythonSignatureExample """ - url = urlparse.urlparse(base_url) + url = urllib.parse.urlparse(base_url) url_to_sign = url.path + '?' + url.query decoded_key = base64.urlsafe_b64decode(private_key) - signature = hmac.new(decoded_key, url_to_sign, hashlib.sha1) - return base64.urlsafe_b64encode(signature.digest()) + signature = hmac.new(decoded_key, url_to_sign.encode(), hashlib.sha1) + return base64.urlsafe_b64encode(signature.digest()).decode('utf-8') if __name__ == "__main__": @@ -292,12 +287,12 @@ def main(): try: result = gcoder.geocode(query) - except GeocodeError, err: + except GeocodeError as err: sys.stderr.write('%s\n%s\nResponse:\n' % (err.url, err)) json.dump(err.response, sys.stderr, indent=4) sys.exit(1) for r in result: - print r - print r.coordinates + print(r) + print(r.coordinates) main() diff --git a/setup.py b/setup.py index d68214f..9cfd412 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ import os -from distutils.core import setup +from setuptools import setup import ggeocoder @@ -12,7 +12,7 @@ author='Aaron Madison', url='https://github.com/imtapps/ggeocoder', description='A Python library for working with Google Geocoding API V3.', - long_description=file( + long_description=open( os.path.join(os.path.dirname(__file__), 'README.rst') ).read(), py_modules=['ggeocoder'], @@ -23,7 +23,7 @@ 'Intended Audience :: Developers', 'Natural Language :: English', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', 'License :: OSI Approved :: BSD License', 'Topic :: Internet', 'Topic :: Internet :: WWW/HTTP', @@ -32,4 +32,4 @@ ], keywords='google maps api v3 geocode geocoding geocoder reverse', license='BSD License', -) \ No newline at end of file +) diff --git a/test.py b/test.py index 926bedc..3168244 100644 --- a/test.py +++ b/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import mock -from StringIO import StringIO +from io import StringIO import unittest try: @@ -94,7 +94,8 @@ def test_geocode_error_raises_status_for_message(self): with self.assertRaises(GeocodeError) as ctx: raise GeocodeError(status, url=query_url) - self.assertEqual(status, ctx.exception.message) + self.assertEqual(status, ctx.exception.args[0]) + class GeoResultTests(unittest.TestCase): @@ -158,9 +159,9 @@ class TestResult(GeoResult): } t = TestResult(self.data) - self.assertEqual(u'94043', t.ZIP) - self.assertEqual(u'California', t.state) - self.assertEqual(u'CA', t.state__short_name) + self.assertEqual('94043', t.ZIP) + self.assertEqual('California', t.state) + self.assertEqual('CA', t.state__short_name) def test_custom_mapped_attrs_support_dunder_lookup(self): class TestResult(GeoResult): @@ -169,7 +170,7 @@ class TestResult(GeoResult): } t = TestResult(self.data) - self.assertEqual(u'CA', t.state) + self.assertEqual('CA', t.state) class GeocoderResultTests(unittest.TestCase): @@ -205,6 +206,7 @@ def test_iterates_through_data_items_on_iter(self): result = GeocoderResult(self.data, self.result_class) self.assertEqual([GeoResult(self.data[0]), GeoResult(self.data[1])], list(result)) + class GeocoderTests(unittest.TestCase): def setUp(self): @@ -221,13 +223,13 @@ def test_raises_exception_when_client_id_provided_without_private_key(self): with self.assertRaises(GeocodeError) as ctx: Geocoder(client_id=self.client_id) msg = "You must provide both a client_id and private_key to use Premier Account." - self.assertEqual(msg, ctx.exception.message) + self.assertEqual(msg, ctx.exception.args[0]) def test_raises_exception_when_private_key_provided_without_client_id(self): with self.assertRaises(GeocodeError) as ctx: Geocoder(private_key=self.private_key) msg = "You must provide both a client_id and private_key to use Premier Account." - self.assertEqual(msg, ctx.exception.message) + self.assertEqual(msg, ctx.exception.args[0]) def test_allows_initialization_with_no_credentials(self): g = Geocoder() @@ -254,7 +256,7 @@ def test_get_premier_url_adds_client_and_signature_to_query_string(self): expected_url = self.base_url + '&client={0}&signature={1}'.format(self.client_id, self.known_signature) self.assertEqual(expected_url, premier_url) - @mock.patch('urllib.urlencode') + @mock.patch('urllib.parse.urlencode') def test_get_request_url_returns_url_with_params_when_not_premier(self, urlencode): params = dict(address='New York', sensor='false') urlencode.return_value = 'address=New+York&sensor=false' @@ -265,7 +267,7 @@ def test_get_request_url_returns_url_with_params_when_not_premier(self, urlencod self.assertEqual(g.GOOGLE_API_URL + urlencode.return_value, request_url) urlencode.assert_called_once_with(params) - @mock.patch('urllib.urlencode') + @mock.patch('urllib.parse.urlencode') @mock.patch.object(Geocoder, '_get_premier_url') def test_gets_premier_url_when_supplied_credentials(self, get_premier_url, urlencode): params = dict(address='New York', sensor='false') @@ -280,7 +282,7 @@ def test_gets_premier_url_when_supplied_credentials(self, get_premier_url, urlen get_premier_url.assert_called_once_with(expected_url_to_pass) @mock.patch.object(Geocoder, '_process_response', mock.Mock()) - @mock.patch('urllib2.urlopen') + @mock.patch('urllib.request.urlopen') @mock.patch.object(Geocoder, '_get_request_url') def test_get_results_opens_url_with_request_url_and_timeout(self, get_url, urlopen): params = dict(address='New York', sensor='false') @@ -290,7 +292,7 @@ def test_get_results_opens_url_with_request_url_and_timeout(self, get_url, urlop get_url.assert_called_once_with(params) urlopen.assert_called_once_with(get_url.return_value, timeout=g.TIMEOUT_SECONDS) - @mock.patch('urllib2.urlopen') + @mock.patch('urllib.request.urlopen') @mock.patch.object(Geocoder, '_get_request_url') @mock.patch.object(Geocoder, '_process_response') def test_get_results_returns_processed_response(self, process_response, get_url, urlopen): From 20538c82b860ddab6160c5880c94882418b11755 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 27 Jun 2019 10:58:49 -0500 Subject: [PATCH 02/13] Enhancement: Allow api_key to be used [https://github.com/imtapps/ggeocoder/issues/3] --- ggeocoder.py | 15 +++++++++++---- test.py | 32 +++++++++++++++++--------------- 2 files changed, 28 insertions(+), 19 deletions(-) mode change 100644 => 100755 test.py diff --git a/ggeocoder.py b/ggeocoder.py index 4786e88..70fbdcb 100755 --- a/ggeocoder.py +++ b/ggeocoder.py @@ -167,17 +167,17 @@ def __getitem__(self, idx): class Geocoder(object): """ Interface for interacting with Google's Geocoding V3's API. - http://code.google.com/apis/maps/documentation/geocoding/ + https://developers.google.com/maps/documentation/geocoding/start?csw=1 If you have a Google Maps Premier account, you can supply your client_id and private_key and the :class:`Geocoder` will make the request with a properly signed url """ PREMIER_CREDENTIALS_ERROR = "You must provide both a client_id and private_key to use Premier Account." - GOOGLE_API_URL = 'http://maps.googleapis.com/maps/api/geocode/json?' + GOOGLE_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json?' TIMEOUT_SECONDS = 3 - def __init__(self, client_id=None, private_key=None): + def __init__(self, client_id=None, private_key=None, api_key=None): """ Google Maps API Premier users can provide credentials to make 100,000 requests a day vs the standard 2,500 requests a day without @@ -189,9 +189,14 @@ def __init__(self, client_id=None, private_key=None): :type client_id: str :param private_key: private key used to sign urls :type private_key: str + :param api_key: Google Maps API key. + this will trump if it is present. Google has moved away from the + "premier accounts" and now just requires an API key + :type api_key: str """ self.credentials = (client_id, private_key) + self.api_key = api_key if any(self.credentials) and not all(self.credentials): raise GeocodeError(self.PREMIER_CREDENTIALS_ERROR) @@ -232,7 +237,9 @@ def _process_response(self, response, url): def _get_request_url(self, params): encoded_params = urllib.parse.urlencode(params) url = self.GOOGLE_API_URL + encoded_params - if self.use_premier_key: + if self.api_key: + url = url + "&key={}".format(self.api_key) + elif self.use_premier_key: url = self._get_premier_url(url) return url diff --git a/test.py b/test.py old mode 100644 new mode 100755 index 3168244..efaaaca --- a/test.py +++ b/test.py @@ -3,6 +3,7 @@ import mock from io import StringIO import unittest +from urllib.parse import urlencode try: import json @@ -256,30 +257,30 @@ def test_get_premier_url_adds_client_and_signature_to_query_string(self): expected_url = self.base_url + '&client={0}&signature={1}'.format(self.client_id, self.known_signature) self.assertEqual(expected_url, premier_url) - @mock.patch('urllib.parse.urlencode') - def test_get_request_url_returns_url_with_params_when_not_premier(self, urlencode): - params = dict(address='New York', sensor='false') - urlencode.return_value = 'address=New+York&sensor=false' + def test_get_request_url_returns_url_with_params_when_not_premier(self): + params = dict(address='New York') g = Geocoder() request_url = g._get_request_url(params) - self.assertEqual(g.GOOGLE_API_URL + urlencode.return_value, request_url) - urlencode.assert_called_once_with(params) + self.assertEqual(g.GOOGLE_API_URL + urlencode(params), request_url) - @mock.patch('urllib.parse.urlencode') - @mock.patch.object(Geocoder, '_get_premier_url') - def test_gets_premier_url_when_supplied_credentials(self, get_premier_url, urlencode): - params = dict(address='New York', sensor='false') - urlencode.return_value = 'address=New+York&sensor=false' + def test_get_request_url_returns_url_uses_api_key_when_present(self): + params = dict(address='New York') - g = Geocoder(client_id=self.client_id, private_key=self.private_key) + g = Geocoder(api_key="FAKE-API-KEY") request_url = g._get_request_url(params) - self.assertEqual(get_premier_url.return_value, request_url) + self.assertEqual(g.GOOGLE_API_URL + urlencode(params) + "&key=FAKE-API-KEY", request_url) + + def test_gets_premier_url_when_supplied_credentials(self): + params = dict(address='New York', sensor="false") - expected_url_to_pass = g.GOOGLE_API_URL + urlencode.return_value - get_premier_url.assert_called_once_with(expected_url_to_pass) + g = Geocoder(client_id=self.client_id, private_key=self.private_key) + request_url = g._get_request_url(params) + + expected_url = g.GOOGLE_API_URL + urlencode(params) + '&client={0}&signature={1}'.format(self.client_id, self.known_signature) + self.assertEqual(expected_url, request_url) @mock.patch.object(Geocoder, '_process_response', mock.Mock()) @mock.patch('urllib.request.urlopen') @@ -366,5 +367,6 @@ def test_reverse_geocode_adds_sensor_parameter_when_not_supplied(self, get_resul Geocoder().reverse_geocode(lat, lng) get_results.assert_called_once_with(params=dict(latlng='37.421827,-122.0842409', sensor='false')) + if __name__ == "__main__": unittest.main() From 2e31e9ba8ca1b874211085fd70bea0910251d83f Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 27 Jun 2019 11:38:59 -0500 Subject: [PATCH 03/13] Enhancement: Python 2 and 3 compatibility [https://github.com/imtapps/ggeocoder/issues/3] --- ggeocoder.py | 23 ++++++++++++++--------- test.py | 44 ++++++++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/ggeocoder.py b/ggeocoder.py index 70fbdcb..2175f57 100755 --- a/ggeocoder.py +++ b/ggeocoder.py @@ -8,9 +8,16 @@ import base64 import hashlib import hmac -import urllib.request, urllib.parse, urllib.error -import urllib.request, urllib.error, urllib.parse -import urllib.parse +import sys + +if sys.version_info > (3, 0): + from urllib.parse import urlencode + from urllib.parse import urlparse, urlencode + from urllib.request import urlopen +else: + from urllib import urlencode + from urllib2 import urlopen + from urlparse import urlparse try: import json @@ -219,13 +226,11 @@ def use_premier_key(self): return all(self.credentials) def _get_geocoder_result(self, result_class=GeoResult, **params): - geo_params = {'sensor': 'false'} # API says sensor must always have a value - geo_params.update(params) - return GeocoderResult(self._get_results(params=geo_params), result_class) + return GeocoderResult(self._get_results(params=params), result_class) def _get_results(self, params=None): url = self._get_request_url(params or {}) - response = urllib.request.urlopen(url, timeout=self.TIMEOUT_SECONDS) + response = urlopen(url, timeout=self.TIMEOUT_SECONDS) return self._process_response(response, url) def _process_response(self, response, url): @@ -235,7 +240,7 @@ def _process_response(self, response, url): return j['results'] def _get_request_url(self, params): - encoded_params = urllib.parse.urlencode(params) + encoded_params = urlencode(params) url = self.GOOGLE_API_URL + encoded_params if self.api_key: url = url + "&key={}".format(self.api_key) @@ -254,7 +259,7 @@ def _generate_signature(self, base_url, private_key): """ http://code.google.com/apis/maps/documentation/webservices/index.html#PythonSignatureExample """ - url = urllib.parse.urlparse(base_url) + url = urlparse(base_url) url_to_sign = url.path + '?' + url.query decoded_key = base64.urlsafe_b64decode(private_key) signature = hmac.new(decoded_key, url_to_sign.encode(), hashlib.sha1) diff --git a/test.py b/test.py index efaaaca..facc62d 100755 --- a/test.py +++ b/test.py @@ -1,15 +1,23 @@ #!/usr/bin/env python import mock -from io import StringIO +import sys + import unittest -from urllib.parse import urlencode try: import json except ImportError: import simplejson as json +if sys.version_info > (3, 0): + from urllib.parse import urlencode + from io import StringIO +else: + from urllib import urlencode + from cStringIO import StringIO + + from ggeocoder import Geocoder, GeoResult, GeocoderResult, GeocodeError no_results_response = """{ @@ -213,8 +221,8 @@ class GeocoderTests(unittest.TestCase): def setUp(self): self.private_key = 'vNIXE0xscrmjlyV-12Nj_BvUPaw=' self.client_id = 'clientID' - self.base_url = 'http://maps.googleapis.com/maps/api/geocode/json?address=New+York&sensor=false' - self.known_signature = 'KrU1TzVQM7Ur0i8i7K3huiw3MsA=' # signature generated for above credentials + self.base_url = 'https://maps.googleapis.com/maps/api/geocode/json?address=New+York' + self.known_signature = 'chaRF2hTJKOScPr-RQCEhZbSzIE=' # signature generated for above credentials def test_accepts_client_id_and_private_key_on_initialization(self): g = Geocoder(client_id=self.client_id, private_key=self.private_key) @@ -274,7 +282,7 @@ def test_get_request_url_returns_url_uses_api_key_when_present(self): self.assertEqual(g.GOOGLE_API_URL + urlencode(params) + "&key=FAKE-API-KEY", request_url) def test_gets_premier_url_when_supplied_credentials(self): - params = dict(address='New York', sensor="false") + params = dict(address='New York') g = Geocoder(client_id=self.client_id, private_key=self.private_key) request_url = g._get_request_url(params) @@ -283,21 +291,21 @@ def test_gets_premier_url_when_supplied_credentials(self): self.assertEqual(expected_url, request_url) @mock.patch.object(Geocoder, '_process_response', mock.Mock()) - @mock.patch('urllib.request.urlopen') + @mock.patch('ggeocoder.urlopen') @mock.patch.object(Geocoder, '_get_request_url') def test_get_results_opens_url_with_request_url_and_timeout(self, get_url, urlopen): - params = dict(address='New York', sensor='false') + params = dict(address='New York') g = Geocoder(client_id=self.client_id, private_key=self.private_key) g._get_results(params) get_url.assert_called_once_with(params) urlopen.assert_called_once_with(get_url.return_value, timeout=g.TIMEOUT_SECONDS) - @mock.patch('urllib.request.urlopen') + @mock.patch('ggeocoder.urlopen') @mock.patch.object(Geocoder, '_get_request_url') @mock.patch.object(Geocoder, '_process_response') def test_get_results_returns_processed_response(self, process_response, get_url, urlopen): - params = dict(address='New York', sensor='false') + params = dict(address='New York') g = Geocoder(client_id=self.client_id, private_key=self.private_key) results = g._get_results(params) @@ -334,8 +342,8 @@ def test_process_response_returns_results_when_status_is_ok(self): def test_geocode_returns_result_by_address_and_additional_params(self, get_results, geocoder_result_class): mock_result_class = mock.Mock() address = "1600 Amphitheatre Pkwy" - result = Geocoder().geocode(address, sensor='true', result_class=mock_result_class) - get_results.assert_called_once_with(params=dict(address=address, sensor='true')) + result = Geocoder().geocode(address, result_class=mock_result_class) + get_results.assert_called_once_with(params=dict(address=address)) geocoder_result_class.assert_called_once_with(get_results.return_value, mock_result_class) self.assertEqual(geocoder_result_class.return_value, result) @@ -345,7 +353,7 @@ def test_geocode_adds_sensor_parameter_when_not_supplied(self, get_results): address = "1600 Amphitheatre Pkwy" Geocoder().geocode(address) - get_results.assert_called_once_with(params=dict(address=address, sensor='false')) + get_results.assert_called_once_with(params=dict(address=address)) @mock.patch('ggeocoder.GeocoderResult') @mock.patch.object(Geocoder, '_get_results') @@ -354,19 +362,11 @@ def test_reverse_geocode_returns_result_by_latlng_and_additional_params(self, ge mock_result_class = mock.Mock() - result = Geocoder().reverse_geocode(lat, lng, sensor='true', language='fr', result_class=mock_result_class) - get_results.assert_called_once_with(params=dict(latlng='37.421827,-122.084241', sensor='true', language='fr')) + result = Geocoder().reverse_geocode(lat, lng, language='fr', result_class=mock_result_class) + get_results.assert_called_once_with(params=dict(latlng='37.421827,-122.084241', language='fr')) geocoder_result_class.assert_called_once_with(get_results.return_value, mock_result_class) self.assertEqual(geocoder_result_class.return_value, result) - @mock.patch('ggeocoder.GeocoderResult', mock.Mock(spec_set=GeocoderResult)) - @mock.patch.object(Geocoder, '_get_results') - def test_reverse_geocode_adds_sensor_parameter_when_not_supplied(self, get_results): - lat, lng = 37.4218270, -122.0842409 - - Geocoder().reverse_geocode(lat, lng) - get_results.assert_called_once_with(params=dict(latlng='37.421827,-122.0842409', sensor='false')) - if __name__ == "__main__": unittest.main() From 040cca91fc683a0bcda86fde67a231a91b642017 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 27 Jun 2019 12:00:10 -0500 Subject: [PATCH 04/13] Peripheral: Add travis-ci [private] --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b47a98b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +dist: xenial +language: python +python: + - "2.7" + - "3.4" + - "3.5" + - "3.6" + - "3.7" + +install: + - pip install mock coverage flake8 +script: + - coverage run test.py + - coverage report --show-missing + - flake8 ggeocoder.py From 8445a5c2c07baf78dff777457f15dd1a5f4c3c85 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 27 Jun 2019 12:11:07 -0500 Subject: [PATCH 05/13] Style: flake8 cleanup [private] --- ggeocoder.py | 49 +++++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/ggeocoder.py b/ggeocoder.py index 2175f57..1633806 100755 --- a/ggeocoder.py +++ b/ggeocoder.py @@ -11,7 +11,6 @@ import sys if sys.version_info > (3, 0): - from urllib.parse import urlencode from urllib.parse import urlparse, urlencode from urllib.request import urlopen else: @@ -27,17 +26,17 @@ VERSION = '2.0.0' -__all__ = ['Geocoder', 'GeocoderResult', 'GeoResult', 'GeocodeError',] +__all__ = ['Geocoder', 'GeocoderResult', 'GeoResult', 'GeocodeError'] class GeocodeError(Exception): """ Base class for errors in the :mod:`ggeocoder` module. - + Methods of the :class:`Geocoder` raise this when the Google Maps API returns a status of anything other than 'OK'. - - See http://code.google.com/apis/maps/documentation/geocoding/index.html#StatusCodes + + See http://code.google.com/apis/maps/documentation/geocoding/index.html#StatusCodes # noqa: E501 for status codes and their meanings. """ G_GEO_OK = "OK" @@ -45,10 +44,10 @@ class GeocodeError(Exception): G_GEO_OVER_QUERY_LIMIT = "OVER_QUERY_LIMIT" G_GEO_REQUEST_DENIED = "REQUEST_DENIED" G_GEO_MISSING_QUERY = "INVALID_REQUEST" - + def __init__(self, status, url=None, response=None): """Create an exception with a status and optional full response. - + :param status: Either a ``G_GEO_`` code or a string explaining the exception. :type status: int or string @@ -56,13 +55,12 @@ def __init__(self, status, url=None, response=None): :type url: string :param response: The actual response returned from Google, if any. :type response: dict - """ super(GeocodeError, self).__init__(status) self.status = status self.url = url self.response = response - + def __str__(self): """Return a string representation of this :exc:`GeocoderError`.""" return 'Error: {0}\nQuery: {1}'.format(self.status, self.url) @@ -90,7 +88,7 @@ def __eq__(self, other): def __str__(self): return self.formatted_address - + def __getattr__(self, name): attr, prop = self.get_property_components(name) @@ -144,7 +142,6 @@ def raw(self): return self.data - class GeocoderResult(object): """ Helps process all the results returned from Google Maps API @@ -158,7 +155,6 @@ class GeocoderResult(object): print result.formatted_address, result.coordinates You can also customize the result_class created if you'd like. - """ def __init__(self, data, result_class): @@ -180,7 +176,7 @@ class Geocoder(object): client_id and private_key and the :class:`Geocoder` will make the request with a properly signed url """ - PREMIER_CREDENTIALS_ERROR = "You must provide both a client_id and private_key to use Premier Account." + PREMIER_CREDENTIALS_ERROR = "You must provide both a client_id and private_keyto use Premier Account." # noqa: E501 GOOGLE_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json?' TIMEOUT_SECONDS = 3 @@ -200,7 +196,6 @@ def __init__(self, client_id=None, private_key=None, api_key=None): this will trump if it is present. Google has moved away from the "premier accounts" and now just requires an API key :type api_key: str - """ self.credentials = (client_id, private_key) self.api_key = api_key @@ -210,14 +205,14 @@ def __init__(self, client_id=None, private_key=None, api_key=None): def geocode(self, address, **params): """ | Params may be any valid parameter accepted by Google's API. - | http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests + | http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests # noqa: E501 """ return self._get_geocoder_result(address=address, **params) def reverse_geocode(self, lat, lng, **params): """ | Params may be any valid parameter accepted by Google's API. - | http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests + | http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests # noqa: E501 """ return self._get_geocoder_result(latlng="%s,%s" % (lat, lng), **params) @@ -269,41 +264,43 @@ def _generate_signature(self, base_url, private_key): if __name__ == "__main__": import sys from optparse import OptionParser - + def main(): """ Geocodes a location given on the command line. - + Usage: ggeocoder.py "1600 amphitheatre mountain view ca" [YOUR_API_KEY] ggeocoder.py 37.4218272,-122.0842409 [YOUR_API_KEY] - + When providing a latitude and longitude on the command line, ensure they are separated by a comma and no space. - + """ usage = "usage: %prog [options] address" parser = OptionParser(usage, version=VERSION) parser.add_option("-c", "--client_id", - dest="client_id", help="Your Google Maps Client Id key") + dest="client_id", + help="Your Google Maps Client Id key") parser.add_option("-k", "--private_key", - dest="private_key", help="Your Google Maps Private Signature key") + dest="private_key", + help="Your Google Maps Private Signature key") (options, args) = parser.parse_args() - + if len(args) != 1: parser.print_usage() sys.exit(1) - + query = args[0] gcoder = Geocoder(options.client_id, options.private_key) - + try: result = gcoder.geocode(query) except GeocodeError as err: sys.stderr.write('%s\n%s\nResponse:\n' % (err.url, err)) json.dump(err.response, sys.stderr, indent=4) sys.exit(1) - + for r in result: print(r) print(r.coordinates) From 6b9743b138b4ff5f2fb0942c8ce56e1da628b7de Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 27 Jun 2019 13:28:09 -0500 Subject: [PATCH 06/13] Style: flake8 cleanup [private] --- ggeocoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ggeocoder.py b/ggeocoder.py index 1633806..6e62744 100755 --- a/ggeocoder.py +++ b/ggeocoder.py @@ -176,7 +176,7 @@ class Geocoder(object): client_id and private_key and the :class:`Geocoder` will make the request with a properly signed url """ - PREMIER_CREDENTIALS_ERROR = "You must provide both a client_id and private_keyto use Premier Account." # noqa: E501 + PREMIER_CREDENTIALS_ERROR = "You must provide both a client_id and private_key to use Premier Account." # noqa: E501 GOOGLE_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json?' TIMEOUT_SECONDS = 3 From 25b3700ce05c1884be58c41d80553515719c01aa Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 27 Jun 2019 13:35:23 -0500 Subject: [PATCH 07/13] Peripheral: Added travis-ci badge to readme --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 1567d39..5dd2687 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,8 @@ .. _geocoder_api: +.. image:: https://travis-ci.org/imtapps/ggeocoder.svg?branch=master + :target: https://travis-ci.org/imtapps/ggeocoder + ******************* Google Geocoder API ******************* From cf27ae679580bdedad1956d9aa7df8a8494ee2fb Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 27 Jun 2019 13:41:54 -0500 Subject: [PATCH 08/13] Peripheral: Updated versions in setup.py classifiers [private] --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9cfd412..73b84cf 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,11 @@ 'Intended Audience :: Developers', 'Natural Language :: English', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'License :: OSI Approved :: BSD License', 'Topic :: Internet', 'Topic :: Internet :: WWW/HTTP', From 9290864cbb0a79ac20ae7b7ceeef4211ee26f946 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Tue, 17 Sep 2024 05:32:59 -0500 Subject: [PATCH 09/13] Peripheral: Adding Github actions --- .github/workflows/acceptance.yml | 31 ++++++++++++++++++++++++++++ .github/workflows/python-publish.yml | 31 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .github/workflows/acceptance.yml create mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml new file mode 100644 index 0000000..eb6309f --- /dev/null +++ b/.github/workflows/acceptance.yml @@ -0,0 +1,31 @@ +name: Acceptance CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: ['2.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install mock flake8 + + - name: Run Tests + run: | + python -m unittest test.py diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..7f407bd --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,31 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME_PUBLIC }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD_PUBLIC }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* From 1cc95a28893f53e54d18b7c8ea7de01fa61ef3a0 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Tue, 17 Sep 2024 05:41:17 -0500 Subject: [PATCH 10/13] Peripheral: trying to test python 2.7 --- .github/workflows/27acceptance.yml | 24 ++++++++++++++++++++++++ .github/workflows/acceptance.yml | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/27acceptance.yml diff --git a/.github/workflows/27acceptance.yml b/.github/workflows/27acceptance.yml new file mode 100644 index 0000000..02e307c --- /dev/null +++ b/.github/workflows/27acceptance.yml @@ -0,0 +1,24 @@ +name: Acceptance CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + container: + image: python:2.7.18-buster + + steps: + - uses: actions/checkout@v4 + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install mock + + - name: Run Tests + run: | + python -m unittest test.py diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index eb6309f..1074295 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['2.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install mock flake8 + pip install mock - name: Run Tests run: | From ac6736106f1c7da63efdb4da38481e6b9a953366 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Tue, 17 Sep 2024 05:45:05 -0500 Subject: [PATCH 11/13] Peripheral: trying to test python 2.7 --- .github/workflows/27acceptance.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/27acceptance.yml b/.github/workflows/27acceptance.yml index 02e307c..f2cf2cc 100644 --- a/.github/workflows/27acceptance.yml +++ b/.github/workflows/27acceptance.yml @@ -1,4 +1,4 @@ -name: Acceptance CI +name: 2.7 Acceptance CI on: push: @@ -17,7 +17,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install mock + pip install mock unittest2 - name: Run Tests run: | From dfb643cedafa8be0290aa927d7940383cb67f9a2 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Tue, 17 Sep 2024 05:49:18 -0500 Subject: [PATCH 12/13] Peripheral: trying to test python 2.7 --- .github/workflows/27acceptance.yml | 2 +- test.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/27acceptance.yml b/.github/workflows/27acceptance.yml index f2cf2cc..99b3220 100644 --- a/.github/workflows/27acceptance.yml +++ b/.github/workflows/27acceptance.yml @@ -21,4 +21,4 @@ jobs: - name: Run Tests run: | - python -m unittest test.py + python -m test diff --git a/test.py b/test.py index facc62d..a08105d 100755 --- a/test.py +++ b/test.py @@ -89,6 +89,7 @@ "status" : "OK" }""" + class GeocodeErrorTests(unittest.TestCase): def test_geocode_error_string_representation_contains_error_and_query(self): From 407d90887efe773fdd0a9d925a6b31e648bf68b7 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Tue, 17 Sep 2024 05:54:53 -0500 Subject: [PATCH 13/13] Peripheral: remove travis ci --- .travis.yml | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b47a98b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -dist: xenial -language: python -python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" - - "3.7" - -install: - - pip install mock coverage flake8 -script: - - coverage run test.py - - coverage report --show-missing - - flake8 ggeocoder.py