Skip to content

Commit 2c0783d

Browse files
Merge pull request #4 from imtapps/python3
Python3
2 parents 1db83d4 + cf27ae6 commit 2c0783d

File tree

5 files changed

+126
-96
lines changed

5 files changed

+126
-96
lines changed

.travis.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
dist: xenial
2+
language: python
3+
python:
4+
- "2.7"
5+
- "3.4"
6+
- "3.5"
7+
- "3.6"
8+
- "3.7"
9+
10+
install:
11+
- pip install mock coverage flake8
12+
script:
13+
- coverage run test.py
14+
- coverage report --show-missing
15+
- flake8 ggeocoder.py

README.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
.. _geocoder_api:
22

3+
.. image:: https://travis-ci.org/imtapps/ggeocoder.svg?branch=master
4+
:target: https://travis-ci.org/imtapps/ggeocoder
5+
36
*******************
47
Google Geocoder API
58
*******************

ggeocoder.py

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,60 +8,63 @@
88
import base64
99
import hashlib
1010
import hmac
11-
import urllib
12-
import urllib2
13-
import urlparse
11+
import sys
12+
13+
if sys.version_info > (3, 0):
14+
from urllib.parse import urlparse, urlencode
15+
from urllib.request import urlopen
16+
else:
17+
from urllib import urlencode
18+
from urllib2 import urlopen
19+
from urlparse import urlparse
1420

1521
try:
1622
import json
1723
except ImportError:
1824
import simplejson as json
1925

2026

21-
VERSION = '1.0.2'
27+
VERSION = '2.0.0'
28+
29+
__all__ = ['Geocoder', 'GeocoderResult', 'GeoResult', 'GeocodeError']
2230

23-
__all__ = ['Geocoder', 'GeocoderResult', 'GeoResult', 'GeocodeError',]
2431

2532
class GeocodeError(Exception):
2633
"""
2734
Base class for errors in the :mod:`ggeocoder` module.
28-
35+
2936
Methods of the :class:`Geocoder` raise this when the Google Maps API
3037
returns a status of anything other than 'OK'.
31-
32-
See http://code.google.com/apis/maps/documentation/geocoding/index.html#StatusCodes
38+
39+
See http://code.google.com/apis/maps/documentation/geocoding/index.html#StatusCodes # noqa: E501
3340
for status codes and their meanings.
3441
"""
3542
G_GEO_OK = "OK"
3643
G_GEO_ZERO_RESULTS = "ZERO_RESULTS"
3744
G_GEO_OVER_QUERY_LIMIT = "OVER_QUERY_LIMIT"
3845
G_GEO_REQUEST_DENIED = "REQUEST_DENIED"
3946
G_GEO_MISSING_QUERY = "INVALID_REQUEST"
40-
47+
4148
def __init__(self, status, url=None, response=None):
4249
"""Create an exception with a status and optional full response.
43-
50+
4451
:param status: Either a ``G_GEO_`` code or a string explaining the
4552
exception.
4653
:type status: int or string
4754
:param url: The query URL that resulted in the error, if any.
4855
:type url: string
4956
:param response: The actual response returned from Google, if any.
5057
:type response: dict
51-
5258
"""
5359
super(GeocodeError, self).__init__(status)
5460
self.status = status
5561
self.url = url
5662
self.response = response
57-
63+
5864
def __str__(self):
5965
"""Return a string representation of this :exc:`GeocoderError`."""
6066
return 'Error: {0}\nQuery: {1}'.format(self.status, self.url)
61-
62-
def __unicode__(self, *args, **kwargs):
63-
"""Return a unicode representation of this :exc:`GeocoderError`."""
64-
return unicode(self.__str__())
67+
6568

