From 9ef956903251317d2ae6f85a0c6337bdf58ea1e1 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sat, 10 Jan 2026 22:54:58 +0300 Subject: [PATCH 1/5] PortException is updated - explicit constructor - own __repr__ implmentation - tests --- src/exceptions.py | 33 ++++++++++++++++++- .../exceptions/PortForException/__init__.py | 0 .../test_set001__constructor.py | 24 ++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/units/exceptions/PortForException/__init__.py create mode 100644 tests/units/exceptions/PortForException/test_set001__constructor.py diff --git a/src/exceptions.py b/src/exceptions.py index 769fbe23..2ade8434 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 diff --git a/tests/units/exceptions/PortForException/__init__.py b/tests/units/exceptions/PortForException/__init__.py new file mode 100644 index 00000000..e69de29b 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 00000000..219dd913 --- /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 From a86e816c02a85ce1665e2b0c272bb27bde2d293c Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sat, 10 Jan 2026 22:57:05 +0300 Subject: [PATCH 2/5] InitNodeException is updated - explicit constructor - own __repr__ implmentation - tests --- src/exceptions.py | 33 ++++++++++++++++++- .../exceptions/InitNodeException/__init__.py | 0 .../test_set001__constructor.py | 24 ++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/units/exceptions/InitNodeException/__init__.py create mode 100644 tests/units/exceptions/InitNodeException/test_set001__constructor.py diff --git a/src/exceptions.py b/src/exceptions.py index 2ade8434..beb3db46 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -182,7 +182,38 @@ 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): diff --git a/tests/units/exceptions/InitNodeException/__init__.py b/tests/units/exceptions/InitNodeException/__init__.py new file mode 100644 index 00000000..e69de29b 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 00000000..c4acae84 --- /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 From 804e98a8a3a09ef0459b3f63d8feba7baf7890e0 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sat, 10 Jan 2026 22:58:28 +0300 Subject: [PATCH 3/5] BackupException is updated - explicit constructor - own __repr__ implmentation - tests --- src/exceptions.py | 33 ++++++++++++++++++- .../exceptions/BackupException/__init__.py | 0 .../test_set001__constructor.py | 24 ++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/units/exceptions/BackupException/__init__.py create mode 100644 tests/units/exceptions/BackupException/test_set001__constructor.py diff --git a/src/exceptions.py b/src/exceptions.py index beb3db46..0faea6fe 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -217,7 +217,38 @@ def __repr__(self) -> str: 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/tests/units/exceptions/BackupException/__init__.py b/tests/units/exceptions/BackupException/__init__.py new file mode 100644 index 00000000..e69de29b 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 00000000..bdde56c9 --- /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 From 943ae13cadb5134eb303b146d7f0f06b18249fcf Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sat, 10 Jan 2026 23:03:04 +0300 Subject: [PATCH 4/5] CatchUpException is updated (attention!) - inherits TestgresException, not QueryException - explicit constructor - own __repr__ implmentation - tests --- src/exceptions.py | 36 +++++++++++++++++-- .../exceptions/CatchUpException/__init__.py | 0 .../test_set001__constructor.py | 24 +++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/units/exceptions/CatchUpException/__init__.py create mode 100644 tests/units/exceptions/CatchUpException/test_set001__constructor.py diff --git a/src/exceptions.py b/src/exceptions.py index 0faea6fe..00f087cb 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -112,8 +112,40 @@ class TimeoutException(QueryException): pass -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 diff --git a/tests/units/exceptions/CatchUpException/__init__.py b/tests/units/exceptions/CatchUpException/__init__.py new file mode 100644 index 00000000..e69de29b 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 00000000..014797c6 --- /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 From 93b005b3c7812fa699975e4b2112d5d8c91f61e1 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sat, 10 Jan 2026 23:19:08 +0300 Subject: [PATCH 5/5] QueryTimeoutException replaces TimeoutException TimeoutException is saved as an alias of QueryTimeoutException --- src/__init__.py | 5 +- src/exceptions.py | 17 +++++- src/node.py | 6 +- tests/test_testgres_common.py | 8 +-- .../QueryTimeoutException/__init__.py | 0 .../test_set001__constructor.py | 57 +++++++++++++++++++ .../exceptions/TimeoutException/__init__.py | 0 .../TimeoutException/test_set001.py | 11 ++++ 8 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 tests/units/exceptions/QueryTimeoutException/__init__.py create mode 100644 tests/units/exceptions/QueryTimeoutException/test_set001__constructor.py create mode 100644 tests/units/exceptions/TimeoutException/__init__.py create mode 100644 tests/units/exceptions/TimeoutException/test_set001.py diff --git a/src/__init__.py b/src/__init__.py index bebd6878..02284b35 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 00f087cb..a46c12c9 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -108,8 +108,21 @@ 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 # [2026-01-10] It inherits TestgresException now, not QueryException diff --git a/src/node.py b/src/node.py index 2eedeb45..67d5df01 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 16d7b6a0..16473ffd 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/QueryTimeoutException/__init__.py b/tests/units/exceptions/QueryTimeoutException/__init__.py new file mode 100644 index 00000000..e69de29b 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 00000000..d69b2589 --- /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 00000000..e69de29b diff --git a/tests/units/exceptions/TimeoutException/test_set001.py b/tests/units/exceptions/TimeoutException/test_set001.py new file mode 100644 index 00000000..a4b9ad0e --- /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