[Python-modules-commits] [python-docker] 02/13: Import python-docker_1.9.0.orig.tar.gz

Ondřej Nový onovy at moszumanska.debian.org
Thu Sep 8 12:38:53 UTC 2016


This is an automated email from the git hooks/post-receive script.

onovy pushed a commit to branch master
in repository python-docker.

commit 49556cb01423a89a6d2ce7a58b1f5cb64dde0ff3
Author: Ondřej Nový <onovy at debian.org>
Date:   Thu Sep 8 14:22:11 2016 +0200

    Import python-docker_1.9.0.orig.tar.gz
---
 MANIFEST.in                                |   2 +
 PKG-INFO                                   |   5 +-
 README.md                                  |   8 +-
 README.rst                                 |  37 +++++
 docker/__init__.py                         |   2 +-
 docker/api/build.py                        |  11 +-
 docker/api/container.py                    |  46 ++++-
 docker/api/daemon.py                       |   4 +-
 docker/api/image.py                        |   8 +-
 docker/api/network.py                      |  25 ++-
 docker/auth/auth.py                        |  15 +-
 docker/client.py                           |  67 ++++++--
 docker/constants.py                        |   9 +-
 docker/ssladapter/ssladapter.py            |   9 +
 docker/tls.py                              |   7 +-
 docker/transport/__init__.py               |   6 +
 docker/transport/npipeconn.py              |  80 +++++++++
 docker/transport/npipesocket.py            | 191 +++++++++++++++++++++
 docker/{unixconn => transport}/unixconn.py |   8 +-
 docker/unixconn/__init__.py                |   1 -
 docker/utils/utils.py                      | 258 +++++++++++++++++++++--------
 docker/version.py                          |   2 +-
 docker_py.egg-info/PKG-INFO                |   5 +-
 docker_py.egg-info/SOURCES.txt             |  15 +-
 docker_py.egg-info/pbr.json                |   1 -
 docker_py.egg-info/requires.txt            |   6 +
 requirements.txt                           |   2 +
 setup.py                                   |  17 +-
 test-requirements.txt                      |   4 +-
 tests/integration/api_test.py              |   2 +-
 tests/integration/build_test.py            |  28 ++++
 tests/integration/container_test.py        |  92 +++++++++-
 tests/integration/image_test.py            |  14 ++
 tests/integration/network_test.py          | 141 +++++++++++++++-
 tests/unit/api_test.py                     |  30 ++++
 tests/unit/auth_test.py                    |   5 +-
 tests/unit/client_test.py                  |  70 ++++++++
 tests/unit/container_test.py               | 129 ++++++++++++++-
 tests/unit/fake_api.py                     |   7 +
 tests/unit/image_test.py                   |   3 +-
 tests/unit/ssladapter_test.py              |  79 +++++++++
 tests/unit/testdata/certs/ca.pem           |   0
 tests/unit/testdata/certs/cert.pem         |   0
 tests/unit/testdata/certs/key.pem          |   0
 tests/unit/utils_test.py                   | 174 +++++++++----------
 45 files changed, 1391 insertions(+), 234 deletions(-)

diff --git a/MANIFEST.in b/MANIFEST.in
index ab64732..ee6cdbb 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,7 @@
 include test-requirements.txt
 include requirements.txt
 include README.md
+include README.rst
 include LICENSE
 recursive-include tests *.py
+recursive-include tests/unit/testdata *
diff --git a/PKG-INFO b/PKG-INFO
index fd5c3e7..79c1598 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: docker-py
-Version: 1.7.2
+Version: 1.9.0
 Summary: Python client for Docker.
 Home-page: https://github.com/docker/docker-py/
 Author: UNKNOWN
@@ -13,9 +13,12 @@ Classifier: Environment :: Other Environment
 Classifier: Intended Audience :: Developers
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
 Classifier: Topic :: Utilities
 Classifier: License :: OSI Approved :: Apache Software License
