plaid/plaid-python

asset_report_get raising TypeError when asset report has warnings

darahayes opened this issue · 4 comments

We recently upgraded from plaid-python 7.x (non OpenAPI version) to plaid-python 9.1.0

Since the upgrade we are seeing a case where client.asset_report_get throws the following error:

TypeError: __init__() missing 1 required positional argument: 'error'

It looks like if an asset report contains any warnings in the warnings field, then the client fails to deserialize the response from the API correctly.

I cannot easily provide a way to reproduce but I'll try to provide as much info as possible.

In our application we listen for the assets webhook:

{
"asset_report_id": "xxx--xxx--xxx", 
"webhook_code": "PRODUCT_READY", 
"webhook_type": "ASSETS"
}

during the handling of that webhook, we use client.asset_report_get to get the contents of the report and determine if we need to apply filtering.

We saw an example where after receiving a PRODUCT_READY notification for a report, when we tried to get the report we got the TypeError mentioned above. I can see the response that came back from the plaid API looked like this:

{
  report: {
    asset_report_id: 'xxxx', 
    client_report_id: None, 
    date_generated: '2022-03-23T11:53:28Z', 
    days_requested: 19, 
    items: [...], 
    user: {}
  }, 
  request_id: 'qh5EJhxemMpgEsS', 
  warnings: [
    {
      cause: {"display_message":"'This financial institution is not currently responding to requests. We apologize for the inconvenience.'","error_code":"'INSTITUTION_NOT_RESPONDING'","error_message":"'this institution is not currently responding to this request. please try again soon'","error_type":"'INSTITUTION_ERROR'","item_id":"'xxxxxx'"}, 
      warning_code: 'OWNERS_UNAVAILABLE', 
      warning_type: 'ASSET_REPORT_WARNING'
    }
  ]
}

