Giter VIP home page Giter VIP logo

plaid-python's Introduction

plaid-python PyPI version

The official python client library for the Plaid API, which is generated from our OpenAPI spec.

Table of Contents

Installation

This library only supports python3!

$ pip3 install plaid-python

Versioning

This release only supports the latest Plaid API version, 2020-09-14.

For information about what has changed between versions and how to update your integration, head to the API upgrade guide.

The plaid-python client library is typically updated on a monthly basis. The canonical source for the latest version number is the client library changelog. New versions are published as GitHub tags, not as Releases. New versions are also published on PyPi. Plaid uses semantic versioning to version the client libraries, with potentially breaking changes being indicated by a major version bump.

All users are strongly recommended to use a recent version of the library, as older versions do not contain support for new endpoints and fields. For more details, see the Migration Guide.

Getting Started

Calling Endpoints

To call an endpoint you must create a PlaidApi object.

import plaid
from plaid.api import plaid_api

# Available environments are
# 'Production'
# 'Development'
# 'Sandbox'
configuration = plaid.Configuration(
    host=plaid.Environment.Sandbox,
    api_key={
        'clientId': client_id,
        'secret': secret,
    }
)

api_client = plaid.ApiClient(configuration)
client = plaid_api.PlaidApi(api_client)

Each endpoint returns a dictionary which contains the parsed JSON from the HTTP response.

Errors

All non-200 responses will throw a plaid.ApiException.

import plaid
from plaid.model.asset_report_get_request import AssetReportGetRequest

try:
    request = AssetReportGetRequest(
        asset_report_token=asset_report_token,
    )
    return client.asset_report_get(request)
except plaid.ApiException as e:
    response = json.loads(e.body)
    # check the code attribute of the error to determine the specific error
    if response['error_code'] == 'ITEM_LOGIN_REQUIRED':
        # the users' login information has changed, generate a public_token
        # for the user and initialize Link in update mode to
        # restore access to this user's data
        # see https://plaid.com/docs/api/#updating-items-via-link
    else:
        ...

For more information on Plaid response codes, head to the docs.

Converting the response to a JSON

As this is a common question, we've included this in the README. plaid-python uses models like TransactionsSyncResponse to encapsulate API responses. If you want to convert this to a JSON, do something like this:

import json
...
response = ... # type TransactionsSyncResponse
# to_dict makes it first a python dictionary, and then we turn it into a string JSON.
json_string = json.dumps(response.to_dict(), default=str)

Dates

Dates and datetimes in requests, which are represented as strings in the API and in previous client library versions, are represented in this version of the Python client library as Python datetime.date or datetime.datetime objects. If you need to convert between dates and strings, you can use the datetime.strptime method. For an example, see the Retrieve Transactions sample code later in this Readme. For more information on the Python's datetime module, see Python's official documentation.

Note that the datetime.strptime method will silently remove time zone information. Time zone information is required for request fields that accept datetimes. Failing to include time zone information (or passing in a string, instead of a datetime.datetime object) will result in an error. See the following examples for guidance on datetime.date and datetime.datetime usage.

If the API reference documentation for a field specifies format: date, either of following are acceptable:

from datetime import date

a = date(2022, 5, 5)
b = date.fromisoformat('2022-05-05')

If the API reference documentation for a field specifies format: date-time, the following is acceptable:

from datetime import datetime

a = datetime(2022, 5, 5, 22, 35, 49, tzinfo=datetime.timezone.utc)

Examples

For more examples, see the test suites, Quickstart, or API Reference documentation.

Create an Item using Link

Exchange a public_token from Plaid Link for a Plaid access token:

import plaid
from plaid.model.item_public_token_exchange_request import ItemPublicTokenExchangeRequest

# the public token is received from Plaid Link
exchange_request = ItemPublicTokenExchangeRequest(
    public_token=pt_response['public_token']
)
exchange_response = client.item_public_token_exchange(exchange_request)
access_token = exchange_response['access_token']

Remove Item

