[sqlite] drop pysqlite2 support, using plain python sqlite3 module (closes #125083)

The pysqlite2 tracker states: "If you're using Python 2.5 and up, you already have a working version of pysqlite 2, bundled as sqlite3. You can stop here."

We're currently supporting python >= 2.6, hence this should be good.

The lazy initialisation mechanism is changed a bit:

  • documentation
  • don't monkeypatch the module itself
authorAurelien Campeas <aurelien.campeas@logilab.fr>
changesetafa47933b69d
branchdefault
phasepublic
hiddenno
parent revision#8d9520575c0e [sqlgen] Allow arbitrary SQL expression in statements generated by SQLGenerator. Closes #132590
child revision#8fc210bcd2e6 backport stable
files modified by this revision
sqlite.py
test/unittest_sqlite.py
# HG changeset patch
# User Aurelien Campeas <aurelien.campeas@logilab.fr>
# Date 1366205250 -7200
# Wed Apr 17 15:27:30 2013 +0200
# Node ID afa47933b69dd729a0f57a1f5ff951b75c9ab106
# Parent 8d9520575c0e289d90d1beb36af6dd11842eeca7
[sqlite] drop pysqlite2 support, using plain python sqlite3 module (closes #125083)

The pysqlite2 tracker states: "If you're using Python 2.5 and up, you
already have a working version of pysqlite 2, bundled as sqlite3. You
can stop here."

We're currently supporting python >= 2.6, hence this should be good.

The lazy initialisation mechanism is changed a bit:

* documentation
* don't monkeypatch the module itself

diff --git a/sqlite.py b/sqlite.py
@@ -15,45 +15,48 @@
1  #
2  # You should have received a copy of the GNU Lesser General Public License along
3  # with logilab-database. If not, see <http://www.gnu.org/licenses/>.
4  """Sqlite RDBMS support
5 
6 -Supported drivers, in order of preference:
7 -- pysqlite2 (recommended, others are not well tested)
8 -- sqlite
9 -- sqlite3
10 -
11 +Supported driver: sqlite3
12  """
13  __docformat__ = "restructuredtext en"
14 
15  from os.path import abspath
16  import os
17  import re
18 
19  from logilab.common.date import strptime
20  from logilab import database as db
21 
22 -class _PySqlite2Adapter(db.DBAPIAdapter):
23 -    """Simple pysqlite2 Adapter to DBAPI
24 -    """
25 -    # no type code in pysqlite2
26 +class _Sqlite3Adapter(db.DBAPIAdapter):
27 +    # no type code in sqlite3
28      BINARY = 'XXX'
29      STRING = 'XXX'
30      DATETIME = 'XXX'
31      NUMBER = 'XXX'
32      BOOLEAN = 'XXX'
33 +    _module_is_initialized = False
34 
35      def __init__(self, native_module, pywrap=False):
36          db.DBAPIAdapter.__init__(self, native_module, pywrap)
37 -        self._init_pysqlite2()
38 +        self._init_module()
39 +
40 +    def _init_module(self):
41 +        """ This declares adapters for the sqlite _module_
42 +        and should be called only once.
43 
44 -    def _init_pysqlite2(self):
45 -        """initialize pysqlite2 to use mx.DateTime for date and timestamps"""
46 +        We do this here in order to be as lazy as possible
47 +        (these adapters won't be added until we instantiate at
48 +        least one Sqlite3Adapter object).
49 +        """
50 +        if self._module_is_initialized:
51 +            return
52 +        _Sqlite3Adapter._module_is_initialized = True
53 +
54 +        # let's register adapters
55          sqlite = self._native_module
56 -        if hasattr(sqlite, '_lc_initialized'):
57 -            return
58 -        sqlite._lc_initialized = 1
59 
60          # bytea type handling
61          from StringIO import StringIO
62          def adapt_bytea(data):
63              return data.getvalue()
@@ -140,11 +143,11 @@
64 
65      def connect(self, host='', database='', user='', password='', port=None, extra_args=None):
66          """Handles sqlite connection format"""
67          sqlite = self._native_module
68 
69 -        class PySqlite2Cursor(sqlite.Cursor):
70 +        class Sqlite3Cursor(sqlite.Cursor):
71              """cursor adapting usual dict format to pysqlite named format
72              in SQL queries
73              """
74              def _replace_parameters(self, sql, kwargs):
75                  for k,v in kwargs.iteritems():
@@ -165,39 +168,37 @@
76              def executemany(self, sql, kwargss):
77                  if not isinstance(kwargss, (list, tuple)):
78                      kwargss = tuple(kwargss)
79                  self.__class__.__bases__[0].executemany(self, self._replace_parameters(sql, kwargss[0]), kwargss)
80 
81 -        class PySqlite2CnxWrapper:
82 +        class Sqlite3CnxWrapper:
83              def __init__(self, cnx):
84                  self._cnx = cnx
85 
86              def cursor(self):
87 -                return self._cnx.cursor(PySqlite2Cursor)
88 +                return self._cnx.cursor(Sqlite3Cursor)
89              def __getattr__(self, attrname):
90                  return getattr(self._cnx, attrname)
91 
92          # abspath so we can change cwd without breaking further queries on the
93          # database
94          cnx = sqlite.connect(abspath(database),
95                               detect_types=sqlite.PARSE_DECLTYPES)
96 -        return self._wrap_if_needed(PySqlite2CnxWrapper(cnx))
97 +        return self._wrap_if_needed(Sqlite3CnxWrapper(cnx))
98 
99      def _transformation_callback(self, description, encoding='utf-8', binarywrap=None):
100          def _transform(value):
101              if binarywrap is not None and isinstance(value, self._native_module.Binary):
102                  return binarywrap(value)
103              return value # no type code support, can't do anything
104          return _transform
105 
106 
107  db._PREFERED_DRIVERS['sqlite'] = [
108 -    'pysqlite2.dbapi2',
109      'sqlite3']
110  db._ADAPTER_DIRECTORY['sqlite'] = {
111 -    'pysqlite2.dbapi2' : _PySqlite2Adapter,
112 -    'sqlite3' : _PySqlite2Adapter,
113 +    'sqlite3' : _Sqlite3Adapter,
114      }
115 
116 
117  class _SqliteAdvFuncHelper(db._GenericAdvFuncHelper):
118      """Generic helper, trying to provide generic way to implement
diff --git a/test/unittest_sqlite.py b/test/unittest_sqlite.py
@@ -32,8 +32,16 @@
119          self.assertEqual(self.helper.TYPE_MAPPING['Datetime'], 'timestamp')
120          self.assertEqual(self.helper.TYPE_MAPPING['String'], 'text')
121          self.assertEqual(self.helper.TYPE_MAPPING['Password'], 'bytea')
122          self.assertEqual(self.helper.TYPE_MAPPING['Bytes'], 'bytea')
123 
124 +class SQLiteAdapterTC(unittest.TestCase):
125 +
126 +    def test_only_one_lazy_module_initialization(self):
127 +        import sqlite3
128 +        from logilab.database import sqlite as lgdbsqlite
129 +        self.assertFalse(lgdbsqlite._Sqlite3Adapter._module_is_initialized)
130 +        adapter = lgdbsqlite._Sqlite3Adapter(sqlite3)
131 +        self.assertTrue(adapter._module_is_initialized)
132 
133  if __name__ == '__main__':
134      unittest.main()