default is stable

authorSylvain Th?nault <sylvain.thenault@logilab.fr>
changesetabf6be2bf5fa
branchstable
phasepublic
hiddenno
parent revision#6812b30300b2 make it clear names in registry are stored uppercased, #d9a785c7ac93 Added tag logilab-database-debian-version-1.9.0-1 for changeset 89a6e45cf45e
child revision#df5efe92aac5 merge with more recent stable changes, #34f9dfea50fc [sqlgen] Allow to unprotect some parameters during the creation of an SQL INSERT query
files modified by this revision
.hgtags
__init__.py
__pkginfo__.py
debian/changelog
postgres.py
python-logilab-database.spec
sqlite.py
# HG changeset patch
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1358242540 -3600
# Tue Jan 15 10:35:40 2013 +0100
# Branch stable
# Node ID abf6be2bf5fa309b3c25a3a655b3e7136a809e26
# Parent 6812b30300b2f782e080b076b587644501ef3155
# Parent d9a785c7ac93783a287478477051733ee8e11e86
default is stable

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