From 4d6475bf1f97a1934b20157465932404f118d47e Mon Sep 17 00:00:00 2001 From: Torsten Werner Date: Sun, 23 Jan 2011 23:29:06 +0100 Subject: [PATCH] Add classes Validator and ValidatorTestCase. Signed-off-by: Torsten Werner --- daklib/dbconn.py | 49 ++++++++++++++++++++++++++++++++++---- tests/dbtest_validation.py | 37 ++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) create mode 100755 tests/dbtest_validation.py diff --git a/daklib/dbconn.py b/daklib/dbconn.py index 5d0e054e..877c5a39 100755 --- a/daklib/dbconn.py +++ b/daklib/dbconn.py @@ -54,7 +54,8 @@ from inspect import getargspec import sqlalchemy from sqlalchemy import create_engine, Table, MetaData, Column, Integer -from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, backref +from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, \ + backref, MapperExtension, EXT_CONTINUE from sqlalchemy import types as sqltypes # Don't remove this, we re-export the exceptions to scripts which import us @@ -65,7 +66,7 @@ from sqlalchemy.orm.exc import NoResultFound # in the database from config import Config from textutils import fix_maintainer -from dak_exceptions import NoSourceFieldError +from dak_exceptions import DBUpdateError, NoSourceFieldError # suppress some deprecation warnings in squeeze related to sqlalchemy import warnings @@ -192,7 +193,10 @@ class ORMObject(object): for property in all_properties: # check for list or query if property[-6:] == '_count': - value = getattr(self, property[:-6]) + real_property = property[:-6] + if not hasattr(self, real_property): + continue + value = getattr(self, real_property) if hasattr(value, '__len__'): # list value = len(value) @@ -202,6 +206,8 @@ class ORMObject(object): else: raise KeyError('Do not understand property %s.' % property) else: + if not hasattr(self, property): + continue # plain object value = getattr(self, property) if value is None: @@ -239,10 +245,36 @@ class ORMObject(object): ''' return '<%s %s>' % (self.classname(), self.json()) + def validate(self): + ''' + This function should be implemented by derived classes to validate self. + It may raise the DBUpdateError exception if needed. + ''' + pass + __all__.append('ORMObject') ################################################################################ +class Validator(MapperExtension): + ''' + This class calls the validate() method for each instance for the + 'before_update' and 'before_insert' events. A global object validator is + used for configuring the individual mappers. + ''' + + def before_update(self, mapper, connection, instance): + instance.validate() + return EXT_CONTINUE + + def before_insert(self, mapper, connection, instance): + instance.validate() + return EXT_CONTINUE + +validator = Validator() + +################################################################################ + class Architecture(ORMObject): def __init__(self, arch_string = None, description = None): self.arch_string = arch_string @@ -263,6 +295,12 @@ class Architecture(ORMObject): def properties(self): return ['arch_string', 'arch_id', 'suites_count'] + def validate(self): + if self.arch_string is None or len(self.arch_string) == 0: + raise DBUpdateError( \ + "Validation failed because 'arch_string' must not be empty in object\n%s" % \ + str(self)) + __all__.append('Architecture') @session_wrapper @@ -2907,10 +2945,11 @@ class DBConn(object): def __setupmappers(self): mapper(Architecture, self.tbl_architecture, - properties = dict(arch_id = self.tbl_architecture.c.id, + properties = dict(arch_id = self.tbl_architecture.c.id, suites = relation(Suite, secondary=self.tbl_suite_architectures, order_by='suite_name', - backref=backref('architectures', order_by='arch_string')))) + backref=backref('architectures', order_by='arch_string'))), + extension = validator) mapper(Archive, self.tbl_archive, properties = dict(archive_id = self.tbl_archive.c.id, diff --git a/tests/dbtest_validation.py b/tests/dbtest_validation.py new file mode 100755 index 00000000..597097c9 --- /dev/null +++ b/tests/dbtest_validation.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +from db_test import DBDakTestCase + +from daklib.dbconn import Architecture +from daklib.dak_exceptions import DBUpdateError + +import unittest + +class ValidatorTestCase(DBDakTestCase): + """ + The ValidatorTestCase tests the validation mechanism. + """ + + def must_fail(self): + '''' + This function must fail with DBUpdateError because arch_string is not + set. It rolls back the transaction before re-raising the exception. + ''' + try: + architecture = Architecture() + self.session.add(architecture) + self.session.flush() + except: + self.session.rollback() + raise + + def test_validation(self): + 'tests validate()' + self.assertRaises(DBUpdateError, self.must_fail) + # should not fail + architecture = Architecture('i386') + self.session.add(architecture) + self.session.flush() + +if __name__ == '__main__': + unittest.main() -- 2.39.2