diff options
author | sylvain thenault <sylvain.thenault@logilab.fr> | 2009-05-13 15:03:39 +0200 |
---|---|---|
committer | sylvain thenault <sylvain.thenault@logilab.fr> | 2009-05-13 15:03:39 +0200 |
commit | be487c0d9ae34516eb452e7c8274e7642b1b57a7 (patch) | |
tree | d56fc485bde854865ede10b58d9047b8b133ca5a | |
parent | 01a561af7df98424e5bcffe03970c039354921f9 (diff) | |
download | logilab-common-be487c0d9ae34516eb452e7c8274e7642b1b57a7.tar.gz |
generalise mx/datetime switch to be used by umessage
-rw-r--r-- | __init__.py | 16 | ||||
-rw-r--r-- | db.py | 99 | ||||
-rw-r--r-- | umessage.py | 24 |
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']) @@ -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 |