6669
class GeoResult(object):
6770
"""
@@ -84,11 +87,8 @@ def __eq__(self, other):
8487
return False
8588

8689
def __str__(self):
87-
return unicode(self).encode('utf-8')
88-
89-
def __unicode__(self):
9090
return self.formatted_address
91-
91+
9292
def __getattr__(self, name):
9393
attr, prop = self.get_property_components(name)
9494

@@ -142,7 +142,6 @@ def raw(self):
142142
return self.data
143143

144144

145-
146145
class GeocoderResult(object):
147146
"""
148147
Helps process all the results returned from Google Maps API
@@ -156,7 +155,6 @@ class GeocoderResult(object):
156155
print result.formatted_address, result.coordinates
157156
158157
You can also customize the result_class created if you'd like.
159-
160158
"""
161159

162160
def __init__(self, data, result_class):
@@ -172,17 +170,17 @@ def __getitem__(self, idx):
172170
class Geocoder(object):
173171
"""
174172
Interface for interacting with Google's Geocoding V3's API.
175-
http://code.google.com/apis/maps/documentation/geocoding/
173+
https://developers.google.com/maps/documentation/geocoding/start?csw=1
176174
177175
If you have a Google Maps Premier account, you can supply your
178176
client_id and private_key and the :class:`Geocoder` will make
179177
the request with a properly signed url
180178
"""
181-
PREMIER_CREDENTIALS_ERROR = "You must provide both a client_id and private_key to use Premier Account."
182-
GOOGLE_API_URL = 'http://maps.googleapis.com/maps/api/geocode/json?'
179+
PREMIER_CREDENTIALS_ERROR = "You must provide both a client_id and private_key to use Premier Account." # noqa: E501
180+
GOOGLE_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json?'
183181
TIMEOUT_SECONDS = 3
184182

185-
def __init__(self, client_id=None, private_key=None):
183+
def __init__(self, client_id=None, private_key=None, api_key=None):
186184
"""
187185
Google Maps API Premier users can provide credentials to make 100,000
188186
requests a day vs the standard 2,500 requests a day without
@@ -194,23 +192,27 @@ def __init__(self, client_id=None, private_key=None):
194192
:type client_id: str
195193
:param private_key: private key used to sign urls
196194
:type private_key: str
197-
195+
:param api_key: Google Maps API key.
196+
this will trump if it is present. Google has moved away from the
197+
"premier accounts" and now just requires an API key
198+
:type api_key: str
198199
"""
199200
self.credentials = (client_id, private_key)
201+
self.api_key = api_key
200202
if any(self.credentials) and not all(self.credentials):
201203
raise GeocodeError(self.PREMIER_CREDENTIALS_ERROR)
202204

203205
def geocode(self, address, **params):
204206
"""
205207
| Params may be any valid parameter accepted by Google's API.
206-
| http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests
208+
| http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests # noqa: E501
207209
"""
208210
return self._get_geocoder_result(address=address, **params)
209211

210212
def reverse_geocode(self, lat, lng, **params):
211213
"""
212214
| Params may be any valid parameter accepted by Google's API.
213-
| http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests
215+
| http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests # noqa: E501
214216
"""
215217
return self._get_geocoder_result(latlng="%s,%s" % (lat, lng), **params)
216218

@@ -219,13 +221,11 @@ def use_premier_key(self):
219221
return all(self.credentials)
220222

221223
def _get_geocoder_result(self, result_class=GeoResult, **params):
222-
geo_params = {'sensor': 'false'} # API says sensor must always have a value
223-
geo_params.update(params)
224-
return GeocoderResult(self._get_results(params=geo_params), result_class)
224+
return GeocoderResult(self._get_results(params=params), result_class)
225225

226226
def _get_results(self, params=None):
227227
url = self._get_request_url(params or {})
228-
response = urllib2.urlopen(url, timeout=self.TIMEOUT_SECONDS)
228+
response = urlopen(url, timeout=self.TIMEOUT_SECONDS)
229229
return self._process_response(response, url)
230230

231231
def _process_response(self, response, url):
@@ -235,9 +235,11 @@ def _process_response(self, response, url):
235235
return j['results']
236236

237237
def _get_request_url(self, params):
238-
encoded_params = urllib.urlencode(params)
238+
encoded_params = urlencode(params)
239239
url = self.GOOGLE_API_URL + encoded_params
240-
if self.use_premier_key:
240+
if self.api_key:
241+
url = url + "&key={}".format(self.api_key)
242+
elif self.use_premier_key:
241243
url = self._get_premier_url(url)
242244
return url
243245

@@ -252,52 +254,54 @@ def _generate_signature(self, base_url, private_key):
252254
"""
253255
http://code.google.com/apis/maps/documentation/webservices/index.html#PythonSignatureExample
254256
"""
255-
url = urlparse.urlparse(base_url)
257+
url = urlparse(base_url)
256258
url_to_sign = url.path + '?' + url.query
257259
decoded_key = base64.urlsafe_b64decode(private_key)
258-
signature = hmac.new(decoded_key, url_to_sign, hashlib.sha1)
259-
return base64.urlsafe_b64encode(signature.digest())
260+
signature = hmac.new(decoded_key, url_to_sign.encode(), hashlib.sha1)
261+
return base64.urlsafe_b64encode(signature.digest()).decode('utf-8')
260262

261263

262264
if __name__ == "__main__":
263265
import sys
264266
from optparse import OptionParser
265-
267+
266268
def main():
267269
"""
268270
Geocodes a location given on the command line.
269-
271+
270272
Usage:
271273
ggeocoder.py "1600 amphitheatre mountain view ca" [YOUR_API_KEY]
272274
ggeocoder.py 37.4218272,-122.0842409 [YOUR_API_KEY]
273-
275+
274276
When providing a latitude and longitude on the command line, ensure
275277
they are separated by a comma and no space.
276-
278+
277279
"""
278280
usage = "usage: %prog [options] address"
279281
parser = OptionParser(usage, version=VERSION)
280282
parser.add_option("-c", "--client_id",
281-
dest="client_id", help="Your Google Maps Client Id key")
283+
dest="client_id",
284+
help="Your Google Maps Client Id key")
282285
parser.add_option("-k", "--private_key",
283-
dest="private_key", help="Your Google Maps Private Signature key")
286+
dest="private_key",
287+
help="Your Google Maps Private Signature key")
284288
(options, args) = parser.parse_args()
285-
289+
286290
if len(args) != 1:
287291
parser.print_usage()
288292
sys.exit(1)
289-
293+
290294
query = args[0]
291295
gcoder = Geocoder(options.client_id, options.private_key)
292-
296+
293297
try:
294298
result = gcoder.geocode(query)
295-
except GeocodeError, err:
299+
except GeocodeError as err:
296300
sys.stderr.write('%s\n%s\nResponse:\n' % (err.url, err))
297301
json.dump(err.response, sys.stderr, indent=4)
298302
sys.exit(1)
299-
303+
300304
for r in result:
301-
print r
302-
print r.coordinates
305+
print(r)
306+
print(r.coordinates)
303307
main()

setup.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
import os
5-
from distutils.core import setup
5+
from setuptools import setup
66

77
import ggeocoder
88

@@ -12,7 +12,7 @@
1212
author='Aaron Madison',
1313
url='https://github.com/imtapps/ggeocoder',
1414
description='A Python library for working with Google Geocoding API V3.',
15-
long_description=file(
15+
long_description=open(
1616
os.path.join(os.path.dirname(__file__), 'README.rst')
1717
).read(),
1818
py_modules=['ggeocoder'],
@@ -23,7 +23,11 @@
2323
'Intended Audience :: Developers',
2424
'Natural Language :: English',
2525
'Operating System :: OS Independent',
26-
'Programming Language :: Python :: 2',
26+
'Programming Language :: Python :: 2.7',
27+
'Programming Language :: Python :: 3.4',
28+
'Programming Language :: Python :: 3.5',
29+
'Programming Language :: Python :: 3.6',
30+
'Programming Language :: Python :: 3.7',
2731
'License :: OSI Approved :: BSD License',
2832
'Topic :: Internet',
2933
'Topic :: Internet :: WWW/HTTP',
@@ -32,4 +36,4 @@
3236
],
3337
keywords='google maps api v3 geocode geocoding geocoder reverse',
3438
license='BSD License',
35-
)
39+
)

0 commit comments

Comments
 (0)