sendgrid/sendgrid-python

AttributeError: 'str' object has no attribute 'get' when sending with batch_id

jordaniza opened this issue · 6 comments

EDIT: Condensed all example code way down

Issue Summary

The Mail object invokes the get() method upon sending.

This in turn creates a JSON representation of the mail object, by calling _get_or_none(self.attr) for all provided attributes.

While the functionality works correctly more most attributes, if one attaches a batch_id to the Mail object, the _get_or_none method throws an AttributeError: 'str' object has no attribute 'get'

What's interesting is that other attributes do not face the same issue (send_at, for example - I appreciate it's an integer but also has no get() method), upon closer inspection it appears send_at has a more sophisticated getter and setter and returns an actual SendAt object, so I think the problem is that the API is missing a BatchId object.

Steps to Reproduce

  • Initialise the Mail helper
  • Add a batch_id attribute
  • Call sg.send on the instance
  • Enjoy

###Code Snippet

   message = Mail(
     # init message here
    )
   message.batch_id = 'iamabatchid'
   response = sg.send(message)

Exception/Log

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <sendgrid.helpers.mail.mail.Mail object at 0x0000013D0E464280>, from_obj = 'YWUwNWQ0Y2MtYjk4MS0xMWViLWIxYzctZDZkMjJjNTU5YTcyLWY1Y2EyMGYyYw'

    def _get_or_none(self, from_obj):
        """Get the JSON representation of the object, else return None

        :param from_obj: Get the JSON representation of the object,
        else return None
        :type from_obj: obj
        """
>       return from_obj.get() if from_obj is not None else None
E       AttributeError: 'str' object has no attribute 'get'

venv\lib\site-packages\sendgrid\helpers\mail\mail.py:133: AttributeError

Technical details:

  • sendgrid-python version: 6.7.0
  • python version: 3.8

Workarounds so far:

  • Use the JSON API directly ( 👎 )
  • Create a simple wrapper for the batch_id with a get method:
class BatchId:
    def __init__(self, id: str):
         self.id = id
    
    def get(self) -> str:
        return self.id

You can then attach the BatchId object to the Mail object.

batch_id = BatchId('id')
message.batch_id = batch_id

EDIT: On reflection, suspect a batch id object is what's needed, based on the other setters for the mail object - happy to look into a suggested code change if needed.

On inspection, turns out there is an existing BatchId object defined in the API, but it's not been added as a setter.

Would welcome input from the development team as to the correct way to use the following fields:

  • Mail.batch_id
  • Mail.categories
  • Mail.custom_args

Namely, are we to attach the Category, CustomArg and BatchId objects directly to the Mail object, or is there a preferred way to use these fields, that I am missing?

Cheers

Hello @jordaniza,

Thanks for taking the time to express this issue in great detail!

The BatchId should be set via: message.batch_id = BatchId("HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi") per this full example and here is the setter.

Does that work for you?

With best regards,

Elmer

Thanks @thinkingserious,

Yes, good to know that's the right approach.

I clearly missed the Kitchen Sink file. I was looking for examples on how to use the API in the /examples folder, but all of said examples use JSON payloads, instead of the Python Objects.

Thanks for sharing the example folder - I would be happy to prepare an addendum to the README if it would help other users save some time.

@jordaniza,

Awesome! We would certainly appreciate a PR :) Thanks!!

If you are trying it on a pandas Dataframe, Use to_json()
It converts String column into proper json format, then you can use the get fn.
It works! :)