import plaid
from plaid.model.item_remove_request import ItemRemoveRequest

# Provide the access token for the Item you want to remove
request = ItemRemoveRequest(
    access_token=accessToken
)
response = client.item_remove(request)

Retrieve Transactions (preferred method)

import plaid
from plaid.model.transactions_sync_request import TransactionsSyncRequest

request = TransactionsSyncRequest(
    access_token=access_token,
)
response = client.transactions_sync(request)
transactions = response['added']

# the transactions in the response are paginated, so make multiple calls while incrementing the cursor to
# retrieve all transactions
while (response['has_more']):
    request = TransactionsSyncRequest(
        access_token=access_token,
        cursor=response['next_cursor']
    )
    response = client.transactions_sync(request)
    transactions += response['added']

Retrieve Transactions (older method)

import plaid
from plaid.model.transactions_get_request_options import TransactionsGetRequestOptions
from plaid.model.transactions_get_request import TransactionsGetRequest

request = TransactionsGetRequest(
    access_token=access_token,
    start_date=datetime.strptime('2020-01-01', '%Y-%m-%d').date(),
    end_date=datetime.strptime('2021-01-01', '%Y-%m-%d').date(),
)
response = client.transactions_get(request)
transactions = response['transactions']

# the transactions in the response are paginated, so make multiple calls while increasing the offset to
# retrieve all transactions
while len(transactions) < response['total_transactions']:
    options = TransactionsGetRequestOptions()
    options.offset = len(transactions)

    request = TransactionsGetRequest(
        access_token=access_token,
        start_date=datetime.strptime('2020-01-01', '%Y-%m-%d').date(),
        end_date=datetime.strptime('2021-01-01', '%Y-%m-%d').date(),
        options=options
    )
    response = client.transactions_get(request)

Retrieve Asset Report PDF

from plaid.model.asset_report_pdf_get_request import AssetReportPDFGetRequest

pdf_request = AssetReportPDFGetRequest(asset_report_token=PDF_TOKEN)
pdf = client.asset_report_pdf_get(pdf_request)
FILE = open('asset_report.pdf', 'wb')
FILE.write(pdf.read())
FILE.close()

Migration guide

8.0.0 or later to latest

Migrating from version 8.0.0 or later of the library to a recent version should involve very minor integration changes. Many customers will not need to make changes to their integrations at all. To see a list of all potentially-breaking changes since your current version, see the client library changelog and search for "Breaking changes in this version". Breaking changes are annotated at the top of each major version header.

Pre-8.0.0 to latest

Version 8.0.0 of the client library was released in August 2021 and contains multiple interface changes, as described below.

Client initialization

From:

from plaid import Client
Client(
    client_id=os.environ['CLIENT_ID'],
    secret=os.environ['SECRET'],
    environment='sandbox',
    api_version="2020-09-14",
    client_app="plaid-python-unit-tests"
)

To:

import plaid
from plaid.api import plaid_api
configuration = plaid.Configuration(
    host=plaid.Environment.Sandbox,
    api_key={
        'clientId': client_id,
        'secret': secret,
        'plaidVersion': '2020-09-14'
    }
)
api_client = plaid.ApiClient(configuration)
client = plaid_api.PlaidApi(api_client)

Endpoints

All endpoint requests now take a request model and the functions have been renamed to include _.

From:

response = client.Auth.get(access_token)

To:

import plaid
from plaid.model.auth_get_request import AuthGetRequest
from plaid.model.auth_get_request_options import AuthGetRequestOptions

ag_request = AuthGetRequest(
    access_token=access_token
)

response = client.auth_get(ag_request)

Errors

From:

try:
    client.Auth.get(access_token)
except ItemError as e:
    if e.code == 'ITEM_LOGIN_REQUIRED':
    else:
        ...
except APIError as e:
    if e.code == 'PLANNED_MAINTENANCE':
        # inform user
    else:
        ...

To:

try:
    request = AssetReportGetRequest(
        asset_report_token=asset_report_token,
    )
    return client.asset_report_get(request)
