ZPsycopgDA work.

This commit is contained in:
Federico Di Gregorio 2005-05-26 07:34:27 +00:00
parent a9ecbd0985
commit 1af8b57706
12 changed files with 268 additions and 150 deletions

View File

@ -1,3 +1,14 @@
2005-05-26 Federico Di Gregorio <fog@debian.org>
* ZPsycopgDA/db.py (DB.convert_description): isolated description
conversion (and fixed the conversion as per #18).
* ZPsycopgDA/DA.py: fixed again; this time Zope should work for
real. :/ Also fixed the type-casters (psycopg 2 added the extra
cursor parameter) as reported in #18.
* psycopg/psycopgmodule.c (init_psycopg): fixed Python 2.2 build.
2005-05-19 Federico Di Gregorio <fog@debian.org>
* Release 2.0b2.

View File

@ -4,9 +4,8 @@ Compiling and installing psycopg
While psycopg 1.x used autoconf for its build process psycopg 2 switched to
the more pythoning setup.py. Currently both psycopg's author and distutils
have some limitations so the file setup.cfg is almost unused and most build
options are hidden in setup.py. Before building psycopg look at the very
first lines of setup.py and change any settings to follow your system (or
taste); then:
options are hidden in setup.py. Before building psycopg look at setup.cfg file
and change any settings to follow your system (or taste); then:
python setup.py build
@ -42,7 +41,8 @@ Steps required to package your own version of psycopg:
1.1. create the file libpqdll.a (you should already know how to build
Python extensions with mingw: see
http://starship.python.net/crew/kernr/mingw32/Notes.html for details):
http://starship.python.net/crew/kernr/mingw32/Notes.html for
details):
1.1.1. cd C:\Program Files\PostgreSQL\8.0\lib

View File

@ -18,25 +18,28 @@
# See the LICENSE file for details.
ALLOWED_PSYCOPG_VERSIONS = ('2.0b2')
ALLOWED_PSYCOPG_VERSIONS = ('2.0b2', '2.0b3')
import sys
import time
import db
import DABase
import Acquisition
import Shared.DC.ZRDB.Connection
from db import DB
from Globals import DTMLFile
from Globals import HTMLFile
from ImageFile import ImageFile
from ExtensionClass import Base
from App.Dialogs import MessageDialog
from DateTime import DateTime
# import psycopg and functions/singletons needed for date/time conversions
import psycopg
from psycopg import DATETIME
from psycopg.extensions import TIME, DATE, INTERVAL
from psycopg import NUMBER, STRING, ROWID, DATETIME
from psycopg.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE
from psycopg.extensions import TIME, INTERVAL
from psycopg.extensions import new_type, register_type
@ -57,12 +60,14 @@ def manage_addZPsycopgConnection(self, id, title, connection_string,
# the connection object
class Connection(DABase.Connection):
class Connection(Shared.DC.ZRDB.Connection.Connection):
"""ZPsycopg Connection."""
id = 'Psycopg_database_connection'
_isAnSQLConnection = 1
id = 'Psycopg_database_connection'
database_type = 'Psycopg'
meta_type = title = 'Z Psycopg Database Connection'
icon = 'misc_/ZPsycopg/conn'
icon = 'misc_/ZPsycopgDA/conn'
def __init__(self, id, title, connection_string,
zdatetime, check=None, tilevel=2, encoding=''):
@ -74,9 +79,8 @@ class Connection(DABase.Connection):
def factory(self):
return DB
def table_info(self):
return self._v_database_connection.table_info()
## connection parameters editing ##
def edit(self, title, connection_string,
zdatetime, check=None, tilevel=2, encoding=''):
self.title = title
@ -108,8 +112,8 @@ class Connection(DABase.Connection):
pass
# check psycopg version and raise exception if does not match
if psycopg.__version__ not in ALLOWED_PSYCOPG_VERSIONS:
raise ImportError("psycopg version mismatch (imported %s)" %s
if psycopg.__version__[:5] not in ALLOWED_PSYCOPG_VERSIONS:
raise ImportError("psycopg version mismatch (imported %s)" %
psycopg.__version__)
self.set_type_casts()
@ -138,8 +142,48 @@ class Connection(DABase.Connection):
register_type(DATE)
register_type(TIME)
register_type(INTERVAL)
## browsing and table/column management ##
manage_options = Shared.DC.ZRDB.Connection.Connection.manage_options + (
{'label': 'Browse', 'action':'manage_browse'},)
manage_tables = DTMLFile('dtml/tables', globals())
manage_browse = DTMLFile('dtml/browse', globals())
info = None
# database connection registration data
def table_info(self):
return self._v_database_connection.table_info()
def __getitem__(self, name):
if name == 'tableNamed':
if not hasattr(self, '_v_tables'): self.tpValues()
return self._v_tables.__of__(self)
raise KeyError, name
def tpValues(self):
res = []
conn = self._v_database_connection
for d in conn.tables(rdb=0):
try:
name = d['TABLE_NAME']
b = TableBrowser()
b.__name__ = name
b._d = d
b._c = c
try:
b.icon = table_icons[d['TABLE_TYPE']]
except:
pass
r.append(b)
except:
pass
return res
## database connection registration data ##
classes = (Connection,)
@ -162,37 +206,38 @@ for icon in ('table', 'view', 'stable', 'what', 'field', 'text', 'bin',
'int', 'float', 'date', 'time', 'datetime'):
misc_[icon] = ImageFile('icons/%s.gif' % icon, globals())
# zope-specific psycopg typecasters
## zope-specific psycopg typecasters ##
# convert an ISO timestamp string from postgres to a Zope DateTime object
def _cast_DateTime(str):
def _cast_DateTime(str, curs):
if str:
# this will split us into [date, time, GMT/AM/PM(if there)]
dt = split(str, ' ')
dt = str.split(' ')
if len(dt) > 1:
# we now should split out any timezone info
dt[1] = split(dt[1], '-')[0]
dt[1] = split(dt[1], '+')[0]
return DateTime(join(dt[:2], ' '))
dt[1] = dt[1].split('-')[0]
dt[1] = dt[1].split('+')[0]
return DateTime(' '.join(dt[:2]))
else:
return DateTime(dt[0])
# convert an ISO date string from postgres to a Zope DateTime object
def _cast_Date(str):
def _cast_Date(str, curs):
if str:
return DateTime(str)
# Convert a time string from postgres to a Zope DateTime object.
# NOTE: we set the day as today before feeding to DateTime so
# that it has the same DST settings.
def _cast_Time(str):
def _cast_Time(str, curs):
if str:
return DateTime(time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(time.time())[:3]+
time.strptime(str[:8], "%H:%M:%S")[3:]))
# TODO: DateTime does not support intervals: what's the best we can do?
def _cast_Interval(str):
def _cast_Interval(str, curs):
return str
ZDATETIME = new_type((1184, 1114), "ZDATETIME", _cast_DateTime)
@ -200,3 +245,120 @@ ZINTERVAL = new_type((1186,), "ZINTERVAL", _cast_Interval)
ZDATE = new_type((1082,), "ZDATE", _cast_Date)
ZTIME = new_type((1083,), "ZTIME", _cast_Time)
## table browsing helpers ##
class TableBrowserCollection(Acquisition.Implicit):
pass
class Browser(Base):
def __getattr__(self, name):
try:
return self._d[name]
except KeyError:
raise AttributeError, name
class values:
def len(self):
return 1
def __getitem__(self, i):
try:
return self._d[i]
except AttributeError:
pass
self._d = self._f()
return self._d[i]
class TableBrowser(Browser, Acquisition.Implicit):
icon = 'what'
Description = check = ''
info = DTMLFile('table_info', globals())
menu = DTMLFile('table_menu', globals())
def tpValues(self):
v = values()
v._f = self.tpValues_
return v
def tpValues_(self):
r=[]
tname=self.__name__
for d in self._c.columns(tname):
b=ColumnBrowser()
b._d=d
try: b.icon=field_icons[d['Type']]
except: pass
b.TABLE_NAME=tname
r.append(b)
return r
def tpId(self): return self._d['TABLE_NAME']
def tpURL(self): return "Table/%s" % self._d['TABLE_NAME']
def Name(self): return self._d['TABLE_NAME']
def Type(self): return self._d['TABLE_TYPE']
manage_designInput=DTMLFile('designInput',globals())
def manage_buildInput(self, id, source, default, REQUEST=None):
"Create a database method for an input form"
args=[]
values=[]
names=[]
columns=self._columns
for i in range(len(source)):
s=source[i]
if s=='Null': continue
c=columns[i]
d=default[i]
t=c['Type']
n=c['Name']
names.append(n)
if s=='Argument':
values.append("<dtml-sqlvar %s type=%s>'" %
(n, vartype(t)))
a='%s%s' % (n, boboType(t))
if d: a="%s=%s" % (a,d)
args.append(a)
elif s=='Property':
values.append("<dtml-sqlvar %s type=%s>'" %
(n, vartype(t)))
else:
if isStringType(t):
if find(d,"\'") >= 0: d=join(split(d,"\'"),"''")
values.append("'%s'" % d)
elif d:
values.append(str(d))
else:
raise ValueError, (
'no default was given for <em>%s</em>' % n)
class ColumnBrowser(Browser):
icon='field'
def check(self):
return ('\t<input type=checkbox name="%s.%s">' %
(self.TABLE_NAME, self._d['Name']))
def tpId(self): return self._d['Name']
def tpURL(self): return "Column/%s" % self._d['Name']
def Description(self):
d=self._d
if d['Scale']:
return " %(Type)s(%(Precision)s,%(Scale)s) %(Nullable)s" % d
else:
return " %(Type)s(%(Precision)s) %(Nullable)s" % d
table_icons={
'TABLE': 'table',
'VIEW':'view',
'SYSTEM_TABLE': 'stable',
}
field_icons={
NUMBER.name: 'i',
STRING.name: 'text',
DATETIME.name: 'date',
INTEGER.name: 'int',
FLOAT.name: 'float',
BOOLEAN.name: 'bin',
ROWID.name: 'int'
}

View File

@ -1,67 +0,0 @@
# ZPsycopgDA/DABase.py - ZPsycopgDA Zope product: Database inspection
#
# Copyright (C) 2004 Federico Di Gregorio <fog@initd.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# Or, at your option this program (ZPsycopgDA) can be distributed under the
# Zope Public License (ZPL) Version 1.0, as published on the Zope web site,
# http://www.zope.org/Resources/ZPL.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.
#
# See the LICENSE file for details.
import sys
import Shared.DC.ZRDB.Connection
from db import DB
from Globals import HTMLFile
from ImageFile import ImageFile
from ExtensionClass import Base
from DateTime import DateTime
# import psycopg and functions/singletons needed for date/time conversions
import psycopg
from psycopg.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN
from psycopg import NUMBER, STRING, ROWID, DATETIME
class Connection(Shared.DC.ZRDB.Connection.Connection):
_isAnSQLConnection = 1
info = None
#manage_options = Shared.DC.ZRDB.Connection.Connection.manage_options + (
# {'label': 'Browse', 'action':'manage_browse'},)
#manage_tables = HTMLFile('tables', globals())
#manage_browse = HTMLFile('browse',globals())
def __getitem__(self, name):
if name == 'tableNamed':
if not hasattr(self, '_v_tables'): self.tpValues()
return self._v_tables.__of__(self)
raise KeyError, name
## old stuff from ZPsycopgDA 1.1 (never implemented) ##
def manage_wizard(self, tables):
"Wizard of what? Oozing?"
def manage_join(self, tables, select_cols, join_cols, REQUEST=None):
"""Create an SQL join"""
def manage_insert(self, table, cols, REQUEST=None):
"""Create an SQL insert"""
def manage_update(self, table, keys, cols, REQUEST=None):
"""Create an SQL update"""

View File

@ -20,8 +20,6 @@
__doc__ = "ZPsycopg Database Adalper Registration."
__version__ = '2.0'
import sys
import string
import DA
methods = DA.folder_methods
@ -30,3 +28,11 @@ meta_types = DA.meta_types
misc_ = DA.misc_
__ac_permissions__=DA.__ac_permissions__
def initialize(context):
context.registerClass(
DA.Connection,
permission = 'Add Z Psycopg Database Connections',
constructors = (DA.manage_addZPsycopgConnectionForm,
DA.manage_addZPsycopgConnection),
icon = SOFTWARE_HOME + '/Shared/DC/ZRDB/www/DBAdapterFolder_icon.gif')

View File

@ -22,12 +22,11 @@ from Shared.DC.ZRDB import dbi_db
from ZODB.POSException import ConflictError
import time
import site
import pool
import psycopg
from psycopg.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN
from psycopg.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE
from psycopg import NUMBER, STRING, ROWID, DATETIME
@ -91,6 +90,38 @@ class DB(TM, dbi_db.DB):
def sortKey(self):
return 1
def convert_description(self, desc, use_psycopg_types=False):
"""Convert DBAPI-2.0 description field to Zope format."""
items = []
for name, typ, width, ds, p, scale, null_ok in desc:
if typ == NUMBER:
if typ == INTEGER or typ == LONGINTEGER:
typs = 'i'
else:
typs = 'n'
typp = NUMBER
elif typ == BOOLEAN:
typs = 'n'
typp = BOOLEAN
elif typ == ROWID:
typs = 'i'
typp = ROWID
elif typ == DATETIME or typ == DATE:
typs = 'd'
typp = DATETIME
else:
typs = 's'
typp = STRING
items.append({
'name': name,
'type': use_psycopg_types and typp or typs,
'width': width,
'precision': p,
'scale': scale,
'null': null_ok,
})
return items
## tables and rows ##
def tables(self, rdb=0, _care=('TABLE', 'VIEW')):
@ -119,30 +150,8 @@ class DB(TM, dbi_db.DB):
r = c.execute('SELECT * FROM "%s" WHERE 1=0' % table_name)
except:
return ()
res = []
for name, type, width, ds, p, scale, null_ok in c.description:
if type == NUMBER:
if type == INTEGER:
type = INTEGER
elif type == FLOAT:
type = FLOAT
else: type = NUMBER
elif type == BOOLEAN:
type = BOOLEAN
elif type == ROWID:
type = ROWID
elif type == DATETIME:
type = DATETIME
else:
type = STRING
res.append({'Name': name,
'Type': type.name,
'Precision': 0,
'Scale': 0,
'Nullable': 0})
self.putconn()
return res
return self.convert_description(c.description, True)
## query execution ##
@ -201,24 +210,4 @@ class DB(TM, dbi_db.DB):
self._abort()
raise err
items = []
for name, typ, width, ds, p, scale, null_ok in desc:
if typ == NUMBER:
if typ == INTEGER or typ == LONGINTEGER: typs = 'i'
else: typs = 'n'
elif typ == BOOLEAN:
typs = 'n'
elif typ == ROWID:
typs = 'i'
elif typ == DATETIME:
typs = 'd'
else:
typs = 's'
items.append({
'name': name,
'type': typs,
'width': width,
'null': null_ok,
})
return items, res
return self.convert_description(desc), res

View File

@ -0,0 +1,11 @@
<html>
<head><title><dtml-var title_or_id >tables</title></head>
<body bgcolor="#FFFFFF" link="#000099" vlink="#555555" alink="#77003B">
<dtml-var manage_tabs>
<dtml-tree header="info">
<IMG SRC="<dtml-var SCRIPT_NAME >/misc_/ZPsycopgDA/<dtml-var icon>"
ALT="<dtml-var Type>" BORDER="0">
<dtml-var Name><dtml-var Description>
</dtml-tree>
</body>
</html>

View File

@ -45,10 +45,10 @@
<td align="left" valign="top">
<select name="tilevel:int">
<option value="1"
<dtml-if expr="tilevel==1">selected="YES"</dtml-if">>
<dtml-if expr="tilevel==1">selected="YES"</dtml-if>>
Read committed</option>
<option value="2"
<dtml-if expr="tilevel==2">selected="YES"</dtml-if">>
<dtml-if expr="tilevel==2">selected="YES"</dtml-if>>
Serializable</option>
</select>
</td>

View File

@ -0,0 +1,7 @@
<dtml-var standard_html_header>
<dtml-var TABLE_TYPE><dtml-if TABLE_OWNER>
owned by <dtml-var TABLE_OWNER></dtml-if>
<dtml-if REMARKS><br><dtml-var REMARKS></dtml-if>
<dtml-var standard_html_footer>

View File

@ -22,10 +22,6 @@
#include <Python.h>
#include <structmember.h>
#include <stringobject.h>
#include <datetime.h>
#include <time.h>
#include <string.h>
#define PSYCOPG_MODULE
#include "psycopg/config.h"

View File

@ -503,12 +503,15 @@ init_psycopg(void)
binaryType.tp_alloc = PyType_GenericAlloc;
isqlquoteType.tp_alloc = PyType_GenericAlloc;
pbooleanType.tp_alloc = PyType_GenericAlloc;
pydatetimeType.tp_alloc = PyType_GenericAlloc;
connectionType.tp_alloc = PyType_GenericAlloc;
asisType.tp_alloc = PyType_GenericAlloc;
qstringType.tp_alloc = PyType_GenericAlloc;
listType.tp_alloc = PyType_GenericAlloc;
chunkType.tp_alloc = PyType_GenericAlloc;
#ifdef HAVE_PYDATETIME
pydatetimeType.tp_alloc = PyType_GenericAlloc;
#endif
Dprintf("initpsycopg: module initialization complete");
}

View File

@ -5,7 +5,7 @@ define=PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3
# PSYCOPG_OWN_QUOTING can be added above but it is deprecated
# set to 1 to use Python datatime objects for date/time representation
use_pydatetime=1
use_pydatetime=0
# include_dirs is the preferred method for locating postgresql headers,
# but some extra checks on sys.platform will still be done in setup.py