Thread: [SQLObject] Thoughts on how to get from values to objects
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
From: Bud P. B. <bu...@si...> - 2003-05-22 11:22:54
|
Been thinking about how to marshal from a set of values stored in the DB to an object and want to share what I have come up with. The following has two sections: my misunderstanding of what SQLObject currently does (Ian, be patient with me) and a possible alternative approach. 1. SQLObject ------------ It seems to me that database columns translate to properties in SQLObject and that the setter method of the property also triggers an UPDATE query (while the insert query is handled by the new method, not attribute access). Important is probably that there are no explicit database methods such as insert (store, save) and update. This is done implicitly while using the object (i.e., attribute access). When SQLObject retrieves an object from the db, it first gets a list of column values and then calls the objects class (eg., p=Person()) to create an object instance and then sets all the values. [I derive this from the statement from the manual that "__init__ is called everytime an object is just fetched from the cache"]. What in plain python classes normally goes in __init__ should go in the _init method for SQLObject subclasses. _init seems to be called by SQLObject's new method. 2. Possible Alternative Approach -------------------------------- A possible alternative approach makes a major change in the look and feel inassuch that persistence methods (insert, update, ..) need to be called explicitly. My current intuition is that this is a good choice although I haven't cristallized the reasons out very clearly. The following matters for my intuition: * it allows the use of temporary object (without ever storing them in the db * if we use transactions, we need to deal with storage issues anyhow * same goes for resolution of collisions in concurrancy control. I don't see a way of hiding these issues from the application programmer So in this case, column values could be stored in normal attributes (in the __dict__) without the need for properties that overload the setter method to update the db. To retrieve an object from the db, a first step retrieves the column values, then creates an empty object using <class>.__new__(<class>) (thus avoiding __init__) and then assign the (unmarshelled) values to the __dict__ (or better through use of normal setattr). The advantages of this seem to be that objects behave much more like normal python objects including: * __init__ is just normal * I believe (haven't verified) that it should be possible for users to define properties in lieu of just normal __dict__ items without any adverse effects. (I reason that the use of getattr and setattr by the middleware works equally well) I hope this was of interest to someone (has anyone gotten that far reading) and am hoping for comments and being straightened out if I got it wrong... cheers --b /----------------------------------------------------------------- | Bud P. Bruegger, Ph.D. | Sistema (www.sistema.it) | Via U. Bassi, 54 | 58100 Grosseto, Italy | +39-0564-411682 (voice and fax) \----------------------------------------------------------------- |
From: Luke O. <lu...@me...> - 2003-05-22 16:17:26
|
Quoting "Bud P. Bruegger" <bu...@si...>: > Important is probably that there are no explicit database methods such > as insert (store, save) and update. This is done implicitly while > using the object (i.e., attribute access). Yep, and this is the main selling point for SQLObject for me. A class acts (in use - i know you disagree about defining classes) just like a python object. > When SQLObject retrieves an object from the db, it first gets a list > of column values and then calls the objects class (eg., p=Person()) to > create an object instance and then sets all the values. [I derive > this from the statement from the manual that "__init__ is called > everytime an object is just fetched from the cache"]. > > What in plain python classes normally goes in __init__ should go in > the _init method for SQLObject subclasses. _init seems to be called > by SQLObject's new method. A little misunderstanding here. Off the top of my head this problem will occur for any system that may transparently return an internally cached object on 'creation'. You're free to do whatever you please in an SQLObject __init__ method, so long as you realize it will be called whether this is a new object (Person.new()), an exisiting row (Person(12)), or an existing object (Person(12) that returns a cached value). It seems to me that 'typical' python __init__ stuff is only relevant to the first case, and that any ORM system should provide ways to override the first two separately (for SO, .new and ._init), and probably probably hide the third case from users. But we can probably agree that this behavior is not 'normal' python behavior, that it would be nice if __init__ could be used for the first case... but what do you do for the second case? Note that the call cycle in python (SQLObject) is: for Person.new(): new __new__ (explicit in new) __init__ (implicit after __new__) set (explicit in new) _init (explicit in new/finishCreate) for Person(12) (not cached): __new__ _init (explicit in __new__) __init__ (implicit after __new__) for Person(12) (cached): __new__ __init__ (implicit after __new__) > 2. Possible Alternative Approach > -------------------------------- > > A possible alternative approach makes a major change in the look and > feel inassuch that persistence methods (insert, update, ..) need to be > called explicitly. > > * same goes for resolution of collisions in concurrancy control. I > don't see a way of hiding these issues from the application > programmer Agreed. I suppose this directly relates to my problem with any foreignKey access possibly causing an SQLObjectNoFound error; with constraints and collisions, any attribute access could conceivably need to be safe-guarded. Not sure how I feel about that. While I initially don't like explicit storage commands, I'm also not excited about how to deal with the above. > So in this case, column values could be stored in normal attributes > (in the __dict__) without the need for properties that overload the > setter method to update the db. > > To retrieve an object from the db, a first step retrieves the column > values, then creates an empty object using <class>.__new__(<class>) > (thus avoiding __init__) and then assign the (unmarshelled) values to > the __dict__ (or better through use of normal setattr). See above, it's impossible in python to call __new__ and avoid __init__ to my knowledge. > * I believe (haven't verified) that it should be possible for users > to define properties in lieu of just normal __dict__ items without > any adverse effects. (I reason that the use of getattr and > setattr by the middleware works equally well) Just to be clear, there's no danger in setting your own properties in SQLObject. SQLObject tries to help by letting you just define _get/_set and get a property generated, but manually created properties will just be ignored. I think the key thing to think about here is how collisions or constraints affect using SQLObject at the attribute level. - Luke |
From: Bud P. B. <bu...@si...> - 2003-05-22 17:26:11
|
Hi Luke, thanks for the explanations. Some responses below.. On Thu, 22 May 2003 11:02:36 -0500 Luke Opperman <lu...@me...> wrote: > A little misunderstanding here. Off the top of my head this problem will occur > for any system that may transparently return an internally cached object on > 'creation'. I see cases where a _init hook is necessary. Say you store a derived attribute (that is redundant) but you don't want to put it in the db--then, everytime you get the object from the db, you have to recompute that derived one. But apart from these special cases, I don't necessarily believe that you really need it... Normal __init__ should do. > You're free to do whatever you please in an SQLObject __init__ > method, so long as you realize it will be called whether this is a new object > (Person.new()), an exisiting row (Person(12)), or an existing object > (Person(12) that returns a cached value). But what happens if I give it arguments other than self? The only example that comes to mind quickly is class Balanace(object): def __init__(self, amount, currency): amountInLocalCurrancy=amount * factor(currency) self.amount = amountInLocalCurrancy While amount would typically be stored in the db, currency is just for one time use and thrown out.. > It seems to me that 'typical' python > __init__ stuff is only relevant to the first case, and that any ORM system > should provide ways to override the first two separately (for SO, .new and > ._init), and probably probably hide the third case from users. But we can > probably agree that this behavior is not 'normal' python behavior, that it > would be nice if __init__ could be used for the first case... but what do you > do for the second case? In the second case I do s.th. along the following lines: curs.execute('SELECT id, name, age, ... FROM person_table ' +\ 'WHERE id = %s', requestedId) columnVals = curs.fetchone() p = instanceOfDesiredClass.__new__(instanceOfDesiredClass) p.name = columnVals[1] p.age = columnVals[2] etc. As I wrote in the separate message, I don't think this calls __init__ > Note that the call cycle in python (SQLObject) is: > > for Person.new(): > new > __new__ (explicit in new) > __init__ (implicit after __new__) > set (explicit in new) > _init (explicit in new/finishCreate) > > > for Person(12) (not cached): > __new__ > _init (explicit in __new__) > __init__ (implicit after __new__) > > for Person(12) (cached): > __new__ > __init__ (implicit after __new__) I must say I'm puzzled about this implicit __init__. Can you enlighten me on that? > > 2. Possible Alternative Approach > > -------------------------------- > > > > A possible alternative approach makes a major change in the look and > > feel inassuch that persistence methods (insert, update, ..) need to be > > called explicitly. > > > > * same goes for resolution of collisions in concurrancy control. I > > don't see a way of hiding these issues from the application > > programmer > > Agreed. I suppose this directly relates to my problem with any foreignKey > access possibly causing an SQLObjectNoFound error; with constraints and > collisions, any attribute access could conceivably need to be safe-guarded. > Not sure how I feel about that. While I initially don't like explicit storage > commands, I'm also not excited about how to deal with the above. I imagine that a typical use case is that some piece of code assigns vlues attribute by attribute and once done, commits the changes. Since there is no way in knowing automatically, when an application programmer wants to commit, then there needs to be a persistence-related command anyhow. Call it storeNcommit or whatever--but I don't see a way of doing this all implicitly. This is to say that if you want to do a dbms app without transactions, it would work. But that kind of beats the purpose of using a dbms in the first place (at least for me). > > * I believe (haven't verified) that it should be possible for users > > to define properties in lieu of just normal __dict__ items without > > any adverse effects. (I reason that the use of getattr and > > setattr by the middleware works equally well) > > Just to be clear, there's no danger in setting your own properties in > SQLObject. SQLObject tries to help by letting you just define _get/_set and > get a property generated, but manually created properties will just be > ignored. Agreed, you can do that. But the way you do that is not the normal python way--and this is exactly my point. If we manage to get SQLObject work as much as possible in the normal python way, it is easy to use (some people would call that "no magic") and learn and it is easy to use SQLObject with all kind of pre-existing code that now needs persistence. I personally believe that business logic should be written without a (at least specific) persistence method in mind and then made persistent in a second step. The more "magic", the more rewriting and surprises.. > I think the key thing to think about here is how collisions or constraints > affect using SQLObject at the attribute level. And probably transactions (see my point above that I don't see that you get away without explicit persistence commands when using them). cheers --b |
From: Ian B. <ia...@co...> - 2003-05-22 19:03:23
|
On Thu, 2003-05-22 at 11:02, Luke Opperman wrote: > > What in plain python classes normally goes in __init__ should go in > > the _init method for SQLObject subclasses. _init seems to be called > > by SQLObject's new method. > > A little misunderstanding here. Off the top of my head this problem will occur > for any system that may transparently return an internally cached object on > 'creation'. You're free to do whatever you please in an SQLObject __init__ > method, so long as you realize it will be called whether this is a new object > (Person.new()), an exisiting row (Person(12)), or an existing object > (Person(12) that returns a cached value). It seems to me that 'typical' python > __init__ stuff is only relevant to the first case, and that any ORM system > should provide ways to override the first two separately (for SO, .new and > ._init), and probably probably hide the third case from users. But we can > probably agree that this behavior is not 'normal' python behavior, that it > would be nice if __init__ could be used for the first case... but what do you > do for the second case? Yes, _init is just a workaround for __init__ being called on a cache hit. I don't know why Python is designed like this -- it seems totally dumb to me. object.__new__ should call __init__, though maybe for some reason people wanted to get at the object before __init__, but after instantiation (which is what you currently can do). It's just dumb and annoying, and this is the workaround. The only alternative is to rewrite __init__ to _init in the metaclass, but then (for instance), calling the superclass's __init__ will fail. > Note that the call cycle in python (SQLObject) is: > > for Person.new(): > new > __new__ (explicit in new) > __init__ (implicit after __new__) > set (explicit in new) > _init (explicit in new/finishCreate) Hmmm... maybe that's a little funny. > > 2. Possible Alternative Approach > > -------------------------------- > > > > A possible alternative approach makes a major change in the look and > > feel inassuch that persistence methods (insert, update, ..) need to be > > called explicitly. > > > > * same goes for resolution of collisions in concurrancy control. I > > don't see a way of hiding these issues from the application > > programmer > > Agreed. I suppose this directly relates to my problem with any foreignKey > access possibly causing an SQLObjectNoFound error; with constraints and > collisions, any attribute access could conceivably need to be safe-guarded. If you turn off column caching, yes. With caching (_cacheValues), then potentially all input could be validated/converted when it was fetched. But I would expect the checks run on input from the database will be pretty light -- it's setting attributes that's more likely to cause an exception. It would be possible to batch setting and getting. Setting, for instance, is cached on a call to new() creation (based on _SO_creating). That could be generalized slightly, so that there was a sync() method (or update() or insert() or something). That wouldn't be a big change. It might look like: obj = SomeObject(12) obj.batch() obj.a = 10 obj.c = 20 obj.update() Maybe batch (or probably another name) could take arguments that would also get it to handle locking. For getting, the equivalent to obj.update() might be obj.sync(), which would refetch the object from the database. This all makes sense mostly if you are using _cacheValues, but not if you are caching instances. Ian |
From: Bud P. B. <bu...@si...> - 2003-05-22 16:41:32
|
On Thu, 22 May 2003 11:02:36 -0500 Luke Opperman <lu...@me...> wrote: > See above, it's impossible in python to call __new__ and avoid __init__ to my > knowledge. While I'm surely not a Python wizzard, this kind of surprises me. So I ran a little test: #------------------------------------------------- #!/usr/local/bin/python class C(object): def __init__(self): self.bud="was here" c1 = C() print c1.bud c2 = C.__new__(C) try: print c2.bud except: print "c2 has no attribute 'bud'" #------------------------------------------------- That produces the following output: #------------------------------------------------- was here c2 has no attribute 'bud' #------------------------------------------------- So I don't think __init__ ran in any way! Also, if __init__ was to run implicitly, where would it get the parameters from? I avoided parameters other than self above, but this seems weird to me... --b |
From: Ian B. <ia...@co...> - 2003-05-22 19:07:42
|
Huh... that is weird. Maybe I misinterpreted __new__/__init__ documentation, and I guess I never did really test it out. So I'm guessing my use of _init in __new__ can be replaced by __init__, and all will be well... but now I want to know what the real story is too, so I'll have to look at the Python docs again. On Thu, 2003-05-22 at 11:40, Bud P.Bruegger wrote: > On Thu, 22 May 2003 11:02:36 -0500 > Luke Opperman <lu...@me...> wrote: > > > See above, it's impossible in python to call __new__ and avoid __init__ to my > > knowledge. > > While I'm surely not a Python wizzard, this kind of surprises me. So I > ran a little test: > > #------------------------------------------------- > #!/usr/local/bin/python > > class C(object): > def __init__(self): > self.bud="was here" > > c1 = C() > print c1.bud > c2 = C.__new__(C) > try: > print c2.bud > except: > print "c2 has no attribute 'bud'" > #------------------------------------------------- > > That produces the following output: > > #------------------------------------------------- > was here > c2 has no attribute 'bud' > #------------------------------------------------- > > > So I don't think __init__ ran in any way! > > Also, if __init__ was to run implicitly, where would it get the > parameters from? I avoided parameters other than self above, but this > seems weird to me... > > --b > > > > ------------------------------------------------------- > This SF.net email is sponsored by: ObjectStore. > If flattening out C++ or Java code to make your application fit in a > relational database is painful, don't do it! Check out ObjectStore. > Now part of Progress Software. http://www.objectstore.net/sourceforge > _______________________________________________ > sqlobject-discuss mailing list > sql...@li... > https://lists.sourceforge.net/lists/listinfo/sqlobject-discuss |
From: Luke O. <lu...@me...> - 2003-05-22 19:05:12
|
> While I'm surely not a Python wizzard, this kind of surprises me. So I > ran a little test: Indeed, my information was incomplete. Here's the real story: class instantiation: __init__ will be called if __new__ returns an instance of the same class. 2.2.1 has a bug that disobeys this, and will call __init__ regardless. SQLObject always does full instantiation, not calling just __new__. I can't find any direct documentation against just calling __new__, so I suppose that's one way to avoid __init__. But it means you can't have something like Person(12). Other options are to return a different class (an SQLObjectCached?) in __new__, as mentioned above, or to redefine __init__ to check for a cached version. Thanks to exarkun on #python for the initial idea (some maybe-code here, regarding cache syntax): class Cacheable(type): cached = Cache() def __new__(klass, name, bases, attrs): init = attrs.get('__init__', None) def __init__(self, *args, **kw): if init and not self.__class__.cached.get(id): init(self, *args, **kw) attrs['__init__'] = __init__ return type.__new__(klass, name, bases, attrs) class C(object): __metaclass__ = Cacheable def __new__(cls, id=None): inst = cls.cached.get(id,None): if not inst: inst = object.__new__(cls) cls.cached.put(inst) return inst This is almost exactly what SQLObject does, except it doesn't override __init__ today. So it looks like it would be easy enough to make SQLObject behave 'more' appropriately... Oh, and I'm not sure what we're talking about as far as properties go. When I said 'manually defining properties will be ignored' i meant SQLObject doesn't care about them, and they'll work fine. Agree with your summary that some form of transaction handling is required, and hence delayed updates, if you want to sanely handle attribute constraints/collisions. Enjoy, - Luke |
From: Bud P. B. <bu...@si...> - 2003-05-22 21:11:45
|
On Thu, 22 May 2003 13:50:22 -0500 Luke Opperman <lu...@me...> wrote: > Indeed, my information was incomplete. Here's the real story: > > class instantiation: > __init__ will be called if __new__ returns an instance of the same class. This is when you call the class as a constructor. Just calling __new__ gets all the lower level allocation in order and __init__ usually assigns values to __dict__. So since these values were already assigned and we made them persistant in some way, they don't have to be reassigned... Also consistency of the values was checked during initial assignment so we can assume that the values we have are consistent... I'm wondering what pickle does--it's kind of the same problem. Does it call __init__ during unpickling or just __new__??? While I haven't looked into the details, I'm quite positive the latter holds.. > 2.2.1 has a bug that disobeys this, and will call __init__ regardless. > > SQLObject always does full instantiation, not calling just __new__. I can't > find any direct documentation against just calling __new__, so I suppose > that's one way to avoid __init__. But it means you can't have something like > Person(12). For what it's worth, some free flowing thoughts on the issue of Person(12). It seems that our discussion so far has furthered each others understanding (open source at work), so here some more provocative thoughts: A python class has only a single "constructor", ie. a combination of __new__ and __init__. If you want multiple constructors, you have to subclass. I do this regularly to provide different __init__ with a signature specialized for different purposes. (And the rest of the subclass is unchanged). Applying this thought to Person(12), you really need an __init__ with a different signature (12, instead of name=..., age=...), so a subclass may be one solution. Is there a good naming convention for this? Maybe GetPerson(12) where GetPerson is a subclass of Person? But then, why does the act of retrieving an existing person from some storage (or namespace, to think more generally) need to be a class constructor? Why not Person.retrieve(12) or SQLObject.person(12) (both class methods)? Or actually Persons.get(12) may be a good way of putting it. (At least if the natural language of programming in English and application programmers don't chose to end their class names with and 's'). If you went that latter way, you could have Person('Ian', 'Bicking', 'autor of SQLObject') as a constructor instead of a classmethod Person.new('Ian', 'Bicking', '...'). The former way seems to be what I expect from plain Python. Is there any specific rational why you reversed this? > Oh, and I'm not sure what we're talking about as far as properties go. When I > said 'manually defining properties will be ignored' i meant SQLObject doesn't > care about them, and they'll work fine. Not sure whether I misunderstand, so to find out here is how I think about the issue. Every column corrsponds to a property whose setter method is overloaded to store the object in the dbms. If application programmers have a reason to (independently of SQLObject) make the same attribute into a property, they have to possibly call SQLObject's setter method from their setter method (in the same way as the __init__ of a subclass usually calls the __init__ of the superclass). One possible example of such as thing would be to log all state changes to a file. So if this understanding of what is happening is accurate, application programmer need to take special steps when defining properties related to columns. Or to avoid that, use the non-standard-python shortcut method to define properties. So in any case, the way to define properties (related to columns) is different from that of plain pyton. In contrast, if column values are simply represented in the __dict__ of the instance, everything is as in plain python. > Agree with your summary that some form of transaction handling is required, > and hence delayed updates, if you want to sanely handle attribute > constraints/collisions. Yes, you're right with the constraints--haven't thought of these before. Moving from one consistent state of an object to another will often require the change of more than one attribute. Same idea as transactions... The other reason why I feel unconfortable about updating on every single attribute change is performance. Single attribute getter and setter methods are an antipattern of distributed computing... While SQLObject gives the possiblity to change several attributes at once (with the set method), in my view this is quite awkward: it kind of requires you to shadow the object with an object or an equivalent unstructured collection of local variables to prepare the new state. Usually, you would want to call several methods on the object in order to prepare a new consistent state. If you allow attribute assignments without committing, you can do that. The set method condemns you to do the same without using any class methods and thus gets in the way.. Enough for today... cheers --bud |
From: Bud P. B. <bu...@si...> - 2003-05-23 08:31:56
|
Actually, I've thought more about the issue below. The problem aren't properties, the problem are when you define them: either at class creation time (via __metaclass__ and inheritance from SQLObject) or at runtime after the class is created: In the former case, you define property setters and getters before the user can and the user has to take you setters and getters into consideration--in the latter case, you have to take possible setters and getters defined by the user into consideration. This should be possible through introspection...??? --b On Thu, 22 May 2003 23:10:33 +0200 "Bud P. Bruegger" <bu...@si...> wrote: > > Oh, and I'm not sure what we're talking about as far as properties go. When > > I said 'manually defining properties will be ignored' i meant SQLObject > > doesn't care about them, and they'll work fine. > > Not sure whether I misunderstand, so to find out here is how I think > about the issue. Every column corrsponds to a property whose setter > method is overloaded to store the object in the dbms. If application > programmers have a reason to (independently of SQLObject) make the > same attribute into a property, they have to possibly call SQLObject's > setter method from their setter method (in the same way as the > __init__ of a subclass usually calls the __init__ of the superclass). > One possible example of such as thing would be to log all state > changes to a file. > > So if this understanding of what is happening is accurate, application > programmer need to take special steps when defining properties related > to columns. Or to avoid that, use the non-standard-python shortcut > method to define properties. So in any case, the way to define > properties (related to columns) is different from that of plain pyton. > In contrast, if column values are simply represented in the __dict__ > of the instance, everything is as in plain python. /----------------------------------------------------------------- | Bud P. Bruegger, Ph.D. | Sistema (www.sistema.it) | Via U. Bassi, 54 | 58100 Grosseto, Italy | +39-0564-411682 (voice and fax) \----------------------------------------------------------------- |
From: Ian B. <ia...@co...> - 2003-05-25 07:04:50
|
On Thu, 2003-05-22 at 16:10, Bud P.Bruegger wrote: > I'm wondering what pickle does--it's kind of the same problem. Does > it call __init__ during unpickling or just __new__??? While I haven't > looked into the details, I'm quite positive the latter holds.. I don't know what exactly it does, but it doesn't call either __init__ or __new__ -- I believe it calls __setstate__, if you want to change how pickle works on your object. > For what it's worth, some free flowing thoughts on the issue of > Person(12). It seems that our discussion so far has furthered each > others understanding (open source at work), so here some more > provocative thoughts: > > A python class has only a single "constructor", ie. a combination of > __new__ and __init__. If you want multiple constructors, you have to > subclass. I do this regularly to provide different __init__ with a > signature specialized for different purposes. (And the rest of the > subclass is unchanged). > > Applying this thought to Person(12), you really need an __init__ with > a different signature (12, instead of name=..., age=...), so a > subclass may be one solution. Is there a good naming convention for > this? Maybe GetPerson(12) where GetPerson is a subclass of Person? > > But then, why does the act of retrieving an existing person from some > storage (or namespace, to think more generally) need to be a class > constructor? Why not Person.retrieve(12) or SQLObject.person(12) (both > class methods)? Or actually Persons.get(12) may be a good way of > putting it. (At least if the natural language of programming in > English and application programmers don't chose to end their class > names with and 's'). > > If you went that latter way, you could have Person('Ian', 'Bicking', > 'autor of SQLObject') as a constructor instead of a classmethod > Person.new('Ian', 'Bicking', '...'). The former way seems to be what > I expect from plain Python. Is there any specific rational why you > reversed this? Well, I don't have the entire reason formulated, but I'll give it a go. __new__/_init is called on every *Python* object creation. It'd be a pain to avoid that, and I don't think it *should* be avoided. The reality is that there is a disconnect between database objects and Python objects. SQLObject doesn't provide an object store, ala ZODB. It maps, but there's no expectation that it will be perfect -- so creating a Python object from the database row *is* a creation/construction operation, it's not just like we've just brung the object into memory from a different storage mechanism (as with pickle or ZODB). >From a purely pragmatic point of view, fetching is much more common than constructing, so it's nice that it's shorter too. > > Oh, and I'm not sure what we're talking about as far as properties go. When I > > said 'manually defining properties will be ignored' i meant SQLObject doesn't > > care about them, and they'll work fine. > > Not sure whether I misunderstand, so to find out here is how I think > about the issue. Every column corrsponds to a property whose setter > method is overloaded to store the object in the dbms. If application > programmers have a reason to (independently of SQLObject) make the > same attribute into a property, they have to possibly call SQLObject's > setter method from their setter method (in the same way as the > __init__ of a subclass usually calls the __init__ of the superclass). > One possible example of such as thing would be to log all state > changes to a file. > > So if this understanding of what is happening is accurate, application > programmer need to take special steps when defining properties related > to columns. Or to avoid that, use the non-standard-python shortcut > method to define properties. So in any case, the way to define > properties (related to columns) is different from that of plain pyton. > In contrast, if column values are simply represented in the __dict__ > of the instance, everything is as in plain python. Personally I find properties a little funny to work with anyway. Hmmm... I guess using super() you can access properties in a superclass, but the old techniques don't apply. But anyway, if you want to add something to a property's getter or setter, you'd normally do it by subclassing, etc, but there isn't really a superclass available with the way SQLObject works. If you *do* subclass a SQLObject instance, you probably could do what you want. Anyway, the current situation just means you have to understand the way some methods work, like _SO_get_columnName, or _SO_getValue/_SO_setValue. > > Agree with your summary that some form of transaction handling is required, > > and hence delayed updates, if you want to sanely handle attribute > > constraints/collisions. > > Yes, you're right with the constraints--haven't thought of these > before. Moving from one consistent state of an object to another will > often require the change of more than one attribute. Same idea as > transactions... > > The other reason why I feel unconfortable about updating on every > single attribute change is performance. Single attribute getter and > setter methods are an antipattern of distributed computing... > > While SQLObject gives the possiblity to change several attributes at > once (with the set method), in my view this is quite awkward: it kind > of requires you to shadow the object with an object or an equivalent > unstructured collection of local variables to prepare the new state. > Usually, you would want to call several methods on the object in order > to prepare a new consistent state. If you allow attribute assignments > without committing, you can do that. The set method condemns you to > do the same without using any class methods and thus gets in the way.. Using *Python* convention, if you want to ensure consistency you do so *as each attribute is set*. People don't write Python objects that have some uncommitted/committed state, where verification is delayed until the commit. If you need to check consistency among several attributes, you set them all at once. I don't see a problem with that -- it's what I do for non-database objects already. Ian |
From: Bud P. B. <bu...@si...> - 2003-05-26 09:47:31
|
On 25 May 2003 02:05:34 -0500 Ian Bicking <ia...@co...> wrote: > On Thu, 2003-05-22 at 16:10, Bud P.Bruegger wrote: > > I'm wondering what pickle does--it's kind of the same problem. Does > > it call __init__ during unpickling or just __new__??? While I haven't > > looked into the details, I'm quite positive the latter holds.. > > I don't know what exactly it does, but it doesn't call either __init__ > or __new__ -- I believe it calls __setstate__, if you want to change how > pickle works on your object. I just looked it up and by default, it saves and restores the values of __dict__ without calling __init__. If one wants __init__ to be called, __getinitargs__() needs to be provided and the resulting tuple is stored as part of the pickle. __getstate__ and __setstate__ seem to be used if other values than the object's __dict__ need to be stored... ... > The reality is that there is a disconnect between database objects and > Python objects. SQLObject doesn't provide an object store, ala ZODB. > It maps, but there's no expectation that it will be perfect -- so > creating a Python object from the database row *is* a > creation/construction operation, it's not just like we've just brung the > object into memory from a different storage mechanism (as with pickle or > ZODB). I think I agree with this. The Attribute layer that in my proposed architecture sits between a class and the columns actually makes this process explicit (I have toSQL and fromSQL functions). Still, in most cases, I believe these will not be necessary and the default functions really just say to store and restore the __dict__. > Personally I find properties a little funny to work with anyway. ... I agree. My incentive is to impose as few limitations on classes that can be made persistent as possible.. > Using *Python* convention, if you want to ensure consistency you do so > *as each attribute is set*. People don't write Python objects that have > some uncommitted/committed state, where verification is delayed until > the commit. > > If you need to check consistency among several attributes, you set them > all at once. I don't see a problem with that -- it's what I do for > non-database objects already. hmm. you are probably right here unless the app has a need for long transactons... thanks for your feedback --b |