Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions googlemaps/__init__.py
Original file line number Diff line number Diff line change
@@ -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']
44 changes: 39 additions & 5 deletions googlemaps/common.py
Original file line number Diff line number Diff line change
@@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed that this isn't enforced at the moment. TODO?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a TODO.

"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.

Expand All @@ -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()

Expand Down
16 changes: 0 additions & 16 deletions googlemaps/context.py

This file was deleted.

132 changes: 86 additions & 46 deletions googlemaps/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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):
Expand All @@ -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.

Expand All @@ -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__)

15 changes: 9 additions & 6 deletions googlemaps/directions.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions googlemaps/geocoding.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand Down