├── .circleci └── config.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── cfg └── example-account.json ├── gen-access-token ├── README.md ├── exists.html ├── home.html ├── icon.svg ├── screenshot.png └── server.py ├── plaid-credentials.json ├── plaid2qif.sublime-project ├── plaid2qif ├── __init__.py ├── plaid2qif.py ├── transaction_writer.py └── util.py ├── poetry.lock ├── pyproject.toml ├── release.sh ├── requirements.txt ├── sample.env └── setup.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # cf.: https://raw.githubusercontent.com/levlaz/circleci.py/master/.circleci/config.yml 2 | version: 2 3 | jobs: 4 | build: 5 | docker: 6 | - image: circleci/python:3.9.0 7 | steps: 8 | - checkout 9 | 10 | - restore_cache: 11 | key: v1-dependency-cache-{{ checksum "requirements.txt" }} 12 | 13 | - run: 14 | name: install python dependencies 15 | command: | 16 | python3 -m venv venv 17 | . venv/bin/activate 18 | pip install --upgrade pip 19 | pip install poetry 20 | poetry export --dev --format "requirements.txt" --without-hashes --output requirements.txt 21 | pip install -r requirements.txt 22 | 23 | - save_cache: 24 | key: v1-dependency-cache-{{ checksum "requirements.txt" }} 25 | paths: 26 | - "venv" 27 | 28 | deploy: 29 | docker: 30 | - image: circleci/python:3.9.0 31 | steps: 32 | - checkout 33 | 34 | - restore_cache: 35 | key: v1-dependency-cache-{{ checksum "requirements.txt" }} 36 | 37 | - run: 38 | name: install python dependencies 39 | command: | 40 | python3 -m venv venv 41 | . venv/bin/activate 42 | pip install --upgrade pip 43 | pip install poetry 44 | poetry export --dev --format "requirements.txt" --without-hashes --output requirements.txt 45 | pip install -r requirements.txt 46 | pip install --upgrade build wheel 47 | 48 | - save_cache: 49 | key: v1-dependency-cache-{{ checksum "requirements.txt" }} 50 | paths: 51 | - "venv" 52 | 53 | - run: 54 | name: init .pypirc 55 | command: | 56 | echo -e "[pypi]" >> ~/.pypirc 57 | echo -e "username = $PYPI_USERNAME" >> ~/.pypirc 58 | echo -e "password = $PYPI_PASSWORD" >> ~/.pypirc 59 | 60 | - run: 61 | name: create packages & upload to pypi 62 | command: | 63 | . venv/bin/activate 64 | python3 -m build 65 | twine upload dist/* 66 | 67 | workflows: 68 | version: 2 69 | build_and_deploy: 70 | jobs: 71 | - build: 72 | filters: 73 | tags: 74 | only: /.*/ 75 | - deploy: 76 | requires: 77 | - build 78 | filters: 79 | tags: 80 | only: /v[0-9]+(\.[0-9]+)*(\.[0-9]+)*/ 81 | branches: 82 | ignore: /.*/ 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | *.egg-info 4 | plaid-credentials.json 5 | cfg/ 6 | *.sublime-workspace 7 | **/__pycache__ 8 | **/.env 9 | .vscode/ 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### MIT License 2 | 3 | Copyright (c) 2017 Edward Q. Bridges 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/ebridges/plaid2qif/tree/master.svg?style=svg)](https://circleci.com/gh/ebridges/plaid2qif/tree/master) 2 | [![GitHub watchers](https://img.shields.io/github/watchers/badges/shields.svg?style=social&label=Watch&style=flat-square)]() 3 | [![Crates.io](https://img.shields.io/crates/l/rustc-serialize.svg?style=flat-square)]() 4 | [![PyPi](https://img.shields.io/pypi/v/plaid2qif.svg?style=flat-square)](https://pypi.org/project/plaid2qif) 5 | \ 6 | \ 7 | [![Plaid2QIF Logo](gen-access-token/icon.svg)](https://github.com/eqbridges/plaid2qif) 8 | # Plaid2QIF 9 | 10 | ## Description 11 | 12 | Plaid2QIF downloads transactions from various financial institutions in JSON format and converts to formats usable by financial software. 13 | 14 | ### Output Formats supported: 15 | * [QIF](https://en.wikipedia.org/wiki/Quicken_Interchange_Format) 16 | * CSV 17 | * JSON 18 | * Extensible to others 19 | 20 | ### Notes 21 | * Tested extensively with [GnuCash](https://www.gnucash.org/). Supported by any financial software that supports import from [QIF](https://en.wikipedia.org/wiki/Quicken_Interchange_Format). 22 | * Supports any institution supported by [Plaid](https://www.plaid.com). 23 | 24 | ## Summary 25 | 26 | ``` 27 | # Download transactions in various formats (default QIF) from Plaid 28 | plaid2qif download \ 29 | --account= \ 30 | --account-type= \ 31 | --account-id= \ 32 | --from= \ 33 | --to= \ 34 | [--output-format=] \ 35 | [--output-dir=] \ 36 | [--ignore-pending] \ 37 | [--verbose] 38 | ``` 39 | 40 | ## Usage 41 | 42 | 1. Install the `plaid2qif` command using `pip` 43 | 44 | $ pip install plaid2qif 45 | 46 | 2. Authenticate and link with your financial institution (first time only). To do this, follow the steps for using the associated [Account Linker](gen-access-token/README.md) tool. 47 | 48 | 3. Configure your environment with required values. See "Authentication Configuration" below. 49 | 50 | 3. Once configured, you're ready to download transactions and save them as QIF files: 51 | 52 | plaid2qif download \ 53 | --from= \ 54 | --to= \ 55 | --account-type= \ 56 | --account= \ 57 | --account-id= \ 58 | --credentials= 59 | 60 | * `account` is the path to an account in the ledger in GnuCash that you ultimately want to import the transactions to. This is added to the `!Account` header in the QIF file. e.g.: `Assets: Checking Accounts:Personal Checking Account`. If the name has spaces be sure to quote this param. 61 | * `account-type` is a GnuCash account identifier type as [documented here](https://github.com/Gnucash/gnucash/blob/cdb764fec525642bbe85dd5a0a49ec967c55f089/gnucash/import-export/qif-imp/file-format.txt#L23). 62 | * `account-id` is Plaid's account ID for the account you want to download, as obtained via `list-accounts` above. 63 | * By default, output will go to stdout to be redirected. If you want it to be written to a location use the `output-dir` parameter. 64 | 65 | ## Authentication Configuration 66 | 67 | * You will need the following information configured in your environment in order to use this tool. 68 | * The suggested way to populate your environment would be to use a file named `.env` in your current working directory. Alternatively you could put the values in your `~/.profile` or however you normally initialize your environment. 69 | 70 | Configuration Parameter | Environment Variable Name | Description | Notes 71 | ---------|----------|---------|--------- 72 | Client ID | `PLAID_CLIENT_ID` | Plaid's unique indentifier for your Plaid account. [Obtain from your dashboard](https://dashboard.plaid.com/overview/development) | Required. 73 | Client Secret | `PLAID_SECRET` | Plaid's authentication token for your Plaid account. [Obtain from your dashboard](https://dashboard.plaid.com/overview/development) | Required. 74 | Plaid Environment | `PLAID_ENV` | Operating environment. | Optional. Should be one of: `sandbox`, `development`, or `production`. Defaults to `development`. 75 | Plaid API Version | `PLAID_API_VERSION` | Version of the API that the `plaid-python` library supports. | Optional. Defaults to `2020-09-14` 76 | Access Token location | `ACCESS_TOKEN_FILE` | Location of the token that grants access to a particular financial institution for downloading records from. | Required. 77 | 78 | ### **Notes on Authentication Configuration** 79 | 80 | * The access token and Plaid credentials are sensitive material as they grant access to data within your financial accounts. They should be handled carefully and not shared. 81 | 82 | * These are the most important values that need configuration in order to authenticate with your institution and then download records. Other values can be found in the [sample.env](./sample.env). 83 | 84 | * If you're downloading from different institutions that result in multiple access token files, you can override the location of the file at the command line; see below for an example. _This approach is open to suggestions for improvement if this doesn't work well for others. See Issue #27._ 85 | 86 | $ ACCESS_TOKEN_FILE=./cfg/chase.txt plaid2qif ... 87 | $ ACCESS_TOKEN_FILE=./cfg/citi.txt plaid2qif ... 88 | 89 | 90 | ## Distribution 91 | 92 | ``` 93 | # increment version in `plaid2qif/__init__.py` 94 | # commit everything & push 95 | $ git tag -s vX.Y.Z 96 | $ git push --tags 97 | $ python3 setup.py sdist bdist_wheel 98 | $ twine upload dist/* 99 | ``` 100 | -------------------------------------------------------------------------------- /cfg/example-account.json: -------------------------------------------------------------------------------- 1 | { 2 | "public_token" : "", 3 | "access_token" : "", 4 | "item_id" : "" 5 | } 6 | -------------------------------------------------------------------------------- /gen-access-token/README.md: -------------------------------------------------------------------------------- 1 | [![Plaid2QIF Logo](icon.svg)](https://github.com/eqbridges/plaid2qif) 2 | 3 | # Plaid2QIF Account Linker 4 | 5 | In order to use Plaid2QIF you must grant Plaid2QIF authorization to access your financial institution's account on your behalf with via Plaid. This is done by using this tool to log into your account and exchange credentials to obtain an "access token". 6 | 7 | You should follow these steps for gaining access to each financial institution's accounts that you'd like to use with Plaid2QIF. Each access token should be stored in a separate file for each institution. (This could be improved -- see #27). 8 | 9 | ## Usage 10 | 11 | **To Begin** 12 | * Decide on a location where you want this service to write out the access token. 13 | * Configure your environment for authentication as documented in the [parent README](../README.md#authentication-configuration) 14 | 15 | ``` 16 | cd ./gen-access-token 17 | cp sample.env /path/to/your/working/directory/.env 18 | 19 | # Configure as described in the [parent README](../README.md#authentication-configuration) 20 | vi .env 21 | 22 | python server.py 23 | open http://127.0.0.1:8080 24 | ``` 25 | 26 | The access token is written to a file as plain text -- one line with nothing but the access token -- to the location configured in your `.env` file. 27 | 28 | ## Screenshot 29 | 30 | ![Service Screenshot](screenshot.png) 31 | 32 | ## Footnotes 33 | 34 | * Visit https://dashboard.plaid.com/team/api and configure a redirect URL that corresponds to the value of `PLAID_SANDBOX_REDIRECT_URI`. This only gets used if your financial institution relies on OAuth. I've not tested this scenario, so if it doesn't work I'd appreciate your notes (or a PR) to fix it. 35 | 36 | ## Disclaimer 37 | 38 | The access token generated by using this tool is sensitive material as it grants access to your accounts. It should be handled with care and protected from unauthorized access. 39 | 40 | You must be sure to store it in such a way that makes it impossible for others to gain access. The creators and maintainers of Plaid2QIF are in no way responsible if you mishandle your access token. 41 | -------------------------------------------------------------------------------- /gen-access-token/exists.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Plaid2QIF Account Linker 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | Plaid2QIF 18 | 19 |
20 | 21 |
22 |

