[Checkins] SVN: psycopgda/trunk/ - You've been eggified. Resistance is futile

Sidnei da Silva sidnei at enfoldsystems.com
Thu Oct 11 13:44:37 EDT 2007


Log message for revision 80852:
   - You've been eggified. Resistance is futile

Changed:
  _U  psycopgda/trunk/
  D   psycopgda/trunk/DEPENDENCIES.cfg
  D   psycopgda/trunk/PACKAGE.cfg
  D   psycopgda/trunk/PUBLICATION.cfg
  D   psycopgda/trunk/SETUP.cfg
  D   psycopgda/trunk/__init__.py
  D   psycopgda/trunk/adapter.py
  D   psycopgda/trunk/configure.zcml
  A   psycopgda/trunk/psycopgda/
  A   psycopgda/trunk/psycopgda/__init__.py
  A   psycopgda/trunk/psycopgda/adapter.py
  A   psycopgda/trunk/psycopgda/configure.zcml
  A   psycopgda/trunk/psycopgda/tests.py
  A   psycopgda/trunk/setup.py
  D   psycopgda/trunk/tests.py
  A   psycopgda/trunk/version.txt

-=-

Property changes on: psycopgda/trunk
___________________________________________________________________
Name: svn:ignore
   + psycopgda.egg-info


Deleted: psycopgda/trunk/DEPENDENCIES.cfg
===================================================================
--- psycopgda/trunk/DEPENDENCIES.cfg	2007-10-11 15:33:54 UTC (rev 80851)
+++ psycopgda/trunk/DEPENDENCIES.cfg	2007-10-11 17:44:36 UTC (rev 80852)
@@ -1,2 +0,0 @@
-psycopg
-zope.app
\ No newline at end of file

Deleted: psycopgda/trunk/PACKAGE.cfg
===================================================================
--- psycopgda/trunk/PACKAGE.cfg	2007-10-11 15:33:54 UTC (rev 80851)
+++ psycopgda/trunk/PACKAGE.cfg	2007-10-11 17:44:36 UTC (rev 80852)
@@ -1,24 +0,0 @@
-# Load the license from an external source, so we don't have to keep a
-# copy of it sitting around:
-<load>
-  LICENSE.txt  http://svn.zope.org/*checkout*/Zope3/trunk/ZopePublicLicense.txt?rev=25177
-</load>
-
-# Add a few things to the distribution root.
-<distribution>
-  README.txt
-</distribution>
-
-# Specify what is included in the component.
-<collection>
-
-  # Documentation files of the package:
-  *.txt
-
-  # Python modules from the package:
-  *.py
-
-  # Configuration files of the package:
-  *.zcml
-
-</collection>

Deleted: psycopgda/trunk/PUBLICATION.cfg
===================================================================
--- psycopgda/trunk/PUBLICATION.cfg	2007-10-11 15:33:54 UTC (rev 80851)
+++ psycopgda/trunk/PUBLICATION.cfg	2007-10-11 17:44:36 UTC (rev 80852)
@@ -1,9 +0,0 @@
-Metadata-Version: 1.0
-Name: psycopgda
-Summary: Psycopg Database Adapter for Zope 3
-Author: Zope Corporation and Contributors
-Author-email: zope3-dev at zope.org
-License: ZPL 2.1
-Description:
-        This package allows Zope 3 to connect to any PostGreSQL database via
-        the common Zope 3 RDB connection facilities.

Deleted: psycopgda/trunk/SETUP.cfg
===================================================================
--- psycopgda/trunk/SETUP.cfg	2007-10-11 15:33:54 UTC (rev 80851)
+++ psycopgda/trunk/SETUP.cfg	2007-10-11 17:44:36 UTC (rev 80852)
@@ -1,3 +0,0 @@
-<data-files zopeskel/etc/package-includes>
-  psycopgda-*.zcml
-</data-files>

Deleted: psycopgda/trunk/__init__.py
===================================================================
--- psycopgda/trunk/__init__.py	2007-10-11 15:33:54 UTC (rev 80851)
+++ psycopgda/trunk/__init__.py	2007-10-11 17:44:36 UTC (rev 80852)
@@ -1 +0,0 @@
-# make this directory a package

