Re: [sqlalchemy] dirtying attributes of a user-defined type
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
Re: [sqlalchemy] dirtying attributes of a user-defined type
On Jan 14, 2011, at 3:26 PM, A.M. wrote: I suspect I have to implement special converter methods with psycopg2 register_adapter/new_type. That is what I am experimenting with now. It looks like SQLAlchemy's array type support doesn't support anything beyond basic built-in types that accept numbers or text values. (Please do correct me if I am wrong.) I suspect this is a limitation of the default psycopg2 array support which does not allow passing a type along with the array during execution. It also seems to always bind arrays inline. I worked around this by installing type converters directly in psycopg2. SQLAlchemy then ends up with the values as pre-cooked ACLItem and ACLItemArray objects. From an external API standpoint, nothing changed. One stumbling block I had was that the code sample in the docs was too slim and there is no UserDefinedType example in the examples directory, so I spent a lot of time in the debugger. I hope this code may help others. Here is the code that worked: == import sqlalchemy.types as types import re import sqlalchemy.exc from sqlalchemy.dialects.postgresql import ARRAY from project.lib.aclitem import ACLItem,ACLItemArray from copy import deepcopy #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 ACLItemArrayType(types.MutableType,types.UserDefinedType,types.Concatenable): def get_col_spec(self): return 'aclitem[]' def bind_processor(self,dialect): return None def result_process(self,dialect): return None def copy_value(self,value): return deepcopy(value) def compare_values(self,a,b): return a == b 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): return None def copy_value(self,value): return deepcopy(value) def result_processor(self,dialect,column_type): return None === import re from copy import deepcopy 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) def __deepcopy__(self,memo): return ACLItem(self.grantee,self.permissions,self.grantor,self.grant_option) def __copy__(self): return deepcopy(self) @classmethod def _from_pgsql_string(klass,aclstring): grantee=perms*/grantor matches = re.match('([^=]+)=([^/\*]+)(\*?)/(\w+)',aclstring) if matches is None: raise ValueError('string does not appear to represent a PostgreSQL ACL') 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() def __repr__(self): return ACLItem('%s',%s,'%s',%s) % (self.grantee,self.permissions,self.grantor,self.grant_option) class ACLItemArray(list): #in an aclitem array, the def aclitem_for_grantee(self,role): for acl in self: if role ==
[sqlalchemy] dirtying attributes of a user-defined type
Hello, I have created an SQLAlchemy type which represents a postgresql aclitem (which represents postgresql access control lists). I am able to load and save newly-created ACLItems from the database, however, modifying the values of an instance of the type does not dirty it for flushing. Is there some decorator for dirtying accessors to the type instance convenience methods? Specifically, modifying any of grantee, grantor, permissions, and grant_option, does not trigger a proper update. Cheers, M import sqlalchemy.types as types import re import sqlalchemy.exc #include/utils/acl.h #define ACL_ALL_RIGHTS_STR arwdDxtXUCTc class ACLItem(types.UserDefinedType): def __init__(self,grantee=None,permissions=None,grantor=None,grant_option=False): #note that sqlalchemy calls this with None arguments for processing self.grantee = grantee self.permissions = [] if permissions: for p in permissions: self.permissions.append(p) self.grantor = grantor self.grant_option = grant_option def get_col_spec(self): return 'aclitem' def bind_processor(self,dialect): def acl2string(aclitem): return aclitem._as_pgsql_string() return acl2string def compare_values(self,a,b): return a._as_pgsql_string() == b._as_pgsql_string() 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 = len(matches.group(3)) grantor = matches.group(4) return ACLItem(grantee,permissions,grantor,grant_option) def result_processor(self,dialect,column_type): def string2acl(aclstring): return ACLItem._from_pgsql_string(aclstring) return string2acl 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): if on: self.permissions.append(permission) else: self.permissions.remove(permission) def clear_permissions(self): del self.permissions[:] def __str__(self): return self._as_pgsql_string() -- 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.
Re: [sqlalchemy] dirtying attributes of a user-defined type
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. On Jan 13, 2011, at 1:33 PM, A.M. wrote: Hello, I have created an SQLAlchemy type which represents a postgresql aclitem (which represents postgresql access control lists). I am able to load and save newly-created ACLItems from the database, however, modifying the values of an instance of the type does not dirty it for flushing. Is there some decorator for dirtying accessors to the type instance convenience methods? Specifically, modifying any of grantee, grantor, permissions, and grant_option, does not trigger a proper update. Cheers, M import sqlalchemy.types as types import re import sqlalchemy.exc #include/utils/acl.h #define ACL_ALL_RIGHTS_STR arwdDxtXUCTc class ACLItem(types.UserDefinedType): def __init__(self,grantee=None,permissions=None,grantor=None,grant_option=False): #note that sqlalchemy calls this with None arguments for processing self.grantee = grantee self.permissions = [] if permissions: for p in permissions: self.permissions.append(p) self.grantor = grantor self.grant_option = grant_option def get_col_spec(self): return 'aclitem' def bind_processor(self,dialect): def acl2string(aclitem): return aclitem._as_pgsql_string() return acl2string def compare_values(self,a,b): return a._as_pgsql_string() == b._as_pgsql_string() 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 = len(matches.group(3)) grantor = matches.group(4) return ACLItem(grantee,permissions,grantor,grant_option) def result_processor(self,dialect,column_type): def string2acl(aclstring): return ACLItem._from_pgsql_string(aclstring) return string2acl 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): if on: self.permissions.append(permission) else: self.permissions.remove(permission) def clear_permissions(self): del self.permissions[:] def __str__(self): return self._as_pgsql_string() -- 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. -- 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.