Python bindings HOWTO proof reader request

Justus Winter justus at gnupg.org
Thu Mar 15 15:01:30 CET 2018


Hi Ben,

thanks for working on this.

Ben McGinnes <ben at adversary.org> writes:
> https://files.gnupg.net/file/data/ossmg4ung2hcpyyuks6j/PHID-FILE-xgbofmytge7fzn3u5kuc/GPGMEpythonHOWTOen.org
>
> [...]
>
> I'm more interested in being sure that the example code works (it
> should, I was running it as I was writing the thing) and that the
> corresponding text descriptions actually help to clarify what's going
> on in that code.

I find many examples not very elegant, as in they are not very pythonic
and they promote the use of low-level interfaces (op_*).

For example:

>   #+begin_src python
>     import gpg
>     import os
>
>     rkey = "0x12345678DEADBEEF"
>     text = """
>     Some plain text to test with.  Obtained from any input source Python can read.
>
>     It makes no difference whether it is string or bytes, but the bindings always
>     produce byte output data.  Which is useful to know when writing out either the
>     encrypted or decrypted results.
>
>     """
>
>     plain = gpg.core.Data(text)
>     cipher = gpg.core.Data()

In almost all cases, explicit wrapping with core.Data is no longer
necessary and provides no benefit.

>     c = gpg.core.Context()
>     c.set_armor(1)

c = gpg.core.Context(armor=True)

>     c.op_keylist_start(rkey, 0)
>     r = c.op_keylist_next()
>
>     if r == None:
>	 print("""The key for user "{0}" was not found""".format(rkey))

r = list(c.keylist(rkey))
if not r:
    print("The key for user {0!r} was not found".format(rkey))

>     else:
>	 try:

I see no value in wrapping this in a try-block for demonstration
purposes.

>	     c.op_encrypt([r], 1, plain, cipher)
>	     cipher.seek(0, os.SEEK_SET)
>	     del(text)
>	     del(plain)

In general, there is no need to del(..) anything.

>	     afile = open("secret_plans.txt.asc", "wb")
>	     afile.write(cipher.read())
>	     afile.close()
>	 except gpg.errors.GPGMEError as ex:
>	     print(ex.getstring())

c. encrypt(text.encode(), r, sink=open("secret_plans.txt.asc", "w"))

>   #+end_src

Or:

>    #+begin_src python
>      import gpg
>
>      text = b"""Oh look, another test message.
>
>      The same rules apply as with the previous example and more likely
>      than not, the message will actually be drawn from reading the
>      contents of a file or, maybe, from entering data at an input()
>      prompt.
>
>      Since the text in this case must be bytes, it is most likely that
>      the input form will be a separate file which is opened with "rb"
>      as this is the simplest method of obtaining the correct data
>      format.
>      """
>
>      c = gpg.Context(armor=True)
>      rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
>      logrus = []
>
>      for i in range(len(rpattern)):
>	  if rpattern[i].can_encrypt == 1:
>	      logrus.append(rpattern[i])

r = [r
     for r in c.keylist(pattern="@gnupg.org", secret=False)
     if r.can_encrypt]

Also, I don't get 'logrus'.

>      cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True)
>
>      afile = open("secret_plans.txt.asc", "wb")
>      afile.write(cipher[0])
>      afile.close()

Again, sink= is better.  But if you do want to get results from
e.g. .encrypt(..), please use destructuring assignment to get the
returned values, e.g.:

cipher, _, _ = c.encrypt(...)

>    #+end_src

>   #+begin_src python
>     import os.path
>     import gpg
>
>     if os.path.exists("/path/to/secret_plans.txt.asc") is True:
>	 ciphertext = "/path/to/secret_plans.txt.asc"

1/ I don't see why example code wouldn't just assume one variant, or
maybe even better, take the path as command line argument.

2/ some_predicate is True =^= some_predicate

>     elif os.path.exists("/path/to/secret_plans.txt.gpg") is True:
>	 ciphertext = "/path/to/secret_plans.txt.gpg"
>     else:
>	 ciphertext = None
>
>     if ciphertext is not None:

Just fail if the input does not exist.

>	 afile = open(ciphertext, "rb")
>	 plaintext = gpg.Context().decrypt(afile)
>	 afile.close()
>	 newfile = open("/path/to/secret_plans.txt", "wb")
>	 newfile.write(plaintext[0])
>	 newfile.close()
>	 print(plaintext[0])
>	 plaintext[1]
>	 plaintext[2]

Statements without effect.

>	 del(plaintext)

Don't del stuff.

>     else:
>	 pass

if pred:
    pass
else:
    pass

=^=

if pred:
    pass

>   #+end_src

Also, I feel like this whole example should be a oneliner:

gpg.core.Context().decrypt(sys.stdin, sink=sys.stdout)

>     signed = c.sign(text, mode=0)

Destructuring assignment,  please don't use magic values, use symbolic
modes instead.

>      text = text0.encode("utf-8")

UTF-8 is the default for .encode() and .decode().


Cheers,
Justus
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 487 bytes
Desc: not available
URL: <https://lists.gnupg.org/pipermail/gnupg-devel/attachments/20180315/70d41e66/attachment.sig>


More information about the Gnupg-devel mailing list