except plaid.ApiException as e:
    response = json.loads(e.body)
    if response['error_code'] == 'ITEM_LOGIN_REQUIRED':
    else:

Data type changes

See the sections above on Dates and Converting the response to a JSON.

Enums

While the API and previous library versions prior to 8.0.0 represent enums using strings, this current library uses Python classes with restricted values.

From:

'products': ['auth', 'transactions'],
'country_codes': ['US'],

To:

products=[Products('auth'), Products('transactions')],
country_codes=[CountryCode('US')],

Configuration options

Some configuration options, including request timeouts, have moved from global (client-level) to being specified a per-request level, and additional configuration options have been added.

Global configuration options: configuration.py Per-request configuration options: api_client.py

From:

class PlaidClient(plaid.Client):
   def __init__(
       self,
      ...
       timeout=60
   ):
   ...

To:

response = client.accounts_balance_get(request, _request_timeout=60)

Contributing

Please see Contributing for guidelines and instructions for local development.

License

MIT

plaid-python's People

Contributors

aarohmankad avatar benjamts avatar bpless avatar cgfarmer4 avatar charleswli avatar chrisforrette avatar ctrembl avatar davidchambers avatar davidzhanghp avatar dsfish avatar erikbern avatar gae123 avatar gargavi avatar jking-plaid avatar joyzheng avatar jzheng-plaid avatar lucamartinetti avatar mattiskan avatar mattnguyen1 avatar michaelckelly avatar nathankot avatar notthefakestephen avatar otherchen avatar pbernasconi avatar rayraycano avatar skylarmb avatar stephenjayakar avatar thomasw avatar vorpus avatar zperret avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

plaid-python's Issues

Support for custom request headers/fields

plaid-python doesn't give us the functionality to add custom headers/fields to requests, which we need for routing requests through our secure network layer. As a workaround, we point to our fork of plaid-python that adds that functionality.

We'd love to see this functionality in plaid-python so we can stop maintaining our fork. Specifically for our use case, this is the ability to add custom request headers and pass values for proxies and verify to python requests. Thanks!

Feature Request: Optional timeout override on a per-call basis

Currently we can configure the timeout used for the Plaid client as a whole by passing in a setting when the client is created. All calls made with that client will then use that timeout.

In some cases it is desirable to have different timeouts for different kinds of operations. It allows "fast" operations to time out quickly if something is wrong, while letting "slow" ones take all the time they need. It would therefore be helpful to have the option to set timeouts on a per-call basis.

This might look like an optional parameter to every API call (e.g. by default timeout=None), which, if set, would override the globally-configured default value. If not set, the usual default would be used.

Thanks for considering it!

ConnectionError when running Python Quickstart code

Hello,

I get this error when trying to run the python quickstart code on this github. I also tried running the node version, which returned a 400 INVALID_REQUEST error, with error code INVALID_FIELD.

Specifically, here is the screenshot when I try to log in using user_good on sandbox mode:

screen shot 2017-06-22 at 10 12 00 am

Does anyone know what could be the issue? Aside from adding my api keys, I have not modified the code. I have used pip to install all the relevant packages.

Thanks!
Sam

Support for pending transactions?

It seems support for pending transactions must pass through to the /connect endpoint as per the docs:

http://i.nick.sg/image/321k2Q0g353N/Image%202015-11-30%20at%202.05.46%20PM.png

However, passing this option like this does not appear to return pending transactions:

response = client.connect('usaa', {
    'username': 'user',
    'password': 'pass',
    'pin': '1234'
}, options={
    'pending': True
})

This Python lib seems to want the pending arg on connect_get here: https://github.com/plaid/plaid-python/blob/master/plaid/client.py#L390, but that doesn't appear to work, either.

Not sure if this issue is with plaid-python's implementation or an issue with the API.

SSLError: EOF occurred in violation of protocol (_ssl.c:581)

I get this error both when using the library to make API calls and when using the requests library directly: SSLError: EOF occurred in violation of protocol (_ssl.c:581)

