<commit_before>"""
A simple wrapper around the 'experimental' Google Places API, documented
here: http://code.google.com/apis/maps/documentation/places/. This library
also makes use of the v3 Maps API for geocoding.

Prerequisites: A Google API key with Places activated against it. Please
check the Google API console, here: http://code.google.com/apis/console

NOTE: Please ensure that you read the Google terms of service (labeled 'Limits
and Requirements' on the documentation url) prior to using this library in a
production environment.

@author: <EMAIL>
"""

try:
    import json
except ImportError:
    import simplejson as json
import urllib
import urllib2

import lang
import ranking
import types


__all__ = ['GooglePlaces', 'GooglePlacesError', 'GooglePlacesAttributeError',
           'geocode_location']
__version__ = '0.9.0'
__author__ = '<NAME>'
__email__ = '<EMAIL>'

def _fetch_remote_json(service_url, params={}, use_http_post=False):
    """Retrieves a JSON object from a URL."""
    if not use_http_post:
        encoded_data = urllib.urlencode(params)
        query_url = (service_url if service_url.endswith('?') else
                     '%s?' % service_url)
        request_url = query_url + encoded_data
        request = urllib2.Request(request_url)
    else:
        request_url = service_url
        request = urllib2.Request(service_url, data=params)
    response = urllib2.urlopen(request)
    return (request_url, json.load(response))

def geocode_location(location, sensor=False):
    """Converts a human-readable location to lat-lng.

    Returns a dict with lat and lng keys.

    keyword arguments:
    location -- A human-readable location, e.g 'London, England'
    sensor   -- Boolean flag denoting if the location came from a device using
                its' location sensor (default False)

    raises:
    GooglePlacesError -- if the geocoder fails to find a location.
    """

    url, geo_response = _fetch_remote_json(
            GooglePlaces.GEOCODE_API_URL,
            {'address': location, 'sensor': str(sensor).lower()})
    _validate_response(url, geo_response)
    if geo_response['status'] == GooglePlaces.RESPONSE_STATUS_ZERO_RESULTS:
        error_detail = ('Lat/Lng for location \'%s\' can\'t be determined.' %
                        location)
        raise GooglePlacesError, error_detail
    return geo_response['results'][0]['geometry']['location']

def _get_place_details(reference, api_key, sensor=False):
    """Gets a detailed place response.

    keyword arguments:
    reference -- The unique Google reference for the required place.
    """
    url, detail_response = _fetch_remote_json(GooglePlaces.DETAIL_API_URL,
                                              {'reference': reference,
                                               'sensor': str(sensor).lower(),
                                               'key': api_key})
    _validate_response(url, detail_response)
    return detail_response['result']

def _validate_response(url, response):
    """Validates that the response from Google was successful."""
    if response['status'] not in [GooglePlaces.RESPONSE_STATUS_OK,
                                  GooglePlaces.RESPONSE_STATUS_ZERO_RESULTS]:
        error_detail = ('Request to URL %s failed with response code: %s' %
                        (url, response['status']))
        raise GooglePlacesError, error_detail


class GooglePlacesError(Exception):
    pass


class GooglePlacesAttributeError(AttributeError):
    """Exception thrown when a detailed property is unavailable.

    A search query from the places API returns only a summary of the Place.
    in order to get full details, a further API call must be made using
    the place reference. This exception will be thrown when a property made
    available by only the detailed API call is looked up against the summary
    object.

    An explicit call to get_details() must be made on the summary object in
    order to convert a summary object to a detailed object.
    """
    # I could spend forever muling between this design decision and creating
    # a PlaceSummary object as well as a Place object. I'm leaning towards this
    # method in order to keep the API as simple as possible.
    pass