Deleted: psycopgda/trunk/adapter.py
===================================================================
--- psycopgda/trunk/adapter.py	2007-10-11 15:33:54 UTC (rev 80851)
+++ psycopgda/trunk/adapter.py	2007-10-11 17:44:36 UTC (rev 80852)
@@ -1,419 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002-2006 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""PostgreSQL Database Adapter for Zope 3
-
-$Id$
-"""
-
-from zope.interface import implements
-from zope.rdb import (
-        ZopeDatabaseAdapter, parseDSN, ZopeConnection, ZopeCursor
-        )
-from zope.rdb.interfaces import DatabaseException, IZopeConnection
-from zope.publisher.interfaces import Retry
-
-from datetime import date, time, datetime, timedelta
-import psycopg
-import re
-import sys
-
-
-# These OIDs are taken from include/server/pg_type.h from PostgreSQL headers.
-# Unfortunatelly psycopg does not export them as constants, and
-# we cannot use psycopg.FOO.values because they overlap.
-DATE_OID        = 1082
-TIME_OID        = 1083
-TIMETZ_OID      = 1266
-TIMESTAMP_OID   = 1114
-TIMESTAMPTZ_OID = 1184
-INTERVAL_OID    = 1186
-
-CHAR_OID = 18
-TEXT_OID = 25
-BPCHAR_OID = 1042
-VARCHAR_OID = 1043
-
-# The following ones are obsolete and we don't handle them
-#ABSTIME_OID     = 702
-#RELTIME_OID     = 703
-#TINTERVAL_OID   = 704
-
-# Date/time parsing functions
-
-_dateFmt = re.compile(r"^(\d\d\d\d)-?([01]\d)-?([0-3]\d)$")
-
-def parse_date(s):
-    """Parses ISO-8601 compliant dates and returns a tuple (year, month,
-    day).
-
-    The following formats are accepted:
-        YYYY-MM-DD  (extended format)
-        YYYYMMDD    (basic format)
-    """
-    m = _dateFmt.match(s)
-    if m is None:
-        raise ValueError, 'invalid date string: %s' % s
-    year, month, day = m.groups()
-    return int(year), int(month), int(day)
-
-
-_timeFmt = re.compile(
-    r"^([0-2]\d)(?::?([0-5]\d)(?::?([0-5]\d)(?:[.,](\d+))?)?)?$")
-
-def parse_time(s):
-    """Parses ISO-8601 compliant times and returns a tuple (hour, minute,
-    second).
-
-    The following formats are accepted:
-        HH:MM:SS.ssss or HHMMSS.ssss
-        HH:MM:SS,ssss or HHMMSS,ssss
-        HH:MM:SS      or HHMMSS
-        HH:MM         or HHMM
-        HH
-    """
-    m = _timeFmt.match(s)
-    if m is None:
-        raise ValueError, 'invalid time string: %s' % s
-    hr, mn, sc, msc = m.groups(0)
-    if msc != 0:
-        sc = float("%s.%s" % (sc, msc))
-    else:
-        sc = int(sc)
-    return int(hr), int(mn), sc
-
-
-_tzFmt = re.compile(r"^([+-])([0-2]\d)(?::?([0-5]\d))?$")
-
-def parse_tz(s):
-    """Parses ISO-8601 timezones and returns the offset east of UTC in
-    minutes.
-
-    The following formats are accepted:
-        +/-HH:MM
-        +/-HHMM
-        +/-HH
-        Z           (equivalent to +0000)
-    """
-    if s == 'Z':
-        return 0
-    m = _tzFmt.match(s)
-    if m is None:
-        raise ValueError, 'invalid time zone: %s' % s
-    d, hoff, moff = m.groups(0)
-    if d == "-":
-        return - int(hoff) * 60 - int(moff)
-    return int(hoff) * 60 + int(moff)
-
-
-_tzPos = re.compile(r"[Z+-]")
-
-def parse_timetz(s):
-    """Parses ISO-8601 compliant times that may include timezone information
-    and returns a tuple (hour, minute, second, tzoffset).
-
-    tzoffset is the offset east of UTC in minutes.  It will be None if s does
-    not include time zone information.
-
-    Formats accepted are those listed in the descriptions of parse_time() and
-    parse_tz().  Time zone should immediatelly follow time without intervening
-    spaces.
-    """
-    m = _tzPos.search(s)
-    if m is None:
-        return parse_time(s) + (None,)
-    pos = m.start()
-    return parse_time(s[:pos]) + (parse_tz(s[pos:]),)
-
-
-_datetimeFmt = re.compile(r"[T ]")
-
-def _split_datetime(s):
-    """Split date and time parts of ISO-8601 compliant timestamp and
-    return a tuple (date, time).
-
-    ' ' or 'T' used to separate date and time parts.
-    """
-    m = _datetimeFmt.search(s)
-    if m is None:
-        raise ValueError, 'time part of datetime missing: %s' % s
-    pos = m.start()
-    return s[:pos], s[pos + 1:]
-
-
-def parse_datetime(s):
-    """Parses ISO-8601 compliant timestamp and returns a tuple (year, month,
-    day, hour, minute, second).
-
-    Formats accepted are those listed in the descriptions of parse_date() and
-    parse_time() with ' ' or 'T' used to separate date and time parts.
-    """
-    dt, tm = _split_datetime(s)
-    return parse_date(dt) + parse_time(tm)
-
-
-def parse_datetimetz(s):
-    """Parses ISO-8601 compliant timestamp that may include timezone
-    information and returns a tuple (year, month, day, hour, minute, second,
-    tzoffset).
-
-    tzoffset is the offset east of UTC in minutes.  It will be None if s does
-    not include time zone information.
-
-    Formats accepted are those listed in the descriptions of parse_date() and
-    parse_timetz() with ' ' or 'T' used to separate date and time parts.
-    """
-    dt, tm = _split_datetime(s)
-    return parse_date(dt) + parse_timetz(tm)
-
-
-def parse_interval(s):
-    """Parses PostgreSQL interval notation and returns a tuple (years, months,
-    days, hours, minutes, seconds).
-
-    Values accepted:
-        interval  ::= date
-                   |  time
-                   |  date time
-        date      ::= date_comp
-                   |  date date_comp
-        date_comp ::= 1 'day'
-                   |  number 'days'
-                   |  1 'month'
-                   |  1 'mon'
-                   |  number 'months'
-                   |  number 'mons'
-                   |  1 'year'
-                   |  number 'years'
-        time      ::= number ':' number
-                   |  number ':' number ':' number
-                   |  number ':' number ':' number '.' fraction
-    """
-    years = months = days = 0
-    hours = minutes = seconds = 0
-    elements = s.split()
-    # Tests with 7.4.6 on Ubuntu 5.4 interval output returns 'mon' and 'mons'
-    # and not 'month' or 'months' as expected. I've fixed this and left
-    # the original matches there too in case this is dependant on
-    # OS or PostgreSQL release.
-    for i in range(0, len(elements) - 1, 2):
-        count, unit = elements[i:i+2]
-        if unit == 'day' and count == '1':
-            days += 1
-        elif unit == 'days':
-            days += int(count)
-        elif unit == 'month' and count == '1':
-            months += 1
-        elif unit == 'mon' and count == '1':
-            months += 1
-        elif unit == 'months':
-            months += int(count)
-        elif unit == 'mons':
-            months += int(count)
-        elif unit == 'year' and count == '1':
-            years += 1
-        elif unit == 'years':
-            years += int(count)
-        else:
-            raise ValueError, 'unknown time interval %s %s' % (count, unit)
-    if len(elements) % 2 == 1:
-        hours, minutes, seconds = parse_time(elements[-1])
-    return (years, months, days, hours, minutes, seconds)
-
-
-# Type conversions
-def _conv_date(s):
-    if s:
-        return date(*parse_date(s))
-
-def _conv_time(s):
-    if s:
-        hr, mn, sc = parse_time(s)
-        sc, micro = divmod(sc, 1.0)
-        micro = round(micro * 1000000)
-        return time(hr, mn, int(sc), int(micro))
-
-def _conv_timetz(s):
-    if s:
-        from zope.app.datetimeutils import tzinfo
-        hr, mn, sc, tz = parse_timetz(s)
-        sc, micro = divmod(sc, 1.0)
-        micro = round(micro * 1000000)
-        if tz: tz = tzinfo(tz)
-        return time(hr, mn, int(sc), int(micro), tz)
-
-def _conv_timestamp(s):
-    if s:
-        y, m, d, hr, mn, sc = parse_datetime(s)
-        sc, micro = divmod(sc, 1.0)
-        micro = round(micro * 1000000)
-        return datetime(y, m, d, hr, mn, int(sc), int(micro))
-
-def _conv_timestamptz(s):
-    if s:
-        from zope.app.datetimeutils import tzinfo
-        y, m, d, hr, mn, sc, tz = parse_datetimetz(s)
-        sc, micro = divmod(sc, 1.0)
-        micro = round(micro * 1000000)
-        if tz: tz = tzinfo(tz)
-        return datetime(y, m, d, hr, mn, int(sc), int(micro), tz)
-
-def _conv_interval(s):
-    if s:
-        y, m, d, hr, mn, sc = parse_interval(s)
-        if (y, m) != (0, 0):
-            # XXX: Currently there's no way to represent years and months as
-            # timedeltas
-            return s
-        else:
-            return timedelta(days=d, hours=hr, minutes=mn, seconds=sc)
-
-def _get_string_conv(encoding):
-    def _conv_string(s):
-        if s is not None:
-            s = s.decode(encoding)
-        return s
-    return _conv_string
-
-# User-defined types
-DATE = psycopg.new_type((DATE_OID,), "ZDATE", _conv_date)
-TIME = psycopg.new_type((TIME_OID,), "ZTIME", _conv_time)
-TIMETZ = psycopg.new_type((TIMETZ_OID,), "ZTIMETZ", _conv_timetz)
-TIMESTAMP = psycopg.new_type((TIMESTAMP_OID,), "ZTIMESTAMP", _conv_timestamp)
-TIMESTAMPTZ = psycopg.new_type((TIMESTAMPTZ_OID,), "ZTIMESTAMPTZ",
-                                _conv_timestamptz)
-INTERVAL = psycopg.new_type((INTERVAL_OID,), "ZINTERVAL", _conv_interval)
-
-
-dsn2option_mapping = {'host': 'host',
-                      'port': 'port',
-                      'dbname': 'dbname',
-                      'username': 'user',
-                      'password': 'password'}
-
-def registerTypes(encoding):
-    """Register type conversions for psycopg"""
-    psycopg.register_type(DATE)
-    psycopg.register_type(TIME)
-    psycopg.register_type(TIMETZ)
-    psycopg.register_type(TIMESTAMP)
-    psycopg.register_type(TIMESTAMPTZ)
-    psycopg.register_type(INTERVAL)
-    STRING = psycopg.new_type((CHAR_OID, TEXT_OID, BPCHAR_OID, VARCHAR_OID),
-                              "ZSTRING", _get_string_conv(encoding))
-    psycopg.register_type(STRING)
-
-class PsycopgAdapter(ZopeDatabaseAdapter):
-    """A PsycoPG adapter for Zope3.
-
-    The following type conversions are performed:
-
-        DATE -> datetime.date
-        TIME -> datetime.time
-        TIMETZ -> datetime.time
-        TIMESTAMP -> datetime.datetime
-        TIMESTAMPTZ -> datetime.datetime
-
-    XXX: INTERVAL cannot be represented exactly as datetime.timedelta since
-    it might be something like '1 month', which is a variable number of days.
-    """
-
-    def connect(self):
-        if not self.isConnected():
-            try:
-                self._v_connection = PsycopgConnection(
-                        self._connection_factory(), self
-                        )
-            except psycopg.Error, error:
-                raise DatabaseException, str(error)
-
-    def registerTypes(self):
-        registerTypes(self.getEncoding())
-
-    def _connection_factory(self):
-        """Create a Psycopg DBI connection based on the DSN"""
-        self.registerTypes()
-        conn_info = parseDSN(self.dsn)
-        conn_list = []
-        for dsnname, optname in dsn2option_mapping.iteritems():
-            if conn_info[dsnname]:
-                conn_list.append('%s=%s' % (optname, conn_info[dsnname]))
-        conn_str = ' '.join(conn_list)
-        connection = psycopg.connect(conn_str)
-
-        # Ensure we are in SERIALIZABLE transaction isolation level.
-        # This is the default under psycopg1, but changed to READ COMMITTED
-        # under psycopg2. This should become an option if anyone wants
-        # different isolation levels.
-        connection.set_isolation_level(3)
-
-        return connection
-
-
-def _handle_psycopg_exception(error):
-    """Called from a exception handler for psycopg.Error.
-
-    If we have a serialization exception or a deadlock, we should retry the
-    transaction by raising a Retry exception. Otherwise, we reraise.
-    """
-    if not error.args:
-        raise
-    msg = error.args[0]
-    # These messages are from PostgreSQL 8.0. They may change between
-    # PostgreSQL releases - if so, the different messages should be added
-    # rather than the existing ones changed so this logic works with
-    # different versions.
-    if msg.startswith(
-            'ERROR:  could not serialize access due to concurrent update'
-            ):
-        raise Retry(sys.exc_info())
-    if msg.startswith('ERROR:  deadlock detected'):
-        raise Retry(sys.exc_info())
-    raise
-
-
-class IPsycopgZopeConnection(IZopeConnection):
-    """A marker interface stating that this connection uses PostgreSQL."""
-
-
-class PsycopgConnection(ZopeConnection):
-
-    implements(IPsycopgZopeConnection)
-
-    def cursor(self):
-        """See IZopeConnection"""
-        return PsycopgCursor(self.conn.cursor(), self)
-
-    def commit(self):
-        try:
-            ZopeConnection.commit(self)
-        except psycopg.Error, error:
-            _handle_psycopg_exception(error)
-
-
-class PsycopgCursor(ZopeCursor):
-
-    def execute(self, operation, parameters=None):
-        """See IZopeCursor"""
-        try:
-            return ZopeCursor.execute(self, operation, parameters)
-        except psycopg.Error, error:
-            _handle_psycopg_exception(error)
-
-    def executemany(operation, seq_of_parameters=None):
-        """See IZopeCursor"""
-        raise RuntimeError, 'Oos'
-        try:
-            return ZopeCursor.execute(self, operation, seq_of_parameters)
-        except psycopg.Error, error:
-            _handle_psycopg_exception(error)

Deleted: psycopgda/trunk/configure.zcml
===================================================================
--- psycopgda/trunk/configure.zcml	2007-10-11 15:33:54 UTC (rev 80851)
+++ psycopgda/trunk/configure.zcml	2007-10-11 17:44:36 UTC (rev 80852)
@@ -1,51 +0,0 @@
-<configure
-    xmlns="http://namespaces.zope.org/zope"
-    xmlns:browser="http://namespaces.zope.org/browser"
-    i18n_domain="psycopgda">
-
-  <class class=".adapter.PsycopgAdapter">
-    <factory id="zope.da.PsycopgDA" />
-    <require
-        permission="zope.rdb.Use"
-        interface="zope.rdb.interfaces.IZopeDatabaseAdapter"
-        />
-    <require
-        permission="zope.ManageServices"
-        interface="zope.rdb.interfaces.IZopeDatabaseAdapterManagement"
-        />
-  </class>
-
-  <class class=".adapter.PsycopgConnection">
-    <require
-        permission="zope.rdb.Use"
-        interface="zope.rdb.interfaces.IZopeConnection"
-        />
-  </class>
-
-  <class class=".adapter.PsycopgCursor">
-    <require
-        permission="zope.rdb.Use"
-        interface="zope.rdb.interfaces.IZopeCursor"
-        />
-  </class>
-
-  <browser:addform
-      name="AddPsycopgDA"
-      schema="zope.rdb.interfaces.IManageableZopeDatabaseAdapter"
-      label="Add Psycopg (PostGreSQL) Database Adapter"
-      content_factory=".adapter.PsycopgAdapter"
-      arguments="dsn"
-      fields="dsn"
-      permission="zope.ManageContent"
-      />
-
-  <!-- Menu entry for "add utility" menu -->
-  <browser:addMenuItem
-      class=".adapter.PsycopgAdapter"
-      title="Psycopg DA"
-      description="A PostgreSQL Database Adapter using the Psycopg driver"
-      permission="zope.ManageApplication"
-      view="AddPsycopgDA"
-      />
-
-</configure>


Property changes on: psycopgda/trunk/psycopgda
___________________________________________________________________
Name: svn:ignore
   + '*.pyc'


Copied: psycopgda/trunk/psycopgda/__init__.py (from rev 80794, psycopgda/trunk/__init__.py)
===================================================================
--- psycopgda/trunk/psycopgda/__init__.py	                        (rev 0)
+++ psycopgda/trunk/psycopgda/__init__.py	2007-10-11 17:44:36 UTC (rev 80852)
@@ -0,0 +1 @@
+# make this directory a package

Copied: psycopgda/trunk/psycopgda/adapter.py (from rev 80794, psycopgda/trunk/adapter.py)
===================================================================
--- psycopgda/trunk/psycopgda/adapter.py	                        (rev 0)
+++ psycopgda/trunk/psycopgda/adapter.py	2007-10-11 17:44:36 UTC (rev 80852)
@@ -0,0 +1,419 @@
+##############################################################################
+#
+# Copyright (c) 2002-2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""PostgreSQL Database Adapter for Zope 3
+
+$Id$
+"""
+
+from zope.interface import implements
+from zope.rdb import (
+        ZopeDatabaseAdapter, parseDSN, ZopeConnection, ZopeCursor
+        )
+from zope.rdb.interfaces import DatabaseException, IZopeConnection
+from zope.publisher.interfaces import Retry
+
+from datetime import date, time, datetime, timedelta
+import psycopg
+import re
+import sys
+
+
+# These OIDs are taken from include/server/pg_type.h from PostgreSQL headers.
+# Unfortunatelly psycopg does not export them as constants, and
+# we cannot use psycopg.FOO.values because they overlap.
+DATE_OID        = 1082
+TIME_OID        = 1083
+TIMETZ_OID      = 1266
+TIMESTAMP_OID   = 1114
+TIMESTAMPTZ_OID = 1184
+INTERVAL_OID    = 1186
+
+CHAR_OID = 18
+TEXT_OID = 25
+BPCHAR_OID = 1042
+VARCHAR_OID = 1043
+
+# The following ones are obsolete and we don't handle them
+#ABSTIME_OID     = 702
+#RELTIME_OID     = 703
+#TINTERVAL_OID   = 704
+
+# Date/time parsing functions
+
+_dateFmt = re.compile(r"^(\d\d\d\d)-?([01]\d)-?([0-3]\d)$")
+
+def parse_date(s):
+    """Parses ISO-8601 compliant dates and returns a tuple (year, month,
+    day).
+
+    The following formats are accepted:
+        YYYY-MM-DD  (extended format)
+        YYYYMMDD    (basic format)
+    """
+    m = _dateFmt.match(s)
+    if m is None:
+        raise ValueError, 'invalid date string: %s' % s
+    year, month, day = m.groups()
+    return int(year), int(month), int(day)
+
+
+_timeFmt = re.compile(
+    r"^([0-2]\d)(?::?([0-5]\d)(?::?([0-5]\d)(?:[.,](\d+))?)?)?$")
+
+def parse_time(s):
+    """Parses ISO-8601 compliant times and returns a tuple (hour, minute,
+    second).
+
+    The following formats are accepted:
+        HH:MM:SS.ssss or HHMMSS.ssss
+        HH:MM:SS,ssss or HHMMSS,ssss
+        HH:MM:SS      or HHMMSS
+        HH:MM         or HHMM
+        HH
+    """
+    m = _timeFmt.match(s)
+    if m is None:
+        raise ValueError, 'invalid time string: %s' % s
+    hr, mn, sc, msc = m.groups(0)
+    if msc != 0:
+        sc = float("%s.%s" % (sc, msc))
+    else:
+        sc = int(sc)
+    return int(hr), int(mn), sc
+
+
+_tzFmt = re.compile(r"^([+-])([0-2]\d)(?::?([0-5]\d))?$")
+
+def parse_tz(s):
+    """Parses ISO-8601 timezones and returns the offset east of UTC in
+    minutes.
+
+    The following formats are accepted:
+        +/-HH:MM
+        +/-HHMM
+        +/-HH
+        Z           (equivalent to +0000)
+    """
+    if s == 'Z':
+        return 0
+    m = _tzFmt.match(s)
+    if m is None:
+        raise ValueError, 'invalid time zone: %s' % s
+    d, hoff, moff = m.groups(0)
+    if d == "-":
+        return - int(hoff) * 60 - int(moff)
+    return int(hoff) * 60 + int(moff)
+
+
+_tzPos = re.compile(r"[Z+-]")
+
+def parse_timetz(s):
+    """Parses ISO-8601 compliant times that may include timezone information
+    and returns a tuple (hour, minute, second, tzoffset).
+
+    tzoffset is the offset east of UTC in minutes.  It will be None if s does
+    not include time zone information.
+
+    Formats accepted are those listed in the descriptions of parse_time() and
+    parse_tz().  Time zone should immediatelly follow time without intervening
+    spaces.
+    """
+    m = _tzPos.search(s)
+    if m is None:
+        return parse_time(s) + (None,)
+    pos = m.start()
+    return parse_time(s[:pos]) + (parse_tz(s[pos:]),)
+
+
+_datetimeFmt = re.compile(r"[T ]")
+
+def _split_datetime(s):
+    """Split date and time parts of ISO-8601 compliant timestamp and
+    return a tuple (date, time).
+
+    ' ' or 'T' used to separate date and time parts.
+    """
+    m = _datetimeFmt.search(s)
+    if m is None:
+        raise ValueError, 'time part of datetime missing: %s' % s
+    pos = m.start()
+    return s[:pos], s[pos + 1:]
+
+
+def parse_datetime(s):
+    """Parses ISO-8601 compliant timestamp and returns a tuple (year, month,
+    day, hour, minute, second).
+
+    Formats accepted are those listed in the descriptions of parse_date() and
+    parse_time() with ' ' or 'T' used to separate date and time parts.
+    """
+    dt, tm = _split_datetime(s)
+    return parse_date(dt) + parse_time(tm)
+
+
+def parse_datetimetz(s):
+    """Parses ISO-8601 compliant timestamp that may include timezone
+    information and returns a tuple (year, month, day, hour, minute, second,
+    tzoffset).
+
+    tzoffset is the offset east of UTC in minutes.  It will be None if s does
+    not include time zone information.
+
+    Formats accepted are those listed in the descriptions of parse_date() and
+    parse_timetz() with ' ' or 'T' used to separate date and time parts.
+    """
+    dt, tm = _split_datetime(s)
+    return parse_date(dt) + parse_timetz(tm)
+
+
+def parse_interval(s):
+    """Parses PostgreSQL interval notation and returns a tuple (years, months,
+    days, hours, minutes, seconds).
+
+    Values accepted:
+        interval  ::= date
+                   |  time
+                   |  date time
+        date      ::= date_comp
+                   |  date date_comp
+        date_comp ::= 1 'day'
+                   |  number 'days'
+                   |  1 'month'
+                   |  1 'mon'
+                   |  number 'months'
+                   |  number 'mons'
+                   |  1 'year'
+                   |  number 'years'
+        time      ::= number ':' number
+                   |  number ':' number ':' number
+                   |  number ':' number ':' number '.' fraction
+    """
+    years = months = days = 0
+    hours = minutes = seconds = 0
+    elements = s.split()
+    # Tests with 7.4.6 on Ubuntu 5.4 interval output returns 'mon' and 'mons'
+    # and not 'month' or 'months' as expected. I've fixed this and left
+    # the original matches there too in case this is dependant on
+    # OS or PostgreSQL release.
+    for i in range(0, len(elements) - 1, 2):
+        count, unit = elements[i:i+2]
+        if unit == 'day' and count == '1':
+            days += 1
+        elif unit == 'days':
+            days += int(count)
+        elif unit == 'month' and count == '1':
+            months += 1
+        elif unit == 'mon' and count == '1':
+            months += 1
+        elif unit == 'months':
+            months += int(count)
+        elif unit == 'mons':
+            months += int(count)
+        elif unit == 'year' and count == '1':
+            years += 1
+        elif unit == 'years':
+            years += int(count)
+        else:
+            raise ValueError, 'unknown time interval %s %s' % (count, unit)
+    if len(elements) % 2 == 1:
+        hours, minutes, seconds = parse_time(elements[-1])
+    return (years, months, days, hours, minutes, seconds)
+
+
+# Type conversions
+def _conv_date(s):
+    if s:
+        return date(*parse_date(s))
+
+def _conv_time(s):
+    if s:
+        hr, mn, sc = parse_time(s)
+        sc, micro = divmod(sc, 1.0)
+        micro = round(micro * 1000000)
+        return time(hr, mn, int(sc), int(micro))
+
+def _conv_timetz(s):
+    if s:
+        from zope.datetime import tzinfo
+        hr, mn, sc, tz = parse_timetz(s)
+        sc, micro = divmod(sc, 1.0)
+        micro = round(micro * 1000000)
+        if tz: tz = tzinfo(tz)
+        return time(hr, mn, int(sc), int(micro), tz)
+
+def _conv_timestamp(s):
+    if s:
+        y, m, d, hr, mn, sc = parse_datetime(s)
+        sc, micro = divmod(sc, 1.0)
+        micro = round(micro * 1000000)
+        return datetime(y, m, d, hr, mn, int(sc), int(micro))
+
+def _conv_timestamptz(s):
+    if s:
+        from zope.datetime import tzinfo
+        y, m, d, hr, mn, sc, tz = parse_datetimetz(s)
+        sc, micro = divmod(sc, 1.0)
+        micro = round(micro * 1000000)
+        if tz: tz = tzinfo(tz)
+        return datetime(y, m, d, hr, mn, int(sc), int(micro), tz)
+
+def _conv_interval(s):
+    if s:
+        y, m, d, hr, mn, sc = parse_interval(s)
+        if (y, m) != (0, 0):
+            # XXX: Currently there's no way to represent years and months as
+            # timedeltas
+            return s
+        else:
+            return timedelta(days=d, hours=hr, minutes=mn, seconds=sc)
+
+def _get_string_conv(encoding):
+    def _conv_string(s):
+        if s is not None:
+            s = s.decode(encoding)
+        return s
+    return _conv_string
+
+# User-defined types
+DATE = psycopg.new_type((DATE_OID,), "ZDATE", _conv_date)
+TIME = psycopg.new_type((TIME_OID,), "ZTIME", _conv_time)
+TIMETZ = psycopg.new_type((TIMETZ_OID,), "ZTIMETZ", _conv_timetz)
+TIMESTAMP = psycopg.new_type((TIMESTAMP_OID,), "ZTIMESTAMP", _conv_timestamp)
+TIMESTAMPTZ = psycopg.new_type((TIMESTAMPTZ_OID,), "ZTIMESTAMPTZ",
+                                _conv_timestamptz)
+INTERVAL = psycopg.new_type((INTERVAL_OID,), "ZINTERVAL", _conv_interval)
+
+
+dsn2option_mapping = {'host': 'host',
+                      'port': 'port',
+                      'dbname': 'dbname',
+                      'username': 'user',
+                      'password': 'password'}
+
+def registerTypes(encoding):
+    """Register type conversions for psycopg"""
+    psycopg.register_type(DATE)
+    psycopg.register_type(TIME)
+    psycopg.register_type(TIMETZ)
+    psycopg.register_type(TIMESTAMP)
+    psycopg.register_type(TIMESTAMPTZ)
+    psycopg.register_type(INTERVAL)
+    STRING = psycopg.new_type((CHAR_OID, TEXT_OID, BPCHAR_OID, VARCHAR_OID),
+                              "ZSTRING", _get_string_conv(encoding))
+    psycopg.register_type(STRING)
+
+class PsycopgAdapter(ZopeDatabaseAdapter):
+    """A PsycoPG adapter for Zope3.
+
+    The following type conversions are performed:
+
+        DATE -> datetime.date
+        TIME -> datetime.time
+        TIMETZ -> datetime.time
+        TIMESTAMP -> datetime.datetime
+        TIMESTAMPTZ -> datetime.datetime
+
+    XXX: INTERVAL cannot be represented exactly as datetime.timedelta since
+    it might be something like '1 month', which is a variable number of days.
+    """
+
+    def connect(self):
+        if not self.isConnected():
+            try:
+                self._v_connection = PsycopgConnection(
+                        self._connection_factory(), self
+                        )
+            except psycopg.Error, error:
+                raise DatabaseException, str(error)
+
+    def registerTypes(self):
+        registerTypes(self.getEncoding())
+
+    def _connection_factory(self):
+        """Create a Psycopg DBI connection based on the DSN"""
+        self.registerTypes()
+        conn_info = parseDSN(self.dsn)
+        conn_list = []
+        for dsnname, optname in dsn2option_mapping.iteritems():
+            if conn_info[dsnname]:
+                conn_list.append('%s=%s' % (optname, conn_info[dsnname]))
+        conn_str = ' '.join(conn_list)
+        connection = psycopg.connect(conn_str)
+
+        # Ensure we are in SERIALIZABLE transaction isolation level.
+        # This is the default under psycopg1, but changed to READ COMMITTED
+        # under psycopg2. This should become an option if anyone wants
+        # different isolation levels.
+        connection.set_isolation_level(3)
+
+        return connection
+
+
+def _handle_psycopg_exception(error):
+    """Called from a exception handler for psycopg.Error.
+
+    If we have a serialization exception or a deadlock, we should retry the
+    transaction by raising a Retry exception. Otherwise, we reraise.
+    """
+    if not error.args:
+        raise
+    msg = error.args[0]
+    # These messages are from PostgreSQL 8.0. They may change between
+    # PostgreSQL releases - if so, the different messages should be added
+    # rather than the existing ones changed so this logic works with
+    # different versions.
+    if msg.startswith(
+            'ERROR:  could not serialize access due to concurrent update'
+            ):
+        raise Retry(sys.exc_info())
+    if msg.startswith('ERROR:  deadlock detected'):
+        raise Retry(sys.exc_info())
+    raise
+
+
+class IPsycopgZopeConnection(IZopeConnection):
+    """A marker interface stating that this connection uses PostgreSQL."""
+
+
+class PsycopgConnection(ZopeConnection):
+
+    implements(IPsycopgZopeConnection)
+
+    def cursor(self):
+        """See IZopeConnection"""
+        return PsycopgCursor(self.conn.cursor(), self)
+
+    def commit(self):
+        try:
+            ZopeConnection.commit(self)
+        except psycopg.Error, error:
+            _handle_psycopg_exception(error)
+
+
+class PsycopgCursor(ZopeCursor):
+
+    def execute(self, operation, parameters=None):
+        """See IZopeCursor"""
+        try:
+            return ZopeCursor.execute(self, operation, parameters)
+        except psycopg.Error, error:
+            _handle_psycopg_exception(error)
+
+    def executemany(operation, seq_of_parameters=None):
+        """See IZopeCursor"""
+        raise RuntimeError, 'Oos'
+        try:
+            return ZopeCursor.execute(self, operation, seq_of_parameters)
+        except psycopg.Error, error:
+            _handle_psycopg_exception(error)

