Repository: bird-house/birdy

Issue:
Retrieve the data if no converter is found
## Description

At the moment, if no converter is available, `get(asobj=True)` returns the link. I suggest that instead we return the output of `retrieveData()`.



Comments:

Patch:
diff --git a/birdy/client/converters.py b/birdy/client/converters.py
index 8e6b3fc..f3e9471 100644
--- a/birdy/client/converters.py
+++ b/birdy/client/converters.py
@@ -17,7 +17,7 @@ else:
 class BaseConverter(object):
     mimetype = None
     extensions = []
-    priority = 1
+    priority = None
     nested = False
 
     def __init__(self, output=None, path=None, verify=True):
@@ -41,11 +41,20 @@ class BaseConverter(object):
 
     @property
     def file(self):
+        """Return output Path object. Download from server if """
         if self._file is None:
             self.output.writeToDisk(path=self.path, verify=self.verify)
             self._file = Path(self.output.filePath)
         return self._file
 
+    @property
+    def data(self):
+        """Return the data from the remote output in memory."""
+        if self._file is not None:
+            return self.file.read_bytes()
+        else:
+            return self.output.retrieveData()
+
     def check_dependencies(self):
         pass
 
@@ -62,13 +71,26 @@ class BaseConverter(object):
             raise type(e)(message.format(self.__class__.__name__, name))
 
     def convert(self):
-        return self.file.read_text(encoding='utf8')
+        """To be subclassed"""
+        raise NotImplementedError
+
+
+class GenericConverter(BaseConverter):
+    priority = 0
+
+    def convert(self):
+        """Return raw bytes memory representation."""
+        return self.data
 
 
 class TextConverter(BaseConverter):
     mimetype = "text/plain"
-    extensions = ['txt', 'csv']
+    extensions = ['txt', 'csv', 'md', 'rst']
+    priority = 1
 
+    def convert(self):
+        """Return text content."""
+        return self.file.read_text(encoding='utf8')
 
 # class HTMLConverter(BaseConverter):
 #     """Create HTML cell in notebook."""
@@ -89,6 +111,7 @@ class TextConverter(BaseConverter):
 class JSONConverter(BaseConverter):
     mimetype = "application/json"
     extensions = ['json', ]
+    priority = 1
 
     def convert(self):
         """
@@ -103,6 +126,7 @@ class JSONConverter(BaseConverter):
 class GeoJSONConverter(BaseConverter):
     mimetype = "application/vnd.geo+json"
     extensions = ['geojson', ]
+    priority = 1
 
     def check_dependencies(self):
         self._check_import("geojson")
@@ -117,6 +141,7 @@ class MetalinkConverter(BaseConverter):
     mimetype = "application/metalink+xml; version=3.0"
     extensions = ['metalink', ]
     nested = True
+    priority = 1
 
     def check_dependencies(self):
         self._check_import("metalink.download")
@@ -135,6 +160,7 @@ class Meta4Converter(MetalinkConverter):
 class Netcdf4Converter(BaseConverter):
     mimetype = "application/x-netcdf"
     extensions = ['nc', ]
+    priority = 1
 
     def check_dependencies(self):
         self._check_import("netCDF4")
@@ -180,6 +206,7 @@ class XarrayConverter(BaseConverter):
 
 class ShpFionaConverter(BaseConverter):
     mimetype = "application/x-zipped-shp"
+    priority = 1
 
     def check_dependencies(self):
         self._check_import("fiona")
@@ -193,6 +220,7 @@ class ShpFionaConverter(BaseConverter):
 
 class ShpOgrConverter(BaseConverter):
     mimetype = "application/x-zipped-shp"
+    priority = 1
 
     def check_dependencies(self):
         self._check_import("ogr", package="osgeo")
@@ -207,6 +235,7 @@ class ShpOgrConverter(BaseConverter):
 class ImageConverter(BaseConverter):
     mimetype = 'image/png'
     extensions = ['png', ]
+    priority = 1
 
     def check_dependencies(self):
         return nb.is_notebook()
@@ -220,6 +249,7 @@ class ZipConverter(BaseConverter):
     mimetype = 'application/zip'
     extensions = ['zip', ]
     nested = True
+    priority = 1
 
     def convert(self):
         import zipfile
@@ -231,7 +261,7 @@ class ZipConverter(BaseConverter):
 def _find_converter(mimetype=None, extension=None, converters=()):
     """Return a list of compatible converters ordered by priority.
     """
-    select = []
+    select = [GenericConverter]
     for obj in converters:
         if (mimetype == obj.mimetype) or (extension in obj.extensions):
             select.append(obj)
@@ -269,13 +299,16 @@ def convert(output, path, converters=None, verify=True):
     Returns
     -------
     objs
-      Python object or path to file if no converter was found.
+      Python object or file's content as bytes.
     """
+    # Get all converters
     if converters is None:
         converters = all_subclasses(BaseConverter)
 
+    # Find converters matching mime type or extension.
     convs = find_converter(output, converters)
 
+    # Try converters in order of priority
     for cls in convs:
         try:
             converter = cls(output, path=path, verify=verify)
@@ -287,13 +320,6 @@ def convert(output, path, converters=None, verify=True):
         except (ImportError, NotImplementedError):
             pass
 
-    if isinstance(output, Output):
-        warnings.warn(UserWarning("No converter was found for {}".format(output.identifier)))
-        return output.reference
-    else:
-        warnings.warn(UserWarning("No converter was found for {}".format(output)))
-        return output
-
 
 def all_subclasses(cls):
     """Return all subclasses of a class."""


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index 1f77f00..f9881d0 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -150,11 +150,10 @@ def test_asobj(wps):
         out = resp.get(asobj=True)
         assert 'URL' in out.output
 
-    # If the converter is missing, we should still get the reference.
-    with pytest.warns(UserWarning):
-        resp._converters = []
-        out = resp.get(asobj=True)
-        assert out.output.startswith('http://')
+    # If the converter is missing, we should still get the data as bytes.
+    resp._converters = []
+    out = resp.get(asobj=True)
+    assert isinstance(out.output, bytes)
 
 
 @pytest.mark.online
@@ -316,13 +315,14 @@ def test_zipconverter():
     assert len(ob.splitlines()) == 2
 
 
-@pytest.mark.skip("jpeg not supported yet")
 def test_jpeg_imageconverter():
+    "Since the format is not supported, bytes will be returned."
     fn = tempfile.mktemp(suffix='.jpeg')
     with open(fn, 'w') as f:
         f.write('jpeg.jpg JPEG 1x1 1x1+0+0 8-bit Grayscale Gray 256c 107B 0.000u 0:00.000')
 
-    converters.convert(fn, path='/tmp')
+    b = converters.convert(fn, path='/tmp')
+    assert isinstance(b, bytes)
 
 
 class TestIsEmbedded():