>From ba0199c487feec7f02cfc00ba3e900e6fa5ed4d8 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Wed, 3 Apr 2013 18:58:43 +0100 Subject: [PATCH] python: Let RHashtable be returned as a Python dict. The initial proposal was suggested by Matt Booth and discussed on the mailing list here: https://www.redhat.com/archives/libguestfs/2013-April/msg00007.html --- generator/python.ml | 43 ++++++++++++++++++++++++++++++++------ python/examples/create_disk.py | 6 +++++- python/examples/guestfs-python.pod | 13 +++++++++++- python/examples/inspect_vm.py | 19 +++++++---------- python/t/010-basic.py | 2 +- python/t/028-python-dict.py | 34 ++++++++++++++++++++++++++++++ python/t/060-optargs.py | 2 +- python/t/400-events.py | 2 +- python/t/800-explicit-close.py | 6 +++--- python/t/rhbz811650.py | 2 +- 10 files changed, 103 insertions(+), 26 deletions(-) create mode 100644 python/t/028-python-dict.py diff --git a/generator/python.ml b/generator/python.ml index 836ae0d..a3e2271 100644 --- a/generator/python.ml +++ b/generator/python.ml @@ -601,7 +601,7 @@ and generate_python_py () = \"\"\"Python bindings for libguestfs import guestfs -g = guestfs.GuestFS () +g = guestfs.GuestFS (python_return_dict=True) g.add_drive_opts (\"guest.img\", format=\"raw\") g.launch () parts = g.list_partitions () @@ -667,12 +667,26 @@ class ClosedHandle(ValueError): class GuestFS(object): \"\"\"Instances of this class are libguestfs API handles.\"\"\" - def __init__ (self, environment=True, close_on_exit=True): - \"\"\"Create a new libguestfs handle.\"\"\" + def __init__ (self, python_return_dict=False, + environment=True, close_on_exit=True): + \"\"\"Create a new libguestfs handle. + + Note about \"python_return_dict\" flag: + + Setting this flag to 'True' causes all functions + that internally return hashes to return a dict. This is + natural for Python, and all new code should use + python_return_dict=True. + + If this flag is not present then hashes are returned + as lists of pairs. This was the only possible behaviour + in libguestfs <= 1.20. + \"\"\" flags = 0 if not environment: flags |= 1 if not close_on_exit: flags |= 2 self._o = libguestfsmod.create (flags) + self._python_return_dict = python_return_dict def __del__ (self): if self._o: @@ -682,6 +696,11 @@ class GuestFS(object): if not self._o: raise ClosedHandle (\"GuestFS: method called on closed handle\") + def _maybe_convert_to_dict (self, r): + if self._python_return_dict == True: + r = dict (r) + return r + def close (self): \"\"\"Explicitly close the guestfs handle. @@ -754,7 +773,7 @@ class GuestFS(object): | RStructList (_, typ) -> doc ^ sprintf "\n\nThis function returns a list of %ss. Each %s is represented as a dictionary." typ typ | RHashtable _ -> - doc ^ "\n\nThis function returns a dictionary." in + doc ^ "\n\nThis function returns a hash. If the GuestFS constructor was called with python_return_dict=True (recommended) then the return value is in fact a Python dict. Otherwise the return value is a list of pairs of strings, for compatibility with old code." in let doc = if f.protocol_limit_warning then doc ^ "\n\n" ^ protocol_limit_warning @@ -782,10 +801,22 @@ class GuestFS(object): pr " %s = list (%s)\n" n n ) args; pr " self._check_not_closed ()\n"; - pr " return libguestfsmod.%s (self._o" f.name; + pr " r = libguestfsmod.%s (self._o" f.name; List.iter (fun arg -> pr ", %s" (name_of_argt arg)) (args @ args_of_optargs optargs); - pr ")\n\n"; + pr ")\n"; + + (* For RHashtable, if self._python_return_dict=True then we + * have to convert the result to a dict. + *) + (match ret with + | RHashtable _ -> + pr " r = self._maybe_convert_to_dict (r)\n"; + | _ -> () + ); + + pr " return r\n"; + pr "\n"; (* Aliases. *) List.iter ( diff --git a/python/examples/create_disk.py b/python/examples/create_disk.py index 0869fc6..2f57f3f 100644 --- a/python/examples/create_disk.py +++ b/python/examples/create_disk.py @@ -5,7 +5,11 @@ import guestfs output = "disk.img" -g = guestfs.GuestFS () +# All new Python code should pass python_return_dict=True +# to the constructor. It indicates that your program wants +# to receive Python dicts for methods in the API that return +# hashtables. +g = guestfs.GuestFS (python_return_dict=True) # Create a raw-format sparse disk image, 512 MB in size. f = open (output, "w") diff --git a/python/examples/guestfs-python.pod b/python/examples/guestfs-python.pod index b17cf07..56be347 100644 --- a/python/examples/guestfs-python.pod +++ b/python/examples/guestfs-python.pod @@ -7,7 +7,7 @@ guestfs-python - How to use libguestfs from Python =head1 SYNOPSIS import guestfs - g = guestfs.GuestFS () + g = guestfs.GuestFS (python_return_dict=True) g.add_drive_opts ("disk.img", format="raw", readonly=1) g.launch () @@ -18,6 +18,17 @@ programming language. This page just documents the differences from the C API and gives some examples. If you are not familiar with using libguestfs, you also need to read L. +=head2 python_return_dict=True + +All new code should construct the handle using: + + g = guestfs.GuestFS (python_return_dict=True) + +This indicates that your program wants to receive Python dicts for +methods in the API that return hashtables. + +In a future version of libguestfs, this will become the default. + =head2 EXCEPTIONS Errors from libguestfs functions are mapped into C diff --git a/python/examples/inspect_vm.py b/python/examples/inspect_vm.py index c491a2c..0bbae78 100644 --- a/python/examples/inspect_vm.py +++ b/python/examples/inspect_vm.py @@ -6,7 +6,11 @@ import guestfs assert (len (sys.argv) == 2) disk = sys.argv[1] -g = guestfs.GuestFS () +# All new Python code should pass python_return_dict=True +# to the constructor. It indicates that your program wants +# to receive Python dicts for methods in the API that return +# hashtables. +g = guestfs.GuestFS (python_return_dict=True) # Attach the disk image read-only to libguestfs. g.add_drive_opts (disk, readonly=1) @@ -35,17 +39,10 @@ for root in roots: # Sort keys by length, shortest first, so that we end up # mounting the filesystems in the correct order. mps = g.inspect_get_mountpoints (root) - def compare (a, b): - if len(a[0]) > len(b[0]): - return 1 - elif len(a[0]) == len(b[0]): - return 0 - else: - return -1 - mps.sort (compare) - for mp_dev in mps: + def compare (a, b): return len(a) - len(b) + for device in sorted (mps.keys(), compare): try: - g.mount_ro (mp_dev[1], mp_dev[0]) + g.mount_ro (mps[device], device) except RuntimeError as msg: print "%s (ignored)" % msg diff --git a/python/t/010-basic.py b/python/t/010-basic.py index 950b89a..6332a94 100644 --- a/python/t/010-basic.py +++ b/python/t/010-basic.py @@ -18,7 +18,7 @@ import os import guestfs -g = guestfs.GuestFS() +g = guestfs.GuestFS (python_return_dict=True) f = open ("test.img", "w") f.truncate (500 * 1024 * 1024) f.close () diff --git a/python/t/028-python-dict.py b/python/t/028-python-dict.py new file mode 100644 index 0000000..8ba0e87 --- /dev/null +++ b/python/t/028-python-dict.py @@ -0,0 +1,34 @@ +# libguestfs Python bindings +# Copyright (C) 2013 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Test python-specific python_return_dict parameter. + +from types import * +import os +import guestfs + +g = guestfs.GuestFS (python_return_dict=False) + +r = g.internal_test_rhashtable ("5") +if type(r) != list or r != [ ("0","0"), ("1","1"), ("2","2"), ("3","3"), ("4","4") ]: + raise Exception ("python_return_dict=False: internal_test_rhashtable returned %s" % r) + +g = guestfs.GuestFS (python_return_dict=True) + +r = g.internal_test_rhashtable ("5") +if type(r) != dict or sorted (r.keys()) != ["0","1","2","3","4"] or r["0"] != "0" or r["1"] != "1" or r["2"] != "2" or r["3"] != "3" or r["4"] != "4": + raise Exception ("python_return_dict=True: internal_test_rhashtable returned %s" % r) diff --git a/python/t/060-optargs.py b/python/t/060-optargs.py index 241185b..7be0ce6 100644 --- a/python/t/060-optargs.py +++ b/python/t/060-optargs.py @@ -18,7 +18,7 @@ import os import guestfs -g = guestfs.GuestFS() +g = guestfs.GuestFS (python_return_dict=True) g.add_drive ("/dev/null") g.add_drive ("/dev/null", readonly = True) g.add_drive ("/dev/null", iface = "virtio", format = "raw") diff --git a/python/t/400-events.py b/python/t/400-events.py index d24ec4d..bc9eb12 100644 --- a/python/t/400-events.py +++ b/python/t/400-events.py @@ -18,7 +18,7 @@ import os import guestfs -g = guestfs.GuestFS() +g = guestfs.GuestFS (python_return_dict=True) def log_callback (ev,eh,buf,array): if ev == guestfs.EVENT_APPLIANCE: diff --git a/python/t/800-explicit-close.py b/python/t/800-explicit-close.py index 4ed6774..9b8ff84 100644 --- a/python/t/800-explicit-close.py +++ b/python/t/800-explicit-close.py @@ -20,13 +20,13 @@ import os import guestfs -g = guestfs.GuestFS () +g = guestfs.GuestFS (python_return_dict=True) g.close () # explicit close del g # implicit close - should be no error/warning # Expect an exception if we call a method on a closed handle. -g = guestfs.GuestFS () +g = guestfs.GuestFS (python_return_dict=True) g.close () try: g.set_memsize (512) @@ -37,7 +37,7 @@ del g # Verify that the handle is really being closed by g.close, by setting # up a close event and testing that it happened. -g = guestfs.GuestFS () +g = guestfs.GuestFS (python_return_dict=True) close_invoked = 0 diff --git a/python/t/rhbz811650.py b/python/t/rhbz811650.py index c84f094..ee85c02 100644 --- a/python/t/rhbz811650.py +++ b/python/t/rhbz811650.py @@ -22,7 +22,7 @@ f = open ("test.img", "w") f.truncate (500 * 1024 * 1024) f.close () -g = guestfs.GuestFS () +g = guestfs.GuestFS (python_return_dict=True) # Deliberate error: the disk format is supposed to be raw. g.add_drive ("test.img", format="qcow2"); -- 1.8.1.4