Repository: docker/docker-py

Issue:
Issue with port option in 2.4.0 version
Hi,
I update to the 2.4 today and i got issue with docker-compose when i try to add the following line to my configuration file (docker-compose.yml) : 
`ports:
            - "127.0.0.1:9292:9090"`

I got the following error:

`
ERROR: for ContainerName  expected string or buffer
Traceback (most recent call last):
  File "/usr/local/bin/docker-compose", line 11, in <module>
    sys.exit(main())
  File "/usr/local/lib/python2.7/dist-packages/compose/cli/main.py", line 68, in main
    command()
  File "/usr/local/lib/python2.7/dist-packages/compose/cli/main.py", line 118, in perform_command
    handler(command, command_options)
  File "/usr/local/lib/python2.7/dist-packages/compose/cli/main.py", line 926, in up
    scale_override=parse_scale_args(options['--scale']),
  File "/usr/local/lib/python2.7/dist-packages/compose/project.py", line 424, in up
    get_deps
  File "/usr/local/lib/python2.7/dist-packages/compose/parallel.py", line 69, in parallel_execute
    raise error_to_reraise
TypeError: expected string or buffer
`

I have no issue when i downgrade again to the 2.3 version of the package

To reproduce the issue, i use the following configuration ( it doesn't seem to depend on the image):
```
version: '2'

services :
    ContainerName:
        image: bae2d441e03a
        ports:
            - "127.0.0.1:9292:9090"
```

I run on Ubuntu 14.04.5 LTS with the following package:
```
docker==2.4.0
docker-compose==1.14.0
docker-pycreds==0.2.1
dockerpty==0.4.1
Python 2.7.6
Client:
 Version:      17.05.0-ce
 API version:  1.29
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:06:06 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.05.0-ce
 API version:  1.29 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:06:06 2017
 OS/Arch:      linux/amd64
 Experimental: false
```


Discussion:
I experienced a similar problem with docker-compose after upgrading docker-py to 2.4

Using both docker-compose 1.14 and 1.13 and the following configuration:


```
version: '3'

services:
  myservice:
    image: myimage
    ports:
      - "80:80"
```

I obtain the following error:

```
docker-compose up

ERROR: for myservice expected string or bytes-like object
Traceback (most recent call last):
  File "/usr/local/bin/docker-compose", line 11, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.5/dist-packages/compose/cli/main.py", line 68, in main
    command()
  File "/usr/local/lib/python3.5/dist-packages/compose/cli/main.py", line 118, in perform_command
    handler(command, command_options)
  File "/usr/local/lib/python3.5/dist-packages/compose/cli/main.py", line 926, in up
    scale_override=parse_scale_args(options['--scale']),
  File "/usr/local/lib/python3.5/dist-packages/compose/project.py", line 424, in up
    get_deps
  File "/usr/local/lib/python3.5/dist-packages/compose/parallel.py", line 69, in parallel_execute
    raise error_to_reraise
  File "/usr/local/lib/python3.5/dist-packages/compose/parallel.py", line 167, in producer
    result = func(obj)
  File "/usr/local/lib/python3.5/dist-packages/compose/project.py", line 410, in do
    rescale=rescale
  File "/usr/local/lib/python3.5/dist-packages/compose/service.py", line 460, in execute_convergence_plan
    self.show_scale_warnings(scale)
  File "/usr/local/lib/python3.5/dist-packages/compose/service.py", line 205, in show_scale_warnings
    if self.specifies_host_port() and desired_num > 1:
  File "/usr/local/lib/python3.5/dist-packages/compose/service.py", line 983, in specifies_host_port
    return any(has_host_port(binding) for binding in self.options.get('ports', []))
  File "/usr/local/lib/python3.5/dist-packages/compose/service.py", line 983, in <genexpr>
    return any(has_host_port(binding) for binding in self.options.get('ports', []))
  File "/usr/local/lib/python3.5/dist-packages/compose/service.py", line 963, in has_host_port
    _, external_bindings = split_port(binding)
  File "/usr/local/lib/python3.5/dist-packages/docker/utils/ports.py", line 57, in split_port
    match = PORT_SPEC.match(port)
TypeError: expected string or bytes-like object
```

Using compose 1.12 it still works


I added a couple of prints in split_port to confirm that compose calls the split_port function by passing a non-string argument:


```
def split_port(port):
    print(port)
    print(type(port))
    match = PORT_SPEC.match(port)
```

And this is the output:


```
80:80
<class 'str'>
80:80
<class 'str'>
ServicePort(target=80, published=80, protocol=None, mode=None, external_ip=None)
<class 'compose.config.types.ServicePort'>

ERROR: for myservice  expected string or bytes-like object
Traceback (most recent call last):
[...] (previous stack)
  File "/usr/local/lib/python3.5/dist-packages/docker/utils/ports.py", line 59, in split_port
    match = PORT_SPEC.match(port)
TypeError: expected string or bytes-like object
```

I think that the problem was introduced with this commit: 5dd91cd4aaa2e7cd8dde1dd316d53cab25ef9b78

Prior of this commit the cast to string of the input port (`parts = str(port).split(':')`) was robust enough to withstand this call from compose




Same error here
This is causing a pretty big issue on Circle CI right now
If you're on Arch and this bit you:
```
sudo pacman -U /var/cache/pacman/pkg/python-docker-2.3.0-1-any.pkg.tar.xz
```

assuming 2.3 is still in your pacman cache, confirmed the downgrade fixed it for me locally
I used 
```
pip install docker-compose docker==2.3.0
```
to avoid the error with docker-compose "ports" notations.
Yes same here
My work around:
```
pip uninstall docker
pip uninstall docker-compose

pip install docker==2.3.0
pip install docker-compose==1.14.0
```

Referencing the docker-compose issue as well:
* https://github.com/docker/compose/issues/4972


Resolving patch:
diff --git a/docker/utils/ports.py b/docker/utils/ports.py
--- a/docker/utils/ports.py
+++ b/docker/utils/ports.py
@@ -54,6 +54,7 @@ def port_range(start, end, proto, randomly_available_port=False):
 
 
 def split_port(port):
+    port = str(port)
     match = PORT_SPEC.match(port)
     if match is None:
         _raise_invalid_port(port)


Test patch:
diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py
--- a/tests/unit/utils_test.py
+++ b/tests/unit/utils_test.py
@@ -587,6 +587,9 @@ def test_with_no_container_port(self):
     def test_split_port_empty_string(self):
         self.assertRaises(ValueError, lambda: split_port(""))
 
+    def test_split_port_non_string(self):
+        assert split_port(1243) == (['1243'], None)
+
     def test_build_port_bindings_with_one_port(self):
         port_bindings = build_port_bindings(["127.0.0.1:1000:1000"])
         self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")])