On Wed, Dec 23, 2015 at 11:09:35PM +0100, Oleg Broytman <p...@phdru.name> wrote: > 1. Postgres. Until now SQLObject used to create ForeignKeys of type > idType of the current table, i.e. usually INT. I fixed that (by adding a > lookup of idType of the referenced table), but that a big > backward-incompatible change and I don't want to apply it to bugfix > releases. So I think the patches will go to 2.2.0b2 and 3.0.0a2.
BTW, see the patches (attached). > > > Cheers! > > > > > > - n > > > > > > -- > > > Dr. Nathan Edwards n...@georgetown.edu > > > Department of Biochemistry and Molecular & Cellular Biology > > > Georgetown University Medical Center > > > Room 1217, Harris Building, > > > 3300 Whitehaven St, NW, Washington DC 20007 > > > Phone: 202-687-7042, Fax: 202-687-0057 Oleg. -- Oleg Broytman http://phdru.name/ p...@phdru.name Programmers don't die, they just GOSUB without RETURN.
>From 278c209f056fda12f5c7b4a79d0d18a6879a098e Mon Sep 17 00:00:00 2001 From: Oleg Broytman <p...@phdru.name> Date: Thu, 24 Dec 2015 00:44:58 +0300 Subject: [PATCH 1/3] Fix ForeignKey column type For ForeignKey, the type of column is determined by the other side (the type of id column of the referenced table). --- sqlobject/col.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sqlobject/col.py b/sqlobject/col.py index 1b1efc7..1eacf10 100644 --- a/sqlobject/col.py +++ b/sqlobject/col.py @@ -796,16 +796,19 @@ class SOKeyCol(SOCol): self.refColumn = kw.pop('refColumn', None) super(SOKeyCol, self).__init__(**kw) + def _idType(self): + return self.soClass.sqlmeta.idType + def _sqlType(self): - return self.key_type[self.soClass.sqlmeta.idType] + return self.key_type[self._idType()] def _sybaseType(self): key_type = {int: "NUMERIC(18,0) NULL", str: "TEXT"} - return key_type[self.soClass.sqlmeta.idType] + return key_type[self._idType()] def _mssqlType(self): key_type = {int: "INT NULL", str: "TEXT"} - return key_type[self.soClass.sqlmeta.idType] + return key_type[self._idType()] class KeyCol(Col): @@ -823,6 +826,10 @@ class SOForeignKey(SOKeyCol): kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey)) super(SOForeignKey, self).__init__(**kw) + def _idType(self): + other = findClass(self.foreignKey, self.soClass.sqlmeta.registry) + return other.sqlmeta.idType + def sqliteCreateSQL(self): sql = SOKeyCol.sqliteCreateSQL(self) other = findClass(self.foreignKey, self.soClass.sqlmeta.registry) -- 2.1.4
>From e35d2fec433c94fa2714d0e300f299b85cc67090 Mon Sep 17 00:00:00 2001 From: Oleg Broytman <p...@phdru.name> Date: Thu, 24 Dec 2015 00:47:39 +0300 Subject: [PATCH 2/3] Add a helper to test mutually referencing tables --- sqlobject/tests/dbtest.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/sqlobject/tests/dbtest.py b/sqlobject/tests/dbtest.py index 5b884dc..1473e7b 100644 --- a/sqlobject/tests/dbtest.py +++ b/sqlobject/tests/dbtest.py @@ -6,7 +6,7 @@ import logging import os import re import sys -from py.test import raises +from py.test import raises, skip import sqlobject import sqlobject.conftest as conftest @@ -320,6 +320,23 @@ def setupLogging(): logger = logging.getLogger() logger.addHandler(hdlr) +def setupCyclicClasses(*classes): + if not supports('dropTableCascade'): + skip("dropTableCascade isn't supported") + conn = getConnection() + for soClass in classes: + soClass.setConnection(conn) + soClass.dropTable(ifExists=True, cascade=True) + + constraints = [] + for soClass in classes: + constraints += soClass.createTable(ifNotExists=True, + applyConstraints=False) + for constraint in constraints: + conn.query(constraint) + __all__ = ['getConnection', 'getConnectionURI', 'setupClass', 'Dummy', 'raises', 'inserts', 'supports', 'deprecated_module', - 'setup_module', 'teardown_module', 'setupLogging'] + 'setup_module', 'teardown_module', 'setupLogging', + 'setupCyclicClasses', + ] -- 2.1.4
>From 9fa57a00f4109659e184c250a6d3363516ebd706 Mon Sep 17 00:00:00 2001 From: Oleg Broytman <p...@phdru.name> Date: Thu, 24 Dec 2015 00:49:20 +0300 Subject: [PATCH 3/3] Add ForeignKeyValidator Validate foreign keys using idType of the referenced column. --- sqlobject/col.py | 27 +++++++++++++++++++++++++++ sqlobject/tests/test_ForeignKey.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/sqlobject/col.py b/sqlobject/col.py index 1eacf10..eeeac93 100644 --- a/sqlobject/col.py +++ b/sqlobject/col.py @@ -814,6 +814,29 @@ class KeyCol(Col): baseClass = SOKeyCol +class ForeignKeyValidator(SOValidator): + + def __init__(self, *args, **kw): + super(ForeignKeyValidator, self).__init__(*args, **kw) + self.fkIDType = None + + def from_python(self, value, state): + if value is None: + return None + # Avoid importing the main module to get the SQLObject class for isinstance + if hasattr(value, 'sqlmeta'): + return value + if self.fkIDType is None: + otherTable = findClass(self.soCol.foreignKey, self.soCol.soClass.sqlmeta.registry) + self.fkIDType = otherTable.sqlmeta.idType + try: + value = self.fkIDType(value) + return value + except (ValueError, TypeError), e: + pass + raise validators.Invalid("expected a %r for the ForeignKey '%s', got %s %r instead" % \ + (self.fkIDType, self.name, type(value), value), value, state) + class SOForeignKey(SOKeyCol): def __init__(self, **kw): @@ -826,6 +849,10 @@ class SOForeignKey(SOKeyCol): kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey)) super(SOForeignKey, self).__init__(**kw) + def createValidators(self): + return [ForeignKeyValidator(name=self.name)] + \ + super(SOForeignKey, self).createValidators() + def _idType(self): other = findClass(self.foreignKey, self.soClass.sqlmeta.registry) return other.sqlmeta.idType diff --git a/sqlobject/tests/test_ForeignKey.py b/sqlobject/tests/test_ForeignKey.py index 44a1929..30c4127 100644 --- a/sqlobject/tests/test_ForeignKey.py +++ b/sqlobject/tests/test_ForeignKey.py @@ -1,3 +1,4 @@ +from formencode import validators from sqlobject import * from sqlobject.tests.dbtest import * from sqlobject.tests.dbtest import InstalledTestDatabase @@ -84,3 +85,35 @@ def test_otherColumn(): getConnection().cache.clear() assert test_fkey.key1 == test_composer1 assert test_other.key2 == test_composer2 + +class TestFKValidationA(SQLObject): + name = StringCol() + bfk = ForeignKey("TestFKValidationB") + cfk = ForeignKey("TestFKValidationC", default=None) + +class TestFKValidationB(SQLObject): + name = StringCol() + afk = ForeignKey("TestFKValidationA") + +class TestFKValidationC(SQLObject): + class sqlmeta: + idType = str + name = StringCol() + +def test_foreignkey_validation(): + setupCyclicClasses(TestFKValidationA, TestFKValidationB, TestFKValidationC) + a = TestFKValidationA(name="testa", bfk=None) + b = TestFKValidationB(name="testb", afk=a) + c = TestFKValidationC(id='testc', name="testc") + a.bfk = b + a.cfk = c + assert a.bfk == b + assert a.cfk == c + assert b.afk == a + + raises(validators.Invalid, + TestFKValidationA, name="testa", bfk='testb', cfk='testc') + + a = TestFKValidationA(name="testa", bfk=1, cfk='testc') + assert a.bfkID == 1 + assert a.cfkID == 'testc' -- 2.1.4
------------------------------------------------------------------------------
_______________________________________________ sqlobject-discuss mailing list sqlobject-discuss@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/sqlobject-discuss