testgres 1.13.7__tar.gz → 1.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {testgres-1.13.7/testgres.egg-info → testgres-1.14.0}/PKG-INFO +3 -4
  2. {testgres-1.13.7 → testgres-1.14.0}/README.md +1 -2
  3. {testgres-1.13.7 → testgres-1.14.0}/pyproject.toml +1 -1
  4. {testgres-1.13.7 → testgres-1.14.0}/src/__init__.py +1 -1
  5. {testgres-1.13.7 → testgres-1.14.0}/src/api.py +11 -2
  6. {testgres-1.13.7 → testgres-1.14.0}/src/backup.py +1 -6
  7. {testgres-1.13.7 → testgres-1.14.0}/src/node.py +84 -73
  8. {testgres-1.13.7 → testgres-1.14.0}/src/node_app.py +40 -40
  9. {testgres-1.13.7 → testgres-1.14.0}/src/utils.py +28 -1
  10. {testgres-1.13.7 → testgres-1.14.0/testgres.egg-info}/PKG-INFO +3 -4
  11. {testgres-1.13.7 → testgres-1.14.0}/testgres.egg-info/SOURCES.txt +1 -0
  12. {testgres-1.13.7 → testgres-1.14.0}/testgres.egg-info/requires.txt +1 -1
  13. testgres-1.14.0/tests/test_api.py +30 -0
  14. {testgres-1.13.7 → testgres-1.14.0}/tests/test_testgres_common.py +6 -4
  15. {testgres-1.13.7 → testgres-1.14.0}/LICENSE +0 -0
  16. {testgres-1.13.7 → testgres-1.14.0}/setup.cfg +0 -0
  17. {testgres-1.13.7 → testgres-1.14.0}/src/cache.py +0 -0
  18. {testgres-1.13.7 → testgres-1.14.0}/src/config.py +0 -0
  19. {testgres-1.13.7 → testgres-1.14.0}/src/connection.py +0 -0
  20. {testgres-1.13.7 → testgres-1.14.0}/src/consts.py +0 -0
  21. {testgres-1.13.7 → testgres-1.14.0}/src/decorators.py +0 -0
  22. {testgres-1.13.7 → testgres-1.14.0}/src/defaults.py +0 -0
  23. {testgres-1.13.7 → testgres-1.14.0}/src/enums.py +0 -0
  24. {testgres-1.13.7 → testgres-1.14.0}/src/exceptions.py +0 -0
  25. {testgres-1.13.7 → testgres-1.14.0}/src/impl/internal_utils.py +0 -0
  26. {testgres-1.13.7 → testgres-1.14.0}/src/impl/platforms/internal_platform_utils.py +0 -0
  27. {testgres-1.13.7 → testgres-1.14.0}/src/impl/platforms/internal_platform_utils_factory.py +0 -0
  28. {testgres-1.13.7 → testgres-1.14.0}/src/impl/platforms/linux/internal_platform_utils.py +0 -0
  29. {testgres-1.13.7 → testgres-1.14.0}/src/impl/platforms/win32/internal_platform_utils.py +0 -0
  30. {testgres-1.13.7 → testgres-1.14.0}/src/impl/port_manager__generic.py +0 -0
  31. {testgres-1.13.7 → testgres-1.14.0}/src/impl/port_manager__this_host.py +0 -0
  32. {testgres-1.13.7 → testgres-1.14.0}/src/logger.py +0 -0
  33. {testgres-1.13.7 → testgres-1.14.0}/src/port_manager.py +0 -0
  34. {testgres-1.13.7 → testgres-1.14.0}/src/pubsub.py +0 -0
  35. {testgres-1.13.7 → testgres-1.14.0}/src/raise_error.py +0 -0
  36. {testgres-1.13.7 → testgres-1.14.0}/src/standby.py +0 -0
  37. {testgres-1.13.7 → testgres-1.14.0}/testgres.egg-info/dependency_links.txt +0 -0
  38. {testgres-1.13.7 → testgres-1.14.0}/testgres.egg-info/top_level.txt +0 -0
  39. {testgres-1.13.7 → testgres-1.14.0}/tests/test_config.py +0 -0
  40. {testgres-1.13.7 → testgres-1.14.0}/tests/test_os_ops_common.py +0 -0
  41. {testgres-1.13.7 → testgres-1.14.0}/tests/test_os_ops_local.py +0 -0
  42. {testgres-1.13.7 → testgres-1.14.0}/tests/test_os_ops_remote.py +0 -0
  43. {testgres-1.13.7 → testgres-1.14.0}/tests/test_raise_error.py +0 -0
  44. {testgres-1.13.7 → testgres-1.14.0}/tests/test_testgres_local.py +0 -0
  45. {testgres-1.13.7 → testgres-1.14.0}/tests/test_testgres_remote.py +0 -0
  46. {testgres-1.13.7 → testgres-1.14.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testgres
3
- Version: 1.13.7
3
+ Version: 1.14.0
4
4
  Summary: Testing utility for PostgreSQL and its extensions
5
5
  Author-email: Postgres Professional <testgres@postgrespro.ru>
6
6
  License: PostgreSQL
@@ -27,11 +27,10 @@ Requires-Dist: port-for>=0.4
27
27
  Requires-Dist: six>=1.9.0
28
28
  Requires-Dist: psutil
29
29
  Requires-Dist: packaging
30
- Requires-Dist: testgres.os_ops<3.0.0,>=2.1.0
30
+ Requires-Dist: testgres.os_ops<3.0.0,>=2.3.0
31
31
  Dynamic: license-file
32
32
 
33
- [![CI Status](https://img.shields.io/github/actions/workflow/status/postgrespro/testgres/.github/workflows/package-verification.yml?label=CI)](https://github.com/postgrespro/testgres/actions/workflows/package-verification.yml)
34
- [![codecov](https://codecov.io/gh/postgrespro/testgres/branch/master/graph/badge.svg)](https://codecov.io/gh/postgrespro/testgres)
33
+ [![CI Status](https://img.shields.io/github/actions/workflow/status/postgrespro/testgres/.github/workflows/ci.yml?label=CI)](https://github.com/postgrespro/testgres/actions/workflows/ci.yml)
35
34
  [![PyPI package version](https://badge.fury.io/py/testgres.svg)](https://badge.fury.io/py/testgres)
36
35
  [![PyPI python versions](https://img.shields.io/pypi/pyversions/testgres)](https://pypi.org/project/testgres)
37
36
  [![PyPI downloads](https://img.shields.io/pypi/dm/testgres)](https://pypi.org/project/testgres)
@@ -1,5 +1,4 @@
1
- [![CI Status](https://img.shields.io/github/actions/workflow/status/postgrespro/testgres/.github/workflows/package-verification.yml?label=CI)](https://github.com/postgrespro/testgres/actions/workflows/package-verification.yml)
2
- [![codecov](https://codecov.io/gh/postgrespro/testgres/branch/master/graph/badge.svg)](https://codecov.io/gh/postgrespro/testgres)
1
+ [![CI Status](https://img.shields.io/github/actions/workflow/status/postgrespro/testgres/.github/workflows/ci.yml?label=CI)](https://github.com/postgrespro/testgres/actions/workflows/ci.yml)
3
2
  [![PyPI package version](https://badge.fury.io/py/testgres.svg)](https://badge.fury.io/py/testgres)
4
3
  [![PyPI python versions](https://img.shields.io/pypi/pyversions/testgres)](https://pypi.org/project/testgres)
5
4
  [![PyPI downloads](https://img.shields.io/pypi/dm/testgres)](https://pypi.org/project/testgres)
@@ -65,7 +65,7 @@ dependencies = [
65
65
  "six>=1.9.0",
66
66
  "psutil",
67
67
  "packaging",
68
- "testgres.os_ops>=2.1.0,<3.0.0",
68
+ "testgres.os_ops>=2.3.0,<3.0.0",
69
69
  ]
70
70
 
71
71
  [project.urls]
@@ -56,7 +56,7 @@ from testgres.operations.os_ops import OsOperations, ConnectionParams
56
56
  from testgres.operations.local_ops import LocalOperations
57
57
  from testgres.operations.remote_ops import RemoteOperations
58
58
 
59
- __version__ = "1.13.7"
59
+ __version__ = "1.14.0"
60
60
 
61
61
  __all__ = [
62
62
  "get_new_node",
@@ -31,6 +31,10 @@ PostgresNode(name='...', port=..., base_dir='...')
31
31
  [(3,)]
32
32
  """
33
33
  from .node import PostgresNode
34
+ from testgres.operations.remote_ops import ConnectionParams
35
+ from testgres.operations.remote_ops import RemoteOperations
36
+
37
+ import typing
34
38
 
35
39
 
36
40
  def get_new_node(name=None, base_dir=None, **kwargs):
@@ -42,11 +46,16 @@ def get_new_node(name=None, base_dir=None, **kwargs):
42
46
  return PostgresNode(name=name, base_dir=base_dir, **kwargs)
43
47
 
44
48
 
45
- def get_remote_node(name=None, conn_params=None):
49
+ def get_remote_node(name=None, conn_params: typing.Optional[ConnectionParams] = None):
46
50
  """
47
51
  Simply a wrapper around :class:`.PostgresNode` constructor for remote node.
48
52
  See :meth:`.PostgresNode.__init__` for details.
49
53
  For remote connection you can add the next parameter:
50
54
  conn_params = ConnectionParams(host='127.0.0.1', ssh_key=None, username=default_username())
51
55
  """
52
- return get_new_node(name=name, conn_params=conn_params)
56
+
57
+ if conn_params is None:
58
+ raise ValueError("Argument 'conn_params' is None.")
59
+
60
+ os_ops = RemoteOperations(conn_params)
61
+ return PostgresNode(name=name, os_ops=os_ops)
@@ -152,12 +152,7 @@ class NodeBackup(object):
152
152
  # Build a new PostgresNode
153
153
  assert self.original_node is not None
154
154
 
155
- if (hasattr(self.original_node, "clone_with_new_name_and_base_dir")):
156
- node = self.original_node.clone_with_new_name_and_base_dir(name=name, base_dir=base_dir)
157
- else:
158
- # For backward compatibility
159
- NodeClass = self.original_node.__class__
160
- node = NodeClass(name=name, base_dir=base_dir, conn_params=self.original_node.os_ops.conn_params)
155
+ node = self.original_node.clone_with_new_name_and_base_dir(name=name, base_dir=base_dir)
161
156
 
162
157
  assert node is not None
163
158
  assert type(node) is self.original_node.__class__
@@ -2,7 +2,6 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import logging
5
- import os
6
5
  import signal
7
6
  import subprocess
8
7
 
@@ -91,7 +90,6 @@ from . import utils
91
90
  from .utils import \
92
91
  PgVer, \
93
92
  eprint, \
94
- get_bin_path2, \
95
93
  get_pg_version2, \
96
94
  execute_utility2, \
97
95
  options_string, \
@@ -101,7 +99,6 @@ from .raise_error import RaiseError
101
99
 
102
100
  from .backup import NodeBackup
103
101
 
104
- from testgres.operations.os_ops import ConnectionParams
105
102
  from testgres.operations.os_ops import OsOperations
106
103
  from testgres.operations.local_ops import LocalOperations
107
104
 
@@ -165,6 +162,7 @@ class PostgresNode(object):
165
162
 
166
163
  _name: typing.Optional[str]
167
164
  _port: typing.Optional[int]
165
+ _bin_dir: str
168
166
  _should_free_port: bool
169
167
  _os_ops: OsOperations
170
168
  _port_manager: typing.Optional[PortManager]
@@ -174,8 +172,7 @@ class PostgresNode(object):
174
172
  name=None,
175
173
  base_dir=None,
176
174
  port: typing.Optional[int] = None,
177
- conn_params: typing.Optional[ConnectionParams] = None,
178
- bin_dir=None,
175
+ bin_dir: typing.Optional[str] = None,
179
176
  prefix=None,
180
177
  os_ops: typing.Optional[OsOperations] = None,
181
178
  port_manager: typing.Optional[PortManager] = None):
@@ -191,14 +188,10 @@ class PostgresNode(object):
191
188
  port_manager: None or correct port manager object.
192
189
  """
193
190
  assert port is None or type(port) is int
191
+ assert bin_dir is None or type(bin_dir) is str
194
192
  assert os_ops is None or isinstance(os_ops, OsOperations)
195
193
  assert port_manager is None or isinstance(port_manager, PortManager)
196
194
 
197
- if conn_params is not None:
198
- assert type(conn_params) is ConnectionParams
199
-
200
- raise InvalidOperationException("conn_params is deprecated, please use os_ops parameter instead.")
201
-
202
195
  # private
203
196
  if os_ops is None:
204
197
  self._os_ops = __class__._get_os_ops()
@@ -210,9 +203,15 @@ class PostgresNode(object):
210
203
  assert self._os_ops is not None
211
204
  assert isinstance(self._os_ops, OsOperations)
212
205
 
213
- self._pg_version = PgVer(get_pg_version2(self._os_ops, bin_dir))
206
+ if bin_dir is not None:
207
+ self._bin_dir = bin_dir
208
+ else:
209
+ self._bin_dir = utils.get_bin_dir(self._os_ops)
210
+
211
+ assert type(self._bin_dir) is str
212
+
213
+ self._pg_version = PgVer(get_pg_version2(self._os_ops, self._bin_dir))
214
214
  self._base_dir = base_dir
215
- self._bin_dir = bin_dir
216
215
  self._prefix = prefix
217
216
  self._logger = None
218
217
  self._master = None
@@ -486,18 +485,17 @@ class PostgresNode(object):
486
485
  @property
487
486
  def base_dir(self):
488
487
  if not self._base_dir:
489
- self._base_dir = self.os_ops.mkdtemp(prefix=self._prefix or TMP_NODE)
488
+ self._base_dir = self._os_ops.mkdtemp(prefix=self._prefix or TMP_NODE)
490
489
 
491
490
  # NOTE: it's safe to create a new dir
492
- if not self.os_ops.path_exists(self._base_dir):
493
- self.os_ops.makedirs(self._base_dir)
491
+ if not self._os_ops.path_exists(self._base_dir):
492
+ self._os_ops.makedirs(self._base_dir)
494
493
 
495
494
  return self._base_dir
496
495
 
497
496
  @property
498
- def bin_dir(self):
499
- if not self._bin_dir:
500
- self._bin_dir = os.path.dirname(get_bin_path2(self.os_ops, "pg_config"))
497
+ def bin_dir(self) -> str:
498
+ assert type(self._bin_dir) is str
501
499
  return self._bin_dir
502
500
 
503
501
  @property
@@ -509,8 +507,8 @@ class PostgresNode(object):
509
507
  assert type(path) is str
510
508
 
511
509
  # NOTE: it's safe to create a new dir
512
- if not self.os_ops.path_exists(path):
513
- self.os_ops.makedirs(path)
510
+ if not self._os_ops.path_exists(path):
511
+ self._os_ops.makedirs(path)
514
512
 
515
513
  return path
516
514
 
@@ -587,7 +585,7 @@ class PostgresNode(object):
587
585
 
588
586
  ps_command = ['ps', '-o', 'pid=', '-p', str(node_pid)]
589
587
 
590
- ps_output = self.os_ops.exec_command(cmd=ps_command, shell=True, ignore_errors=True).decode('utf-8')
588
+ ps_output = self._os_ops.exec_command(cmd=ps_command, shell=True, ignore_errors=True).decode('utf-8')
591
589
  assert type(ps_output) is str
592
590
 
593
591
  if ps_output == "":
@@ -600,13 +598,13 @@ class PostgresNode(object):
600
598
 
601
599
  try:
602
600
  eprint('Force stopping node {0} with PID {1}'.format(self.name, node_pid))
603
- self.os_ops.kill(node_pid, signal.SIGKILL)
601
+ self._os_ops.kill(node_pid, signal.SIGKILL)
604
602
  except Exception:
605
603
  # The node has already stopped
606
604
  pass
607
605
 
608
606
  # Check that node stopped - print only column pid without headers
609
- ps_output = self.os_ops.exec_command(cmd=ps_command, shell=True, ignore_errors=True).decode('utf-8')
607
+ ps_output = self._os_ops.exec_command(cmd=ps_command, shell=True, ignore_errors=True).decode('utf-8')
610
608
  assert type(ps_output) is str
611
609
 
612
610
  if ps_output == "":
@@ -669,7 +667,7 @@ class PostgresNode(object):
669
667
 
670
668
  signal_name = self._os_ops.build_path(self.data_dir, "standby.signal")
671
669
  assert type(signal_name) is str
672
- self.os_ops.touch(signal_name)
670
+ self._os_ops.touch(signal_name)
673
671
  else:
674
672
  line += "standby_mode=on\n"
675
673
 
@@ -730,10 +728,10 @@ class PostgresNode(object):
730
728
 
731
729
  for f, num_lines in files:
732
730
  # skip missing files
733
- if not self.os_ops.path_exists(f):
731
+ if not self._os_ops.path_exists(f):
734
732
  continue
735
733
 
736
- file_lines = self.os_ops.readlines(f, num_lines, binary=True, encoding=None)
734
+ file_lines = self._os_ops.readlines(f, num_lines, binary=True, encoding=None)
737
735
  lines = b''.join(file_lines)
738
736
 
739
737
  # fill list
@@ -800,14 +798,14 @@ class PostgresNode(object):
800
798
 
801
799
  # filter lines in hba file
802
800
  # get rid of comments and blank lines
803
- hba_conf_file = self.os_ops.readlines(hba_conf)
801
+ hba_conf_file = self._os_ops.readlines(hba_conf)
804
802
  lines = [
805
803
  s for s in hba_conf_file
806
804
  if len(s.strip()) > 0 and not s.startswith('#')
807
805
  ]
808
806
 
809
807
  # write filtered lines
810
- self.os_ops.write(hba_conf, lines, truncate=True)
808
+ self._os_ops.write(hba_conf, lines, truncate=True)
811
809
 
812
810
  # replication-related settings
813
811
  if allow_streaming:
@@ -819,7 +817,7 @@ class PostgresNode(object):
819
817
  # get auth methods
820
818
  auth_local = get_auth_method('local')
821
819
  auth_host = get_auth_method('host')
822
- subnet_base = ".".join(self.os_ops.host.split('.')[:-1] + ['0'])
820
+ subnet_base = ".".join(self._os_ops.host.split('.')[:-1] + ['0'])
823
821
 
824
822
  new_lines = [
825
823
  u"local\treplication\tall\t\t\t{}\n".format(auth_local),
@@ -832,10 +830,10 @@ class PostgresNode(object):
832
830
  ] # yapf: disable
833
831
 
834
832
  # write missing lines
835
- self.os_ops.write(hba_conf, new_lines)
833
+ self._os_ops.write(hba_conf, new_lines)
836
834
 
837
835
  # overwrite config file
838
- self.os_ops.write(postgres_conf, '', truncate=True)
836
+ self._os_ops.write(postgres_conf, '', truncate=True)
839
837
 
840
838
  self.append_conf(fsync=fsync,
841
839
  max_worker_processes=MAX_WORKER_PROCESSES,
@@ -915,7 +913,7 @@ class PostgresNode(object):
915
913
  conf_text = ''
916
914
  for line in lines:
917
915
  conf_text += text_type(line) + '\n'
918
- self.os_ops.write(config_name, conf_text)
916
+ self._os_ops.write(config_name, conf_text)
919
917
 
920
918
  return self
921
919
 
@@ -948,7 +946,7 @@ class PostgresNode(object):
948
946
  _params += ["-D"] if self._pg_version >= PgVer('9.5') else []
949
947
  _params += [self.data_dir]
950
948
 
951
- data = execute_utility2(self.os_ops, _params, self.utils_log_file)
949
+ data = execute_utility2(self._os_ops, _params, self.utils_log_file)
952
950
 
953
951
  out_dict = {}
954
952
 
@@ -958,7 +956,14 @@ class PostgresNode(object):
958
956
 
959
957
  return out_dict
960
958
 
961
- def slow_start(self, replica=False, dbname='template1', username=None, max_attempts=0, exec_env=None):
959
+ def slow_start(
960
+ self,
961
+ replica: bool = False,
962
+ dbname: typing.Optional[str] = 'template1',
963
+ username: typing.Optional[str] = None,
964
+ max_attempts: int = 0,
965
+ exec_env: typing.Optional[typing.Dict[str, str]] = None,
966
+ ):
962
967
  """
963
968
  Starts the PostgreSQL instance and then polls the instance
964
969
  until it reaches the expected state (primary or replica). The state is checked
@@ -971,6 +976,8 @@ class PostgresNode(object):
971
976
  If False, waits for the instance to be in primary mode. Default is False.
972
977
  max_attempts:
973
978
  """
979
+ assert dbname is None or type(dbname) is str
980
+ assert username is None or type(username) is str
974
981
  assert exec_env is None or type(exec_env) is dict
975
982
 
976
983
  self.start(exec_env=exec_env)
@@ -992,7 +999,7 @@ class PostgresNode(object):
992
999
  self.poll_query_until(
993
1000
  query=query,
994
1001
  dbname=dbname,
995
- username=username or self.os_ops.username,
1002
+ username=username or self._os_ops.username,
996
1003
  suppress=suppressed_exceptions,
997
1004
  max_attempts=max_attempts,
998
1005
  )
@@ -1094,7 +1101,7 @@ class PostgresNode(object):
1094
1101
 
1095
1102
  def LOCAL__start_node():
1096
1103
  # 'error' will be None on Windows
1097
- _, _, error = execute_utility2(self.os_ops, _params, self.utils_log_file, verbose=True, exec_env=exec_env)
1104
+ _, _, error = execute_utility2(self._os_ops, _params, self.utils_log_file, verbose=True, exec_env=exec_env)
1098
1105
  assert error is None or type(error) is str
1099
1106
  if error and 'does not exist' in error:
1100
1107
  raise Exception(error)
@@ -1183,7 +1190,7 @@ class PostgresNode(object):
1183
1190
  "stop"
1184
1191
  ] + params # yapf: disable
1185
1192
 
1186
- execute_utility2(self.os_ops, _params, self.utils_log_file)
1193
+ execute_utility2(self._os_ops, _params, self.utils_log_file)
1187
1194
 
1188
1195
  self._manually_started_pm_pid = None
1189
1196
 
@@ -1207,7 +1214,10 @@ class PostgresNode(object):
1207
1214
 
1208
1215
  assert x.node_status == NodeStatus.Running
1209
1216
  assert type(x.pid) is int
1210
- sig = signal.SIGKILL if os.name != 'nt' else signal.SIGBREAK
1217
+ if self._os_ops.get_platform() == "win32":
1218
+ sig = 21 # signal.SIGBREAK
1219
+ else:
1220
+ sig = signal.SIGKILL
1211
1221
  if someone is None:
1212
1222
  self._os_ops.kill(x.pid, sig)
1213
1223
  self._manually_started_pm_pid = None
@@ -1240,7 +1250,7 @@ class PostgresNode(object):
1240
1250
  ] + params # yapf: disable
1241
1251
 
1242
1252
  try:
1243
- error_code, out, error = execute_utility2(self.os_ops, _params, self.utils_log_file, verbose=True)
1253
+ error_code, out, error = execute_utility2(self._os_ops, _params, self.utils_log_file, verbose=True)
1244
1254
  if error and 'could not start server' in error:
1245
1255
  raise ExecUtilException
1246
1256
  except ExecUtilException as e:
@@ -1269,7 +1279,7 @@ class PostgresNode(object):
1269
1279
  "reload"
1270
1280
  ] + params # yapf: disable
1271
1281
 
1272
- execute_utility2(self.os_ops, _params, self.utils_log_file)
1282
+ execute_utility2(self._os_ops, _params, self.utils_log_file)
1273
1283
 
1274
1284
  return self
1275
1285
 
@@ -1291,7 +1301,7 @@ class PostgresNode(object):
1291
1301
  "promote"
1292
1302
  ] # yapf: disable
1293
1303
 
1294
- execute_utility2(self.os_ops, _params, self.utils_log_file)
1304
+ execute_utility2(self._os_ops, _params, self.utils_log_file)
1295
1305
 
1296
1306
  # for versions below 10 `promote` is asynchronous so we need to wait
1297
1307
  # until it actually becomes writable
@@ -1326,7 +1336,7 @@ class PostgresNode(object):
1326
1336
  "-w" # wait
1327
1337
  ] + params # yapf: disable
1328
1338
 
1329
- return execute_utility2(self.os_ops, _params, self.utils_log_file)
1339
+ return execute_utility2(self._os_ops, _params, self.utils_log_file)
1330
1340
 
1331
1341
  def release_resources(self):
1332
1342
  """
@@ -1362,7 +1372,7 @@ class PostgresNode(object):
1362
1372
  else:
1363
1373
  rm_dir = self.data_dir # just data, save logs
1364
1374
 
1365
- self.os_ops.rmdirs(rm_dir, ignore_errors=False)
1375
+ self._os_ops.rmdirs(rm_dir, ignore_errors=False)
1366
1376
 
1367
1377
  if release_resources:
1368
1378
  self._release_resources()
@@ -1457,7 +1467,7 @@ class PostgresNode(object):
1457
1467
  self._get_bin_path("psql"),
1458
1468
  "-p", str(port),
1459
1469
  "-h", host,
1460
- "-U", username or self.os_ops.username,
1470
+ "-U", username or self._os_ops.username,
1461
1471
  "-d", dbname or default_dbname(),
1462
1472
  "-X", # no .psqlrc
1463
1473
  "-A", # unaligned output
@@ -1477,7 +1487,7 @@ class PostgresNode(object):
1477
1487
  else:
1478
1488
  raise QueryException('Query or filename must be provided')
1479
1489
 
1480
- return self.os_ops.exec_command(
1490
+ return self._os_ops.exec_command(
1481
1491
  psql_params,
1482
1492
  verbose=True,
1483
1493
  input=input,
@@ -1560,9 +1570,9 @@ class PostgresNode(object):
1560
1570
  # Generate tmpfile or tmpdir
1561
1571
  def tmpfile():
1562
1572
  if format == DumpFormat.Directory:
1563
- fname = self.os_ops.mkdtemp(prefix=TMP_DUMP)
1573
+ fname = self._os_ops.mkdtemp(prefix=TMP_DUMP)
1564
1574
  else:
1565
- fname = self.os_ops.mkstemp(prefix=TMP_DUMP)
1575
+ fname = self._os_ops.mkstemp(prefix=TMP_DUMP)
1566
1576
  return fname
1567
1577
 
1568
1578
  filename = filename or tmpfile()
@@ -1572,7 +1582,7 @@ class PostgresNode(object):
1572
1582
  "-p", str(self.port),
1573
1583
  "-h", self.host,
1574
1584
  "-f", filename,
1575
- "-U", username or self.os_ops.username,
1585
+ "-U", username or self._os_ops.username,
1576
1586
  "-d", dbname or default_dbname(),
1577
1587
  "-F", format.value
1578
1588
  ] # yapf: disable
@@ -1581,7 +1591,7 @@ class PostgresNode(object):
1581
1591
  if options:
1582
1592
  _params.extend(options)
1583
1593
 
1584
- execute_utility2(self.os_ops, _params, self.utils_log_file)
1594
+ execute_utility2(self._os_ops, _params, self.utils_log_file)
1585
1595
 
1586
1596
  return filename
1587
1597
 
@@ -1597,7 +1607,7 @@ class PostgresNode(object):
1597
1607
 
1598
1608
  # Set default arguments
1599
1609
  dbname = dbname or default_dbname()
1600
- username = username or self.os_ops.username
1610
+ username = username or self._os_ops.username
1601
1611
 
1602
1612
  _params = [
1603
1613
  self._get_bin_path("pg_restore"),
@@ -1610,20 +1620,22 @@ class PostgresNode(object):
1610
1620
 
1611
1621
  # try pg_restore if dump is binary format, and psql if not
1612
1622
  try:
1613
- execute_utility2(self.os_ops, _params, self.utils_log_name)
1623
+ execute_utility2(self._os_ops, _params, self.utils_log_name)
1614
1624
  except ExecUtilException:
1615
1625
  self.psql(filename=filename, dbname=dbname, username=username)
1616
1626
 
1617
1627
  @method_decorator(positional_args_hack(['dbname', 'query']))
1618
- def poll_query_until(self,
1619
- query,
1620
- dbname=None,
1621
- username=None,
1622
- max_attempts=0,
1623
- sleep_time: typing.Union[int, float] = 1,
1624
- expected=True,
1625
- commit=True,
1626
- suppress=None):
1628
+ def poll_query_until(
1629
+ self,
1630
+ query,
1631
+ dbname: typing.Optional[str] = None,
1632
+ username: typing.Optional[str] = None,
1633
+ max_attempts: int = 0,
1634
+ sleep_time: typing.Union[int, float] = 1,
1635
+ expected: bool = True,
1636
+ commit: bool = True,
1637
+ suppress: typing.Optional[typing.Iterable[BaseException]] = None,
1638
+ ) -> None:
1627
1639
  """
1628
1640
  Run a query once per second until it returns 'expected'.
1629
1641
  Query should return a single value (1 row, 1 column).
@@ -1650,6 +1662,8 @@ class PostgresNode(object):
1650
1662
  assert max_attempts >= 0
1651
1663
  assert type(sleep_time) in [int, float]
1652
1664
  assert sleep_time > 0
1665
+ assert suppress is None or isinstance(suppress, typing.Iterable)
1666
+
1653
1667
  attempts = 0
1654
1668
  while max_attempts == 0 or attempts < max_attempts:
1655
1669
  try:
@@ -1875,13 +1889,13 @@ class PostgresNode(object):
1875
1889
  self._get_bin_path("pgbench"),
1876
1890
  "-p", str(self.port),
1877
1891
  "-h", self.host,
1878
- "-U", username or self.os_ops.username
1892
+ "-U", username or self._os_ops.username
1879
1893
  ] + options # yapf: disable
1880
1894
 
1881
1895
  # should be the last one
1882
1896
  _params.append(dbname)
1883
1897
 
1884
- proc = self.os_ops.exec_command(_params, stdout=stdout, stderr=stderr, wait_exit=True, get_process=True)
1898
+ proc = self._os_ops.exec_command(_params, stdout=stdout, stderr=stderr, wait_exit=True, get_process=True)
1885
1899
 
1886
1900
  return proc
1887
1901
 
@@ -1948,7 +1962,7 @@ class PostgresNode(object):
1948
1962
  self._get_bin_path("pgbench"),
1949
1963
  "-p", str(self.port),
1950
1964
  "-h", self.host,
1951
- "-U", username or self.os_ops.username
1965
+ "-U", username or self._os_ops.username
1952
1966
  ] + options # yapf: disable
1953
1967
 
1954
1968
  for key, value in iteritems(kwargs):
@@ -1965,7 +1979,7 @@ class PostgresNode(object):
1965
1979
  # should be the last one
1966
1980
  _params.append(dbname)
1967
1981
 
1968
- return execute_utility2(self.os_ops, _params, self.utils_log_file)
1982
+ return execute_utility2(self._os_ops, _params, self.utils_log_file)
1969
1983
 
1970
1984
  def connect(self,
1971
1985
  dbname=None,
@@ -2054,9 +2068,9 @@ class PostgresNode(object):
2054
2068
  assert isinstance(self._os_ops, OsOperations)
2055
2069
 
2056
2070
  # parse postgresql.auto.conf
2057
- path = self.os_ops.build_path(self.data_dir, config)
2071
+ path = self._os_ops.build_path(self.data_dir, config)
2058
2072
 
2059
- lines = self.os_ops.readlines(path)
2073
+ lines = self._os_ops.readlines(path)
2060
2074
  current_options = {}
2061
2075
  current_directives = []
2062
2076
  for line in lines:
@@ -2103,7 +2117,7 @@ class PostgresNode(object):
2103
2117
  for directive in current_directives:
2104
2118
  auto_conf += directive + "\n"
2105
2119
 
2106
- self.os_ops.write(path, auto_conf, truncate=True)
2120
+ self._os_ops.write(path, auto_conf, truncate=True)
2107
2121
 
2108
2122
  def upgrade_from(self, old_node, options=None, expect_error=False):
2109
2123
  """
@@ -2138,7 +2152,7 @@ class PostgresNode(object):
2138
2152
  ]
2139
2153
  upgrade_command += options
2140
2154
 
2141
- return self.os_ops.exec_command(upgrade_command, expect_error=expect_error)
2155
+ return self._os_ops.exec_command(upgrade_command, expect_error=expect_error)
2142
2156
 
2143
2157
  def _release_resources(self):
2144
2158
  self._free_port()
@@ -2162,12 +2176,9 @@ class PostgresNode(object):
2162
2176
  def _get_bin_path(self, filename):
2163
2177
  assert self._os_ops is not None
2164
2178
  assert isinstance(self._os_ops, OsOperations)
2179
+ assert type(self._bin_dir) is str
2165
2180
 
2166
- if self.bin_dir:
2167
- bin_path = self._os_ops.build_path(self.bin_dir, filename)
2168
- else:
2169
- bin_path = get_bin_path2(self.os_ops, filename)
2170
- return bin_path
2181
+ return self._os_ops.build_path(self._bin_dir, filename)
2171
2182
 
2172
2183
  @staticmethod
2173
2184
  def _escape_config_value(value):
@@ -3,9 +3,6 @@ from .node import LocalOperations
3
3
  from .node import PostgresNode
4
4
  from .node import PortManager
5
5
 
6
- import os
7
- import platform
8
- import tempfile
9
6
  import typing
10
7
 
11
8
 
@@ -16,7 +13,7 @@ T_LIST_STR = typing.List[str]
16
13
  class NodeApp:
17
14
  _test_path: str
18
15
  _os_ops: OsOperations
19
- _port_manager: PortManager
16
+ _port_manager: typing.Optional[PortManager]
20
17
  _nodes_to_cleanup: typing.List[PostgresNode]
21
18
 
22
19
  def __init__(
@@ -39,7 +36,7 @@ class NodeApp:
39
36
 
40
37
  if test_path is None:
41
38
  self._test_path = os_ops.cwd()
42
- elif os.path.isabs(test_path):
39
+ elif self._os_ops.is_abs_path(test_path):
43
40
  self._test_path = test_path
44
41
  else:
45
42
  self._test_path = os_ops.build_path(os_ops.cwd(), test_path)
@@ -60,7 +57,7 @@ class NodeApp:
60
57
  return self._os_ops
61
58
 
62
59
  @property
63
- def port_manager(self) -> PortManager:
60
+ def port_manager(self) -> typing.Optional[PortManager]:
64
61
  assert self._port_manager is None or isinstance(self._port_manager, PortManager)
65
62
  return self._port_manager
66
63
 
@@ -158,6 +155,8 @@ class NodeApp:
158
155
 
159
156
  # set major version
160
157
  pg_version_file = self._os_ops.read(self._os_ops.build_path(node.data_dir, 'PG_VERSION'))
158
+
159
+ # What is it ???
161
160
  node.major_version_str = str(pg_version_file.rstrip())
162
161
  node.major_version = float(node.major_version_str)
163
162
 
@@ -200,7 +199,7 @@ class NodeApp:
200
199
 
201
200
  # Define delayed propertyes
202
201
  if "unix_socket_directories" not in options.keys():
203
- options["unix_socket_directories"] = __class__._gettempdir_for_socket()
202
+ options["unix_socket_directories"] = self._gettempdir_for_socket()
204
203
 
205
204
  # Set config values
206
205
  node.set_auto_conf(options)
@@ -260,49 +259,50 @@ class NodeApp:
260
259
  return updated_params
261
260
  return __class__._paramlist_append(user_params, updated_params, param)
262
261
 
263
- @staticmethod
264
- def _gettempdir_for_socket() -> str:
265
- platform_system_name = platform.system().lower()
266
-
267
- if platform_system_name == "windows":
268
- return __class__._gettempdir()
269
-
270
- #
271
- # [2025-02-17] Hot fix.
272
- #
273
- # Let's use hard coded path as Postgres likes.
274
- #
275
- # pg_config_manual.h:
276
- #
277
- # #ifndef WIN32
278
- # #define DEFAULT_PGSOCKET_DIR "/tmp"
279
- # #else
280
- # #define DEFAULT_PGSOCKET_DIR ""
281
- # #endif
282
- #
283
- # On the altlinux-10 tempfile.gettempdir() may return
284
- # the path to "private" temp directiry - "/temp/.private/<username>/"
285
- #
286
- # But Postgres want to find a socket file in "/tmp" (see above).
287
- #
262
+ def _gettempdir_for_socket(self) -> str:
263
+ assert isinstance(self._os_ops, OsOperations)
288
264
 
289
- return "/tmp"
265
+ platform_name = self._os_ops.get_platform()
266
+
267
+ if platform_name == "linux":
268
+ #
269
+ # [2025-02-17] Hot fix.
270
+ #
271
+ # Let's use hard coded path as Postgres likes.
272
+ #
273
+ # pg_config_manual.h:
274
+ #
275
+ # #ifndef WIN32
276
+ # #define DEFAULT_PGSOCKET_DIR "/tmp"
277
+ # #else
278
+ # #define DEFAULT_PGSOCKET_DIR ""
279
+ # #endif
280
+ #
281
+ # On the altlinux-10 tempfile.gettempdir() may return
282
+ # the path to "private" temp directiry - "/temp/.private/<username>/"
283
+ #
284
+ # But Postgres want to find a socket file in "/tmp" (see above).
285
+ #
286
+ return "/tmp"
287
+
288
+ return self._gettempdir()
289
+
290
+ def _gettempdir(self) -> str:
291
+ assert isinstance(self._os_ops, OsOperations)
290
292
 
291
- @staticmethod
292
- def _gettempdir() -> str:
293
- v = tempfile.gettempdir()
293
+ v = self._os_ops.get_tempdir()
294
294
 
295
295
  #
296
296
  # Paranoid checks
297
297
  #
298
298
  if type(v) is str:
299
- __class__._raise_bugcheck("tempfile.gettempdir returned a value with type {0}.".format(type(v).__name__))
299
+ __class__._raise_bugcheck("os_ops.get_tempdir returned a value with type {0}.".format(type(v).__name__))
300
300
 
301
301
  if v == "":
302
- __class__._raise_bugcheck("tempfile.gettempdir returned an empty string.")
302
+ __class__._raise_bugcheck("os_ops.get_tempdir returned an empty string.")
303
303
 
304
- if not os.path.exists(v):
305
- __class__._raise_bugcheck("tempfile.gettempdir returned a not exist path [{0}].".format(v))
304
+ if not self._os_ops.path_exists(v):
305
+ __class__._raise_bugcheck("os_ops.get_tempdir returned a not exist path [{0}].".format(v))
306
306
 
307
307
  # OK
308
308
  return v
@@ -140,7 +140,7 @@ def get_bin_path2(os_ops: OsOperations, filename):
140
140
  assert isinstance(os_ops, OsOperations)
141
141
 
142
142
  # check if it's already absolute
143
- if os.path.isabs(filename):
143
+ if os_ops.is_abs_path(filename):
144
144
  return filename
145
145
  if isinstance(os_ops, RemoteOperations):
146
146
  pg_config = os.environ.get("PG_CONFIG_REMOTE") or os.environ.get("PG_CONFIG")
@@ -165,6 +165,33 @@ def get_bin_path2(os_ops: OsOperations, filename):
165
165
  return filename
166
166
 
167
167
 
168
+ def get_bin_dir(os_ops: OsOperations) -> str:
169
+ assert os_ops is not None
170
+ assert isinstance(os_ops, OsOperations)
171
+
172
+ if isinstance(os_ops, RemoteOperations):
173
+ pg_config = os.environ.get("PG_CONFIG_REMOTE") or os.environ.get("PG_CONFIG")
174
+ else:
175
+ # try PG_CONFIG - get from local machine
176
+ pg_config = os.environ.get("PG_CONFIG")
177
+
178
+ if pg_config:
179
+ bindir = get_pg_config(pg_config, os_ops)["BINDIR"]
180
+ return bindir
181
+
182
+ # try PG_BIN
183
+ pg_bin = os_ops.environ("PG_BIN")
184
+ if pg_bin:
185
+ return pg_bin
186
+
187
+ pg_config_path = os_ops.find_executable('pg_config')
188
+ if pg_config_path:
189
+ bindir = get_pg_config(pg_config_path)["BINDIR"]
190
+ return bindir
191
+
192
+ raise RuntimeError("BinDir is not detected.")
193
+
194
+
168
195
  def get_pg_config(pg_config_path=None, os_ops=None):
169
196
  """
170
197
  Return output of pg_config (provided that it is installed).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testgres
3
- Version: 1.13.7
3
+ Version: 1.14.0
4
4
  Summary: Testing utility for PostgreSQL and its extensions
5
5
  Author-email: Postgres Professional <testgres@postgrespro.ru>
6
6
  License: PostgreSQL
@@ -27,11 +27,10 @@ Requires-Dist: port-for>=0.4
27
27
  Requires-Dist: six>=1.9.0
28
28
  Requires-Dist: psutil
29
29
  Requires-Dist: packaging
30
- Requires-Dist: testgres.os_ops<3.0.0,>=2.1.0
30
+ Requires-Dist: testgres.os_ops<3.0.0,>=2.3.0
31
31
  Dynamic: license-file
32
32
 
33
- [![CI Status](https://img.shields.io/github/actions/workflow/status/postgrespro/testgres/.github/workflows/package-verification.yml?label=CI)](https://github.com/postgrespro/testgres/actions/workflows/package-verification.yml)
34
- [![codecov](https://codecov.io/gh/postgrespro/testgres/branch/master/graph/badge.svg)](https://codecov.io/gh/postgrespro/testgres)
33
+ [![CI Status](https://img.shields.io/github/actions/workflow/status/postgrespro/testgres/.github/workflows/ci.yml?label=CI)](https://github.com/postgrespro/testgres/actions/workflows/ci.yml)
35
34
  [![PyPI package version](https://badge.fury.io/py/testgres.svg)](https://badge.fury.io/py/testgres)
36
35
  [![PyPI python versions](https://img.shields.io/pypi/pyversions/testgres)](https://pypi.org/project/testgres)
37
36
  [![PyPI downloads](https://img.shields.io/pypi/dm/testgres)](https://pypi.org/project/testgres)
@@ -32,6 +32,7 @@ testgres.egg-info/SOURCES.txt
32
32
  testgres.egg-info/dependency_links.txt
33
33
  testgres.egg-info/requires.txt
34
34
  testgres.egg-info/top_level.txt
35
+ tests/test_api.py
35
36
  tests/test_config.py
36
37
  tests/test_os_ops_common.py
37
38
  tests/test_os_ops_local.py
@@ -3,4 +3,4 @@ port-for>=0.4
3
3
  six>=1.9.0
4
4
  psutil
5
5
  packaging
6
- testgres.os_ops<3.0.0,>=2.1.0
6
+ testgres.os_ops<3.0.0,>=2.3.0
@@ -0,0 +1,30 @@
1
+ from src import api as testgres_api
2
+ from src.node import PostgresNode
3
+
4
+ from tests.helpers.global_data import OsOpsDescrs
5
+
6
+
7
+ class TestAPI:
8
+ def test_001__get_new_node(self):
9
+ C_NODE_NAME = "abc"
10
+
11
+ with testgres_api.get_new_node(name=C_NODE_NAME) as node:
12
+ assert type(node) is PostgresNode
13
+ assert node.name == C_NODE_NAME
14
+ node.init()
15
+ node.slow_start()
16
+ node.stop()
17
+ return
18
+
19
+ def test_001__get_remote_node(self):
20
+ C_NODE_NAME = "abc"
21
+
22
+ conn_params = OsOpsDescrs.sm_remote_conn_params
23
+
24
+ with testgres_api.get_remote_node(name=C_NODE_NAME, conn_params=conn_params) as node:
25
+ assert type(node) is PostgresNode
26
+ assert node.name == C_NODE_NAME
27
+ node.init()
28
+ node.slow_start()
29
+ node.stop()
30
+ return
@@ -94,8 +94,8 @@ class TestTestgresCommon:
94
94
 
95
95
  # Author: Mark G.
96
96
  assert v.major == 1
97
- assert v.minor == 13
98
- assert v.micro == 7
97
+ assert v.minor == 14
98
+ assert v.micro == 0
99
99
 
100
100
  assert str(v) == testgres_version
101
101
  return
@@ -2422,13 +2422,14 @@ where c.relname=%s;"""
2422
2422
  class tag_rmdirs_protector:
2423
2423
  _os_ops: OsOperations
2424
2424
  _cwd: str
2425
- _old_rmdirs: any
2425
+ _old_rmdirs: typing.Optional[typing.Callable]
2426
2426
  _cwd: str
2427
2427
 
2428
2428
  def __init__(self, os_ops: OsOperations):
2429
2429
  self._os_ops = os_ops
2430
2430
  self._cwd = os.path.abspath(os_ops.cwd())
2431
2431
  self._old_rmdirs = os_ops.rmdirs
2432
+ return
2432
2433
 
2433
2434
  def __enter__(self):
2434
2435
  assert self._os_ops.rmdirs == self._old_rmdirs
@@ -2437,6 +2438,7 @@ where c.relname=%s;"""
2437
2438
 
2438
2439
  def __exit__(self, exc_type, exc_val, exc_tb):
2439
2440
  assert self._os_ops.rmdirs == self.proxy__rmdirs
2441
+ assert isinstance(self._old_rmdirs, typing.Callable)
2440
2442
  self._os_ops.rmdirs = self._old_rmdirs
2441
2443
  return False
2442
2444
 
@@ -2465,7 +2467,7 @@ where c.relname=%s;"""
2465
2467
  assert node_app.os_ops is os_ops
2466
2468
 
2467
2469
  with pytest.raises(expected_exception=BaseException) as x:
2468
- node_app.make_empty(base_dir=None)
2470
+ node_app.make_empty(base_dir=None) # type: ignore
2469
2471
 
2470
2472
  if type(x.value) is AssertionError:
2471
2473
  pass
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes