merge with more recent stable changes

authorPierre-Yves David <pierre-yves.david@logilab.fr>
changesetdf5efe92aac5
branchstable
phasepublic
hiddenno
parent revision#abf6be2bf5fa default is stable, #1f1600be7e3d [test] better protection again missing configuration
child revision#7db3d7c30c53 default is stable, #e75c170c3f34 [packaging] fix version in spec file and don't ship logilab/__init__.py, #3781d3898726 [packaging] fix version in spec file, #87f26f15a66e merge with stable
files modified by this revision
.hgtags
__init__.py
__pkginfo__.py
debian/changelog
debian/control
postgres.py
python-logilab-database.spec
sqlite.py
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@logilab.fr>
# Date 1363881210 -3600
# Thu Mar 21 16:53:30 2013 +0100
# Branch stable
# Node ID df5efe92aac5282bc59f396c914d26eb28aca464
# Parent 1f1600be7e3d2e62446053a3ee2d44c2db324f5e
# Parent abf6be2bf5fa309b3c25a3a655b3e7136a809e26
merge with more recent stable changes

diff --git a/.hgtags b/.hgtags
@@ -48,5 +48,8 @@
1  d1bffb496fa151077320cca5576fd15e6e00d5fc logilab-database-version-1.8.0
2  9416ef63fdf70b22c1e6b66c3b8009be258cbfc0 logilab-database-debian-version-1.8.0-1
3  80afbe3afe8a74938aa7d08fc3d270d47ceb0d86 logilab-database-version-1.8.1
4  bbb86bf3775af37ab1586c1697f4ddec4c4f57a2 logilab-database-debian-version-1.8.1-1
5  443b3bc7cab5c5bac77dc4b4026c26b68c3620d6 logilab-database-version-1.8.2
6 +93688ee18263966fe7ee41d84ff100552a5644c8 logilab-database-debian-version-1.8.2-1
7 +dcbf659a5fe2f04c80172345692864e8800ee29e logilab-database-version-1.9.0
8 +89a6e45cf45ec69350b05b840d2572518ed1afaf logilab-database-debian-version-1.9.0-1
diff --git a/__init__.py b/__init__.py
@@ -1,6 +1,6 @@
9 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
10 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
11  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
12  #
13  # This file is part of logilab-database.
14  #
15  # logilab-database is free software: you can redistribute it and/or modify it
@@ -21,40 +21,47 @@
16  Currently support:
17 
18  - postgresql (pgdb, psycopg, psycopg2, pyPgSQL)
19  - mysql (MySQLdb)
20  - sqlite (pysqlite2, sqlite, sqlite3)
21 +- sqlserver 2000, 2005, 2008 (pyodbc, adodbapi)
22 
23  just use the `get_connection` function from this module to get a
24  wrapped connection.  If multiple drivers for a database are available,
25  you can control which one you want to use using the
26  `set_prefered_driver` function.
27 
28  Additional helpers are also provided for advanced functionalities such
29  as listing existing users or databases, creating database... Get the
30  helper for your database using the `get_db_helper` function.
31  """
32 +from __future__ import with_statement
33 
34  __docformat__ = "restructuredtext en"
35 
36  import sys
37 +import threading
38  import logging
39  from datetime import datetime, time
40 
41  from logilab.common.modutils import load_module_from_name
42  from logilab.common.date import utcdatetime, utctime
43 
44  _LOGGER = logging.getLogger('logilab.database')
45 
46  USE_MX_DATETIME = False
47 
48 +_LOAD_MODULES_LOCK = threading.Lock()
49 +
50  _PREFERED_DRIVERS = {}
51  _ADV_FUNC_HELPER_DIRECTORY = {}
52 
53  def _ensure_module_loaded(driver):
54      if driver in ('postgres', 'sqlite', 'mysql', 'sqlserver2005'):
55 -        __import__('logilab.database.%s' % driver)
56 +        with _LOAD_MODULES_LOCK:
57 +            __import__('logilab.database.%s' % driver)
58 +
59  # main functions ###############################################################
60 
61  def get_db_helper(driver):
62      """returns an advanced function helper for the given driver"""
63      _ensure_module_loaded(driver)
@@ -81,11 +88,11 @@
64      module, modname = _import_driver_module(driver, drivers)
65      try:
66          adapter = _ADAPTER_DIRECTORY.get_adapter(driver, modname)
67      except NoAdapterFound, err:
68          if not quiet:
69 -            msg = 'No Adapter found for %s, using default one' 
70 +            msg = 'No Adapter found for %s, using default one'
71              _LOGGER.warning(msg, err.objname)
72          adapted_module = DBAPIAdapter(module, pywrap)
73      else:
74          adapted_module = adapter(module, pywrap)
75      if host and not port:
@@ -104,20 +111,21 @@
76      module is the name of the module providing the connect function
77      syntax is (params_func, post_process_func_or_None)
78      _drivers is a optional dictionary of drivers
79      """
80      _ensure_module_loaded(driver)
81 -    try:
82 -        modules = _drivers[driver]
83 -    except KeyError:
84 -        raise UnknownDriver('Unknown driver %s' % driver)
85 -    # Remove module from modules list, and re-insert it in first position
86 -    try:
87 -        modules.remove(module)
88 -    except ValueError:
89 -        raise UnknownDriver('Unknown module %s for %s' % (module, driver))
90 -    modules.insert(0, module)
91 +    with _LOAD_MODULES_LOCK:
92 +        try:
93 +            modules = _drivers[driver]
94 +        except KeyError:
95 +            raise UnknownDriver('Unknown driver %s' % driver)
96 +        # Remove module from modules list, and re-insert it in first position
97 +        try:
98 +            modules.remove(module)
99 +        except ValueError:
100 +            raise UnknownDriver('Unknown module %s for %s' % (module, driver))
101 +        modules.insert(0, module)
102 
103 
104  # unified db api ###############################################################
105 
106  class UnknownDriver(Exception):
@@ -173,22 +181,23 @@
107      :returns: the tuple module_object, module_name where module_object
108                is the dbapi module, and modname the module's name
109      """
110      if not driver in drivers:
111          raise UnknownDriver(driver)
112 -    for modname in drivers[driver]:
113 -        try:
114 -            if not quiet:
115 -                _LOGGER.info('Trying %s', modname)
116 -            module = load_module_from_name(modname, use_sys=False)
117 -            break
118 -        except ImportError:
119 -            if not quiet:
120 -                _LOGGER.warning('%s is not available', modname)
121 -            continue
122 -    else:
123 -        raise ImportError('Unable to import a %s module' % driver)
124 +    with _LOAD_MODULES_LOCK:
125 +        for modname in drivers[driver]:
126 +            try:
127 +                if not quiet:
128 +                    _LOGGER.info('Trying %s', modname)
129 +                module = load_module_from_name(modname, use_sys=False)
130 +                break
131 +            except ImportError:
132 +                if not quiet:
133 +                    _LOGGER.warning('%s is not available', modname)
134 +                continue
135 +        else:
136 +            raise ImportError('Unable to import a %s module' % driver)
137      return module, modname
138 
139 
140  ## base connection and cursor wrappers #####################
141 
@@ -274,10 +283,12 @@
142      UNKNOWN = None
143      # True if the fetch*() methods return a mutable structure (i.e. not a tuple)
144      row_is_mutable = False
145      # True if the fetch*() methods return unicode and not binary strings
146      returns_unicode = False
147 +    # True is the backend support COPY FROM method
148 +    support_copy_from = False
149 
150      def __init__(self, native_module, pywrap=False):
151          """
152          :type native_module: module
153          :param native_module: the database's driver adapted module
@@ -531,10 +542,16 @@
154      field = 'MINUTE'
155 
156  class SECOND(ExtractDateField):
157      field = 'SECOND'
158 
159 +class EPOCH(ExtractDateField):
160 +    """Return EPOCH timestamp from a datetime/date ;
161 +    return number of seconds for an interval.
162 +    """
163 +    field = 'EPOCH'
164 +
165  class WEEKDAY(FunctionDescr):
166      """Return the day of the week represented by the date.
167 
168      Sunday == 1, Saturday = 7
169 
@@ -593,11 +610,11 @@
170                  funcdef = funcdef()
171          except TypeError: # issubclass is quite strict
172              pass
173          assert isinstance(funcdef, FunctionDescr)
174          funcname = funcname or funcdef.name
175 -        self.functions[funcname] = funcdef
176 +        self.functions[funcname.upper()] = funcdef
177 
178      def get_function(self, funcname):
179          try:
180              return self.functions[funcname.upper()]
181          except KeyError:
@@ -622,11 +639,11 @@
182      MIN, MAX, SUM, COUNT, AVG,
183      # transformation functions
184      ABS, RANDOM,
185      UPPER, LOWER, SUBSTRING, LENGTH,
186      DATE,
187 -    YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, WEEKDAY, AT_TZ,
188 +    YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, WEEKDAY, EPOCH, AT_TZ,
189      # cast functions
190      CAST,
191      # keyword function
192      IN):
193      SQL_FUNCTIONS_REGISTRY.register_function(func_class())
diff --git a/__pkginfo__.py b/__pkginfo__.py
@@ -1,6 +1,6 @@
194 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
195 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
196  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
197  #
198  # This file is part of logilab-database.
199  #
200  # logilab-database is free software: you can redistribute it and/or modify it
@@ -17,11 +17,11 @@
201  # with logilab-database. If not, see <http://www.gnu.org/licenses/>.
202  """logilab.database packaging information."""
203 
204  distname = 'logilab-database'
205  modname = 'database'
206 -numversion = (1, 8, 2)
207 +numversion = (1, 9, 0)
208  version = '.'.join([str(num) for num in numversion])
209  license = 'LGPL'
210 
211  author = "Logilab"
212  author_email = "contact@logilab.fr"
diff --git a/debian/changelog b/debian/changelog
@@ -1,5 +1,17 @@
213 +logilab-database (1.9.0-1) unstable; urgency=low
214 +
215 +  * new upstream release
216 +
217 + -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 19 Dec 2012 16:11:39 +0100
218 +
219 +logilab-database (1.8.2-2) unstable; urgency=low
220 +
221 +  * Mark the package as incompatible with python < 2.5.
222 +
223 + -- Julien Cristau <jcristau@debian.org>  Fri, 22 Jun 2012 14:03:59 +0200
224 +
225  logilab-database (1.8.2-1) unstable; urgency=low
226 
227    * new upstream release
228 
229   -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Fri, 03 Feb 2012 16:18:24 +0100
diff --git a/debian/control b/debian/control
@@ -4,11 +4,11 @@
230  Maintainer: Debian Python Modules Team <python-modules-team@lists.alioth.debian.org>
231  Uploaders: Alexandre Fayolle <afayolle@debian.org>,
232  	   Sandro Tosi <morph@debian.org>,
233  Build-Depends: debhelper (>= 5.0.38), python (>= 2.4.6-2)
234  Build-Depends-Indep: python-support, python-epydoc, graphviz
235 -XS-Python-Version: all
236 +XS-Python-Version: >= 2.5
237  Standards-Version: 3.8.2
238  Homepage: http://www.logilab.org/project/logilab-database
239 
240  Package: python-logilab-database
241  Architecture: all
diff --git a/postgres.py b/postgres.py
@@ -90,15 +90,22 @@
242      """
243      # not defined in psycopg2.extensions
244      # "select typname from pg_type where oid=705";
245      UNKNOWN = 705
246      returns_unicode = True
247 +    # True is the backend support COPY FROM method
248 +    support_copy_from = True
249 
250      def __init__(self, native_module, pywrap=False):
251          from psycopg2 import extensions
252          extensions.register_type(extensions.UNICODE)
253 -        extensions.register_type(extensions.UNICODEARRAY)
254 +        try:
255 +            unicodearray = extensions.UNICODEARRAY
256 +        except AttributeError:
257 +            from psycopg2 import _psycopg
258 +            unicodearray = _psycopg.UNICODEARRAY
259 +        extensions.register_type(unicodearray)
260          self.BOOLEAN = extensions.BOOLEAN
261          db.DBAPIAdapter.__init__(self, native_module, pywrap)
262          self._init_psycopg2()
263 
264      def _init_psycopg2(self):
@@ -125,10 +132,21 @@
265              #extensions.register_adapter(StringIO.StringIO, adapt_stringio)
266              #import cStringIO
267              #extensions.register_adapter(cStringIO.StringIO, adapt_stringio)
268 
269 
270 +class _Psycopg2CtypesAdapter(_Psycopg2Adapter):
271 +    """psycopg2-ctypes adapter
272 +
273 +    cf. https://github.com/mvantellingen/psycopg2-ctypes
274 +    """
275 +    def __init__(self, native_module, pywrap=False):
276 +        # install psycopg2 compatibility
277 +        from psycopg2ct import compat
278 +        compat.register()
279 +        _Psycopg2Adapter.__init__(self, native_module, pywrap)
280 +
281  class _PgsqlAdapter(db.DBAPIAdapter):
282      """Simple pyPgSQL Adapter to DBAPI
283      """
284      def connect(self, host='', database='', user='', password='', port='', extra_args=None):
285          """Handles psycopg connection format"""
@@ -148,16 +166,17 @@
286          return getattr(self._native_module, attrname)
287 
288 
289  db._PREFERED_DRIVERS['postgres'] = [
290      #'logilab.database._pyodbcwrap',
291 -    'psycopg2', 'psycopg', 'pgdb', 'pyPgSQL.PgSQL',
292 +    'psycopg2', 'psycopg2ct', 'psycopg', 'pgdb', 'pyPgSQL.PgSQL',
293      ]
294  db._ADAPTER_DIRECTORY['postgres'] = {
295      'pgdb' : _PgdbAdapter,
296      'psycopg' : _PsycopgAdapter,
297      'psycopg2' : _Psycopg2Adapter,
298 +    'psycopg2ct' : _Psycopg2CtypesAdapter,
299      'pyPgSQL.PgSQL' : _PgsqlAdapter,
300      }
301 
302 
303 
@@ -417,17 +436,18 @@
304      def sql_init_fti(self):
305          """Return the sql definition of table()s used by the full text index.
306 
307          Require extensions to be already in.
308          """
309 -        # XXX create GIN or GIST index, see FTS wiki
310          return """
311  CREATE table appears(
312    uid     INTEGER PRIMARY KEY NOT NULL,
313    words   tsvector,
314    weight  FLOAT
315  );
316 +
317 +CREATE INDEX appears_words_idx ON appears USING gin(words);
318  """
319 
320      def sql_drop_fti(self):
321          """Drop tables used by the full text index."""
322          return 'DROP TABLE appears;'
diff --git a/python-logilab-database.spec b/python-logilab-database.spec
@@ -0,0 +1,65 @@
323 +# for el5, force use of python2.6
324 +%if 0%{?el5}
325 +%define python python26
326 +%define __python /usr/bin/python2.6
327 +%{!?python_scriptarch: %define python_scriptarch %(%{__python} -c "from distutils.sysconfig import get_python_lib; from os.path import join; print join(get_python_lib(1, 1), 'scripts')")}
328 +%else
329 +%define python python
330 +%define __python /usr/bin/python
331 +%endif
332 +%{!?_python_sitelib: %define _python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
333 +
334 +Name:           %{python}-logilab-database
335 +Version:        1.8.2
336 +Release:        logilab.1%{?dist}
337 +Summary:        Unified database access library for python
338 +
339 +Group:          Development/Libraries
340 +License:        LGPLv2+
341 +URL:            http://www.logilab.org/projects/logilab-database
342 +Source0:        http://download.logilab.org/pub/database/logilab-database-%{version}.tar.gz
343 +BuildArch:      noarch
344 +BuildRoot:      %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
345 +
346 +BuildRequires:  %{python}
347 +Requires:       %{python}, %{python}-logilab-common >= 0.55.2
348 +
349 +
350 +%description
351 +logilab-database provides some classes to make unified access
352 +to different RDBMS possible:
353 +
354 +* actually compatible db-api from different drivers
355 +* extensions functions for common tasks such as creating database, index,
356 +  users, dump and restore, etc...
357 +* additional api for full text search
358 +
359 +%prep
360 +%setup -q -n logilab-database-%{version}
361 +
362 +
363 +%build
364 +%{__python} setup.py build
365 +%if 0%{?el5}
366 +# change the python version in shebangs
367 +find . -name '*.py' -type f -print0 |  xargs -0 sed -i '1,3s;^#!.*python.*$;#! /usr/bin/python2.6;'
368 +%endif
369 +
370 +
371 +%install
372 +rm -rf $RPM_BUILD_ROOT
373 +NO_SETUPTOOLS=1 %{__python} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT %{?python_scriptarch: --install-scripts=%{python_scriptarch}}
374 +rm -rf $RPM_BUILD_ROOT%{_python_sitelib}/logilab/database/test
375 +
376 +%check
377 +%{__python} setup.py test
378 +
379 +%clean
380 +rm -rf $RPM_BUILD_ROOT
381 +
382 +
383 +%files
384 +%defattr(-,root,root,-)
385 +%doc README ChangeLog COPYING
386 +%{_python_sitelib}/logilab*
387 +
diff --git a/sqlite.py b/sqlite.py
@@ -188,26 +188,15 @@
388                  return binarywrap(value)
389              return value # no type code support, can't do anything
390          return _transform
391 
392 
393 -class _SqliteAdapter(db.DBAPIAdapter):
394 -    """Simple sqlite Adapter to DBAPI
395 -    """
396 -    def __init__(self, native_module, pywrap=False):
397 -        db.DBAPIAdapter.__init__(self, native_module, pywrap)
398 -        self.DATETIME = native_module.TIMESTAMP
399 -
400 -    def connect(self, host='', database='', user='', password='', port='', extra_args=None):
401 -        """Handles sqlite connection format"""
402 -        return self._wrap_if_needed(self._native_module.connect(database))
403 -
404 -
405 -db._PREFERED_DRIVERS['sqlite'] = ['pysqlite2.dbapi2', 'sqlite', 'sqlite3']
406 +db._PREFERED_DRIVERS['sqlite'] = [
407 +    'pysqlite2.dbapi2',
408 +    'sqlite3']
409  db._ADAPTER_DIRECTORY['sqlite'] = {
410      'pysqlite2.dbapi2' : _PySqlite2Adapter,
411 -    'sqlite' : _SqliteAdapter,
412      'sqlite3' : _PySqlite2Adapter,
413      }
414 
415 
416  class _SqliteAdvFuncHelper(db._GenericAdvFuncHelper):