From 231042a5202291ada6e1c2cd2e05d214ba3f5af2 Mon Sep 17 00:00:00 2001 From: Evgeniy L Date: Fri, 28 Mar 2014 22:46:18 +0400 Subject: [PATCH 1/2] Create scope for docker client specific errors With more granular and specific exceptions user will be able to handle errors properly. Also changed raising of Exception to more specific TypeError which python raises in case of wrong arguments. --- docker/auth/auth.py | 15 +++++++++------ docker/client.py | 2 +- docker/errors.py | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 docker/errors.py diff --git a/docker/auth/auth.py b/docker/auth/auth.py index d62705f0..871996de 100644 --- a/docker/auth/auth.py +++ b/docker/auth/auth.py @@ -20,6 +20,7 @@ import os import six from ..utils import utils +from docker import errors INDEX_URL = 'https://index.docker.io/v1/' DOCKER_CONFIG_FILENAME = '.dockercfg' @@ -45,18 +46,19 @@ def expand_registry_url(hostname): def resolve_repository_name(repo_name): if '://' in repo_name: - raise ValueError('Repository name cannot contain a ' - 'scheme ({0})'.format(repo_name)) + raise errors.InvalidRepository( + 'Repository name cannot contain a scheme ({0})'.format(repo_name)) parts = repo_name.split('/', 1) if '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost': # This is a docker index repo (ex: foo/bar or ubuntu) return INDEX_URL, repo_name if len(parts) < 2: - raise ValueError('Invalid repository name ({0})'.format(repo_name)) + raise errors.InvalidRepository( + 'Invalid repository name ({0})'.format(repo_name)) if 'index.docker.io' in parts[0]: - raise ValueError('Invalid repository name,' - 'try "{0}" instead'.format(parts[1])) + raise errors.InvalidRepository( + 'Invalid repository name, try "{0}" instead'.format(parts[1])) return expand_registry_url(parts[0]), parts[1] @@ -147,7 +149,8 @@ def load_config(root=None): data.append(line.strip().split(' = ')[1]) if len(data) < 2: # Not enough data - raise Exception('Invalid or empty configuration file!') + raise errors.InvalidConfigFile( + 'Invalid or empty configuration file!') username, password = decode_auth(data[0]) conf[INDEX_URL] = { diff --git a/docker/client.py b/docker/client.py index 5dfa889d..8a635438 100644 --- a/docker/client.py +++ b/docker/client.py @@ -341,7 +341,7 @@ class Client(requests.Session): nocache=False, rm=False, stream=False, timeout=None): remote = context = headers = None if path is None and fileobj is None: - raise Exception("Either path or fileobj needs to be provided.") + raise TypeError("Either path or fileobj needs to be provided.") if fileobj is not None: context = utils.mkbuildcontext(fileobj) diff --git a/docker/errors.py b/docker/errors.py new file mode 100644 index 00000000..a462f93d --- /dev/null +++ b/docker/errors.py @@ -0,0 +1,24 @@ +# Copyright 2014 dotCloud inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class DockerException(Exception): + pass + + +class InvalidRepository(DockerException): + pass + + +class InvalidConfigFile(DockerException): + pass From 18d4db09ecc79cef49830fa2c28c5e99dd02be42 Mon Sep 17 00:00:00 2001 From: Evgeniy L Date: Mon, 7 Apr 2014 13:31:13 +0400 Subject: [PATCH 2/2] Moved APIError exception to docker.errors module. --- docker/__init__.py | 2 +- docker/client.py | 38 ++------------------------------------ docker/errors.py | 37 +++++++++++++++++++++++++++++++++++++ tests/integration_test.py | 8 ++++---- 4 files changed, 44 insertions(+), 41 deletions(-) diff --git a/docker/__init__.py b/docker/__init__.py index 5388e728..e10a5761 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -15,4 +15,4 @@ __title__ = 'docker-py' __version__ = '0.3.0' -from .client import Client, APIError # flake8: noqa +from .client import Client # flake8: noqa diff --git a/docker/client.py b/docker/client.py index 8a635438..813902d8 100644 --- a/docker/client.py +++ b/docker/client.py @@ -24,6 +24,7 @@ import six from .auth import auth from .unixconn import unixconn from .utils import utils +from docker import errors if not six.PY3: import websocket @@ -33,41 +34,6 @@ DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 -class APIError(requests.exceptions.HTTPError): - def __init__(self, message, response, explanation=None): - # requests 1.2 supports response as a keyword argument, but - # requests 1.1 doesn't - super(APIError, self).__init__(message) - self.response = response - - self.explanation = explanation - - if self.explanation is None and response.content: - self.explanation = response.content.strip() - - def __str__(self): - message = super(APIError, self).__str__() - - if self.is_client_error(): - message = '%s Client Error: %s' % ( - self.response.status_code, self.response.reason) - - elif self.is_server_error(): - message = '%s Server Error: %s' % ( - self.response.status_code, self.response.reason) - - if self.explanation: - message = '%s ("%s")' % (message, self.explanation) - - return message - - def is_client_error(self): - return 400 <= self.response.status_code < 500 - - def is_server_error(self): - return 500 <= self.response.status_code < 600 - - class Client(requests.Session): def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION, timeout=DEFAULT_TIMEOUT_SECONDS): @@ -112,7 +78,7 @@ class Client(requests.Session): try: response.raise_for_status() except requests.exceptions.HTTPError as e: - raise APIError(e, response, explanation=explanation) + raise errors.APIError(e, response, explanation=explanation) def _result(self, response, json=False, binary=False): assert not (json and binary) diff --git a/docker/errors.py b/docker/errors.py index a462f93d..9aad700d 100644 --- a/docker/errors.py +++ b/docker/errors.py @@ -11,6 +11,43 @@ # See the License for the specific language governing permissions and # limitations under the License. +import requests + + +class APIError(requests.exceptions.HTTPError): + def __init__(self, message, response, explanation=None): + # requests 1.2 supports response as a keyword argument, but + # requests 1.1 doesn't + super(APIError, self).__init__(message) + self.response = response + + self.explanation = explanation + + if self.explanation is None and response.content: + self.explanation = response.content.strip() + + def __str__(self): + message = super(APIError, self).__str__() + + if self.is_client_error(): + message = '%s Client Error: %s' % ( + self.response.status_code, self.response.reason) + + elif self.is_server_error(): + message = '%s Server Error: %s' % ( + self.response.status_code, self.response.reason) + + if self.explanation: + message = '%s ("%s")' % (message, self.explanation) + + return message + + def is_client_error(self): + return 400 <= self.response.status_code < 500 + + def is_server_error(self): + return 500 <= self.response.status_code < 600 + class DockerException(Exception): pass diff --git a/tests/integration_test.py b/tests/integration_test.py index c1cd1f42..42dcf458 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -41,13 +41,13 @@ class BaseTestCase(unittest.TestCase): for img in self.tmp_imgs: try: self.client.remove_image(img) - except docker.APIError: + except docker.errors.APIError: pass for container in self.tmp_containers: try: self.client.stop(container, timeout=1) self.client.remove_container(container) - except docker.APIError: + except docker.errors.APIError: pass ######################### @@ -641,7 +641,7 @@ class TestPull(BaseTestCase): try: self.client.remove_image('joffrey/test001') self.client.remove_image('376968a23351') - except docker.APIError: + except docker.errors.APIError: pass info = self.client.info() self.assertIn('Images', info) @@ -660,7 +660,7 @@ class TestPullStream(BaseTestCase): try: self.client.remove_image('joffrey/test001') self.client.remove_image('376968a23351') - except docker.APIError: + except docker.errors.APIError: pass info = self.client.info() self.assertIn('Images', info)