Copied: psycopgda/trunk/psycopgda/configure.zcml (from rev 80794, psycopgda/trunk/configure.zcml)
===================================================================
--- psycopgda/trunk/psycopgda/configure.zcml	                        (rev 0)
+++ psycopgda/trunk/psycopgda/configure.zcml	2007-10-11 17:44:36 UTC (rev 80852)
@@ -0,0 +1,51 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    i18n_domain="psycopgda">
+
+  <class class=".adapter.PsycopgAdapter">
+    <factory id="zope.da.PsycopgDA" />
+    <require
+        permission="zope.rdb.Use"
+        interface="zope.rdb.interfaces.IZopeDatabaseAdapter"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.rdb.interfaces.IZopeDatabaseAdapterManagement"
+        />
+  </class>
+
+  <class class=".adapter.PsycopgConnection">
+    <require
+        permission="zope.rdb.Use"
+        interface="zope.rdb.interfaces.IZopeConnection"
+        />
+  </class>
+
+  <class class=".adapter.PsycopgCursor">
+    <require
+        permission="zope.rdb.Use"
+        interface="zope.rdb.interfaces.IZopeCursor"
+        />
+  </class>
+
+  <browser:addform
+      name="AddPsycopgDA"
+      schema="zope.rdb.interfaces.IManageableZopeDatabaseAdapter"
+      label="Add Psycopg (PostGreSQL) Database Adapter"
+      content_factory=".adapter.PsycopgAdapter"
+      arguments="dsn"
+      fields="dsn"
+      permission="zope.ManageContent"
+      />
+
+  <!-- Menu entry for "add utility" menu -->
+  <browser:addMenuItem
+      class=".adapter.PsycopgAdapter"
+      title="Psycopg DA"
+      description="A PostgreSQL Database Adapter using the Psycopg driver"
+      permission="zope.ManageApplication"
+      view="AddPsycopgDA"
+      />
+
+</configure>

