Skip to content

Commit 88bc5be

Browse files
author
James William Pye
committed
Fix a lot of bugs from the addition of test_connect and test_ssl_connect.
* Warning and Message propagation * sslmode now works under all conditions * Remove stale-state for the next attempt in the connect loop * correct itertools.interlace * Fix Password authentication
1 parent ac33cb1 commit 88bc5be

23 files changed

Lines changed: 951 additions & 239 deletions

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include AUTHORS
2+
include LICENSE

postgresql/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
# http://python.projects.postgresql.org
44
##
55
"""
6-
py-postgresql
7-
=============
8-
9-
py-postgresql is a Python library for using PostgreSQL. This includes low-level
6+
py-postgresql is a Python package for using PostgreSQL. This includes low-level
107
protocol tools, a driver(PG-API and DB-API), and cluster management tools.
118
"""
129
__author__ = "James William Pye <x@jwp.name>"
@@ -26,3 +23,5 @@
2623

2724
#'PGGSSLIB' : 'gsslib',
2825
#'PGLOCALEDIR' : 'localedir',
26+
27+
__docformat__ = 'reStructured Text'

postgresql/api.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import collections
2424
from abc import ABCMeta, abstractproperty, abstractmethod
2525
from operator import methodcaller, itemgetter
26+
2627
from .python.doc import Doc
2728
from .python.decorlib import propertydoc
2829

@@ -180,6 +181,7 @@ def ife_ancestry_snapshot_text(self) -> [(object, str, str)]:
180181
x, getattr(x, 'ife_label', type(x).__name__),
181182
(x.ife_snapshot_text() if hasattr(x, 'ife_snapshot_text') else str(x))
182183
) for x in a
184+
if getattr(x, '_ife_exclude_snapshot', False) is not True
183185
]
184186
return l
185187

@@ -224,10 +226,10 @@ def ife_emit(self,
224226
property get, etc).
225227
226228
To handle these additional results, the object is passed up through the
227-
ancestry. Any ancestor that has receptors will see the object
229+
ancestry. Any ancestor that has receptors will see the object.
228230
229231
If `obj` was consumed by a receptor, the receptor that consumed it will be
230-
returned
232+
returned.
231233
"""
232234
# Don't include ancestors without receptors.
233235
a = [
@@ -244,6 +246,7 @@ def ife_emit(self,
244246
if r is True and allow_consumption:
245247
# receptor indicated halt
246248
return (recep, ife)
249+
# if went unstopped
247250
return False
248251

249252
def ife_connect(self,
@@ -263,8 +266,9 @@ def ife_connect(self,
263266
self._ife_receptors = list(args)
264267
return
265268
# Prepend the list. Newer receptors are given priority.
266-
new = list(recept)
267-
self._ife_receptors = new.extend(self._ife_receptors)
269+
new = list(args)
270+
new.extend(self._ife_receptors)
271+
self._ife_receptors = new
268272

269273
def ife_sever(self,
270274
*args : (Receptor,)
@@ -337,31 +341,57 @@ def __repr__(self):
337341
)
338342
)
339343

340-
def ife_snapshot_text(self):
344+
def __str__(self):
345+
ss = getattr(self, 'snapshot', None)
346+
if ss is None:
347+
ss = obj.ife_ancestry_snapshot_text()
348+
sev = self.details.get('severity', self.ife_label).upper()
349+
detailstr = self.details_string
350+
if detailstr:
351+
detailstr = os.linesep + detailstr
352+
locstr = self.location_string
353+
if locstr:
354+
locstr = os.linesep + locstr
355+
356+
code = "" if not self.code or self.code == "00000" else '(' + self.code + ')'
357+
return sev + code + ': ' + self.message + locstr + detailstr + \
358+
os.linesep + \
359+
os.linesep.join([': '.join(x[1:]) for x in ss]) + \
360+
os.linesep
361+
362+
@property
363+
def location_string(self):
341364
details = self.details
342365
loc = [
343366
details.get(k, '?') for k in ('file', 'line', 'function')
344367
]
345-
locstr = (
346-
"" if tuple(loc) == ('?', '?', '?')
347-
else os.linesep + \
348-
"LOCATION: File {0!r}, "\
349-
"line {1!s}, in {2!s}".format(*loc)
368+
return (
369+
"" if loc == ['?', '?', '?']
370+
else "LOCATION: File {0!r}, "\
371+
"line {1!s}, in {2!s}".format(*loc)
350372
)
351373

352-
code = (os.linesep + "CODE: " + self.code) if self.code else ""
374+
@property
375+
def details_string(self):
376+
return os.linesep.join((
377+
': '.join((k.upper(), str(v)))
378+
for k, v in sorted(self.details.items(), key = itemgetter(0))
379+
if k not in ('message', 'severity', 'file', 'function', 'line')
380+
))
353381

382+
def ife_snapshot_text(self):
383+
details = self.details
384+
code = (os.linesep + "CODE: " + self.code) if self.code else ""
354385
sev = details.get('severity')
355386
sevmsg = ""
356387
if sev:
357388
sevmsg = os.linesep + "SEVERITY: " + sev.upper()
358-
detailstr = os.linesep.join((
359-
': '.join((k.upper(), str(v)))
360-
for k, v in sorted(details.items(), key = itemgetter(0))
361-
if k not in ('message', 'severity', 'file', 'function', 'line')
362-
))
389+
detailstr = self.details_string
363390
if detailstr:
364391
detailstr = os.linesep + detailstr
392+
locstr = self.location_string
393+
if locstr:
394+
locstr = os.linesep + locstr
365395
return self.message + code + sevmsg + detailstr + locstr
366396

367397
def emit(self):

postgresql/bin/pg_withcluster

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,95 @@ Entry point for pg_withcluster.
44
"""
55
import tempfile
66
import subprocess as sp
7+
import getpass
78
from optparse import OptionParser
89
from postgresql.cluster import Cluster
10+
from postgresql.installation import Installation
911

