docs and examples: python bindings
authorBen McGinnes <ben@adversary.org>
Mon, 24 Sep 2018 23:59:31 +0000 (09:59 +1000)
committerBen McGinnes <ben@adversary.org>
Mon, 24 Sep 2018 23:59:31 +0000 (09:59 +1000)
* Woumd up the "what's new" section.
* Added an example for sending a key to the keyservers via hkp4py.
* Updated the export key code to use a more complete check for the
  $GNUPGHOME location.
* Expanded on the installation and reinstallation troubleshooting
  section.

Tested-by: Ben McGinnes <ben@adversary.org>
Signed-off-by: Ben McGinnes <ben@adversary.org>
doc/gpgme-python-howto.texi
lang/python/docs/gpgme-python-howto.org
lang/python/examples/howto/export-key.py
lang/python/examples/howto/export-minimised-key.py
lang/python/examples/howto/export-secret-key.py
lang/python/examples/howto/export-secret-keys.py
lang/python/examples/howto/send-key-to-keyserver.py [new file with mode: 0755]

index 2e13d87..69ae959 100644 (file)
@@ -64,6 +64,10 @@ GPGME Python bindings installation
 * Installation::
 * Known Issues::
 
+Requirements
+
+* Recommended Additions::
+
 Installation
 
 * Installing GPGME::
@@ -71,6 +75,7 @@ Installation
 Known Issues
 
 * Breaking Builds::
+* Reinstalling Responsibly::
 * Multiple installations::
 * Won't Work With Windows::
 * CFFI is the Bestâ„¢ and GPGME should use it instead of SWIG::
@@ -101,6 +106,7 @@ Exporting keys
 
 * Exporting public keys::
 * Exporting secret keys::
+* Sending public keys to the SKS Keyservers::
 
 Basic Functions
 
@@ -445,7 +451,8 @@ The GPGME Python bindings only have three requirements:
 @enumerate
 @item
 A suitable version of Python 2 or Python 3.  With Python 2 that
