summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsylvain thenault <sylvain.thenault@logilab.fr>2009-05-13 15:03:39 +0200
committersylvain thenault <sylvain.thenault@logilab.fr>2009-05-13 15:03:39 +0200
commitbe487c0d9ae34516eb452e7c8274e7642b1b57a7 (patch)
treed56fc485bde854865ede10b58d9047b8b133ca5a
parent01a561af7df98424e5bcffe03970c039354921f9 (diff)
downloadlogilab-common-be487c0d9ae34516eb452e7c8274e7642b1b57a7.tar.gz
generalise mx/datetime switch to be used by umessage
-rw-r--r--__init__.py16
-rw-r--r--db.py99
-rw-r--r--umessage.py24
3 files changed, 73 insertions, 66 deletions
diff --git a/__init__.py b/__init__.py
index 7697ed1..f227213 100644
--- a/__init__.py
+++ b/__init__.py
@@ -8,7 +8,7 @@
:var IGNORED_EXTENSIONS: file extensions that may usually be ignored
:copyright:
- 2000-2008 `LOGILAB S.A. <http://www.logilab.fr>`_ (Paris, FRANCE),
+ 2000-2009 `LOGILAB S.A. <http://www.logilab.fr>`_ (Paris, FRANCE),
all rights reserved.
:contact:
@@ -26,6 +26,10 @@ STD_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build')
IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~')
+# set this to False if you've mx DateTime installed but you don't want your db
+# adapter to use it (should be set before you got a connection)
+USE_MX_DATETIME = True
+
class attrdict(dict):
"""A dictionary for which keys are also accessible as attributes."""
@@ -34,17 +38,17 @@ class attrdict(dict):
return self[attr]
except KeyError:
raise AttributeError(attr)
-
+
class dictattr(dict):
def __init__(self, proxy):
self.__proxy = proxy
-
+
def __getitem__(self, attr):
try:
return getattr(self.__proxy, attr)
except AttributeError:
raise KeyError(attr)
-
+
class nullobject(object):
def __nonzero__(self):
return False
@@ -68,7 +72,7 @@ class nullobject(object):
#
#def flatten(seq):
# for item in seq:
-# if is_scalar(item):
+# if is_scalar(item):
# yield item
# else:
# for subitem in flatten(item):
@@ -105,7 +109,7 @@ def make_domains(lists):
combinations of possibles values.
:rtype: list
-
+
Example:
>>> make_domains(['a', 'b'], ['c','d', 'e'])
diff --git a/db.py b/db.py
index f912c4b..c8c11d0 100644
--- a/db.py
+++ b/db.py
@@ -26,18 +26,16 @@ import sys
import re
from warnings import warn
+import logilab.common as lgc
from logilab.common.deprecation import obsolete
+
try:
from mx.DateTime import DateTimeType, DateTimeDeltaType, strptime
HAS_MX_DATETIME = True
except:
HAS_MX_DATETIME = False
-# set this to False if you've mx DateTime installed but you don't want your db
-# adapter to use it (should be set before you got a connection)
-USE_MX_DATETIME = True
-
-__all__ = ['get_dbapi_compliant_module',
+__all__ = ['get_dbapi_compliant_module',
'get_connection', 'set_prefered_driver',
'PyConnection', 'PyCursor',
'UnknownDriver', 'NoAdapterFound',
@@ -87,7 +85,7 @@ def _import_driver_module(driver, drivers, imported_elements=None, quiet=True):
## Connection and cursor wrappers #############################################
-
+
class SimpleConnectionWrapper:
"""A simple connection wrapper in python to decorated C-level connections
with additional attributes
@@ -96,7 +94,7 @@ class SimpleConnectionWrapper:
"""Wraps the original connection object"""
self._cnx = cnx
- # XXX : Would it work if only __getattr__ was defined
+ # XXX : Would it work if only __getattr__ was defined
def cursor(self):
"""Wraps cursor()"""
return self._cnx.cursor()
@@ -114,7 +112,7 @@ class SimpleConnectionWrapper:
return self._cnx.close()
def __getattr__(self, attrname):
- return getattr(self._cnx, attrname)
+ return getattr(self._cnx, attrname)
class PyConnection(SimpleConnectionWrapper):
"""A simple connection wrapper in python, generating wrapper for cursors as
@@ -123,7 +121,7 @@ class PyConnection(SimpleConnectionWrapper):
def __init__(self, cnx):
"""Wraps the original connection object"""
self._cnx = cnx
-
+
def cursor(self):
"""Wraps cursor()"""
return PyCursor(self._cnx.cursor())
@@ -138,7 +136,7 @@ class PyCursor:
def close(self):
"""Wraps close()"""
return self._cursor.close()
-
+
def execute(self, *args, **kwargs):
"""Wraps execute()"""
return self._cursor.execute(*args, **kwargs)
@@ -161,14 +159,14 @@ class PyCursor:
def __getattr__(self, attrname):
return getattr(self._cursor, attrname)
-
+
## Adapters list ##############################################################
-
+
class DBAPIAdapter:
"""Base class for all DBAPI adpaters"""
UNKNOWN = None
-
+
def __init__(self, native_module, pywrap=False):
"""
:type native_module: module
@@ -184,7 +182,7 @@ class DBAPIAdapter:
setattr(self, typecode, getattr(self, typecode))
except AttributeError:
print 'WARNING: %s adapter has no %s type code' % (self, typecode)
-
+
def connect(self, host='', database='', user='', password='', port=''):
"""Wraps the native module connect method"""
kwargs = {'host' : host, 'port' : port, 'database' : database,
@@ -203,16 +201,16 @@ class DBAPIAdapter:
except AttributeError:
# C or __slots__ object
cnx = SimpleConnectionWrapper(cnx)
- cnx.logged_user = user
+ cnx.logged_user = user
return cnx
-
+
def __getattr__(self, attrname):
return getattr(self._native_module, attrname)
def process_value(self, value, description, encoding='utf-8', binarywrap=None):
# if the dbapi module isn't supporting type codes, override to return value directly
typecode = description[1]
- assert typecode is not None, self
+ assert typecode is not None, self
if typecode == self.STRING:
if isinstance(value, str):
return unicode(value, encoding, 'replace')
@@ -222,10 +220,10 @@ class DBAPIAdapter:
return binarywrap(value)
elif typecode == self.UNKNOWN:
# may occurs on constant selection for instance (eg SELECT 'hop')
- # with postgresql at least
+ # with postgresql at least
if isinstance(value, str):
return unicode(value, encoding, 'replace')
-
+
## elif typecode == dbapimod.DATETIME:
## pass
## elif typecode == dbapimod.NUMBER:
@@ -234,8 +232,8 @@ class DBAPIAdapter:
## self.warning("type -%s- unknown for %r (%s) ",
## typecode, value, type(value))
return value
-
-
+
+
# Postgresql #########################################################
class _PgdbAdapter(DBAPIAdapter):
@@ -246,7 +244,7 @@ class _PgdbAdapter(DBAPIAdapter):
self.NUMBER = native_module.pgdbType('int2', 'int4', 'serial',
'int8', 'float4', 'float8',
'numeric', 'bool', 'money')
-
+
def connect(self, host='', database='', user='', password='', port=''):
"""Wraps the native module connect method"""
if port:
@@ -274,14 +272,14 @@ class _PsycopgAdapter(DBAPIAdapter):
cnx.set_isolation_level(1)
return self._wrap_if_needed(cnx, user)
-
+
class _Psycopg2Adapter(_PsycopgAdapter):
"""Simple Psycopg2 Adapter to DBAPI (cnx_string differs from classical ones)
"""
# not defined in psycopg2.extensions
# "select typname from pg_type where oid=705";
UNKNOWN = 705
-
+
def __init__(self, native_module, pywrap=False):
from psycopg2 import extensions
self.BOOLEAN = extensions.BOOLEAN
@@ -296,7 +294,7 @@ class _Psycopg2Adapter(_PsycopgAdapter):
return
psycopg2._lc_initialized = 1
# use mxDateTime instead of datetime if available
- if HAS_MX_DATETIME and USE_MX_DATETIME:
+ if HAS_MX_DATETIME and lgc.USE_MX_DATETIME:
from psycopg2 import extensions
extensions.register_type(psycopg2._psycopg.MXDATETIME)
extensions.register_type(psycopg2._psycopg.MXINTERVAL)
@@ -312,7 +310,7 @@ class _Psycopg2Adapter(_PsycopgAdapter):
#extensions.register_adapter(StringIO.StringIO, adapt_stringio)
#import cStringIO
#extensions.register_adapter(cStringIO.StringIO, adapt_stringio)
-
+
class _PgsqlAdapter(DBAPIAdapter):
"""Simple pyPgSQL Adapter to DBAPI
@@ -329,7 +327,7 @@ class _PgsqlAdapter(DBAPIAdapter):
def Binary(self, string):
"""Emulates the Binary (cf. DB-API) function"""
return str
-
+
def __getattr__(self, attrname):
# __import__('pyPgSQL.PgSQL', ...) imports the toplevel package
return getattr(self._native_module, attrname)
@@ -346,7 +344,7 @@ class _PySqlite2Adapter(DBAPIAdapter):
DATETIME = 'XXX'
NUMBER = 'XXX'
BOOLEAN = 'XXX'
-
+
def __init__(self, native_module, pywrap=False):
DBAPIAdapter.__init__(self, native_module, pywrap)
self._init_pysqlite2()
@@ -376,27 +374,27 @@ class _PySqlite2Adapter(DBAPIAdapter):
def adapt_boolean(bval):
return str(bval).upper()
sqlite.register_adapter(bool, adapt_boolean)
-
+
# decimal type handling
from decimal import Decimal
def adapt_decimal(data):
return str(data)
- sqlite.register_adapter(Decimal,adapt_decimal)
+ sqlite.register_adapter(Decimal, adapt_decimal)
def convert_decimal(data):
return Decimal(data)
- sqlite.register_converter('decimal',convert_decimal)
+ sqlite.register_converter('decimal', convert_decimal)
# date/time types handling
- if HAS_MX_DATETIME and USE_MX_DATETIME:
+ if HAS_MX_DATETIME and lgc.USE_MX_DATETIME:
def adapt_mxdatetime(mxd):
return mxd.strftime('%Y-%m-%d %H:%M:%S')
sqlite.register_adapter(DateTimeType, adapt_mxdatetime)
def adapt_mxdatetimedelta(mxd):
return mxd.strftime('%H:%M:%S')
sqlite.register_adapter(DateTimeDeltaType, adapt_mxdatetimedelta)
-
+
def convert_mxdate(ustr):
return strptime(ustr, '%Y-%m-%d %H:%M:%S')
sqlite.register_converter('date', convert_mxdate)
@@ -422,11 +420,10 @@ class _PySqlite2Adapter(DBAPIAdapter):
-
def connect(self, host='', database='', user='', password='', port=None):
"""Handles sqlite connexion format"""
sqlite = self._native_module
-
+
class PySqlite2Cursor(sqlite.Cursor):
"""cursor adapting usual dict format to pysqlite named format
in SQL queries
@@ -436,7 +433,7 @@ class _PySqlite2Adapter(DBAPIAdapter):
return re.sub(r'%\(([^\)]+)\)s', r':\1', sql)
# XXX dumb
return re.sub(r'%s', r'?', sql)
-
+
def execute(self, sql, kwargs=None):
if kwargs is None:
self.__class__.__bases__[0].execute(self, sql)
@@ -448,31 +445,31 @@ class _PySqlite2Adapter(DBAPIAdapter):
if not isinstance(kwargss, (list, tuple)):
kwargss = tuple(kwargss)
self.__class__.__bases__[0].executemany(self, self._replace_parameters(sql, kwargss[0]), kwargss)
-
+
class PySqlite2CnxWrapper:
def __init__(self, cnx):
self._cnx = cnx
-
+
def cursor(self):
return self._cnx.cursor(PySqlite2Cursor)
def __getattr__(self, attrname):
return getattr(self._cnx, attrname)
cnx = sqlite.connect(database, detect_types=sqlite.PARSE_DECLTYPES)
return self._wrap_if_needed(PySqlite2CnxWrapper(cnx), user)
-
+
def process_value(self, value, description, encoding='utf-8', binarywrap=None):
if not binarywrap is None and isinstance(value, self._native_module.Binary):
return binarywrap(value)
return value # no type code support, can't do anything
-
+
class _SqliteAdapter(DBAPIAdapter):
"""Simple sqlite Adapter to DBAPI
"""
def __init__(self, native_module, pywrap=False):
DBAPIAdapter.__init__(self, native_module, pywrap)
self.DATETIME = native_module.TIMESTAMP
-
+
def connect(self, host='', database='', user='', password='', port=''):
"""Handles sqlite connexion format"""
cnx = self._native_module.connect(database)
@@ -497,7 +494,7 @@ class _MySqlDBAdapter(DBAPIAdapter):
return
natmod._lc_initialized = 1
# date/time types handling
- if HAS_MX_DATETIME and USE_MX_DATETIME:
+ if HAS_MX_DATETIME and lgc.USE_MX_DATETIME:
from MySQLdb import times
from mx import DateTime as mxdt
times.Date = times.date = mxdt.Date
@@ -506,7 +503,7 @@ class _MySqlDBAdapter(DBAPIAdapter):
times.TimeDelta = times.timedelta = mxdt.TimeDelta
times.DateTimeType = mxdt.DateTimeType
times.DateTimeDeltaType = mxdt.DateTimeDeltaType
-
+
def connect(self, host='', database='', user='', password='', port=None,
unicode=True, charset='utf8'):
"""Handles mysqldb connexion format
@@ -557,7 +554,7 @@ class _MySqlDBAdapter(DBAPIAdapter):
try:
cursor.execute("""CREATE TABLE _type_code_test(
varchar_field varchar(50),
- text_field text unicode,
+ text_field text unicode,
mtext_field mediumtext,
binary_field tinyblob,
blob_field blob,
@@ -572,7 +569,7 @@ class _MySqlDBAdapter(DBAPIAdapter):
print name, descr[i]
finally:
cursor.execute("DROP TABLE _type_code_test")
-
+
## Drivers, Adapters and helpers registries ###################################
@@ -606,10 +603,10 @@ class _AdapterDirectory(dict):
driver_dict = self[driver]
except KeyError:
self[driver] = {}
-
+
# XXX Should we have a list of adapters ?
driver_dict[modname] = adapter
-
+
def adapt(self, database, prefered_drivers = None, pywrap = False):
"""Returns an dbapi-compliant object based for database"""
prefered_drivers = prefered_drivers or PREFERED_DRIVERS
@@ -617,7 +614,7 @@ class _AdapterDirectory(dict):
try:
return self[database][modname](module, pywrap=pywrap)
except KeyError:
- raise NoAdapterFound(obj=module)
+ raise NoAdapterFound(obj=module)
def get_adapter(self, database, modname):
try:
@@ -630,7 +627,7 @@ del _AdapterDirectory
## Main functions #############################################################
-
+
def set_prefered_driver(database, module, _drivers=PREFERED_DRIVERS):
"""sets the prefered driver module for database
database is the name of the db engine (postgresql, mysql...)
@@ -648,7 +645,7 @@ def set_prefered_driver(database, module, _drivers=PREFERED_DRIVERS):
except ValueError:
raise UnknownDriver('Unknown module %s for %s' % (module, database))
modules.insert(0, module)
-
+
def get_dbapi_compliant_module(driver, prefered_drivers = None, quiet = False,
pywrap = False):
"""returns a fully dbapi compliant module"""
@@ -663,7 +660,7 @@ def get_dbapi_compliant_module(driver, prefered_drivers = None, quiet = False,
mod.adv_func_helper = get_adv_func_helper(driver)
return mod
-def get_connection(driver='postgres', host='', database='', user='',
+def get_connection(driver='postgres', host='', database='', user='',
password='', port='', quiet=False, drivers=PREFERED_DRIVERS,
pywrap=False):
"""return a db connexion according to given arguments"""
diff --git a/umessage.py b/umessage.py
index 4aa52fe..7766bdd 100644
--- a/umessage.py
+++ b/umessage.py
@@ -10,11 +10,15 @@ import email
from encodings import search_function
from email.Utils import parseaddr, parsedate
from email.Header import decode_header
+from datetime import datetime
try:
from mx.DateTime import DateTime
except ImportError:
- def DateTime(*args): return None
+ DateTime = datetime
+
+import logilab.common as lgc
+
def decode_QP(string):
parts = []
@@ -30,13 +34,13 @@ def message_from_file(fd):
return UMessage(email.message_from_file(fd))
except email.Errors.MessageParseError:
return ''
-
+
def message_from_string(string):
try:
return UMessage(email.message_from_string(string))
except email.Errors.MessageParseError:
return ''
-
+
class UMessage:
"""Encapsulates an email.Message instance and returns only unicode objects.
"""
@@ -45,7 +49,7 @@ class UMessage:
self.message = message
# email.Message interface #################################################
-
+
def get(self, header, default=None):
value = self.message.get(header, default)
if value:
@@ -55,7 +59,7 @@ class UMessage:
def get_all(self, header, default=()):
return [decode_QP(val) for val in self.message.get_all(header, default)
if val is not None]
-
+
def get_payload(self, index=None, decode=False):
message = self.message
if index is None:
@@ -82,7 +86,7 @@ class UMessage:
def walk(self):
for part in self.message.walk():
yield UMessage(part)
-
+
def get_content_maintype(self):
return unicode(self.message.get_content_maintype())
@@ -116,9 +120,9 @@ class UMessage:
name, mail = parseaddr(person)
persons.append((name, mail))
return persons
-
+
def date(self, alternative_source=False, return_str=False):
- """return a mx.DateTime object for the email's date or None if no date is
+ """return a datetime object for the email's date or None if no date is
set or if it can't be parsed
"""
value = self.get('date')
@@ -132,7 +136,9 @@ class UMessage:
if value is not None:
datetuple = parsedate(value)
if datetuple:
- return DateTime(*datetuple[:6])
+ if lgc.USE_MX_DATETIME:
+ return DateTime(*datetuple[:6])
+ return datetime(*datetuple[:6])
elif not return_str:
return None
return value