Repository: holoviz/panel

Issue:
Support reading from BytesIO/StringIO
I'm trying to write a GIF to memory and use panel to read from memory rather than storage.
`ValueError: GIF pane does not support objects of type 'BytesIO'`

```
from io import BytesIO
import imageio

memory = BytesIO()

save_fps = [..]

with imageio.get_writer(memory, mode='I', format='gif', fps=8) as writer:
    for fp in save_fps:
        image = imageio.imread(fp)
        writer.append_data(image)

memory.seek(0)
pn.pane.GIF(memory)
```


Discussion:
Extending it to support a file-like object should be reasonable; sure.  I think you'd need to add a case for that in panel.pane.image.ImageBase:_img(); it currently accepts a string that it interprets as a file path or as a URL.

Resolving patch:
diff --git a/panel/pane/image.py b/panel/pane/image.py
--- a/panel/pane/image.py
+++ b/panel/pane/image.py
@@ -39,24 +39,37 @@ class ImageBase(DivPaneBase):
     @classmethod
     def applies(cls, obj):
         imgtype = cls.imgtype
-        return (hasattr(obj, '_repr_'+imgtype+'_') or
-                (isinstance(obj, string_types) and
-                 ((os.path.isfile(obj) and obj.endswith('.'+imgtype)) or
-                  cls._is_url(obj))))
+        if hasattr(obj, '_repr_{}_'.format(imgtype)):
+            return True
+        if isinstance(obj, string_types):
+            if os.path.isfile(obj) and obj.endswith('.'+imgtype):
+                return True
+            if cls._is_url(obj):
+                return True
+        if hasattr(obj, 'read'):  # Check for file like object
+            return True
+        return False
 
     @classmethod
     def _is_url(cls, obj):
-        return (isinstance(obj, string_types) and
-                (obj.startswith('http://') or obj.startswith('https://'))
-                and obj.endswith('.'+cls.imgtype))
+        if isinstance(obj, string_types):
+            lower_string = obj.lower()
+            return (
+                lower_string.startswith('http://')
+                or lower_string.startswith('https://')
+            ) and lower_string.endswith('.'+cls.imgtype)
+        return False
 
     def _img(self):
-        if not isinstance(self.object, string_types):
-            return getattr(self.object, '_repr_'+self.imgtype+'_')()
-        elif os.path.isfile(self.object):
-            with open(self.object, 'rb') as f:
-                return f.read()
-        else:
+        if hasattr(self.object, '_repr_{}_'.format(self.imgtype)):
+            return getattr(self.object, '_repr_' + self.imgtype + '_')()
+        if isinstance(self.object, string_types):
+            if os.path.isfile(self.object):
+                with open(self.object, 'rb') as f:
+                    return f.read()
+        if hasattr(self.object, 'read'):
+            return self.object.read()
+        if self._is_url(self.object):
             import requests
             r = requests.request(url=self.object, method='GET')
             return r.content


Test patch:
diff --git a/panel/tests/pane/test_image.py b/panel/tests/pane/test_image.py
--- a/panel/tests/pane/test_image.py
+++ b/panel/tests/pane/test_image.py
@@ -1,8 +1,12 @@
 from __future__ import absolute_import, division, unicode_literals
 
+import sys
+import pytest
+
 from base64 import b64decode, b64encode
 
 from panel.pane import GIF, JPG, PNG, SVG
+from io import BytesIO, StringIO
 
 
 def test_svg_pane(document, comm):
@@ -53,3 +57,33 @@ def test_imgshape():
         w,h = t._imgshape(b64decode(twopixel[t.name.lower()]))
         assert w == 2
         assert h == 1
+
+def test_load_from_byteio():
+    """Testing a loading a image from a ByteIo"""
+    memory = BytesIO()
+    with open('panel/tests/test_data/logo.png', 'rb') as image_file:
+        memory.write(image_file.read())
+    memory.seek(0)
+    image_pane = PNG(memory)
+    image_data = image_pane._img()
+    assert b'PNG' in image_data
+
+@pytest.mark.skipif(sys.version_info.major <= 2, reason="Doesn't work with python 2")
+def test_load_from_stringio():
+    """Testing a loading a image from a StringIO"""
+    memory = StringIO()
+    with open('panel/tests/test_data/logo.png', 'rb') as image_file:
+        memory.write(str(image_file.read()))
+    memory.seek(0)
+    image_pane = PNG(memory)
+    image_data = image_pane._img()
+    assert 'PNG' in image_data
+
+def test_loading_a_image_from_url():
+    """Tests the loading of a image from a url"""
+    url = 'https://upload.wikimedia.org/wikipedia/commons/7/71/' \
+          '1700_CE_world_map.PNG'
+
+    image_pane = PNG(url)
+    image_data = image_pane._img()
+    assert b'PNG' in image_data
diff --git a/panel/tests/test_data/logo.png b/panel/tests/test_data/logo.png
new file mode 100644
Binary files /dev/null and b/panel/tests/test_data/logo.png differ