Repository: jddeal/python-cmr

Issue:
Support more result formats
Adding in support for the other MIME Types wouldn't be a bad idea or difficult. Both granule and collection queries support the following:
- json (default)
- xml
- echo10
- iso
- iso19115
- dif
- dif10
- csv
- atom
- kml
- native

Collection queries also support these formats:
- opendata
- umm_json
- umm_json_vX_Y (X/Y are numbers the user provides in the string)

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index 5a0e177..91f2785 100644
--- a/README.rst
+++ b/README.rst
@@ -140,3 +140,31 @@ To inspect and retreive results from the API, the following methods are availabl
 
     # retrieve all the granules possible for the query
     >>> granules = api.get_all()  # this is a shortcut for api.get(api.hits())
+
+
+By default the responses will return as json and be accessible as a list of python dictionaries.
+Other formats can be specified before making the request:
+
+::
+
+    >>> granules = api.format("echo10").get(100)
+
+The following formats are supported for both granule and collection queries:
+
+* json (default)
+* xml
+* echo10
+* iso
+* iso19115
+* csv
+* atom
+* kml
+* native
+
+Collection queries also support the following formats:
+
+* dif
+* dif10
+* opendata
+* umm_json
+* umm_json_vX_Y (ex: umm_json_v1_9)
diff --git a/cmr/queries.py b/cmr/queries.py
index ca2a024..bfc187c 100644
--- a/cmr/queries.py
+++ b/cmr/queries.py
@@ -8,6 +8,7 @@ except ImportError:
     from urllib import pathname2url as quote
 
 from datetime import datetime
+from re import search
 from requests import get, exceptions
 
 CMR_OPS = "https://cmr.earthdata.nasa.gov/search/"
@@ -21,6 +22,11 @@ class Query(object):
 
     _base_url = ""
     _route = ""
+    _format = "json"
+    _valid_formats_regex = [
+        "json", "xml", "echo10", "iso", "iso19115",
+        "csv", "atom", "kml", "native"
+    ]
 
     def __init__(self, route, mode=CMR_OPS):
         self.params = {}
@@ -50,11 +56,15 @@ class Query(object):
             except exceptions.HTTPError as ex:
                 raise RuntimeError(ex.response.text)
 
-            latest = response.json()['feed']['entry']
+            if self._format == "json":
+                latest = response.json()['feed']['entry']
+            else:
+                latest = response.text
+
             if len(latest) == 0:
                 break
 
-            results = results + latest
+            results.append(latest)
             page += 1
 
         return results
@@ -89,6 +99,26 @@ class Query(object):
 
         return self.get(self.hits())
 
+    def format(self, output_format="json"):
+        """
+        Sets the format for the returned results.
+
+        :param output_format: Preferred output format
+        :returns: Query instance
+        """
+
+        if not output_format:
+            output_format = "json"
+
+        # check requested format against the valid format regex's
+        for _format in self._valid_formats_regex:
+            if search(_format, output_format):
+                self._format = output_format
+                return self
+
+        # if we got here, we didn't find a matching format
+        raise ValueError("Unsupported format '{}'".format(output_format))
+
     def online_only(self, online_only):
         """
         Only match granules that are listed online and not available for download.
@@ -367,7 +397,12 @@ class Query(object):
 
         options_as_string = "&".join(formatted_options)
 
-        return "{}?{}&{}".format(self._base_url, params_as_string, options_as_string)
+        return "{}.{}?{}&{}".format(
+            self._base_url,
+            self._format,
+            params_as_string,
+            options_as_string
+        )
 
     def _valid_state(self):
         """
@@ -398,7 +433,7 @@ class GranuleQuery(Query):
     """
 
     def __init__(self, mode=CMR_OPS):
-        Query.__init__(self, "granules.json", mode)
+        Query.__init__(self, "granules", mode)
 
     def orbit_number(self, orbit1, orbit2=None):
         """"
@@ -524,7 +559,11 @@ class CollectionQuery(Query):
     """
 
     def __init__(self, mode=CMR_OPS):
-        Query.__init__(self, "collections.json", mode)
+        Query.__init__(self, "collections", mode)
+
+        self._valid_formats_regex.extend([
+            "dif", "dif10", "opendata", "umm_json", "umm_json_v[0-9]_[0-9]"
+        ])
 
     def archive_center(self, center):
         """


Test patch:
diff --git a/tests/test_collection.py b/tests/test_collection.py
index 1272c24..3241e79 100644
--- a/tests/test_collection.py
+++ b/tests/test_collection.py
@@ -17,3 +17,22 @@ class TestCollectionClass(unittest.TestCase):
 
         self.assertIn("keyword", query.params)
         self.assertEqual(query.params["keyword"], "AST_*")
+    
+    def test_valid_formats(self):
+        query = CollectionQuery()
+        formats = [
+            "json", "xml", "echo10", "iso", "iso19115",
+            "csv", "atom", "kml", "native", "dif", "dif10",
+            "opendata", "umm_json", "umm_json_v1_1" "umm_json_v1_9"]
+
+        for _format in formats:
+            query.format(_format)
+            self.assertEqual(query._format, _format)
+    
+    def test_invalid_format(self):
+        query = CollectionQuery()
+
+        with self.assertRaises(ValueError):
+            query.format("invalid")
+            query.format("jsonn")
+            query.format("iso19116")
diff --git a/tests/test_granule.py b/tests/test_granule.py
index 6c31372..11b481b 100644
--- a/tests/test_granule.py
+++ b/tests/test_granule.py
@@ -349,3 +349,19 @@ class TestGranuleClass(unittest.TestCase):
     def test_invalid_mode_constructor(self):
         with self.assertRaises(ValueError):
             query = GranuleQuery(None)
+    
+    def test_valid_formats(self):
+        query = GranuleQuery()
+        formats = ["json", "xml", "echo10", "iso", "iso19115", "csv", "atom", "kml", "native"]
+
+        for _format in formats:
+            query.format(_format)
+            self.assertEqual(query._format, _format)
+    
+    def test_invalid_format(self):
+        query = GranuleQuery()
+
+        with self.assertRaises(ValueError):
+            query.format("invalid")
+            query.format("jsonn")
+            query.format("iso19116")