class GooglePlaces(object):
    """A wrapper around the Google Places Query API."""

    GEOCODE_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json?'
    QUERY_API_URL = 'https://maps.googleapis.com/maps/api/place/search/json?'
    DETAIL_API_URL = ('https://maps.googleapis.com/maps/api/place/details/' +
                      'json?')
    CHECKIN_API_URL = ('https://maps.googleapis.com/maps/api/place/check-in/' +
                       'json?sensor=%s&key=%s')
    ADD_API_URL = ('https://maps.googleapis.com/maps/api/place/add/json?' +
                   'sensor=%s&key=%s')
    DELETE_API_URL = ('https://maps.googleapis.com/maps/api/place/delete/' +
                      'json?sensor=%s&key=%s')

    MAXIMUM_SEARCH_RADIUS = 50000
    RESPONSE_STATUS_OK = 'OK'
    RESPONSE_STATUS_ZERO_RESULTS = 'ZERO_RESULTS'

    def __init__(self, api_key):
        self._api_key = api_key
        self._sensor = False
        self._request_params = None

    def query(self, language=lang.ENGLISH, keyword=None, location=None,
               lat_lng=None, name=None, radius=3200, rankby=ranking.PROMINENCE,
               sensor=False, types=[]):
        """Perform a search using the Google Places API.

        One of either location or lat_lng are required, the rest of the keyword
        arguments are optional.

        keyword arguments:
        keyword  -- A term to be matched against all available fields, including
                    but not limited to name, type, and address (default None)
        location -- A human readable location, e.g 'London, England'
                    (default None)
        language -- The language code, indicating in which language the
                    results should be returned, if possble. (default lang.ENGLISH)
        lat_lng  -- A dict containing the following keys: lat, lng
                    (default None)
        name     -- A term to be matched against the names of the Places.
                    Results will be restricted to those containing the passed
                    name value. (default None)
        radius   -- The radius (in meters) around the location/lat_lng to
                    restrict the search to. The maximum is 50000 meters.
                    (default 3200)
        rankby   -- Specifies the order in which results are listed :
                    ranking.PROMINENCE (default) or ranking.DISTANCE
                    (imply no radius argument).
        sensor   -- Indicates whether or not the Place request came from a
                    device using a location sensor (default False).
        types    -- An optional list of types, restricting the results to
                    Places (default []).
        """
        if location is None and lat_lng is None:
            raise ValueError('One of location or lat_lng must be passed in.')
        if rankby == "distance":
            if keyword is None and types == []:
                raise ValueError('When rankby = "distance", at least one in keyword or types is required.')
        self._sensor = sensor
        self._lat_lng = (lat_lng if lat_lng is not None
                         else geocode_location(location))
        radius = (radius if radius <= GooglePlaces.MAXIMUM_SEARCH_RADIUS
                  else GooglePlaces.MAXIMUM_SEARCH_RADIUS)
        lat_lng_str = '%(lat)s,%(lng)s' % self._lat_lng
        self._request_params = {'location': lat_lng_str}
        if rankby == 'prominence':
            self._request_params['radius'] = radius
        else:
            self._request_params['rankby'] = rankby
        if len(types) > 0:
            self._request_params['types'] = '|'.join(types)
        if keyword is not None:
            self._request_params['keyword'] = keyword
        if name is not None:
            self._request_params['name'] = name<commit_msg>=Formatting + change error message<commit_after>"""
A simple wrapper around the 'experimental' Google Places API, documented
here: http://code.google.com/apis/maps/documentation/places/. This library
also makes use of the v3 Maps API for geocoding.

Prerequisites: A Google API key with Places activated against it. Please
check the Google API console, here: http://code.google.com/apis/console

NOTE: Please ensure that you read the Google terms of service (labelled 'Limits
and Requirements' on the documentation url) prior to using this library in a
production environment.

@author: <EMAIL>
"""

try:
    import json
except ImportError:
    import simplejson as json
import urllib
import urllib2

import lang
import ranking
import types


__all__ = ['GooglePlaces', 'GooglePlacesError', 'GooglePlacesAttributeError',
           'geocode_location']
__version__ = '0.9.0'
__author__ = '<NAME>'
__email__ = '<EMAIL>'

def _fetch_remote_json(service_url, params={}, use_http_post=False):
    """Retrieves a JSON object from a URL."""
    if not use_http_post:
        encoded_data = urllib.urlencode(params)
        query_url = (service_url if service_url.endswith('?') else
                     '%s?' % service_url)
        request_url = query_url + encoded_data
        request = urllib2.Request(request_url)
    else:
        request_url = service_url
        request = urllib2.Request(service_url, data=params)
    response = urllib2.urlopen(request)
    return (request_url, json.load(response))

def geocode_location(location, sensor=False):
    """Converts a human-readable location to lat-lng.

    Returns a dict with lat and lng keys.

    keyword arguments:
    location -- A human-readable location, e.g 'London, England'
    sensor   -- Boolean flag denoting if the location came from a device using
                its' location sensor (default False)

    raises:
    GooglePlacesError -- if the geocoder fails to find a location.
    """

    url, geo_response = _fetch_remote_json(
            GooglePlaces.GEOCODE_API_URL,
            {'address': location, 'sensor': str(sensor).lower()})
    _validate_response(url, geo_response)
    if geo_response['status'] == GooglePlaces.RESPONSE_STATUS_ZERO_RESULTS:
        error_detail = ('Lat/Lng for location \'%s\' can\'t be determined.' %
                        location)
        raise GooglePlacesError, error_detail
    return geo_response['results'][0]['geometry']['location']

def _get_place_details(reference, api_key, sensor=False):
    """Gets a detailed place response.

    keyword arguments:
    reference -- The unique Google reference for the required place.
    """
    url, detail_response = _fetch_remote_json(GooglePlaces.DETAIL_API_URL,
                                              {'reference': reference,
                                               'sensor': str(sensor).lower(),
                                               'key': api_key})
    _validate_response(url, detail_response)
    return detail_response['result']

