Changelog enhancements

John Wright john at movingsucks.org
Wed Nov 22 07:14:06 CET 2006


Hi James,

I've been hacking on changelog.py, and I've got a couple changes you
might be interested in.

First, I thought it'd be a good thing, since a bunch of methods in the
Changelog class are just getters/setters, to expose those methods as
attributes instead, using Python "properties".  Now, instead of

    v = cl.version() # get the version
    cl.set_version(Version('1.3.5-2'))

you can do something like

    v = cl.version
    cl.version = '1.3.5-2'

(Note, I've also made the version setter create a Version instance from
its argument, and I've modified Version to be able to accept another
Version instance or any object implementing __str__ to initialize.)

While I was playing with this, I realized that it might be confusing for
people that Version instances are mutable, but that changing any of the
attributes doesn't update other affected ones.  (For example, if I
change v.debian_version, v.full_version doesn't include the new debian
revision.)

I guess there are a couple good ways to handle this.  The one I picked
was to expose the attributes through properties which only implement a
get method, effectively making the objects immutable.  Another way, of
course, would be to add more logic to the class, perhaps by storing each
part of the version separately, and having the full_version attribute
actually call a method that reconstructs the version.

Or, you could leave it alone, since Python is a language for "consenting
adults". :)


I also added an __eq__ method to the Version class.  It might be cool in
the future to have a full __cmp__ method, perhaps like the one in
debian_support.py, or perhaps to use apt_pkg.VersionCompare if apt_pkg
is available.


I've attached a python bundle of my changes to the trunk.  The branch
also exists at
    http://alioth.debian.org/~jsw-guest/python-debian/

I hope this is helpful!  Of course I'll understand if you don't think
it's worth including, since it's not really much of a feature
enhancement. :)

Thanks,
John
-------------- next part --------------
# Bazaar revision bundle v0.8
#
# message:
#   Update the test suite
# committer: John Wright <john at movingsucks.org>
# date: Tue 2006-11-21 22:44:17.923000097 -0700

=== modified file debian_bundle/changelog.py
--- debian_bundle/changelog.py
+++ debian_bundle/changelog.py
@@ -51,20 +51,31 @@
 
   def __init__(self, version):
     
-    self.full_version = version
-    p = re.compile(r'^(?:(\d+):)?([A-Za-z0-9.+:~-]+?)'
-                   + r'(?:-([A-Za-z0-9.~+]+))?$')
-    m = p.match(version)
+    p = re.compile(r'^(?:(?P<epoch>\d+):)?(?P<upstream>[A-Za-z0-9.+:~-]+?)'
+                   + r'(?:-(?P<debian>[A-Za-z0-9.~+]+))?$')
+    m = p.match(str(version))
     if m is None:
       raise VersionError(version)
-    (epoch, upstream, debian) = m.groups()
-    self.epoch = epoch
-    self.upstream_version = upstream
-    self.debian_version = debian
+    self.__attrs = m.groupdict()
+    self.__attrs['full'] = str(version)
+
+  # Expose some attributes, read-only (so it's not possible to accidentally
+  # overrite, say, debian_version without really changing the version)
+
+  # FIXME: Would it be better to make Version objects mutable, but have smart
+  # methods that know how to construct a version from these three attributes?
+  
+  full_version = property(lambda self: self.__attrs['full'])
+  epoch = property(lambda self: self.__attrs['epoch'])
+  upstream_version = property(lambda self: self.__attrs['upstream'])
+  debian_version = property(lambda self: self.__attrs['debian'])
 
   def __str__(self):
     return self.full_version
 
+  def __eq__(self, other):
+    return self.full_version == other.full_version
+
 class ChangeBlock(object):
   """Holds all the information about one block from the changelog."""
 
@@ -213,61 +224,70 @@
       #TODO: shouldn't be required should it?
       self._blocks[-1].del_trailing_newline()
 
-  def full_version(self):
-    """Returns the full version number of the last version."""
-    return self._blocks[0].version.full_version
-
-  def debian_version(self):
-    """Returns the debian part of the version number of the last version. 
-    Will be None if it is a native package"""
-    return self._blocks[0].version.debian_version
-
-  def upstream_version(self):
-    """Returns the upstream part of the version number of the last version"""
-    return self._blocks[0].version.upstream_version
-
-  def epoch(self):
-    """Returns the epoch number of the last revision, or None if no epoch was
-    used"""
-    return self._blocks[0].version.epoch
-
-  def package(self):
+  def get_version(self):
+    """Return a Version object for the last version"""
+    return self._blocks[0].version
+
+  def set_version(self, version):
+    """Set the version of the last changelog block
+
+    version can be a full version string, or a Version object
+    """
+    self._blocks[0].version = Version(version)
+
+  version = property(get_version, set_version,
+                     doc="Version object for last changelog block""")
+
+  ### For convenience, let's expose some of the version properties
+  full_version = property(lambda self: self.version.full_version)
+  debian_version = property(lambda self: self.version.debian_version)
+  upstream_version = property(lambda self: self.version.upstream_version)
+
+  def get_package(self):
     """Returns the name of the package in the last version."""
     return self._blocks[0].package
-
-  def versions(self):
+  
+  def set_package(self, package):
+    self._blocks[0].package = package
+
+  package = property(get_package, set_package,
+                     doc="Name of the package in the last version")
+
+  def get_versions(self):
     """Returns a list of version objects that the package went through."""
     versions = []
     for block in self._blocks:
       versions.append[block.version]
     return versions
 
+  versions = property(get_versions,
+                      doc="List of version objects the package went through")
+
   def __str__(self):
     cl = ""
     for block in self._blocks:
       cl += str(block)
     return cl
 
-  def set_package(self, package):
-    self._blocks[0].package = package
-
-  def set_version(self, version):
-    self._blocks[0].version = version
-
   def set_distributions(self, distributions):
     self._blocks[0].distributions = distributions
+  distributions = property(lambda self: self._blocks[0].distributions,
+                           set_distributions)
 
   def set_urgency(self, urgency):
     self._blocks[0].urgency = urgency
+  urgency = property(lambda self: self._blocks[0].urgency, set_urgency)
 
   def add_change(self, change):
     self._blocks[0].add_change(change)
 
   def set_author(self, author):
     self._blocks[0].author = author
+  author = property(lambda self: self._blocks[0].author, set_author)
 
   def set_date(self, date):
     self._blocks[0].date = date
+  date = property(lambda self: self._blocks[0].date, set_date)
 
   def new_block(self, package=None, version=None, distributions=None,
                 urgency=None, changes=None, author=None, date=None):
@@ -299,13 +319,13 @@
 
     c = open('test_modify_changelog1').read()
     cl = Changelog(c)
-    cl.set_package('gnutls14')
-    cl.set_version(Version('1:1.4.1-2'))
-    cl.set_distributions('experimental')
-    cl.set_urgency('medium')
+    cl.package = 'gnutls14'
+    cl.version = '1:1.4.1-2'
+    cl.distributions = 'experimental'
+    cl.urgency = 'medium'
     cl.add_change('  * Add magic foo')
-    cl.set_author('James Westby <jw+debian at jameswestby.net>')
-    cl.set_date('Sat, 16 Jul 2008 11:11:08 -0200')
+    cl.author = 'James Westby <jw+debian at jameswestby.net>'
+    cl.date = 'Sat, 16 Jul 2008 11:11:08 -0200'
     c = open('test_modify_changelog2').read()
     clines = c.split('\n')
     cslines = str(cl).split('\n')
@@ -333,6 +353,16 @@
       self.assertEqual(clines[i], cslines[i])
     self.assertEqual(len(clines), len(cslines), "Different lengths")
 
+  def test_set_version_with_string(self):
+    c1 = Changelog(open('test_modify_changelog1').read())
+    c2 = Changelog(open('test_modify_changelog1').read())
+
+    c1.version = '1:2.3.5-2'
+    c2.version = Version('1:2.3.5-2')
+    self.assertEqual(c1.version, c2.version)
+    self.assertEqual((c1.full_version, c1.upstream_version, c1.debian_version),
+                     (c2.full_version, c2.upstream_version, c2.debian_version))
+
 class VersionTests(unittest.TestCase):
 
   def _test_version(self, full_version, epoch, upstream, debian):