The stack trace was this:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<console>", line 1, in <module>
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/api_client.py", line 769, in __call__
    return self.callable(self, *args, **kwargs)
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/api/plaid_api.py", line 1308, in __asset_report_get
    return self.call_with_http_info(**kwargs)
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/api_client.py", line 831, in call_with_http_info
    return self.api_client.call_api(
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/api_client.py", line 406, in call_api
    return self.__call_api(resource_path, method,
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/api_client.py", line 221, in __call_api
    return_data = self.deserialize(
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/api_client.py", line 322, in deserialize
    deserialized_data = validate_and_convert_types(
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1354, in validate_and_convert_types
    converted_instance = attempt_convert_item(
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1241, in attempt_convert_item
    return deserialize_model(input_value, valid_class,
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1161, in deserialize_model
    return model_class(**kw_args)
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1526, in wrapped_init
    return fn(self, *args, **kwargs)
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model/asset_report_get_response.py", line 180, in __init__
    self.warnings = warnings
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 165, in __setattr__
    self[attr] = value
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 350, in __setitem__
    self.set_attribute(name, value)
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 137, in set_attribute
    value = validate_and_convert_types(
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1404, in validate_and_convert_types
    input_value[index] = validate_and_convert_types(
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1354, in validate_and_convert_types
    converted_instance = attempt_convert_item(
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1241, in attempt_convert_item
    return deserialize_model(input_value, valid_class,
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1161, in deserialize_model
    return model_class(**kw_args)
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1526, in wrapped_init
    return fn(self, *args, **kwargs)
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model/warning.py", line 183, in __init__
    self.cause = cause
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 165, in __setattr__
    self[attr] = value
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 350, in __setitem__
    self.set_attribute(name, value)
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 137, in set_attribute
    value = validate_and_convert_types(
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1354, in validate_and_convert_types
    converted_instance = attempt_convert_item(
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1241, in attempt_convert_item
    return deserialize_model(input_value, valid_class,
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1161, in deserialize_model
    return model_class(**kw_args)
  File "/<redacted>/venv/lib/python3.9/site-packages/plaid/model_utils.py", line 1526, in wrapped_init
    return fn(self, *args, **kwargs)
TypeError: __init__() missing 1 required positional argument: 'error'

I was able to capture more detail in Sentry. I can see the failure comes from trying to deserialise the cause part of the warning.

The call to deserialize_model that failed had the following args



check_type=True

configuration=<plaid.configuration.Configuration object at 0x7f5733d00790>

kw_args={_check_type: True, _configuration: <plaid.configuration.Configuration object at 0x7f5733d00790>, _path_to_item: ['received_data', 'warnings', 0, 'cause'], _spec_property_naming: True, display_message: 'This financial institution is not currently responding to requests. We apologize for the inconvenience.', error_code: 'INSTITUTION_NOT_RESPONDING', error_message: 'this institution is not currently responding to this request. please try again soon', error_type: 'INSTITUTION_ERROR', item_id: 'v66OZpaA48TRXb1vveBrT450YdENeBUmPRMAO'}

model_class=<class 'plaid.model.cause.Cause'>

model_data={display_message: 'This financial institution is not currently responding to requests. We apologize for the inconvenience.', error_code: 'INSTITUTION_NOT_RESPONDING', error_message: 'this institution is not currently responding to this request. please try again soon', error_type: 'INSTITUTION_ERROR', item_id: 'v66OZpaA48TRXb1vveBrT450YdENeBUmPRMAO'}
path_to_item=['received_data', 'warnings', 0, 'cause']

spec_property_naming=True

Thank you for the super detailed error report, it was very helpful! It looks like the root cause here is probably that our API specification used to generate this library misrepresents the schema of the cause object -- it specifies it as an item_id and a nested error object when your copy and paste of the response data shows that the error object is actually "flattened" in the response, rather than nested. I'm working on a fix now. Do you guys need a hotfix urgently, or is it ok if we wait for the next regularly scheduled release in ~3 weeks?

I don't want to be too pushy, if we could get a fix that would be ideal, but we could probably wait too. This is a bit off topic to the original issue but the reason we decided to upgrade to the OpenAPI based client is because we were migrating to the Account Select V2 feature which according to the docs - "by March 2022, we will automatically migrate all of your customizations to Account Select v2"

We made all the preparations and enabled it for all of our plaid link customizations in production but we had missed the one recommendation which was to upgrade our python client to at least 8.3.0 (we were on 7.x).

We noticed after enabling account select v2 with the old python client, that in the plaid link flow, users who were redirected back to our application after authenticating with an Oauth institution would be presented with an account selection screen within plaid link after after they had already been presented with an account selection screen on the bank side. We reached out to plaid support and they confirmed that a user who goes through the Oauth flow shouldn't see a second account selection step in plaid link. Their recommendation was to update our plaid python client.

To be honest I don't really understand this recommendation given that the flow is entirely frontend and even though we're on an old python client, we're targeting the latest Plaid API. Unless there is something different about how the newer clients call out to create the plaid link token or the API is doing something differently based on the client version in the request headers.

Again - this is off topic but if you know of any workarounds to get the desired plaid link frontend experience using the outdated client, then we'd be happy to try that out while you get a release out on your regular schedule!

The only reason we recommend upgrading the client library is to enable use of the update.account_selection_enabled parameter when initializing Link for Update Mode, in order to use Update Mode to pick up new accounts detected by the NEW_ACCOUNTS_AVAILABLE webhook. This parameter is not supported in older client libraries. If you aren't using this, you can continue to use older client libraries.

While it is true that we can sometimes skip the Plaid Link account selection pane after OAuth account selection, the business logic around this is quite complicated and we do not always skip the pane -- it depends on things like whether you are using multi-select or single-select, whether you use account filtering, and how the specific institution that's being connected to implements their OAuth account selection options. The fact that the pane isn't being skipped is likely expected and shouldn't have anything to do with the client library.

This issue is now fixed. Closing.