0

I'm using google-api-python-client==1.7.11

I had python 2 code which was working fine adapted from https://developers.google.com/gmail/api/v1/reference/users/messages/send

I converted my code to python 3, and saw a TypeError from urlsafe_b64encode below

My code:

# standard
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from base64 import urlsafe_b64encode

# pypi
from flask import current_app
from apiclient import errors

def sendmail(subject, fromaddr, toaddr, html, text='', ccaddr=None ):
    '''
    send mail
    :param subject: subject of email
    :param fromaddr: from address to use
    :param toaddr: to address to use, may be list of addresses
    :param html: html to send
    :param text: optional text alternative to send
    '''
    current_app.logger.info('sendmail(): from={}, to={}, cc={}, subject="{}"'.format(fromaddr, toaddr, ccaddr, subject))

    # Create message container - the correct MIME type is multipart/alternative.
    msg = MIMEMultipart('alternative')
    msg['Subject'] = subject
    msg['From'] = fromaddr
    if not isinstance(toaddr, list):
        toaddr = [toaddr]
    msg['To'] = ', '.join(toaddr)
    if ccaddr:
        if not isinstance(ccaddr, list):
            ccaddr = [ccaddr]
        msg['Cc'] = ', '.join(ccaddr)

    # Record the MIME types of both parts - text/plain and text/html.
    # Attach parts into message container.
    # According to RFC 2046, the last part of a multipart message, in this case
    # the HTML message, is best and preferred.
    if text:
        part1 = MIMEText(text, 'plain')
        msg.attach(part1)
    part2 = MIMEText(html, 'html')
    msg.attach(part2)

    # get credentials using the service account file
    gmail = gmail_service_account_login( current_app.config['APP_SERVICE_ACCOUNT_FILE'], 'librarian@steeplechasers.org' )

    try:
        if debug: current_app.logger.debug('sendmail(): msg.as_string()={}'.format(msg.as_string()))
        message = { 'raw' : urlsafe_b64encode(msg.as_string()) }
        sent = gmail.users().messages().send(userId='me', body=message).execute()
        return sent
    except errors.HttpError as error:
        current_app.logger.error('sendmail(): An error occurred: {}'.format(error))
        raise

I modified the code to use as_bytes(), similar to what is mentioned in How to solve "a bytes-like object is required, not 'str'" in create_message() function?

        message = { 'raw' : urlsafe_b64encode(msg.as_bytes()) }

Now I'm seeing a json serialization error when messages().send() is called. I see the google api indicates it supports python 3.6 on pypi. Not sure what additional mods I need to make to allow the gmail interface to work correctly.

Traceback (most recent call last):
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\app.py", line 1951, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\app.py", line 1820, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\views.py", line 89, in view
    return self.dispatch_request(*args, **kwargs)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\flask\views.py", line 163, in dispatch_request
    return meth(*args, **kwargs)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\contracts\views\frontend\eventscalendar.py", line 249, in post
    sendmail( subject, fromlist, tolist, html, ccaddr=cclist )
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\contracts\mailer.py", line 77, in sendmail
    sent = gmail.users().messages().send(userId='me', body=message).execute()
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\googleapiclient\discovery.py", line 789, in method
    actual_path_params, actual_query_params, body_value)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\googleapiclient\model.py", line 158, in request
    body_value = self.serialize(body_value)
  File "C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\venv\lib\site-packages\googleapiclient\model.py", line 267, in serialize
    return json.dumps(body_value)
  File "C:\Users\lking\AppData\Local\Programs\Python\Python36\lib\json\__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "C:\Users\lking\AppData\Local\Programs\Python\Python36\lib\json\encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Users\lking\AppData\Local\Programs\Python\Python36\lib\json\encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "C:\Users\lking\AppData\Local\Programs\Python\Python36\lib\json\encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'bytes' is not JSON serializable
Lou K
  • 1,128
  • 2
  • 12
  • 31

1 Answers1

0

Yes, I should have tried this before posting the question, but hopefully this will help someone else who is looking for this.

Based on Convert byte string to base64-encoded string (output not being a byte string) I tried decoding the bytes output from urlsafe_b64encode, thus

    message = { 'raw' : urlsafe_b64encode(msg.as_bytes()).decode("utf-8") }

My final code is

# standard
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from base64 import urlsafe_b64encode

# pypi
from flask import current_app
from apiclient import errors

def sendmail(subject, fromaddr, toaddr, html, text='', ccaddr=None ):
    '''
    send mail
    :param subject: subject of email
    :param fromaddr: from address to use
    :param toaddr: to address to use, may be list of addresses
    :param html: html to send
    :param text: optional text alternative to send
    '''
    current_app.logger.info('sendmail(): from={}, to={}, cc={}, subject="{}"'.format(fromaddr, toaddr, ccaddr, subject))

    # Create message container - the correct MIME type is multipart/alternative.
    msg = MIMEMultipart('alternative')
    msg['Subject'] = subject
    msg['From'] = fromaddr
    if not isinstance(toaddr, list):
        toaddr = [toaddr]
    msg['To'] = ', '.join(toaddr)
    if ccaddr:
        if not isinstance(ccaddr, list):
            ccaddr = [ccaddr]
        msg['Cc'] = ', '.join(ccaddr)

    # Record the MIME types of both parts - text/plain and text/html.
    # Attach parts into message container.
    # According to RFC 2046, the last part of a multipart message, in this case
    # the HTML message, is best and preferred.
    if text:
        part1 = MIMEText(text, 'plain')
        msg.attach(part1)
    part2 = MIMEText(html, 'html')
    msg.attach(part2)

    # get credentials using the service account file
    gmail = gmail_service_account_login( current_app.config['APP_SERVICE_ACCOUNT_FILE'], 'librarian@steeplechasers.org' )

    try:
        if debug: current_app.logger.debug('sendmail(): msg.as_string()={}'.format(msg.as_string()))
        message = { 'raw' : urlsafe_b64encode(msg.as_bytes()).decode("utf-8") }
        sent = gmail.users().messages().send(userId='me', body=message).execute()
        return sent
    except errors.HttpError as error:
        current_app.logger.error('sendmail(): An error occurred: {}'.format(error))
        raise
Lou K
  • 1,128
  • 2
  • 12
  • 31