diff --git a/googlemaps/__init__.py b/googlemaps/__init__.py index 9381dc6c..0662968b 100644 --- a/googlemaps/__init__.py +++ b/googlemaps/__init__.py @@ -1,7 +1,7 @@ -from context import Context -from directions import directions -from geocoding import geocode -from geocoding import reverse_geocode +from googlemaps.common import Context +from googlemaps.directions import directions +from googlemaps.geocoding import geocode +from googlemaps.geocoding import reverse_geocode # Allow sphinx to pick up these symbols for the documentation. __all__ = ['Context', 'geocode', 'reverse_geocode', 'directions'] diff --git a/googlemaps/common.py b/googlemaps/common.py index a0eb4212..345646bd 100644 --- a/googlemaps/common.py +++ b/googlemaps/common.py @@ -1,5 +1,38 @@ +""" +Common functionality for modules in the googlemaps package, such as performing +HTTP requests. +""" + import requests + +class Context(object): + """Holds state between requests, such as credentials (API key), timeout + settings""" + + def __init__(self, key=None, client_id=None, client_secret=None, + timeout=None): + """ + :param key: Maps API key. Required, unless "client_id" and + "client_secret" are set. + :type key: basestring + + :param timeout: Timeout for requests, in seconds. + :type timeout: int + + :param client_id: (for Maps API for Work customers) Your client ID. + :type client_id: basestring + + :param client_secret: (for Maps API for Work customers) Your client + secret (base64 encoded). + :type client_secret: basestring + """ + # TODO(lukem): simple key validation. + self.key = key + self.timeout = timeout + self.client_id = client_id + self.client_secret = client_secret + def _get(ctx, url, params): """Performs HTTP GET request with credentials, returning the body as JSON. @@ -13,19 +46,20 @@ def _get(ctx, url, params): # TODO(mdr-eng): implement rate limiting, retries, etc. # TODO(mdr-eng): implement enterprise key signing + # TODO(mdr-eng): enforce use of API keys/credentials # TODO(mdr-eng): add jitter (might not be necessary since most uses will be # single threaded) params["key"] = ctx.key resp = requests.get( - "https://maps.googleapis.com" + url, - verify=True, # NOTE(cbro): verify SSL certs. - params=params) + "https://maps.googleapis.com" + url, + verify=True, # NOTE(cbro): verify SSL certs. + params=params) # TODO(mdr-eng): better error handling if resp.status_code != 200: raise Exception( - "Unexpected response: [%d] %s" % - (resp.status_code, resp.text)) + "Unexpected response: [%d] %s" % + (resp.status_code, resp.text)) body = resp.json() diff --git a/googlemaps/context.py b/googlemaps/context.py deleted file mode 100644 index 05cb9487..00000000 --- a/googlemaps/context.py +++ /dev/null @@ -1,16 +0,0 @@ -import requests - - -class Context(object): - - def __init__(self, key, timeout=None): - """ - :param key: Maps API key. Required, unless "client_id" and "client_secret" are set. - :type key: basestring - - :param timeout: Timeout for requests, in seconds. - :type timeout: int - """ - # TODO(lukem): simple key validation. - self.key = key - self.timeout = timeout diff --git a/googlemaps/convert.py b/googlemaps/convert.py index 686b8f6b..5c4629a8 100644 --- a/googlemaps/convert.py +++ b/googlemaps/convert.py @@ -14,7 +14,7 @@ import time as _time -def latlng(ll): +def latlng(arg): """Converts a lat/lon pair to a comma-separated string. Accepts various representations: @@ -32,59 +32,71 @@ def latlng(ll): convert.latlng(sydney) # '-33.8674869,151.2069902' - :param ll: The lat/lon pair. - :type ll: basestring or dict or list + :param arg: The lat/lon pair. + :type arg: basestring or dict or list """ - if isinstance(ll, basestring): - return ll + if isinstance(arg, basestring): + return arg - if isinstance(ll, dict): - if "lat" in ll and "lng" in ll: - return "%f,%f" % (ll["lat"], ll["lng"]) + if isinstance(arg, dict): + if "lat" in arg and "lng" in arg: + return "%f,%f" % (arg["lat"], arg["lng"]) # List or tuple. - if _is_list(ll): - return "%f,%f" % (ll[0], ll[1]) + if _is_list(arg): + return "%f,%f" % (arg[0], arg[1]) raise TypeError( - "Expected a string or lat/lng dict, " - "but got %s" % type(o).__name__) + "Expected a string or lat/lng dict, " + "but got %s" % type(arg).__name__) -def join_list(sep, l): - return sep.join(as_list(l)) +def join_list(sep, arg): + """If arg is list-like, then joins it with sep. + :param sep: Separator string. + :type sep: basestring + :param arg: Value to coerce into a list. + :type arg: basestring or list + :rtype: basestring + """ + return sep.join(as_list(arg)) -def as_list(l): - if _is_list(l): - return l - return [l] +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): + return arg + return [arg] def _is_list(arg): + """Checks if arg is list-like. This excludes strings.""" return (not _has_method(arg, "strip") - and _has_method(arg, "__getitem__") - or _has_method(arg, "__iter__")) + and _has_method(arg, "__getitem__") + or _has_method(arg, "__iter__")) -def time(t): +def time(arg): """Converts the value into a unix time (seconds since unix epoch). For example: convert.time(datetime.now()) # '1409810596' - :param t: The time. - :type t: datetime.datetime or int + :param arg: The time. + :type arg: datetime.datetime or int """ # handle datetime instances. - if _has_method(t, "timetuple"): - t = _time.mktime(t.timetuple()) + if _has_method(arg, "timetuple"): + arg = _time.mktime(arg.timetuple()) - if isinstance(t, float): - t = int(t) + if isinstance(arg, float): + arg = int(arg) - return str(t) + return str(arg) def _has_method(arg, method): @@ -98,7 +110,7 @@ def _has_method(arg, method): return hasattr(arg, method) and callable(getattr(arg, method)) -def components(c): +def components(arg): """Converts a dict of components to the format expected by the Google Maps server. @@ -107,31 +119,59 @@ def components(c): convert.components(c) # 'country:US|postal_code:94043' - :param c: The component filter. - :type c: dict or basestring + :param arg: The component filter. + :type arg: dict or basestring :rtype basestring: """ - if isinstance(c, basestring): - return c + if isinstance(arg, basestring): + return arg - if isinstance(c, dict): - c = ["%s:%s" % (k, c[k]) for k in c] - return "|".join(c) + if isinstance(arg, dict): + arg = ["%s:%s" % (k, arg[k]) for k in arg] + return "|".join(arg) raise TypeError( - "Expected a string or dict for components, " - "but got %s" % type(c).__name__) + "Expected a string or dict for components, " + "but got %s" % type(arg).__name__) + +def bounds(arg): + """Converts a lat/lon bounds to a comma- and pipe-separated string. + + Accepts two representations: + 1) string: pipe-separated pair of comma-separated lat/lon pairs. + 2) dict with two entries - "southwest" and "northeast". See convert.latlng + for information on how these can be represented. + + For example: + + sydney_bounds = { + "northeast" : { + "lat" : -33.4245981, + "lng" : 151.3426361 + }, + "southwest" : { + "lat" : -34.1692489, + "lng" : 150.502229 + } + } + + convert.bounds(sydney_bounds) + # '-34.169249,150.502229|-33.424598,151.342636' + + :param arg: The bounds. + :type arg: basestring or dict + """ -def bounds(b): - if isinstance(b, basestring): - return b + if isinstance(arg, basestring): + return arg - if isinstance(b, dict): - if "southwest" in b and "northeast" in b: - return "%s|%s" % (latlng(b["southwest"]), latlng(b["northeast"])) + if isinstance(arg, dict): + if "southwest" in arg and "northeast" in arg: + return "%s|%s" % (latlng(arg["southwest"]), + latlng(arg["northeast"])) raise TypeError( - "Expected a string or bounds (southwest/northeast) dict, " - "but got %s" % type(b).__name__) + "Expected a string or bounds (southwest/northeast) dict, " + "but got %s" % type(arg).__name__) diff --git a/googlemaps/directions.py b/googlemaps/directions.py index 68d2d199..d938d934 100644 --- a/googlemaps/directions.py +++ b/googlemaps/directions.py @@ -1,11 +1,13 @@ -import common -import convert +"""Performs requests to the Google Maps Directions API.""" + +from googlemaps import common +from googlemaps import convert def directions(ctx, origin, destination, - mode=None, waypoints=None, alternatives=False, avoid=None, - language=None, units=None, region=None, departure_time=None, - arrival_time=None): + mode=None, waypoints=None, alternatives=False, avoid=None, + language=None, units=None, region=None, departure_time=None, + arrival_time=None): """Get directions between an origin point and a destination point. :param ctx: Shared googlemaps.Context @@ -48,7 +50,8 @@ def directions(ctx, origin, destination, :param departure_time: Specifies the desired time of departure. :type departure_time: int or datetime.datetime - :param arrival_time: Specifies the desired time of arrival for transit directions. + :param arrival_time: Specifies the desired time of arrival for transit + directions. :type arrival_time: int or datetime.datetime :rtype: list of routes diff --git a/googlemaps/geocoding.py b/googlemaps/geocoding.py index 1a17d9d5..4cf4e17c 100644 --- a/googlemaps/geocoding.py +++ b/googlemaps/geocoding.py @@ -1,9 +1,11 @@ -import common -import convert +"""Performs requests to the Google Maps Geocoding API.""" +from googlemaps import common +from googlemaps import convert # TODO(mdr-eng): test unicode parameters (e.g. in addresses). -def geocode(ctx, address=None, components=None, bounds=None, region=None, language=None): +def geocode(ctx, address=None, components=None, bounds=None, region=None, + language=None): """ Geocoding is the process of converting addresses (like "1600 Amphitheatre Parkway, Mountain View, CA") into geographic @@ -31,7 +33,8 @@ def geocode(ctx, address=None, components=None, bounds=None, region=None, langua return common._get(ctx, "/maps/api/geocode/json", params)["results"] -def reverse_geocode(ctx, latlng, result_type=None, location_type=None, language=None): +def reverse_geocode(ctx, latlng, result_type=None, location_type=None, + language=None): """ Reverse geocoding is the process of converting geographic coordinates into a human-readable address.