diff --git a/pyproject.toml b/pyproject.toml index 985aec28..7c51b331 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,6 @@ classifiers = [ dependencies = [ "pg8000", "port-for>=0.4", - "six>=1.9.0", "psutil", "packaging", "testgres.os_ops>=3.3.3,<4.0.0", diff --git a/src/backup.py b/src/backup.py index 9c54be17..75ac1af2 100644 --- a/src/backup.py +++ b/src/backup.py @@ -1,7 +1,5 @@ # coding: utf-8 -from six import raise_from - from .enums import XLogMethod from .consts import \ @@ -22,7 +20,7 @@ clean_on_error -class NodeBackup(object): +class NodeBackup: """ Smart object responsible for backups """ @@ -131,7 +129,7 @@ def _prepare_dir(self, destroy): # Copy backup to new data dir self.os_ops.copytree(data1, data2) except Exception as e: - raise_from(BackupException('Failed to copy files'), e) + raise BackupException('Failed to copy files') from e else: dest_base_dir = self.base_dir diff --git a/src/cache.py b/src/cache.py index e205a510..06d7ba4a 100644 --- a/src/cache.py +++ b/src/cache.py @@ -1,7 +1,5 @@ # coding: utf-8 -from six import raise_from - from .config import testgres_config from .consts import XLOG_CONTROL_FILE @@ -49,7 +47,7 @@ def call_initdb(initdb_dir, log=logfile): log, ) except ExecUtilException as e: - raise_from(InitNodeException("Failed to run initdb"), e) + raise InitNodeException("Failed to run initdb") from e if params or not testgres_config.cache_initdb or not cached: call_initdb(data_dir, logfile) @@ -89,7 +87,7 @@ def call_initdb(initdb_dir, log=logfile): except ExecUtilException as e: msg = "Failed to reset WAL for system id" - raise_from(InitNodeException(msg), e) + raise InitNodeException(msg) from e except Exception as e: - raise_from(InitNodeException("Failed to spawn a node"), e) + raise InitNodeException("Failed to spawn a node") from e diff --git a/src/config.py b/src/config.py index 1d09ccb8..41c2108e 100644 --- a/src/config.py +++ b/src/config.py @@ -17,7 +17,7 @@ logging.basicConfig(level=log_level, format=log_format) -class GlobalConfig(object): +class GlobalConfig: """ Global configuration object which allows user to override default settings. """ diff --git a/src/connection.py b/src/connection.py index b16edb23..ec455124 100644 --- a/src/connection.py +++ b/src/connection.py @@ -25,7 +25,7 @@ OperationalError = pglib.OperationalError -class NodeConnection(object): +class NodeConnection: """ Transaction wrapper returned by Node """ diff --git a/src/decorators.py b/src/decorators.py index 7f383ae7..98b10ea7 100644 --- a/src/decorators.py +++ b/src/decorators.py @@ -1,4 +1,3 @@ -import six import functools @@ -18,7 +17,7 @@ def some_api_func(...) for case in special_cases: k = len(case) - assert k not in six.iterkeys(cases), 'len must be unique' + assert k not in cases, 'len must be unique' cases[k] = case def decorator(function): @@ -26,7 +25,7 @@ def decorator(function): def wrapper(*args, **kwargs): k = len(args) - if k in six.iterkeys(cases): + if k in cases: case = cases[k] for i in range(0, k): diff --git a/src/enums.py b/src/enums.py index a483c5b4..da52dc34 100644 --- a/src/enums.py +++ b/src/enums.py @@ -1,5 +1,4 @@ from enum import Enum, IntEnum -from six import iteritems from psutil import NoSuchProcess @@ -83,7 +82,7 @@ def from_process(process): if cmdline.startswith(ptype.value.replace(' ', '')): return ptype - for ptype, names in iteritems(alternative_names): + for ptype, names in alternative_names.items(): for name in names: if cmdline.startswith(name.replace(' ', '')): return ptype diff --git a/src/exceptions.py b/src/exceptions.py index 13de787a..d00de700 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -1,6 +1,5 @@ # coding: utf-8 -import six import typing from testgres.operations.exceptions import TestgresException @@ -44,7 +43,6 @@ def __repr__(self) -> str: return result -@six.python_2_unicode_compatible class QueryException(TestgresException): _description: typing.Optional[str] _query: typing.Optional[str] @@ -76,7 +74,7 @@ def message(self) -> str: if self._query: msg.append(u'Query: {}'.format(self._query)) - r = six.text_type('\n').join(msg) + r = '\n'.join(msg) assert type(r) is str return r @@ -162,7 +160,6 @@ def __repr__(self) -> str: return result -@six.python_2_unicode_compatible class StartNodeException(TestgresException): _description: typing.Optional[str] _files: typing.Optional[typing.Iterable] @@ -196,7 +193,7 @@ def message(self) -> str: assert type(lines) in [str, bytes] msg.append(u'{}\n----\n{}\n'.format(f, lines)) - return six.text_type('\n').join(msg) + return '\n'.join(msg) @property def description(self) -> typing.Optional[str]: diff --git a/src/impl/platforms/darwin/internal_platform_utils.py b/src/impl/platforms/darwin/internal_platform_utils.py index ce8fdb4f..738e9d5e 100644 --- a/src/impl/platforms/darwin/internal_platform_utils.py +++ b/src/impl/platforms/darwin/internal_platform_utils.py @@ -248,7 +248,7 @@ def _FindPostmaster( ) # -------------------------------------------------------------------- - def ProcessIsZombi_soft_check( + def ProcessIsZombie_soft_check( self, os_ops: OsOperations, pid: int, diff --git a/src/impl/platforms/internal_platform_utils.py b/src/impl/platforms/internal_platform_utils.py index e9b121b6..df96db1f 100644 --- a/src/impl/platforms/internal_platform_utils.py +++ b/src/impl/platforms/internal_platform_utils.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ...raise_error import RaiseError + import enum import typing @@ -59,13 +61,13 @@ def FindPostmaster( assert isinstance(os_ops, OsOperations) assert type(bin_dir) is str assert type(data_dir) is str - raise NotImplementedError("InternalPlatformUtils::FindPostmaster is not implemented.") + RaiseError.method_is_not_implemented(__class__, "FindPostmaster") - def ProcessIsZombi_soft_check( + def ProcessIsZombie_soft_check( self, os_ops: OsOperations, pid: int, ) -> typing.Optional[bool]: assert isinstance(os_ops, OsOperations) assert type(pid) is int - raise NotImplementedError("InternalPlatformUtils::ProcessIsZombi_soft_ver is not implemented.") + RaiseError.method_is_not_implemented(__class__, "ProcessIsZombie_soft_check") diff --git a/src/impl/platforms/linux/internal_platform_utils.py b/src/impl/platforms/linux/internal_platform_utils.py index 1021b735..f05e59bf 100644 --- a/src/impl/platforms/linux/internal_platform_utils.py +++ b/src/impl/platforms/linux/internal_platform_utils.py @@ -247,7 +247,7 @@ def _FindPostmaster( ) # -------------------------------------------------------------------- - def ProcessIsZombi_soft_check( + def ProcessIsZombie_soft_check( self, os_ops: OsOperations, pid: int, @@ -264,7 +264,10 @@ def ProcessIsZombi_soft_check( try: # Read one line from /proc/PID/stat - stat_content = os_ops.read_binary(proc_stat_file, 0).decode("utf-8", errors="ignore") + stat_content_b = os_ops.read_binary(proc_stat_file, 0) + assert type(stat_content_b) is bytes + + stat_content = stat_content_b.decode("utf-8", errors="ignore") # We look for the closing parenthesis of the process name to ensure that # we start from it and not depend on spaces inside the parentheses! @@ -281,13 +284,13 @@ def ProcessIsZombi_soft_check( result = proc_status == "Z" except Exception as e: # If the file disappeared right during reading, it means the process is completely erased - if __class__._is_file_not_found_exception(e): + if __class__._is_zombie_file_exception(e): result = False return result @staticmethod - def _is_file_not_found_exception(e: Exception) -> bool: + def _is_zombie_file_exception(e: Exception) -> bool: if isinstance(e, FileNotFoundError): return True diff --git a/src/impl/platforms/win32/internal_platform_utils.py b/src/impl/platforms/win32/internal_platform_utils.py index c6df6378..71aa1c71 100644 --- a/src/impl/platforms/win32/internal_platform_utils.py +++ b/src/impl/platforms/win32/internal_platform_utils.py @@ -17,7 +17,7 @@ def FindPostmaster( assert type(data_dir) is str return __class__.FindPostmasterResult.create_not_implemented() - def ProcessIsZombi_soft_check( + def ProcessIsZombie_soft_check( self, os_ops: OsOperations, pid: int, diff --git a/src/node.py b/src/node.py index ded039ec..b2e81684 100644 --- a/src/node.py +++ b/src/node.py @@ -74,6 +74,7 @@ from testgres.operations.os_ops import OsProcessController from testgres.operations.local_ops import LocalOperations +import ipaddress import logging import signal import subprocess @@ -81,11 +82,6 @@ import time import typing -try: - from collections.abc import Iterable -except ImportError: - from collections import Iterable - # we support both pg8000 and psycopg2 try: import psycopg2 as pglib @@ -95,8 +91,6 @@ except ImportError: raise ImportError("You must have psycopg2 or pg8000 modules installed") -from six import raise_from, iteritems, text_type - InternalError = pglib.InternalError ProgrammingError = pglib.ProgrammingError @@ -106,7 +100,7 @@ assert TimeoutException == QueryTimeoutException -class ProcessProxy(object): +class ProcessProxy: """ Wrapper for psutil.Process @@ -150,7 +144,7 @@ def ptype(self) -> ProcessType: return self._ptype -class PostgresNode(object): +class PostgresNode: # a max number of node start attempts _C_MAX_START_ATEMPTS = 5 @@ -756,7 +750,6 @@ def _create_recovery_conf(self, username, slot=None): # host is tricky try: - import ipaddress ipaddress.ip_address(master.host) conninfo["hostaddr"] = master.host except ValueError: @@ -1072,7 +1065,7 @@ def append_conf(self, line='', filename=PG_CONF_FILE, **kwargs): lines = [line] - for option, value in iteritems(kwargs): + for option, value in kwargs.items(): if isinstance(value, bool): value = 'on' if value else 'off' elif not str(value).replace('.', '', 1).isdigit(): @@ -1086,7 +1079,7 @@ def append_conf(self, line='', filename=PG_CONF_FILE, **kwargs): config_name = self._os_ops.build_path(self.data_dir, filename) conf_text = '' for line in lines: - conf_text += text_type(line) + '\n' + conf_text += str(line) + '\n' self._os_ops.write(config_name, conf_text) return self @@ -1368,7 +1361,7 @@ def _raise_cannot_start_node( assert from_exception is None or isinstance(from_exception, Exception) assert type(msg) is str files = self._collect_special_files() - raise_from(StartNodeException(msg, files), from_exception) + raise StartNodeException(msg, files) from from_exception def stop( self, @@ -1471,7 +1464,7 @@ def restart(self, params=[]): except ExecUtilException as e: msg = 'Cannot restart node' files = self._collect_special_files() - raise_from(StartNodeException(msg, files), e) + raise StartNodeException(msg, files) from e self._maybe_start_logger() @@ -1715,7 +1708,7 @@ def _psql( ] # yapf: disable # set variables before execution - for key, value in iteritems(variables): + for key, value in variables.items(): psql_params.extend(["--set", '{}={}'.format(key, value)]) # select query source @@ -2067,12 +2060,11 @@ def set_synchronous_standbys(self, standbys): """ if self._pg_version >= utils.PgVer('9.6'): - if isinstance(standbys, Iterable): + if isinstance(standbys, typing.Iterable): standbys = First(1, standbys) else: - if isinstance(standbys, Iterable): - standbys = u", ".join(u"\"{}\"".format(r.name) - for r in standbys) + if isinstance(standbys, typing.Iterable): + standbys = ", ".join("\"{}\"".format(r.name) for r in standbys) else: raise TestgresException( "Feature isn't supported in " @@ -2112,7 +2104,7 @@ def catchup(self, dbname=None, username=None): max_attempts=0, ) except Exception as e: - raise_from(CatchUpException("Failed to catch up."), e) + raise CatchUpException("Failed to catch up.") from e def publish(self, name, **kwargs): """ @@ -2283,7 +2275,7 @@ def pgbench_run( "-U", username or self._os_ops.username ] + options # yapf: disable - for key, value in iteritems(kwargs): + for key, value in kwargs.items(): # rename keys for pgbench key = key.replace('_', '-') diff --git a/src/port_manager.py b/src/port_manager.py index c003a038..338da2f1 100644 --- a/src/port_manager.py +++ b/src/port_manager.py @@ -1,10 +1,13 @@ +from .raise_error import RaiseError + + class PortManager: def __init__(self): super().__init__() def reserve_port(self) -> int: - raise NotImplementedError("PortManager::reserve_port is not implemented.") + RaiseError.method_is_not_implemented(__class__, "reserve_port") def release_port(self, number: int) -> None: assert type(number) is int - raise NotImplementedError("PortManager::release_port is not implemented.") + RaiseError.method_is_not_implemented(__class__, "release_port") diff --git a/src/pubsub.py b/src/pubsub.py index c8a446ef..07b1ec29 100644 --- a/src/pubsub.py +++ b/src/pubsub.py @@ -42,15 +42,13 @@ [(1, 1), (2, 2)] """ -from six import raise_from - from .consts import LOGICAL_REPL_MAX_CATCHUP_ATTEMPTS from .defaults import default_dbname, default_username2 from .exceptions import CatchUpException from .utils import options_string -class Publication(object): +class Publication: def __init__(self, name, node, tables=None, dbname=None, username=None): """ Constructor. Use :meth:`.PostgresNode.publish()` instead of direct @@ -134,7 +132,7 @@ def add_tables(self, tables, dbname=None, username=None): ) -class Subscription(object): +class Subscription: def __init__( self, node, @@ -341,4 +339,4 @@ def catchup(self, username=None): max_attempts=LOGICAL_REPL_MAX_CATCHUP_ATTEMPTS, ) except Exception as e: - raise_from(CatchUpException("Failed to catch up"), e) + raise CatchUpException("Failed to catch up") from e diff --git a/src/raise_error.py b/src/raise_error.py index aa6a910b..751ceb6e 100644 --- a/src/raise_error.py +++ b/src/raise_error.py @@ -5,6 +5,20 @@ class RaiseError: + @staticmethod + def method_is_not_implemented( + class_type: type, + method_name: str, + ) -> typing.NoReturn: + assert type(class_type) is type + assert type(method_name) is str + + err_msg = "Method {}::{} is not implemented.".format( + class_type.__name__, + method_name, + ) + raise NotImplementedError(err_msg) + @staticmethod def pg_ctl_returns_an_empty_string(_params) -> typing.NoReturn: errLines = [] diff --git a/src/standby.py b/src/standby.py index 859f874e..4e0912af 100644 --- a/src/standby.py +++ b/src/standby.py @@ -1,9 +1,6 @@ # coding: utf-8 -import six - -@six.python_2_unicode_compatible class First: """ Specifies a priority-based synchronous replication and makes transaction @@ -21,12 +18,12 @@ def __init__(self, sync_num, standbys): self.standbys = standbys def __str__(self): - return u"{} ({})".format( + return "{} ({})".format( self.sync_num, - u", ".join(u"\"{}\"".format(r.name) for r in self.standbys)) + ", ".join("\"{}\"".format(r.name) for r in self.standbys), + ) -@six.python_2_unicode_compatible class Any: """ Specifies a quorum-based synchronous replication and makes transaction @@ -44,6 +41,7 @@ def __init__(self, sync_num, standbys): self.standbys = standbys def __str__(self): - return u"ANY {} ({})".format( + return "ANY {} ({})".format( self.sync_num, - u", ".join(u"\"{}\"".format(r.name) for r in self.standbys)) + ", ".join("\"{}\"".format(r.name) for r in self.standbys), + ) diff --git a/src/utils.py b/src/utils.py index eb63694d..16d6784e 100644 --- a/src/utils.py +++ b/src/utils.py @@ -30,7 +30,6 @@ import re import typing -from six import iteritems from contextlib import contextmanager from packaging.version import Version, InvalidVersion @@ -404,8 +403,8 @@ def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) -def options_string(separator=u" ", **kwargs): - return separator.join(u"{}={}".format(k, v) for k, v in iteritems(kwargs)) +def options_string(separator=" ", **kwargs): + return separator.join("{}={}".format(k, v) for k, v in kwargs.items()) @contextmanager @@ -566,7 +565,7 @@ def exec( assert pid != 0 # ----------------- detect zombie - if platform_utils_provider.get().ProcessIsZombi_soft_check(os_ops, pid) is True: + if platform_utils_provider.get().ProcessIsZombie_soft_check(os_ops, pid) is True: internal_utils.send_log_debug("Postmaster process {} is a zombie.".format( pid, )) diff --git a/tests/requirements.txt b/tests/requirements.txt index fd3d7c75..b85c6074 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -2,7 +2,6 @@ pytest pytest-env pytest-xdist psutil -six psycopg2 testgres.os_ops>=3.3.3,<4.0.0 testgres.postgres_configuration>=0.2.2,<1.0.0 diff --git a/tests/test_testgres_common.py b/tests/test_testgres_common.py index 8604933e..32fab746 100644 --- a/tests/test_testgres_common.py +++ b/tests/test_testgres_common.py @@ -50,7 +50,6 @@ from contextlib import contextmanager import pytest -import six import logging import time import tempfile @@ -161,7 +160,7 @@ def test_version_management(self, node_svc: PostgresNodeService): version = get_pg_version2(node_svc.os_ops) with __class__.helper__get_node(node_svc) as node: - assert (isinstance(version, six.string_types)) + assert (isinstance(version, str)) assert (isinstance(node.version, PgVer)) assert (node.version == PgVer(version))