Re: [sqlalchemy] dirtying attributes of a user-defined type

2011-01-14 Thread A . M .

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

2011-01-14 Thread A.M.

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

2011-01-13 Thread A.M.
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

2011-01-13 Thread Michael Bayer
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.