[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