Commit 3b97ddc6 authored by georgschoelly's avatar georgschoelly Committed by ORD

add exception classes for all bad status codes (#275)

This enables us to catch status codes directly like this:

    try:
        raise UaStatusCodeError(0x80010000)
    except BadUnexpectedError:
        # do something
parent 25ee45df
from ._base import *
from ._auto import *
This diff is collapsed.
......@@ -2,11 +2,29 @@
Define exceptions to be raised at various places in the stack
"""
from opcua.compat import with_metaclass
class _AutoRegister(type):
def __new__(mcs, name, bases, dict):
SubClass = type.__new__(mcs, name, bases, dict)
# register subclass in bases
for base in bases:
try:
subclasses = base._subclasses
code = dict['code']
except (AttributeError, KeyError):
pass
else:
subclasses[code] = SubClass
return SubClass
class UaError(RuntimeError):
pass
class UaStatusCodeError(UaError):
class UaStatusCodeError(with_metaclass(_AutoRegister, UaError)):
"""
This exception is raised when a bad status code is encountered.
......@@ -16,10 +34,38 @@ class UaStatusCodeError(UaError):
The list of status error codes can be found in opcua.ua.status_codes.
"""
def __init__(self, code):
""" Dict containing all subclasses keyed to their status code. """
_subclasses = {}
def __new__(cls, *args):
"""
Creates a new UaStatusCodeError but returns a more specific subclass
if possible, e.g.
UaStatusCodeError(0x80010000) => BadUnexpectedError()
"""
# switch class to a more appropriate subclass
if len(args) >= 1:
code = args[0]
try:
cls = cls._subclasses[code]
except (KeyError, AttributeError):
pass
else:
args = args[1:]
return UaError.__new__(cls, *args)
def __init__(self, code=None):
"""
:param code: The code of the exception. (Should be a number.)
:param code: The code of the exception. Only needed when not instanciating
a concrete subclass such as BadInternalError.
"""
if code is None:
if type(self) is UaStatusCodeError:
raise TypeError("UaStatusCodeError(code) cannot be instantiated without a status code.")
UaError.__init__(self, code)
def __str__(self):
......
""" Module with Python 2/3 compatibility functions. """
def with_metaclass(Meta, *bases):
""" Allows to specify metaclasses in Python 2 and 3 compatible ways.
Might not allow
"""
return Meta("Meta", bases, {})
from textwrap import dedent
from generate_statuscode import status_codes
from string import Template
if __name__ == "__main__":
codes = status_codes()
with open("../opcua/common/uaerrors/_auto.py", "w") as f:
preamble = """\
#AUTOGENERATED!!!
from opcua.common.uaerrors import UaStatusCodeError
"""
f.write(dedent(preamble))
for name, code, _ in codes:
# skip non-bad because they should not be thrown as exceptions
if not name.startswith("Bad"):
continue
template = Template(dedent("""\
class $name(UaStatusCodeError):
code = $code
"""))
print(template.safe_substitute(name=name, code=code), file=f)
......@@ -12,6 +12,7 @@ from tests_unit import TestUnit, TestMaskEnum
from tests_history import TestHistory, TestHistorySQL, TestHistoryLimits, TestHistorySQLLimits
from tests_history import TestHistorySQL
from tests_crypto_connect import TestCryptoConnect
from tests_uaerrors import TestUaErrors
if __name__ == '__main__':
......
import unittest
import opcua.common.uaerrors as uaerrors
from opcua.common.uaerrors import UaStatusCodeError
class TestUaErrors(unittest.TestCase):
status_code_bad_internal = 0x80020000
status_code_unknown = "Definitely Not A Status Code"
def setUp(self):
self.direct = uaerrors.BadInternalError()
self.indirect = UaStatusCodeError(self.status_code_bad_internal)
self.unknown = UaStatusCodeError(self.status_code_unknown)
def testSubclassSelection(self):
self.assertIs(type(self.direct), uaerrors.BadInternalError)
self.assertIs(type(self.indirect), uaerrors.BadInternalError)
self.assertIs(type(self.unknown), UaStatusCodeError)
def testCode(self):
self.assertEqual(self.direct.code, self.status_code_bad_internal)
self.assertEqual(self.indirect.code, self.status_code_bad_internal)
self.assertEqual(self.unknown.code, self.status_code_unknown)
def testStringRepr(self):
self.assertIn("BadInternal", str(self.direct))
self.assertIn("BadInternal", str(self.indirect))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment