Re: [Zope-dev] ZPatterns AttributeProvider question
Hi! A little update.. Actually I was mistaken and SetAttributeFor() is called. Just _objectChanged() is not called in that case.. and thus my attributes are not stored to the database. Also commit() is not called at all in this case. It get's called once when the subtranscation is committed but not after some data has changed (which happens after that). Printing _v_status_ in SetAttributeFor() also gives me "Changed", thus this seems right. Maybe some more code will help (just the important pieces): def _objectAdded(self,client): """ store cached data in database """ ... here we do an sql query to insert things in database ... def _objectChanged(self,client): """ store cached data in database """ ... here we do an sql query to update things in database ... def _objectAdding(self,client, _tmap={ None:AddedStatus, DeletedStatus:ChangedStatus, ChangedStatus:AddedStatus } ): t = client._getTokenFor(self) s = t.status t.status = _tmap.get(s,s) # we need to do a commit here in order to store the record into the database! # and we need to have it stored as an AttributeFor() of the primary key will # return None otherwise (as this also just makes a query). client.commitSubtransaction() def _SetAttributeFor(self,client,name,value): """Set the attribute and return true if successful""" # attribs stores the attributes we care about if name in self._attribs.keys(): client._getCache()[name]=value client.__dict__[name]=value; client._p_changed = 1 return 1 return None So _objectChanging() is not overridden, the rest I have ommitted.. So as you also can see I already do a subtransaction commit but it does not seem to help for this problem.. regards, Christian PS: It might also be that I am simply blind ;-) But somehow ZPatterns always confuses me, especially the inner workings ;-) -- COM.lounge http://comlounge.net/ communication & design info@comlounge.net
At 08:29 PM 4/22/01 +0200, Christian Scholz wrote:
Hi!
A little update..
Actually I was mistaken and SetAttributeFor() is called. Just _objectChanged() is not called in that case.. and thus my attributes are not stored to the database.
When an object is newly added, only _objectAdded() is called. _objectAdded(), _objectChanged(), and _objectDeleted() are mutually exclusive within a given transaction. That is, only one of them occurs in a given transaction. See more below.
Maybe some more code will help (just the important pieces):
def _objectAdded(self,client): """ store cached data in database """
... here we do an sql query to insert things in database ...
def _objectChanged(self,client): """ store cached data in database """
... here we do an sql query to update things in database ...
def _objectAdding(self,client, _tmap={ None:AddedStatus, DeletedStatus:ChangedStatus, ChangedStatus:AddedStatus } ): t = client._getTokenFor(self) s = t.status t.status = _tmap.get(s,s)
# we need to do a commit here in order to store the record into the database! # and we need to have it stored as an AttributeFor() of the primary key will # return None otherwise (as this also just makes a query). client.commitSubtransaction()
This is broken. Don't do this! commitSubtransaction() is an application-level operation. Only. If you have to use it inside of provider-level code or code called from SkinScript, chances are that you are doing something horribly wrong. You run the risk of causing a recursive commit operation in the Zope transaction machinery. Don't do this!
def _SetAttributeFor(self,client,name,value): """Set the attribute and return true if successful""" # attribs stores the attributes we care about if name in self._attribs.keys(): client._getCache()[name]=value client.__dict__[name]=value; client._p_changed = 1 return 1 return None
This also looks broken to me. Why are you changing both the cache and the persistent state? There's no reason to do both.
So _objectChanging() is not overridden, the rest I have ommitted..
So as you also can see I already do a subtransaction commit but it does not seem to help for this problem..
I think you're seriously over-engineering this. I don't see anything that you're doing here that couldn't be done more easily with SkinScript statements. If all you want to do is store the data in an SQL database, a few SkinScript triggers would suffice. Now, if you're trying to get a row to be inserted in an SQL database at the actual time of the newItem() call, there is a trick you can use to do that from SkinScript also. Do this: INITIALIZE OBJECT WITH someAttr1=default1, someAttr2=default2,...,dummyAttributeName=myInsertQuery(primaryKey=self.id) Where myInsertQuery() is an SQL insert query that takes primaryKey as a parameter, and dummyAttributeName is some attribute name you don't care about. You should also set any default values for fields that will be inserted. Your insert query will be called, initializing the row in the database, and then use something like this: WHEN OBJECT ADDED,CHANGED STORE foo,bar,baz USING UpdateMethod(widget_id=self.id, foo_field=self.foo, wbar=self.bar) To update the data at transaction commit time. Voila. No need to make any custom attribute providers just to do this. Just to clarify the event sequence that should take place: 1. newItem() is called, which fires an _objectCreating() event 1.1. The SkinScript INITIALIZE OBJECT statement is triggered by the _objectCreating() event, and calls myInsertQuery() to insert a row in the database, with default values for everything but the primary key. 1.2. newItem() fires an _objectAdding() event, which does nothing except flag the object as "AddedStatus" and pass the event back to the DataManager (Rack). 1.3. The Rack passes the _objectAdding() event to all appropriately registered providers. The WHEN OBJECT ADDED,CHANGED statement is such a provider, so it receives the message and notes that the DataSkin was added, but does not act on it yet. Instead, it registers a proxy for itself with the Zope transaction object so that it will be notified of transaction commit. 2. newItem() returns a new DataSkin to your application, bearing the contents set up by the INITIALIZE OBJECT WITH statement. 3. Your application calls manage_changeProperties, to change an attribute. 3.1. The DataSkin fires an _objectChanging() event, which flags the object as "ChangedStatus", and the event is propagated to all registered providers in the Rack. (Such as the WHEN OBJECT statement) 3.2. The WHEN OBJECT statement ignores the _objectChanging() event, because from its point of view the object is in AddedStatus. 3.3. The DataSkin calls _SetAttributeFor(), which the Rack passes on to the WHEN OBJECT statement. The statement ignores this except to note which attribute was changed for future reference, and to save the new value in the object's attribute cache. 3.4. The DataSkin returns control to the application level code after a successful manage_changeProperties. 4. The application code ends, returning control to Zope and causing a transaction commit. 4.1. Zope notifies registered objects that the transaction is commiting. One of those objects is the WHEN OBJECT statement, which notes that it should act because the object was indeed added during this transaction, *and* one of the specified attributes was changed. So it calls the update query, which replaces the default data in the row in the database with the changed attribute value(s). 4.2. Zope begins the second phase of the commit operation, which causes the SQL database adapter to send a COMMIT TRANSACTION operation to the SQL database, commiting all the work done. Whew. The above is a vastly simplified picture of the whole sequence, leaving out a lot of things such as how a statement can actually be responsible for many dataskins at once, the state machine that agents use to decide which event is considered to have occurred, etc. Anyway... the point of all that was... SkinScript is your friend. :) You can do virtually anything by calling something from SkinScript, so there's almost never a need to go to Python attribute providers. About the only times it makes sense to write a specialized provider or agent is when what you're doing involves access to secured object innards, and you need it to be cleaner than calling an external method (because for example you want to distribute the code as part of a Product). In the early days of ZPatterns, I assumed that I would create SQL providers, LDAP providers, and suchlike gizmos. Later, it became clear that it was more useful to have a simple "glue" language to allow harnessing the full power of Zope in the context of events happening to objects. I still toy with the idea of making an "SQL attribute provider", however, that would be based on the ZSQLMethod object and add some ZPatterns hooks to it, but it's not a big priority. Its main value would be to cut down on some repetitive typing between one's SQL statements and SkinScript statements.
At 08:29 PM 4/22/01 +0200, Christian Scholz wrote:
Hi!
A little update..
Actually I was mistaken and SetAttributeFor() is called. Just _objectChanged() is not called in that case.. and thus my attributes are not stored to the database.
When an object is newly added, only _objectAdded() is called. _objectAdded(), _objectChanged(), and _objectDeleted() are mutually exclusive within a given transaction. That is, only one of them occurs in a given transaction. See more below.
Maybe some more code will help (just the important pieces):
def _objectAdded(self,client): """ store cached data in database """
... here we do an sql query to insert things in database ...
def _objectChanged(self,client): """ store cached data in database """
... here we do an sql query to update things in database ...
def _objectAdding(self,client, _tmap={ None:AddedStatus, DeletedStatus:ChangedStatus, ChangedStatus:AddedStatus } ): t = client._getTokenFor(self) s = t.status t.status = _tmap.get(s,s)
# we need to do a commit here in order to store the record into the database! # and we need to have it stored as an AttributeFor() of the primary key will # return None otherwise (as this also just makes a query). client.commitSubtransaction()
This is broken. Don't do this! commitSubtransaction() is an application-level operation. Only. If you have to use it inside of provider-level code or code called from SkinScript, chances are that you are doing something horribly wrong. You run the risk of causing a recursive commit operation in the Zope transaction machinery. Don't do this!
def _SetAttributeFor(self,client,name,value): """Set the attribute and return true if successful""" # attribs stores the attributes we care about if name in self._attribs.keys(): client._getCache()[name]=value client.__dict__[name]=value; client._p_changed = 1 return 1 return None
This also looks broken to me. Why are you changing both the cache and the persistent state? There's no reason to do both.
So _objectChanging() is not overridden, the rest I have ommitted..
So as you also can see I already do a subtransaction commit but it does not seem to help for this problem..
I think you're seriously over-engineering this. I don't see anything that you're doing here that couldn't be done more easily with SkinScript statements. If all you want to do is store the data in an SQL database, a few SkinScript triggers would suffice. Now, if you're trying to get a row to be inserted in an SQL database at the actual time of the newItem() call, there is a trick you can use to do that from SkinScript also. Do this: INITIALIZE OBJECT WITH someAttr1=default1, someAttr2=default2,...,dummyAttributeName=myInsertQuery(primaryKey=self.id) Where myInsertQuery() is an SQL insert query that takes primaryKey as a parameter, and dummyAttributeName is some attribute name you don't care about. You should also set any default values for fields that will be inserted. Your insert query will be called, initializing the row in the database, and then use something like this: WHEN OBJECT ADDED,CHANGED STORE foo,bar,baz USING UpdateMethod(widget_id=self.id, foo_field=self.foo, wbar=self.bar) To update the data at transaction commit time. Voila. No need to make any custom attribute providers just to do this. Just to clarify the event sequence that should take place: 1. newItem() is called, which fires an _objectCreating() event 1.1. The SkinScript INITIALIZE OBJECT statement is triggered by the _objectCreating() event, and calls myInsertQuery() to insert a row in the database, with default values for everything but the primary key. 1.2. newItem() fires an _objectAdding() event, which does nothing except flag the object as "AddedStatus" and pass the event back to the DataManager (Rack). 1.3. The Rack passes the _objectAdding() event to all appropriately registered providers. The WHEN OBJECT ADDED,CHANGED statement is such a provider, so it receives the message and notes that the DataSkin was added, but does not act on it yet. Instead, it registers a proxy for itself with the Zope transaction object so that it will be notified of transaction commit. 2. newItem() returns a new DataSkin to your application, bearing the contents set up by the INITIALIZE OBJECT WITH statement. 3. Your application calls manage_changeProperties, to change an attribute. 3.1. The DataSkin fires an _objectChanging() event, which flags the object as "ChangedStatus", and the event is propagated to all registered providers in the Rack. (Such as the WHEN OBJECT statement) 3.2. The WHEN OBJECT statement ignores the _objectChanging() event, because from its point of view the object is in AddedStatus. 3.3. The DataSkin calls _SetAttributeFor(), which the Rack passes on to the WHEN OBJECT statement. The statement ignores this except to note which attribute was changed for future reference, and to save the new value in the object's attribute cache. 3.4. The DataSkin returns control to the application level code after a successful manage_changeProperties. 4. The application code ends, returning control to Zope and causing a transaction commit. 4.1. Zope notifies registered objects that the transaction is commiting. One of those objects is the WHEN OBJECT statement, which notes that it should act because the object was indeed added during this transaction, *and* one of the specified attributes was changed. So it calls the update query, which replaces the default data in the row in the database with the changed attribute value(s). 4.2. Zope begins the second phase of the commit operation, which causes the SQL database adapter to send a COMMIT TRANSACTION operation to the SQL database, commiting all the work done. Whew. The above is a vastly simplified picture of the whole sequence, leaving out a lot of things such as how a statement can actually be responsible for many dataskins at once, the state machine that agents use to decide which event is considered to have occurred, etc. Anyway... the point of all that was... SkinScript is your friend. :) You can do virtually anything by calling something from SkinScript, so there's almost never a need to go to Python attribute providers. About the only times it makes sense to write a specialized provider or agent is when what you're doing involves access to secured object innards, and you need it to be cleaner than calling an external method (because for example you want to distribute the code as part of a Product). In the early days of ZPatterns, I assumed that I would create SQL providers, LDAP providers, and suchlike gizmos. Later, it became clear that it was more useful to have a simple "glue" language to allow harnessing the full power of Zope in the context of events happening to objects. I still toy with the idea of making an "SQL attribute provider", however, that would be based on the ZSQLMethod object and add some ZPatterns hooks to it, but it's not a big priority. Its main value would be to cut down on some repetitive typing between one's SQL statements and SkinScript statements.
participants (2)
-
cs@comlounge.net -
Phillip J. Eby