-means Python 2.7 and with Python 3 that means Python 3.4 or higher.
+means CPython 2.7 and with Python 3 that means CPython 3.4 or
+higher.
 @item
 @uref{https://www.swig.org, SWIG}.
 @item
@@ -453,6 +460,33 @@ GPGME itself.  Which also means that all of GPGME's dependencies
 must be installed too.
 @end enumerate
 
+@menu
+* Recommended Additions::
+@end menu
+
+@node Recommended Additions
+@subsection Recommended Additions
+
+Though none of the following are absolute requirements, they are all
+recommended for use with the Python bindings.  In some cases these
+recommendations refer to which version(s) of CPython to use the
+bindings with, while others refer to third party modules which provide
+a significant advantage in some way.
+
+@enumerate
+@item
+If possible, use Python 3 instead of 2.
+@item
+Favour a more recent version of Python since even 3.4 is due to
+reach EOL soon.  In production systems and services, Python 3.6
+should be robust enough to be relied on.
+@item
+If possible add the following Python modules which are not part of
+the standard library: @uref{http://docs.python-requests.org/en/latest/index.html, Requests}, @uref{http://cython.org/, Cython} and @uref{https://github.com/Selfnet/hkp4py, hkp4py}.  Chances are
+quite high that at least the first one and maybe two of those will
+already be installed.
+@end enumerate
+
 @node Installation
 @section Installation
 
@@ -495,6 +529,7 @@ they be encountered.
 
 @menu
 * Breaking Builds::
+* Reinstalling Responsibly::
 * Multiple installations::
 * Won't Work With Windows::
 * CFFI is the Bestâ„¢ and GPGME should use it instead of SWIG::
@@ -559,10 +594,34 @@ If Python is set to precede one of the other languages then it is
 possible that the errors described here may interrupt the build
 process before generating bindings for those other languages.  In
 these cases it may be preferable to configure all preferred language
-howto-export-public-keybindings separately with alternative @samp{configure} steps for GPGME using
+bindings separately with alternative @samp{configure} steps for GPGME using
 the @samp{--enable-languages=$LANGUAGE} option.
 @end enumerate
 
+@node Reinstalling Responsibly
+@subsection Reinstalling Responsibly
+
+Regardless of whether you're installing for one version of Python or
+several, there will come a point where reinstallation is required.
+With most Python module installations, the installed files go into the
+relevant site-packages directory and are then forgotten about.  Then
+the module is upgraded, the new files are copied over the old and
+that's the end of the matter.
+
+While the same is true of these bindings, there have been intermittent
+issues observed on some platforms which have benefited significantly
+from removing all the previous installations of the bindings before
+installing the updated versions.
+
+Removing the previous version(s) is simply a matter of changing to the
+relevant @samp{site-packages} directory for the version of Python in
+question and removing the @samp{gpg/} directory and any accompanying
+egg-info files for that module.
+
+In most cases this will require root or administration privileges on
+the system, but the same is true of installing the module in the first
+place.
+
 @node Multiple installations
 @subsection Multiple installations
 
@@ -1427,6 +1486,7 @@ third is for exporting secret keys.
 @menu
 * Exporting public keys::
 * Exporting secret keys::
+* Sending public keys to the SKS Keyservers::
 @end menu
 
 @node Exporting public keys
@@ -1586,12 +1646,26 @@ else:
     logrus = input("Enter the UID matching the secret key(s) to export: ")
     homedir = input("Enter the GPG configuration directory path (optional): ")
 
-if homedir.startswith("~"):
-    if os.path.exists(os.path.expanduser(homedir)) is True:
-        c.home_dir = os.path.expanduser(homedir)
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
     else:
         pass
-elif os.path.exists(homedir) is True:
+
+if homedir is not None:
     c.home_dir = homedir
 else:
     pass
@@ -1656,12 +1730,26 @@ else:
     logrus = input("Enter the UID matching the secret key(s) to export: ")
     homedir = input("Enter the GPG configuration directory path (optional): ")
 
-if homedir.startswith("~"):
-    if os.path.exists(os.path.expanduser(homedir)) is True:
-        c.home_dir = os.path.expanduser(homedir)
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
     else:
         pass
-elif os.path.exists(homedir) is True:
+
+if homedir is not None:
     c.home_dir = homedir
 else:
     pass
@@ -1712,6 +1800,70 @@ else:
     pass
 @end example
 
+@node Sending public keys to the SKS Keyservers
+@subsection Sending public keys to the SKS Keyservers
+
+As with the previous section on importing keys, the @samp{hkp4py} module
+adds another option with exporting keys in order to send them to the
+public keyservers.
+
+The following example demonstrates how this may be done.
+
+@example
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script sends one or more public keys to the SKS keyservers and is
+essentially a slight variation on the export-key.py script.
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+
+if len(sys.argv) > 2:
+    logrus = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+    logrus = sys.argv[1]
+else:
+    logrus = input("Enter the UID matching the key(s) to send: ")
+
+if len(logrus) > 0:
+    try:
+        export_result = c.key_export(pattern=logrus)
+    except Exception as e:
+        print(e)
+        export_result = None
+else:
+    export_result = c.key_export(pattern=None)
+
+if export_result is not None:
+    try:
+        try:
+            send_result = server.add(export_result)
+        except:
+            send_result = server.add(export_result.decode())
+        if send_result is not None:
+            print(send_result)
+        else:
+            pass
+    except Exception as e:
+        print(e)
+else:
+    pass
+@end example
+
+An expanded version of this script with additional functions for
+specifying an alternative homedir location is in the examples
+directory as @samp{send-key-to-keyserver.py}.
+
+The @samp{hkp4py} module appears to handle both string and byte literal text
+data equally well, but the GPGME bindings deal primarily with byte
+literal data only and so this script sends in that format first, then
+tries the string literal form.
+
 @node Basic Functions
 @chapter Basic Functions
 
index 6c599cb..90e133b 100644 (file)
@@ -293,12 +293,34 @@ section for further details.
 The GPGME Python bindings only have three requirements:
 
 1. A suitable version of Python 2 or Python 3.  With Python 2 that
-   means Python 2.7 and with Python 3 that means Python 3.4 or higher.
+   means CPython 2.7 and with Python 3 that means CPython 3.4 or
+   higher.
 2. [[https://www.swig.org][SWIG]].
 3. GPGME itself.  Which also means that all of GPGME's dependencies
    must be installed too.
 
 
+*** Recommended Additions
+   :PROPERTIES:
+   :CUSTOM_ID: gpgme-python-recommendations
+   :END:
+
+Though none of the following are absolute requirements, they are all
+recommended for use with the Python bindings.  In some cases these
+recommendations refer to which version(s) of CPython to use the
+bindings with, while others refer to third party modules which provide
+a significant advantage in some way.
+
+1. If possible, use Python 3 instead of 2.
+2. Favour a more recent version of Python since even 3.4 is due to
+   reach EOL soon.  In production systems and services, Python 3.6
+   should be robust enough to be relied on.
+3. If possible add the following Python modules which are not part of
+   the standard library: [[http://docs.python-requests.org/en/latest/index.html][Requests]], [[http://cython.org/][Cython]] and [[https://github.com/Selfnet/hkp4py][hkp4py]].  Chances are
+   quite high that at least the first one and maybe two of those will
+   already be installed.
+
+
 ** Installation
    :PROPERTIES:
    :CUSTOM_ID: installation
@@ -398,10 +420,37 @@ If Python is set to precede one of the other languages then it is
 possible that the errors described here may interrupt the build
 process before generating bindings for those other languages.  In
 these cases it may be preferable to configure all preferred language
-howto-export-public-keybindings separately with alternative =configure= steps for GPGME using
+bindings separately with alternative =configure= steps for GPGME using
 the =--enable-languages=$LANGUAGE= option.
 
 
+*** Reinstalling Responsibly
+    :PROPERTIES:
+    :CUSTOM_ID: snafu-lessons-for-the-lazy
+    :END:
+
+Regardless of whether you're installing for one version of Python or
+several, there will come a point where reinstallation is required.
+With most Python module installations, the installed files go into the
+relevant site-packages directory and are then forgotten about.  Then
+the module is upgraded, the new files are copied over the old and
+that's the end of the matter.
+
+While the same is true of these bindings, there have been intermittent
+issues observed on some platforms which have benefited significantly
+from removing all the previous installations of the bindings before
+installing the updated versions.
+
+Removing the previous version(s) is simply a matter of changing to the
+relevant =site-packages= directory for the version of Python in
+question and removing the =gpg/= directory and any accompanying
+egg-info files for that module.
+
+In most cases this will require root or administration privileges on
+the system, but the same is true of installing the module in the first
+place.
+
+
 *** Multiple installations
     :PROPERTIES:
     :CUSTOM_ID: snafu-the-full-monty
@@ -1446,12 +1495,26 @@ else:
     logrus = input("Enter the UID matching the secret key(s) to export: ")
     homedir = input("Enter the GPG configuration directory path (optional): ")
 
-if homedir.startswith("~"):
-    if os.path.exists(os.path.expanduser(homedir)) is True:
-        c.home_dir = os.path.expanduser(homedir)
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
     else:
         pass
-elif os.path.exists(homedir) is True:
+
+if homedir is not None:
     c.home_dir = homedir
 else:
     pass
@@ -1516,12 +1579,26 @@ else:
     logrus = input("Enter the UID matching the secret key(s) to export: ")
     homedir = input("Enter the GPG configuration directory path (optional): ")
 
-if homedir.startswith("~"):
-    if os.path.exists(os.path.expanduser(homedir)) is True:
-        c.home_dir = os.path.expanduser(homedir)
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
     else:
         pass
-elif os.path.exists(homedir) is True:
+
+if homedir is not None:
     c.home_dir = homedir
 else:
     pass
@@ -1573,6 +1650,73 @@ else:
 #+END_SRC
 
 
+*** Sending public keys to the SKS Keyservers
+    :PROPERTIES:
+    :CUSTOM_ID: howto-send-public-key
+    :END:
+
+As with the previous section on importing keys, the =hkp4py= module
+adds another option with exporting keys in order to send them to the
+public keyservers.
+
+The following example demonstrates how this may be done.
+
+#+BEGIN_SRC python -i
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script sends one or more public keys to the SKS keyservers and is
+essentially a slight variation on the export-key.py script.
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+
+if len(sys.argv) > 2:
+    logrus = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+    logrus = sys.argv[1]
+else:
+    logrus = input("Enter the UID matching the key(s) to send: ")
+
+if len(logrus) > 0:
+    try:
+        export_result = c.key_export(pattern=logrus)
+    except Exception as e:
+        print(e)
+        export_result = None
+else:
+    export_result = c.key_export(pattern=None)
+
+if export_result is not None:
+    try:
+        try:
+            send_result = server.add(export_result)
+        except:
+            send_result = server.add(export_result.decode())
+        if send_result is not None:
+            print(send_result)
+        else:
+            pass
+    except Exception as e:
+        print(e)
+else:
+    pass
+#+END_SRC
+
+An expanded version of this script with additional functions for
+specifying an alternative homedir location is in the examples
+directory as =send-key-to-keyserver.py=.
+
+The =hkp4py= module appears to handle both string and byte literal text
+data equally well, but the GPGME bindings deal primarily with byte
+literal data only and so this script sends in that format first, then
+tries the string literal form.
+
+
 * Basic Functions
   :PROPERTIES:
   :CUSTOM_ID: howto-the-basics
index 80768fe..c17f247 100755 (executable)
@@ -51,12 +51,26 @@ else:
     logrus = input("Enter the UID matching the key(s) to export: ")
     homedir = input("Enter the GPG configuration directory path (optional): ")
 
-if homedir.startswith("~"):
-    if os.path.exists(os.path.expanduser(homedir)) is True:
-        c.home_dir = os.path.expanduser(homedir)
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
     else:
         pass
-elif os.path.exists(homedir) is True:
+
+if homedir is not None:
     c.home_dir = homedir
 else:
     pass
index 9d5f848..a5a453c 100755 (executable)
@@ -51,12 +51,26 @@ else:
     logrus = input("Enter the UID matching the key(s) to export: ")
     homedir = input("Enter the GPG configuration directory path (optional): ")
 
-if homedir.startswith("~"):
-    if os.path.exists(os.path.expanduser(homedir)) is True:
-        c.home_dir = os.path.expanduser(homedir)
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
     else:
         pass
-elif os.path.exists(homedir) is True:
+
+if homedir is not None:
     c.home_dir = homedir
 else:
     pass
index ccc9f45..e7d4e3a 100755 (executable)
@@ -54,12 +54,26 @@ else:
     logrus = input("Enter the UID matching the secret key(s) to export: ")
     homedir = input("Enter the GPG configuration directory path (optional): ")
 
-if homedir.startswith("~"):
-    if os.path.exists(os.path.expanduser(homedir)) is True:
-        c.home_dir = os.path.expanduser(homedir)
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
     else:
         pass
-elif os.path.exists(homedir) is True:
+
+if homedir is not None:
     c.home_dir = homedir
 else:
     pass
index f2f1ccd..f0fddc6 100755 (executable)
@@ -63,12 +63,26 @@ else:
     logrus = input("Enter the UID matching the secret key(s) to export: ")
     homedir = input("Enter the GPG configuration directory path (optional): ")
 
-if homedir.startswith("~"):
-    if os.path.exists(os.path.expanduser(homedir)) is True:
-        c.home_dir = os.path.expanduser(homedir)
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
     else:
         pass
-elif os.path.exists(homedir) is True:
+
+if homedir is not None:
     c.home_dir = homedir
 else:
     pass
diff --git a/lang/python/examples/howto/send-key-to-keyserver.py b/lang/python/examples/howto/send-key-to-keyserver.py
new file mode 100755 (executable)
index 0000000..3541b19
--- /dev/null
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import hkp4py
+import os.path
+import sys
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# 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 free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 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 and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script sends one or more public keys to the SKS keyservers and is
+essentially a slight variation on the export-key.py script.
+
+The default is to send all keys if there is no pattern or search term.
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+
+if len(sys.argv) >= 3:
+    logrus = sys.argv[1]
+    homedir = sys.argv[2]
+elif len(sys.argv) == 2:
+    logrus = sys.argv[1]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+if len(logrus) > 0:
+    try:
+        export_result = c.key_export(pattern=logrus)
+    except Exception as e:
+        print(e)
+        export_result = None
+else:
+    export_result = c.key_export(pattern=None)
+
+if export_result is not None:
+    try:
+        try:
+            send_result = server.add(export_result)
+        except:
+            send_result = server.add(export_result.decode())
+        if send_result is not None:
+            print(send_result)
+        else:
+            pass
+    except Exception as e:
+        print(e)
+else:
+    pass