Skip to content

Commit f6bbd69

Browse files
committed
Desupport old Python versions, require 2.7 or 3.5+
1 parent af81623 commit f6bbd69

12 files changed

Lines changed: 56 additions & 176 deletions

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ language: python
55

66
python:
77
- "2.7"
8-
- "3.4"
98
- "3.5"
109
- "3.6"
1110
- "3.7"
11+
- "3.8"
1212

1313
install:
1414
- pip install .

docs/about.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ D'Arcy (darcy@druid.net) renamed it to PyGreSQL starting with
3737
version 2.0 and serves as the "BDFL" of PyGreSQL.
3838

3939
The current version PyGreSQL 5.2 needs PostgreSQL 9.0 to 9.6 or 10 to 12, and
40-
Python 2.6, 2.7 or 3.3 to 3.8. If you need to support older PostgreSQL versions
41-
or older Python 2.x versions, you can resort to the PyGreSQL 4.x versions that
40+
Python 2.7 or 3.5 to 3.8. If you need to support older PostgreSQL versions or
41+
older Python 2.x versions, you can resort to the PyGreSQL 4.x versions that
4242
still support them.

docs/announce.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ This version has been built and unit tested on:
2323
- Ubuntu
2424
- Windows 7 and 10 with both MinGW and Visual Studio
2525
- PostgreSQL 9.0 to 9.6 and 10 to 12 (32 and 64bit)
26-
- Python 2.6, 2.7 and 3.3 to 3.8 (32 and 64bit)
26+
- Python 2.7 and 3.5 to 3.8 (32 and 64bit)
2727

2828
| D'Arcy J.M. Cain
2929
| darcy@PyGreSQL.org

docs/contents/changelog.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ ChangeLog
33

44
Version 5.2 (to be released)
55
----------------------------
6-
- ...
6+
- We now Python version 2.7 or 3.5 and newer
77

88

99
Version 5.1.2 (2020-04-19)

docs/contents/install.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ If you are on Windows, make sure that the directory that contains
1111
libpq.dll is part of your ``PATH`` environment variable.
1212

1313
The current version of PyGreSQL has been tested with Python versions
14-
2.6, 2.7 and 3.3 to 3.8, and PostgreSQL versions 9.0 to 9.6 and 10 to 12.
14+
2.7 and 3.5 to 3.8, and PostgreSQL versions 9.0 to 9.6 and 10 to 12.
1515

1616
PyGreSQL will be installed as three modules, a shared library called
1717
_pg.so (on Linux) or a DLL called _pg.pyd (on Windows), and two pure

pg.py

Lines changed: 30 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@
7575
from datetime import date, time, datetime, timedelta, tzinfo
7676
from decimal import Decimal
7777
from math import isnan, isinf
78-
from collections import namedtuple
79-
from keyword import iskeyword
78+
from collections import namedtuple, OrderedDict
8079
from operator import itemgetter
8180
from functools import partial
8281
from re import compile as regex
@@ -179,91 +178,6 @@ def wrapper(arg):
179178

180179
# Auxiliary classes and functions that are independent from a DB connection:
181180

182-
try:
183-
from collections import OrderedDict
184-
except ImportError: # Python 2.6 or 3.0
185-
OrderedDict = dict
186-
187-
188-
class AttrDict(dict):
189-
"""Simple read-only ordered dictionary for storing attribute names."""
190-
191-
def __init__(self, *args, **kw):
192-
if len(args) > 1 or kw:
193-
raise TypeError
194-
items = args[0] if args else []
195-
if isinstance(items, dict):
196-
raise TypeError
197-
items = list(items)
198-
self._keys = [item[0] for item in items]
199-
dict.__init__(self, items)
200-
self._read_only = True
201-
error = self._read_only_error
202-
self.clear = self.update = error
203-
self.pop = self.setdefault = self.popitem = error
204-
205-
def __setitem__(self, key, value):
206-
if self._read_only:
207-
self._read_only_error()
208-
dict.__setitem__(self, key, value)
209-
210-
def __delitem__(self, key):
211-
if self._read_only:
212-
self._read_only_error()
213-
dict.__delitem__(self, key)
214-
215-
def __iter__(self):
216-
return iter(self._keys)
217-
218-
def keys(self):
219-
return list(self._keys)
220-
221-
def values(self):
222-
return [self[key] for key in self]
223-
224-
def items(self):
225-
return [(key, self[key]) for key in self]
226-
227-
def iterkeys(self):
228-
return self.__iter__()
229-
230-
def itervalues(self):
231-
return iter(self.values())
232-
233-
def iteritems(self):
234-
return iter(self.items())
235-
236-
@staticmethod
237-
def _read_only_error(*args, **kw):
238-
raise TypeError('This object is read-only')
239-
240-
else:
241-
242-
class AttrDict(OrderedDict):
243-
"""Simple read-only ordered dictionary for storing attribute names."""
244-
245-
def __init__(self, *args, **kw):
246-
self._read_only = False
247-
OrderedDict.__init__(self, *args, **kw)
248-
self._read_only = True
249-
error = self._read_only_error
250-
self.clear = self.update = error
251-
self.pop = self.setdefault = self.popitem = error
252-
253-
def __setitem__(self, key, value):
254-
if self._read_only:
255-
self._read_only_error()
256-
OrderedDict.__setitem__(self, key, value)
257-
258-
def __delitem__(self, key):
259-
if self._read_only:
260-
self._read_only_error()
261-
OrderedDict.__delitem__(self, key)
262-
263-
@staticmethod
264-
def _read_only_error(*args, **kw):
265-
raise TypeError('This object is read-only')
266-
267181
try:
268182
from inspect import signature
269183
except ImportError: # Python < 3.3
@@ -356,8 +270,8 @@ def __init__(self):
356270
self[key] = typ
357271
self['_%s' % key] = '%s[]' % typ
358272

359-
# this could be a static method in Python > 2.6
360-
def __missing__(self, key):
273+
@staticmethod
274+
def __missing__(key):
361275
return 'text'
362276

363277
_simpletypes = _SimpleTypes()
@@ -428,6 +342,32 @@ class Literal(str):
428342
"""Wrapper class for marking literal SQL values."""
429343

430344

345+
class AttrDict(OrderedDict):
346+
"""Simple read-only ordered dictionary for storing attribute names."""
347+
348+
def __init__(self, *args, **kw):
349+
self._read_only = False
350+
OrderedDict.__init__(self, *args, **kw)
351+
self._read_only = True
352+
error = self._read_only_error
353+
self.clear = self.update = error
354+
self.pop = self.setdefault = self.popitem = error
355+
356+
def __setitem__(self, key, value):
357+
if self._read_only:
358+
self._read_only_error()
359+
OrderedDict.__setitem__(self, key, value)
360+
361+
def __delitem__(self, key):
362+
if self._read_only:
363+
self._read_only_error()
364+
OrderedDict.__delitem__(self, key)
365+
366+
@staticmethod
367+
def _read_only_error(*args, **kw):
368+
raise TypeError('This object is read-only')
369+
370+
431371
class Adapter:
432372
"""Class providing methods for adapting parameters to the database."""
433373

@@ -1328,13 +1268,7 @@ def typecast(self, value, typ):
13281268
def _row_factory(names):
13291269
"""Get a namedtuple factory for row results with the given names."""
13301270
try:
1331-
try:
1332-
return namedtuple('Row', names, rename=True)._make
1333-
except TypeError: # Python 2.6 and 3.0 do not support rename
1334-
names = [v if _re_fieldname.match(v) and not iskeyword(v)
1335-
else 'column_%d' % (n,)
1336-
for n, v in enumerate(names)]
1337-
return namedtuple('Row', names)._make
1271+
return namedtuple('Row', names, rename=True)._make
13381272
except ValueError: # there is still a problem with the field names
13391273
names = ['column_%d' % (n,) for n in range(len(names))]
13401274
return namedtuple('Row', names)._make

pgdb.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@
112112
except ImportError: # Python < 3.3
113113
from collections import Iterable
114114
from collections import namedtuple
115-
from keyword import iskeyword
116115
from functools import partial
117116
from re import compile as regex
118117
from json import loads as jsondecode, dumps as jsonencode
@@ -867,13 +866,7 @@ def _op_error(msg):
867866
def _row_factory(names):
868867
"""Get a namedtuple factory for row results with the given names."""
869868
try:
870-
try:
871-
return namedtuple('Row', names, rename=True)._make
872-
except TypeError: # Python 2.6 and 3.0 do not support rename
873-
names = [v if _re_fieldname.match(v) and not iskeyword(v)
874-
else 'column_%d' % (n,)
875-
for n, v in enumerate(names)]
876-
return namedtuple('Row', names)._make
869+
return namedtuple('Row', names, rename=True)._make
877870
except ValueError: # there is still a problem with the field names
878871
names = ['column_%d' % (n,) for n in range(len(names))]
879872
return namedtuple('Row', names)._make

