
Also, if you ask for 4 bytes from token_hex, do you get 4 hex digits or 8 (four bytes of entropy)?
[Steven D'Aprano]
I think the answer there has to be 8. I interpret Tim's reference to "same" as that the intent of token_hex is to call os.urandom(nbytes), then convert it to a hex string.
Absolutely. If we're trying to "fail safe", it's the number of unpredictable source bytes that's important, not the length of the string produced. And, e.g., in the case of a URL-safe base64 encoding, passing "number of characters in the string" would be plain idiotic ;-)
So the implementation might be as simple as:
def token_hex(nbytes): return binascii.hexlify(os.urandom(nbytes)) modulo a call to .decode('ascii') if we want it to return a string.
Nick Coghlan already posted implementation of these things, before this thread started. They're all easy, _provided that_ you know which obscure functions to call; e.g., def token_url(nbytes): return base64.urlsafe_b64encode(os.urandom(nbytes)).decode("ascii")