Readonly objects/protecting objects from modifications

7 views
Skip to first unread message

Andreas Jung

unread,
Dec 21, 2007, 10:44:55 AM12/21/07
to sqlal...@googlegroups.com
Is there a way to protect objects from modifications? I have a complex
self-referential data structure that is passed to some other modules
written by co-workers. I need to avoid that the data structure is changed
(because
of programming errors). Is there an easy way to accomplish this (other than
by using a dedicated read-only DB connection where the user has only
select grants)?

Andreas

Rick Morrison

unread,
Dec 21, 2007, 3:13:26 PM12/21/07
to sqlal...@googlegroups.com

Something like this is available on a roll-your-own basis via Python properties along with some mapper tricks:

   http://www.sqlalchemy.org/docs/04/mappers.html#advdatamapping_mapper_overriding

I would be +1 for such a feature implemented on mapped instances, could be useful for detecting those hard-to-find bugs, but I can't think of a nice and simple API for it. For mapped instances via Query(), it  could be an .option(), but I can't see a good way for its use on relation()s. Also not sure if such a feature would throw an exception on attribute setting, or whether it ought to simply be ingored during a flush (OIOW, have it's "dirty" flag locked down to False)

sdo...@sistechnology.com

unread,
Dec 21, 2007, 1:52:38 PM12/21/07
to sqlal...@googlegroups.com
i have such thing implemented externaly but it is definitely not nice (read:
tricky and underground) - replacing the __dict__ with something handmade
that does what i say as i say if i say. that's dbcook's reflector for my
static_type structures; look in dbcook/usage/static_type if interested. and
it is now broken with the latest instance_state handling mechanism.

another, similar or not, feature i needed while doing dbcook, was a
readonly/loadonly mapper; i.e. a mapper for a sort-of intermediate
base-class which should not have its own instances; only subclasses may have
instances/DB-footprint. That i made via MapperExt, throwing at
before_insert/update/delete.

Michael Bayer

unread,
Dec 21, 2007, 4:33:34 PM12/21/07
to sqlal...@googlegroups.com
I think the only way something like this should be done is as a test fixture which decorates classes during unit tests.    It would be fairly clumsy to have in production code.

If you have coworkers who write broken code, the way you solve that is by having unit tests which will fail when the coworkers in question do something theyre not supposed to.   If other people are writing code that sets attrbutes its not supposed to and breaks things, you need more tests to catch those conditions.  If youre putting code into production that hasnt been tested, then you need a build process, automated testing, etc.    There is definitely a "best practice" here and test driven development is it.

Andreas Jung

unread,
Dec 22, 2007, 12:34:08 AM12/22/07
to sqlal...@googlegroups.com

--On 21. Dezember 2007 16:33:34 -0500 Michael Bayer
<mik...@zzzcomputing.com> wrote:

With all respect, this is not a useful answer. Even with tests (unittests
and weeks of manual tests) I had the case that a simple programming error
(of my own) produced a data disaster after some weeks. There is no 100%
test coverage. Tests don't solve all problems. There is sometimes the need
for a better security belt.

Andreas

Michael Bayer

unread,
Dec 22, 2007, 11:03:12 AM12/22/07
to sqlal...@googlegroups.com

I am certainly suggesting a fixture that detects illegal assignments
to attributes. That it be limited to just unit tests is only a
suggestion. To establish this functionality regardless of
environment, like Rick said just create properties which prohibit
assignment. Create mappers like this:

class AttrGetter(object):
def __init__(self, name):
self.name = name
def __get__(self, instance, name):
if instance is None:
return self
return getattr(instance, '_' + name)
def __set__(self, instance, value):
raise AssertionError("Sets are not allowed")
def __delete__(self, instance):
raise AssertionError("Deletes are not allowed")

class MyClass(object):
somecolumn = AttrGetter('somecolumn')
someothercolumn = AttrGetter('someothercolumn')

mapper(MyClass, sometable, properties={
'_somecolumn':sometable.c.somecolumn,
'_someothercolumn':sometable.c.someothercolumn
})

To automate the above process with no modifications to source code,
create an instrumented mapper() function which applies the above
recipe to all table columns:

from sqlalchemy.orm import mapper as _mapper
def mapper(cls, table, **kwargs):
attrs = {}
for c in table.c:
attrs['_' + c.key] = c
setattr(cls, c.key, AttrGetter(c.key))
properties = kwargs.setdefault('properties', {})
properties.update(attrs)
return _mapper(cls, table, **kwargs)


Hope this helps.

klaus

unread,
Jan 18, 2008, 10:41:59 AM1/18/08
to sqlalchemy
Hi all,
it's only now that I came across this interesting discussion.

I tried similar things but what I wanted to protect was my cached
data. And session.merge(obj, dont_load=True) triggers these
AssertionErrors. :-(

So I went for a MapperExtension instead. The after_update method can
still prevent changes from being committed to the database. This
solution is not very elegant, however.

Best regards
Klaus


On 22 Dez. 2007, 17:03, Michael Bayer <mike...@zzzcomputing.com>
wrote:
> On Dec 22, 2007, at 12:34 AM, Andreas Jung wrote:
>
>
>
>
>
> > --On 21. Dezember 2007 16:33:34 -0500 Michael Bayer <mike...@zzzcomputing.com
Reply all
Reply to author
Forward
0 new messages