setup.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* PostgreSQL pg_config tool (usually included in the devel package)
2727
(the Windows installer has it as part of the database server feature)
2828
29-
PyGreSQL currently supports Python versions 2.6, 2.7 and 3.3 to 3.8,
29+
PyGreSQL currently supports Python versions 2.7 and 3.5 to 3.8,
3030
and PostgreSQL versions 9.0 to 9.6 and 10 to 12.
3131
3232
Use as follows:
@@ -232,11 +232,8 @@ def finalize_options(self):
232232
"Programming Language :: C",
233233
'Programming Language :: Python',
234234
'Programming Language :: Python :: 2',
235-
'Programming Language :: Python :: 2.6',
236235
'Programming Language :: Python :: 2.7',
237236
'Programming Language :: Python :: 3',
238-
'Programming Language :: Python :: 3.3',
239-
'Programming Language :: Python :: 3.4',
240237
'Programming Language :: Python :: 3.5',
241238
'Programming Language :: Python :: 3.6',
242239
'Programming Language :: Python :: 3.7',

tests/test_classic_connection.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -462,12 +462,8 @@ def testNamedresultWithGoodFieldnames(self):
462462
self.assertEqual(v._fields, ('snake_case_alias', 'CamelCaseAlias'))
463463

464464
def testNamedresultWithBadFieldnames(self):
465-
try:
466-
r = namedtuple('Bad', ['?'] * 6, rename=True)
467-
except TypeError: # Python 2.6 or 3.0
468-
fields = tuple('column_%d' % n for n in range(6))
469-
else:
470-
fields = r._fields
465+
r = namedtuple('Bad', ['?'] * 6, rename=True)
466+
fields = r._fields
471467
q = ('select 3 as "0alias", 4 as _alias, 5 as "alias$", 6 as "alias?",'
472468
' 7 as "kebap-case-alias", 8 as break, 9 as and_a_good_one')
473469
result = [tuple(range(3, 10))]

tests/test_classic_dbwrapper.py

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,11 @@
5656
except NameError: # Python >= 3.0
5757
unicode = str
5858

59-
try:
60-
from collections import OrderedDict
61-
except ImportError: # Python 2.6 or 3.0
62-
OrderedDict = dict
59+
from collections import OrderedDict
6360

6461
if str is bytes: # noinspection PyUnresolvedReferences
6562
from StringIO import StringIO
66-
else:
63+
else: # Python >= 3.0
6764
from io import StringIO
6865

6966
windows = os.name == 'nt'
@@ -226,11 +223,7 @@ def testAllDBAttributes(self):
226223
'unescape_bytea', 'update', 'upsert',
227224
'use_regtypes', 'user',
228225
]
229-
# __dir__ is not called in Python 2.6 for old-style classes
230-
db_attributes = dir(self.db) if hasattr(
231-
self.db.__class__, '__class__') else self.db.__dir__()
232-
db_attributes = [a for a in db_attributes
233-
if not a.startswith('_')]
226+
db_attributes = [a for a in self.db.__dir__() if not a.startswith('_')]
234227
self.assertEqual(attributes, db_attributes)
235228