Access Token Storage File Already Exists

23 |

Please remove it from the location ${access_token_storage} and then retry. 24 |

25 | 26 |
27 | Plaid2QIF
© 2022 Edward Q. Bridges
28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /gen-access-token/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Plaid2QIF Account Linker 9 | 10 | 11 | 73 | 74 | 75 | 76 | 122 | 123 | 124 | 125 | 126 |
127 |
128 | 129 | 130 | Plaid2QIF 131 | 132 |
133 | 134 |
135 |

Link an Account with Plaid2QIF

136 |

137 | Get started with Plaid2QIF by linking the Bank or Credit Card account that you'd like to download transactions from.
Click the button below to launch the Plaid authentication process.
Once that's completed your access token will 138 | be stored at this following location:
139 |

${access_token_storage}

140 |

141 | 142 |
143 | 146 |
147 |

148 | 
149 | 
150 |             
151 |
152 |

Plaid2QIF

153 |

Plaid2QIF is a tool for downloading transactions from your financial institution, making it usable by financial software.

154 | 160 |
161 | 162 |
163 |

Plaid

164 |

Plaid's developer-friendly platform makes it easy for people to securely connect their financial accounts to fintech services.

165 | 171 |
172 | 173 |
174 |
175 |
176 | 177 |
178 | 179 | Plaid2QIF
© 2022 Edward Q. Bridges
180 |
181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /gen-access-token/icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /gen-access-token/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebridges/plaid2qif/8463b374cc791aeb2a575bb04a301169abddf967/gen-access-token/screenshot.png -------------------------------------------------------------------------------- /gen-access-token/server.py: -------------------------------------------------------------------------------- 1 | from os.path import exists 2 | from os import environ 3 | from string import Template 4 | 5 | import plaid 6 | from plaid.api import plaid_api 7 | from plaid.model.item_public_token_exchange_request import ItemPublicTokenExchangeRequest 8 | from plaid.model.link_token_create_request import LinkTokenCreateRequest 9 | from plaid.model.link_token_create_request_user import LinkTokenCreateRequestUser 10 | from plaid.model.products import Products 11 | from plaid.model.country_code import CountryCode 12 | 13 | from flask import Flask 14 | from flask import request 15 | from flask import send_file 16 | from dotenv import load_dotenv 17 | 18 | load_dotenv() 19 | 20 | PORT=environ['PORT_NUMBER'] 21 | 22 | def env_lookup(): 23 | env = environ['PLAID_ENV'] 24 | if env == 'sandbox': 25 | return plaid.Environment.Sandbox 26 | if env == 'development': 27 | return plaid.Environment.Development 28 | if env == 'production': 29 | return plaid.Environment.Production 30 | raise Exception('Expected one of [sandbox|development|production] as an environment.') 31 | 32 | def init_client(): 33 | configuration = plaid.Configuration( 34 | host=env_lookup(), 35 | api_key={ 36 | 'clientId': environ['PLAID_CLIENT_ID'], 37 | 'secret': environ['PLAID_SECRET'] 38 | } 39 | ) 40 | api_client = plaid.ApiClient(configuration) 41 | return plaid_api.PlaidApi(api_client) 42 | 43 | 44 | client = init_client() 45 | app = Flask('Plaid2QIF Account Linker') 46 | 47 | 48 | @app.route("/api/exchange-public-token", methods=['POST']) 49 | def exchange_public_token(): 50 | exchange_request = ItemPublicTokenExchangeRequest(public_token=request.json['public_token']) 51 | exchange_response = client.item_public_token_exchange(exchange_request) 52 | access_token = exchange_response['access_token'] 53 | with open(environ['ACCESS_TOKEN_FILE'], 'w') as f: 54 | f.write(access_token) 55 | return f"Access token written to: {environ['ACCESS_TOKEN_FILE']}" 56 | 57 | 58 | @app.route("/api/create-link-token", methods=['GET']) 59 | def create_link_token(): 60 | req = LinkTokenCreateRequest( 61 | products=[Products("transactions")], 62 | client_name='Plaid2QIF Account Linker', 63 | country_codes=[CountryCode('US')], 64 | language='en', 65 | user=LinkTokenCreateRequestUser( 66 | client_user_id='absent-user' 67 | ), 68 | redirect_uri=environ['PLAID_SANDBOX_REDIRECT_URI'] 69 | ) 70 | response = client.link_token_create(req) 71 | return response.to_dict() 72 | 73 | 74 | @app.route("/", methods=['GET']) 75 | def create_link(): 76 | template = 'home.html' 77 | if exists(environ['ACCESS_TOKEN_FILE']): 78 | template = 'exists.html' 79 | 80 | with open(template, "r") as file: 81 | data = file.read() 82 | 83 | t = Template(data) 84 | return t.safe_substitute(access_token_storage=environ['ACCESS_TOKEN_FILE']) 85 | 86 | 87 | @app.route('/icon.svg') 88 | def icon(): 89 | return send_file('icon.svg', mimetype='image/svg+xml') 90 | 91 | 92 | if __name__ == "__main__": 93 | app.run(port=PORT) 94 | -------------------------------------------------------------------------------- /plaid-credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "client_id" : "", 3 | "public_key" : "", 4 | "secret" : "" 5 | } 6 | -------------------------------------------------------------------------------- /plaid2qif.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": ".", 6 | "folder_exclude_patterns": [ 7 | "build", 8 | "dist", 9 | "*.egg-info" 10 | ], 11 | "file_exclude_patterns": [ 12 | "*.sublime-project" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /plaid2qif/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION='1.4.0' 2 | -------------------------------------------------------------------------------- /plaid2qif/plaid2qif.py: -------------------------------------------------------------------------------- 1 | """ 2 | Plaid 2 QIF. 3 | 4 | Download financial transactions from Plaid and convert to QIF files. 5 | 6 | Usage: 7 | plaid2qif download --account= --account-type= --account-id= --from= --to= [--output-format=] [--output-dir=] [--ignore-pending] [--verbose] 8 | plaid2qif list-accounts [--verbose] 9 | plaid2qif info 10 | plaid2qif -h | --help 11 | plaid2qif --version 12 | 13 | Options: 14 | -h --help Show this screen. 15 | --version Show version. 16 | --account= Complete account name from accounting system that transactions will be imported to. 17 | --account-type= Account type [Default: Bank] 18 | --account-id= Plaid's account id for this account. 19 | --from= Beginning of date range. 20 | --to= End of date range. 21 | --output-format= Output format either 'raw', 'csv' or 'qif'. [Default: qif] 22 | --output-dir= Location to output file to. (Default: output to stdout) 23 | --ignore-pending Ignore pending transactions. 24 | --verbose Verbose logging output. 25 | """ 26 | from datetime import datetime 27 | from logging import debug, info 28 | from os import environ 29 | from sys import stdout 30 | from pathlib import Path 31 | 32 | from docopt import docopt 33 | from pkg_resources import require 34 | from dotenv import load_dotenv 35 | 36 | from plaid.api import plaid_api 37 | from plaid.model.accounts_get_request import AccountsGetRequest 38 | from plaid.model.transactions_get_request import TransactionsGetRequest 39 | from plaid.model.transactions_get_request_options import TransactionsGetRequestOptions 40 | import plaid 41 | 42 | from plaid2qif import transaction_writer 43 | from plaid2qif import util 44 | 45 | 46 | def download(account, fromto, output, ignore_pending, limiter=util.Limiter()): 47 | 48 | options = TransactionsGetRequestOptions() 49 | 50 | def do_request(options, limiter): 51 | access_token = read_access_token() 52 | options.account_ids = [account['id']] 53 | client = open_client() 54 | request = TransactionsGetRequest( 55 | access_token=access_token, 56 | start_date=fromto['start'], 57 | end_date=fromto['end'], 58 | options=options, 59 | ) 60 | 61 | if limiter.time_to_pause(): 62 | limiter.pause() 63 | 64 | return client.transactions_get(request) 65 | 66 | response = do_request(options, limiter) 67 | txn_batch = len(response['transactions']) 68 | txn_total = response['total_transactions'] 69 | txn_sofar = txn_batch 70 | 71 | output_to_file = True if output['dir'] else False 72 | output_file = '%s/%s' % (output['dir'], util.output_filename(account['name'], fromto, output['format'])) 73 | 74 | output_handle = output_to_file and open(output_file, 'w') or stdout 75 | 76 | try: 77 | w = transaction_writer.TransactionWriter.instance(output['format'], output_handle) 78 | w.begin(account) 79 | 80 | debug("txn cnt: %d, txn total: %d" % (txn_batch, txn_total)) 81 | while 0 < txn_batch <= txn_total: 82 | for t in response['transactions']: 83 | if ignore_pending and t['pending']: 84 | info('skipping pending transaction for [%s: %s]' % (t['date'], t['name'])) 85 | continue 86 | info('writing record for [%s: %s]' % (t['date'], t['name'])) 87 | debug('%s' % t) 88 | w.write_record(t) 89 | 90 | options.offset = txn_sofar 91 | response = do_request(options, limiter) 92 | 93 | txn_batch = len(response['transactions']) 94 | txn_total = response['total_transactions'] 95 | txn_sofar = txn_batch+txn_sofar 96 | 97 | debug("txn cnt: %d, txn_sofar: %d, txn total: %d" % (txn_batch, txn_sofar, txn_total)) 98 | 99 | w.end() 100 | 101 | finally: 102 | if output_handle is not stdout: 103 | output_handle.close() 104 | 105 | info('completed writing %d transactions' % txn_sofar) 106 | 107 | 108 | def list_accounts(): 109 | client = open_client() 110 | request = AccountsGetRequest(access_token=read_access_token()) 111 | response = client.accounts_get(request) 112 | accounts = response['accounts'] 113 | 114 | print('Account:Subaccount\tAccountName\tAcctNum\tAcctID') 115 | for a in accounts: 116 | print('%s:%s\t%s\t%s\t%s' % (a['type'], a['subtype'], a['name'], a['mask'], a['account_id'])) 117 | 118 | 119 | def read_access_token(): 120 | with open(environ.get('ACCESS_TOKEN_FILE')) as f: 121 | return f.readline().rstrip() 122 | 123 | 124 | def open_client(): 125 | envs = { 126 | 'development': plaid.Environment.Development, 127 | 'sandbox': plaid.Environment.Sandbox, 128 | 'production': plaid.Environment.Production, 129 | } 130 | plaid_env = environ.get('PLAID_ENV', 'development') 131 | if plaid_env not in envs.keys(): 132 | raise ValueError(f'PLAID_ENV={plaid_env} is not a valid choice among: {envs.keys()}') 133 | 134 | plaid_client_id = environ.get('PLAID_CLIENT_ID') 135 | if not plaid_client_id: 136 | raise ValueError('PLAID_CLIENT_ID not found in environment.') 137 | 138 | plaid_secret = environ.get('PLAID_SECRET') 139 | if not plaid_secret: 140 | raise ValueError('PLAID_SECRET not found in environment.') 141 | 142 | plaid_version = environ.get('PLAID_API_VERSION', '2020-09-14') 143 | 144 | debug('opening client for %s' % plaid_env) 145 | 146 | configuration = plaid.Configuration( 147 | host=envs[plaid_env], 148 | api_key={ 149 | 'clientId': plaid_client_id, 150 | 'secret': plaid_secret, 151 | 'plaidVersion': plaid_version, 152 | } 153 | ) 154 | api_client = plaid.ApiClient(configuration) 155 | client = plaid_api.PlaidApi(api_client) 156 | return client 157 | 158 | def display_info(args): 159 | print('Command line arguments:') 160 | for arg in sorted(args.keys()): 161 | print(f"\t {arg}: {args[arg]}") 162 | print('Environment variables:') 163 | print(f"\tACCESS_TOKEN_FILE: {environ.get('ACCESS_TOKEN_FILE', 'absent')}") 164 | print(f"\tPLAID_CLIENT_ID: {environ.get('PLAID_CLIENT_ID', 'absent')}") 165 | print(f"\tPLAID_SECRET: {environ.get('PLAID_SECRET', 'absent')}") 166 | print(f"\tPLAID_ENV: {environ.get('PLAID_ENV', 'absent')}") 167 | print(f"\tAPPLICATION_NAME: {environ.get('APPLICATION_NAME', 'absent')}") 168 | print(f"\tPLAID_SANDBOX_REDIRECT_URI: {environ.get('PLAID_SANDBOX_REDIRECT_URI', 'absent')}") 169 | print(f"\tPORT_NUMBER: {environ.get('PORT_NUMBER', 'absent')}") 170 | 171 | 172 | def main(): 173 | load_dotenv(dotenv_path=f'{Path.cwd()}/.env') 174 | version = require("plaid2qif")[0].version 175 | args = docopt(__doc__, version=version) 176 | util.configure_logging(args['--verbose']) 177 | debug(args) 178 | 179 | if args['info']: 180 | display_info(args) 181 | return 182 | 183 | if args['list-accounts']: 184 | list_accounts() 185 | return 186 | 187 | if args['download']: 188 | account = { 189 | 'id' : args['--account-id'], 190 | 'name': args['--account'], 191 | 'type': args['--account-type'], 192 | } 193 | fromto = { 194 | 'start': datetime.strptime(args['--from'], '%Y-%m-%d').date(), 195 | 'end': datetime.strptime(args['--to'], '%Y-%m-%d').date(), 196 | } 197 | output = { 198 | 'dir': args['--output-dir'], 199 | 'format': args['--output-format'] 200 | } 201 | ignore_pending = args['--ignore-pending'] 202 | download(account, fromto, output, ignore_pending) 203 | return 204 | 205 | if __name__ == '__main__': 206 | main() 207 | -------------------------------------------------------------------------------- /plaid2qif/transaction_writer.py: -------------------------------------------------------------------------------- 1 | from dateutil.parser import parse 2 | from decimal import Decimal 3 | from logging import info 4 | from json import dumps 5 | from datetime import datetime, date 6 | import unicodedata 7 | 8 | TWOPLACES = Decimal(10) ** -2 9 | 10 | class TransactionWriter(object): 11 | def __init__(self, output): 12 | if output: 13 | self.output = output 14 | 15 | def instance(t, output): 16 | if t == 'csv': 17 | return CsvTransactionWriter(output) 18 | if t == 'qif': 19 | return QifTransactionWriter(output) 20 | if t == 'raw': 21 | return JsonTransactionWriter(output) 22 | 23 | instance = staticmethod(instance) 24 | 25 | def begin(self, account_info): 26 | pass 27 | 28 | def write_record(self, transaction): 29 | pass 30 | 31 | def end(self): 32 | pass 33 | 34 | 35 | class JsonTransactionWriter(TransactionWriter): 36 | def begin(self, account_info): 37 | print( dumps(account_info, sort_keys=True), file=self.output) 38 | 39 | def write_record(self, transaction): 40 | def encode_date(obj): 41 | if isinstance(obj, (date, datetime)): 42 | return obj.isoformat() 43 | print( dumps(transaction.to_dict(), sort_keys=True, default=encode_date), file=self.output) 44 | 45 | 46 | class CsvTransactionWriter(TransactionWriter): 47 | def begin(self, account_info): 48 | print('Date,Amount,Description,Category,CategoryID,TransactionID,TransactionType', file=self.output) 49 | 50 | def write_record(self, transaction): 51 | print("{},{},{},{},{},{},{}".format(transaction['date'], transaction['amount'], '"' + unicodedata.normalize('NFKD', transaction['name'].replace('"', '\'')) + '"', 52 | '"' + '|'.join([sub.replace('"', '\'') for sub in transaction['category']]) + '"', transaction['category_id'], transaction['transaction_id'], transaction['transaction_type']), file=self.output) 53 | 54 | class QifTransactionWriter(TransactionWriter): 55 | def begin(self, account): 56 | print('!Account', file=self.output) 57 | print('N%s' % account['name'], file=self.output) 58 | print('T%s' % account['type'], file=self.output) 59 | if 'description' in account: 60 | print('D%s' % account['description'], file=self.output) 61 | print('^', file=self.output) 62 | print('!Type:%s' % account['type'], file=self.output) 63 | 64 | 65 | def write_record(self, transaction): 66 | print('C', file=self.output) # cleared status: Values are blank (not cleared), "*" or "c" (cleared) and "X" or "R" (reconciled). 67 | print('D%s' % self.format_date(transaction['date']), file=self.output) 68 | print('N%s' % self.format_chknum(transaction), file=self.output) 69 | print('P%s' % transaction['name'], file=self.output) 70 | print('T%s' % self.format_amount(transaction['amount']), file=self.output) 71 | 72 | # if there's a location key for the transaction 73 | # and all of its values are non-empty, then record 74 | # the address in the metadata of the QIF record 75 | if 'location' in transaction and self.check_location(transaction['location']): 76 | print('A%s' % transaction['location']['address'], file=self.output) 77 | print('A%s, %s %s' % (transaction['location']['city'], transaction['location']['state'], transaction['location']['zip']), file=self.output) 78 | 79 | # ditto for lon/lat 80 | if 'location' in transaction and (transaction['location']['lon'] and transaction['location']['lat']): 81 | print('ALon:%s,Lat:%s' % (transaction['location']['lon'], transaction['location']['lat']), file=self.output) 82 | 83 | print('^', file=self.output) 84 | 85 | 86 | def format_date(self, date): 87 | return date.strftime('%m/%d/%Y') 88 | 89 | 90 | def format_chknum(self, t): 91 | if(t['payment_meta']['reference_number']): 92 | return t['payment_meta']['reference_number'] 93 | return 'N/A' 94 | 95 | 96 | def format_amount(self,a): 97 | d = Decimal(a).quantize(TWOPLACES).copy_negate() 98 | info("formatted amount [%s] as [%s]" % (a, str(d))) 99 | return d 100 | 101 | 102 | def check_location(self,loc): 103 | if ('address' in loc and loc['address']) and ('city' in loc and loc['city']) and ('state' in loc and loc['state']) and ('zip' in loc and loc['zip']): 104 | return True 105 | else: 106 | return False 107 | -------------------------------------------------------------------------------- /plaid2qif/util.py: -------------------------------------------------------------------------------- 1 | from dateutil.parser import parse 2 | from logging import INFO, DEBUG, basicConfig 3 | from time import sleep 4 | 5 | def output_filename(account_path, fromto, file_ext): 6 | account = account_path.split(':')[-1] 7 | return '%s--%s-%s.%s' % (fromto['start'], fromto['end'], account, file_ext) 8 | 9 | 10 | def configure_logging(level): 11 | if not level: 12 | level = INFO 13 | else: 14 | level = DEBUG 15 | basicConfig( 16 | format='[%(asctime)s][%(levelname)s] %(message)s', 17 | datefmt='%Y/%m/%d %H:%M:%S', 18 | level=level) 19 | 20 | 21 | class Limiter(): 22 | def __init__(self): 23 | pass 24 | 25 | def time_to_pause(self): 26 | return False 27 | 28 | def pause(self): 29 | return 30 | 31 | 32 | class SleepLimiter(Limiter): 33 | def __init__(self, limit=30, wait_for=30): 34 | self.count = 0 35 | self.limit=limit 36 | self.wait_for=wait_for 37 | 38 | def time_to_pause(self): 39 | self.count += 1 40 | if self.count > self.limit: 41 | self.count = 0 42 | return True 43 | else: 44 | return False 45 | 46 | def pause(self): 47 | sleep(self.wait_for) 48 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "blinker" 5 | version = "1.7.0" 6 | description = "Fast, simple object-to-object and broadcast signaling" 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, 11 | {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, 12 | ] 13 | 14 | [[package]] 15 | name = "certifi" 16 | version = "2023.11.17" 17 | description = "Python package for providing Mozilla's CA Bundle." 18 | optional = false 19 | python-versions = ">=3.6" 20 | files = [ 21 | {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, 22 | {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, 23 | ] 24 | 25 | [[package]] 26 | name = "cffi" 27 | version = "1.16.0" 28 | description = "Foreign Function Interface for Python calling C code." 29 | optional = false 30 | python-versions = ">=3.8" 31 | files = [ 32 | {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, 33 | {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, 34 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, 35 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, 36 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, 37 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, 38 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, 39 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, 40 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, 41 | {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, 42 | {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, 43 | {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, 44 | {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, 45 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, 46 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, 47 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, 48 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, 49 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, 50 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, 51 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, 52 | {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, 53 | {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, 54 | {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, 55 | {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, 56 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, 57 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, 58 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, 59 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, 60 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, 61 | {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, 62 | {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, 63 | {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, 64 | {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, 65 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, 66 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, 67 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, 68 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, 69 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, 70 | {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, 71 | {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, 72 | {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, 73 | {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, 74 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, 75 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, 76 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, 77 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, 78 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, 79 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, 80 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, 81 | {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, 82 | {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, 83 | {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, 84 | ] 85 | 86 | [package.dependencies] 87 | pycparser = "*" 88 | 89 | [[package]] 90 | name = "charset-normalizer" 91 | version = "3.3.2" 92 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 93 | optional = false 94 | python-versions = ">=3.7.0" 95 | files = [ 96 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 97 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 98 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 99 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 100 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 101 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 102 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 103 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 104 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 105 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 106 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 107 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 108 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 109 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 110 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 111 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 112 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 113 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 114 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 115 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 116 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 117 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 118 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 119 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 120 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 121 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 122 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 123 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 124 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 125 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 126 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 127 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 128 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 129 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 130 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 131 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 132 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 133 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 134 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 135 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 136 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 137 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 138 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 139 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 140 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 141 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 142 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 143 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 144 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 145 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 146 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 147 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 148 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 149 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 150 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 151 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 152 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 153 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 154 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 155 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 156 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 157 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 158 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 159 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 160 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 161 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 162 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 163 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 164 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 165 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 166 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 167 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 168 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 169 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 170 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 171 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 172 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 173 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 174 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 175 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 176 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 177 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 178 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 179 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 180 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 181 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 182 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 183 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 184 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 185 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 186 | ] 187 | 188 | [[package]] 189 | name = "click" 190 | version = "8.1.7" 191 | description = "Composable command line interface toolkit" 192 | optional = false 193 | python-versions = ">=3.7" 194 | files = [ 195 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 196 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 197 | ] 198 | 199 | [package.dependencies] 200 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 201 | 202 | [[package]] 203 | name = "colorama" 204 | version = "0.4.6" 205 | description = "Cross-platform colored terminal text." 206 | optional = false 207 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 208 | files = [ 209 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 210 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 211 | ] 212 | 213 | [[package]] 214 | name = "cryptography" 215 | version = "41.0.7" 216 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 217 | optional = false 218 | python-versions = ">=3.7" 219 | files = [ 220 | {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, 221 | {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, 222 | {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, 223 | {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, 224 | {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, 225 | {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, 226 | {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, 227 | {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, 228 | {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, 229 | {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, 230 | {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, 231 | {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, 232 | {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, 233 | {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, 234 | {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, 235 | {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, 236 | {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, 237 | {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, 238 | {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, 239 | {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, 240 | {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, 241 | {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, 242 | {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, 243 | ] 244 | 245 | [package.dependencies] 246 | cffi = ">=1.12" 247 | 248 | [package.extras] 249 | docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] 250 | docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] 251 | nox = ["nox"] 252 | pep8test = ["black", "check-sdist", "mypy", "ruff"] 253 | sdist = ["build"] 254 | ssh = ["bcrypt (>=3.1.5)"] 255 | test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] 256 | test-randomorder = ["pytest-randomly"] 257 | 258 | [[package]] 259 | name = "docopt" 260 | version = "0.6.2" 261 | description = "Pythonic argument parser, that will make you smile" 262 | optional = false 263 | python-versions = "*" 264 | files = [ 265 | {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, 266 | ] 267 | 268 | [[package]] 269 | name = "docutils" 270 | version = "0.20.1" 271 | description = "Docutils -- Python Documentation Utilities" 272 | optional = false 273 | python-versions = ">=3.7" 274 | files = [ 275 | {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, 276 | {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, 277 | ] 278 | 279 | [[package]] 280 | name = "flask" 281 | version = "2.3.3" 282 | description = "A simple framework for building complex web applications." 283 | optional = false 284 | python-versions = ">=3.8" 285 | files = [ 286 | {file = "flask-2.3.3-py3-none-any.whl", hash = "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b"}, 287 | {file = "flask-2.3.3.tar.gz", hash = "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc"}, 288 | ] 289 | 290 | [package.dependencies] 291 | blinker = ">=1.6.2" 292 | click = ">=8.1.3" 293 | importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} 294 | itsdangerous = ">=2.1.2" 295 | Jinja2 = ">=3.1.2" 296 | Werkzeug = ">=2.3.7" 297 | 298 | [package.extras] 299 | async = ["asgiref (>=3.2)"] 300 | dotenv = ["python-dotenv"] 301 | 302 | [[package]] 303 | name = "idna" 304 | version = "3.6" 305 | description = "Internationalized Domain Names in Applications (IDNA)" 306 | optional = false 307 | python-versions = ">=3.5" 308 | files = [ 309 | {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 310 | {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 311 | ] 312 | 313 | [[package]] 314 | name = "importlib-metadata" 315 | version = "7.0.1" 316 | description = "Read metadata from Python packages" 317 | optional = false 318 | python-versions = ">=3.8" 319 | files = [ 320 | {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, 321 | {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, 322 | ] 323 | 324 | [package.dependencies] 325 | zipp = ">=0.5" 326 | 327 | [package.extras] 328 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 329 | perf = ["ipython"] 330 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] 331 | 332 | [[package]] 333 | name = "itsdangerous" 334 | version = "2.1.2" 335 | description = "Safely pass data to untrusted environments and back." 336 | optional = false 337 | python-versions = ">=3.7" 338 | files = [ 339 | {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, 340 | {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, 341 | ] 342 | 343 | [[package]] 344 | name = "jaraco-classes" 345 | version = "3.3.0" 346 | description = "Utility functions for Python class constructs" 347 | optional = false 348 | python-versions = ">=3.8" 349 | files = [ 350 | {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, 351 | {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, 352 | ] 353 | 354 | [package.dependencies] 355 | more-itertools = "*" 356 | 357 | [package.extras] 358 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 359 | testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] 360 | 361 | [[package]] 362 | name = "jeepney" 363 | version = "0.8.0" 364 | description = "Low-level, pure Python DBus protocol wrapper." 365 | optional = false 366 | python-versions = ">=3.7" 367 | files = [ 368 | {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, 369 | {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, 370 | ] 371 | 372 | [package.extras] 373 | test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] 374 | trio = ["async_generator", "trio"] 375 | 376 | [[package]] 377 | name = "jinja2" 378 | version = "3.1.3" 379 | description = "A very fast and expressive template engine." 380 | optional = false 381 | python-versions = ">=3.7" 382 | files = [ 383 | {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, 384 | {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, 385 | ] 386 | 387 | [package.dependencies] 388 | MarkupSafe = ">=2.0" 389 | 390 | [package.extras] 391 | i18n = ["Babel (>=2.7)"] 392 | 393 | [[package]] 394 | name = "keyring" 395 | version = "24.3.0" 396 | description = "Store and access your passwords safely." 397 | optional = false 398 | python-versions = ">=3.8" 399 | files = [ 400 | {file = "keyring-24.3.0-py3-none-any.whl", hash = "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836"}, 401 | {file = "keyring-24.3.0.tar.gz", hash = "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25"}, 402 | ] 403 | 404 | [package.dependencies] 405 | importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} 406 | "jaraco.classes" = "*" 407 | jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} 408 | pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} 409 | SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} 410 | 411 | [package.extras] 412 | completion = ["shtab (>=1.1.0)"] 413 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 414 | testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] 415 | 416 | [[package]] 417 | name = "markdown-it-py" 418 | version = "3.0.0" 419 | description = "Python port of markdown-it. Markdown parsing, done right!" 420 | optional = false 421 | python-versions = ">=3.8" 422 | files = [ 423 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 424 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 425 | ] 426 | 427 | [package.dependencies] 428 | mdurl = ">=0.1,<1.0" 429 | 430 | [package.extras] 431 | benchmarking = ["psutil", "pytest", "pytest-benchmark"] 432 | code-style = ["pre-commit (>=3.0,<4.0)"] 433 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] 434 | linkify = ["linkify-it-py (>=1,<3)"] 435 | plugins = ["mdit-py-plugins"] 436 | profiling = ["gprof2dot"] 437 | rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 438 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 439 | 440 | [[package]] 441 | name = "markupsafe" 442 | version = "2.1.3" 443 | description = "Safely add untrusted strings to HTML/XML markup." 444 | optional = false 445 | python-versions = ">=3.7" 446 | files = [ 447 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, 448 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, 449 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, 450 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, 451 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, 452 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, 453 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, 454 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, 455 | {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, 456 | {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, 457 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, 458 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, 459 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, 460 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, 461 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, 462 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, 463 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, 464 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, 465 | {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, 466 | {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, 467 | {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, 468 | {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, 469 | {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, 470 | {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, 471 | {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, 472 | {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, 473 | {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, 474 | {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, 475 | {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, 476 | {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, 477 | {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, 478 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, 479 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, 480 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, 481 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, 482 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, 483 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, 484 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, 485 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, 486 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, 487 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, 488 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, 489 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, 490 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, 491 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, 492 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, 493 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, 494 | {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, 495 | {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, 496 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, 497 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, 498 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, 499 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, 500 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, 501 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, 502 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, 503 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, 504 | {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, 505 | {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, 506 | {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, 507 | ] 508 | 509 | [[package]] 510 | name = "mdurl" 511 | version = "0.1.2" 512 | description = "Markdown URL utilities" 513 | optional = false 514 | python-versions = ">=3.7" 515 | files = [ 516 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 517 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 518 | ] 519 | 520 | [[package]] 521 | name = "more-itertools" 522 | version = "10.2.0" 523 | description = "More routines for operating on iterables, beyond itertools" 524 | optional = false 525 | python-versions = ">=3.8" 526 | files = [ 527 | {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, 528 | {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, 529 | ] 530 | 531 | [[package]] 532 | name = "nh3" 533 | version = "0.2.15" 534 | description = "Python bindings to the ammonia HTML sanitization library." 535 | optional = false 536 | python-versions = "*" 537 | files = [ 538 | {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0"}, 539 | {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3"}, 540 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97"}, 541 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28"}, 542 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf"}, 543 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911"}, 544 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf"}, 545 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7"}, 546 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305"}, 547 | {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770"}, 548 | {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6"}, 549 | {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d"}, 550 | {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5"}, 551 | {file = "nh3-0.2.15-cp37-abi3-win32.whl", hash = "sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601"}, 552 | {file = "nh3-0.2.15-cp37-abi3-win_amd64.whl", hash = "sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e"}, 553 | {file = "nh3-0.2.15.tar.gz", hash = "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3"}, 554 | ] 555 | 556 | [[package]] 557 | name = "nulltype" 558 | version = "2.3.1" 559 | description = "Null values and sentinels like (but not) None, False & True" 560 | optional = false 561 | python-versions = "*" 562 | files = [ 563 | {file = "nulltype-2.3.1-py2.py3-none-any.whl", hash = "sha256:16ae565745118e37e0558441f5821c76351d8c3a789640b5bca277cf65b2271b"}, 564 | {file = "nulltype-2.3.1.zip", hash = "sha256:64aa3cb2ab5e904d1b37175b9b922bea268c13f9ce32e3d373313150ab5ef272"}, 565 | ] 566 | 567 | [[package]] 568 | name = "pkginfo" 569 | version = "1.9.6" 570 | description = "Query metadata from sdists / bdists / installed packages." 571 | optional = false 572 | python-versions = ">=3.6" 573 | files = [ 574 | {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, 575 | {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, 576 | ] 577 | 578 | [package.extras] 579 | testing = ["pytest", "pytest-cov"] 580 | 581 | [[package]] 582 | name = "plaid-python" 583 | version = "9.9.0" 584 | description = "Python client library for the Plaid API and Link" 585 | optional = false 586 | python-versions = ">=3.6" 587 | files = [ 588 | {file = "plaid-python-9.9.0.tar.gz", hash = "sha256:bafa331b597e6860ece27cce3a32943d270b2cc355a29e6ed98f3db1ef06bed6"}, 589 | ] 590 | 591 | [package.dependencies] 592 | nulltype = "*" 593 | python-dateutil = "*" 594 | urllib3 = ">=1.25.3" 595 | 596 | [[package]] 597 | name = "pycparser" 598 | version = "2.21" 599 | description = "C parser in Python" 600 | optional = false 601 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 602 | files = [ 603 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 604 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 605 | ] 606 | 607 | [[package]] 608 | name = "pygments" 609 | version = "2.17.2" 610 | description = "Pygments is a syntax highlighting package written in Python." 611 | optional = false 612 | python-versions = ">=3.7" 613 | files = [ 614 | {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, 615 | {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, 616 | ] 617 | 618 | [package.extras] 619 | plugins = ["importlib-metadata"] 620 | windows-terminal = ["colorama (>=0.4.6)"] 621 | 622 | [[package]] 623 | name = "python-dateutil" 624 | version = "2.8.2" 625 | description = "Extensions to the standard Python datetime module" 626 | optional = false 627 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 628 | files = [ 629 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 630 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 631 | ] 632 | 633 | [package.dependencies] 634 | six = ">=1.5" 635 | 636 | [[package]] 637 | name = "python-dotenv" 638 | version = "0.20.0" 639 | description = "Read key-value pairs from a .env file and set them as environment variables" 640 | optional = false 641 | python-versions = ">=3.5" 642 | files = [ 643 | {file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"}, 644 | {file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"}, 645 | ] 646 | 647 | [package.extras] 648 | cli = ["click (>=5.0)"] 649 | 650 | [[package]] 651 | name = "pywin32-ctypes" 652 | version = "0.2.2" 653 | description = "A (partial) reimplementation of pywin32 using ctypes/cffi" 654 | optional = false 655 | python-versions = ">=3.6" 656 | files = [ 657 | {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, 658 | {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, 659 | ] 660 | 661 | [[package]] 662 | name = "readme-renderer" 663 | version = "42.0" 664 | description = "readme_renderer is a library for rendering readme descriptions for Warehouse" 665 | optional = false 666 | python-versions = ">=3.8" 667 | files = [ 668 | {file = "readme_renderer-42.0-py3-none-any.whl", hash = "sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d"}, 669 | {file = "readme_renderer-42.0.tar.gz", hash = "sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1"}, 670 | ] 671 | 672 | [package.dependencies] 673 | docutils = ">=0.13.1" 674 | nh3 = ">=0.2.14" 675 | Pygments = ">=2.5.1" 676 | 677 | [package.extras] 678 | md = ["cmarkgfm (>=0.8.0)"] 679 | 680 | [[package]] 681 | name = "requests" 682 | version = "2.31.0" 683 | description = "Python HTTP for Humans." 684 | optional = false 685 | python-versions = ">=3.7" 686 | files = [ 687 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 688 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 689 | ] 690 | 691 | [package.dependencies] 692 | certifi = ">=2017.4.17" 693 | charset-normalizer = ">=2,<4" 694 | idna = ">=2.5,<4" 695 | urllib3 = ">=1.21.1,<3" 696 | 697 | [package.extras] 698 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 699 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 700 | 701 | [[package]] 702 | name = "requests-toolbelt" 703 | version = "1.0.0" 704 | description = "A utility belt for advanced users of python-requests" 705 | optional = false 706 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 707 | files = [ 708 | {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, 709 | {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, 710 | ] 711 | 712 | [package.dependencies] 713 | requests = ">=2.0.1,<3.0.0" 714 | 715 | [[package]] 716 | name = "rfc3986" 717 | version = "2.0.0" 718 | description = "Validating URI References per RFC 3986" 719 | optional = false 720 | python-versions = ">=3.7" 721 | files = [ 722 | {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, 723 | {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, 724 | ] 725 | 726 | [package.extras] 727 | idna2008 = ["idna"] 728 | 729 | [[package]] 730 | name = "rich" 731 | version = "13.7.0" 732 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 733 | optional = false 734 | python-versions = ">=3.7.0" 735 | files = [ 736 | {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, 737 | {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, 738 | ] 739 | 740 | [package.dependencies] 741 | markdown-it-py = ">=2.2.0" 742 | pygments = ">=2.13.0,<3.0.0" 743 | 744 | [package.extras] 745 | jupyter = ["ipywidgets (>=7.5.1,<9)"] 746 | 747 | [[package]] 748 | name = "secretstorage" 749 | version = "3.3.3" 750 | description = "Python bindings to FreeDesktop.org Secret Service API" 751 | optional = false 752 | python-versions = ">=3.6" 753 | files = [ 754 | {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, 755 | {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, 756 | ] 757 | 758 | [package.dependencies] 759 | cryptography = ">=2.0" 760 | jeepney = ">=0.6" 761 | 762 | [[package]] 763 | name = "six" 764 | version = "1.16.0" 765 | description = "Python 2 and 3 compatibility utilities" 766 | optional = false 767 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 768 | files = [ 769 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 770 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 771 | ] 772 | 773 | [[package]] 774 | name = "twine" 775 | version = "4.0.2" 776 | description = "Collection of utilities for publishing packages on PyPI" 777 | optional = false 778 | python-versions = ">=3.7" 779 | files = [ 780 | {file = "twine-4.0.2-py3-none-any.whl", hash = "sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8"}, 781 | {file = "twine-4.0.2.tar.gz", hash = "sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8"}, 782 | ] 783 | 784 | [package.dependencies] 785 | importlib-metadata = ">=3.6" 786 | keyring = ">=15.1" 787 | pkginfo = ">=1.8.1" 788 | readme-renderer = ">=35.0" 789 | requests = ">=2.20" 790 | requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" 791 | rfc3986 = ">=1.4.0" 792 | rich = ">=12.0.0" 793 | urllib3 = ">=1.26.0" 794 | 795 | [[package]] 796 | name = "urllib3" 797 | version = "2.1.0" 798 | description = "HTTP library with thread-safe connection pooling, file post, and more." 799 | optional = false 800 | python-versions = ">=3.8" 801 | files = [ 802 | {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, 803 | {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, 804 | ] 805 | 806 | [package.extras] 807 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 808 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 809 | zstd = ["zstandard (>=0.18.0)"] 810 | 811 | [[package]] 812 | name = "werkzeug" 813 | version = "3.0.1" 814 | description = "The comprehensive WSGI web application library." 815 | optional = false 816 | python-versions = ">=3.8" 817 | files = [ 818 | {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, 819 | {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, 820 | ] 821 | 822 | [package.dependencies] 823 | MarkupSafe = ">=2.1.1" 824 | 825 | [package.extras] 826 | watchdog = ["watchdog (>=2.3)"] 827 | 828 | [[package]] 829 | name = "wheel" 830 | version = "0.38.4" 831 | description = "A built-package format for Python" 832 | optional = false 833 | python-versions = ">=3.7" 834 | files = [ 835 | {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, 836 | {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, 837 | ] 838 | 839 | [package.extras] 840 | test = ["pytest (>=3.0.0)"] 841 | 842 | [[package]] 843 | name = "zipp" 844 | version = "3.17.0" 845 | description = "Backport of pathlib-compatible object wrapper for zip files" 846 | optional = false 847 | python-versions = ">=3.8" 848 | files = [ 849 | {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, 850 | {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, 851 | ] 852 | 853 | [package.extras] 854 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 855 | testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] 856 | 857 | [metadata] 858 | lock-version = "2.0" 859 | python-versions = "^3.9" 860 | content-hash = "e644babbad7f95f969a64af684d6d859913ab235413ba6be9b0f7189d729464b" 861 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "plaid2qif" 3 | version = "1.4.0" 4 | description = "Download financial transactions from Plaid as QIF files." 5 | authors = ["Edward Q. Bridges "] 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/ebridges/plaid2qif" 9 | classifiers = [ 10 | "Development Status :: 5 - Production/Stable", 11 | "Intended Audience :: End Users/Desktop", 12 | "Intended Audience :: Financial and Insurance Industry", 13 | "Topic :: Home Automation", 14 | "Topic :: Office/Business :: Financial", 15 | "Topic :: Office/Business :: Financial :: Accounting", 16 | "Topic :: Utilities", 17 | ] 18 | 19 | [tool.poetry.dependencies] 20 | python = "^3.9" 21 | docopt = "^0.6.2" 22 | plaid-python = "^9.4.0" 23 | python-dateutil = "^2.8.1" 24 | python-dotenv = "^0.20.0" 25 | 26 | [tool.poetry.dev-dependencies] 27 | wheel = "^0.38.1" 28 | twine = "^4.0.1" 29 | Flask = "^2.3.2" 30 | 31 | [tool.poetry.scripts] 32 | plaid2qif = 'plaid2qif.plaid2qif:main' 33 | 34 | [build-system] 35 | requires = ["poetry-core>=1.0.0"] 36 | build-backend = "poetry.core.masonry.api" 37 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=${1} 4 | DEPLOY=${2} 5 | 6 | if [ -z "${VERSION}" ]; 7 | then 8 | echo "Usage: ${0} [--deploy]" 9 | exit 1 10 | fi 11 | 12 | echo "VERSION='${VERSION}'" > plaid2qif/__init__.py 13 | git tag --sign v${VERSION} --message="Release v${VERSION}" 14 | git push origin --tags 15 | 16 | if [ ! -z "${DEPLOY}" ]; 17 | then 18 | python3 -m build 19 | twine upload dist/* 20 | fi 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | docopt 2 | plaid-python >= 8.0.0 3 | python-dateutil 4 | -------------------------------------------------------------------------------- /sample.env: -------------------------------------------------------------------------------- 1 | ACCESS_TOKEN_FILE= 2 | 3 | PLAID_CLIENT_ID= 4 | PLAID_SECRET= 5 | PLAID_ENV=development 6 | 7 | PLAID_SANDBOX_REDIRECT_URI=https://localhost:8080/oauth 8 | PORT_NUMBER=8080 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from distutils.core import setup 3 | from setuptools import setup, find_packages 4 | 5 | with open('requirements.txt') as f: 6 | REQUIRED = [line.rstrip('\n') for line in f] 7 | 8 | __version__ = None 9 | with open('plaid2qif/__init__.py') as f: 10 | for line in f: 11 | if(line.startswith('VERSION')): 12 | __version__ = str(line.strip().split('=')[1]) 13 | __version__ = __version__.replace("'", '') # !!! 14 | print ("version is: [%s]" % __version__) 15 | break 16 | 17 | 18 | def read(fname): 19 | return open(os.path.join(os.path.dirname(__file__), fname), encoding='utf-8').read() 20 | 21 | setup( 22 | name = "plaid2qif", 23 | version = __version__, 24 | author = "Edward Bridges", 25 | author_email = "ebridges@roja.cc", 26 | description = "Download financial transactions from Plaid as QIF files.", 27 | license = "MIT", 28 | classifiers=[ 29 | "Development Status :: 5 - Production/Stable", 30 | "Intended Audience :: End Users/Desktop", 31 | "Intended Audience :: Financial and Insurance Industry", 32 | "License :: OSI Approved :: MIT License", 33 | "Operating System :: OS Independent", 34 | "Topic :: Home Automation", 35 | "Topic :: Office/Business :: Financial", 36 | "Topic :: Office/Business :: Financial :: Accounting", 37 | "Topic :: Utilities", 38 | "Programming Language :: Python :: 3", 39 | "Programming Language :: Python :: 3.6", 40 | "Programming Language :: Python :: 3 :: Only", 41 | ], 42 | keywords='plaid qif gnucash', 43 | packages=find_packages(), 44 | include_package_data=True, 45 | long_description=read('README.md'), 46 | long_description_content_type='text/markdown', 47 | install_requires=REQUIRED, 48 | python_requires='>=3', 49 | entry_points={ 50 | 'console_scripts': [ 51 | 'plaid2qif = plaid2qif.plaid2qif:main', 52 | ] 53 | }, 54 | ) 55 | --------------------------------------------------------------------------------