I am able to get a 200 response via curl and also when using the production api endpoint.

Institution Listing

I pulled this code from the documentation:

# Pull 50 institutions that support both Auth and Connect
institutions = client.institutions(count=50, offset=0, products=["auth","connect"]).json()

But that results in the below error:

TypeError: institutions() got an unexpected keyword argument 'count'

Expecting value: line 1 column 1 (char 0)

My code is calling plaid_client.Transactions.get(token, self._start_date, self._end_date, offset=len(transactions)) which mostly has been working great but occasionally throws the following error:

Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.948010 2017] [:error] [pid 12503] An error occurred while resolving field Mutation.sync
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950807 2017] [:error] [pid 12503] Traceback (most recent call last):
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950824 2017] [:error] [pid 12503] File "/opt/python/run/venv/local/lib/python3.4/site-packages/graphql/execution/executor.py", line 311, in resolve_or_error
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950835 2017] [:error] [pid 12503] return executor.execute(resolve_fn, source, info, **args)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950845 2017] [:error] [pid 12503] File "/opt/python/run/venv/local/lib/python3.4/site-packages/graphql/execution/executors/sync.py", line 7, in execute
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950855 2017] [:error] [pid 12503] return fn(*args, **kwargs)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950865 2017] [:error] [pid 12503] File "/opt/python/current/app/moirai/gql/sync.py", line 23, in mutate
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950874 2017] [:error] [pid 12503] state = record.store_transactions(access_token)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950885 2017] [:error] [pid 12503] File "/opt/python/current/app/moirai/db/user_record.py", line 43, in store_transactions
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950894 2017] [:error] [pid 12503] token=access_token, since=last_updated)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950904 2017] [:error] [pid 12503] File "/opt/python/current/app/moirai/db/plaid_connection.py", line 48, in get_transactions_and_accounts
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950914 2017] [:error] [pid 12503] return self._get_transactions(token=token, since=since)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950934 2017] [:error] [pid 12503] response = self._client.Transactions.get(token, START_DATE, self._end_date, offset=len(transactions))
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950944 2017] [:error] [pid 12503] File "/opt/python/run/venv/local/lib/python3.4/site-packages/plaid/api/transactions.py", line 49, in get
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950954 2017] [:error] [pid 12503] "options": options,
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950963 2017] [:error] [pid 12503] File "/opt/python/run/venv/local/lib/python3.4/site-packages/plaid/client.py", line 83, in post
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950973 2017] [:error] [pid 12503] return self._post(path, post_data)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950983 2017] [:error] [pid 12503] File "/opt/python/run/venv/local/lib/python3.4/site-packages/plaid/client.py", line 101, in _post
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.950993 2017] [:error] [pid 12503] timeout=self.timeout
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951002 2017] [:error] [pid 12503] File "/opt/python/run/venv/local/lib/python3.4/site-packages/plaid/requester.py", line 33, in http_request
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951012 2017] [:error] [pid 12503] response_body = json.loads(response.text)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951021 2017] [:error] [pid 12503] File "/usr/lib64/python3.4/json/__init__.py", line 318, in loads
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951031 2017] [:error] [pid 12503] return _default_decoder.decode(s)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951040 2017] [:error] [pid 12503] File "/usr/lib64/python3.4/json/decoder.py", line 343, in decode
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951050 2017] [:error] [pid 12503] obj, end = self.raw_decode(s, idx=_w(s, 0).end())
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951060 2017] [:error] [pid 12503] File "/usr/lib64/python3.4/json/decoder.py", line 361, in raw_decode
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951069 2017] [:error] [pid 12503] raise ValueError(errmsg("Expecting value", s, err.value)) from None
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951084 2017] [:error] [pid 12503] ValueError: Expecting value: line 1 column 1 (char 0)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.951105 2017] [:error] [pid 12503]
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.957345 2017] [:error] [pid 12503] Traceback (most recent call last):
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.957380 2017] [:error] [pid 12503] File "/opt/python/run/venv/local/lib/python3.4/site-packages/graphql/execution/executor.py", line 330, in complete_value_catching_error
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.957856 2017] [:error] [pid 12503] exe_context, return_type, field_asts, info, result)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.957885 2017] [:error] [pid 12503] File "/opt/python/run/venv/local/lib/python3.4/site-packages/graphql/execution/executor.py", line 383, in complete_value
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.958301 2017] [:error] [pid 12503] raise GraphQLLocatedError(field_asts, original_error=result)
Dec 07 04:12:44pm ip-172-30-1-22 error_log : [Thu Dec 07 16:33:42.958356 2017] [:error] [pid 12503] graphql.error.located_error.GraphQLLocatedError: Expecting value: line 1 column 1 (char 0)

