_guess_columns patch

Jonathan Ellis <[email protected]> Tue, 17 May 2005 10:49:41 -0600
Newsgroups gmane.comp.web.skunkweb
Message-ID <[email protected]>
Here is a patch that adds a _guess_columns field to PyDO; when True, it reads 
column/unique/seq info from the database.  Only implemented for postgresql 
right now; figured the rest could wait until Jacob signs off on the approach.

Patch is against 2.0a2 since SF.net anon CVS doesn't seem to be working.

-Jonathan

diff -ru PyDO-2.0a2-orig/src/PyDO2/ PyDO-2.0a2/src/PyDO2/ | grep -v "Only in"
diff -ru PyDO-2.0a2-orig/src/PyDO2/base.py PyDO-2.0a2/src/PyDO2/base.py
--- PyDO-2.0a2-orig/src/PyDO2/base.py   2005-05-12 10:48:32.000000000 -0400
+++ PyDO-2.0a2/src/PyDO2/base.py        2005-05-18 00:52:11.271722536 -0400
@@ -46,7 +46,32 @@
      """

      def __init__(cls, cl_name, bases, namespace):
+        if cls._guess_columns:
+            cls._fields, cls._unique = cls.getDBI().describeTable(cls.getTable(
))
+        else:
+            # initializes cls._fields, cls._unique
+            cls._init_fields(bases, namespace)
+
+        # We now have all the inherited declarations; figure out
+        # sequences and additional unique constraints.
+        cls._sequenced={}
+        for f in cls._fields.itervalues():
+            if f.sequence:
+                cls._sequenced[f.name]=f.sequence
+            if f.unique:
+                cls._unique.add(f.name)
+
+        # this doesn't need to be
+        cls._unique=frozenset(cls._unique)
+
+        # add attribute access to fields
+        if cls.use_attributes:
+            for name in cls._fields:
+                if not hasattr(cls, name):
+                    # a field is also a descriptor
+                    setattr(cls, name, cls._fields[name])

+    def _init_fields(cls, bases, namespace):
          # create Field objects declared locally in this class
          # and store them in a temporary dict
          fielddict={}
@@ -74,7 +99,7 @@
          revbases=[x for x in bases[::-1] if x not in (object, dict)]

          cls._fields={}
-        uniqueset=set()
+        cls._unique=set()
          for b in revbases:
              flds=getattr(b, '_fields', None)
              if flds:
@@ -89,7 +114,7 @@
                  uniq=set(_setize(x) for x in uniq)
                  if cls._is_projection:
                      uniq=_restrict(fielddict, uniq)
-                uniqueset.update(uniq)
+                cls._unique.update(uniq)

          # If a field is declared upstream and you redeclare it in a
          # subclass as a simple field (just a fieldname), then the
@@ -98,26 +123,8 @@
          updatefields=((x, y) for x, y in fielddict.iteritems() \
                        if not (x in cls._fields and x in simplefields))
          cls._fields.update(updatefields)
-        uniqueset.update(_setize(x) for x in namespace.get('unique', ()))
-
-        # We now have all the inherited declarations, and figure out
-        # sequences and additional unique constraints.
-        cls._sequenced={}
-        for f in cls._fields.itervalues():
-            if f.sequence:
-                cls._sequenced[f.name]=f.sequence
-            if f.unique:
-                uniqueset.add(f.name)
+        cls._unique.update(_setize(x) for x in namespace.get('unique', ()))

-        # this doesn't need to be
-        cls._unique=frozenset(uniqueset)
-
-        # add attribute access to fields
-        if cls.use_attributes:
-            for name in cls._fields:
-                if not hasattr(cls, name):
-                    # a field is also a descriptor
-                    setattr(cls, name, cls._fields[name])

  class PyDO(dict):
      """ Base class for PyDO data classes."""
@@ -125,6 +132,7 @@
      __metaclass__=_metapydo
      _is_projection=False
      _guess_tablename=True
+    _guess_columns=False
      mutable=True
      use_attributes=True
      connectionAlias=None
@@ -328,6 +336,8 @@
      @classmethod
      def getDBI(cls):
          """return the database interface"""
+        if not cls.connectionAlias:
+            raise KeyError, "class %s must declare a DBI alias" % cls.__name__
          conn=getConnection(cls.connectionAlias)
          return conn

diff -ru PyDO-2.0a2-orig/src/PyDO2/dbi.py PyDO-2.0a2/src/PyDO2/dbi.py
--- PyDO-2.0a2-orig/src/PyDO2/dbi.py    2005-05-05 12:05:04.000000000 -0400
+++ PyDO-2.0a2/src/PyDO2/dbi.py 2005-05-18 01:49:00.521437800 -0400
@@ -171,9 +171,11 @@
          raise NotImplementedError

      def describeTable(self, table):
-        """returns a table description for the given table.
-        The description is a 3-tuple of fields, unique constraints,
-        and sequences/auto_increment for the table in question"""
+        """
+        for the given table, returns a 2-tuple of
+        (dict of Field objects keyed by name,
+         list of multicolumn unique constraints (sets of Fields))
+        """
          raise NotImplementedError


diff -ru PyDO-2.0a2-orig/src/PyDO2/drivers/psycopgconn.py PyDO-2.0a2/src/PyDO2/d
rivers/psycopgconn.py
--- PyDO-2.0a2-orig/src/PyDO2/drivers/psycopgconn.py    2005-05-05 12:02:14.0000
00000 -0400
+++ PyDO-2.0a2/src/PyDO2/drivers/psycopgconn.py 2005-05-18 01:28:43.565443232 -0
400
@@ -12,6 +12,7 @@
  from PyDO2.operators import BindingConverter
  from PyDO2.dbtypes import DATE, TIMESTAMP, BINARY, INTERVAL, \
       date_formats, timestamp_formats
+from PyDO2.field import Field

  import time
  import datetime
@@ -19,10 +20,10 @@
  import psycopg

  if psycopg.__version__[:3] >= '1.9':
-   #psycopg version two.
-   psycopg_version=2
+    #psycopg version two.
+    psycopg_version=2
  else:
-   psycopg_version=1
+    psycopg_version=1

  try:
     import mx.DateTime
@@ -147,5 +148,65 @@
              raise PyDOError, "could not get value for sequence %s!" % name
          return res[0]

+    def describeTable(self, table):
+        sql = """
+        SELECT a.attname, a.attnum
+        FROM pg_catalog.pg_attribute a
+        WHERE a.attrelid = %s::regclass
+          AND a.attnum > 0
+          AND NOT a.attisdropped
+        ORDER BY a.attnum
+        """
+        fields = {}
+        cur = self.conn.cursor()
+        if self.verbose:
+            debug("SQL: %s", (sql,))
+        cur.execute(sql, (table,))
+        for row in cur.fetchall():
+            if self.verbose:
+                debug("Found column %s" % list(row))
+            fields[row[1]] = Field(row[0])
+
+        sql = """
+        SELECT indkey
+        FROM pg_catalog.pg_index i
+        WHERE i.indrelid = %s::regclass
+          AND i.indisunique
+        """
+        unique = set()
+        if self.verbose:
+            debug("SQL: %s", (sql,))
+        cur.execute(sql, (table,))
+        for row in cur.fetchall():
+            L = [int(i) for i in row[0].split(' ')]
+            if self.verbose:
+                debug("Found unique index on %s" % L)
+            if len(L) == 1:
+                fields[L[0]].unique = True
+            else:
+                unique.add(frozenset([fields[i] for i in L]))
+
+        sql = """
+        SELECT relname
+        FROM pg_class
+        WHERE relname like '%s_%%_seq'
+          AND relkind = 'S'
+        """ % table
+        if self.verbose:
+            debug("SQL: %s", (sql,))
+        cur.execute(sql)
+        for row in cur.fetchall():
+            maybecolname = row[0][len(table) + 1:-4]
+            for field in fields.values():
+                if field.name == maybecolname:
+                    if self.verbose:
+                        debug("Found sequence %s on %s" % (row[0], field.name))
+                    field.sequence = row[0]
+                    break
+
+        cur.close()
+        d = {}
+        for f in fields.values():
+            d[f.name] = f
+        return (d, unique)

-


-------------------------------------------------------
This SF.Net email is sponsored by Oracle Space Sweepstakes
Want to be the first software developer in space?
Enter now for the Oracle Space Sweepstakes!
http://ads.osdn.com/?ad_id=7412&alloc_id=16344&op=click