[Libguestfs] RHashtable and the python bindings

Richard W.M. Jones rjones at redhat.com
Wed Apr 3 18:19:39 UTC 2013


Here's a simple implementation.

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
virt-df lists disk usage of guests without needing to install any
software inside the virtual machine.  Supports Linux and Windows.
http://people.redhat.com/~rjones/virt-df/
-------------- next part --------------
>From ba0199c487feec7f02cfc00ba3e900e6fa5ed4d8 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones at redhat.com>
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<guestfs(3)>.
 
+=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<RuntimeException>
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



More information about the Libguestfs mailing list