Merge lp:~ev/apt-clone/python3 into lp:apt-clone

Proposed by Evan
Status: Merged
Merged at revision: 100
Proposed branch: lp:~ev/apt-clone/python3
Merge into: lp:apt-clone
Diff against target: 485 lines (+119/-83)
8 files modified
apt-clone (+10/-8)
apt_clone.py (+46/-40)
debian/changelog (+18/-1)
tests/Makefile (+3/-1)
tests/test_clone.py (+19/-11)
tests/test_clone_upgrade.py (+15/-16)
tests/test_in_chroot.py (+5/-3)
tests/test_merge_sources.py (+3/-3)
To merge this branch: bzr merge lp:~ev/apt-clone/python3
Reviewer Review Type Date Requested Status
Colin Watson Approve
Review via email: mp+109672@code.launchpad.net

Description of the change

Port to Python3.

To post a comment you must log in.
lp:~ev/apt-clone/python3 updated
109. By Evan

* Finish port to Python 3:
  - Handle unicode changes.
  - Do not leak file descriptors.
  - Test against both Python2.7 and Python3.
  - Do not use the deprecated failUnless.
  - Use the io module instead of StringIO.
  - Fix a failing test caused by acpi-support being in main.

110. By Evan

Actually use python3 :)

Revision history for this message
Colin Watson (cjwatson) wrote :

> === modified file 'apt_clone.py'
> --- apt_clone.py 2012-01-27 17:14:05 +0000
> +++ apt_clone.py 2012-06-11 15:50:47 +0000
> @@ -182,7 +184,8 @@
> tarinfo = tarfile.TarInfo("./var/lib/apt-clone/installed.pkgs")
> tarinfo.size = len(s)
> tarinfo.mtime = time.time()
> - tar.addfile(tarinfo, StringIO(s))
> + s = s.encode('utf-8')
> + tar.addfile(tarinfo, BytesIO(s))

tarinfo.size will be wrong if s isn't entirely ASCII. I think you need
to encode earlier.

> - #print tar.getnames()
> + print(tar.getnames())

Did you mean to uncomment this?

> === modified file 'debian/changelog'
> --- debian/changelog 2012-01-27 16:54:20 +0000
> +++ debian/changelog 2012-06-11 15:50:47 +0000
> @@ -8,6 +8,16 @@
>
> -- Michael Vogt <email address hidden> Fri, 27 Jan 2012 16:29:17 +0100
>
> +apt-clone (0.2.2ubuntu1) UNRELEASED; urgency=low
> +
> + * Port to Python 3:
> + - Use Python 3-style print functions.
> + - Use "raise Exception(value)" syntax rather than the old-style "raise
> + Exception, value".
> + - Use dict.items() rather than dict.iteritems().
> +
> + -- Colin Watson <email address hidden> Mon, 11 Jun 2012 09:12:14 +0100
> +
> apt-clone (0.2.2) unstable; urgency=low
>
> * fix extraction of no-longer downloadable debs, thanks
>

This might need updating :-)

The rest looks good to me.

 review needs-fixing

review: Needs Fixing
lp:~ev/apt-clone/python3 updated
111. By Evan

Encode earlier so tarinfo.size is accurate. Thanks Colin Watson!

112. By Evan

Re-comment debugging print statement.

113. By Evan

Fix changelog.

Revision history for this message
Evan (ev) wrote :

I've addressed the issues Colin raises as r111, r112, and r113.

Revision history for this message
Colin Watson (cjwatson) wrote :

