Changeset 901


Ignore:
Timestamp:
Jan 6, 2017, 7:25:02 AM (2 years ago)
Author:
cito
Message:

Improve creation of named tuples in Python 2.6 and 3.0

Location:
trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/docs/contents/changelog.rst

    r900 r901  
    55------------------------------
    66- query_formatted() can now be used without parameters.
     7- The automatic renaming of columns that are invalid as field names of
     8  named tuples now works more accurately in Python 2.6 and 3.0.
    79
    810Version 5.0.3 (2016-12-10)
     
    1820  entries, but this can be changed with the set_row_factory_size() function.
    1921  In certain cases this change can notably improve the performance.
     22- The namedresult() method in the classic API now also tries to rename
     23  columns that would result in invalid field names.
    2024
    2125Version 5.0.2 (2016-09-13)
  • trunk/docs/contents/pg/query.rst

    r781 r901  
    6565with each row returned as a named tuple with proper field names.
    6666
     67Column names in the database that are not valid as field names for
     68named tuples (particularly, names starting with an underscore) are
     69automatically renamed to valid positional names.
     70
    6771Note that since PyGreSQL 5.0 this will return the values of array type
    6872columns as Python lists.
  • trunk/docs/contents/pgdb/cursor.rst

    r814 r901  
    217217Fetch all (remaining) rows of a query result, returning them as list of
    218218named tuples. The field names of the named tuple are the same as the column
    219 names of the database query as long as they are valid Python identifiers.
     219names of the database query as long as they are valid as field names for
     220named tuples, otherwise they are given positional names.
    220221
    221222Note that the cursor's :attr:`arraysize` attribute can affect the performance
  • trunk/pg.py

    r900 r901  
    4242from math import isnan, isinf
    4343from collections import namedtuple
     44from keyword import iskeyword
    4445from operator import itemgetter
    4546from functools import partial
     
    12641265
    12651266
     1267_re_fieldname = regex('^[A-Za-z][_a-zA-Z0-9]*$')
     1268
    12661269# The result rows for database operations are returned as named tuples
    12671270# by default. Since creating namedtuple classes is a somewhat expensive
     
    12751278            return namedtuple('Row', names, rename=True)._make
    12761279        except TypeError:  # Python 2.6 and 3.0 do not support rename
    1277             names = [v if v.isalnum() else 'column_%d' % (n,)
     1280            names = [v if _re_fieldname.match(v) and not iskeyword(v)
     1281                        else 'column_%d' % (n,)
    12781282                     for n, v in enumerate(names)]
    12791283            return namedtuple('Row', names)._make
  • trunk/pgdb.py

    r894 r901  
    7676from math import isnan, isinf
    7777from collections import namedtuple, Iterable
     78from keyword import iskeyword
    7879from functools import partial
    7980from re import compile as regex
     
    818819### Row Tuples
    819820
     821_re_fieldname = regex('^[A-Za-z][_a-zA-Z0-9]*$')
     822
    820823# The result rows for database operations are returned as named tuples
    821824# by default. Since creating namedtuple classes is a somewhat expensive
     
    829832            return namedtuple('Row', names, rename=True)._make
    830833        except TypeError:  # Python 2.6 and 3.0 do not support rename
    831             names = [v if v.isalnum() else 'column_%d' % (n,)
     834            names = [v if _re_fieldname.match(v) and not iskeyword(v)
     835                        else 'column_%d' % (n,)
    832836                     for n, v in enumerate(names)]
    833837            return namedtuple('Row', names)._make
  • trunk/tests/test_classic_connection.py

    r894 r901  
    1919import os
    2020
     21from collections import namedtuple
     22from decimal import Decimal
     23
    2124import pg  # the module under test
    22 
    23 from decimal import Decimal
    2425
    2526# We need a database to test against.  If LOCAL_PyGreSQL.py exists we will
     
    406407        self.assertEqual(v._fields, ('alias0',))
    407408        self.assertEqual(v.alias0, 0)
     409
     410    def testNamedresultWithGoodFieldnames(self):
     411        q = 'select 1 as snake_case_alias, 2 as "CamelCaseAlias"'
     412        result = [(1, 2)]
     413        r = self.c.query(q).namedresult()
     414        self.assertEqual(r, result)
     415        v = r[0]
     416        self.assertEqual(v._fields, ('snake_case_alias', 'CamelCaseAlias'))
     417
     418    def testNamedresultWithBadFieldnames(self):
     419        try:
     420            r = namedtuple('Bad', ['?'] * 6, rename=True)
     421        except TypeError:  # Python 2.6 or 3.0
     422            fields = tuple('column_%d' % n for n in range(6))
     423        else:
     424            fields = r._fields
     425        q = ('select 3 as "0alias", 4 as _alias, 5 as "alias$", 6 as "alias?",'
     426            ' 7 as "kebap-case-alias", 8 as break, 9 as and_a_good_one')
     427        result = [tuple(range(3, 10))]
     428        r = self.c.query(q).namedresult()
     429        self.assertEqual(r, result)
     430        v = r[0]
     431        self.assertEqual(v._fields[:6], fields)
     432        self.assertEqual(v._fields[6], 'and_a_good_one')
    408433
    409434    def testGet3Cols(self):
  • trunk/tests/test_dbapi20.py

    r894 r901  
    260260        else:
    261261            self.assertEqual(res._fields, ('one', '_1', 'three'))
     262
     263    def test_cursor_with_badly_named_columns(self):
     264        con = self._connect()
     265        cur = con.cursor()
    262266        cur.execute("select 1 as abc, 2 as def")
    263267        res = cur.fetchone()
    264268        self.assertIsInstance(res, tuple)
    265269        self.assertEqual(res, (1, 2))
     270        old_py = OrderedDict is None  # Python 2.6 or 3.0
    266271        if old_py:
    267             self.assertEqual(res._fields, ('column_0', 'column_1'))
     272            self.assertEqual(res._fields, ('abc', 'column_1'))
    268273        else:
    269274            self.assertEqual(res._fields, ('abc', '_1'))
     275        cur.execute('select 1 as snake_case, 2 as "CamelCase",'
     276            ' 3 as "kebap-case", 4 as "_bad", 5 as "0bad", 6 as "bad$"')
     277        res = cur.fetchone()
     278        self.assertIsInstance(res, tuple)
     279        self.assertEqual(res, (1, 2, 3, 4, 5, 6))
     280        # old Python versions cannot rename tuple fields with underscore
     281        self.assertEqual(res._fields[:2], ('snake_case', 'CamelCase'))
     282        fields = ('_2', '_3', '_4', '_5')
     283        if old_py:
     284            fields = tuple('column' + field for field in fields)
     285        self.assertEqual(res._fields[2:], fields)
    270286
    271287    def test_colnames(self):
Note: See TracChangeset for help on using the changeset viewer.