From a4343c3eb384684b4bb973cd3e3a539972f81e35 Mon Sep 17 00:00:00 2001
From: Roger Ostrander <atiaxi@gmail.com>
Date: Sat, 3 Feb 2018 23:08:15 -0500
Subject: [PATCH 1/4] API call to blob_list with uri parameter now succeeds

---
 lbrynet/core/utils.py                 | 25 ++++++++++++++-
 lbrynet/daemon/Daemon.py              |  5 ++-
 lbrynet/tests/unit/core/test_utils.py | 44 +++++++++++++++++++++++++++
 3 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/lbrynet/core/utils.py b/lbrynet/core/utils.py
index 3fc867f4e..7d9683224 100644
--- a/lbrynet/core/utils.py
+++ b/lbrynet/core/utils.py
@@ -134,8 +134,31 @@ def get_sd_hash(stream_info):
         return None
     if isinstance(stream_info, ClaimDict):
         return stream_info.source_hash
-    return stream_info['stream']['source']['source']
+    path = ['claim', 'value', 'stream', 'source', 'source']
+    result = safe_dict_descend(stream_info, *path)
+    if not result:
+        log.warn("Unable to get sd_hash via path %s" % path)
+    return result
 
 
 def json_dumps_pretty(obj, **kwargs):
     return json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': '), **kwargs)
+
+
+def safe_dict_descend(source, *path):
+    """
+    For when you want to do something like
+    stream_info['claim']['value']['stream']['source']['source']"
+    but don't trust that every last one of those keys exists
+    """
+    cur_source = source
+    for path_entry in path:
+        try:
+            if path_entry not in cur_source:
+                return None
+        except TypeError:
+            # This happens if we try to keep going along a path that isn't
+            # a dictionary (see e.g. test_safe_dict_descend_typeerror)
+            return None
+        cur_source = cur_source[path_entry]
+    return cur_source
diff --git a/lbrynet/daemon/Daemon.py b/lbrynet/daemon/Daemon.py
index d19ce92b7..e65c76f9a 100644
--- a/lbrynet/daemon/Daemon.py
+++ b/lbrynet/daemon/Daemon.py
@@ -2881,7 +2881,10 @@ class Daemon(AuthJSONRPCServer):
         if uri:
             metadata = yield self._resolve_name(uri)
             sd_hash = utils.get_sd_hash(metadata)
-            blobs = yield self.get_blobs_for_sd_hash(sd_hash)
+            try:
+                blobs = yield self.get_blobs_for_sd_hash(sd_hash)
+            except NoSuchSDHash:
+                blobs = []
         elif stream_hash:
             try:
                 blobs = yield self.get_blobs_for_stream_hash(stream_hash)
diff --git a/lbrynet/tests/unit/core/test_utils.py b/lbrynet/tests/unit/core/test_utils.py
index b7eecea60..4d55736d4 100644
--- a/lbrynet/tests/unit/core/test_utils.py
+++ b/lbrynet/tests/unit/core/test_utils.py
@@ -31,3 +31,47 @@ class ObfuscationTest(unittest.TestCase):
         plain = '☃'
         obf = utils.obfuscate(plain)
         self.assertEqual(plain, utils.deobfuscate(obf))
+
+
+class SafeDictDescendTest(unittest.TestCase):
+
+    def test_safe_dict_descend_happy(self):
+        nested = {
+            'foo': {
+                'bar': {
+                    'baz': 3
+                }
+            }
+        }
+        self.assertEqual(
+            utils.safe_dict_descend(nested, 'foo', 'bar', 'baz'),
+            3
+        )
+
+    def test_safe_dict_descend_typeerror(self):
+        nested = {
+            'foo': {
+                'bar': 7
+            }
+        }
+        self.assertIsNone(utils.safe_dict_descend(nested, 'foo', 'bar', 'baz'))
+
+    def test_safe_dict_descend_missing(self):
+        nested = {
+            'foo': {
+                'barn': 7
+            }
+        }
+        self.assertIsNone(utils.safe_dict_descend(nested, 'foo', 'bar', 'baz'))
+
+    def test_empty_dict_doesnt_explode(self):
+        nested = {}
+        self.assertIsNone(utils.safe_dict_descend(nested, 'foo', 'bar', 'baz'))
+
+    def test_identity(self):
+        nested = {
+            'foo': {
+                'bar': 7
+            }
+        }
+        self.assertIs(nested, utils.safe_dict_descend(nested))

From 3fdde0f4ce08da229090639d173b9bf758bfeef4 Mon Sep 17 00:00:00 2001
From: Roger Ostrander <atiaxi@gmail.com>
Date: Sat, 3 Feb 2018 23:23:34 -0500
Subject: [PATCH 2/4] Added CHANGELOG entry for issue 895

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 647f1c7bc..238489da5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ at anytime.
   * Fixed handling decryption error for blobs encrypted with an invalid key
   * Fixed handling stream with no data blob (https://github.com/lbryio/lbry/issues/905)
   * Fixed fetching the external ip
+  * Fixed API call to blob_list with --uri parameter (https://github.com/lbryio/lbry/issues/895)
 
 ### Deprecated
   * `channel_list_mine`, replaced with `channel_list`

From d8e1738f27912ede75cf9895e117e910027e4031 Mon Sep 17 00:00:00 2001
From: Roger Ostrander <atiaxi@gmail.com>
Date: Tue, 6 Feb 2018 01:16:10 -0500
Subject: [PATCH 3/4] Code review changes (removed safe_dict_descend)

---
 lbrynet/core/utils.py                 | 28 +++----------
 lbrynet/tests/unit/core/test_utils.py | 57 +++++++++++----------------
 2 files changed, 28 insertions(+), 57 deletions(-)

diff --git a/lbrynet/core/utils.py b/lbrynet/core/utils.py
index 7d9683224..2d295f718 100644
--- a/lbrynet/core/utils.py
+++ b/lbrynet/core/utils.py
@@ -134,31 +134,15 @@ def get_sd_hash(stream_info):
         return None
     if isinstance(stream_info, ClaimDict):
         return stream_info.source_hash
-    path = ['claim', 'value', 'stream', 'source', 'source']
-    result = safe_dict_descend(stream_info, *path)
+    result = stream_info.get('claim', {}).\
+        get('value', {}).\
+        get('stream', {}).\
+        get('source', {}).\
+        get('source')
     if not result:
-        log.warn("Unable to get sd_hash via path %s" % path)
+        log.warn("Unable to get sd_hash")
     return result
 
 
 def json_dumps_pretty(obj, **kwargs):
     return json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': '), **kwargs)
-
-
-def safe_dict_descend(source, *path):
-    """
-    For when you want to do something like
-    stream_info['claim']['value']['stream']['source']['source']"
-    but don't trust that every last one of those keys exists
-    """
-    cur_source = source
-    for path_entry in path:
-        try:
-            if path_entry not in cur_source:
-                return None
-        except TypeError:
-            # This happens if we try to keep going along a path that isn't
-            # a dictionary (see e.g. test_safe_dict_descend_typeerror)
-            return None
-        cur_source = cur_source[path_entry]
-    return cur_source
diff --git a/lbrynet/tests/unit/core/test_utils.py b/lbrynet/tests/unit/core/test_utils.py
index 4d55736d4..5aa6cf8ae 100644
--- a/lbrynet/tests/unit/core/test_utils.py
+++ b/lbrynet/tests/unit/core/test_utils.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 from lbrynet.core import utils
+from lbryschema.claim import ClaimDict
 
 from twisted.trial import unittest
 
@@ -33,45 +34,31 @@ class ObfuscationTest(unittest.TestCase):
         self.assertEqual(plain, utils.deobfuscate(obf))
 
 
-class SafeDictDescendTest(unittest.TestCase):
+class SdHashTests(unittest.TestCase):
 
-    def test_safe_dict_descend_happy(self):
-        nested = {
-            'foo': {
-                'bar': {
-                    'baz': 3
+    def test_none_in_none_out(self):
+        self.assertIsNone(utils.get_sd_hash(None))
+
+    def test_ordinary_dict(self):
+        claim = {
+            "claim": {
+                "value": {
+                    "stream": {
+                        "source": {
+                            "source": "0123456789ABCDEF"
+                        }
+                    }
                 }
             }
         }
-        self.assertEqual(
-            utils.safe_dict_descend(nested, 'foo', 'bar', 'baz'),
-            3
-        )
+        self.assertEqual("0123456789ABCDEF", utils.get_sd_hash(claim))
 
-    def test_safe_dict_descend_typeerror(self):
-        nested = {
-            'foo': {
-                'bar': 7
+    def test_old_shape_fails(self):
+        claim = {
+            "stream": {
+                "source": {
+                    "source": "0123456789ABCDEF"
+                }
             }
         }
-        self.assertIsNone(utils.safe_dict_descend(nested, 'foo', 'bar', 'baz'))
-
-    def test_safe_dict_descend_missing(self):
-        nested = {
-            'foo': {
-                'barn': 7
-            }
-        }
-        self.assertIsNone(utils.safe_dict_descend(nested, 'foo', 'bar', 'baz'))
-
-    def test_empty_dict_doesnt_explode(self):
-        nested = {}
-        self.assertIsNone(utils.safe_dict_descend(nested, 'foo', 'bar', 'baz'))
-
-    def test_identity(self):
-        nested = {
-            'foo': {
-                'bar': 7
-            }
-        }
-        self.assertIs(nested, utils.safe_dict_descend(nested))
+        self.assertIsNone(utils.get_sd_hash(claim))

From 44df26abd34ef127b35470f77fd4174b9afc4e2a Mon Sep 17 00:00:00 2001
From: Roger Ostrander <atiaxi@gmail.com>
Date: Tue, 6 Feb 2018 01:32:10 -0500
Subject: [PATCH 4/4] Removing unused import

---
 lbrynet/tests/unit/core/test_utils.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lbrynet/tests/unit/core/test_utils.py b/lbrynet/tests/unit/core/test_utils.py
index 5aa6cf8ae..9575108be 100644
--- a/lbrynet/tests/unit/core/test_utils.py
+++ b/lbrynet/tests/unit/core/test_utils.py
@@ -1,6 +1,5 @@
 # -*- coding: utf-8 -*-
 from lbrynet.core import utils
-from lbryschema.claim import ClaimDict
 
 from twisted.trial import unittest