From 1af8b57706d19c108a844966d6ff6b9a01158447 Mon Sep 17 00:00:00 2001 From: Federico Di Gregorio Date: Thu, 26 May 2005 07:34:27 +0000 Subject: [PATCH] ZPsycopgDA work. --- ChangeLog | 11 ++ INSTALL | 8 +- ZPsycopgDA/DA.py | 208 ++++++++++++++++++++++++++++---- ZPsycopgDA/DABase.py | 67 ---------- ZPsycopgDA/__init__.py | 10 +- ZPsycopgDA/db.py | 81 ++++++------- ZPsycopgDA/dtml/browse.dtml | 11 ++ ZPsycopgDA/dtml/edit.dtml | 4 +- ZPsycopgDA/dtml/table_info.dtml | 7 ++ psycopg/adapter_list.c | 4 - psycopg/psycopgmodule.c | 5 +- setup.cfg | 2 +- 12 files changed, 268 insertions(+), 150 deletions(-) delete mode 100644 ZPsycopgDA/DABase.py create mode 100644 ZPsycopgDA/dtml/browse.dtml create mode 100644 ZPsycopgDA/dtml/table_info.dtml diff --git a/ChangeLog b/ChangeLog index 73147536..d8ccfb68 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2005-05-26 Federico Di Gregorio + + * 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 * Release 2.0b2. diff --git a/INSTALL b/INSTALL index dd33fb53..512bd18a 100644 --- a/INSTALL +++ b/INSTALL @@ -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 diff --git a/ZPsycopgDA/DA.py b/ZPsycopgDA/DA.py index 2ebb8e20..e61b3b14 100644 --- a/ZPsycopgDA/DA.py +++ b/ZPsycopgDA/DA.py @@ -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("'" % + (n, vartype(t))) + a='%s%s' % (n, boboType(t)) + if d: a="%s=%s" % (a,d) + args.append(a) + elif s=='Property': + values.append("'" % + (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 %s' % n) + +class ColumnBrowser(Browser): + icon='field' + + def check(self): + return ('\t' % + (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' + } diff --git a/ZPsycopgDA/DABase.py b/ZPsycopgDA/DABase.py deleted file mode 100644 index 03102c34..00000000 --- a/ZPsycopgDA/DABase.py +++ /dev/null @@ -1,67 +0,0 @@ -# ZPsycopgDA/DABase.py - ZPsycopgDA Zope product: Database inspection -# -# Copyright (C) 2004 Federico Di Gregorio -# -# 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""" diff --git a/ZPsycopgDA/__init__.py b/ZPsycopgDA/__init__.py index b0e2a45e..f9244ff2 100644 --- a/ZPsycopgDA/__init__.py +++ b/ZPsycopgDA/__init__.py @@ -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') diff --git a/ZPsycopgDA/db.py b/ZPsycopgDA/db.py index de2955d9..738747d7 100644 --- a/ZPsycopgDA/db.py +++ b/ZPsycopgDA/db.py @@ -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 diff --git a/ZPsycopgDA/dtml/browse.dtml b/ZPsycopgDA/dtml/browse.dtml new file mode 100644 index 00000000..deffd0ab --- /dev/null +++ b/ZPsycopgDA/dtml/browse.dtml @@ -0,0 +1,11 @@ + + <dtml-var title_or_id >tables + + + + <dtml-var Type> + + + + diff --git a/ZPsycopgDA/dtml/edit.dtml b/ZPsycopgDA/dtml/edit.dtml index 45275edd..7cb371fe 100644 --- a/ZPsycopgDA/dtml/edit.dtml +++ b/ZPsycopgDA/dtml/edit.dtml @@ -45,10 +45,10 @@ diff --git a/ZPsycopgDA/dtml/table_info.dtml b/ZPsycopgDA/dtml/table_info.dtml new file mode 100644 index 00000000..639c23fd --- /dev/null +++ b/ZPsycopgDA/dtml/table_info.dtml @@ -0,0 +1,7 @@ + + + + owned by +
+ + diff --git a/psycopg/adapter_list.c b/psycopg/adapter_list.c index 88db602b..5d803561 100644 --- a/psycopg/adapter_list.c +++ b/psycopg/adapter_list.c @@ -22,10 +22,6 @@ #include #include #include -#include - -#include -#include #define PSYCOPG_MODULE #include "psycopg/config.h" diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index 89d64a45..fd5c5d63 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -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"); } diff --git a/setup.cfg b/setup.cfg index 76b73d5a..6d6f48e9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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