Plaid is trying to call json.loads on response.text, which is apparently ''.

I'm using plaid-python version 2.0.2.

Async Support

I will be using this for a personal project using async. As it stands currently, it does not support async at all. I managed a hack to get it to work for the time being, but it is sort of ugly using a hacky check and whatnot.

I would like to add proper async support, using with https://github.com/aio-libs/aiohttp . But as this will be a big change, I want to see up front whether or not this kind of change (PR) would be accepted here, or if I should make my own library separate.

The reason this would require a lot of changes is because the I/O is coupled to every class that makes a call to the plaid API. To make it properly support both synchronous and async methods, the I/O needs to be removed and passed in as a parameter (though, defaulting the the default non async class). So a handful of changes need to be done.

I would like to contribute back and make this available here for every one to use. But I want to make sure it is something that will be accepted. Due to the nature of the change. I am fine maintaining my own version, mind you. I just thought I would try here directly first and to get your thoughts.

Income and Risk Support

I am able to use the Plaid Link to authenticate and pull user data, but I am having trouble getting all the information that the API reference says is available.

Currently I can use:

  • info_get() to find personal information
  • auth_get() to find balances, account information
  • connect_get() for transaction information

Am I just not seeing the documentation for risk and income or is that a WiP?

Thanks!

AttributeError: module 'plaid' has no attribute 'Client'