10-
op = OptionParser(
11-
"%prog [-p] [--version] {[setting=value], ...} [pg_config] command",
12-
version = '1.0'
13-
)
14-
op.allow_interspersed_args = False
15-
op.add_option(
16-
'-p',
17-
dest = 'look_in_path',
18-
help = 'Look for "pg_config" in the PATH environment',
19-
action = 'store_true',
20-
default = False
21-
)
22-
co, ca = op.parse_args(args[1:])
23-
if not ca:
24-
sys.stderr.write("no command given to run in cluster context")
25-
sys.exit(1)
12+
def main(args = sys.argv):
13+
op = OptionParser(
14+
"%prog [-p] [--version] {[setting=value], ...} [pg_config] command",
15+
version = '1.0'
16+
)
17+
op.allow_interspersed_args = False
18+
op.add_option(
19+
'-l',
20+
dest = 'logfile',
21+
help = 'logfile for the PostgreSQL daemon',
22+
default = '/dev/stderr',
23+
)
24+
op.add_option(
25+
'-p',
26+
dest = 'look_in_path',
27+
help = 'Look for "pg_config" in the PATH environment',
28+
action = 'store_true',
29+
default = False
30+
)
31+
op.add_option(
32+
'-N',
33+
dest = 'max_connections',
34+
help = 'Number',
35+
type = 'int',
36+
default = 4,
37+
)
38+
co, ca = op.parse_args(args[1:])
39+
if not ca:
40+
sys.stderr.write("no command given to run in cluster context")
41+
sys.exit(1)
2642

