Repository: geopy/geopy
Pull request #287 (2018-05-06 14:34:25)

Update ArcGIS to latest API + add output_fields param
This PR supersedes #227.

Difference from the original PR:
- relaxed tests
- rebased on top of the current master
- moved `output_fields` param to the end of the signature (see https://github.com/geopy/geopy/pull/227#discussion_r174673414)


Patch:
diff --git a/geopy/geocoders/arcgis.py b/geopy/geocoders/arcgis.py
index 88e5fc5..79ef357 100644
--- a/geopy/geocoders/arcgis.py
+++ b/geopy/geocoders/arcgis.py
@@ -4,7 +4,7 @@
 
 import json
 from time import time
-from geopy.compat import urlencode, Request
+from geopy.compat import urlencode, Request, string_compare
 
 from geopy.geocoders.base import Geocoder, DEFAULT_SCHEME, DEFAULT_TIMEOUT, \
     DEFAULT_WKID, DEFAULT_FORMAT_STRING
@@ -111,7 +111,7 @@ class ArcGIS(Geocoder):  # pylint: disable=R0921,R0902,W0223
 
         self.api = (
             '%s://geocode.arcgis.com/arcgis/rest/services/'
-            'World/GeocodeServer/find' % self.scheme
+            'World/GeocodeServer/findAddressCandidates' % self.scheme
         )
         self.reverse_api = (
             '%s://geocode.arcgis.com/arcgis/rest/services/'
@@ -130,7 +130,7 @@ class ArcGIS(Geocoder):  # pylint: disable=R0921,R0902,W0223
         )
         return self._base_call_geocoder(request, timeout=timeout)
 
-    def geocode(self, query, exactly_one=True, timeout=None):
+    def geocode(self, query, exactly_one=True, timeout=None, out_fields=None):
         """
         Geocode a location query.
 
@@ -143,10 +143,26 @@ class ArcGIS(Geocoder):  # pylint: disable=R0921,R0902,W0223
             to respond before raising a :class:`geopy.exc.GeocoderTimedOut`
             exception. Set this only if you wish to override, on this call
             only, the value set during the geocoder's initialization.
+
+        :param out_fields: A list of output fields to be returned in the
+            attributes field of the raw data. This can be either a python
+            list/tuple of fields or a comma-separated string. See
+            https://developers.arcgis.com/rest/geocode/api-reference/geocoding-service-output.htm
+            for a list of supported output fields. If you want to return all
+            supported output fields, set ``out_fields="*"``.
+
+            .. versionadded:: 1.14.0
+        :type out_fields: str or iterable
+
         """
-        params = {'text': self.format_string % query, 'f': 'json'}
+        params = {'singleLine': self.format_string % query, 'f': 'json'}
         if exactly_one:
             params['maxLocations'] = 1
+        if out_fields is not None:
+            if isinstance(out_fields, string_compare):
+                params['outFields'] = out_fields
+            else:
+                params['outFields'] = ",".join(out_fields)
         url = "?".join((self.api, urlencode(params)))
         logger.debug("%s.geocode: %s", self.__class__.__name__, url)
         response = self._call_geocoder(url, timeout=timeout)
@@ -162,14 +178,14 @@ class ArcGIS(Geocoder):  # pylint: disable=R0921,R0902,W0223
             raise GeocoderServiceError(str(response['error']))
 
         # Success; convert from the ArcGIS JSON format.
-        if not len(response['locations']):
+        if not len(response['candidates']):
             return None
         geocoded = []
-        for resource in response['locations']:
-            geometry = resource['feature']['geometry']
+        for resource in response['candidates']:
+            geometry = resource['location']
             geocoded.append(
                 Location(
-                    resource['name'], (geometry['y'], geometry['x']), resource
+                    resource['address'], (geometry['y'], geometry['x']), resource
                 )
             )
         if exactly_one:


Test patch:
diff --git a/test/geocoders/arcgis.py b/test/geocoders/arcgis.py
index 9676472..980ad5c 100644
--- a/test/geocoders/arcgis.py
+++ b/test/geocoders/arcgis.py
@@ -59,6 +59,37 @@ class ArcGISTestCase(GeocoderTestBase):
             {"latitude": 39.916, "longitude": 116.390},
         )
 
+    def test_empty_response(self):
+        self.geocode_run(
+            {"query": "dksahdksahdjksahdoufydshf"},
+            {},
+            expect_failure=True
+        )
+
+    def test_geocode_with_out_fields_string(self):
+        """
+        ArcGIS.geocode with outFields string
+        """
+        result = self.geocode_run(
+            {"query": "Trafalgar Square, London",
+             "out_fields": "Country"},
+            {}
+        )
+        self.assertDictEqual(result.raw['attributes'],
+                             {'Country': 'GBR'})
+
+    def test_geocode_with_out_fields_list(self):
+        """
+        ArcGIS.geocode with outFields list
+        """
+        result = self.geocode_run(
+            {"query": "Trafalgar Square, London",
+             "out_fields": ["City", "Type"]},
+            {}
+        )
+        self.assertDictEqual(result.raw['attributes'],
+                             {'City': 'London', 'Type': 'Tourist Attraction'})
+
     def test_reverse_point(self):
         """
         ArcGIS.reverse using point