236229
def testAttributeDb(self):
@@ -1005,11 +998,6 @@ def testQueryFormatted(self):
1005998
# test with tuple, inline
1006999
q = f("select %s, %s, %s, %s", (3, 2.5, 'hello', True), inline=True)
10071000
r = q.getresult()[0]
1008-
if isinstance(r[1], Decimal):
1009-
# Python 2.6 cannot compare float and Decimal
1010-
r = list(r)
1011-
r[1] = float(r[1])
1012-
r = tuple(r)
10131001
self.assertEqual(r, (3, 2.5, 'hello', t))
10141002
# test with dict
10151003
q = f("select %(a)s::int, %(b)s::real, %(c)s::text, %(d)s::bool",
@@ -2944,8 +2932,7 @@ def testGetAsDict(self):
29442932
self.assertEqual(row.rgb, t[0])
29452933
self.assertEqual(row.name, t[1])
29462934
self.assertEqual(row._asdict(), dict(rgb=t[0], name=t[1]))
2947-
if OrderedDict is not dict: # Python > 2.6
2948-
self.assertEqual(r.keys(), expected.keys())
2935+
self.assertEqual(r.keys(), expected.keys())
29492936
r = get_as_dict(table, keyname='rgb')
29502937
self.assertIsInstance(r, OrderedDict)
29512938
expected = OrderedDict((row[1], (row[0], row[2]))
@@ -2962,8 +2949,7 @@ def testGetAsDict(self):
29622949
self.assertEqual(row.id, t[0])
29632950
self.assertEqual(row.name, t[1])
29642951
self.assertEqual(row._asdict(), dict(id=t[0], name=t[1]))
2965-
if OrderedDict is not dict: # Python > 2.6
2966-
self.assertEqual(r.keys(), expected.keys())
2952+
self.assertEqual(r.keys(), expected.keys())
29672953
r = get_as_dict(table, keyname=['id', 'rgb'])
29682954
self.assertIsInstance(r, OrderedDict)
29692955
expected = OrderedDict((row[:2], row[2:]) for row in colors)
@@ -2983,8 +2969,7 @@ def testGetAsDict(self):
29832969
if named:
29842970
self.assertEqual(row.name, t[0])
29852971
self.assertEqual(row._asdict(), dict(name=t[0]))
2986-
if OrderedDict is not dict: # Python > 2.6
2987-
self.assertEqual(r.keys(), expected.keys())
2972+
self.assertEqual(r.keys(), expected.keys())
29882973
r = get_as_dict(table, keyname=['id', 'rgb'], scalar=True)
29892974
self.assertIsInstance(r, OrderedDict)
29902975
expected = OrderedDict((row[:2], row[2]) for row in colors)
@@ -2995,8 +2980,7 @@ def testGetAsDict(self):
29952980
self.assertIsInstance(row, str)
29962981
t = expected[key]
29972982
self.assertEqual(row, t)
2998-
if OrderedDict is not dict: # Python > 2.6
2999-
self.assertEqual(r.keys(), expected.keys())
2983+
self.assertEqual(r.keys(), expected.keys())
30002984
r = get_as_dict(table, keyname='rgb', what=['rgb', 'name'], scalar=True)
30012985
self.assertIsInstance(r, OrderedDict)
30022986
expected = OrderedDict((row[1], row[2])
@@ -3008,8 +2992,7 @@ def testGetAsDict(self):
30082992
self.assertIsInstance(row, str)
30092993
t = expected[key]
30102994
self.assertEqual(row, t)
3011-
if OrderedDict is not dict: # Python > 2.6
3012-
self.assertEqual(r.keys(), expected.keys())
2995+
self.assertEqual(r.keys(), expected.keys())
30132996
r = get_as_dict(table, what='id, name',
30142997
where="rgb like '#b%'", scalar=True)
30152998
self.assertIsInstance(r, OrderedDict)
@@ -3021,8 +3004,7 @@ def testGetAsDict(self):
30213004
self.assertIsInstance(row, str)
30223005
t = expected[key]
30233006
self.assertEqual(row, t)
3024-
if OrderedDict is not dict: # Python > 2.6
3025-
self.assertEqual(r.keys(), expected.keys())
3007+
self.assertEqual(r.keys(), expected.keys())
30263008
expected = r
30273009
r = get_as_dict(table, what=['name', 'id'],
30283010
where=['id > 1', 'id < 4', "rgb like '#b%'",
@@ -3050,8 +3032,7 @@ def testGetAsDict(self):
30503032
r = get_as_dict(table, order=False)
30513033
self.assertIsInstance(r, dict)
30523034
self.assertEqual(r, expected)
3053-
if dict is not OrderedDict: # Python > 2.6
3054-
self.assertNotIsInstance(self, OrderedDict)
3035+
self.assertNotIsInstance(self, OrderedDict)
30553036
# test with arbitrary from clause
30563037
from_table = '(select id, lower(name) as n2 from "%s") as t2' % table
30573038
# primary key must be passed explicitly in this case

0 commit comments

Comments
 (0)