27-
i = 0
28-
for x in ca:
29-
if '=' not in x:
30-
break
31-
i += 1
32-
settings = ca[0:i]
33-
command = ca[i:]
34-
d = tempfile.mkdtemp()
35-
try:
36-
c = Cluster.create(d)
37-
c.settings.update([
38-
x.split('=', 1) for x in settings
39-
])
40-
c.control.start()
43+
max_con = co.max_connections
44+
if max_con <= 0:
45+
max_con = 4
46+
i = 0
47+
for x in ca:
48+
if '=' not in x:
49+
break
50+
i += 1
51+
settings = ca[0:i]
52+
command = ca[i:]
53+
d = tempfile.mkdtemp()
4154
try:
42-
p = sp.Popen(
43-
command,
44-
env = {
45-
},
55+
c = Cluster(d, inn)
56+
c.init(
57+
user = getpass.getuser(),
4658
)
47-
return p.wait()
59+
c.settings.update([
60+
x.split('=', 1) for x in settings
61+
])
62+
c.settings.update({
63+
'port' : '14327',
64+
'listen_addresses' : 'localhost',
65+
'max_connections' : str(co.max_connections),
66+
'shared_buffers' : str((co.max_connections * 2) + 50),
67+
})
68+
c.start(logfile = sys.stderr)
69+
c.wait_until_started()
70+
with c.connect() as con:
71+
pass
72+
try:
73+
p = sp.Popen(
74+
command,
75+
env = {
76+
'PGUSER' : getpass.getuser(),
77+
'PGHOST' : 'localhost',
78+
'PGPORT' : c.settings['port'],
79+
'PGDATABASE' : getpass.getuser(),
80+
'PGINSTALLATION' : c.installation.pg_config_path,
81+
'PGDATA' : d,
82+
'PG_WITHCLUSTER' : '1',
83+
},
84+
)
85+
while True:
86+
try:
87+
return p.wait()
88+
except OSError as e:
89+
if e.errno != errno.ESRCH:
90+
raise
91+
# sigh.
92+
finally:
93+
c.stop()
4894
finally:
49-
c.control.stop()
50-
finally:
51-
c.drop()
95+
c.drop()
96+
97+
if __name__ == '__main__':
98+
sys.exit(main())

postgresql/clientparameters.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from itertools import chain
1313
from functools import partial
1414

15-
1615
from . import iri as pg_iri
1716
from . import dsn as pg_dsn
1817
from . import pgpassfile as pg_pass

postgresql/cluster.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ def drop(self):
255255
w = pg_exc.ClusterWarning(
256256
'cluster failed to shutdown after kill',
257257
details = {
258-
'HINT' : 'Shared memory may be leaked.'
258+
'hint' : 'Shared memory may be leaked.'
259259
}
260260
)
261261
self.ife_descend(w)
@@ -444,16 +444,23 @@ def ready_for_connections(self):
444444
host = host or 'localhost',
445445
port = port or 5432,
446446
database = 'template1',
447+
sslmode = 'disable',
447448
).close()
448449
except pg_exc.ClientCannotConnectError as err:
449450
for (ssltried, sockc, x) in err.connection_failures:
450451
if self.installation.version_info[:2] < (8,1):
451-
if isinstance(x, pg_exc.UndefinedObjectError):
452+
if isinstance(x, (
453+
pg_exc.UndefinedObjectError,
454+
pg_exc.AuthenticationSpecificationError,
455+
)):
452456
# undefined user.. whatever...
453457
return True
454458
else:
455459
if isinstance(x, pg_exc.AuthenticationSpecificationError):
456460
return True
461+
# configuration file error. ya, that's probably not going to change.
462+
if isinstance(x, pg_exc.CFError):
463+
raise x
457464
if isinstance(x, pg_exc.ServerNotReadyError):
458465
e = x
459466
break
@@ -466,7 +473,6 @@ def ready_for_connections(self):
466473
def wait_until_started(self,
467474
timeout : "how long to wait before throwing a timeout exception" = 10,
468475
delay : "how long to sleep before re-testing" = 0.05,
469-
least : "minimum wait time" = 0.25,
470476
):
471477
"""
472478
After the `start` method is used, this can be ran in order to block
@@ -492,16 +498,15 @@ def wait_until_started(self,
492498
self.ife_descend(e)
493499
e.raise_exception()
494500
else:
495-
if checkpoint - start > least:
496-
e = pg_exc.ClusterNotRunningError(
497-
"postgresql daemon has not been started"
498-
)
499-
self.ife_descend(e)
500-
return e.raise_exception()
501+
e = pg_exc.ClusterNotRunningError(
502+
"postgresql daemon has not been started"
503+
)
504+
self.ife_descend(e)
505+
return e.raise_exception()
501506
r = self.ready_for_connections()
502507

503508
checkpoint = time.time()
504-
if r is True and checkpoint - start > least:
509+
if r is True:
505510
break
506511

507512
if checkpoint - start >= timeout:

postgresql/configfile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
'PostgreSQL configuration file parser and editor functions.'
66
import sys
77
import os
8-
import postgresql.string as pg_str
9-
import postgresql.api as pg_api
8+
from . import string as pg_str
9+
from . import api as pg_api
1010

1111
quote = "'"
1212
comment = '#'

0 commit comments

Comments
 (0)