def _validate_response(url, response):
    """Validates that the response from Google was successful."""
    if response['status'] not in [GooglePlaces.RESPONSE_STATUS_OK,
                                  GooglePlaces.RESPONSE_STATUS_ZERO_RESULTS]:
        error_detail = ('Request to URL %s failed with response code: %s' %
                        (url, response['status']))
        raise GooglePlacesError, error_detail


class GooglePlacesError(Exception):
    pass


class GooglePlacesAttributeError(AttributeError):
    """Exception thrown when a detailed property is unavailable.

    A search query from the places API returns only a summary of the Place.
    in order to get full details, a further API call must be made using
    the place reference. This exception will be thrown when a property made
    available by only the detailed API call is looked up against the summary
    object.

    An explicit call to get_details() must be made on the summary object in
    order to convert a summary object to a detailed object.
    """
    # I could spend forever muling between this design decision and creating
    # a PlaceSummary object as well as a Place object. I'm leaning towards this
    # method in order to keep the API as simple as possible.
    pass


class GooglePlaces(object):
    """A wrapper around the Google Places Query API."""

    GEOCODE_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json?'
    QUERY_API_URL = 'https://maps.googleapis.com/maps/api/place/search/json?'
    DETAIL_API_URL = ('https://maps.googleapis.com/maps/api/place/details/' +
                      'json?')
    CHECKIN_API_URL = ('https://maps.googleapis.com/maps/api/place/check-in/' +
                       'json?sensor=%s&key=%s')
    ADD_API_URL = ('https://maps.googleapis.com/maps/api/place/add/json?' +
                   'sensor=%s&key=%s')
    DELETE_API_URL = ('https://maps.googleapis.com/maps/api/place/delete/' +
                      'json?sensor=%s&key=%s')

    MAXIMUM_SEARCH_RADIUS = 50000
    RESPONSE_STATUS_OK = 'OK'
    RESPONSE_STATUS_ZERO_RESULTS = 'ZERO_RESULTS'

    def __init__(self, api_key):
        self._api_key = api_key
        self._sensor = False
        self._request_params = None

    def query(self, language=lang.ENGLISH, keyword=None, location=None,
               lat_lng=None, name=None, radius=3200, rankby=ranking.PROMINENCE,
               sensor=False, types=[]):
        """Perform a search using the Google Places API.

        One of either location or lat_lng are required, the rest of the keyword
        arguments are optional.

        keyword arguments:
        keyword  -- A term to be matched against all available fields, including
                    but not limited to name, type, and address (default None)
        location -- A human readable location, e.g 'London, England'
                    (default None)
        language -- The language code, indicating in which language the
                    results should be returned, if possble. (default lang.ENGLISH)
        lat_lng  -- A dict containing the following keys: lat, lng
                    (default None)
        name     -- A term to be matched against the names of the Places.
                    Results will be restricted to those containing the passed
                    name value. (default None)
        radius   -- The radius (in meters) around the location/lat_lng to
                    restrict the search to. The maximum is 50000 meters.
                    (default 3200)
        rankby   -- Specifies the order in which results are listed :
                    ranking.PROMINENCE (default) or ranking.DISTANCE
                    (imply no radius argument).
        sensor   -- Indicates whether or not the Place request came from a
                    device using a location sensor (default False).
        types    -- An optional list of types, restricting the results to
                    Places (default []).
        """
        if location is None and lat_lng is None:
            raise ValueError('One of location or lat_lng must be passed in.')
        if rankby == 'distance':
            if keyword is None and types == []:
                raise ValueError('When rankby = googleplaces.ranking.DISTANCE, ' +
                                 'one of either the keyword or types kwargs ' +
                                 'must be specified.')
        self._sensor = sensor
        self._lat_lng = (lat_lng if lat_lng is not None
                         else geocode_location(location))
        radius = (radius if radius <= GooglePlaces.MAXIMUM_SEARCH_RADIUS
                  else GooglePlaces.MAXIMUM_SEARCH_RADIUS)
        lat_lng_str = '%(lat)s,%(lng)s' % self._lat_lng
        self._request_params = {'location': lat_lng_str}
        if rankby == 'prominence':
            self._request_params['radius'] = radius
        else:
            self._request_params['rankby'] = rankby
        if len(types) > 0:
            self._request_params['types'] = '|'.join(types)
        if keyword is not None:
            self._request_params['keyword'] = keyword
        if name is not None:
            self._request_params['name'] = name
{'fraction_non_alphanumeric': 0.08613496932515337, 'fraction_numerical': 0.0034355828220858898, 'mean_word_length': 2.4123927150931546, 'pattern_counts': {'":': 1, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 12, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4789341', 'n_tokens_mistral': 4476, 'n_tokens_neox': 4295, 'n_words': 1470}