On Sat, Apr 05, 2003 at 04:05:19PM +0200, Steve Alexander wrote:
Will there be a "standard" way of updating existing instances to changes in the python source?
You can use the __setstate__ hook for this.
I know. __setstate__ is what I called "implicit update". But AFAIK objects which are already in memory don't get updated until the next time they get unpickeled. That's why I prefere a explicit update. I called this method refresh() in my python product.
Ok. Can you explain exactly what your refresh() method does?
Example from zope mailinglist: Imaginge you have a class Foo, and some instances are already in the ZODB. Now you want to add a birthday. class Foo: def __init__(self): self.birthday=time.time() ==> Only new created objects get a birthday. Old ones don't have this attribute. You could do this: #Example (1) class Foo: birthday=0 But sometimes it is better to have a method which "refreshes" exiting objects to changes in the python source. #Example (2) class Foo: def refresh(self): if not hasattr(self, "birthday"): self.birthday=0 Then your application can have a method like refreshAllObjects(), which calls refresh on all objects. I know this does not scale if you have several thousand objects already in the ZODB, but it works for my solution. This example could be solved with the code from example 1, but sometimes you have more complex update logic which, I think, needs to be done in a method like in example 2. Regards, Thomas -- Thomas Guettler <guettli@thomas-guettler.de> http://www.thomas-guettler.de
Thomas Guettler wrote:
I know. __setstate__ is what I called "implicit update". But AFAIK objects which are already in memory don't get updated until the next time they get unpickeled. That's why I prefere a explicit update. I called this method refresh() in my python product.
I agree, it's a good way to do it. I do think a name more unique then refresh is good though, because then you can simply traverse the hierarchy and call the upgrade method if it exists. I also make it a habit of letting any upgrade method set a version attribute on the object when it upgrades. It should also return something useful, like information of if it actually did an upgrade or not. Like this: security.declarePrivate('Upgrade Objets', 'UpgradeObjectVersion') def UpgradeObjectVersion(self): """Upgrades the object to new versions""" # Check if upgrade is necessary: if hasattr(self, '_object_version') and \ self._object_version == '1.1': return 0 #No upgrade # Upgrade if not hasattr(self, "birthday"): self.birthday=0 # Set version and return success self._object_version = '1.1' return 1 #Yup, upgraded [Although in this specific case, I would not check for the _object_version to determine if it should be upgraded, I would check for the birthday attribute. Ah, well, details... :) ].
Lennart Regebro wrote:
I agree, it's a good way to do it. I do think a name more unique then refresh is good though, because then you can simply traverse the hierarchy and call the upgrade method if it exists.
It would be nice to do this in a more interactive way. Like a small updater product (in Z2) wich could work like the following ascii art: ############################## meta_type(s): [___________] Python Script: ---------------------------------- if hasattr(obj, 'birthday'): return 'Not updated: %s' % obj.title_or_id() else: obj.birthday = 0 return 'Updated: %s' % obj.title_or_id() ---------------------------------- [ Save ] [ Execute ] [ Undo ] ############################## regards Max M
On Mon, Apr 07, 2003 at 10:28:24AM +0200, Max M wrote:
Lennart Regebro wrote:
I agree, it's a good way to do it. I do think a name more unique then refresh is good though, because then you can simply traverse the hierarchy and call the upgrade method if it exists.
It would be nice to do this in a more interactive way. Like a small updater product (in Z2) wich could work like the following ascii art:
Sorry, I disagree. You might have seveal thousand objects. So interactivity makes no sense. Back to what I wanted: To make it more easy to read other peoples code, I think it would be nice to agree on a common method name for upgrades/refreshes. I like the name "updateObject". I think it is enough to agree on a method name, no need to create an Interface. thomas -- Thomas Guettler <guettli@thomas-guettler.de> http://www.thomas-guettler.de
Thomas Guettler wrote:
Sorry, I disagree. You might have seveal thousand objects. So interactivity makes no sense.
Then you misunderstand what I meant. The python script in my interface would be called on every object of a given metatype automatically. What I am missing is something similar to the way it is don in a sql database. You add a column and every row has the new attribute. We cannot do this in zope, as the objects are in a hierachal structure. So usually you use ZopeFind() to find all objects of a given meta_type and then you do something similar to the small pythonscript example i included. But many who develop Python Products get confused over how to do this in the "canonical" way. So a small class like the one I tried to illustrate, would probably lessen the confusion. regards Max M
On Monday 07 April 2003 11:25 am, Max M wrote:
I am missing is something similar to the way it is don in a sql database. You add a column and every row has the new attribute.
Search the zodb-dev archives. Jim is preparing a tool. -- Toby Dickenson http://www.geminidataloggers.com/people/tdickenson
Toby Dickenson wrote:
On Monday 07 April 2003 11:25 am, Max M wrote:
I am missing is something similar to the way it is don in a sql database. You add a column and every row has the new attribute.
Search the zodb-dev archives. Jim is preparing a tool.
Could you by any chance remember a search-word? I cannot find it in the archives :-( regards Max M
On Monday 07 April 2003 6:06 pm, Max M wrote:
Could you by any chance remember a search-word? I cannot find it in the archives :-(
http://mail.python.org/pipermail/zodb-dev/2003-January/004234.html -- Toby Dickenson http://www.geminidataloggers.com/people/tdickenson
Max M wrote:
It would be nice to do this in a more interactive way. Like a small updater product (in Z2) wich could work like the following ascii art:
Well, what you do it that you have a generic script that does this and it calls the objects update script, and returns information if it was updated or not.
Lennart Regebro wrote:
Max M wrote:
It would be nice to do this in a more interactive way. Like a small updater product (in Z2) wich could work like the following ascii art:
Well, what you do it that you have a generic script that does this and it calls the objects update script, and returns information if it was updated or not.
Yes I know that. It would typically be an external method wich looks like this: def addBirthdayAttribute(self): "migrates" meta_type = 'Some Product' objects = self.ZopeFind(self, obj_metatypes=[meta_type], search_sub=1) for path, obj in objects: if not hasattr(obj, 'birthday'): obj.birthday = 0 But the other approach would give new users a different feel. regards Max M
Max M wrote:
But the other approach would give new users a different feel.
No, it's the same approach. :-) It's the external script/external product/whatever that should give the users the feel. This should not be a part of the products upgrade method, because that would result is uneccesary code duplication.
At 10:01 2003-04-07 +0200, Lennart Regebro said:
Thomas Guettler wrote:
I know. __setstate__ is what I called "implicit update". But AFAIK objects which are already in memory don't get updated until the next time they get unpickeled. That's why I prefere a explicit update. I called this method refresh() in my python product.
Like this:
security.declarePrivate('Upgrade Objets', 'UpgradeObjectVersion') def UpgradeObjectVersion(self): """Upgrades the object to new versions"""
# Check if upgrade is necessary: if hasattr(self, '_object_version') and \ self._object_version == '1.1': return 0 #No upgrade
I think it should use a float instead of a string because it would make it easier to identify the occurrence of an older version than X.X. Integers would work as well, but I have actually change the Easy Publisher upgrade method to use a float, so float is preferred for me ;-) (The integer part of the float represents the main version number and the fraction represents the minor version (I know it will be difficult because of the three version standard if the second or third digit become greater that 9, I have solved it by never letting them be greater that 9). Floats are easier than tuples, I like it easy ;-) Now days I also return a tuple with: (upgrade_needed_flag, message_list) message_list is a list of strings that will be outputted by the Easy Publisher Upgrade Report Engine. When upgrading a object it usually happens that a lot of things needs to be done. For instance upgrading a Catalog you want to upgrade Indexes and Vocabularies and it's allot easier to do from the Catalog object because you need to delete and add object to the catalog. You still want to output what's been done to the log. Best Regards, Johan Carlsson -- Easy Publisher Developers Team Johan Carlsson johanc@easypublisher.com Mail: Birkagatan 9 SE-113 36 Stockholm Sweden Phone +46-(0)8-31 24 94 Fax +46-(0)8-675 04 44 Mobil +46-(0)70-558 25 24 http://www.easypublisher.com
Johan Carlsson [EasyPublisher] wrote:
I think it should use a float instead of a string because it would make it easier to identify the occurrence of an older version than X.X.
Integers would work as well, but I have actually change the Easy Publisher upgrade method to use a float, so float is preferred for me ;-) (The integer part of the float represents the main version number and the fraction represents the minor version (I know it will be difficult because of the three version standard if the second or third digit become greater that 9, I have solved it by never letting them be greater that 9).
Floats are easier than tuples, I like it easy ;-)
None Zope (X)3 related 2c: But wouldn't tuples in python be the ideal structure to store stuff like version numbers?
version_list = [(2,), (1, 1, 2), (1, 1), (1,), (1, 19)] version_list.sort() verlist [(1,), (1, 1), (1, 1, 2), (1, 19), (2,)]
cheers, oliver
At 15:39 2003-04-07 +0200, Oliver Bleutgen said:
Johan Carlsson [EasyPublisher] wrote:
I think it should use a float instead of a string because it would make it easier to identify the occurrence of an older version than X.X. Integers would work as well, but I have actually change the Easy Publisher upgrade method to use a float, so float is preferred for me ;-) (The integer part of the float represents the main version number and the fraction represents the minor version (I know it will be difficult because of the three version standard if the second or third digit become greater that 9, I have solved it by never letting them be greater that 9). Floats are easier than tuples, I like it easy ;-)
None Zope (X)3 related 2c:
But wouldn't tuples in python be the ideal structure to store stuff like version numbers?
version_list = [(2,), (1, 1, 2), (1, 1), (1,), (1, 19)] version_list.sort() verlist [(1,), (1, 1), (1, 1, 2), (1, 19), (2,)]
Aha. Off course! Python is always better then I expect it to be :-) Cheers, Johan Carlsson -- Easy Publisher Developers Team Johan Carlsson johanc@easypublisher.com Mail: Birkagatan 9 SE-113 36 Stockholm Sweden Phone +46-(0)8-31 24 94 Fax +46-(0)8-675 04 44 Mobil +46-(0)70-558 25 24 http://www.easypublisher.com
Johan Carlsson [EasyPublisher] wrote:
I think it should use a float instead of a string because it would make it easier to identify the occurrence of an older version than X.X.
Well, that's a good point. Comparing floats is always a pain though, and while > and < works, == and != is unrealiable, and you instead have to use abs(float - comparefloat) < 0.001 and stuff like that, and for most cases all you want to know is if it's the current version or not. Integers would work nice, though.
At 16:39 2003-04-07 +0200, Lennart Regebro said:
Johan Carlsson [EasyPublisher] wrote:
I think it should use a float instead of a string because it would make it easier to identify the occurrence of an older version than X.X.
Well, that's a good point. Comparing floats is always a pain though, and while > and < works, == and != is unrealiable, and you instead have to use abs(float - comparefloat) < 0.001 and stuff like that, and for most cases all you want to know is if it's the current version or not.
I disagree most cases you want to know if it's never that a specific version. You may want to reuse the same code to upgrade version 1.6 and 1.7 to version 1.8.
Integers would work nice, though.
Yes, integers works nice. But Oliver tuple examle got me going. Tuple of integers would be almost perfect, except for: (2,) != (2,0) They should evaluate equal. Also one interesting aspect would be to allow Version to be constructed from a string, which made me write one. I have whipped up a little VersionNumber class. It supports comparisons, addition and subtraction. It can be initialized from a string or a tuple. Strings can have any syntax, integers in the string will be extracted to form a tuple. Best Regards, Johan Carlsson from types import TupleType, ListType import re intListRE=re.compile(r'\d+') class VersionNumber : def __init__(self, *args): self.digits=self._parse_args(args) def _parse_args(self, args): if len(args)==1: return tuple(map(lambda x: int(x),intListRE.findall(str(args)))) elif type(args) in (TupleType, ListType): new_args=() for arg in args: try: new_args=new_args+(int(arg),) except: pass return new_args else: new_args=() try: new_args=new_args+(int(args),) except: pass return new_args def __repr__(self): return '.'.join(map(lambda x: str(x), self.digits)) def _normalize(self, others): selfs=self.digits if len(selfs)>len(others): others=others+((0,)*(len(selfs)-len(others))) elif len(selfs)<len(others): selfs=selfs+((0,)*(len(others)-len(selfs))) return selfs, others def __cmp__(self, other): if not isinstance(other,self.__class__): raise TypeError selfs, others=self._normalize(other.digits) return cmp(selfs,others) def __add__(self, other): if isinstance(other,self.__class__): others=other.digits else: others=self._parse_args(args) selfs, others=self._normalize(others) return tuple(map(lambda x,y: x+y, selfs, others)) def __sub__(self, other): if isinstance(other,self.__class__): others=other.digits else: others=self._parse_args(args) selfs, others=self._normalize(others) return tuple(map(lambda x,y: x-y, selfs, others)) #Tests def compare(e1,e2, value): result=e1>e2 if result != value: print "%s > %s\t\t" %(e1,e2),value,result def is_equal(e1,e2, value): result=e1==e2 if result != value: print "%s == %s\t\t" %(e1,e2),value,result def add(e1,e2, value): print "%s + %s\t\t" %(e1,e2),e1+e2 def sub(e1,e2, value): print "%s - %s\t\t" %(e1,e2),e1-e2 def test(): ep16=VersionNumber(1,6) ep161=VersionNumber(1,6,1) ep1_6_123=VersionNumber(1,6,123) ep163=VersionNumber(1,6,3) ep20=VersionNumber(2,0) ep2=VersionNumber(2) print compare(ep16,ep161, 0) compare(ep16,ep1_6_123, 0) compare(ep16,ep16, 0) compare(ep16,ep20, 0) compare(ep16,ep163, 0) compare(ep16,ep2, 0) compare(ep2,ep20, 0) compare(ep20,ep161, 1) print print "="*25 compare(ep163,ep161, 1) compare(ep163,ep1_6_123, 0) compare(ep163,ep16, 1) compare(ep163,ep20, 0) compare(ep163,ep163, 0) compare(ep163,ep2, 0) compare(ep163,ep20, 0) compare(ep163,ep161, 1) print print "="*25 is_equal(ep163,ep161, 0) is_equal(ep163,ep1_6_123, 0) is_equal(ep163,ep16, 0) is_equal(ep163,ep20, 0) is_equal(ep163,ep163, 1) is_equal(ep163,ep2, 0) is_equal(ep163,ep20, 0) is_equal(ep163,ep161, 0) print "-"*25 add(ep163,ep161, 0) add(ep163,ep1_6_123, 0) add(ep163,ep16, 0) add(ep163,ep20, 0) add(ep163,ep163, 1) add(ep163,ep2, 0) add(ep163,ep20, 0) add(ep163,ep161, 0) print "-"*25 sub(ep163,ep161, 0) sub(ep163,ep1_6_123, 0) sub(ep163,ep16, 0) sub(ep163,ep20, 0) sub(ep163,ep163, 1) sub(ep163,ep2, 0) sub(ep163,ep20, 0) sub(ep163,ep161, 0) print "-"*25 compare(VersionNumber("1,6,3"),VersionNumber("1-6-1"), 1) compare(VersionNumber("1,6,3"),VersionNumber("1,6,123"), 0) compare(VersionNumber("1-6-3"),VersionNumber("1.6"), 1) compare(VersionNumber("1,6,3"),VersionNumber("1.6.,3"), 0) compare(VersionNumber("1,6,3"),VersionNumber("2"), 0) compare(VersionNumber("1,6,3"),VersionNumber("2,0"), 0) compare(VersionNumber("2"),VersionNumber("2-2"), 0) compare(VersionNumber("ver-2-0"),VersionNumber("1,6,1"), 1) if __name__ == '__main__': test() -- Easy Publisher Developers Team Johan Carlsson johanc@easypublisher.com Mail: Birkagatan 9 SE-113 36 Stockholm Sweden Phone +46-(0)8-31 24 94 Fax +46-(0)8-675 04 44 Mobil +46-(0)70-558 25 24 http://www.easypublisher.com
participants (6)
-
Johan Carlsson [EasyPublisher] -
Lennart Regebro -
Max M -
Oliver Bleutgen -
Thomas Guettler -
Toby Dickenson