Hi, I'm trying to embed your quick start code into my program. This error

  client = plaid.Client(client_id = PLAID_CLIENT_ID, secret=PLAID_SECRET,
AttributeError: module 'plaid' has no attribute 'Client'

happens when I combined your code into my program even though I run the quick start code successfully. I actually just copied the whole program. How can I fix this error?

Version locking requests dependency in plaid-python and plaid-python-legacy

Currently plaid-python and plaid-python-legacy both have a sub-dependency of requests and it is locked to a specific version ==2.7.0.

It is extremely rare to have a library lock a sub-dependency like this, as it causes all sorts of issues with sub-dependency clashes (in fact, our application has over 50 packages, and 0 have a fully locked sub-dependency, all are >= other than plaid-python), and it locks the users of your library out of valuable bug fixes that have been implemented since 2.7.0 was released in May of 2015.

Finally, requests will not introduce any breaking changes except on major version updates (http://docs.python-requests.org/en/master/community/release-process/).

For those reasons, I would recommend you change your dependency on requests to >=2.7.0, or <3.0.0,>=2.7.0 if you want to be safer.

Change log is missing

I couldn't find the detailed log of library changes, is there one? I find upgrading the library without referring to a change log unsettling, it's not clear what can go wrong.

suppress_warnings flag clarification

In client.py the suppress_warnings flag returns a warning ...Swap out url for https://api.plaid.com... if not set to True upon client initialization. After reviewing the code it seems this flag must be set to True in order to use the development environment. However, I don't see it mentioned anywhere else in the code.

Perhaps this flag could be named something more indicative of its purpose. I am not clear if I am actually suppressing warnings the api would have returned otherwise. Also, the url contained in the warning message is a bit unclear. I believe the urls follow an https://environment.plaid.com scheme, so I am not sure why it mentions swapping out the url for https://api.plaid.com.

I may be missing something so if anyone can provide clarification on this or point me to any documentation where it is mentioned I will close the issue. Thanks for all your work on the client.

Should exceptions raised by underlying libraries be wrapped or passed through?

Currently, if an library used within the Plaid client throws an exception, it won't always be caught, wrapped, and re-thrown as a PlaidError or subclass. For example, requests may throw a ConnectionError when there's a network problem.

This isn't necessarily an issue, but may rather just be a design choice in the library.

On the one hand, it makes it a bit difficult for users, who won't necessarily know what libraries are used internally, or what exceptions they may thrown. On the other hand, you don't control what exceptions the libraries you use throw, and you can't necessarily anticipate and catch them all, so users may have to be prepared for the eventuality that one leaks out anyway, and in that case, the supposed benefit from catching and wrapping them all is reduced.

Do you have a policy/preference/philosophy either way? If we see an exception thrown by an inner library, should we consider that a bug and report it as an issue?

In particular, we've seen the ConnectionError from requests case actually come up in a context where we only expected PlaidError (and subclasses). It originally came from

def _requests_http_request(url, method, data, timeout=DEFAULT_TIMEOUT):
normalized_method = method.lower()
if normalized_method in ALLOWED_METHODS:
return getattr(requests, normalized_method)(
url,
json=data,
headers={
'User-Agent': 'Plaid Python v{}'.format(__version__),
},
timeout=timeout,
)
else:
raise Exception(
'Invalid request method {}'.format(method)
)

Would you consider that in this case this library should check for that and re-raise it as a PlaidError?

Thanks!

Return Decimals instead of floats?

Hi. I was playing with the SDK and noticed I was getting float values back from the calls.
This means that if you try to use these numbers you might hit weird floating point math issues.

A quick solution is to add parse_float=Decimal to json.loads would that be possible, or at least configurable?

Options: lte and gte

I am trying to pull transaction data from fixed dates, but I am not sure where to pass in the lte and gte.

New release

Hi,

There have been a couple of bugfixes since the last release that would be good to incorporate into our system. Would it be possible to cut a new bugfix/minor release so we can upgrade? Thanks!

README contains erroneous code

In the README, there is a section with the following code:

from plaid import Client

client = Client.config({
    'url': 'https://tartan.plaid.com',
    'suppress_http_errors': True,
})
response = client.connect('bofa', {
    'username': '[something_invalid]',
    'password': '***'
})

However, this doesn't actually work properly, as config() doesn't return a Client instance. It would have to look more like:

from plaid import Client

Client.config({
    'url': 'https://tartan.plaid.com',
    'suppress_http_errors': True,
})
client = Client('test_id', 'test_secret')
response = client.connect('bofa', {
    'username': '[something_invalid]',
    'password': '***'
})

Alternatively, correct me if I'm wrong, but couldn't I just use:

from plaid import Client

client = Client('test_id', 'test_secret')
client.base_url = 'https://tartan.plaid.com'
client.suppress_http_errors = True
response = client.connect('bofa', {
    'username': '[something_invalid]',
    'password': '***'
})

Support /auth endpoint

It'd be great if I could use this library to register an auth user and grab their details.

pypi release?

Are there any plans to release this package on pypi? I noticed that a package exists out there but it does not have any distributions.

client ID is only authorized to use Plaid Link

I'm trying to create an Item through the python library, not using link, which seems to be supported according to the present code. However, the response contains the following error:

plaid.errors.InvalidInputError: your client ID is only authorized to use Plaid Link. head to the docs (https://plaid.com/docs) to get started.

Is there any way to create an Item without using Link? Might be related to this?

How to debug PlaidServerError

Using version (1.3.0). I started getting the error PlaidServerError: An unexpected error has occurred on our systems; we've been notified and are looking into it! when running plaid_client.exchange_token(public_token). I'm not sure what's the cause of this error and would appreciate any suggestions as to what's happening. We started seeing this last weekend.

Readme misleading for App Engine requests

On GAE plaid-python uses urlfetch, which returns a _URLFetchResult object. This object has no json() method.

Readme should read (for the GAE case):

client = Client(client_id='***', secret='***', access_token='usertoken')
response = client.connect_get()
if response.status_code == 200:
    transactions = json.loads(response.content)

HTTP request helper parses response as JSON without checking for errors

The http_request helper method in requester.py unconditionally attempts to parse the response as JSON:

response_body = json.loads(response.text)

By "unconditionally" I mean that it doesn't first check the HTTP response status code, nor is the json.loads call wrapped in a try/except block that would allow it to handle failures.

This means that if you get a response like the following, you get a JSONDecodeError:

<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx</center>
</body>
</html>

Ideally, the method should either/both check the HTTP status code and/or catch exceptions from json.loads, and raise a custom exception as appropriate, without leaking the JSONDecodeError.

Schwab Support

Hello! I noticed on the reference guide, that schwab is listed as a supported institution, yet I get an error saying it is not supported using the python library and plain old curl. I just thought I would give the heads up so you can update your guide to avoid other confused users!

README Examples Don't Work

Two README examples fail. One:

from plaid import Client
from plaid import errors as plaid_errors
from plaid.utils import json


client = Client()
institutions = json.loads(client.institutions().content)
categories = json.loads(client.categories().content)

And it breaks cause the Client doesn't actually allow no params for client_id and secret. Even if I use a Client with a legitimate params, as below it will fail because of json.loads (see below).

I also tried this example (with my actual client, username, password, etc):

from plaid import Client
from plaid import errors as plaid_errors
from plaid.utils import json


client = Client(client_id='***', secret='***')
account_type = 'bofa'

try:
    response = client.connect(account_type, {
     'username': '***',
     'password': '***'
    })
except plaid_errors.PlaidError:
     pass
else:
    connect_data = json.loads(response.content)

This works with a 200 but response.content is a byte object and not a string. So json.loads fails. This happens using both python's json library and plaid's json util. response.text and response.json() still give me what I want so there's a workaround.

I'm using python 3.4 and it looks like you guys added support for that in the 1.10.

Tests for python 3.7

Python 3.7 has been released for months.

Surely we should have tests run for 3.7?

Dropping python 2.x support (tag 2.x compatible version?)

I am curious to your opinion on dropping python 2.x support in this library. As it stands, python 2 will be End of Life on PyCon 2020. And python 3 usage has surpassed 2.x usage. Most libraries (and now django 2.0) is dropping support for python 2 and supporting 3.4+ (my preference is for 3.5+ for type annotations).

As in another issue I posted about adding async support, I would like to use more modern language features. Not supporting python 2.x will make that much easier.

I know some people are probably using this with 2.x, so I expect some resistance with this. It is fine. I am still debating whether I want to make a dedicated async only fork of plaid-python, which would be easiest. Or to make a PR to this repo to add support for async but still need to keep python 2.x support.

This is why I have these 2 issues. I am willing to do much of the work for this. As otherwise, I will be forking it anyways.

CI testing should be in place

It looks like this library has tests setup in place - but I can't find travis or circleci tests running anywhere.

Support for institution search?

It appears there is no way to use this library to perform a search against Long Tail institutions. Was this excluded intentionally? If not, are there plans to support that? Is this something I would need to build myself?

Update README sample

I believe line 22 in the README.md should be client.connect_step instead of client.step.

Also the MFA isn't entirely clear to me. My BoA account was locked while trying to connect to it. I believe the following should support multiple security questions, but the answers kept being rejected.

if connect.ok:
    response = json.loads(connect.content)
    while response['mfa']:
        answer = raw_input(response['mfa'][0]["question"] + ": ")
        step = client.connect_step(account_type='bofa', mfa=answer)
        reponse = step.content
    if step.ok:
        transactions = json.loads(step.content)

Also, is there a difference between Client.auth_step() and Client.connect_step()?

And finally transactions = json.loads(step.content) doesn't make sense to me. Wouldn't one need to call Client.transactions() to get a list of transactions?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.