Skip to content

Commit

Permalink
Baidu: return back urlencode, clean up tests
Browse files Browse the repository at this point in the history
  • Loading branch information
KostyaEsmukov committed Jun 27, 2018
1 parent 8f1d266 commit 3836855
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 87 deletions.
63 changes: 26 additions & 37 deletions geopy/geocoders/baidu.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
:class:`.Baidu` is the Baidu Maps geocoder.
"""

from geopy.compat import quote, quote_plus
import hashlib

from geopy.compat import quote_plus, urlencode
from geopy.exc import (
GeocoderServiceError,
GeocoderAuthenticationFailure,
Expand All @@ -12,7 +14,6 @@
from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder
from geopy.location import Location
from geopy.util import logger
import hashlib

__all__ = ("Baidu", )

Expand All @@ -39,7 +40,7 @@ def __init__(
):
"""
:param str api_key: The API key required by Baidu Map to perform
:param str api_key: The API key (AK) required by Baidu Map to perform
geocoding requests. API keys are managed through the Baidu APIs
console (http://lbsyun.baidu.com/apiconsole/key).
Expand Down Expand Up @@ -71,9 +72,9 @@ def __init__(
.. versionadded:: 1.14.0
:param str security_key: The security key to calculate <sn> parameter
in request if authentication setting requires.
(http://lbsyun.baidu.com/index.php?title=lbscloud/api/appendix)
:param str security_key: The security key (SK) to calculate
the SN parameter in request if authentication setting requires
(http://lbsyun.baidu.com/index.php?title=lbscloud/api/appendix).
"""
super(Baidu, self).__init__(
format_string=format_string,
Expand Down Expand Up @@ -123,14 +124,10 @@ def geocode(
params = {
'ak': self.api_key,
'output': 'json',
'address': quote(self.format_string % query, safe="!*'();:@&=+$,/?#[]"),
'address': self.format_string % query,
}
params = "&".join(["{}={}".format(k, v) for k, v in params.items()])

if self.security_key is None:
url = "?".join((self.api, params))
else:
url = self._url_with_signature(params)
url = self._construct_url(params)

logger.debug("%s.geocode: %s", self.__class__.__name__, url)
return self._parse_json(
Expand Down Expand Up @@ -163,17 +160,10 @@ def reverse(self, query, exactly_one=True, timeout=DEFAULT_SENTINEL):
params = {
'ak': self.api_key,
'output': 'json',
'location': quote(
self._coerce_point_to_string(query),
safe="!*'();:@&=+$,/?#[]"
),
'location': self._coerce_point_to_string(query),
}
params = "&".join(["{}={}".format(k, v) for k, v in params.items()])

if self.security_key is None:
url = "?".join((self.api, params))
else:
url = self._url_with_signature(params)
url = self._construct_url(params)

logger.debug("%s.reverse: %s", self.__class__.__name__, url)
return self._parse_reverse_json(
Expand All @@ -184,7 +174,7 @@ def _parse_reverse_json(self, page, exactly_one=True):
"""
Parses a location from a single-result reverse API call.
"""
place = page.get('result', None)
place = page.get('result')

if not place:
self._check_status(page.get('status'))
Expand All @@ -205,7 +195,7 @@ def _parse_json(self, page, exactly_one=True):
Returns location, (latitude, longitude) from JSON feed.
"""

place = page.get('result', None)
place = page.get('result')

if not place:
self._check_status(page.get('status'))
Expand Down Expand Up @@ -267,26 +257,25 @@ def _check_status(status):
)
elif status == 211:
raise GeocoderAuthenticationFailure(
'Invalid SK'
'Invalid SN'
)
elif 200 <= status <= 300:
elif 200 <= status < 300:
raise GeocoderAuthenticationFailure(
'Authentication Failure'
)
elif 301 <= status <= 402:
elif 300 <= status < 500:
raise GeocoderQuotaExceeded(
'Quota Error.'
)
else:
raise GeocoderQueryError('Unknown error')

def _url_with_signature(self, quoted_params):
"""
Return the request url with signature.
qutoted_params is the escaped str of params in request.
"""
raise GeocoderQueryError('Unknown error. Status: %r' % status)

# Since quoted_params is escaped by urlencode, don't apply quote twice
raw = self.api_path + '?' + quoted_params + self.security_key
sn = hashlib.md5(quote_plus(raw).encode('utf-8')).hexdigest()
return self.api + '?' + quoted_params + '&sn=' + sn
def _construct_url(self, params):
query_string = urlencode(params)
if self.security_key is None:
return "%s?%s" % (self.api, query_string)
else:
# http://lbsyun.baidu.com/index.php?title=lbscloud/api/appendix
raw = "%s?%s%s" % (self.api_path, query_string, self.security_key)
sn = hashlib.md5(quote_plus(raw).encode('utf-8')).hexdigest()
return "%s?%s&sn=%s" % (self.api, query_string, sn)
74 changes: 24 additions & 50 deletions test/geocoders/baidu.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,8 @@ def setUpClass(cls):
def test_user_agent_custom(self):
self.assertEqual(self.geocoder.headers['User-Agent'], 'my_user_agent/1.0')

def test_invalid_ak(self):
with self.assertRaises(GeocoderAuthenticationFailure) as cm:
self.geocode_run({"query": u("baidu")}, None)
self.assertEqual(str(cm.exception), 'Invalid AK')


@unittest.skipUnless(
bool(env.get('BAIDU_KEY')),
"No BAIDU_KEY env variable set"
)
class BaiduTestCase(GeocoderTestBase):

@classmethod
def setUpClass(cls):
cls.geocoder = Baidu(
api_key=env['BAIDU_KEY'],
timeout=3,
)
cls.delta_exact = 0.02
class BaiduQueriesTestCaseMixin:

def test_basic_address(self):
"""
Expand Down Expand Up @@ -66,11 +49,31 @@ def test_reverse_point(self):
)


@unittest.skipUnless(
bool(env.get('BAIDU_KEY')),
"No BAIDU_KEY env variable set"
)
class BaiduTestCase(BaiduQueriesTestCaseMixin, GeocoderTestBase):

@classmethod
def setUpClass(cls):
cls.geocoder = Baidu(
api_key=env['BAIDU_KEY'],
timeout=3,
)

def test_invalid_ak(self):
self.geocoder = Baidu(api_key='DUMMYKEY1234')
with self.assertRaises(GeocoderAuthenticationFailure) as cm:
self.geocode_run({"query": u("baidu")}, None)
self.assertEqual(str(cm.exception), 'Invalid AK')


@unittest.skipUnless(
bool(env.get('BAIDU_KEY_REQUIRES_SK')) and bool(env.get('BAIDU_SEC_KEY')),
"BAIDU_KEY_REQUIRES_SK and BAIDU_SEC_KEY env variables not set"
)
class BaiduSKTestCase(GeocoderTestBase):
class BaiduSKTestCase(BaiduQueriesTestCaseMixin, GeocoderTestBase):

@classmethod
def setUpClass(cls):
Expand All @@ -79,42 +82,13 @@ def setUpClass(cls):
security_key=env['BAIDU_SEC_KEY'],
timeout=3,
)
cls.delta_exact = 0.02

def test_basic_address(self):
"""
Baidu.geocode
"""
self.geocode_run(
{"query": u(
"\u5317\u4eac\u5e02\u6d77\u6dc0\u533a"
"\u4e2d\u5173\u6751\u5927\u885727\u53f7"
)},
{"latitude": 39.983615544507, "longitude": 116.32295155093},
)

def test_reverse_point(self):
"""
Baidu.reverse Point
"""
self.reverse_run(
{"query": Point(39.983615544507, 116.32295155093)},
{"latitude": 39.983615544507, "longitude": 116.32295155093},
)
self.reverse_run(
{"query": Point(39.983615544507, 116.32295155093), "exactly_one": False},
{"latitude": 39.983615544507, "longitude": 116.32295155093},
)

def test_safe_within_signature(self):
"""
Baidu signature calculation with safe characters
"""
def test_sn_with_peculiar_chars(self):
self.geocode_run(
{"query": u(
"\u5317\u4eac\u5e02\u6d77\u6dc0\u533a"
"\u4e2d\u5173\u6751\u5927\u885727\u53f7"
"!*'();:@&=+$,/?[] %"
" ' & = , ? %"
)},
{"latitude": 39.983615544507, "longitude": 116.32295155093},
)

0 comments on commit 3836855

Please sign in to comment.