1818import warnings
1919import tempfile
2020from contextlib import closing
21+ from operator import attrgetter
2122
2223from . import api as pg_api
2324from . import configfile
24- from . import pg_config
25+ from . import installation as pg_inn
2526from . import exceptions as pg_exc
2627from . import driver as pg_driver
2728
29+ DEFAULT_CLUSTER_ENCODING = 'utf-8'
2830DEFAULT_CONFIG_FILENAME = 'postgresql.conf'
2931DEFAULT_HBA_FILENAME = 'pg_hba.conf'
3032DEFAULT_PID_FILENAME = 'postmaster.pid'
3840 'numeric' : '--lc-numeric' ,
3941 'time' : '--lc-time' ,
4042 'authentication' : '-A' ,
41- 'superusername ' : '-U' ,
43+ 'user ' : '-U' ,
4244}
4345
4446class Cluster (pg_api .Cluster ):
@@ -48,12 +50,29 @@ class Cluster(pg_api.Cluster):
4850 Provides mechanisms to start, stop, restart, kill, drop, and configure a
4951 cluster(data directory).
5052 """
53+ installation = None
54+ DEFAULT_CLUSTER_ENCODING = DEFAULT_CLUSTER_ENCODING
55+ DEFAULT_CONFIG_FILENAME = DEFAULT_CONFIG_FILENAME
56+ DEFAULT_PID_FILENAME = DEFAULT_PID_FILENAME
57+ DEFAULT_HBA_FILENAME = DEFAULT_HBA_FILENAME
58+
59+ ife_ancestor = property (attrgetter ('installation' ))
60+ def ife_snapshot_text (self ):
61+ return self .data_directory + ' [' + (
62+ 'running: ' + str (self .get_pid_from_file ())
63+ if self .running () else 'not running'
64+ ) + ']'
65+
66+ @property
67+ def daemon_path (self ):
68+ return self .installation .postmaster or self .installation .postgres
69+
5170 def get_pid_from_file (self ):
5271 """
5372 The current pid from the postmaster.pid file.
5473 """
5574 try :
56- with closing ( open (os .path .join (self .data_directory , DEFAULT_PID_FILENAME ) )) as f :
75+ with open (os .path .join (self .data_directory , self . DEFAULT_PID_FILENAME )) as f :
5776 return int (f .readline ())
5877 except IOError as e :
5978 if e .errno in (errno .EIO , errno .ENOENT ):
@@ -69,24 +88,21 @@ def settings(self):
6988 def hba_file (self ):
7089 return self .settings .get (
7190 'hba_file' ,
72- os .path .join (self .data_directory , DEFAULT_HBA_FILENAME )
91+ os .path .join (self .data_directory , self . DEFAULT_HBA_FILENAME )
7392 )
7493
94+ @classmethod
95+ def from_pg_config_path (type , data_directory , pg_config_path ):
96+ "Create the cluster using the data_directory and the *path* to pg_config"
97+ return type (data_directory , pg_inn .Installation (pg_config_path ))
98+
7599 def __init__ (self ,
76100 data_directory : "path to the data directory" ,
77- pg_config_path : "path to pg_config to use" = 'pg_config' ,
78- pg_config_data : "pg_config data to use; uses _path if None" = None
101+ installation : "postgresql.installation.Installation" ,
79102 ):
80103 self .data_directory = os .path .abspath (data_directory )
81- self .pgsql_dot_conf = os .path .join (data_directory , DEFAULT_CONFIG_FILENAME )
82- if pg_config_data is None :
83- self .config = pg_config .dictionary (pg_config_path )
84- else :
85- self .config = pg_config_data
86-
87- self .postgres_path = os .path .join (self .config ['bindir' ], 'postmaster' )
88- if not os .path .exists (self .postgres_path ):
89- self .postgres_path = os .path .join (self .config ['bindir' ], 'postgres' )
104+ self .installation = installation
105+ self .pgsql_dot_conf = os .path .join (data_directory , self .DEFAULT_CONFIG_FILENAME )
90106 self .daemon_process = None
91107 self .last_known_pid = self .get_pid_from_file ()
92108
@@ -95,13 +111,12 @@ def __repr__(self):
95111 type (self ).__module__ ,
96112 type (self ).__name__ ,
97113 self .data_directory ,
98- self .postgres_path ,
114+ self .installation ,
99115 )
100116
101117 def init (self ,
102- initdb : "explicitly state the initdb binary to use" = None ,
103- verbose = False ,
104- superuserpass = None ,
118+ password : "Password to assign to the cluster's superuser(`user` keyword)." = None ,
119+ initdb : "[BEWARE] explicitly state the initdb binary to use" = None ,
105120 ** kw
106121 ):
107122 """
@@ -111,7 +126,16 @@ def init(self,
111126 `command_option_map` provides the mapping of keyword arguments
112127 to command options.
113128 """
129+ if initdb is None :
130+ initdb = self .installation .initdb
131+ if initdb is None :
132+ raise pg_exc .ClusterInitializationError (
133+ "unable to find `initdb` executable for installation: " + \
134+ repr (self .installation )
135+ )
136+
114137 # Transform keyword options into command options for the executable.
138+ kw .setdefault ('encoding' , self .DEFAULT_CLUSTER_ENCODING )
115139 opts = []
116140 for x in kw :
117141 if x in ('logfile' , 'extra_arguments' ):
@@ -124,14 +148,16 @@ def init(self,
124148 extra_args = tuple ([
125149 str (x ) for x in kw .get ('extra_arguments' , ())
126150 ])
127- verbose = (initdb_option_map ['verbose' ],) if verbose is False else ()
128- if superuserpass is not None :
129- pass
130151
131- if initdb is None :
132- initdb = os .path .join (self .config ['bindir' ], 'initdb' )
152+ supw_file = ()
153+ if password is not None :
154+ # got a superuserpass, it's
155+ supw_tmp = tempfile .NamedTemporaryFile (mode = 'w' , encoding = kw ['encoding' ])
156+ supw_tmp .write (password )
157+ supw_tmp .flush ()
158+ supw_file = ('--pwfile=' + supw_tmp .name ,)
133159
134- cmd = (initdb , '-D' , self .data_directory ) + verbose + tuple (opts ) + extra_args
160+ cmd = (initdb , '-D' , self .data_directory ) + tuple (opts ) + supw_file + extra_args
135161 p = sp .Popen (
136162 cmd ,
137163 close_fds = True ,
@@ -142,6 +168,9 @@ def init(self,
142168 p .stdin .close ()
143169
144170 rc = p .wait ()
171+ if password is not None :
172+ supw_tmp .close ()
173+
145174 if rc != 0 :
146175 raise pg_exc .InitDBError (cmd , rc , p .stderr .read ())
147176
@@ -168,9 +197,9 @@ def start(self,
168197 """
169198 if self .running ():
170199 return None
171- cmd = [self .postgres_path , '-D' , self .data_directory ]
200+ cmd = [self .daemon_path , '-D' , self .data_directory ]
172201 if settings is not None :
173- for k ,v in settings :
202+ for k ,v in dict ( settings ). items () :
174203 cmd .append ('--{k}={v}' .format (k = k ,v = v ))
175204
176205 p = sp .Popen (
@@ -205,7 +234,7 @@ def restart(self, timeout = 10):
205234 self .stop ()
206235 self .wait_until_stopped (timeout = timeout )
207236 if not self .running ():
208- raise ClusterError ("failed to shutdown cluster" )
237+ raise pg_exc . ClusterError ("failed to shutdown cluster" )
209238 self .start ()
210239 self .wait_until_started (timeout = timeout )
211240
@@ -253,7 +282,7 @@ def ready_for_connections(self):
253282 'port' ,
254283 ))
255284 if 'listen_addresses' not in d :
256- raise ClusterError (
285+ raise pg_exc . ClusterError (
257286 "postmaster pings can only be made to TCP/IP configurations"
258287 )
259288
@@ -288,7 +317,7 @@ def ready_for_connections(self):
288317
289318 def wait_until_started (self ,
290319 timeout : "how long to wait before throwing a timeout exception" = 10 ,
291- delay : "how long to sleep before re-testing" = 0.1
320+ delay : "how long to sleep before re-testing" = 0.05
292321 ):
293322 """
294323 After the `start` method is used, this can be ran in order to block until
@@ -308,15 +337,15 @@ def wait_until_started(self,
308337
309338 def wait_until_stopped (self ,
310339 timeout : "how long to wait before throwing a timeout exception" = 10 ,
311- delay : "how long to sleep before re-testing" = 0.1
340+ delay : "how long to sleep before re-testing" = 0.05
312341 ):
313342 """
314343 After the `stop` method is used, this can be ran in order to block until
315344 the cluster is shutdown.
316345
317346 Additionally, catching `ClusterTimeoutError` exceptions would be a
318347 starting point for making decisions about whether or not to issue a kill
319- to the postgres daemon.
348+ to the daemon.
320349 """
321350 start = time .time ()
322351 while self .running ():
0 commit comments