+ '''
+ Returns a short string representation of the object using the first
+ element from the properties() method.
+ '''
+ primary_property = self.properties()[0]
+ value = getattr(self, primary_property)
+ return '<%s %s>' % (self.classname(), str(value))
+
+ def __str__(self):
+ '''
+ Returns a human readable form of the object using the properties()
+ method.
+ '''
+ return '<%s %s>' % (self.classname(), self.json())
+
+ def not_null_constraints(self):
+ '''
+ Returns a list of properties that must be not NULL. Derived classes
+ should override this method if needed.
+ '''
+ return []
+
+ validation_message = \
+ "Validation failed because property '%s' must not be empty in object\n%s"
+
+ in_validation = False
+
+ def validate(self):
+ '''
+ This function validates the not NULL constraints as returned by
+ not_null_constraints(). It raises the DBUpdateError exception if
+ validation fails.
+ '''
+ for property in self.not_null_constraints():
+ # TODO: It is a bit awkward that the mapper configuration allow
+ # directly setting the numeric _id columns. We should get rid of it
+ # in the long run.
+ if hasattr(self, property + '_id') and \
+ getattr(self, property + '_id') is not None:
+ continue
+ if not hasattr(self, property) or getattr(self, property) is None:
+ # str() might lead to races due to a 2nd flush
+ self.in_validation = True
+ message = self.validation_message % (property, str(self))
+ self.in_validation = False
+ raise DBUpdateError(message)
+
+ @classmethod
+ @session_wrapper
+ def get(cls, primary_key, session = None):
+ '''
+ This is a support function that allows getting an object by its primary
+ key.
+
+ Architecture.get(3[, session])
+
+ instead of the more verbose
+
+ session.query(Architecture).get(3)
+ '''
+ return session.query(cls).get(primary_key)
+
+ def session(self, replace = False):
+ '''
+ Returns the current session that is associated with the object. May
+ return None is object is in detached state.
+ '''
+
+ return object_session(self)
+
+ def clone(self, session = None):
+ '''
+ Clones the current object in a new session and returns the new clone. A
+ fresh session is created if the optional session parameter is not
+ provided. The function will fail if a session is provided and has
+ unflushed changes.
+
+ RATIONALE: SQLAlchemy's session is not thread safe. This method clones
+ an existing object to allow several threads to work with their own
+ instances of an ORMObject.
+
+ WARNING: Only persistent (committed) objects can be cloned. Changes
+ made to the original object that are not committed yet will get lost.
+ The session of the new object will always be rolled back to avoid
+ ressource leaks.
+ '''
+
+ if self.session() is None:
+ raise RuntimeError( \
+ 'Method clone() failed for detached object:\n%s' % self)
+ self.session().flush()
+ mapper = object_mapper(self)
+ primary_key = mapper.primary_key_from_instance(self)
+ object_class = self.__class__
+ if session is None:
+ session = DBConn().session()
+ elif len(session.new) + len(session.dirty) + len(session.deleted) > 0:
+ raise RuntimeError( \
+ 'Method clone() failed due to unflushed changes in session.')
+ new_object = session.query(object_class).get(primary_key)
+ session.rollback()
+ if new_object is None:
+ raise RuntimeError( \
+ 'Method clone() failed for non-persistent object:\n%s' % self)
+ return new_object
+
+__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
+ self.description = description
+
+ def __eq__(self, val):
+ if isinstance(val, str):
+ return (self.arch_string== val)
+ # This signals to use the normal comparison operator
+ return NotImplemented
+
+ def __ne__(self, val):
+ if isinstance(val, str):
+ return (self.arch_string != val)
+ # This signals to use the normal comparison operator
+ return NotImplemented
+
+ def properties(self):
+ return ['arch_string', 'arch_id', 'suites_count']
+
+ def not_null_constraints(self):
+ return ['arch_string']