On Jan 13, 2011, at 3:29 PM, Michael Bayer wrote: > There's mistakes in how this is structured. UserDefinedType represents a > type object applied to a Column. The actual data handled by such a type is > not meant to be an instance of the type itself. ACLItem() here would be its > own class, and UserDefinedType would be the superclass of a class like > "ACLItemType". ACLItemType() is placed on the Column, and its > bind_processor() and result_processor() deal with ACLItem objects. > > Once you have the roles of "type" and "value" set up, you'd want to mixin > sqlalchemy.types.MutableType, which alerts the ORM that the value of this > type can change "inline". Note that MutableType is not recommended for high > volume applications as it performs terribly, due to the need for flush() to > scan all "mutables" in the session for changes every time it's called - 0.7 > has a new extension that allows mutable values to send change events in an > efficient manner.
Thank you for the detailed response! I have implemented all of your advice, but have hit another roadblock- specifically, I cannot determine how to properly cast the array type from within the ACLItemArrayType class. ....../sqlalchemy/engine/default.py", line 299, in do_execute cursor.execute(statement, parameters) ProgrammingError: (ProgrammingError) column "aclarray" is of type aclitem[] but expression is of type text[] LINE 1: INSERT INTO imageaclarray (aclarray) VALUES (ARRAY[E'user te... ^ HINT: You will need to rewrite or cast the expression. 'INSERT INTO imageaclarray (aclarray) VALUES (%(aclarray)s) RETURNING imageaclarray.id' {'aclarray': ['user testrole1=rU*/testrole1', 'user testrole2=Xc/testrole1']} What is really need is: insert into imageaclarray (aclarray) values (ARRAY[E'user agentm=rU*/agentm',E'user agentm=Xc/agentm']::aclitem[]); or insert into imageaclarray (aclarray) values (ARRAY[E'user agentm=rU*/agentm'::aclitem,E'user agentm=Xc/agentm'::aclitem]); but I can't figure out how to add casting or if it is even possible... clearly bind_processor is too late, but psycopg2 doesn't seem to offer an option to set the oid for the container array either. Is there a method during compilation I can use? Cheers, M Code and unit test follow: ========================= import sqlalchemy.types as types import re import sqlalchemy.exc from sqlalchemy.dialects.postgresql import ARRAY #include/utils/acl.h #define ACL_ALL_RIGHTS_STR "arwdDxtXUCTc" #update for SQLAlchemy 0.7: http://www.sqlalchemy.org/docs/07/orm/extensions/mutable.html class ACLItem(object): def __init__(self,grantee,permissions,grantor,grant_option=False): self.grantee = grantee self.permissions = [] if permissions: for p in permissions: self.permissions.append(p) self.grantor = grantor self.grant_option = grant_option def _as_pgsql_string(self): #convert to string 'user <grantee>=<perms>/<grantor>' string_perms = '' for perm in self.permissions: string_perms += perm if self.grant_option: grant_option = '*' else: grant_option = '' return "user %s=%s%s/%s" % (self.grantee,string_perms,grant_option,self.grantor) @classmethod def _from_pgsql_string(klass,aclstring): "grantee=perms*/grantor" matches = re.match('([^=]+)=([^/\*]+)(\*?)/(\w+)',aclstring) if matches is None: raise sqlalchemy.exc.DataError(aclstring,[],'') grantee = matches.group(1) permissions = matches.group(2) grant_option = bool(len(matches.group(3))) grantor = matches.group(4) return ACLItem(grantee,permissions,grantor,grant_option) def __eq__(self,other_object): if not isinstance(other_object,self.__class__): return False return str(self) == str(other_object) def has_permission(self,permission_test): return permission_test in self.permissions def set_permission(self,permission,on=True): if not self.has_permission(permission) and on: self.permissions.append(permission) elif self.has_permission(permission) and not on: self.permissions.remove(permission) def clear_permissions(self): del self.permissions[:] def reset_with_acl(self,acl): """takes an acl and replaces its own settings with the argument settings This is useful for cases where an acl in an array is "replaced" without being creating a new aclitem so that the array order in unmodified """ self.grantee = acl.grantee self.permissions = acl.permissions self.grantor = acl.grantor self.grant_option = acl.grant_option def __str__(self): return self._as_pgsql_string() class ACLItemArray(list): #in an aclitem array, the def aclitem_for_grantee(self,role): for acl in self: if role == acl.grantee: return acl return None def has_permission(self,role,permission): return self.aclitem_for_grantee(role).has_permission(permission) def set_permission(self,role,permission): return self.aclitem_for_grantee(role).set_permission(permission) def clear_permissions(self): del self.acls[:] def append(self,acl): #if there is already an acl for this user, replace with new acl (so that there are not duplicate acls for the grantee) existing_acl = self.aclitem_for_grantee(acl.grantee) if existing_acl: existing_acl.reset_with_acl(acl) else: super(ACLItemArray,self).append(acl) def remove_grantee(self,grantee): existing_acl = self.aclitem_for_grantee(grantee) if existing_acl: self.remove(existing_acl) else: raise ValueError('ACL for %s not in list' % grantee) class ACLItemArrayType(ARRAY): def __init__(self,acls=[]): self.acls = acls return super(ACLItemArrayType,self).__init__(ACLItemType) def dialect_impl(self,dialect): return self #def adapt(self,impltype): def bind_processor(self, dialect): return super(ACLItemArrayType,self).bind_processor(dialect) class ACLItemType(types.MutableType,types.UserDefinedType): #class ACLItemType(types.UserDefinedType,types.MutableType): #FAIL def get_col_spec(self): return 'aclitem' def bind_processor(self,dialect): def acl2string(aclitem): return aclitem._as_pgsql_string() return acl2string def copy_value(self,value): return ACLItem(value.grantee, value.permissions, value.grantor, value.grant_option) def result_processor(self,dialect,column_type): def string2acl(aclstring): return ACLItem._from_pgsql_string(aclstring) return string2acl ================= from sqlalchemy.schema import Column from sqlalchemy.types import Integer from sqlalchemy.orm import scoped_session from sqlalchemy.engine import create_engine from sqlalchemy.orm.session import sessionmaker from copy import deepcopy #nosetests -s -x test_aclcolumntype.py:TestACLItemColumnTypeController.test_aclitem #nosetests -s -x test_aclcolumntype.py:TestACLItemColumnTypeController.test_aclitemarray Base = declarative_base() class ImageACL(Base): __tablename__ = 'imageacl' id = Column(Integer,primary_key=True) acl = Column(ACLItemType,nullable=False) class ImageACLArray(Base): __tablename__ = 'imageaclarray' id = Column(Integer,primary_key=True) aclarray = Column(ACLItemArrayType,nullable=False) class TestACLItemColumnTypeController(TestController): def setUp(self): self.engine = create_engine('postgresql://localhost/test') #,echo=True) self.session = scoped_session(sessionmaker(autocommit=False,bind=self.engine)) Base.metadata.drop_all(self.engine) Base.metadata.create_all(self.engine) self.session.execute("CREATE ROLE testrole1;") self.session.execute("CREATE ROLE testrole2;") def tearDown(self): self.session.rollback() def test_aclitem(self): session = self.session #create an aclitem row row = ImageACL() row.acl = ACLItem('testrole1','rX','testrole1') session.add(row) session.flush() session.refresh(row) acl = row.acl assert(acl.grantee == 'testrole1') assert(acl.grantor == 'testrole1') assert(acl.has_permission('r')) assert(acl.has_permission('X')) assert(not acl.has_permission('w')) #set some new permission acl.grantee = 'testrole2' acl.grantor = 'testrole2' acl.clear_permissions() acl.set_permission('r',True) acl.set_permission('r',False) acl.set_permission('X') acl.grant_option = True assert(session.is_modified(row)) session.flush() session.refresh(row) acl = row.acl assert(acl.grantee == 'testrole2') assert(acl.grantor == 'testrole2') assert(acl.has_permission('r') is False) assert(acl.has_permission('X') is True) assert(acl.grant_option is True) def test_aclitemarray(self): session = self.session #create new aclitemarray row = ImageACLArray() row_clone = deepcopy(row) row.aclarray = ACLItemArray() acl1 = ACLItem('testrole1',['r','U'],'testrole1',True) acl2 = ACLItem('testrole2',['X','c'],'testrole1',False) row.aclarray.append(acl1) row.aclarray.append(acl2) #save to database session.add(row) session.flush() session.refresh(row) assert(row == row_clone) -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com. To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.