Repository: docker/docker-py

Issue:
client.networks.containers() is always empty
```
>>> import docker
>>> d = docker.from_env()
>>> [n.name for n in d.networks.list()]
['pedlintegrationtests_default', 'bridge', 'host', 'none']
>>> [n.containers for n in d.networks.list()]
[[], [], [], []]
```

```
$ docker network inspect pedlintegrationtests_default | jq '.[0].Containers | length'
4
```

```
$ pip freeze | grep docker && python --version && docker version
docker==2.5.1
docker-compose==1.16.1
docker-pycreds==0.2.1
dockerpty==0.4.1
nvidia-docker-compose==0.1.4
Python 3.5.2
Client:
 Version:      17.09.0-ce
 API version:  1.32
 Go version:   go1.8.3
 Git commit:   afdb6d4
 Built:        Tue Sep 26 22:42:18 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.09.0-ce
 API version:  1.32 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   afdb6d4
 Built:        Tue Sep 26 22:40:56 2017
 OS/Arch:      linux/amd64
 Experimental: false
```


Discussion:
Does it work if you call `reload()` on the `Network` object?
@shin- Yep, seems like that works:

```
$ python
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import docker
>>> d = docker.from_env()
>>> networks = d.networks.list()
>>> [n.name for n in networks]
['bridge', 'host', 'none', 'pedlintegrationtests_default']
>>> [n.containers for n in networks]
[[], [], [], []]
>>> target = networks[3]
>>> target.containers
[]
>>> target.reload()
>>> target.containers
[<Container: b82bb19d44>, <Container: a6256ad04f>]
>>> [c.id for c in target.containers]
['b82bb19d44f18577ff7629e2bf9f350f3c3c581640e2a238ae710d84cb51d2ca', 'a6256ad04ff04f2ae4ab7d5e67398824a1eb7eba7baf6622519049e67629545c']
>>>
$ docker network inspect pedlintegrationtests_default | jq '.[0].Containers | length'
2
```
Yeah, what is happening is that the `list()` method generates the network objects from the `/networks` endpoint, which isn't as exhaustive as the `/networks/<id>` endpoint, resulting in some fields not being populated until you call `reload`. Obviously there's a speed tradeoff here, but maybe we could add a `greedy` parameter to the `list` method to indicate whether we want to load the complete object or not.
Right, makes sense. FWIW, the current behavior is very confusing, because silently returning `[]` makes it hard to distinguish this situation (incomplete object) from "complete object but network has no connected containers". e.g., if `containers` was `None` (or there was a `containers()` method that threw an exception in this situation), that would at least make it more obvious.
@shin- How does one perform the same reload when using the `APIClient`? I'm seeing something similar when running `docker.client.APIClient.networks()`. Each network's `containers` key is an empty dict.
@shin- It looks, like you said, that hitting the endpoint for a specific ID (via `docker.client.APIClient.inspect_network()`) will get you the details for the containers.

Just adding a friendly reminder here to give the `APIClient` a little love if/when this enhancement is implemented.
Hey guys,

I could not reproduce this behavior though.

```
>>> import docker
>>> d = docker.from_env()
>>> [n.name for n in d.networks.list()]
[u'none', u'host', u'apptest_default', u'ansibleplayground_default', u'bridge']
>>> [n.containers for n in d.networks.list()]
[[], [], [], [], [<Container: 62d7e7819d>]]
```
`pip freeze | grep docker && python --version && docker version`
```
-e git+https://github.com/ChrsMark/docker-py.git@bb148380e1ea92e26c8e4dc783dd18b8a39506c0#egg=docker
docker-pycreds==0.2.1
Python 2.7.6
Client:
 Version:      17.03.1-ce
 API version:  1.27
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:10:36 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.1-ce
 API version:  1.27 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:10:36 2017
 OS/Arch:      linux/amd64
 Experimental: false
``` 

Resolving patch:
diff --git a/docker/models/networks.py b/docker/models/networks.py
--- a/docker/models/networks.py
+++ b/docker/models/networks.py
@@ -1,4 +1,5 @@
 from ..api import APIClient
