def test_pro_sort_AttrSorter(self): """Test AttrSorter postprocessor.""" candidates_in = [self.better, self.best, self.good] candidates_exp = [self.good, self.better, self.best] candidates_out = AttrSorter( self.locators_worse_to_better).process(candidates_in) self.assertEqual_(candidates_out, candidates_exp)
class _EsriNAGeocodeService(): """ Defaults for the _EsriNAGeocodeService """ LOCATOR_MAP = { 'RoofTop': 'rooftop', 'Streets': 'interpolation', } DEFAULT_PREPROCESSORS = [CountryPreProcessor(['US', 'CA'])] DEFAULT_POSTPROCESSORS = [ AttrRename('locator', LOCATOR_MAP), AttrFilter(['rooftop', 'interpolation'], 'locator'), AttrSorter(['rooftop', 'interpolation'], 'locator'), UseHighScoreIfAtLeast(99.8), GroupBy('match_addr'), ScoreSorter(), ]
class EsriWGS(GeocodeService): """ Class to geocode using the `ESRI World Geocoding service <http://geocode.arcgis.com/arcgis/geocoding.html>`_. This uses two endpoints -- one for single-line addresses, and one for multi-part addresses. An optional (key) parameter can be passed to the PlaceQuery which will be passed as a magicKey to the find endpoint if using a single line address/text search. This allows omgeo to be used with the `Esri suggest endpoint <https://developers.arcgis.com/rest/geocode/api-reference/geocoding-suggest.htm>`_. Note: Based on tests using the magicKey parameter, it is recommended that a viewbox not be used with in conjuction with the magicKey. Additionally, address/search text passed via the query may be ignored when using a magicKey. """ LOCATOR_MAP = { 'PointAddress': 'rooftop', 'StreetAddress': 'interpolation', 'PostalExt': 'postal_specific', # accept ZIP+4 'Postal': 'postal' } DEFAULT_PREPROCESSORS = [CancelIfPOBox()] DEFAULT_POSTPROCESSORS = [ AttrFilter( [ 'PointAddress', 'StreetAddress', #'PostalExt', #'Postal' ], 'locator_type'), #AttrExclude(['USA_Postal'], 'locator'), #accept postal from everywhere but US (need PostalExt) AttrSorter( [ 'PointAddress', 'StreetAddress', #'PostalExt', #'Postal' ], 'locator_type'), AttrRename( 'locator', LOCATOR_MAP), # after filter to avoid searching things we toss out UseHighScoreIfAtLeast(99.8), ScoreSorter(), GroupBy('match_addr'), GroupBy(('x', 'y')), ] _endpoint = 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer' def _geocode(self, pq): """ :arg PlaceQuery pq: PlaceQuery object to use for geocoding :returns: list of location Candidates """ #: List of desired output fields #: See `ESRI docs <http://geocode.arcgis.com/arcgis/geocoding.html#output>_` for details outFields = ( 'Loc_name', #'Shape', 'Score', 'Match_Addr', #based on address standards for the country #'Address', # returned by default #'Country' # 3-digit ISO 3166-1 code for a country. Example: Canada = "CAN" #'Admin', #'DepAdmin', #'SubAdmin', #'Locality', #'Postal', #'PostalExt', 'Addr_Type', #'Type', #'Rank', 'AddNum', 'StPreDir', 'StPreType', 'StName', 'StType', 'StDir', #'Side', #'AddNumFrom', #'AddNumTo', #'AddBldg', 'City', 'Subregion', 'Region', 'Postal', 'Country', #'Ymax', #'Ymin', #'Xmin', #'Xmax', #'X', #'Y', 'DisplayX', 'DisplayY', #'LangCode', #'Status', ) outFields = ','.join(outFields) query = dict( f='json', # default HTML. Other options are JSON and KMZ. outFields=outFields, #outSR=WKID, defaults to 4326 maxLocations=20, # default 1; max is 20 ) # Postal-code only searches work in the single-line but not multipart geocoder # Remember that with the default postprocessors, postcode-level results will be eliminated if pq.query == pq.address == '' and pq.postal != '': pq.query = pq.postal if pq.query == '': # multipart method = 'findAddressCandidates' query = dict( query, Address=pq. address, # commonly represents the house number and street name of a complete address Neighborhood=pq.neighborhood, City=pq.city, Subregion=pq.subregion, Region=pq.state, Postal=pq.postal, #PostalExt= CountryCode=pq. country, # full country name or ISO 3166-1 2- or 3-digit country code ) if pq.bounded and pq.viewbox is not None: query = dict(query, searchExtent=pq.viewbox.to_esri_wgs_json()) else: # single-line method = 'find' magic_key = pq.key if hasattr(pq, 'key') else '' query = dict( query, text=pq. query, # This can be a street address, place name, postal code, or POI. sourceCountry=pq. country, # full country name or ISO 3166-1 2- or 3-digit country code ) if magic_key: query[ 'magicKey'] = magic_key # This is a lookup key returned from the suggest endpoint. if pq.bounded and pq.viewbox is not None: query = dict(query, bbox=pq.viewbox.to_esri_wgs_json()) endpoint = self._endpoint + '/' + method response_obj = self._get_json_obj(endpoint, query) returned_candidates = [] # this will be the list returned try: if method == 'find': locations = response_obj['locations'] else: locations = response_obj['candidates'] for location in locations: c = Candidate() if method == 'find': # singlepart attributes = location['feature']['attributes'] else: # findAddressCandidates / multipart attributes = location['attributes'] c.match_addr = attributes['Match_Addr'] c.locator = attributes['Loc_name'] c.locator_type = attributes['Addr_Type'] c.score = attributes['Score'] c.x = attributes[ 'DisplayX'] # represents the actual location of the address. c.y = attributes['DisplayY'] c.wkid = response_obj['spatialReference']['wkid'] c.geoservice = self.__class__.__name__ # Optional address component fields. for in_key, out_key in [('City', 'match_city'), ('Subregion', 'match_subregion'), ('Region', 'match_region'), ('Postal', 'match_postal'), ('Country', 'match_country')]: setattr(c, out_key, attributes.get(in_key, '')) setattr(c, 'match_streetaddr', self._street_addr_from_response(attributes)) returned_candidates.append(c) except KeyError: pass return returned_candidates def _street_addr_from_response(self, attributes): """Construct a street address (no city, region, etc.) from a geocoder response. :param attributes: A dict of address attributes as returned by the Esri geocoder. """ # The exact ordering of the address component fields that should be # used to reconstruct the full street address is not specified in the # Esri documentation, but the examples imply that it is this. ordered_fields = [ 'AddNum', 'StPreDir', 'StPreType', 'StName', 'StType', 'StDir' ] result = [] for field in ordered_fields: result.append(attributes.get(field, '')) if any(result): return ' '.join([s for s in result if s]) # Filter out empty strings. else: return '' def __init__(self, preprocessors=None, postprocessors=None, settings=None): preprocessors = EsriWGS.DEFAULT_PREPROCESSORS if preprocessors is None else preprocessors postprocessors = EsriWGS.DEFAULT_POSTPROCESSORS if postprocessors is None else postprocessors GeocodeService.__init__(self, preprocessors, postprocessors, settings)
class Bing(GeocodeService): """ Class to geocode using Bing services: * `Find a Location by Query <http://msdn.microsoft.com/en-us/library/ff701711.aspx>`_ * `Find a Location by Address <http://msdn.microsoft.com/en-us/library/ff701714.aspx>`_ Settings used by the Bing GeocodeService object may include: * api_key -- The API key used to access Bing services. """ _endpoint = 'http://dev.virtualearth.net/REST/v1/Locations' DEFAULT_PREPROCESSORS = [ReplaceRangeWithNumber()] DEFAULT_POSTPROCESSORS = [ AttrMigrator('confidence', 'score', { 'High': 100, 'Medium': 85, 'Low': 50 }), UseHighScoreIfAtLeast(100), AttrFilter([ 'Address', 'AdministrativeBuilding', 'AgriculturalStructure', 'BusinessName', 'BusinessStructure', 'BusStation', 'Camp', 'Church', 'CityHall', 'CommunityCenter', 'ConventionCenter', 'Courthouse', 'Factory', 'FerryTerminal', 'FishHatchery', 'Fort', 'Garden', 'Geyser', 'Heliport', 'IndustrialStructure', 'InformationCenter', 'Junction', 'LandmarkBuilding', 'Library', 'Lighthouse', 'Marina', 'MedicalStructure', 'MetroStation', 'Mine', 'Mission', 'Monument', 'Mosque', 'Museum', 'NauticalStructure', 'NavigationalStructure', 'OfficeBuilding', 'ParkAndRide', 'PlayingField', 'PoliceStation', 'PostOffice', 'PowerStation', 'Prison', 'RaceTrack', 'ReligiousStructure', 'RestArea', 'Ruin', 'ShoppingCenter', 'Site', 'SkiArea', 'Spring', 'Stadium', 'Temple', 'TouristStructure' ], 'entity'), AttrRename( 'locator', dict(Rooftop='rooftop', Parcel='parcel', ParcelCentroid='parcel', Interpolation='interpolation', InterpolationOffset='interpolation_offset')), AttrSorter( ['rooftop', 'parcel', 'interpolation_offset', 'interpolation'], 'locator'), AttrSorter(['Address'], 'entity'), ScoreSorter(), GroupBy(('x', 'y')), GroupBy('match_addr') ] DEFAULT_POSTPROCESSORS = [] def __init__(self, preprocessors=None, postprocessors=None, settings=None): preprocessors = Bing.DEFAULT_PREPROCESSORS if preprocessors is None else preprocessors postprocessors = Bing.DEFAULT_POSTPROCESSORS if postprocessors is None else postprocessors GeocodeService.__init__(self, preprocessors, postprocessors, settings) def _geocode(self, pq): if pq.query.strip() == '': # No single line query string; use address elements: query = { 'addressLine': pq.address, 'locality': pq.city, 'adminDistrict': pq.state, 'postalCode': pq.postal, 'countryRegion': pq.country } else: query = {'query': pq.query} if pq.viewbox is not None: query = dict(query, **{'umv': pq.viewbox.to_bing_str()}) if hasattr(pq, 'culture'): query = dict(query, c=pq.culture) if hasattr(pq, 'user_ip'): query = dict(query, uip=pq.user_ip) if hasattr(pq, 'user_lat') and hasattr(pq, 'user_lon'): query = dict(query, **{'ul': '%f,%f' % (pq.user_lat, pq.user_lon)}) addl_settings = {'key': self._settings['api_key']} query = dict(query, **addl_settings) response_obj = self._get_json_obj(self._endpoint, query) returned_candidates = [] # this will be the list returned for r in response_obj['resourceSets'][0]['resources']: c = Candidate() c.entity = r['entityType'] c.locator = r['geocodePoints'][0][ 'calculationMethod'] # ex. "Parcel" c.confidence = r['confidence'] # High|Medium|Low c.match_addr = r[ 'name'] # ex. "1 Microsoft Way, Redmond, WA 98052" c.x = r['geocodePoints'][0]['coordinates'][1] # long, ex. -122.13 c.y = r['geocodePoints'][0]['coordinates'][0] # lat, ex. 47.64 c.wkid = 4326 c.geoservice = self.__class__.__name__ returned_candidates.append(c) return returned_candidates
def setUp(self): # Viewbox objects - callowhill is from BSS Spring Garden station to Wash. Sq. vb = { 'callowhill': Viewbox(-75.162628, 39.962769, -75.150963, 39.956322) } # PlaceQuery objects self.pq = { # North American Addresses: 'azavea': PlaceQuery('340 N 12th St Ste 402 Philadelphia PA'), 'ambiguous_azavea': PlaceQuery('340 12th St Ste 402 Philadelphia PA'), 'zip_plus_4_in_postal_plus_country': PlaceQuery(postal='19127-1115', country='US'), 'wolf': PlaceQuery('Wolf Building'), 'wolf_philly': PlaceQuery('Wolf Building, Philadelphia PA'), 'wolf_bounded': PlaceQuery('Wolf Building', bounded=True, viewbox=vb['callowhill']), 'bounded_340_12th': PlaceQuery('340 12th St, Philadelphia PA', bounded=True, viewbox=vb['callowhill']), 'alpha_774R_W_Central_Ave': PlaceQuery('774R W Central Ave Alpha NJ'), 'alpha_774_W_Central_Ave_Rear': PlaceQuery('774 W Central Ave Rear, Alpha NJ'), '8_kirkbride': PlaceQuery('8 Kirkbride Rd 08822'), 'george_washington': PlaceQuery('201 W Montmorency Blvd, George, Washington'), 'pine_needles_dr': PlaceQuery('11761 pine needles providence forge'), 'pine_needles_ct': PlaceQuery('5328 pine needles providence forge'), 'pine_needles_terr': PlaceQuery('5359 pine needles providence forge'), 'moorestown_hyphenated': PlaceQuery('111-113 W Main St Moorestown NJ'), 'willow_street': PlaceQuery('2819F Willow Street Pike Willow Street PA'), 'willow_street_parts': PlaceQuery(address='2819F Willow Street Pike', city='Willow Street', state='PA', country='US'), 'quebec': PlaceQuery('756 Rue Berri Montreal QC', country='CA'), 'quebec_accent': PlaceQuery('527 Ch. Beauséjour, Saint-Elzéar-de-Témiscouata QC'), 'quebec_hyphenated': PlaceQuery('227-227A Rue Commerciale, Saint-Louis-du-Ha! Ha! QC'), 'senado_mx': PlaceQuery('Paseo de la Reforma 135, Tabacalera, Cuauhtémoc, Distrito Federal, 06030'), 'senado_mx_struct': PlaceQuery(address='Paseo de la Reforma 135', neighborhood='Tabacalera, Cuauhtémoc', subregion='', state='Distrito Federal', postal='06030', country='MX'), 'robert_cheetham': PlaceQuery('Robert Cheetham, Philadelphia'), # European Addresses: 'london_pieces': PlaceQuery(address='31 Maiden Lane', city='London', country='UK'), 'london_one_line': PlaceQuery('31 Maiden Lane, London WC2E', country='UK'), 'london_pieces_hyphenated': PlaceQuery(address='31-32 Maiden Lane', city='London', country='UK'), 'london_one_line_hyphenated': PlaceQuery('31-32 Maiden Lane London WC2E', country='UK'), # Oceanian Addresses: 'karori': PlaceQuery('102 Karori Road Karori Wellington', country='NZ'), } if BING_MAPS_API_KEY is not None: bing_settings = dict(api_key=BING_MAPS_API_KEY) self.g_bing = Geocoder( [['omgeo.services.Bing', { 'settings': bing_settings }]]) if MAPQUEST_API_KEY is not None: mapquest_settings = dict(api_key=MAPQUEST_API_KEY) self.g_mapquest = Geocoder( [['omgeo.services.MapQuest', { 'settings': mapquest_settings }]]) self.g_mapquest_ssl = Geocoder([[ 'omgeo.services.MapQuestSSL', { 'settings': mapquest_settings } ]]) if PELIAS_API_KEY is not None: pelias_settings = dict(api_key=PELIAS_API_KEY) self.g_pelias = Geocoder( [['omgeo.services.Pelias', { 'settings': pelias_settings }]]) if GOOGLE_API_KEY is not None: self.g_google = Geocoder([[ 'omgeo.services.Google', { 'settings': { 'api_key': GOOGLE_API_KEY } } ]]) self.g_google_wo_postprocess = Geocoder([[ 'omgeo.services.Google', { 'settings': { 'api_key': GOOGLE_API_KEY }, 'postprocessors': [] } ]]) #: main geocoder used for tests, using default APIs self.g = Geocoder() # geocoders using individual services self.g_esri_wgs = Geocoder([['omgeo.services.EsriWGS', {}]]) if ESRI_CLIENT_ID is not None and ESRI_CLIENT_SECRET is not None: self.g_esri_wgs_auth = Geocoder([[ 'omgeo.services.EsriWGS', { 'settings': { 'client_id': ESRI_CLIENT_ID, 'client_secret': ESRI_CLIENT_SECRET } } ]]) if MAPQUEST_API_KEY is not None: # MapQuest's open Nominatime API now also requires a key self.g_nom = Geocoder([['omgeo.services.Nominatim', {}]]) self.g_census = Geocoder([['omgeo.services.USCensus', {}]]) ESRI_WGS_LOCATOR_MAP = { 'PointAddress': 'rooftop', 'StreetAddress': 'interpolation', 'PostalExt': 'postal_specific', # accept ZIP+4 'Postal': 'postal' } ESRI_WGS_POSTPROCESSORS_POSTAL_OK = [ AttrExclude( ['USA.Postal'], 'locator' ), # accept postal from everywhere but US (need PostalExt) AttrFilter( ['PointAddress', 'StreetAddress', 'PostalExt', 'Postal'], 'locator_type'), AttrSorter( ['PointAddress', 'StreetAddress', 'PostalExt', 'Postal'], 'locator_type'), AttrRename('locator', ESRI_WGS_LOCATOR_MAP ), # after filter to avoid searching things we toss out UseHighScoreIfAtLeast(99.8), ScoreSorter(), GroupBy('match_addr'), GroupBy(('x', 'y')), ] GEOCODERS_POSTAL_OK = [[ 'omgeo.services.EsriWGS', { 'postprocessors': ESRI_WGS_POSTPROCESSORS_POSTAL_OK } ]] self.g_esri_wgs_postal_ok = Geocoder(GEOCODERS_POSTAL_OK) #: geocoder with fast timeout self.impatient_geocoder = Geocoder( [['omgeo.services.EsriWGS', { 'settings': { 'timeout': 0.001 } }]])
class EsriWGS(GeocodeService): """ Class to geocode using the `ESRI World Geocoding service <https://developers.arcgis.com/features/geocoding/>`_. This uses two endpoints -- one for single-line addresses, and one for multi-part addresses. An optional ``key`` parameter can be passed to the :class:`~omgeo.places.PlaceQuery` which will be passed as a ``magicKey`` to the find endpoint if using a single line address/text search. This allows omgeo to be used with the `Esri suggest endpoint <https://developers.arcgis.com/rest/geocode/api-reference/geocoding-suggest.htm>`_. .. warning:: Based on tests using the magicKey parameter, it is recommended that a viewbox not be used with in conjuction with the magicKey. Additionally, address/search text passed via the query may be ignored when using a magicKey. An optional ``for_storage`` flag can be passed to the :class:`~omgeo.places.PlaceQuery` which will cause ``findStorage=true`` to be passed to the find endpoint. The Esri terms of service requires this if the returned point will be stored in a database. If you pass this flag, you `must` set the client_id and client_secret settings. Settings used by the EsriWGS GeocodeService object may include: * client_id -- The Client ID used to access Esri services. * client_secret -- The Client Secret used to access Esri services. """ LOCATOR_MAP = { 'PointAddress': 'rooftop', 'StreetAddress': 'interpolation', 'PostalExt': 'postal_specific', # accept ZIP+4 'Postal': 'postal' } DEFAULT_PREPROCESSORS = [CancelIfPOBox()] DEFAULT_POSTPROCESSORS = [ AttrFilter( [ 'PointAddress', 'StreetAddress', # 'PostalExt', # 'Postal' ], 'locator_type'), # AttrExclude(['USA_Postal'], 'locator'), #accept postal from everywhere but US (need PostalExt) AttrSorter( [ 'PointAddress', 'StreetAddress', # 'PostalExt', # 'Postal' ], 'locator_type'), AttrRename( 'locator', LOCATOR_MAP), # after filter to avoid searching things we toss out UseHighScoreIfAtLeast(99.8), ScoreSorter(), GroupBy('match_addr'), GroupBy(('x', 'y')), ] _endpoint = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer' def __init__(self, preprocessors=None, postprocessors=None, settings=None): preprocessors = EsriWGS.DEFAULT_PREPROCESSORS if preprocessors is None else preprocessors postprocessors = EsriWGS.DEFAULT_POSTPROCESSORS if postprocessors is None else postprocessors settings = {} if settings is None else settings if 'client_id' in settings and 'client_secret' in settings: self._authenticated = True self._client_id = settings['client_id'] self._client_secret = settings['client_secret'] self._token = None elif 'client_id' not in settings and 'client_secret' not in settings: self._authenticated = False else: raise Exception( 'Must specify both client_id and client_secret to use authentication' ) GeocodeService.__init__(self, preprocessors, postprocessors, settings) def _geocode(self, pq): """ :arg PlaceQuery pq: PlaceQuery object to use for geocoding :returns: list of location Candidates """ #: List of desired output fields #: See `ESRI docs <https://developers.arcgis.com/rest/geocode/api-reference/geocoding-geocode-addresses.htm>_` for details outFields = ( 'Loc_name', # 'Shape', 'Score', 'Match_addr', # based on address standards for the country # 'Address', # returned by default # 'Country' # 3-digit ISO 3166-1 code for a country. Example: Canada = "CAN" # 'Admin', # 'DepAdmin', # 'SubAdmin', # 'Locality', # 'Postal', # 'PostalExt', 'Addr_type', # 'Type', # 'Rank', 'AddNum', 'StPreDir', 'StPreType', 'StName', 'StType', 'StDir', # 'Side', # 'AddNumFrom', # 'AddNumTo', # 'AddBldg', 'City', 'Subregion', 'Region', 'Postal', 'Country', # 'Ymax', # 'Ymin', # 'Xmin', # 'Xmax', # 'X', # 'Y', 'DisplayX', 'DisplayY', # 'LangCode', # 'Status', ) outFields = ','.join(outFields) query = dict( f='json', # default HTML. Other options are JSON and KMZ. outFields=outFields, # outSR=WKID, defaults to 4326 maxLocations=20, # default 1; max is 20 ) # Postal-code only searches work in the single-line but not multipart geocoder # Remember that with the default postprocessors, postcode-level results will be eliminated if pq.query == pq.address == '' and pq.postal != '': pq.query = pq.postal if pq.query == '': # multipart query = dict( query, Address=pq. address, # commonly represents the house number and street name of a complete address Neighborhood=pq.neighborhood, City=pq.city, Subregion=pq.subregion, Region=pq.state, Postal=pq.postal, # PostalExt= CountryCode=pq. country, # full country name or ISO 3166-1 2- or 3-digit country code ) else: # single-line magic_key = pq.key if hasattr(pq, 'key') else '' query = dict( query, singleLine=pq. query, # This can be a street address, place name, postal code, or POI. sourceCountry=pq. country, # full country name or ISO 3166-1 2- or 3-digit country code ) if magic_key: query[ 'magicKey'] = magic_key # This is a lookup key returned from the suggest endpoint. if pq.bounded and pq.viewbox is not None: query = dict(query, searchExtent=pq.viewbox.to_esri_wgs_json()) if self._authenticated: if self._token is None or self._token_expiration < datetime.utcnow( ): expiration = timedelta(hours=2) self._token = self.get_token(expiration) self._token_expiration = datetime.utcnow() + expiration query['token'] = self._token if getattr(pq, 'for_storage', False): query['forStorage'] = 'true' endpoint = self._endpoint + '/findAddressCandidates' response_obj = self._get_json_obj(endpoint, query) returned_candidates = [] # this will be the list returned try: locations = response_obj['candidates'] for location in locations: c = Candidate() attributes = location['attributes'] c.match_addr = attributes['Match_addr'] c.locator = attributes['Loc_name'] c.locator_type = attributes['Addr_type'] c.score = attributes['Score'] c.x = attributes[ 'DisplayX'] # represents the actual location of the address. c.y = attributes['DisplayY'] c.wkid = response_obj['spatialReference']['wkid'] c.geoservice = self.__class__.__name__ # Optional address component fields. for in_key, out_key in [('City', 'match_city'), ('Subregion', 'match_subregion'), ('Region', 'match_region'), ('Postal', 'match_postal'), ('Country', 'match_country')]: setattr(c, out_key, attributes.get(in_key, '')) setattr(c, 'match_streetaddr', self._street_addr_from_response(attributes)) returned_candidates.append(c) except KeyError: pass return returned_candidates def _street_addr_from_response(self, attributes): """Construct a street address (no city, region, etc.) from a geocoder response. :param attributes: A dict of address attributes as returned by the Esri geocoder. """ # The exact ordering of the address component fields that should be # used to reconstruct the full street address is not specified in the # Esri documentation, but the examples imply that it is this. ordered_fields = [ 'AddNum', 'StPreDir', 'StPreType', 'StName', 'StType', 'StDir' ] result = [] for field in ordered_fields: result.append(attributes.get(field, '')) if any(result): return ' '.join([s for s in result if s]) # Filter out empty strings. else: return '' def get_token(self, expires=None): """ :param expires: The time until the returned token expires. Must be an instance of :class:`datetime.timedelta`. If not specified, the token will expire in 2 hours. :returns: A token suitable for use with the Esri geocoding API """ endpoint = 'https://www.arcgis.com/sharing/rest/oauth2/token/' query = { 'client_id': self._client_id, 'client_secret': self._client_secret, 'grant_type': 'client_credentials' } if expires is not None: if not isinstance(expires, timedelta): raise Exception( 'If expires is provided it must be a timedelta instance') query['expiration'] = int(expires.total_seconds() / 60) response_obj = self._get_json_obj(endpoint, query, is_post=True) return response_obj['access_token']