Copied: psycopgda/trunk/psycopgda/tests.py (from rev 80794, psycopgda/trunk/tests.py)
===================================================================
--- psycopgda/trunk/psycopgda/tests.py	                        (rev 0)
+++ psycopgda/trunk/psycopgda/tests.py	2007-10-11 17:44:36 UTC (rev 80852)
@@ -0,0 +1,368 @@
+##############################################################################
+#
+# Copyright (c) 2002-2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Unit tests for PsycopgDA.
+
+$Id$
+"""
+from unittest import TestCase, TestSuite, main, makeSuite
+from datetime import tzinfo, timedelta
+import psycopg
+
+class Stub(object):
+
+    def __init__(self, **kw):
+        self.__dict__.update(kw)
+
+class TZStub(tzinfo):
+
+    def __init__(self, h, m):
+        self.offset = h * 60 + m
+
+    def utcoffset(self, dt):
+        return timedelta(minutes=self.offset)
+
+    def dst(self, dt):
+        return 0
+
+    def tzname(self, dt):
+        return ''
+
+    def __repr__(self):
+        return 'tzinfo(%d)' % self.offset
+
+    def __reduce__(self):
+        return type(self), (), self.__dict__
+
+class ConnectionStub(object):
+
+    def set_isolation_level(self, level):
+        pass
+
+class PsycopgStub(object):
+
+    __shared_state = {}     # 'Borg' design pattern
+
+    DATE = psycopg.DATE
+    TIME = psycopg.TIME
+    DATETIME = psycopg.DATETIME
+    INTERVAL = psycopg.INTERVAL
+
+    def __init__(self):
+        self.__dict__ = self.__shared_state
+        self.types = {}
+
+    def connect(self, connection_string):
+        self.last_connection_string = connection_string
+        return ConnectionStub()
+
+    def new_type(self, values, name, converter):
+        return Stub(name=name, values=values)
+
+    def register_type(self, type):
+        for typeid in type.values:
+            self.types[typeid] = type
+
+
+class TestPsycopgTypeConversion(TestCase):
+
+    def test_conv_date(self):
+        from psycopgda.adapter import _conv_date
+        from datetime import date
+        self.assertEquals(_conv_date(''), None)
+        self.assertEquals(_conv_date('2001-03-02'), date(2001, 3, 2))
+
+    def test_conv_time(self):
+        from psycopgda.adapter import _conv_time
+        from datetime import time
+        self.assertEquals(_conv_time(''), None)
+        self.assertEquals(_conv_time('23:17:57'),
+                          time(23, 17, 57))
+        self.assertEquals(_conv_time('23:17:57.037'),
+                          time(23, 17, 57, 37000))
+
+    def test_conv_timetz(self):
+        from psycopgda.adapter import _conv_timetz
+        from datetime import time
+        self.assertEquals(_conv_timetz(''), None)
+        self.assertEquals(_conv_timetz('12:44:01+01:00'),
+                          time(12, 44, 01, 0, TZStub(1,0)))
+        self.assertEquals(_conv_timetz('12:44:01.037-00:30'),
+                          time(12, 44, 01, 37000, TZStub(0,-30)))
+
+    def test_conv_timestamp(self):
+        from psycopgda.adapter import _conv_timestamp
+        from datetime import datetime
+        self.assertEquals(_conv_timestamp(''), None)
+        self.assertEquals(_conv_timestamp('2001-03-02 12:44:01'),
+                          datetime(2001, 3, 2, 12, 44, 01))
+        self.assertEquals(_conv_timestamp('2001-03-02 12:44:01.037'),
+                          datetime(2001, 3, 2, 12, 44, 01, 37000))
+        self.assertEquals(_conv_timestamp('2001-03-02 12:44:01.000001'),
+                          datetime(2001, 3, 2, 12, 44, 01, 1))
+
+    def test_conv_timestamptz(self):
+        from psycopgda.adapter import _conv_timestamptz as c
+        from datetime import datetime
+        self.assertEquals(c(''), None)
+
+        self.assertEquals(c('2001-03-02 12:44:01+01:00'),
+                  datetime(2001, 3, 2, 12, 44, 01, 0, TZStub(1,0)))
+        self.assertEquals(c('2001-03-02 12:44:01.037-00:30'),
+                  datetime(2001, 3, 2, 12, 44, 01, 37000, TZStub(0,-30)))
+        self.assertEquals(c('2001-03-02 12:44:01.000001+12:00'),
+                  datetime(2001, 3, 2, 12, 44, 01, 1, TZStub(12,0)))
+        self.assertEquals(c('2001-06-25 12:14:00-07'),
+                  datetime(2001, 6, 25, 12, 14, 00, 0, TZStub(-7,0)))
+
+    def test_conv_interval(self):
+        from psycopgda.adapter import _conv_interval as c
+        from datetime import timedelta
+        self.assertEquals(c(''), None)
+        self.assertEquals(c('01:00'), timedelta(hours=1))
+        self.assertEquals(c('00:15'), timedelta(minutes=15))
+        self.assertEquals(c('00:00:47'), timedelta(seconds=47))
+        self.assertEquals(c('00:00:00.037'), timedelta(microseconds=37000))
+        self.assertEquals(c('00:00:00.111111'), timedelta(microseconds=111111))
+        self.assertEquals(c('1 day'), timedelta(days=1))
+        self.assertEquals(c('2 days'), timedelta(days=2))
+        self.assertEquals(c('374 days'), timedelta(days=374))
+        self.assertEquals(c('2 days 03:20:15.123456'),
+                          timedelta(days=2, hours=3, minutes=20,
+                                    seconds=15, microseconds=123456))
+        # XXX There's a problem with years and months.  Currently timedelta
+        # cannot represent them accurately
+        self.assertEquals(c('1 month'), '1 month')
+        self.assertEquals(c('2 months'), '2 months')
+        self.assertEquals(c('1 mon'), '1 mon')
+        self.assertEquals(c('2 mons'), '2 mons')
+        self.assertEquals(c('1 year'), '1 year')
+        self.assertEquals(c('3 years'), '3 years')
+        # Later we might be able to use
+        ## self.assertEquals(c('1 month'), timedelta(months=1))
+        ## self.assertEquals(c('2 months'), timedelta(months=2))
+        ## self.assertEquals(c('1 year'), timedelta(years=1))
+        ## self.assertEquals(c('3 years'), timedelta(years=3))
+
+        self.assertRaises(ValueError, c, '2 day')
+        self.assertRaises(ValueError, c, '2days')
+        self.assertRaises(ValueError, c, '123')
+
+    def test_conv_string(self):
+        from psycopgda.adapter import _get_string_conv
+        _conv_string = _get_string_conv("utf-8")
+        self.assertEquals(_conv_string(None), None)
+        self.assertEquals(_conv_string(''), u'')
+        self.assertEquals(_conv_string('c'), u'c')
+        self.assertEquals(_conv_string('\xc3\x82\xc2\xa2'), u'\xc2\xa2')
+        self.assertEquals(_conv_string('c\xc3\x82\xc2\xa2'), u'c\xc2\xa2')
+
+class TestPsycopgAdapter(TestCase):
+
+    def setUp(self):
+        import psycopgda.adapter as adapter
+        self.real_psycopg = adapter.psycopg
+        adapter.psycopg = self.psycopg_stub = PsycopgStub()
+
+    def tearDown(self):
+        import psycopgda.adapter as adapter
+        adapter.psycopg = self.real_psycopg
+
+    def test_connection_factory(self):
+        from psycopgda.adapter import PsycopgAdapter
+        a = PsycopgAdapter('dbi://username:password@hostname:port/dbname;junk=ignored')
+        c = a._connection_factory()
+        args = self.psycopg_stub.last_connection_string.split()
+        args.sort()
+        self.assertEquals(args, ['dbname=dbname', 'host=hostname',
+                                 'password=password', 'port=port',
+                                 'user=username'])
+
+    def test_registerTypes(self):
+        import psycopgda.adapter as adapter
+        from psycopgda.adapter import PsycopgAdapter
+        a = PsycopgAdapter('dbi://')
+        a.registerTypes()
+        for typename in ('DATE', 'TIME', 'TIMETZ', 'TIMESTAMP',
+                         'TIMESTAMPTZ', 'INTERVAL'):
+            typeid = getattr(adapter, '%s_OID' % typename)
+            result = self.psycopg_stub.types.get(typeid, None)
+            if not result:
+                # comparing None with psycopg.type object segfaults
+                self.fail("did not register %s (%d): got None, not Z%s"
+                          % (typename, typeid, typename))
+            else:
+                result_name = getattr(result, 'name', 'None')
+                self.assertEquals(result, getattr(adapter, typename),
+                              "did not register %s (%d): got %s, not Z%s"
+                              % (typename, typeid, result_name, typename))
+
+
+class TestISODateTime(TestCase):
+
+    # Test if date/time parsing functions accept a sensible subset of ISO-8601
+    # compliant date/time strings.
+    #
+    # Resources:
+    #   http://www.cl.cam.ac.uk/~mgk25/iso-time.html
+    #   http://www.mcs.vuw.ac.nz/technical/software/SGML/doc/iso8601/ISO8601.html
+    #   http://www.w3.org/TR/NOTE-datetime
+    #   http://www.ietf.org/rfc/rfc3339.txt
+
+    basic_dates     = (('20020304',   (2002, 03, 04)),
+                       ('20000229',   (2000, 02, 29)))
+
+    extended_dates  = (('2002-03-04', (2002, 03, 04)),
+                       ('2000-02-29', (2000, 02, 29)))
+
+    basic_times     = (('12',         (12, 0, 0)),
+                       ('1234',       (12, 34, 0)),
+                       ('123417',     (12, 34, 17)),
+                       ('123417.5',   (12, 34, 17.5)),
+                       ('123417,5',   (12, 34, 17.5)))
+
+    extended_times  = (('12',         (12, 0, 0)),
+                       ('12:34',      (12, 34, 0)),
+                       ('12:34:17',   (12, 34, 17)),
+                       ('12:34:17.5', (12, 34, 17.5)),
+                       ('12:34:17,5', (12, 34, 17.5)))
+
+    basic_tzs       = (('Z', 0),
+                       ('+02', 2*60),
+                       ('+1130', 11*60+30),
+                       ('-05', -5*60),
+                       ('-0030', -30))
+
+    extended_tzs    = (('Z', 0),
+                       ('+02', 2*60),
+                       ('+11:30', 11*60+30),
+                       ('-05', -5*60),
+                       ('-00:30', -30))
+
+    time_separators = (' ', 'T')
+
+    bad_dates       = ('', 'foo', 'XXXXXXXX', 'XXXX-XX-XX', '2001-2-29',
+                       '1990/13/14')
+
+    bad_times       = ('', 'foo', 'XXXXXX', '12:34,5', '12:34:56,')
+
+    bad_timetzs     = ('12+12 34', '15:45 +1234', '18:00-12:34:56', '18:00+123', '18:00Q')
+
+    bad_datetimes   = ('', 'foo', '2002-03-0412:33')
+
+    bad_datetimetzs = ('', 'foo', '2002-03-04T12:33 +1200')
+
+    exception_type  = ValueError
+
+    # We need the following funcions:
+    #   parse_date       -> (year, month, day)
+    #   parse_time       -> (hour, minute, second)
+    #   parse_timetz     -> (hour, minute, second, tzoffset)
+    #   parse_datetime   -> (year, month, day, hour, minute, second)
+    #   parse_datetimetz -> (year, month, day, hour, minute, second, tzoffset)
+    # second can be a float, all other values are ints
+    # tzoffset is offset in minutes east of UTC
+
+    def setUp(self):
+        from psycopgda.adapter import parse_date, parse_time, \
+                                parse_timetz, parse_datetime, parse_datetimetz
+        self.parse_date = parse_date
+        self.parse_time = parse_time
+        self.parse_timetz = parse_timetz
+        self.parse_datetime = parse_datetime
+        self.parse_datetimetz = parse_datetimetz
+
+    def test_basic_date(self):
+        for s, d in self.basic_dates:
+            self.assertEqual(self.parse_date(s), d)
+
+    def test_extended_date(self):
+        for s, d in self.extended_dates:
+            self.assertEqual(self.parse_date(s), d)
+
+    def test_bad_date(self):
+        for s in self.bad_dates:
+            self.assertRaises(self.exception_type, self.parse_date, s)
+
+    def test_basic_time(self):
+        for s, t in self.basic_times:
+            self.assertEqual(self.parse_time(s), t)
+
+    def test_extended_time(self):
+        for s, t in self.extended_times:
+            self.assertEqual(self.parse_time(s), t)
+
+    def test_bad_time(self):
+        for s in self.bad_times:
+            self.assertRaises(self.exception_type, self.parse_time, s)
+
+    def test_basic_timetz(self):
+        for s, t in self.basic_times:
+            for tz, off in self.basic_tzs:
+                self.assertEqual(self.parse_timetz(s+tz), t + (off,))
+
+    def test_extended_timetz(self):
+        for s, t in self.extended_times:
+            for tz, off in self.extended_tzs:
+                self.assertEqual(self.parse_timetz(s+tz), t + (off,))
+
+    def test_bad_timetzs(self):
+        for s in self.bad_timetzs:
+            self.assertRaises(self.exception_type, self.parse_timetz, s)
+
+    def test_basic_datetime(self):
+        for ds, d in self.basic_dates:
+            for ts, t in self.basic_times:
+                for sep in self.time_separators:
+                    self.assertEqual(self.parse_datetime(ds+sep+ts), d + t)
+
+    def test_extended_datetime(self):
+        for ds, d in self.extended_dates:
+            for ts, t in self.extended_times:
+                for sep in self.time_separators:
+                    self.assertEqual(self.parse_datetime(ds+sep+ts), d + t)
+
+    def test_bad_datetimes(self):
+        for s in self.bad_datetimes:
+            self.assertRaises(self.exception_type, self.parse_datetime, s)
+
+    def test_basic_datetimetz(self):
+        for ds, d in self.basic_dates:
+            for ts, t in self.basic_times:
+                for tz, off in self.basic_tzs:
+                    for sep in self.time_separators:
+                        self.assertEqual(self.parse_datetimetz(ds+sep+ts+tz),
+                                         d + t + (off,))
+
+    def test_extended_datetimetz(self):
+        for ds, d in self.extended_dates:
+            for ts, t in self.extended_times:
+                for tz, off in self.extended_tzs:
+                    for sep in self.time_separators:
+                        self.assertEqual(self.parse_datetimetz(ds+sep+ts+tz),
+                                         d + t + (off,))
+
+    def test_bad_datetimetzs(self):
+        for s in self.bad_datetimetzs:
+            self.assertRaises(self.exception_type, self.parse_datetimetz, s)
+
+
+def test_suite():
+    return TestSuite((
+        makeSuite(TestPsycopgTypeConversion),
+        makeSuite(TestPsycopgAdapter),
+        makeSuite(TestISODateTime),
+        ))
+
+if __name__=='__main__':
+    main(defaultTest='test_suite')

Added: psycopgda/trunk/setup.py
===================================================================
--- psycopgda/trunk/setup.py	                        (rev 0)
+++ psycopgda/trunk/setup.py	2007-10-11 17:44:36 UTC (rev 80852)
@@ -0,0 +1,22 @@
+"""Psycopg Database Adapter for Zope 3
+
+This package allows Zope 3 to connect to any PostGreSQL database via
+the common Zope 3 RDB connection facilities.
+"""
+
+from setuptools import setup, find_packages
+
+# We're using the module docstring as the distutils descriptions.
+doclines = __doc__.split("\n")
+VERSION = open('version.txt', 'rb').read().strip()
+
+setup(name="psycopgda",
+      version=VERSION,
+      author="Zope Corporation and Contributors",
+      author_email="zope3-dev at zope.org",
+      license = "ZPL 2.1",
+      platforms = ["any"],
+      description = doclines[0],
+      long_description = "\n".join(doclines[2:]),
+      packages = find_packages(),
+      )

Deleted: psycopgda/trunk/tests.py
===================================================================
--- psycopgda/trunk/tests.py	2007-10-11 15:33:54 UTC (rev 80851)
+++ psycopgda/trunk/tests.py	2007-10-11 17:44:36 UTC (rev 80852)
@@ -1,368 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002-2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Unit tests for PsycopgDA.
-
-$Id$
-"""
-from unittest import TestCase, TestSuite, main, makeSuite
-from datetime import tzinfo, timedelta
-import psycopg
-
-class Stub(object):
-
-    def __init__(self, **kw):
-        self.__dict__.update(kw)
-
-class TZStub(tzinfo):
-
-    def __init__(self, h, m):
-        self.offset = h * 60 + m
-
-    def utcoffset(self, dt):
-        return timedelta(minutes=self.offset)
-
-    def dst(self, dt):
-        return 0
-
-    def tzname(self, dt):
-        return ''
-
-    def __repr__(self):
-        return 'tzinfo(%d)' % self.offset
-
-    def __reduce__(self):
-        return type(self), (), self.__dict__
-
-class ConnectionStub(object):
-
-    def set_isolation_level(self, level):
-        pass
-
-class PsycopgStub(object):
-
-    __shared_state = {}     # 'Borg' design pattern
-
-    DATE = psycopg.DATE
-    TIME = psycopg.TIME
-    DATETIME = psycopg.DATETIME
-    INTERVAL = psycopg.INTERVAL
-
-    def __init__(self):
-        self.__dict__ = self.__shared_state
-        self.types = {}
-
-    def connect(self, connection_string):
-        self.last_connection_string = connection_string
-        return ConnectionStub()
-
-    def new_type(self, values, name, converter):
-        return Stub(name=name, values=values)
-
-    def register_type(self, type):
-        for typeid in type.values:
-            self.types[typeid] = type
-
-
-class TestPsycopgTypeConversion(TestCase):
-
-    def test_conv_date(self):
-        from psycopgda.adapter import _conv_date
-        from datetime import date
-        self.assertEquals(_conv_date(''), None)
-        self.assertEquals(_conv_date('2001-03-02'), date(2001, 3, 2))
-
-    def test_conv_time(self):
-        from psycopgda.adapter import _conv_time
-        from datetime import time
-        self.assertEquals(_conv_time(''), None)
-        self.assertEquals(_conv_time('23:17:57'),
-                          time(23, 17, 57))
-        self.assertEquals(_conv_time('23:17:57.037'),
-                          time(23, 17, 57, 37000))
-
-    def test_conv_timetz(self):
-        from psycopgda.adapter import _conv_timetz
-        from datetime import time
-        self.assertEquals(_conv_timetz(''), None)
-        self.assertEquals(_conv_timetz('12:44:01+01:00'),
-                          time(12, 44, 01, 0, TZStub(1,0)))
-        self.assertEquals(_conv_timetz('12:44:01.037-00:30'),
-                          time(12, 44, 01, 37000, TZStub(0,-30)))
-
-    def test_conv_timestamp(self):
-        from psycopgda.adapter import _conv_timestamp
-        from datetime import datetime
-        self.assertEquals(_conv_timestamp(''), None)
-        self.assertEquals(_conv_timestamp('2001-03-02 12:44:01'),
-                          datetime(2001, 3, 2, 12, 44, 01))
-        self.assertEquals(_conv_timestamp('2001-03-02 12:44:01.037'),
-                          datetime(2001, 3, 2, 12, 44, 01, 37000))
-        self.assertEquals(_conv_timestamp('2001-03-02 12:44:01.000001'),
-                          datetime(2001, 3, 2, 12, 44, 01, 1))
-
-    def test_conv_timestamptz(self):
-        from psycopgda.adapter import _conv_timestamptz as c
-        from datetime import datetime
-        self.assertEquals(c(''), None)
-
-        self.assertEquals(c('2001-03-02 12:44:01+01:00'),
-                  datetime(2001, 3, 2, 12, 44, 01, 0, TZStub(1,0)))
-        self.assertEquals(c('2001-03-02 12:44:01.037-00:30'),
-                  datetime(2001, 3, 2, 12, 44, 01, 37000, TZStub(0,-30)))
-        self.assertEquals(c('2001-03-02 12:44:01.000001+12:00'),
-                  datetime(2001, 3, 2, 12, 44, 01, 1, TZStub(12,0)))
-        self.assertEquals(c('2001-06-25 12:14:00-07'),
-                  datetime(2001, 6, 25, 12, 14, 00, 0, TZStub(-7,0)))
-
-    def test_conv_interval(self):
-        from psycopgda.adapter import _conv_interval as c
-        from datetime import timedelta
-        self.assertEquals(c(''), None)
-        self.assertEquals(c('01:00'), timedelta(hours=1))
-        self.assertEquals(c('00:15'), timedelta(minutes=15))
-        self.assertEquals(c('00:00:47'), timedelta(seconds=47))
-        self.assertEquals(c('00:00:00.037'), timedelta(microseconds=37000))
-        self.assertEquals(c('00:00:00.111111'), timedelta(microseconds=111111))
-        self.assertEquals(c('1 day'), timedelta(days=1))
-        self.assertEquals(c('2 days'), timedelta(days=2))
-        self.assertEquals(c('374 days'), timedelta(days=374))
-        self.assertEquals(c('2 days 03:20:15.123456'),
-                          timedelta(days=2, hours=3, minutes=20,
-                                    seconds=15, microseconds=123456))
-        # XXX There's a problem with years and months.  Currently timedelta
-        # cannot represent them accurately
-        self.assertEquals(c('1 month'), '1 month')
-        self.assertEquals(c('2 months'), '2 months')
-        self.assertEquals(c('1 mon'), '1 mon')
-        self.assertEquals(c('2 mons'), '2 mons')
-        self.assertEquals(c('1 year'), '1 year')
-        self.assertEquals(c('3 years'), '3 years')
-        # Later we might be able to use
-        ## self.assertEquals(c('1 month'), timedelta(months=1))
-        ## self.assertEquals(c('2 months'), timedelta(months=2))
-        ## self.assertEquals(c('1 year'), timedelta(years=1))
-        ## self.assertEquals(c('3 years'), timedelta(years=3))
-
-        self.assertRaises(ValueError, c, '2 day')
-        self.assertRaises(ValueError, c, '2days')
-        self.assertRaises(ValueError, c, '123')
-
-    def test_conv_string(self):
-        from psycopgda.adapter import _get_string_conv
-        _conv_string = _get_string_conv("utf-8")
-        self.assertEquals(_conv_string(None), None)
-        self.assertEquals(_conv_string(''), u'')
-        self.assertEquals(_conv_string('c'), u'c')
-        self.assertEquals(_conv_string('\xc3\x82\xc2\xa2'), u'\xc2\xa2')
-        self.assertEquals(_conv_string('c\xc3\x82\xc2\xa2'), u'c\xc2\xa2')
-
-class TestPsycopgAdapter(TestCase):
-
-    def setUp(self):
-        import psycopgda.adapter as adapter
-        self.real_psycopg = adapter.psycopg
-        adapter.psycopg = self.psycopg_stub = PsycopgStub()
-
-    def tearDown(self):
-        import psycopgda.adapter as adapter
-        adapter.psycopg = self.real_psycopg
-
-    def test_connection_factory(self):
-        from psycopgda.adapter import PsycopgAdapter
-        a = PsycopgAdapter('dbi://username:password@hostname:port/dbname;junk=ignored')
-        c = a._connection_factory()
-        args = self.psycopg_stub.last_connection_string.split()
-        args.sort()
-        self.assertEquals(args, ['dbname=dbname', 'host=hostname',
-                                 'password=password', 'port=port',
-                                 'user=username'])
-
-    def test_registerTypes(self):
-        import psycopgda.adapter as adapter
-        from psycopgda.adapter import PsycopgAdapter
-        a = PsycopgAdapter('dbi://')
-        a.registerTypes()
-        for typename in ('DATE', 'TIME', 'TIMETZ', 'TIMESTAMP',
-                         'TIMESTAMPTZ', 'INTERVAL'):
-            typeid = getattr(adapter, '%s_OID' % typename)
-            result = self.psycopg_stub.types.get(typeid, None)
-            if not result:
-                # comparing None with psycopg.type object segfaults
-                self.fail("did not register %s (%d): got None, not Z%s"
-                          % (typename, typeid, typename))
-            else:
-                result_name = getattr(result, 'name', 'None')
-                self.assertEquals(result, getattr(adapter, typename),
-                              "did not register %s (%d): got %s, not Z%s"
-                              % (typename, typeid, result_name, typename))
-
-
-class TestISODateTime(TestCase):
-
-    # Test if date/time parsing functions accept a sensible subset of ISO-8601
-    # compliant date/time strings.
-    #
-    # Resources:
-    #   http://www.cl.cam.ac.uk/~mgk25/iso-time.html
-    #   http://www.mcs.vuw.ac.nz/technical/software/SGML/doc/iso8601/ISO8601.html
-    #   http://www.w3.org/TR/NOTE-datetime
-    #   http://www.ietf.org/rfc/rfc3339.txt
-
-    basic_dates     = (('20020304',   (2002, 03, 04)),
-                       ('20000229',   (2000, 02, 29)))
-
-    extended_dates  = (('2002-03-04', (2002, 03, 04)),
-                       ('2000-02-29', (2000, 02, 29)))
-
-    basic_times     = (('12',         (12, 0, 0)),
-                       ('1234',       (12, 34, 0)),
-                       ('123417',     (12, 34, 17)),
-                       ('123417.5',   (12, 34, 17.5)),
-                       ('123417,5',   (12, 34, 17.5)))
-
-    extended_times  = (('12',         (12, 0, 0)),
-                       ('12:34',      (12, 34, 0)),
-                       ('12:34:17',   (12, 34, 17)),
-                       ('12:34:17.5', (12, 34, 17.5)),
-                       ('12:34:17,5', (12, 34, 17.5)))
-
-    basic_tzs       = (('Z', 0),
-                       ('+02', 2*60),
-                       ('+1130', 11*60+30),
-                       ('-05', -5*60),
-                       ('-0030', -30))
-
-    extended_tzs    = (('Z', 0),
-                       ('+02', 2*60),
-                       ('+11:30', 11*60+30),
-                       ('-05', -5*60),
-                       ('-00:30', -30))
-
-    time_separators = (' ', 'T')
-
-    bad_dates       = ('', 'foo', 'XXXXXXXX', 'XXXX-XX-XX', '2001-2-29',
-                       '1990/13/14')
-
-    bad_times       = ('', 'foo', 'XXXXXX', '12:34,5', '12:34:56,')
-
-    bad_timetzs     = ('12+12 34', '15:45 +1234', '18:00-12:34:56', '18:00+123', '18:00Q')
-
-    bad_datetimes   = ('', 'foo', '2002-03-0412:33')
-
-    bad_datetimetzs = ('', 'foo', '2002-03-04T12:33 +1200')
-
-    exception_type  = ValueError
-
-    # We need the following funcions:
-    #   parse_date       -> (year, month, day)
-    #   parse_time       -> (hour, minute, second)
-    #   parse_timetz     -> (hour, minute, second, tzoffset)
-    #   parse_datetime   -> (year, month, day, hour, minute, second)
-    #   parse_datetimetz -> (year, month, day, hour, minute, second, tzoffset)
-    # second can be a float, all other values are ints
-    # tzoffset is offset in minutes east of UTC
-
-    def setUp(self):
-        from psycopgda.adapter import parse_date, parse_time, \
-                                parse_timetz, parse_datetime, parse_datetimetz
-        self.parse_date = parse_date
-        self.parse_time = parse_time
-        self.parse_timetz = parse_timetz
-        self.parse_datetime = parse_datetime
-        self.parse_datetimetz = parse_datetimetz
-
-    def test_basic_date(self):
-        for s, d in self.basic_dates:
-            self.assertEqual(self.parse_date(s), d)
-
-    def test_extended_date(self):
-        for s, d in self.extended_dates:
-            self.assertEqual(self.parse_date(s), d)
-
-    def test_bad_date(self):
-        for s in self.bad_dates:
-            self.assertRaises(self.exception_type, self.parse_date, s)
-
-    def test_basic_time(self):
-        for s, t in self.basic_times:
-            self.assertEqual(self.parse_time(s), t)
-
-    def test_extended_time(self):
-        for s, t in self.extended_times:
-            self.assertEqual(self.parse_time(s), t)
-
-    def test_bad_time(self):
-        for s in self.bad_times:
-            self.assertRaises(self.exception_type, self.parse_time, s)
-
-    def test_basic_timetz(self):
-        for s, t in self.basic_times:
-            for tz, off in self.basic_tzs:
-                self.assertEqual(self.parse_timetz(s+tz), t + (off,))
-
-    def test_extended_timetz(self):
-        for s, t in self.extended_times:
-            for tz, off in self.extended_tzs:
-                self.assertEqual(self.parse_timetz(s+tz), t + (off,))
-
-    def test_bad_timetzs(self):
-        for s in self.bad_timetzs:
-            self.assertRaises(self.exception_type, self.parse_timetz, s)
-
-    def test_basic_datetime(self):
-        for ds, d in self.basic_dates:
-            for ts, t in self.basic_times:
-                for sep in self.time_separators:
-                    self.assertEqual(self.parse_datetime(ds+sep+ts), d + t)
-
-    def test_extended_datetime(self):
-        for ds, d in self.extended_dates:
-            for ts, t in self.extended_times:
-                for sep in self.time_separators:
-                    self.assertEqual(self.parse_datetime(ds+sep+ts), d + t)
-
-    def test_bad_datetimes(self):
-        for s in self.bad_datetimes:
-            self.assertRaises(self.exception_type, self.parse_datetime, s)
-
-    def test_basic_datetimetz(self):
-        for ds, d in self.basic_dates:
-            for ts, t in self.basic_times:
-                for tz, off in self.basic_tzs:
-                    for sep in self.time_separators:
-                        self.assertEqual(self.parse_datetimetz(ds+sep+ts+tz),
-                                         d + t + (off,))
-
-    def test_extended_datetimetz(self):
-        for ds, d in self.extended_dates:
-            for ts, t in self.extended_times:
-                for tz, off in self.extended_tzs:
-                    for sep in self.time_separators:
-                        self.assertEqual(self.parse_datetimetz(ds+sep+ts+tz),
-                                         d + t + (off,))
-
-    def test_bad_datetimetzs(self):
-        for s in self.bad_datetimetzs:
-            self.assertRaises(self.exception_type, self.parse_datetimetz, s)
-
-
-def test_suite():
-    return TestSuite((
-        makeSuite(TestPsycopgTypeConversion),
-        makeSuite(TestPsycopgAdapter),
-        makeSuite(TestISODateTime),
-        ))
-
-if __name__=='__main__':
-    main(defaultTest='test_suite')

Added: psycopgda/trunk/version.txt
===================================================================
--- psycopgda/trunk/version.txt	                        (rev 0)
+++ psycopgda/trunk/version.txt	2007-10-11 17:44:36 UTC (rev 80852)
@@ -0,0 +1 @@
+1.0



More information about the Checkins mailing list