+from ..utils import version_gte
 from .containers import Container
 from .resource import Model, Collection
 
@@ -153,7 +154,7 @@ def create(self, name, *args, **kwargs):
         resp = self.client.api.create_network(name, *args, **kwargs)
         return self.get(resp['Id'])
 
-    def get(self, network_id):
+    def get(self, network_id, *args, **kwargs):
         """
         Get a network by its ID.
 
@@ -175,7 +176,9 @@ def get(self, network_id):
                 If the server returns an error.
 
         """
-        return self.prepare_model(self.client.api.inspect_network(network_id))
+        return self.prepare_model(
+            self.client.api.inspect_network(network_id, *args, **kwargs)
+        )
 
     def list(self, *args, **kwargs):
         """
@@ -184,6 +187,13 @@ def list(self, *args, **kwargs):
         Args:
             names (:py:class:`list`): List of names to filter by.
             ids (:py:class:`list`): List of ids to filter by.
+            filters (dict): Filters to be processed on the network list.
+                Available filters:
+                - ``driver=[<driver-name>]`` Matches a network's driver.
+                - ``label=[<key>]`` or ``label=[<key>=<value>]``.
+                - ``type=["custom"|"builtin"]`` Filters networks by type.
+            greedy (bool): Fetch more details for each network individually.
+                You might want this to get the containers attached to them.
 
         Returns:
             (list of :py:class:`Network`) The networks on the server.
@@ -192,8 +202,13 @@ def list(self, *args, **kwargs):
             :py:class:`docker.errors.APIError`
                 If the server returns an error.
         """
+        greedy = kwargs.pop('greedy', False)
         resp = self.client.api.networks(*args, **kwargs)
-        return [self.prepare_model(item) for item in resp]
+        networks = [self.prepare_model(item) for item in resp]
+        if greedy and version_gte(self.client.api._version, '1.28'):
+            for net in networks:
+                net.reload()
+        return networks
 
     def prune(self, filters=None):
         self.client.api.prune_networks(filters=filters)


Test patch:
diff --git a/tests/integration/models_networks_test.py b/tests/integration/models_networks_test.py
--- a/tests/integration/models_networks_test.py
+++ b/tests/integration/models_networks_test.py
@@ -3,7 +3,7 @@
 from .base import BaseIntegrationTest, TEST_API_VERSION
 
 
-class ImageCollectionTest(BaseIntegrationTest):
+class NetworkCollectionTest(BaseIntegrationTest):
 
     def test_create(self):
         client = docker.from_env(version=TEST_API_VERSION)
@@ -47,7 +47,7 @@ def test_list_remove(self):
         assert network.id not in [n.id for n in client.networks.list()]
 
 
-class ImageTest(BaseIntegrationTest):
+class NetworkTest(BaseIntegrationTest):
 
     def test_connect_disconnect(self):
         client = docker.from_env(version=TEST_API_VERSION)
@@ -59,6 +59,12 @@ def test_connect_disconnect(self):
         network.connect(container)
         container.start()
         assert client.networks.get(network.id).containers == [container]
+        network_containers = list(
+            c
+            for net in client.networks.list(ids=[network.id], greedy=True)
+            for c in net.containers
+        )
+        assert network_containers == [container]
         network.disconnect(container)
         assert network.containers == []
         assert client.networks.get(network.id).containers == []
diff --git a/tests/unit/models_networks_test.py b/tests/unit/models_networks_test.py
--- a/tests/unit/models_networks_test.py
+++ b/tests/unit/models_networks_test.py
@@ -4,7 +4,7 @@
 from .fake_api_client import make_fake_client
 
 
-class ImageCollectionTest(unittest.TestCase):
+class NetworkCollectionTest(unittest.TestCase):
 
     def test_create(self):
         client = make_fake_client()
@@ -37,7 +37,7 @@ def test_list(self):
         assert client.api.networks.called_once_with(names=["foobar"])
 
 
-class ImageTest(unittest.TestCase):
+class NetworkTest(unittest.TestCase):
 
     def test_connect(self):
         client = make_fake_client()