2

I've a problem during exception-handling with the imapclient-library.

I tried to handle the LoginError like this:

source = IMAPClient(host=args.source_server, port=args.source_port, ssl=not args.source_no_ssl)

try:
    print('Login source...'.format(args.source_user), end='', flush=False)
    source.login(args.source_user, args.source_pass)
    print('OK')

except exceptions.LoginError as e:
    print('ERROR: {}'.format(e))
    exit()

In case of exception i've got this:

Login source...ERROR: b'Invalid login'

I think The problem is, that format is calling the __str__()-method of the Exception-object and do not try to decode.

So the main question is who can i convert this string

"b'Invalid login'"

to a normal bytes-object like this?

b'Invalid login'

edit 1

@lenik If i use e.message.decode() like this:

try:
    print('Login source...'.format(args.source_user), end='', flush=False)
    source.login(args.source_user, args.source_pass)
    print('OK')
except exceptions.LoginError as e:
    print('ERROR: {}'.format(e.message.decode()))
    exit()

I've got an AttributeError:

AttributeError: 'LoginError' object has no attribute 'message'

edit 2

@snakecharmerb

try:
    print('Login source...'.format(args.source_user), end='', flush=False)
    source.login(args.source_user, args.source_pass)
    print('OK')
except exceptions.LoginError as e:
    print('ERROR: {}'.format(e.args[0].decode()))
    exit()
AttributeError: 'str' object has no attribute 'decode'
Lukas
  • 43
  • 4

3 Answers3

2

imapclient's login method looks like this:

def login(self, username, password):
    """Login using *username* and *password*, returning the
    server response.
    """
    try:
        rv = self._command_and_check(
            'login',
            to_unicode(username),
            to_unicode(password),
            unpack=True,
        )
    except exceptions.IMAPClientError as e:
        raise exceptions.LoginError(str(e))

    logger.info('Logged in as %s', username)
return rv

We can see that it calls str on IMAPClientError, so if IMAPClientError was created with a bytes instance as its argument then we end up with stringified bytes in LoginError*.

There are two ways to deal with this:

  1. Access the original bytes via the exception's args tuple:

msg = e.args[0].decode()

  1. Use ast.literal_eval to convert the stringified exception:

msg = ast.literal_eval(str(e)).decode()

Of the two approaches, I think (1) is better in this specific case, but (2) is more generally applicable when you have stringified bytes.


*Looking at the history of the imaplib module on github, it looks as if it changed to explicitly decode error messages before raising errors from the authenticate command in Python 3.5. So another solution might be to upgrade to Python 3.5+.

snakecharmerb
  • 47,570
  • 11
  • 100
  • 153
  • Thanks! Your first way does not work. `e.args[0]` is already the wrong string (see edit2). – Lukas Aug 24 '19 at 11:15
0

Have you tried to do this:

>>> a = b'invalid'
>>> a
b'invalid'
>>> a.decode()
'invalid'

?


ok, second take:

>>> import imaplib
>>> dir(imaplib.IMAP4.error)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__getslice__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', '__weakref__', 'args', 'message']
>>> imaplib.IMAP4.error.message
<attribute 'message' of 'exceptions.BaseException' objects>

seems like there should be a message there, because LoginError seems to be a descendant from imaplib.IMAP4.error according to the source: https://imapclient.readthedocs.io/en/2.1.0/_modules/imapclient/exceptions.html#LoginError

You might want to print dir(e) where you catch the exception to see what it has -- there should be something that get's converted by __str__() into a byte string.


Then again, there's a conversation about IMAP4 and IMAPClient library and catching the exceptions here: Catching imaplib exception (using IMAPClient package) in Python

lenik
  • 23,228
  • 4
  • 34
  • 43
  • In my case `"b'Invalid login'"` is not a byte, its a string. So it has no `decode()` only `encode()`. You can reproduce it with `str(b'some bytes')`. You'll get `"b'some bytes'"` what is actually a string. – Lukas Aug 18 '19 at 10:24
  • Dear @Lukas -- I perfectly understand your problem. Just don't use `.format()` function before you `e.message.decode()` and you'll be golden =) – lenik Aug 18 '19 at 10:27
  • Hey lenik, i've modified the question. `e.message.decode()` is sadly not the solution :( – Lukas Aug 18 '19 at 10:42
  • @Lukas Sorry for messed up answer, please, check the "second take" – lenik Aug 18 '19 at 11:43
  • `__cause__` is empty / `None`. Even if i do `except IMAPClient.Error as e` which is scripted in the conversation. Output of `dir(e)`: `['__cause__', '__class__', '__context__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__suppress_context__', '__traceback__', '__weakref__', 'args', 'with_traceback']`. – Lukas Aug 18 '19 at 14:37
  • Sorry, @Lukas -- the message should be somewhere there, but I could not figure out right now where exactly. – lenik Aug 18 '19 at 16:17
0
  1. for "b'xx'" --> b'xx'
>>> s = "b'a'"
>>> eval(s)
b'a'
  1. You can use print('dir(e): {}'.format(dir(e))) to see the attributes, or pickle it to a file and load it to ipython to parse.
Waket Zheng
  • 5,065
  • 2
  • 17
  • 30
  • Thanks. Your answer will work, but in this (and most other cases) `ast.literal_eval()` is more safe to use. See [Using python's eval() vs. ast.literal_eval()?](https://stackoverflow.com/questions/15197673/using-pythons-eval-vs-ast-literal-eval). – Lukas Aug 24 '19 at 11:24