diff --git a/src/__init__.py b/src/__init__.py index bebd687..02284b3 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -19,6 +19,7 @@ TestgresException, \ ExecUtilException, \ QueryException, \ + QueryTimeoutException, \ TimeoutException, \ CatchUpException, \ StartNodeException, \ @@ -61,7 +62,9 @@ "NodeBackup", "testgres_config", "TestgresConfig", "configure_testgres", "scoped_config", "push_config", "pop_config", "NodeConnection", "DatabaseError", "InternalError", "ProgrammingError", "OperationalError", - "TestgresException", "ExecUtilException", "QueryException", "TimeoutException", "CatchUpException", "StartNodeException", "InitNodeException", "BackupException", "InvalidOperationException", + "TestgresException", "ExecUtilException", "QueryException", + QueryTimeoutException.__name__, + "TimeoutException", "CatchUpException", "StartNodeException", "InitNodeException", "BackupException", "InvalidOperationException", "XLogMethod", "IsolationLevel", "NodeStatus", "ProcessType", "DumpFormat", NodeApp.__name__, PostgresNode.__name__, diff --git a/src/exceptions.py b/src/exceptions.py index 769fbe2..a46c12c 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -9,7 +9,38 @@ class PortForException(TestgresException): - pass + _message: typing.Optional[str] + + def __init__( + self, + message: typing.Optional[str] = None, + ): + assert message is None or type(message) == str # noqa: E721 + super().__init__(message) + self._message = message + return + + @property + def message(self) -> str: + assert self._message is None or type(self._message) == str # noqa: E721 + if self._message is None: + return "" + return self._message + + def __repr__(self) -> str: + args = [] + + if self._message is not None: + args.append(("message", self._message)) + + result = "{}(".format(type(self).__name__) + sep = "" + for a in args: + result += sep + a[0] + "=" + repr(a[1]) + sep = ", " + continue + result += ")" + return result @six.python_2_unicode_compatible @@ -77,12 +108,57 @@ def __repr__(self) -> str: return result -class TimeoutException(QueryException): - pass +class QueryTimeoutException(QueryException): + def __init__( + self, + message: typing.Optional[str] = None, + query: typing.Optional[str] = None + ): + assert message is None or type(message) == str # noqa: E721 + assert query is None or type(query) == str # noqa: E721 + + super().__init__(message, query) + return + +# [2026-01-10] To backward compatibility. +TimeoutException = QueryTimeoutException -class CatchUpException(QueryException): - pass + +# [2026-01-10] It inherits TestgresException now, not QueryException +class CatchUpException(TestgresException): + _message: typing.Optional[str] + + def __init__( + self, + message: typing.Optional[str] = None, + ): + assert message is None or type(message) == str # noqa: E721 + super().__init__(message) + self._message = message + return + + @property + def message(self) -> str: + assert self._message is None or type(self._message) == str # noqa: E721 + if self._message is None: + return "" + return self._message + + def __repr__(self) -> str: + args = [] + + if self._message is not None: + args.append(("message", self._message)) + + result = "{}(".format(type(self).__name__) + sep = "" + for a in args: + result += sep + a[0] + "=" + repr(a[1]) + sep = ", " + continue + result += ")" + return result @six.python_2_unicode_compatible @@ -151,11 +227,73 @@ def __repr__(self) -> str: class InitNodeException(TestgresException): - pass + _message: typing.Optional[str] + + def __init__( + self, + message: typing.Optional[str] = None, + ): + assert message is None or type(message) == str # noqa: E721 + super().__init__(message) + self._message = message + return + + @property + def message(self) -> str: + assert self._message is None or type(self._message) == str # noqa: E721 + if self._message is None: + return "" + return self._message + + def __repr__(self) -> str: + args = [] + + if self._message is not None: + args.append(("message", self._message)) + + result = "{}(".format(type(self).__name__) + sep = "" + for a in args: + result += sep + a[0] + "=" + repr(a[1]) + sep = ", " + continue + result += ")" + return result class BackupException(TestgresException): - pass + _message: typing.Optional[str] + + def __init__( + self, + message: typing.Optional[str] = None, + ): + assert message is None or type(message) == str # noqa: E721 + super().__init__(message) + self._message = message + return + + @property + def message(self) -> str: + assert self._message is None or type(self._message) == str # noqa: E721 + if self._message is None: + return "" + return self._message + + def __repr__(self) -> str: + args = [] + + if self._message is not None: + args.append(("message", self._message)) + + result = "{}(".format(type(self).__name__) + sep = "" + for a in args: + result += sep + a[0] + "=" + repr(a[1]) + sep = ", " + continue + result += ")" + return result assert ExecUtilException.__name__ == "ExecUtilException" diff --git a/src/node.py b/src/node.py index 2eedeb4..67d5df0 100644 --- a/src/node.py +++ b/src/node.py @@ -71,6 +71,7 @@ CatchUpException, \ ExecUtilException, \ QueryException, \ + QueryTimeoutException, \ StartNodeException, \ TimeoutException, \ InitNodeException, \ @@ -112,6 +113,9 @@ OperationalError = pglib.OperationalError +assert TimeoutException == QueryTimeoutException + + class ProcessProxy(object): """ Wrapper for psutil.Process @@ -1608,7 +1612,7 @@ def poll_query_until(self, time.sleep(sleep_time) attempts += 1 - raise TimeoutException('Query timeout') + raise QueryTimeoutException('Query timeout', query) @method_decorator(positional_args_hack(['dbname', 'query'])) def execute(self, diff --git a/tests/test_testgres_common.py b/tests/test_testgres_common.py index 16d7b6a..16473ff 100644 --- a/tests/test_testgres_common.py +++ b/tests/test_testgres_common.py @@ -27,7 +27,7 @@ from src import StartNodeException from src import QueryException from src import ExecUtilException -from src import TimeoutException +from src import QueryTimeoutException from src import InvalidOperationException from src import BackupException from src import ProgrammingError @@ -851,7 +851,7 @@ def test_poll_query_until(self, node_svc: PostgresNodeService): expected=None) # check arbitrary expected value, fail - with pytest.raises(expected_exception=TimeoutException): + with pytest.raises(expected_exception=QueryTimeoutException): node.poll_query_until(query='select 3', expected=1, max_attempts=3, @@ -861,7 +861,7 @@ def test_poll_query_until(self, node_svc: PostgresNodeService): node.poll_query_until(query='select 2', expected=2) # check timeout - with pytest.raises(expected_exception=TimeoutException): + with pytest.raises(expected_exception=QueryTimeoutException): node.poll_query_until(query='select 1 > 2', max_attempts=3, sleep_time=0.01) @@ -871,7 +871,7 @@ def test_poll_query_until(self, node_svc: PostgresNodeService): node.poll_query_until(query='dummy1') # check ProgrammingError, ok - with pytest.raises(expected_exception=(TimeoutException)): + with pytest.raises(expected_exception=(QueryTimeoutException)): node.poll_query_until(query='dummy2', max_attempts=3, sleep_time=0.01, diff --git a/tests/units/exceptions/BackupException/__init__.py b/tests/units/exceptions/BackupException/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/units/exceptions/BackupException/test_set001__constructor.py b/tests/units/exceptions/BackupException/test_set001__constructor.py new file mode 100644 index 0000000..bdde56c --- /dev/null +++ b/tests/units/exceptions/BackupException/test_set001__constructor.py @@ -0,0 +1,24 @@ +from src.exceptions import BackupException +from src.exceptions import TestgresException as testgres__TestgresException + + +class TestSet001_Constructor: + def test_001__default(self): + e = BackupException() + assert type(e) == BackupException # noqa: E721 + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "" + assert str(e) == "" + assert repr(e) == "BackupException()" + return + + def test_002__message(self): + e = BackupException(message="abc\n123") + assert type(e) == BackupException # noqa: E721 + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "abc\n123" + assert str(e) == "abc\n123" + assert repr(e) == "BackupException(message='abc\\n123')" + return diff --git a/tests/units/exceptions/CatchUpException/__init__.py b/tests/units/exceptions/CatchUpException/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/units/exceptions/CatchUpException/test_set001__constructor.py b/tests/units/exceptions/CatchUpException/test_set001__constructor.py new file mode 100644 index 0000000..014797c --- /dev/null +++ b/tests/units/exceptions/CatchUpException/test_set001__constructor.py @@ -0,0 +1,24 @@ +from src.exceptions import CatchUpException +from src.exceptions import TestgresException as testgres__TestgresException + + +class TestSet001_Constructor: + def test_001__default(self): + e = CatchUpException() + assert type(e) == CatchUpException # noqa: E721 + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "" + assert str(e) == "" + assert repr(e) == "CatchUpException()" + return + + def test_002__message(self): + e = CatchUpException(message="abc\n123") + assert type(e) == CatchUpException # noqa: E721 + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "abc\n123" + assert str(e) == "abc\n123" + assert repr(e) == "CatchUpException(message='abc\\n123')" + return diff --git a/tests/units/exceptions/InitNodeException/__init__.py b/tests/units/exceptions/InitNodeException/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/units/exceptions/InitNodeException/test_set001__constructor.py b/tests/units/exceptions/InitNodeException/test_set001__constructor.py new file mode 100644 index 0000000..c4acae8 --- /dev/null +++ b/tests/units/exceptions/InitNodeException/test_set001__constructor.py @@ -0,0 +1,24 @@ +from src.exceptions import InitNodeException +from src.exceptions import TestgresException as testgres__TestgresException + + +class TestSet001_Constructor: + def test_001__default(self): + e = InitNodeException() + assert type(e) == InitNodeException # noqa: E721 + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "" + assert str(e) == "" + assert repr(e) == "InitNodeException()" + return + + def test_002__message(self): + e = InitNodeException(message="abc\n123") + assert type(e) == InitNodeException # noqa: E721 + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "abc\n123" + assert str(e) == "abc\n123" + assert repr(e) == "InitNodeException(message='abc\\n123')" + return diff --git a/tests/units/exceptions/PortForException/__init__.py b/tests/units/exceptions/PortForException/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/units/exceptions/PortForException/test_set001__constructor.py b/tests/units/exceptions/PortForException/test_set001__constructor.py new file mode 100644 index 0000000..219dd91 --- /dev/null +++ b/tests/units/exceptions/PortForException/test_set001__constructor.py @@ -0,0 +1,24 @@ +from src.exceptions import PortForException +from src.exceptions import TestgresException as testgres__TestgresException + + +class TestSet001_Constructor: + def test_001__default(self): + e = PortForException() + assert type(e) == PortForException # noqa: E721 + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "" + assert str(e) == "" + assert repr(e) == "PortForException()" + return + + def test_002__message(self): + e = PortForException(message="abc\n123") + assert type(e) == PortForException # noqa: E721 + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "abc\n123" + assert str(e) == "abc\n123" + assert repr(e) == "PortForException(message='abc\\n123')" + return diff --git a/tests/units/exceptions/QueryTimeoutException/__init__.py b/tests/units/exceptions/QueryTimeoutException/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/units/exceptions/QueryTimeoutException/test_set001__constructor.py b/tests/units/exceptions/QueryTimeoutException/test_set001__constructor.py new file mode 100644 index 0000000..d69b258 --- /dev/null +++ b/tests/units/exceptions/QueryTimeoutException/test_set001__constructor.py @@ -0,0 +1,57 @@ +from src.exceptions import QueryTimeoutException +from src.exceptions import QueryException +from src.exceptions import TestgresException as testgres__TestgresException + + +class TestSet001_Constructor: + def test_001__default(self): + e = QueryTimeoutException() + assert type(e) == QueryTimeoutException # noqa: E721 + assert isinstance(e, QueryException) + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "" + assert e.description is None + assert e.query is None + assert str(e) == "" + assert repr(e) == "QueryTimeoutException()" + return + + def test_002__message(self): + e = QueryTimeoutException(message="abc\n123") + assert type(e) == QueryTimeoutException # noqa: E721 + assert isinstance(e, QueryException) + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "abc\n123" + assert e.description == "abc\n123" + assert e.query is None + assert str(e) == "abc\n123" + assert repr(e) == "QueryTimeoutException(message='abc\\n123')" + return + + def test_003__query(self): + e = QueryTimeoutException(query="cba\n321") + assert type(e) == QueryTimeoutException # noqa: E721 + assert isinstance(e, QueryException) + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "Query: cba\n321" + assert e.description is None + assert e.query == "cba\n321" + assert str(e) == "Query: cba\n321" + assert repr(e) == "QueryTimeoutException(query='cba\\n321')" + return + + def test_004__all(self): + e = QueryTimeoutException(message="mmm", query="cba\n321") + assert type(e) == QueryTimeoutException # noqa: E721 + assert isinstance(e, QueryException) + assert isinstance(e, testgres__TestgresException) + assert e.source is None + assert e.message == "mmm\nQuery: cba\n321" + assert e.description == "mmm" + assert e.query == "cba\n321" + assert str(e) == "mmm\nQuery: cba\n321" + assert repr(e) == "QueryTimeoutException(message='mmm', query='cba\\n321')" + return diff --git a/tests/units/exceptions/TimeoutException/__init__.py b/tests/units/exceptions/TimeoutException/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/units/exceptions/TimeoutException/test_set001.py b/tests/units/exceptions/TimeoutException/test_set001.py new file mode 100644 index 0000000..a4b9ad0 --- /dev/null +++ b/tests/units/exceptions/TimeoutException/test_set001.py @@ -0,0 +1,11 @@ +from src.exceptions import QueryTimeoutException +from src.exceptions import TimeoutException +from src.exceptions import QueryException + + +class TestSet001: + def test_001__default(self): + # It is an alias + assert TimeoutException == QueryTimeoutException + assert issubclass(TimeoutException, QueryException) + return