Repository: wandb/wandb

Issue:
Attempting to access summary that is numpy array fails with 'Api' object has no attribute 'download_url'
**Describe the bug**
When using the API, attempting to access a `summary` element that is a numpy array fails with error `'Api' object has no attribute 'download_url'`. This is because the internal API has a `download_urls` function, but no `download_url`. You need to create a function `download_url` in `wandb/apis/internal.py`.

**To Reproduce**
Steps to reproduce the behavior:
```
api = wandb.Api()
# Select a run
run = list(api.runs("xxx"))[0]
# Access an element of the summary that is a numpy array
el = run.summary.the_name


c:\users\zanta\anaconda3\envs\py3\lib\site-packages\wandb\old\summary.py in download_h5(run_id, entity, project, out_dir)
    339 def download_h5(run_id, entity=None, project=None, out_dir=None):
    340     api = Api()
--> 341     meta = api.download_url(project or api.settings(
    342         "project"), DEEP_SUMMARY_FNAME, entity=entity or api.settings("entity"), run=run_id)
    343     if meta and 'md5' in meta and meta['md5'] is not None:

AttributeError: 'Api' object has no attribute 'download_url'
```

**Expected behavior**
Should download the numpy array.

**Operating System**
 - OS: Windows, Python 3.8, '0.10.12'



Discussion:
Hey @patrickmineault 
I tried reproducing the ticket.

## Observation
```python
import wandb
api = wandb.Api()
runs = list(api.runs('ariG23498/errors'))
run = runs[0]
```
Now that I have the run object I tried doing a `run.summary`. I noticed that it is not a numpy array but a dictionary.
```json
{
'_timestamp': 1606879101,
'acc': 0.8307052029904851,
'loss': 3.847122651685902,
'_step': 99,
'val_acc': 0.5345184272244818,
'_runtime': 1,
'val_loss': 3.154548747386185
}
```
After visualizing the dictionary I tried accessing the `loss`
```python
summary = run.summary
loss = summary['loss']
```

Link to the project: https://wandb.ai/arig23498/errors

Let us know if the isuue persists. :smile: 
This only happens when the saved data contains a numpy array that is sufficiently large - scalars don't trigger the bug. Minimalist example:

```
import numpy as np
import wandb

wandb.init(project="test", 
           config={})

results = {'losses': np.arange(100)}

wandb.run.summary.update(results)
```

Wait a little bit, then run:

```
api = wandb.Api()
runs = list(api.runs('test'))
run = runs[0]
print(run.summary)
print(run.summary.losses)


~/anaconda3/envs/py3/lib/python3.8/site-packages/wandb/old/summary.py in download_h5(run_id, entity, project, out_dir)
    339 def download_h5(run_id, entity=None, project=None, out_dir=None):
    340     api = Api()
--> 341     meta = api.download_url(project or api.settings(
    342         "project"), DEEP_SUMMARY_FNAME, entity=entity or api.settings("entity"), run=run_id)
    343     if meta and 'md5' in meta and meta['md5'] is not None:

AttributeError: 'Api' object has no attribute 'download_url'
```

A public summary that contains a large enough array to trigger the bug is located at `pmin/test/2plca8g1`.

Resolving patch:
diff --git a/wandb/apis/internal.py b/wandb/apis/internal.py
--- a/wandb/apis/internal.py
+++ b/wandb/apis/internal.py
@@ -70,6 +70,9 @@ def set_setting(self, *args, **kwargs):
     def parse_slug(self, *args, **kwargs):
         return self.api.parse_slug(*args, **kwargs)
 
+    def download_url(self, *args, **kwargs):
+        return self.api.download_url(*args, **kwargs)
+
     def download_urls(self, *args, **kwargs):
         return self.api.download_urls(*args, **kwargs)
 
diff --git a/wandb/old/summary.py b/wandb/old/summary.py
--- a/wandb/old/summary.py
+++ b/wandb/old/summary.py
@@ -26,7 +26,6 @@
 
 DEEP_SUMMARY_FNAME = 'wandb.h5'
 H5_TYPES = ("numpy.ndarray", "tensorflow.Tensor", "torch.Tensor")
-
 h5py = util.get_module("h5py")
 np = util.get_module("numpy")
 
@@ -38,13 +37,17 @@ class SummarySubDict(object):
     """
 
     def __init__(self, root=None, path=()):
+        self._path = tuple(path)
         if root is None:
             self._root = self
+            self._json_dict = {}
         else:
             self._root = root
-        self._path = tuple(path)
+            json_dict = root._json_dict
+            for k in path:
+                json_dict = json_dict[k]
+            self._json_dict = json_dict
         self._dict = {}
-        self._json_dict = {}
 
         # We use this to track which keys the user has set explicitly
         # so that we don't automatically overwrite them when we update


Test patch:
diff --git a/tests/test_summary.py b/tests/test_summary.py
new file mode 100644
--- /dev/null
+++ b/tests/test_summary.py
@@ -0,0 +1,14 @@
+import pytest
+import wandb
+
+
+@pytest.fixture
+def api(runner):
+    return wandb.Api()
+
+
+def test_nested_summary(api, mock_server):
+    run = api.runs("test/test")[0]
+    summary_dict = {"a": {"b": {"c": 0.9}}}
+    summary = wandb.old.summary.Summary(run, summary_dict)
+    assert summary["a"]["b"]["c"] == 0.9