Skip to content

Commit

Permalink
Added connection information to exceptions
Browse files Browse the repository at this point in the history
TODO:
* Specify the connection in all pywbem exceptions that are raised.

Details:

* Added a keyword argument 'conn' to all pywbem exceptions, to pass the
  WBEMConnection object of the connection.
  Added two new properties to all pywbem exceptions:
  - A `conn` property of the connection object.
  - A `conn_str` property for the connection info string, for use in exception
    messages.

* Extended the unit test cases in test_exceptions.py with a fixture for the
  'conn' init argument, and with a new _assert_connection() method that
  verifies the connection information.

Signed-off-by: Andreas Maier <andreas.r.maier@gmx.de>
  • Loading branch information
andy-maier committed Jul 17, 2018
1 parent 037cccf commit 92c3a83
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 18 deletions.
5 changes: 5 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ Released: not yet
Also, test cases were added for tupleparse for the `PARAMVALUE` element.
See issue #1241.

* Added connection information to all pywbem exceptions. This is done via a
new optional `conn` keyword argument that was added to all pywbem exception
classes. The exception message now has a connection information string at
its end. See issue #1155.

**Cleanup**

* Moved class `NocaseDict` into its own module (Issue #848).
Expand Down
78 changes: 69 additions & 9 deletions pywbem/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,50 @@

class Error(Exception):
"""Base class for pywbem specific exceptions."""
pass

def __init__(self, *args, **kwargs):
"""
Parameters:
conn (:class:`~pywbem.WBEMConnection`): Must be a keyword argument.
Connection in whose context the error happened. Omitted or `None`
if the error did not happen in context of any connection, or if the
connection context was not known.
Any other positional arguments or keyword arguments are passed to
:exc:`py:Exception`.
"""
if 'conn' in kwargs:
conn = kwargs['conn']
del kwargs['conn']
else:
conn = None
super(Error, self).__init__(*args, **kwargs)
self._conn = conn

@property
def conn(self):
"""
:class:`~pywbem.WBEMConnection`: Connection in whose context
the error happened. `None` if the error did not happen in context
of any connection, or if the connection context was not known.
"""
return self._conn

@property
def conn_str(self):
"""
:term:`unicode string`: String that identifies the connection in
exception messages.
"""
conn_id = self.conn.conn_id if self.conn else None
ret_str = "Connection id: %s" % conn_id
return ret_str

def __str__(self):
error_str = super(Error, self).__str__()
ret_str = "%s, %s" % (error_str, self.conn_str)
return ret_str


class ConnectionError(Error):
Expand Down Expand Up @@ -58,7 +101,8 @@ class HTTPError(Error):
"""

# pylint: disable=super-init-not-called
def __init__(self, status, reason, cimerror=None, cimdetails=None):
def __init__(self, status, reason, cimerror=None, cimdetails=None,
conn=None):
"""
Parameters:
Expand All @@ -67,7 +111,7 @@ def __init__(self, status, reason, cimerror=None, cimdetails=None):
reason (:term:`string`): HTTP reason phrase (e.g.
"Internal Server Error").
cimerror (:term:`string`): Value of the `CIMError` header field,
cimerror (:term:`string`): Value of the `CIMError` HTTP header field,
if present. `None`, otherwise.
cimdetails (dict): Dictionary with CIMOM-specific header
Expand All @@ -78,10 +122,18 @@ def __init__(self, status, reason, cimerror=None, cimdetails=None):
* Value: header field value (i.e. text message)
Passing `None` will result in an empty dictionary.
conn (:class:`~pywbem.WBEMConnection`): Connection in whose context
the error happened. `None` if the error did not happen in context
of any connection, or if the connection context was not known.
:ivar args: A tuple (status, reason, cimerror, cimdetails) set from the
corresponding init arguments.
"""
if cimdetails is None:
cimdetails = {}
self.args = (status, reason, cimerror, cimdetails)
super(HTTPError, self).__init__(
status, reason, cimerror, cimdetails, conn=conn)

@property
def status(self):
Expand All @@ -104,7 +156,7 @@ def reason(self):
@property
def cimerror(self):
"""
:term:`string`: Value of `CIMError` header field in response, if
:term:`string`: Value of `CIMError` HTTP header field in response, if
present. `None`, otherwise.
See :term:`DSP0200` for a list of values.
Expand All @@ -128,6 +180,7 @@ def __str__(self):
ret_str += ", CIMError: %s" % self.cimerror
for key in self.cimdetails:
ret_str += ", %s: %s" % (key, self.cimdetails[key])
ret_str += ", %s" % self.conn_str
return ret_str


Expand Down Expand Up @@ -184,7 +237,7 @@ class CIMError(Error):
"""

# pylint: disable=super-init-not-called
def __init__(self, status_code, status_description=None):
def __init__(self, status_code, status_description=None, conn=None):
"""
Parameters:
Expand All @@ -195,10 +248,15 @@ def __init__(self, status_code, status_description=None):
describing the error. `None`, if the server did not return
a description text.
conn (:class:`~pywbem.WBEMConnection`): Connection in whose context
the error happened. `None` if the error did not happen in context
of any connection, or if the connection context was not known.
:ivar args: A tuple (status_code, status_description) set from the
corresponding init arguments.
corresponding init arguments.
"""
self.args = (status_code, status_description)
super(CIMError, self).__init__(
status_code, status_description, conn=conn)

@property
def status_code(self):
Expand Down Expand Up @@ -239,4 +297,6 @@ def status_description(self):
return self.args[1] or _statuscode2string(self.status_code)

def __str__(self):
return "%s: %s" % (self.status_code, self.status_description)
ret_str = "%s: %s, %s" % (self.status_code, self.status_description,
self.conn_str)
return ret_str
65 changes: 56 additions & 9 deletions testsuite/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
import pytest

from pywbem import Error, ConnectionError, AuthError, HTTPError, TimeoutError,\
ParseError, VersionError, CIMError
ParseError, VersionError, CIMError, WBEMConnection

# Test connection object used for showing connection information in exception
# messages
TEST_CONN = WBEMConnection(url='http://blah')

# The expected connection information in exception messages
TEST_CONN_STR = 'Connection id: %s' % TEST_CONN.conn_id
TEST_CONN_STR_NONE = 'Connection id: None'

def _assert_subscription(exc):
"""
Expand Down Expand Up @@ -40,6 +48,17 @@ def _assert_subscription(exc):
assert False, "Access by index did not fail in Python 3"


def _assert_connection(exc, conn_kwarg, exp_conn_str):
"""
Test the exception defined by exc for connection related information.
"""
exp_conn = conn_kwarg.get('conn', None)
exc_str = str(exc)
assert exc.conn is exp_conn
assert exc.conn_str == exp_conn_str
assert exp_conn_str in exc_str


@pytest.fixture(params=[
# The exception classes for which the simple test should be done:
Error,
Expand Down Expand Up @@ -76,13 +95,33 @@ def simple_args(request):
return request.param


def test_simple(simple_class, simple_args):
@pytest.fixture(params=[
# Tuple of (conn_kwarg, exp_conn_str)
(dict(), TEST_CONN_STR_NONE),
(dict(conn=None), TEST_CONN_STR_NONE),
(dict(conn=TEST_CONN), TEST_CONN_STR),
], scope='module')
def conn_info(request):
"""
Fixture representing variations for the conn keyword argument for all
exception classes, and the corresponding expected connection info string.
Returns a tuple of:
* conn_kwarg: dict with the 'conn' keyword argument. May be empty.
* exp_conn_str: Expected connection info string.
"""
return request.param


def test_simple(simple_class, simple_args, conn_info):
# pylint: disable=redefined-outer-name
"""
Test the simple exception classes.
"""

exc = simple_class(*simple_args)
conn_kwarg, exp_conn_str = conn_info

exc = simple_class(*simple_args, **conn_kwarg)

# exc has no len()
assert len(exc.args) == len(simple_args)
Expand All @@ -92,6 +131,7 @@ def test_simple(simple_class, simple_args):
assert exc.args[0:i] == simple_args[0:i]
assert exc.args[:] == simple_args[:]

_assert_connection(exc, conn_kwarg, exp_conn_str)
_assert_subscription(exc)


Expand All @@ -114,13 +154,15 @@ def httperror_args(request):
return request.param


def test_httperror(httperror_args):
def test_httperror(httperror_args, conn_info):
# pylint: disable=redefined-outer-name
"""
Test HTTPError exception class.
"""

exc = HTTPError(*httperror_args)
conn_kwarg, exp_conn_str = conn_info

exc = HTTPError(*httperror_args, **conn_kwarg)

assert exc.status == httperror_args[0]
assert exc.reason == httperror_args[1]
Expand All @@ -139,6 +181,7 @@ def test_httperror(httperror_args):
assert exc.args[3] == exc.cimdetails
assert len(exc.args) == 4

_assert_connection(exc, conn_kwarg, exp_conn_str)
_assert_subscription(exc)


Expand Down Expand Up @@ -190,18 +233,19 @@ def status_tuple(request):
return request.param


def test_cimerror_1(status_tuple):
def test_cimerror_1(status_tuple, conn_info):
# pylint: disable=redefined-outer-name
"""
Test CIMError exception class with just status_code as input.
"""

status_code, status_code_name = status_tuple
conn_kwarg, exp_conn_str = conn_info

invalid_code_name = 'Invalid status code %s' % status_code
invalid_code_desc = 'Invalid status code %s' % status_code

exc = CIMError(status_code)
exc = CIMError(status_code, **conn_kwarg)

assert exc.status_code == status_code
if status_code_name is None:
Expand All @@ -215,21 +259,23 @@ def test_cimerror_1(status_tuple):
assert exc.args[1] is None
assert len(exc.args) == 2

_assert_connection(exc, conn_kwarg, exp_conn_str)
_assert_subscription(exc)


def test_cimerror_2(status_tuple):
def test_cimerror_2(status_tuple, conn_info):
# pylint: disable=redefined-outer-name
"""
Test CIMError exception class with status_code and description as input.
"""

status_code, status_code_name = status_tuple
conn_kwarg, exp_conn_str = conn_info

invalid_code_name = 'Invalid status code %s' % status_code
input_desc = 'foo'

exc = CIMError(status_code, input_desc)
exc = CIMError(status_code, input_desc, **conn_kwarg)

assert exc.status_code == status_code
assert exc.status_description == input_desc
Expand All @@ -242,4 +288,5 @@ def test_cimerror_2(status_tuple):
assert exc.args[1] == input_desc
assert len(exc.args) == 2

_assert_connection(exc, conn_kwarg, exp_conn_str)
_assert_subscription(exc)

0 comments on commit 92c3a83

Please sign in to comment.