Thanks, looks good to me now. I think this is Michael's upstream branch so he may want to flip the version back to 0.2.3 for upload to Debian.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'apt-clone'
2--- apt-clone 2012-01-27 23:47:38 +0000
3+++ apt-clone 2012-06-12 09:23:17 +0000
4@@ -1,4 +1,4 @@
5-#!/usr/bin/python
6+#!/usr/bin/python3
7 # Copyright (C) 2011 Canonical
8 #
9 # Authors:
10@@ -17,6 +17,8 @@
11 # this program; if not, write to the Free Software Foundation, Inc.,
12 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
13
14+from __future__ import print_function
15+
16 import argparse
17 from apt_clone import AptClone
18
19@@ -79,19 +81,19 @@
20 clone = AptClone()
21 if args.command == "info":
22 info = clone.info(args.source)
23- print info
24+ print(info)
25 if args.command == "clone":
26 clone.save_state(args.source, args.destination,
27 args.with_dpkg_repack, args.with_dpkg_status)
28- print "not installable: %s" % ", ".join(clone.not_downloadable)
29- print "version mismatch: %s" % ", ".join(clone.version_mismatch)
30+ print("not installable: %s" % ", ".join(clone.not_downloadable))
31+ print("version mismatch: %s" % ", ".join(clone.version_mismatch))
32 if not args.with_dpkg_repack:
33- print "\nNote that you can use --with-dpkg-repack to include "\
34- "those packages in the clone file."
35+ print("\nNote that you can use --with-dpkg-repack to include "
36+ "those packges in the clone file.")
37 elif args.command == "restore":
38 if args.simulate:
39 miss = clone.simulate_restore_state(args.source)
40- print "missing: %s" % ",".join(sorted(list(miss)))
41+ print("missing: %s" % ",".join(sorted(list(miss))))
42 else:
43 clone.restore_state(args.source, args.destination)
44 elif args.command == "restore-new-distro":
45@@ -111,7 +113,7 @@
46 if args.simulate:
47 miss = clone.simulate_restore_state(
48 args.source, args.new_distro_codename)
49- print "missing: %s" % ",".join(sorted(list(miss)))
50+ print("missing: %s" % ",".join(sorted(list(miss))))
51 else:
52 clone.restore_state(
53 args.source, args.destination, args.new_distro_codename, protect_installed)
54
55=== modified file 'apt_clone.py'
56--- apt_clone.py 2012-01-27 17:14:05 +0000
57+++ apt_clone.py 2012-06-12 09:23:17 +0000
58@@ -16,6 +16,8 @@
59 # this program; if not, write to the Free Software Foundation, Inc.,
60 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
61
62+from __future__ import print_function
63+
64 import apt
65 from apt.cache import FetchFailedException
66 import apt_pkg
67@@ -29,7 +31,7 @@
68 import tempfile
69 import time
70
71-from StringIO import StringIO
72+from io import BytesIO
73
74 if "APT_CLONE_DEBUG_RESOLVER" in os.environ:
75 apt_pkg.config.set("Debug::pkgProblemResolver", "1")
76@@ -158,9 +160,9 @@
77 'arch' : apt_pkg.config.find("APT::Architecture")
78 }
79 # save it
80- f = tempfile.NamedTemporaryFile()
81+ f = tempfile.NamedTemporaryFile(mode='w')
82 info = "\n".join(["%s: %s" % (key, value)
83- for (key, value) in host_info.iteritems()])
84+ for (key, value) in host_info.items()])
85 f.write(info+"\n")
86 f.flush()
87 tar.add(f.name, arcname="./var/lib/apt-clone/uname")
88@@ -180,9 +182,10 @@
89 self.version_mismatch.add(pkg.name)
90 # store the installed.pkgs
91 tarinfo = tarfile.TarInfo("./var/lib/apt-clone/installed.pkgs")
92+ s = s.encode('utf-8')
93 tarinfo.size = len(s)
94 tarinfo.mtime = time.time()
95- tar.addfile(tarinfo, StringIO(s))
96+ tar.addfile(tarinfo, BytesIO(s))
97
98 def _write_state_dpkg_status(self, tar):
99 # store dpkg-status, this is not strictly needed as installed.pkgs
100@@ -232,11 +235,11 @@
101 self.commands.repack_deb(pkgname, tdir)
102 tar.add(tdir, arcname="./var/lib/apt-clone/debs")
103 shutil.rmtree(tdir)
104- #print tdir
105+ #print(tdir)
106
107 # detect prefix
108 def _detect_tarprefix(self, tar):
109- #print tar.getnames()
110+ #print(tar.getnames())
111 if tar.getnames()[-1].startswith("./"):
112 self.TARPREFIX = "./"
113 else:
114@@ -313,7 +316,7 @@
115 self._detect_tarprefix(tar)
116
117 if not os.path.exists(targetdir):
118- print "Dir '%s' does not exist, need to bootstrap first" % targetdir
119+ print("Dir '%s' does not exist, need to bootstrap first" % targetdir)
120 distro = self._get_info_distro(statefile)
121 self.commands.debootstrap(targetdir, distro)
122
123@@ -401,7 +404,7 @@
124 # the actiongroup will help libapt to speed up the following loop
125 with cache.actiongroup():
126 for line in f.readlines():
127- line = line.strip()
128+ line = line.strip().decode('utf-8')
129 if line.startswith("#") or line == "":
130 continue
131 (name, version, auto) = line.split()
132@@ -416,7 +419,7 @@
133 if cache.broken_count > 0:
134 resolver.resolve()
135 if not cache[name].marked_install:
136- raise SystemError, "pkg %s not marked upgrade" % name
137+ raise SystemError("pkg %s not marked upgrade" % name)
138 else:
139 # normal mode, this assume the system is consistent
140 cache[name].mark_install(from_user=from_user)
141@@ -519,9 +522,10 @@
142 owned = set()
143 dpkg_basedir = os.path.dirname(apt_pkg.config.get("Dir::State::status"))
144 for f in glob.glob(os.path.join(dpkg_basedir, "info", "*.list")):
145- for line in open(f):
146- if line.startswith("/etc/"):
147- owned.add(line.strip())
148+ with open(f) as fp:
149+ for line in fp:
150+ if line.startswith("/etc/"):
151+ owned.add(line.strip())
152 # now go over etc
153 unowned = set()
154 for dirpath, dirnames, filenames in os.walk(etcdir):
155@@ -535,37 +539,39 @@
156 dpkg_status = sourcedir+apt_pkg.config.find("Dir::State::status")
157 modified = set()
158 # iterate dpkg-status file
159- tag = apt_pkg.TagFile(open(dpkg_status))
160- for entry in tag:
161- if "conffiles" in entry:
162- for line in entry["conffiles"].split("\n"):
163- obsolete = None
164- if len(line.split()) == 3:
165- name, md5sum, obsolete = line.split()
166- else:
167- name, md5sum = line.split()
168- # update
169- path = sourcedir+name
170- md5sum = md5sum.strip()
171- # ignore oboslete conffiles
172- if obsolete == "obsolete":
173- continue
174- # user removed conffile
175- if not os.path.exists(path):
176- logging.debug("conffile %s removed" % path)
177- modified.add(path)
178- continue
179- # check content
180- md5 = hashlib.md5()
181- md5.update(open(path).read())
182- if md5.hexdigest() != md5sum:
183- logging.debug("conffile %s (%s != %s)" % (
184- path, md5.hexdigest(), md5sum))
185- modified.add(path)
186+ with open(dpkg_status) as fp:
187+ tag = apt_pkg.TagFile(fp)
188+ for entry in tag:
189+ if "conffiles" in entry:
190+ for line in entry["conffiles"].split("\n"):
191+ obsolete = None
192+ if len(line.split()) == 3:
193+ name, md5sum, obsolete = line.split()
194+ else:
195+ name, md5sum = line.split()
196+ # update
197+ path = sourcedir+name
198+ md5sum = md5sum.strip()
199+ # ignore oboslete conffiles
200+ if obsolete == "obsolete":
201+ continue
202+ # user removed conffile
203+ if not os.path.exists(path):
204+ logging.debug("conffile %s removed" % path)
205+ modified.add(path)
206+ continue
207+ # check content
208+ md5 = hashlib.md5()
209+ with open(path, 'rb') as fp:
210+ md5.update(fp.read())
211+ if md5.hexdigest() != md5sum:
212+ logging.debug("conffile %s (%s != %s)" % (
213+ path, md5.hexdigest(), md5sum))
214+ modified.add(path)
215 return modified
216
217 def _dump_debconf_database(self, sourcedir):
218- print "not implemented yet"
219+ print("not implemented yet")
220 # debconf-copydb configdb newdb --config=Name:newdb --config=Driver:File --config=Filename:/tmp/lala.db
221 #
222 # debconf-copydb newdb configdb --config=Name:newdb --config=Driver:File --config=Filename:/tmp/lala.db
223
224=== modified file 'debian/changelog'
225--- debian/changelog 2012-01-27 16:54:20 +0000
226+++ debian/changelog 2012-06-12 09:23:17 +0000
227@@ -1,11 +1,28 @@
228-apt-clone (0.2.3) UNRELEASED; urgency=low
229+apt-clone (0.2.2ubuntu1) UNRELEASED; urgency=low
230
231+ [ Michael Vogt ]
232 * apt_clone.py:
233 - fix restoring of the clone with later apt versions (thanks
234 to Colin Watson)
235 - bind mount /proc, /sys on restore to chroot (thanks to
236 Colin Watson)
237
238+ [ Colin Watson ]
239+ * Port to Python 3:
240+ - Use Python 3-style print functions.
241+ - Use "raise Exception(value)" syntax rather than the old-style "raise
242+ Exception, value".
243+ - Use dict.items() rather than dict.iteritems().
244+
245+ [ Evan Dandrea ]
246+ * Finish port to Python 3:
247+ - Handle unicode changes.
248+ - Do not leak file descriptors.
249+ - Test against both Python2.7 and Python3.
250+ - Do not use the deprecated failUnless.
251+ - Use the io module instead of StringIO.
252+ - Fix a failing test caused by acpi-support being in main.
253+
254 -- Michael Vogt <michael.vogt@ubuntu.com> Fri, 27 Jan 2012 16:29:17 +0100
255
256 apt-clone (0.2.2) unstable; urgency=low
257
258=== modified file 'tests/Makefile'
259--- tests/Makefile 2011-04-08 14:36:22 +0000
260+++ tests/Makefile 2012-06-12 09:23:17 +0000
261@@ -4,7 +4,9 @@
262 test:
263 pyflakes ../apt-clone ../apt_clone.py
264 set -e; for f in *.py; do \
265- PYTHONPATH=.. python $$f; \
266+ for ver in python3 python2; do \
267+ PYTHONPATH=.. $$ver $$f; \
268+ done; \
269 done; \
270 # cruft from the tests
271 rm -f ./data/mock-system/var/cache/apt/*.bin
272
273=== modified file 'tests/test_clone.py'
274--- tests/test_clone.py 2012-01-02 18:08:31 +0000
275+++ tests/test_clone.py 2012-06-12 09:23:17 +0000
276@@ -1,5 +1,7 @@
277 #!/usr/bin/python
278
279+from __future__ import print_function
280+
281 import apt
282 import apt_pkg
283 import mock
284@@ -10,7 +12,7 @@
285 import tempfile
286 import unittest
287
288-from StringIO import StringIO
289+from io import StringIO
290
291 sys.path.insert(0, "..")
292 import apt_clone
293@@ -31,7 +33,8 @@
294 os.makedirs(os.path.join(self.tempdir, "var/lib/dpkg/"))
295 # ensure we are the right arch
296 os.makedirs(os.path.join(self.tempdir, "etc/apt"))
297- open(os.path.join(self.tempdir, "etc/apt/apt.conf"), "w").write('''
298+ with open(os.path.join(self.tempdir, "etc/apt/apt.conf"), "w") as fp:
299+ fp.write('''
300 #clear Dpkg::Post-Invoke;
301 #clear Dpkg::Pre-Invoke;
302 #clear APT::Update;
303@@ -59,7 +62,7 @@
304 tarname = os.path.join(targetdir, clone.CLONE_FILENAME)
305 self.assertTrue(os.path.exists(tarname))
306 tar = tarfile.open(tarname)
307- #print tar.getmembers()
308+ #print(tar.getmembers())
309 # verify members in tar
310 members = [m.name for m in tar.getmembers()]
311 self.assertTrue("./etc/apt/sources.list" in members)
312@@ -108,11 +111,14 @@
313 # create target dir
314 targetdir = self.tempdir
315 # status file from maverick (to simulate running on a maverick live-cd)
316- s=open("./data/dpkg-status/dpkg-status-ubuntu-maverick").read()
317+ with open("./data/dpkg-status/dpkg-status-ubuntu-maverick") as fp:
318+ s = fp.read()
319 s = s.replace(
320 "Architecture: i386",
321 "Architecture: %s" % apt_pkg.config.find("Apt::Architecture"))
322- open(os.path.join(targetdir, "var/lib/dpkg", "status"), "w").write(s)
323+ path = os.path.join(targetdir, "var/lib/dpkg", "status")
324+ with open(path, "w") as fp:
325+ fp.write(s)
326 # test upgrade clone from lucid system to maverick
327 clone = AptClone(cache_cls=MockAptCache)
328 clone.restore_state(
329@@ -121,14 +127,16 @@
330 "maverick")
331 sources_list = os.path.join(targetdir, "etc","apt","sources.list")
332 self.assertTrue(os.path.exists(sources_list))
333- self.assertTrue("maverick" in open(sources_list).read())
334- self.assertFalse("lucid" in open(sources_list).read())
335+ with open(sources_list) as fp:
336+ self.assertTrue("maverick" in fp.read())
337+ with open(sources_list) as fp:
338+ self.assertFalse("lucid" in fp.read())
339
340 def test_restore_state_simulate(self):
341 clone = AptClone()
342 missing = clone.simulate_restore_state("./data/apt-state.tar.gz")
343 # missing, because clone does not have universe enabled
344- self.assertEqual(list(missing), ["accerciser", "acpi-support"])
345+ self.assertEqual(list(missing), ["accerciser"])
346
347 def test_restore_state_simulate_with_new_release(self):
348 #apt_pkg.config.set("Debug::PkgProblemResolver", "1")
349@@ -139,7 +147,7 @@
350 missing = clone.simulate_restore_state(
351 "./data/apt-state-ubuntu-lucid.tar.gz", "maverick")
352 # FIXME: check that the stuff in missing is ok
353- print missing
354+ print(missing)
355
356 def test_modified_conffiles(self):
357 clone = AptClone()
358@@ -162,13 +170,13 @@
359 "Dir::state::status",
360 "/var/lib/dpkg/status")
361 unowned = clone._find_unowned_in_etc()
362- #print unowned
363+ #print(unowned)
364 self.assertNotEqual(unowned, set())
365 # negative test, is created by the installer
366 self.assertTrue("/etc/apt/sources.list" in unowned)
367 # postivie test, belongs to base-files
368 self.assertFalse("/etc/issue" in unowned)
369- print "\n".join(sorted(unowned))
370+ print("\n".join(sorted(unowned)))
371
372 if __name__ == "__main__":
373 unittest.main()
374
375=== modified file 'tests/test_clone_upgrade.py'
376--- tests/test_clone_upgrade.py 2011-04-07 13:02:27 +0000
377+++ tests/test_clone_upgrade.py 2012-06-12 09:23:17 +0000
378@@ -9,7 +9,7 @@
379 import tempfile
380 import unittest
381
382-from StringIO import StringIO
383+from io import StringIO
384
385 sys.path.insert(0, "..")
386 import apt_clone
387@@ -60,7 +60,8 @@
388 sources_list = os.path.join(tmpdir, "etc", "apt", "sources.list")
389 if not os.path.exists(os.path.dirname(sources_list)):
390 os.makedirs(os.path.dirname(sources_list))
391- open(os.path.join(sources_list), "w").write("""
392+ with open(os.path.join(sources_list), "w") as fp:
393+ fp.write("""
394 deb http://archive.ubuntu.com/ubuntu %s main restricted universe multiverse
395 """ % from_dist)
396 cache = apt.Cache(rootdir=tmpdir)
397@@ -74,20 +75,18 @@
398 dpkg_status = os.path.join(tmpdir, "var", "lib", "dpkg", "status")
399 if not os.path.exists(os.path.dirname(dpkg_status)):
400 os.makedirs(os.path.dirname(dpkg_status))
401- dpkg = open(dpkg_status, "w")
402- installed = open(installed_pkgs, "w")
403- for pkg in cache:
404- if pkg.marked_install:
405- s = str(pkg.candidate.record)
406- s = s.replace("Package: %s\n" % pkg.name,
407- "Package: %s\n%s\n" % (
408- pkg.name, "Status: install ok installed"))
409- dpkg.write("%s\n" % s)
410- installed.write("%s %s %s\n" % (pkg.name,
411- pkg.candidate.version,
412- int(pkg.is_auto_installed)))
413- dpkg.close()
414- installed.close()
415+ with open(dpkg_status, "w") as dpkg:
416+ with open(installed_pkgs, "w") as installed:
417+ for pkg in cache:
418+ if pkg.marked_install:
419+ s = str(pkg.candidate.record)
420+ s = s.replace("Package: %s\n" % pkg.name,
421+ "Package: %s\n%s\n" % (
422+ pkg.name, "Status: install ok installed"))
423+ dpkg.write("%s\n" % s)
424+ installed.write("%s %s %s\n" % (pkg.name,
425+ pkg.candidate.version,
426+ int(pkg.is_auto_installed)))
427 return tmpdir
428
429
430
431=== modified file 'tests/test_in_chroot.py'
432--- tests/test_in_chroot.py 2012-01-27 17:14:05 +0000
433+++ tests/test_in_chroot.py 2012-06-12 09:23:17 +0000
434@@ -1,5 +1,7 @@
435 #!/usr/bin/python
436
437+from __future__ import print_function
438+
439 import apt
440 import logging
441 import os
442@@ -25,7 +27,7 @@
443
444 def test_real(self):
445 if os.getuid() != 0:
446- print "Skipping because uid != 0"
447+ print("Skipping because uid != 0")
448 return
449 # do it
450 target = "./test-chroot"
451@@ -34,8 +36,8 @@
452 subprocess.call(["debootstrap", "--arch=i386",
453 "maverick", target])
454 # force i386
455- open(os.path.join(target, "etc/apt/apt.conf"), "w").write(
456- 'APT::Architecture "i386";')
457+ with open(os.path.join(target, "etc/apt/apt.conf"), "w") as fp:
458+ fp.write('APT::Architecture "i386";')
459
460 # restore
461 clone = AptClone()
462
463=== modified file 'tests/test_merge_sources.py'
464--- tests/test_merge_sources.py 2011-04-06 14:06:31 +0000
465+++ tests/test_merge_sources.py 2012-06-12 09:23:17 +0000
466@@ -26,8 +26,8 @@
467 if line != '\n' and not line.startswith('#'):
468 tally[line] += 1
469 # There should not be any duplicate source lines.
470- for line, count in tally.iteritems():
471- self.failUnless(count == 1, '"%s" occurred %d times.'
472+ for line, count in tally.items():
473+ self.assertTrue(count == 1, '"%s" occurred %d times.'
474 % (line, count))
475
476 # Check for extras, others...
477@@ -43,7 +43,7 @@
478 for line in fp:
479 if line == match:
480 found = True
481- self.failUnless(found,
482+ self.assertTrue(found,
483 '%s repository not present or disabled.' % pocket)
484
485 if __name__ == "__main__":

Subscribers

People subscribed via source and target branches

to all changes: