def registerValidation(mapped, exclude=None): ''' Register to mapped class all the validations that can be performed based on the SQL alchemy mapping. @param mapped: class The mapped model class. @param exclude: list[string]|tuple(string) A list of column names to be excluded from automatic validation. @return: Property The property id of the model. ''' assert isclass(mapped), 'Invalid class %s' % mapped mapper, typeModel = mappingFor(mapped), typeFor(mapped) assert isinstance(mapper, Mapper), 'Invalid mapped class %s' % mapped assert isinstance(typeModel, TypeModel), 'Invalid model class %s' % mapped assert not exclude or isinstance(exclude, (list, tuple)), 'Invalid exclude %s' % exclude model = typeModel.container assert isinstance(model, Model) properties = set(model.properties) for cp in mapper.iterate_properties: if not isinstance(cp, ColumnProperty): continue assert isinstance(cp, ColumnProperty) if cp.key: prop = cp.key try: properties.remove(prop) except KeyError: continue if not (exclude and prop in exclude): propRef = getattr(mapped, prop) column = getattr(mapper.c, cp.key, None) assert isinstance(column, Column), 'Invalid column %s' % column if __debug__: propType = typeFor(propRef) assert isinstance(propType, TypeModelProperty), 'Invalid property %s with type %s' % (prop, propType) if column.primary_key and column.autoincrement: if prop != model.propertyId: raise MappingError('The primary key is expected to be %s, but got SQL primary key for %s' % (model.propertyId, prop)) validateAutoId(propRef) elif not column.nullable: validateRequired(propRef) if isinstance(column.type, String) and column.type.length: validateMaxLength(propRef, column.type.length) if column.unique: validateProperty(propRef, partial(onPropertyUnique, mapped)) if column.foreign_keys: for fk in column.foreign_keys: assert isinstance(fk, ForeignKey) try: fkcol = fk.column except AttributeError: raise MappingError('Invalid foreign column for %s, maybe you are not using the meta class' % prop) validateProperty(propRef, partial(onPropertyForeignKey, mapped, fkcol), index=INDEX_PROP_FK) for prop in properties: if not (exclude and prop in exclude): validateManaged(getattr(mapped, prop))
def testValidation(self): Entity._ally_listeners = {} validateAutoId(Entity.Id) validateRequired(Entity.Required) validateMaxLength(Entity.WithLength, 5) validateManaged(Entity.Managed) dummyService = DummyServiceEntity() proxySrvNonValid = proxyWrapFor(dummyService) proxySrv = proxyWrapFor(dummyService) bindValidations(proxySrv) assert isinstance(proxySrv, IServiceEntity) e = Entity() self.assertRaisesRegex(InputError, "(Entity.Required='Expected a value')", proxySrv.insert, e) self.assertEqual(proxySrvNonValid.insert(e), 'inserted') self.assertRaisesRegex(InputError, "(Entity.Id='Expected a value')", proxySrv.update, e) self.assertEqual(proxySrvNonValid.update(e), 'updated') e.Id = 'custom id' self.assertRaisesRegex(InputError, "(Entity.Id='No value expected')", proxySrv.insert, e) self.assertTrue(proxySrv.update(e) == 'updated') e = Entity() e.Required = 'Provided a value' self.assertTrue(proxySrv.insert(e) == 'inserted') e.Id = 'id' self.assertTrue(proxySrv.update(e) == 'updated') e = Entity() e.Required = 'required' e.WithLength = 'This is a longer text then 5' self.assertRaisesRegex(InputError, "(Entity.WithLength='Maximum length allowed is 5)", proxySrv.insert, e) e.WithLength = 'hello' self.assertTrue(proxySrv.insert(e) == 'inserted') e.WithLength = 'This is a longer text then 5' e.Id = 'id' self.assertRaisesRegex(InputError, "(Entity.WithLength='Maximum length allowed is 5)", proxySrv.update, e) e.WithLength = 'hello' self.assertTrue(proxySrv.update(e) == 'updated') e = Entity() e.Required = 'required' e.Managed = 'should not have value' self.assertRaisesRegex(InputError, "(Entity.Managed='No value expected')", proxySrv.insert, e) e.Id = 'id' self.assertRaisesRegex(InputError, "(Entity.Managed='No value expected')", proxySrv.update, e) self.assertRaises(AttributeError, getattr, proxySrv, '_hidden')
return self.author.Name # Non REST model attributes -------------------------------------- typeId = Column('fk_type_id', ForeignKey(PostTypeMapped.id, ondelete='RESTRICT'), nullable=False) type = relationship(PostTypeMapped, uselist=False, lazy='joined') author = relationship(CollaboratorMapped, uselist=False, lazy='joined') creator = relationship(UserMapped, uselist=False, lazy='joined') # Expression for hybrid ------------------------------------ @classmethod @IsModified.expression def _IsModified(cls): return case([(cls.UpdatedOn != None, True)], else_=False) @classmethod @IsPublished.expression def _IsPublished(cls): return case([(cls.PublishedOn != None, True)], else_=False) @classmethod @AuthorName.expression def _AuthorName(cls): return case([(cls.Author == None, UserMapped.Name)], else_=CollaboratorMapped.Name) validateRequired(PostMapped.Type) validateManaged(PostMapped.Type, key=EVENT_PROP_UPDATE) validateManaged(PostMapped.Author, key=EVENT_PROP_UPDATE) validateManaged(PostMapped.CreatedOn) validateManaged(PostMapped.PublishedOn) validateManaged(PostMapped.UpdatedOn) validateManaged(PostMapped.DeletedOn)
from livedesk.meta.blog_type import BlogTypeMapped # -------------------------------------------------------------------- @validate(exclude=('CreatedOn',)) class BlogMapped(Base, Blog): ''' Provides the mapping for Blog. ''' __tablename__ = 'livedesk_blog' __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8') Id = Column('id', INTEGER(unsigned=True), primary_key=True) Type = Column('fk_blog_type', ForeignKey(BlogTypeMapped.Id), nullable=False) Language = Column('fk_language_id', ForeignKey(LanguageEntity.Id), nullable=False) Creator = Column('fk_creator_id', ForeignKey(UserMapped.Id), nullable=False) Title = Column('title', String(255), nullable=False) Description = Column('description', Text) OutputLink = Column('output_link', Text) CreatedOn = Column('created_on', DateTime, nullable=False) LiveOn = Column('live_on', DateTime) ClosedOn = Column('closed_on', DateTime) validateManaged(BlogMapped.CreatedOn) # -------------------------------------------------------------------- from livedesk.meta.blog_post import BlogPostMapped BlogMapped.UpdatedOn = column_property(select([func.max(BlogPostMapped.UpdatedOn)]). where(BlogPostMapped.Blog == BlogMapped.Id))
''' from ..api.user import User from sqlalchemy.schema import Column, ForeignKey from sqlalchemy.types import String, DateTime from superdesk.person.meta.person import PersonMapped from ally.support.sqlalchemy.mapper import validate from ally.container.binder_op import validateManaged, validateRequired # -------------------------------------------------------------------- @validate(exclude=('Password', 'CreatedOn', 'DeletedOn')) class UserMapped(PersonMapped, User): ''' Provides the mapping for User entity. ''' __tablename__ = 'user' __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8') Name = Column('name', String(20), nullable=False) CreatedOn = Column('created_on', DateTime, nullable=False) DeletedOn = Column('deleted_on', DateTime) # Non REST model attribute -------------------------------------- userId = Column('fk_person_id', ForeignKey(PersonMapped.Id, ondelete='CASCADE'), primary_key=True) password = Column('password', String(255), nullable=False) # Never map over the inherited id validateRequired(UserMapped.Password) validateManaged(UserMapped.CreatedOn) validateManaged(UserMapped.DeletedOn)
BlogType = declared_attr(lambda cls: Column( 'fk_blog_type_id', ForeignKey(BlogTypeMapped.Id), nullable=False)) Name = declared_attr( lambda cls: Column('name', String(190), nullable=False, unique=True)) Order = declared_attr(lambda cls: Column('ordering', REAL)) # Non REST model attribute -------------------------------------- blogTypePostId = declared_attr( lambda cls: Column('fk_post_id', ForeignKey(PostMapped.Id, ondelete='CASCADE'), primary_key=True)) # Never map over the inherited id class BlogTypePostEntry(Base, BlogTypePostDefinition): ''' Provides the mapping for BlogPost table where it keeps the connection between the post and the blog. ''' @validate(exclude=('Order', )) class BlogTypePostMapped(BlogTypePostDefinition, PostMapped, BlogTypePost): ''' Provides the mapping for BlogPost in the form of extending the Post. ''' __table_args__ = dict(BlogTypePostDefinition.__table_args__, extend_existing=True) validateManaged(BlogTypePostMapped.Order)
LiveOn = Column('live_on', DateTime) ClosedOn = Column('closed_on', DateTime) @hybrid_property def IsLive(self): return self.LiveOn is not None and self.ClosedOn is None # Expression for hybrid ------------------------------------ @classmethod @IsLive.expression def _IsLive(cls): return case([((cls.LiveOn != None) & (cls.ClosedOn == None), True)], else_=False) validateManaged(BlogMapped.CreatedOn) # -------------------------------------------------------------------- from livedesk.meta.blog_post import BlogPostMapped BlogMapped.UpdatedOn = column_property( select([func.max(BlogPostMapped.UpdatedOn) ]).where(BlogPostMapped.Blog == BlogMapped.Id)) # -------------------------------------------------------------------- class BlogSourceDB(Base): ''' Provides the mapping for BlogSource.
from superdesk.person.meta.person import PersonMapped from sqlalchemy.dialects.mysql.base import INTEGER # -------------------------------------------------------------------- @validate(exclude=('Password', 'CreatedOn', 'Active', 'Type')) class UserMapped(PersonMapped, User): ''' Provides the mapping for User entity. ''' __tablename__ = 'user' __table_args__ = (UniqueConstraint('name', name='uix_user_name'), dict(mysql_engine='InnoDB', mysql_charset='utf8')) Name = Column('name', String(150), nullable=False, unique=True) Uuid = Column('uuid', String(32), unique=True, nullable=True) Cid = Column('cid', INTEGER(unsigned=True), nullable=True, default=0) CreatedOn = Column('created_on', DateTime, nullable=False) Active = Column('active', Boolean, nullable=False, default=True) Type = association_proxy('type', 'Key') # Non REST model attribute -------------------------------------- userId = Column('fk_person_id', ForeignKey(PersonMapped.Id, ondelete='CASCADE'), primary_key=True) password = Column('password', String(255), nullable=False) # Never map over the inherited id typeId = Column('fk_type_id', ForeignKey(UserTypeMapped.id, ondelete='RESTRICT'), nullable=False) type = relationship(UserTypeMapped, uselist=False, lazy='joined') validateRequired(UserMapped.Password) validateManaged(UserMapped.CreatedOn) validateManaged(UserMapped.Active)
class MetaInfoMapped(Base, MetaInfo): def validate(self): o sa fie un singur media data service archive care foloseste file systzem: salveaza fisierul local in pathul cunoscut notifica data handlerurile de noul fisier dand si locatia fisireului in file system. o sa fie un singur thumbnail service unde faci upload la thumbnail si care se ocupa de resizing foloseste file systzem fara sa foloseasca un CDM si face resizing on demand bazat pe o lista cunoscuta de resizing. o sa fie un URL atasat de meta data ptr thumb si cat se cere thumbu o sa se specifice thumb size, eventual de facut o entitate pentru thumb care sa aiba un id unique si sa poate fi folosti ca referinta la upload, asta pentru cazul in care folosesti acelasi thumb pentru mai multe meta data si inclusiv daca se face sql pentru selectarea unui thumb tot merita, deci ideea ii sa folosim REST aproach si sa nu complicam lucrurile cu tot felu de procese. Ideea i ca daca folosim CDM ii complicat procseul si atunci ii mai usor ca serviciul the data si thumbs sa lucreze direct cu file system, ele putand fi distrbuite daca ii cazul sau facute implementari pe baza la altfel de structuri. de facut validatra astfel incat sa nu poti insera meta info pentru alte meta data types, tre facut cu declarative base de continuat cu image media si dupa aia de facut resize table = Table('archive_image_info', meta, Column('fk_meta_info_id', ForeignKey(MetaInfo.Id), primary_key=True, key='Id'), Column('caption', String(255), nullable=False, key='Caption'), mysql_engine='InnoDB', mysql_charset='utf8') ImageInfo = mapperModel(ImageInfo, table, exclude=['MetaData'], inherits=MetaInfo) validateManaged(ImageInfo.MetaData)
from sqlalchemy.schema import Column, ForeignKey, UniqueConstraint from sqlalchemy.types import String, DateTime from superdesk.person.meta.person import PersonMapped # -------------------------------------------------------------------- @validate(exclude=('Password', 'CreatedOn', 'DeletedOn')) class UserMapped(PersonMapped, User): ''' Provides the mapping for User entity. ''' __tablename__ = 'user' __table_args__ = (UniqueConstraint('name', name='uix_user_name'), dict(mysql_engine='InnoDB', mysql_charset='utf8')) Name = Column('name', String(150), nullable=False, unique=True) CreatedOn = Column('created_on', DateTime, nullable=False) DeletedOn = Column('deleted_on', DateTime) # Non REST model attribute -------------------------------------- userId = Column('fk_person_id', ForeignKey(PersonMapped.Id, ondelete='CASCADE'), primary_key=True) password = Column('password', String(255), nullable=False) # Never map over the inherited id validateRequired(UserMapped.Password) validateManaged(UserMapped.CreatedOn) validateManaged(UserMapped.DeletedOn)
# Non REST model attributes -------------------------------------- typeId = Column('fk_type_id', ForeignKey(PostTypeMapped.id, ondelete='RESTRICT'), nullable=False) type = relationship(PostTypeMapped, uselist=False, lazy='joined') author = relationship(CollaboratorMapped, uselist=False, lazy='joined') creator = relationship(UserMapped, uselist=False, lazy='joined') # Expression for hybrid ------------------------------------ @classmethod @IsModified.expression def _IsModified(cls): return case([(cls.UpdatedOn != None, True)], else_=False) @classmethod @AuthorName.expression def _AuthorName(cls): return case([(cls.Author == None, UserMapped.Name)], else_=CollaboratorMapped.Name) validateRequired(PostMapped.Type) validateManaged(PostMapped.Type, key=EVENT_PROP_UPDATE) validateManaged(PostMapped.Author, key=EVENT_PROP_UPDATE) validateManaged(PostMapped.CreatedOn) validateManaged(PostMapped.PublishedOn) validateManaged(PostMapped.UpdatedOn) validateManaged(PostMapped.DeletedOn)
# -------------------------------------------------------------------- class BlogTypePostDefinition: ''' Provides the mapping for BlogCollaborator definition. ''' __tablename__ = 'livedesk_blog_type_post' __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8') BlogType = declared_attr(lambda cls: Column('fk_blog_type_id', ForeignKey(BlogTypeMapped.Id), nullable=False)) Name = declared_attr(lambda cls: Column('name', String(190), nullable=False, unique=True)) Order = declared_attr(lambda cls: Column('ordering', REAL)) # Non REST model attribute -------------------------------------- blogTypePostId = declared_attr(lambda cls: Column('fk_post_id', ForeignKey(PostMapped.Id, ondelete='CASCADE'), primary_key=True)) # Never map over the inherited id class BlogTypePostEntry(Base, BlogTypePostDefinition): ''' Provides the mapping for BlogPost table where it keeps the connection between the post and the blog. ''' @validate(exclude=('Order',)) class BlogTypePostMapped(BlogTypePostDefinition, PostMapped, BlogTypePost): ''' Provides the mapping for BlogPost in the form of extending the Post. ''' __table_args__ = dict(BlogTypePostDefinition.__table_args__, extend_existing=True) validateManaged(BlogTypePostMapped.Order)
def registerValidation(mapped, exclude=None): ''' Register to mapped class all the validations that can be performed based on the SQL alchemy mapping. @param mapped: class The mapped model class. @param exclude: list[string]|tuple(string) A list of column names to be excluded from automatic validation. @return: Property The property id of the model. ''' assert isclass(mapped), 'Invalid class %s' % mapped mapper, typeModel = mappingFor(mapped), typeFor(mapped) assert isinstance(mapper, Mapper), 'Invalid mapped class %s' % mapped assert isinstance(typeModel, TypeModel), 'Invalid model class %s' % mapped assert not exclude or isinstance( exclude, (list, tuple)), 'Invalid exclude %s' % exclude model = typeModel.container assert isinstance(model, Model) properties = set(model.properties) for cp in mapper.iterate_properties: if not isinstance(cp, ColumnProperty): continue assert isinstance(cp, ColumnProperty) if cp.key: prop = cp.key try: properties.remove(prop) except KeyError: continue if not (exclude and prop in exclude): propRef = getattr(mapped, prop) column = getattr(mapper.c, cp.key, None) assert isinstance(column, Column), 'Invalid column %s' % column if __debug__: propType = typeFor(propRef) assert isinstance( propType, TypeModelProperty ), 'Invalid property %s with type %s' % (prop, propType) if column.primary_key and column.autoincrement: if prop != model.propertyId: raise MappingError( 'The primary key is expected to be %s, but got SQL primary key for %s' % (model.propertyId, prop)) validateAutoId(propRef) elif not column.nullable: validateRequired(propRef) if isinstance(column.type, String) and column.type.length: validateMaxLength(propRef, column.type.length) if column.unique: validateProperty(propRef, partial(onPropertyUnique, mapped)) if column.foreign_keys: for fk in column.foreign_keys: assert isinstance(fk, ForeignKey) try: fkcol = fk.column except AttributeError: raise MappingError( 'Invalid foreign column for %s, maybe you are not using the meta class' % prop) validateProperty(propRef, partial(onPropertyForeignKey, mapped, fkcol), index=INDEX_PROP_FK) for prop in properties: if not (exclude and prop in exclude): validateManaged(getattr(mapped, prop))
from ally.container.binder_op import validateManaged from sqlalchemy.ext.hybrid import hybrid_property from superdesk.user.meta.user import UserMapped from superdesk.person.meta.person import PersonMapped # -------------------------------------------------------------------- @validate(exclude=['Item', 'PublishedOn']) class ArticleMapped(Base, Article): ''' Provides the mapping for Article. ''' __tablename__ = 'article' __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8') Id = Column('id', INTEGER(unsigned=True), primary_key=True) Item = Column('fk_item_id', ForeignKey(ItemMapped.GUId, ondelete='RESTRICT'), nullable=False) Creator = Column('fk_creator_id', ForeignKey(UserMapped.Id, ondelete='RESTRICT'), nullable=False) Author = Column('fk_author_id', ForeignKey(PersonMapped.Id, ondelete='RESTRICT'), nullable=False) Content = Column('content', TEXT, nullable=False) PublishedOn = Column('published_on', DateTime) @hybrid_property def IsPublished(self): return self.PublishedOn is not None # Expression for hybrid ------------------------------------ IsPublished.expression(lambda cls: cls.PublishedOn != None) validateManaged(ArticleMapped.Item) validateManaged(ArticleMapped.PublishedOn)
class ArticleMapped(Base, Article): ''' Provides the mapping for Article. ''' __tablename__ = 'article' __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8') Id = Column('id', INTEGER(unsigned=True), primary_key=True) Item = Column('fk_item_id', ForeignKey(ItemMapped.GUId, ondelete='RESTRICT'), nullable=False) Creator = Column('fk_creator_id', ForeignKey(UserMapped.Id, ondelete='RESTRICT'), nullable=False) Author = Column('fk_author_id', ForeignKey(PersonMapped.Id, ondelete='RESTRICT'), nullable=False) Content = Column('content', TEXT, nullable=False) PublishedOn = Column('published_on', DateTime) @hybrid_property def IsPublished(self): return self.PublishedOn is not None # Expression for hybrid ------------------------------------ IsPublished.expression(lambda cls: cls.PublishedOn != None) validateManaged(ArticleMapped.Item) validateManaged(ArticleMapped.PublishedOn)