@@ -362,6 +392,11 @@
     self._test_version('1.8RC4b', None, '1.8RC4b', None)
     self._test_version('0.9~rc1-1', None, '0.9~rc1', '1')
 
+  def test_equality(self):
+    v1 = Version('1:2.3.4-2')
+    v2 = Version('1:2.3.4-2')
+    self.assertEqual(v1, v2)
+
 
 if __name__ == "__main__":
   _test()

# revision id: john at movingsucks.org-20061122054417-2653ad3643f129e6
# sha1: 72bcbd627a6819d465300f977f438202e77acb3d
# inventory sha1: a659915a758c2e28dc834da7241691e144ffd3c7
# parent ids:
#   john at movingsucks.org-20061122052350-5253bb76e196f1dd
# base id: enrico at enricozini.org-20061120213648-cb925575bce6b414
# properties:
#   branch-nick: trunk

# message:
#   * Make Version essentially immutable, to avoid mistakes of, e.g., setting the
#     debian_version attribute but not having it be reflected in the string
#     representation
#   * Expose lots of simple getter-setter methods in Changelog as attributes, using
#     property
#   
# committer: John Wright <john at movingsucks.org>
# date: Tue 2006-11-21 22:23:50.482000113 -0700

=== modified file debian_bundle/changelog.py // encoding:base64
LS0tIGRlYmlhbl9idW5kbGUvY2hhbmdlbG9nLnB5CisrKyBkZWJpYW5fYnVuZGxlL2NoYW5nZWxv
Zy5weQpAQCAtNTEsMTYgKzUxLDI0IEBACiAKICAgZGVmIF9faW5pdF9fKHNlbGYsIHZlcnNpb24p
OgogICAgIAotICAgIHNlbGYuZnVsbF92ZXJzaW9uID0gdmVyc2lvbgotICAgIHAgPSByZS5jb21w
aWxlKHInXig/OihcZCspOik/KFtBLVphLXowLTkuKzp+LV0rPyknCi0gICAgICAgICAgICAgICAg
ICAgKyByJyg/Oi0oW0EtWmEtejAtOS5+K10rKSk/JCcpCi0gICAgbSA9IHAubWF0Y2godmVyc2lv
bikKKyAgICBwID0gcmUuY29tcGlsZShyJ14oPzooP1A8ZXBvY2g+XGQrKTopPyg/UDx1cHN0cmVh
bT5bQS1aYS16MC05Lis6fi1dKz8pJworICAgICAgICAgICAgICAgICAgICsgcicoPzotKD9QPGRl
Ymlhbj5bQS1aYS16MC05Ln4rXSspKT8kJykKKyAgICBtID0gcC5tYXRjaChzdHIodmVyc2lvbikp
CiAgICAgaWYgbSBpcyBOb25lOgogICAgICAgcmFpc2UgVmVyc2lvbkVycm9yKHZlcnNpb24pCi0g
ICAgKGVwb2NoLCB1cHN0cmVhbSwgZGViaWFuKSA9IG0uZ3JvdXBzKCkKLSAgICBzZWxmLmVwb2No
ID0gZXBvY2gKLSAgICBzZWxmLnVwc3RyZWFtX3ZlcnNpb24gPSB1cHN0cmVhbQotICAgIHNlbGYu
ZGViaWFuX3ZlcnNpb24gPSBkZWJpYW4KKyAgICBzZWxmLl9fYXR0cnMgPSBtLmdyb3VwZGljdCgp
CisgICAgc2VsZi5fX2F0dHJzWydmdWxsJ10gPSBzdHIodmVyc2lvbikKKworICAjIEV4cG9zZSBz
b21lIGF0dHJpYnV0ZXMsIHJlYWQtb25seSAoc28gaXQncyBub3QgcG9zc2libGUgdG8gYWNjaWRl
bnRhbGx5CisgICMgb3ZlcnJpdGUsIHNheSwgZGViaWFuX3ZlcnNpb24gd2l0aG91dCByZWFsbHkg
Y2hhbmdpbmcgdGhlIHZlcnNpb24pCisKKyAgIyBGSVhNRTogV291bGQgaXQgYmUgYmV0dGVyIHRv
IG1ha2UgVmVyc2lvbiBvYmplY3RzIG11dGFibGUsIGJ1dCBoYXZlIHNtYXJ0CisgICMgbWV0aG9k
cyB0aGF0IGtub3cgaG93IHRvIGNvbnN0cnVjdCBhIHZlcnNpb24gZnJvbSB0aGVzZSB0aHJlZSBh
dHRyaWJ1dGVzPworICAKKyAgZnVsbF92ZXJzaW9uID0gcHJvcGVydHkobGFtYmRhIHNlbGY6IHNl
bGYuX19hdHRyc1snZnVsbCddKQorICBlcG9jaCA9IHByb3BlcnR5KGxhbWJkYSBzZWxmOiBzZWxm
Ll9fYXR0cnNbJ2Vwb2NoJ10pCisgIHVwc3RyZWFtX3ZlcnNpb24gPSBwcm9wZXJ0eShsYW1iZGEg
c2VsZjogc2VsZi5fX2F0dHJzWyd1cHN0cmVhbSddKQorICBkZWJpYW5fdmVyc2lvbiA9IHByb3Bl
cnR5KGxhbWJkYSBzZWxmOiBzZWxmLl9fYXR0cnNbJ2RlYmlhbiddKQogCiAgIGRlZiBfX3N0cl9f
KHNlbGYpOgogICAgIHJldHVybiBzZWxmLmZ1bGxfdmVyc2lvbgpAQCAtMjEzLDYxICsyMjEsNzAg
QEAKICAgICAgICNUT0RPOiBzaG91bGRuJ3QgYmUgcmVxdWlyZWQgc2hvdWxkIGl0PwogICAgICAg
c2VsZi5fYmxvY2tzWy0xXS5kZWxfdHJhaWxpbmdfbmV3bGluZSgpCiAKLSAgZGVmIGZ1bGxfdmVy
c2lvbihzZWxmKToKLSAgICAiIiJSZXR1cm5zIHRoZSBmdWxsIHZlcnNpb24gbnVtYmVyIG9mIHRo
ZSBsYXN0IHZlcnNpb24uIiIiCi0gICAgcmV0dXJuIHNlbGYuX2Jsb2Nrc1swXS52ZXJzaW9uLmZ1
bGxfdmVyc2lvbgotCi0gIGRlZiBkZWJpYW5fdmVyc2lvbihzZWxmKToKLSAgICAiIiJSZXR1cm5z
IHRoZSBkZWJpYW4gcGFydCBvZiB0aGUgdmVyc2lvbiBudW1iZXIgb2YgdGhlIGxhc3QgdmVyc2lv
bi4gCi0gICAgV2lsbCBiZSBOb25lIGlmIGl0IGlzIGEgbmF0aXZlIHBhY2thZ2UiIiIKLSAgICBy
ZXR1cm4gc2VsZi5fYmxvY2tzWzBdLnZlcnNpb24uZGViaWFuX3ZlcnNpb24KLQotICBkZWYgdXBz
dHJlYW1fdmVyc2lvbihzZWxmKToKLSAgICAiIiJSZXR1cm5zIHRoZSB1cHN0cmVhbSBwYXJ0IG9m
IHRoZSB2ZXJzaW9uIG51bWJlciBvZiB0aGUgbGFzdCB2ZXJzaW9uIiIiCi0gICAgcmV0dXJuIHNl
bGYuX2Jsb2Nrc1swXS52ZXJzaW9uLnVwc3RyZWFtX3ZlcnNpb24KLQotICBkZWYgZXBvY2goc2Vs
Zik6Ci0gICAgIiIiUmV0dXJucyB0aGUgZXBvY2ggbnVtYmVyIG9mIHRoZSBsYXN0IHJldmlzaW9u
LCBvciBOb25lIGlmIG5vIGVwb2NoIHdhcwotICAgIHVzZWQiIiIKLSAgICByZXR1cm4gc2VsZi5f
YmxvY2tzWzBdLnZlcnNpb24uZXBvY2gKLQotICBkZWYgcGFja2FnZShzZWxmKToKKyAgZGVmIGdl
dF92ZXJzaW9uKHNlbGYpOgorICAgICIiIlJldHVybiBhIFZlcnNpb24gb2JqZWN0IGZvciB0aGUg
bGFzdCB2ZXJzaW9uIiIiCisgICAgcmV0dXJuIHNlbGYuX2Jsb2Nrc1swXS52ZXJzaW9uCisKKyAg
ZGVmIHNldF92ZXJzaW9uKHNlbGYsIHZlcnNpb24pOgorICAgICIiIlNldCB0aGUgdmVyc2lvbiBv
ZiB0aGUgbGFzdCBjaGFuZ2Vsb2cgYmxvY2sKKworICAgIHZlcnNpb24gY2FuIGJlIGEgZnVsbCB2
ZXJzaW9uIHN0cmluZywgb3IgYSBWZXJzaW9uIG9iamVjdAorICAgICIiIgorICAgIHNlbGYuX2Js
b2Nrc1swXS52ZXJzaW9uID0gVmVyc2lvbih2ZXJzaW9uKQorCisgIHZlcnNpb24gPSBwcm9wZXJ0
eShnZXRfdmVyc2lvbiwgc2V0X3ZlcnNpb24sCisgICAgICAgICAgICAgICAgICAgICBkb2M9IlZl
cnNpb24gb2JqZWN0IGZvciBsYXN0IGNoYW5nZWxvZyBibG9jayIiIikKKworICAjIyMgRm9yIGNv
bnZlbmllbmNlLCBsZXQncyBleHBvc2Ugc29tZSBvZiB0aGUgdmVyc2lvbiBwcm9wZXJ0aWVzCisg
IGZ1bGxfdmVyc2lvbiA9IHByb3BlcnR5KGxhbWJkYSBzZWxmOiBzZWxmLnZlcnNpb24uZnVsbF92
ZXJzaW9uKQorICBkZWJpYW5fdmVyc2lvbiA9IHByb3BlcnR5KGxhbWJkYSBzZWxmOiBzZWxmLnZl
cnNpb24uZGViaWFuX3ZlcnNpb24pCisgIHVwc3RyZWFtX3ZlcnNpb24gPSBwcm9wZXJ0eShsYW1i
ZGEgc2VsZjogc2VsZi52ZXJzaW9uLnVwc3RyZWFtX3ZlcnNpb24pCisKKyAgZGVmIGdldF9wYWNr
YWdlKHNlbGYpOgogICAgICIiIlJldHVybnMgdGhlIG5hbWUgb2YgdGhlIHBhY2thZ2UgaW4gdGhl
IGxhc3QgdmVyc2lvbi4iIiIKICAgICByZXR1cm4gc2VsZi5fYmxvY2tzWzBdLnBhY2thZ2UKLQot
ICBkZWYgdmVyc2lvbnMoc2VsZik6CisgIAorICBkZWYgc2V0X3BhY2thZ2Uoc2VsZiwgcGFja2Fn
ZSk6CisgICAgc2VsZi5fYmxvY2tzWzBdLnBhY2thZ2UgPSBwYWNrYWdlCisKKyAgcGFja2FnZSA9
IHByb3BlcnR5KGdldF9wYWNrYWdlLCBzZXRfcGFja2FnZSwKKyAgICAgICAgICAgICAgICAgICAg
IGRvYz0iTmFtZSBvZiB0aGUgcGFja2FnZSBpbiB0aGUgbGFzdCB2ZXJzaW9uIikKKworICBkZWYg
Z2V0X3ZlcnNpb25zKHNlbGYpOgogICAgICIiIlJldHVybnMgYSBsaXN0IG9mIHZlcnNpb24gb2Jq
ZWN0cyB0aGF0IHRoZSBwYWNrYWdlIHdlbnQgdGhyb3VnaC4iIiIKICAgICB2ZXJzaW9ucyA9IFtd
CiAgICAgZm9yIGJsb2NrIGluIHNlbGYuX2Jsb2NrczoKICAgICAgIHZlcnNpb25zLmFwcGVuZFti
bG9jay52ZXJzaW9uXQogICAgIHJldHVybiB2ZXJzaW9ucwogCisgIHZlcnNpb25zID0gcHJvcGVy
dHkoZ2V0X3ZlcnNpb25zLAorICAgICAgICAgICAgICAgICAgICAgIGRvYz0iTGlzdCBvZiB2ZXJz
aW9uIG9iamVjdHMgdGhlIHBhY2thZ2Ugd2VudCB0aHJvdWdoIikKKwogICBkZWYgX19zdHJfXyhz
ZWxmKToKICAgICBjbCA9ICIiCiAgICAgZm9yIGJsb2NrIGluIHNlbGYuX2Jsb2NrczoKICAgICAg
IGNsICs9IHN0cihibG9jaykKICAgICByZXR1cm4gY2wKIAotICBkZWYgc2V0X3BhY2thZ2Uoc2Vs
ZiwgcGFja2FnZSk6Ci0gICAgc2VsZi5fYmxvY2tzWzBdLnBhY2thZ2UgPSBwYWNrYWdlCi0KLSAg
ZGVmIHNldF92ZXJzaW9uKHNlbGYsIHZlcnNpb24pOgotICAgIHNlbGYuX2Jsb2Nrc1swXS52ZXJz
aW9uID0gdmVyc2lvbgotCiAgIGRlZiBzZXRfZGlzdHJpYnV0aW9ucyhzZWxmLCBkaXN0cmlidXRp
b25zKToKICAgICBzZWxmLl9ibG9ja3NbMF0uZGlzdHJpYnV0aW9ucyA9IGRpc3RyaWJ1dGlvbnMK
KyAgZGlzdHJpYnV0aW9ucyA9IHByb3BlcnR5KGxhbWJkYSBzZWxmOiBzZWxmLl9ibG9ja3NbMF0u
ZGlzdHJpYnV0aW9ucywKKyAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldF9kaXN0cmlidXRp
b25zKQogCiAgIGRlZiBzZXRfdXJnZW5jeShzZWxmLCB1cmdlbmN5KToKICAgICBzZWxmLl9ibG9j
a3NbMF0udXJnZW5jeSA9IHVyZ2VuY3kKKyAgdXJnZW5jeSA9IHByb3BlcnR5KGxhbWJkYSBzZWxm
OiBzZWxmLl9ibG9ja3NbMF0udXJnZW5jeSwgc2V0X3VyZ2VuY3kpCiAKICAgZGVmIGFkZF9jaGFu
Z2Uoc2VsZiwgY2hhbmdlKToKICAgICBzZWxmLl9ibG9ja3NbMF0uYWRkX2NoYW5nZShjaGFuZ2Up
CiAKICAgZGVmIHNldF9hdXRob3Ioc2VsZiwgYXV0aG9yKToKICAgICBzZWxmLl9ibG9ja3NbMF0u
YXV0aG9yID0gYXV0aG9yCisgIGF1dGhvciA9IHByb3BlcnR5KGxhbWJkYSBzZWxmOiBzZWxmLl9i
bG9ja3NbMF0uYXV0aG9yLCBzZXRfYXV0aG9yKQogCiAgIGRlZiBzZXRfZGF0ZShzZWxmLCBkYXRl
KToKICAgICBzZWxmLl9ibG9ja3NbMF0uZGF0ZSA9IGRhdGUKKyAgZGF0ZSA9IHByb3BlcnR5KGxh
bWJkYSBzZWxmOiBzZWxmLl9ibG9ja3NbMF0uZGF0ZSwgc2V0X2RhdGUpCiAKICAgZGVmIG5ld19i
bG9jayhzZWxmLCBwYWNrYWdlPU5vbmUsIHZlcnNpb249Tm9uZSwgZGlzdHJpYnV0aW9ucz1Ob25l
LAogICAgICAgICAgICAgICAgIHVyZ2VuY3k9Tm9uZSwgY2hhbmdlcz1Ob25lLCBhdXRob3I9Tm9u
ZSwgZGF0ZT1Ob25lKToKCg==

# revision id: john at movingsucks.org-20061122052350-5253bb76e196f1dd
# sha1: bfe249f1f9855c395aaaa0b9e86934c9c866413c
# inventory sha1: cd461b20df5ec225a8f5483bfca16526c80391ad
# parent ids:
#   enrico at enricozini.org-20061120213648-cb925575bce6b414
# properties:
#   branch-nick: trunk



More information about the pkg-python-debian-discuss mailing list