diff --git a/README.md b/README.md
index 385193a..bdec785 100644
--- a/README.md
+++ b/README.md
@@ -3,12 +3,12 @@ docker-py
 
 [![Build Status](https://travis-ci.org/docker/docker-py.png)](https://travis-ci.org/docker/docker-py)
 
-An API client for docker written in Python
+A Python library for the Docker Remote API. It does everything the `docker` command does, but from within Python – run containers, manage them, pull/push images, etc.
 
 Installation
 ------------
 
-Our latest stable is always available on PyPi.
+The latest stable version is always available on PyPi.
 
     pip install docker-py
 
@@ -17,8 +17,8 @@ Documentation
 
 [![Documentation Status](https://readthedocs.org/projects/docker-py/badge/?version=latest)](https://readthedocs.org/projects/docker-py/?badge=latest)
 
-Full documentation is hosted on [ReadTheDocs](http://docker-py.readthedocs.org/en/latest/). 
-Sources are available in the `docs/` directory.
+[Read the full documentation here](https://docker-py.readthedocs.io/en/latest/).
+The source is available in the `docs/` directory.
 
 
 License
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..757b82c
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,37 @@
+docker-py
+=========
+
+|Build Status|
+
+A Python library for the Docker Remote API. It does everything the
+``docker`` command does, but from within Python – run containers, manage
+them, pull/push images, etc.
+
+Installation
+------------
+
+The latest stable version is always available on PyPi.
+
+::
+
+    pip install docker-py
+
+Documentation
+-------------
+
+|Documentation Status|
+
+`Read the full documentation
+here <https://docker-py.readthedocs.io/en/latest/>`__. The source is
+available in the ``docs/`` directory.
+
+License
+-------
+
+Docker is licensed under the Apache License, Version 2.0. See LICENSE
+for full license text
+
+.. |Build Status| image:: https://travis-ci.org/docker/docker-py.png
+   :target: https://travis-ci.org/docker/docker-py
+.. |Documentation Status| image:: https://readthedocs.org/projects/docker-py/badge/?version=latest
+   :target: https://readthedocs.org/projects/docker-py/?badge=latest
diff --git a/docker/__init__.py b/docker/__init__.py
index 3844c81..84d0734 100644
--- a/docker/__init__.py
+++ b/docker/__init__.py
@@ -17,4 +17,4 @@ from .version import version, version_info
 __version__ = version
 __title__ = 'docker-py'
 
-from .client import Client, AutoVersionClient # flake8: noqa
+from .client import Client, AutoVersionClient, from_env # flake8: noqa
diff --git a/docker/api/build.py b/docker/api/build.py
index 6bfaba1..971a50e 100644
--- a/docker/api/build.py
+++ b/docker/api/build.py
@@ -17,11 +17,15 @@ class BuildApiMixin(object):
               nocache=False, rm=False, stream=False, timeout=None,
               custom_context=False, encoding=None, pull=False,
               forcerm=False, dockerfile=None, container_limits=None,
-              decode=False, buildargs=None):
+              decode=False, buildargs=None, gzip=False):
         remote = context = headers = None
         container_limits = container_limits or {}
         if path is None and fileobj is None:
             raise TypeError("Either path or fileobj needs to be provided.")
+        if gzip and encoding is not None:
+            raise errors.DockerException(
+                'Can not use custom encoding if gzip is enabled'
+            )
 
         for key in container_limits.keys():
             if key not in constants.CONTAINER_LIMITS_KEYS:
@@ -46,7 +50,10 @@ class BuildApiMixin(object):
             if os.path.exists(dockerignore):
                 with open(dockerignore, 'r') as f:
                     exclude = list(filter(bool, f.read().splitlines()))
-            context = utils.tar(path, exclude=exclude, dockerfile=dockerfile)
+            context = utils.tar(
+                path, exclude=exclude, dockerfile=dockerfile, gzip=gzip
+            )
+            encoding = 'gzip' if gzip else encoding
 
         if utils.compare_version('1.8', self._version) >= 0:
             stream = True
diff --git a/docker/api/container.py b/docker/api/container.py
index ceac173..9cc14db 100644
--- a/docker/api/container.py
+++ b/docker/api/container.py
@@ -40,13 +40,14 @@ class ContainerApiMixin(object):
 
     @utils.check_resource
     def commit(self, container, repository=None, tag=None, message=None,
-               author=None, conf=None):
+               author=None, changes=None, conf=None):
         params = {
             'container': container,
             'repo': repository,
             'tag': tag,
             'comment': message,
-            'author': author
+            'author': author,
+            'changes': changes
         }
         u = self._url("/commit")
         return self._result(self._post_json(u, data=conf, params=params),
@@ -186,6 +187,8 @@ class ContainerApiMixin(object):
         url = self._url("/containers/{0}/kill", container)
         params = {}
         if signal is not None:
+            if not isinstance(signal, six.string_types):
+                signal = int(signal)
             params['signal'] = signal
         res = self._post(url, params=params)
 
@@ -193,12 +196,14 @@ class ContainerApiMixin(object):
 
     @utils.check_resource
     def logs(self, container, stdout=True, stderr=True, stream=False,
-             timestamps=False, tail='all', since=None):
+             timestamps=False, tail='all', since=None, follow=None):
         if utils.compare_version('1.11', self._version) >= 0:
+            if follow is None:
+                follow = stream
             params = {'stderr': stderr and 1 or 0,
                       'stdout': stdout and 1 or 0,
                       'timestamps': timestamps and 1 or 0,
-                      'follow': stream and 1 or 0,
+                      'follow': follow and 1 or 0,
                       }
             if utils.compare_version('1.13', self._version) >= 0:
                 if tail != 'all' and (not isinstance(tail, int) or tail < 0):
@@ -396,6 +401,39 @@ class ContainerApiMixin(object):
         res = self._post(url)
         self._raise_for_status(res)
 
+    @utils.minimum_version('1.22')
+    @utils.check_resource
+    def update_container(
+        self, container, blkio_weight=None, cpu_period=None, cpu_quota=None,
+        cpu_shares=None, cpuset_cpus=None, cpuset_mems=None, mem_limit=None,
+        mem_reservation=None, memswap_limit=None, kernel_memory=None
+    ):
+        url = self._url('/containers/{0}/update', container)
+        data = {}
+        if blkio_weight:
+            data['BlkioWeight'] = blkio_weight
+        if cpu_period:
+            data['CpuPeriod'] = cpu_period
+        if cpu_shares:
+            data['CpuShares'] = cpu_shares
+        if cpu_quota:
+            data['CpuQuota'] = cpu_quota
+        if cpuset_cpus:
+            data['CpusetCpus'] = cpuset_cpus
+        if cpuset_mems:
+            data['CpusetMems'] = cpuset_mems
+        if mem_limit:
+            data['Memory'] = utils.parse_bytes(mem_limit)
+        if mem_reservation:
+            data['MemoryReservation'] = utils.parse_bytes(mem_reservation)
+        if memswap_limit:
+            data['MemorySwap'] = utils.parse_bytes(memswap_limit)
+        if kernel_memory:
+            data['KernelMemory'] = utils.parse_bytes(kernel_memory)
+
+        res = self._post_json(url, data=data)
+        return self._result(res, True)
+
     @utils.check_resource
     def wait(self, container, timeout=None):
         url = self._url("/containers/{0}/wait", container)
diff --git a/docker/api/daemon.py b/docker/api/daemon.py
index a149e5e..9ebe73c 100644
--- a/docker/api/daemon.py
+++ b/docker/api/daemon.py
@@ -49,8 +49,6 @@ class DaemonApiMixin(object):
         elif not self._auth_configs:
             self._auth_configs = auth.load_config()
 
-        registry = registry or auth.INDEX_URL
-
         authcfg = auth.resolve_authconfig(self._auth_configs, registry)
         # If we found an existing auth config for this registry and username
         # combination, we can return it immediately unless reauth is requested.
@@ -67,7 +65,7 @@ class DaemonApiMixin(object):
 
         response = self._post_json(self._url('/auth'), data=req_data)
         if response.status_code == 200:
-            self._auth_configs[registry] = req_data
+            self._auth_configs[registry or auth.INDEX_NAME] = req_data
         return self._result(response, json=True)
 
     def ping(self):
diff --git a/docker/api/image.py b/docker/api/image.py
index 8493b38..3e66347 100644
--- a/docker/api/image.py
+++ b/docker/api/image.py
@@ -148,7 +148,7 @@ class ImageApiMixin(object):
         self._raise_for_status(res)
 
     def pull(self, repository, tag=None, stream=False,
-             insecure_registry=False, auth_config=None):
+             insecure_registry=False, auth_config=None, decode=False):
         if insecure_registry:
             warnings.warn(
                 INSECURE_REGISTRY_DEPRECATION_WARNING.format('pull()'),
@@ -200,12 +200,12 @@ class ImageApiMixin(object):
         self._raise_for_status(response)
 
         if stream:
-            return self._stream_helper(response)
+            return self._stream_helper(response, decode=decode)
 
         return self._result(response)
 
     def push(self, repository, tag=None, stream=False,
-             insecure_registry=False):
+             insecure_registry=False, decode=False):
         if insecure_registry:
             warnings.warn(
                 INSECURE_REGISTRY_DEPRECATION_WARNING.format('push()'),
@@ -241,7 +241,7 @@ class ImageApiMixin(object):
         self._raise_for_status(response)
 
         if stream:
-            return self._stream_helper(response)
+            return self._stream_helper(response, decode=decode)
 
         return self._result(response)
 
diff --git a/docker/api/network.py b/docker/api/network.py
index d9a6128..a35f0a4 100644
--- a/docker/api/network.py
+++ b/docker/api/network.py
@@ -1,6 +1,8 @@
 import json
 
-from ..utils import check_resource, minimum_version, normalize_links
+from ..errors import InvalidVersion
+from ..utils import check_resource, minimum_version
+from ..utils import version_lt
 
 
 class NetworkApiMixin(object):
@@ -19,7 +21,8 @@ class NetworkApiMixin(object):
         return self._result(res, json=True)
 
     @minimum_version('1.21')
-    def create_network(self, name, driver=None, options=None, ipam=None):
+    def create_network(self, name, driver=None, options=None, ipam=None,
+                       check_duplicate=None, internal=False):
         if options is not None and not isinstance(options, dict):
             raise TypeError('options must be a dictionary')
 
@@ -28,7 +31,15 @@ class NetworkApiMixin(object):
             'Driver': driver,
             'Options': options,
             'IPAM': ipam,
+            'CheckDuplicate': check_duplicate
         }
+
+        if internal:
+            if version_lt(self._version, '1.22'):
+                raise InvalidVersion('Internal networks are not '
+                                     'supported in API version < 1.22')
+            data['Internal'] = True
+
         url = self._url("/networks/create")
         res = self._post_json(url, data=data)
         return self._result(res, json=True)
@@ -48,14 +59,16 @@ class NetworkApiMixin(object):
     @check_resource
     @minimum_version('1.21')
     def connect_container_to_network(self, container, net_id,
+                                     ipv4_address=None, ipv6_address=None,
                                      aliases=None, links=None):
         data = {
             "Container": container,
-            "EndpointConfig": {
-                "Aliases": aliases,
-                "Links": normalize_links(links) if links else None,
-            },
+            "EndpointConfig": self.create_endpoint_config(
+                aliases=aliases, links=links, ipv4_address=ipv4_address,
+                ipv6_address=ipv6_address
+            ),
         }
+
         url = self._url("/networks/{0}/connect", net_id)
         res = self._post_json(url, data=data)
         self._raise_for_status(res)
diff --git a/docker/auth/auth.py b/docker/auth/auth.py
index eedb794..d23e6f3 100644
--- a/docker/auth/auth.py
+++ b/docker/auth/auth.py
@@ -117,7 +117,7 @@ def parse_auth(entries, raise_on_error=False):
 
     conf = {}
     for registry, entry in six.iteritems(entries):
-        if not (isinstance(entry, dict) and 'auth' in entry):
+        if not isinstance(entry, dict):
             log.debug(
                 'Config entry for key {0} is not auth config'.format(registry)
             )
@@ -130,6 +130,16 @@ def parse_auth(entries, raise_on_error=False):
                     'Invalid configuration for registry {0}'.format(registry)
                 )
             return {}
+        if 'auth' not in entry:
+            # Starting with engine v1.11 (API 1.23), an empty dictionary is
+            # a valid value in the auths config.
+            # https://github.com/docker/compose/issues/3265
+            log.debug(
+                'Auth data for {0} is absent. Client might be using a '
+                'credentials store instead.'
+            )
+            return {}
+
         username, password = decode_auth(entry['auth'])
         log.debug(
             'Found entry (registry={0}, username={1})'
@@ -189,6 +199,9 @@ def load_config(config_path=None):
             if data.get('HttpHeaders'):
                 log.debug("Found 'HttpHeaders' section")
                 res.update({'HttpHeaders': data['HttpHeaders']})
+            if data.get('credsStore'):
+                log.debug("Found 'credsStore' section")
+                res.update({'credsStore': data['credsStore']})
             if res:
                 return res
             else:
diff --git a/docker/client.py b/docker/client.py
index 7d1f7c4..81e9de9 100644
--- a/docker/client.py
+++ b/docker/client.py
@@ -14,7 +14,6 @@
 
 import json
 import struct
-import sys
 
 import requests
 import requests.exceptions
@@ -26,10 +25,18 @@ from . import api
 from . import constants
 from . import errors
 from .auth import auth
-from .unixconn import unixconn
 from .ssladapter import ssladapter
-from .utils import utils, check_resource, update_headers
 from .tls import TLSConfig
+from .transport import UnixAdapter
+from .utils import utils, check_resource, update_headers, kwargs_from_env
+try:
+    from .transport import NpipeAdapter
+except ImportError:
+    pass
+
+
+def from_env(**kwargs):
+    return Client.from_env(**kwargs)
 
 
 class Client(
@@ -42,7 +49,8 @@ class Client(
         api.VolumeApiMixin,
         api.NetworkApiMixin):
     def __init__(self, base_url=None, version=None,
-                 timeout=constants.DEFAULT_TIMEOUT_SECONDS, tls=False):
+                 timeout=constants.DEFAULT_TIMEOUT_SECONDS, tls=False,
+                 user_agent=constants.DEFAULT_USER_AGENT):
         super(Client, self).__init__()
 
         if tls and not base_url:
@@ -52,14 +60,30 @@ class Client(
 
         self.base_url = base_url
         self.timeout = timeout
+        self.headers['User-Agent'] = user_agent
 
         self._auth_configs = auth.load_config()
 
-        base_url = utils.parse_host(base_url, sys.platform, tls=bool(tls))
+        base_url = utils.parse_host(
+            base_url, constants.IS_WINDOWS_PLATFORM, tls=bool(tls)
+        )
         if base_url.startswith('http+unix://'):
-            self._custom_adapter = unixconn.UnixAdapter(base_url, timeout)
+            self._custom_adapter = UnixAdapter(base_url, timeout)
             self.mount('http+docker://', self._custom_adapter)
             self.base_url = 'http+docker://localunixsocket'
+        elif base_url.startswith('npipe://'):
+            if not constants.IS_WINDOWS_PLATFORM:
+                raise errors.DockerException(
+                    'The npipe:// protocol is only supported on Windows'
+                )
+            try:
+                self._custom_adapter = NpipeAdapter(base_url, timeout)
+            except NameError:
+                raise errors.DockerException(
+                    'Install pypiwin32 package to enable npipe:// support'
+                )
+            self.mount('http+docker://', self._custom_adapter)
+            self.base_url = 'http+docker://localnpipe'
         else:
             # Use SSLAdapter for the ability to specify SSL version
             if isinstance(tls, TLSConfig):
@@ -84,6 +108,10 @@ class Client(
                 )
             )
 
+    @classmethod
+    def from_env(cls, **kwargs):
+        return cls(**kwargs_from_env(**kwargs))
+
     def _retrieve_server_version(self):
         try:
             return self.version(api_version=False)["ApiVersion"]
@@ -283,14 +311,29 @@ class Client(
         """ Depending on the combination of python version and whether we're
         connecting over http or https, we might need to access _sock, which
         may or may not exist; or we may need to just settimeout on socket
-         itself, which also may or may not have settimeout on it.
+        itself, which also may or may not have settimeout on it. To avoid
+        missing the correct one, we try both.
 
-        To avoid missing the correct one, we try both.
+        We also do not want to set the timeout if it is already disabled, as
+        you run the risk of changing a socket that was non-blocking to
+        blocking, for example when using gevent.
         """
-        if hasattr(socket, "settimeout"):
-            socket.settimeout(None)
-        if hasattr(socket, "_sock") and hasattr(socket._sock, "settimeout"):
-            socket._sock.settimeout(None)
+        sockets = [socket, getattr(socket, '_sock', None)]
+
+        for s in sockets:
+            if not hasattr(s, 'settimeout'):
+                continue
+
+            timeout = -1
+
+            if hasattr(s, 'gettimeout'):
+                timeout = s.gettimeout()
+
+            # Don't change the timeout if it is already disabled.
+            if timeout is None or timeout == 0.0:
+                continue
+
+            s.settimeout(None)
 
     def _get_result(self, container, stream, res):
         cont = self.inspect_container(container)
diff --git a/docker/constants.py b/docker/constants.py
index 0627ba0..904d50e 100644
--- a/docker/constants.py
+++ b/docker/constants.py
@@ -1,4 +1,7 @@
-DEFAULT_DOCKER_API_VERSION = '1.21'
+import sys
+from .version import version
+
+DEFAULT_DOCKER_API_VERSION = '1.22'
 DEFAULT_TIMEOUT_SECONDS = 60
 STREAM_HEADER_SIZE_BYTES = 8
 CONTAINER_LIMITS_KEYS = [
@@ -8,3 +11,7 @@ CONTAINER_LIMITS_KEYS = [
 INSECURE_REGISTRY_DEPRECATION_WARNING = \
     'The `insecure_registry` argument to {} ' \
     'is deprecated and non-functional. Please remove it.'
+
+IS_WINDOWS_PLATFORM = (sys.platform == 'win32')
+
+DEFAULT_USER_AGENT = "docker-py/{0}".format(version)
diff --git a/docker/ssladapter/ssladapter.py b/docker/ssladapter/ssladapter.py
index 5b43aa2..e17dfad 100644
--- a/docker/ssladapter/ssladapter.py
+++ b/docker/ssladapter/ssladapter.py
@@ -2,6 +2,8 @@
       https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests/
       https://github.com/kennethreitz/requests/pull/799
 """
+import sys
+
 from distutils.version import StrictVersion
 from requests.adapters import HTTPAdapter
 
@@ -10,8 +12,15 @@ try:
 except ImportError:
     import urllib3
 
+
 PoolManager = urllib3.poolmanager.PoolManager
 
+# Monkey-patching match_hostname with a version that supports
+# IP-address checking. Not necessary for Python 3.5 and above
+if sys.version_info[0] < 3 or sys.version_info[1] < 5:
+    from backports.ssl_match_hostname import match_hostname
+    urllib3.connection.match_hostname = match_hostname
+
 
 class SSLAdapter(HTTPAdapter):
     '''An HTTPS Transport Adapter that uses an arbitrary SSL version.'''
diff --git a/docker/tls.py b/docker/tls.py
index 83b0ff7..7abfa60 100644
--- a/docker/tls.py
+++ b/docker/tls.py
@@ -1,4 +1,5 @@
 import os
+import ssl
 
 from . import errors
 from .ssladapter import ssladapter
@@ -19,10 +20,14 @@ class TLSConfig(object):
         # here, but also disable any public/default CA pool verification by
         # leaving tls_verify=False
 
-        self.ssl_version = ssl_version
         self.assert_hostname = assert_hostname
         self.assert_fingerprint = assert_fingerprint
 
+        # TLS v1.0 seems to be the safest default; SSLv23 fails in mysterious
+        # ways: https://github.com/docker/docker-py/issues/963
+
+        self.ssl_version = ssl_version or ssl.PROTOCOL_TLSv1
+
         # "tls" and "tls_verify" must have both or neither cert/key files
         # In either case, Alert the user when both are expected, but any are
         # missing.
diff --git a/docker/transport/__init__.py b/docker/transport/__init__.py
new file mode 100644
index 0000000..d647483
--- /dev/null
+++ b/docker/transport/__init__.py
@@ -0,0 +1,6 @@
+# flake8: noqa
+from .unixconn import UnixAdapter
+try:
+    from .npipeconn import NpipeAdapter
+except ImportError:
+    pass
\ No newline at end of file
diff --git a/docker/transport/npipeconn.py b/docker/transport/npipeconn.py
new file mode 100644
index 0000000..736ddf6
--- /dev/null
+++ b/docker/transport/npipeconn.py
@@ -0,0 +1,80 @@
+import six
+import requests.adapters
+
+from .npipesocket import NpipeSocket
+
+if six.PY3:
+    import http.client as httplib
+else:
+    import httplib
+
+try:
+    import requests.packages.urllib3 as urllib3
+except ImportError:
+    import urllib3
+
+
+RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer
+
+
+class NpipeHTTPConnection(httplib.HTTPConnection, object):
+    def __init__(self, npipe_path, timeout=60):
+        super(NpipeHTTPConnection, self).__init__(
+            'localhost', timeout=timeout
+        )
+        self.npipe_path = npipe_path
+        self.timeout = timeout
+
+    def connect(self):
+        sock = NpipeSocket()
+        sock.settimeout(self.timeout)
+        sock.connect(self.npipe_path)
+        self.sock = sock
+
+
+class NpipeHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
+    def __init__(self, npipe_path, timeout=60):
+        super(NpipeHTTPConnectionPool, self).__init__(
+            'localhost', timeout=timeout
+        )
+        self.npipe_path = npipe_path
+        self.timeout = timeout
+
+    def _new_conn(self):
+        return NpipeHTTPConnection(
+            self.npipe_path, self.timeout
+        )
+
+
+class NpipeAdapter(requests.adapters.HTTPAdapter):
+    def __init__(self, base_url, timeout=60):
+        self.npipe_path = base_url.replace('npipe://', '')
+        self.timeout = timeout
+        self.pools = RecentlyUsedContainer(
+            10, dispose_func=lambda p: p.close()
+        )
+        super(NpipeAdapter, self).__init__()
+
+    def get_connection(self, url, proxies=None):
+        with self.pools.lock:
+            pool = self.pools.get(url)
+            if pool:
+                return pool
+
+            pool = NpipeHTTPConnectionPool(
+                self.npipe_path, self.timeout
+            )
+            self.pools[url] = pool
+
+        return pool
+
+    def request_url(self, request, proxies):
+        # The select_proxy utility in requests errors out when the provided URL
+        # doesn't have a hostname, like is the case when using a UNIX socket.
+        # Since proxies are an irrelevant notion in the case of UNIX sockets
+        # anyway, we simply return the path URL directly.
+        # See also: https://github.com/docker/docker-py/issues/811
+        return request.path_url
+
+    def close(self):
+        self.pools.clear()
diff --git a/docker/transport/npipesocket.py b/docker/transport/npipesocket.py
new file mode 100644
index 0000000..35418ef
--- /dev/null
+++ b/docker/transport/npipesocket.py
@@ -0,0 +1,191 @@
+import functools
+import io
+
+import win32file
+import win32pipe
+
+cSECURITY_SQOS_PRESENT = 0x100000
+cSECURITY_ANONYMOUS = 0
+cPIPE_READMODE_MESSAGE = 2
+
+
+def check_closed(f):
+    @functools.wraps(f)
+    def wrapped(self, *args, **kwargs):
+        if self._closed:
+            raise RuntimeError(
+                'Can not reuse socket after connection was closed.'
+            )
+        return f(self, *args, **kwargs)
+    return wrapped
+
+
+class NpipeSocket(object):
+    """ Partial implementation of the socket API over windows named pipes.
+        This implementation is only designed to be used as a client socket,
+        and server-specific methods (bind, listen, accept...) are not
+        implemented.
+    """
+    def __init__(self, handle=None):
+        self._timeout = win32pipe.NMPWAIT_USE_DEFAULT_WAIT
+        self._handle = handle
+        self._closed = False
+
+    def accept(self):
+        raise NotImplementedError()
+
+    def bind(self, address):
+        raise NotImplementedError()
+
+    def close(self):
+        self._handle.Close()
+        self._closed = True
+
+    @check_closed
+    def connect(self, address):
+        win32pipe.WaitNamedPipe(address, self._timeout)
+        handle = win32file.CreateFile(
+            address,
+            win32file.GENERIC_READ | win32file.GENERIC_WRITE,
+            0,
+            None,
+            win32file.OPEN_EXISTING,
+            cSECURITY_ANONYMOUS | cSECURITY_SQOS_PRESENT,
+            0
+        )
+        self.flags = win32pipe.GetNamedPipeInfo(handle)[0]
+
+        self._handle = handle
+        self._address = address
+
+    @check_closed
+    def connect_ex(self, address):
+        return self.connect(address)
+
+    @check_closed
+    def detach(self):
+        self._closed = True
+        return self._handle
+
+    @check_closed
+    def dup(self):
+        return NpipeSocket(self._handle)
+
+    @check_closed
+    def fileno(self):
+        return int(self._handle)
+
+    def getpeername(self):
+        return self._address
+
+    def getsockname(self):
+        return self._address
+
+    def getsockopt(self, level, optname, buflen=None):
+        raise NotImplementedError()
+
+    def ioctl(self, control, option):
+        raise NotImplementedError()
+
+    def listen(self, backlog):
+        raise NotImplementedError()
+
+    def makefile(self, mode=None, bufsize=None):
+        if mode.strip('b') != 'r':
+            raise NotImplementedError()
+        rawio = NpipeFileIOBase(self)
+        if bufsize is None:
+            bufsize = io.DEFAULT_BUFFER_SIZE
+        return io.BufferedReader(rawio, buffer_size=bufsize)
+
+    @check_closed
+    def recv(self, bufsize, flags=0):
+        err, data = win32file.ReadFile(self._handle, bufsize)
+        return data
+
+    @check_closed
+    def recvfrom(self, bufsize, flags=0):
+        data = self.recv(bufsize, flags)
+        return (data, self._address)
+
+    @check_closed
+    def recvfrom_into(self, buf, nbytes=0, flags=0):
+        return self.recv_into(buf, nbytes, flags), self._address
+
+    @check_closed
+    def recv_into(self, buf, nbytes=0):
+        readbuf = buf
+        if not isinstance(buf, memoryview):
+            readbuf = memoryview(buf)
+
+        err, data = win32file.ReadFile(
+            self._handle,
+            readbuf[:nbytes] if nbytes else readbuf
+        )
+        return len(data)
+
+    @check_closed
+    def send(self, string, flags=0):
+        err, nbytes = win32file.WriteFile(self._handle, string)
+        return nbytes
+
+    @check_closed
+    def sendall(self, string, flags=0):
+        return self.send(string, flags)
+
+    @check_closed
+    def sendto(self, string, address):
+        self.connect(address)
+        return self.send(string)
+
+    def setblocking(self, flag):
+        if flag:
+            return self.settimeout(None)
+        return self.settimeout(0)
+
+    def settimeout(self, value):
+        if value is None:
+            self._timeout = win32pipe.NMPWAIT_NOWAIT
+        elif not isinstance(value, (float, int)) or value < 0:
+            raise ValueError('Timeout value out of range')
+        elif value == 0:
+            self._timeout = win32pipe.NMPWAIT_USE_DEFAULT_WAIT
+        else:
+            self._timeout = value
+
+    def gettimeout(self):
+        return self._timeout
+
+    def setsockopt(self, level, optname, value):
+        raise NotImplementedError()
+
+    @check_closed
+    def shutdown(self, how):
+        return self.close()
+
+
+class NpipeFileIOBase(io.RawIOBase):
+    def __init__(self, npipe_socket):
+        self.sock = npipe_socket
+
+    def close(self):
+        super(NpipeFileIOBase, self).close()
+        self.sock = None
+
+    def fileno(self):
+        return self.sock.fileno()
+
+    def isatty(self):
+        return False
+
+    def readable(self):
+        return True
+
+    def readinto(self, buf):
+        return self.sock.recv_into(buf)
+
+    def seekable(self):
+        return False
+
+    def writable(self):
+        return False
diff --git a/docker/unixconn/unixconn.py b/docker/transport/unixconn.py
similarity index 93%
rename from docker/unixconn/unixconn.py
rename to docker/transport/unixconn.py
index d7e249e..f4d83ef 100644
--- a/docker/unixconn/unixconn.py
+++ b/docker/transport/unixconn.py
@@ -30,7 +30,9 @@ RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer
 
 class UnixHTTPConnection(httplib.HTTPConnection, object):
     def __init__(self, base_url, unix_socket, timeout=60):
-        httplib.HTTPConnection.__init__(self, 'localhost', timeout=timeout)
+        super(UnixHTTPConnection, self).__init__(
+            'localhost', timeout=timeout
+        )
         self.base_url = base_url
         self.unix_socket = unix_socket
         self.timeout = timeout
@@ -44,8 +46,8 @@ class UnixHTTPConnection(httplib.HTTPConnection, object):
 
 class UnixHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
     def __init__(self, base_url, socket_path, timeout=60):
-        urllib3.connectionpool.HTTPConnectionPool.__init__(
-            self, 'localhost', timeout=timeout
+        super(UnixHTTPConnectionPool, self).__init__(
+            'localhost', timeout=timeout
         )
         self.base_url = base_url
         self.socket_path = socket_path
diff --git a/docker/unixconn/__init__.py b/docker/unixconn/__init__.py
deleted file mode 100644
index 53711fc..0000000
--- a/docker/unixconn/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .unixconn import UnixAdapter  # flake8: noqa
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
index 6fcf037..2ef8ef0 100644
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -91,10 +91,10 @@ def decode_json_header(header):
     return json.loads(data)
 
 
-def tar(path, exclude=None, dockerfile=None, fileobj=None):
+def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):
     if not fileobj:
         fileobj = tempfile.NamedTemporaryFile()
-    t = tarfile.open(mode='w', fileobj=fileobj)
+    t = tarfile.open(mode='w:gz' if gzip else 'w', fileobj=fileobj)
 
     root = os.path.abspath(path)
... 1714 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-docker.git



More information about the Python-modules-commits mailing list