diff --git a/contributing/index.html b/contributing/index.html index 227d9642..f3b4ebf9 100644 --- a/contributing/index.html +++ b/contributing/index.html @@ -1874,8 +1874,8 @@

Hatch Explanation││ … ││ griffe-fi… │ │ … │ ││ ││ markdown-… │ │ … │ ││ ││ markdown-… │ │ … │ -││ ││ mkdocs │ │ … │ -││ ││ mkdocs-au… │ │ … │ +││ ││ mkdocs │ │ │ +││ ││ mkdocs-au… │ │ │ ││ ││ mkdocs-cl… │ │ │ ││ ││ mkdocs-ge… │ │ │ ││ ││ mkdocs-li… │ │ │ @@ -1971,10 +1971,10 @@

Hatch Explanation┃ Name ┃ Type ┃ Features ┃ Dependencies ┃ Scripts ┃ ┡━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩ │ docs │ pip-compile │ all │ griffe-fieldz │ build │ -│ │ │ │ markdown-callouts │ cov │ -│ │ │ │ markdown-exec │ gh-deploy │ -│ │ │ │ mkdocs │ serve │ -│ │ │ │ mkdocs-autorefs │ test │ +│ │ │ │ markdown-callouts │ gh-deploy │ +│ │ │ │ markdown-exec │ serve │ +│ │ │ │ mkdocs │ │ +│ │ │ │ mkdocs-autorefs │ │ │ │ │ │ mkdocs-click │ │ │ │ │ │ mkdocs-gen-files │ │ │ │ │ │ mkdocs-literate-nav │ │ diff --git a/search/search_index.json b/search/search_index.json index d7c2e0e3..a44e6994 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home \ud83c\udfe0","text":"lunchable

lunchable is a Python Client for the Lunch Money Developer API. It's built on top of pydantic and httpx, it offers an intuitive API, a simple CLI, complete coverage of all endpoints, and plugins to other external services.

"},{"location":"#installation","title":"Installation","text":"
pip install lunchable\n
"},{"location":"#usage","title":"Usage","text":"
from __future__ import annotations\n\nfrom typing import Any\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions()\n\nfirst_transaction: TransactionObject = transactions[0]\ntransaction_as_dict: dict[str, Any] = first_transaction.model_dump()\n
export LUNCHMONEY_ACCESS_TOKEN=\"xxxxxxxxxxx\"\nlunchable transactions get --limit 5\nlunchable http -X GET https://dev.lunchmoney.app/v1/assets\n
"},{"location":"cli/","title":"lunchable CLI","text":"
lunchable --help\n
Usage: lunchable [OPTIONS] COMMAND [ARGS]...\n\n  Interactions with Lunch Money via lunchable \ud83c\udf71\n\nOptions:\n  --version             Show the version and exit.\n  --access-token TEXT   LunchMoney Developer API Access Token\n  --debug / --no-debug  Enable extra debugging output\n  --help                Show this message and exit.\n\nCommands:\n  http          Interact with the LunchMoney API\n  plugins       Interact with Lunchable Plugins\n  transactions  Interact with Lunch Money transactions\n
"},{"location":"cli/#lunchable","title":"lunchable","text":"

Interactions with Lunch Money via lunchable \ud83c\udf71

Usage:

lunchable [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --version boolean Show the version and exit. False --access-token text LunchMoney Developer API Access Token None --debug / --no-debug boolean Enable extra debugging output False --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-http","title":"lunchable http","text":"

Interact with the LunchMoney API

lunchable http /v1/transactions

Usage:

lunchable http [OPTIONS] URL\n

Options:

Name Type Description Default -X, --request text Specify request command to use GET -d, --data text HTTP POST data None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins","title":"lunchable plugins","text":"

Interact with Lunchable Plugins

Usage:

lunchable plugins [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-plugins-primelunch","title":"lunchable plugins primelunch","text":"

PrimeLunch CLI - Syncing LunchMoney with Amazon

Usage:

lunchable plugins primelunch [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-plugins-primelunch-run","title":"lunchable plugins primelunch run","text":"

Run the PrimeLunch Update Process

Usage:

lunchable plugins primelunch run [OPTIONS]\n

Options:

Name Type Description Default -f, --file path File Path of the Amazon Export _required -w, --window integer Allowable time window between Amazon transaction date and credit card transaction date 7 -a, --all boolean Whether to skip the confirmation step and simply update all matched transactions False -t, --token text LunchMoney Access Token - defaults to the LUNCHMONEY_ACCESS_TOKEN environment variable None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-pushlunch","title":"lunchable plugins pushlunch","text":"

Push Notifications for Lunch Money: PushLunch \ud83d\udcf2

Usage:

lunchable plugins pushlunch [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-plugins-pushlunch-notify","title":"lunchable plugins pushlunch notify","text":"

Send a Notification for each Uncleared Transaction

Usage:

lunchable plugins pushlunch notify [OPTIONS]\n

Options:

Name Type Description Default --continuous boolean Whether to continuously check for more uncleared transactions, waiting a fixed amount in between checks. False --interval text Sleep Interval in Between Tries - only applies if continuous is set. Defaults to 60 (minutes). Cannot be less than 5 (minutes) None --user-key text Pushover User Key. Defaults to PUSHOVER_USER_KEY env var None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch","title":"lunchable plugins splitlunch","text":"

Splitwise Plugin for lunchable, SplitLunch \ud83d\udcb2\ud83c\udf71

Usage:

lunchable plugins splitlunch [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-plugins-splitlunch-expenses","title":"lunchable plugins splitlunch expenses","text":"

Retrieve Splitwise Expenses

Usage:

lunchable plugins splitlunch expenses [OPTIONS]\n

Options:

Name Type Description Default --limit text Limit the amount of Results. 0 returns everything. None --offset text Number of expenses to be skipped None --limit text Number of expenses to be returned None --group-id text GroupID of the expenses None --friendship-id text FriendshipID of the expenses None --dated-after text ISO 8601 Date time. Return expenses later that this date None --dated-before text ISO 8601 Date time. Return expenses earlier than this date None --updated-after text ISO 8601 Date time. Return expenses updated after this date None --updated-before text ISO 8601 Date time. Return expenses updated before this date None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-refresh","title":"lunchable plugins splitlunch refresh","text":"

Import New Splitwise Transactions to Lunch Money and

This function gets all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two. This also updates the account balance.

Usage:

lunchable plugins splitlunch refresh [OPTIONS]\n

Options:

Name Type Description Default --dated-after text ISO 8601 Date time. Return expenses later that this date None --dated-before text ISO 8601 Date time. Return expenses earlier than this date None --allow-self-paid / --no-allow-self-paid boolean Allow self-paid expenses to be imported (filtered out by default). False --allow-payments / --no-allow-payments boolean Allow payments to be imported (filtered out by default). False --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-splitlunch","title":"lunchable plugins splitlunch splitlunch","text":"

Split all SplitLunch tagged transactions in half.

One of these new splits will be recategorized to Reimbursement.

Usage:

lunchable plugins splitlunch splitlunch [OPTIONS]\n

Options:

Name Type Description Default --tag-transactions boolean Tag the resulting transactions with a Splitwise tag. False --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-splitlunch-direct-import","title":"lunchable plugins splitlunch splitlunch-direct-import","text":"

Import SplitLunchDirectImport tagged transactions to Splitwise and Split them in Lunch Money

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Any tags will be reapplied.

Usage:

lunchable plugins splitlunch splitlunch-direct-import [OPTIONS]\n

Options:

Name Type Description Default --tag-transactions boolean Tag the resulting transactions with a Splitwise tag. False --financial-partner-id integer Splitwise ID of your financial partner. None --financial-partner-email text Splitwise Email Address of your financial partner. None --financial-partner-group-id integer Splitwise Group ID for financial partner transactions. None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-splitlunch-import","title":"lunchable plugins splitlunch splitlunch-import","text":"

Import SplitLunchImport tagged transactions to Splitwise and Split them in Lunch Money

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Any tags will be reapplied.

Usage:

lunchable plugins splitlunch splitlunch-import [OPTIONS]\n

Options:

Name Type Description Default --tag-transactions boolean Tag the resulting transactions with a Splitwise tag. False --financial-partner-id integer Splitwise ID of your financial partner. None --financial-partner-email text Splitwise Email Address of your financial partner. None --financial-partner-group-id integer Splitwise Group ID for financial partner transactions. None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-update-balance","title":"lunchable plugins splitlunch update-balance","text":"

Update the Splitwise Asset Balance

Usage:

lunchable plugins splitlunch update-balance [OPTIONS]\n

Options:

Name Type Description Default --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-transactions","title":"lunchable transactions","text":"

Interact with Lunch Money transactions

Usage:

lunchable transactions [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-transactions-get","title":"lunchable transactions get","text":"

Retrieve Lunch Money Transactions

Usage:

lunchable transactions get [OPTIONS]\n

Options:

Name Type Description Default --start-date text Denotes the beginning of the time period to fetch transactions for. Defaultsto beginning of current month. Required if end_date exists. Format: YYYY-MM-DD. None --end-date text Denotes the end of the time period you'd like to get transactions for. Defaults to end of current month. Required if start_date exists.Format: YYYY-MM-DD. None --tag-id text Filter by tag. Only accepts IDs, not names. None --recurring-id text Filter by recurring expense None --plaid-account-id text Filter by Plaid account None --category-id text Filter by category. Will also match category groups. None --asset-id text Filter by asset None --group-id text Filter by group_id (if the transaction is part of a specific group) None --is-group text Filter by group (returns transaction groups) None --status text Filter by status (Can be cleared or uncleared. For recurring transactions, use recurring) None --offset text Sets the offset for the records returned None --limit text Sets the maximum number of records to return. Note: The server will not respond with any indication that there are more records to be returned. Please check the response length to determine if you should make another call with an offset to fetch more transactions. None --debit-as-negative text Pass in true if you\u2019d like expenses to be returned as negative amounts and credits as positive amounts. Defaults to false. None --pending boolean Pass in true if you\u2019d like to include imported transactions with a pending status. None --help boolean Show this message and exit. False"},{"location":"contributing/","title":"Contributing","text":""},{"location":"contributing/#environment-setup","title":"Environment Setup","text":"

pipx

This documentaion uses pipx to install and manage non-project command line tools like hatch and pre-commit. If you don't already have pipx installed, make sure to see their documentation. If you prefer not to use pipx, you can use pip instead.

  1. Install hatch

    pipx install hatch\n

    pre-commit

    Hatch will attempt to set up pre-commit hooks for you using pre-commit. If you don't already, make sure to install pre-commit as well: pipx install pre-commit

  2. Build the Virtual Environment

    hatch env create\n
  3. If you need to, you can link a hatch virtual environment to your IDE. They can be located by name with the env find command:

    hatch env find test\n
  4. Activate the Virtual Environment

    hatch shell\n
"},{"location":"contributing/#using-hatch","title":"Using Hatch","text":""},{"location":"contributing/#hatch-cheat-sheet","title":"Hatch Cheat Sheet","text":"Command Description Command Notes Run Tests hatch run cov Runs tests with pytest and coverage Run Formatting hatch run lint:fmt Runs ruff code formatter Run Linting hatch run lint:all Runs ruff and mypy linters / type checkers Run Type Checking hatch run lint:typing Runs mypy type checker Serve the Documentation hatch run docs:serve Serve the documentation using MkDocs Run the pre-commit Hooks hatch run lint:precommit Runs the pre-commit hooks on all files"},{"location":"contributing/#hatch-explanation","title":"Hatch Explanation","text":"

Hatch is a Python package manager. It's most basic use is as a standardized build-system. However, hatch also has some extra features which this project takes advantage of. These features include virtual environment management and the organization of common scripts like linting and testing. All the operations in hatch take place in one of its managed virtual environments.

Hatch has a variety of environments, to see them simply ask hatch:

hatch CLIOutput
hatch env show\n
                                   Standalone                                   \n\u250f\u2533\u2501\u2501\u2501\u2533\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2513\n\u2503\u2503 \u2026 \u2503\u2503 Dependenc\u2026 \u2503 Environment variables                                 \u2503 \u2026 \u2503\n\u2521\u2547\u2501\u2501\u2501\u2547\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2529\n\u2502\u2502 \u2026 \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u251c\u253c\u2500\u2500\u2500\u253c\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524\n\u2502\u2502 \u2026 \u2502\u2502 griffe-fi\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 markdown-\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 markdown-\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 mkdocs     \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-au\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-cl\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-ge\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-li\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-ma\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-se\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocstri\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocstri\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 pymdown-e\u2026 \u2502                                                       \u2502   \u2502\n\u251c\u253c\u2500\u2500\u2500\u253c\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524\n\u2502\u2502 \u2026 \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u251c\u253c\u2500\u2500\u2500\u253c\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524\n\u2502\u2502 \u2026 \u2502\u2502 mypy>=1.6\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 pydantic   \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 ruff~=0.1\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u251c\u253c\u2500\u2500\u2500\u253c\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524\n\u2502\u2502 \u2026 \u2502\u2502 coverage[\u2026 \u2502 LUNCHMONEY_ACCESS_TOKEN=LUNCHMONEY_ACCESS_TOKEN_PLAC\u2026 \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 pytest     \u2502 PUSHOVER_USER_KEY=PUSHOVER_USER_KEY_PLACEHOLDER       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 pytest-mo\u2026 \u2502 SENSITIVE_REQUEST_STRINGS=SENSITIVE_REQUEST_STRINGS_\u2026 \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 vcrpy~=5.\u2026 \u2502 SPLITWISE_API_KEY=SPLITWISE_API_KEY_PLACEHOLDER       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502            \u2502 SPLITWISE_CONSUMER_KEY=SPLITWISE_CONSUMER_KEY_PLACEH\u2026 \u2502   \u2502\n\u2502\u2502   \u2502\u2502            \u2502 SPLITWISE_CONSUMER_SECRET=SPLITWISE_CONSUMER_SECRET_\u2026 \u2502   \u2502\n\u2514\u2534\u2500\u2500\u2500\u2534\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2518\n                                    Matrices                                    \n\u250f\u2533\u2501\u2501\u2533\u2501\u2533\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2513\n\u2503\u2503  \u2503 \u2503\u2503 Depend\u2026 \u2503 Environment variables                                      \u2503\u2503\n\u2521\u2547\u2501\u2501\u2547\u2501\u2547\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2529\n\u2502\u2502  \u2502 \u2502\u2502 covera\u2026 \u2502 LUNCHMONEY_ACCESS_TOKEN={env:LUNCHMONEY_ACCESS_TOKEN:LUNC\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502 pytest  \u2502 PUSHOVER_USER_KEY={env:PUSHOVER_USER_KEY:PUSHOVER_USER_KE\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502 pytest\u2026 \u2502 SENSITIVE_REQUEST_STRINGS={env:SENSITIVE_REQUEST_STRINGS:\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502 vcrpy~\u2026 \u2502 SPLITWISE_API_KEY={env:SPLITWISE_API_KEY:SPLITWISE_API_KE\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502         \u2502 SPLITWISE_CONSUMER_KEY={env:SPLITWISE_CONSUMER_KEY:SPLITW\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502         \u2502 SPLITWISE_CONSUMER_SECRET={env:SPLITWISE_CONSUMER_SECRET:\u2026 \u2502\u2502\n\u2514\u2534\u2500\u2500\u2534\u2500\u2534\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2518\n

That above command will tell you that there are five environments that you can use:

Each of these environments has a set of commands that you can run. To see the commands for a specific environment, run:

hatch CLIOutput
hatch env show default\n
                  Standalone                  \n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503 Name    \u2503 Type        \u2503 Features \u2503 Scripts \u2503\n\u2521\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2529\n\u2502 default \u2502 pip-compile \u2502 all      \u2502 cov     \u2502\n\u2502         \u2502             \u2502          \u2502 test    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n

Here we can see that the default environment has the following commands:

The one that we're interested in is cov, which will run the tests for the project.

hatch run cov\n

Since cov is in the default environment, we can run it without specifying the environment. However, to run the serve command in the docs environment, we need to specify the environment:

hatch run docs:serve\n

You can see what scripts are available using the env show command

hatch CLIOutput
hatch env show docs\n
                             Standalone                             \n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503 Name \u2503 Type        \u2503 Features \u2503 Dependencies         \u2503 Scripts   \u2503\n\u2521\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2529\n\u2502 docs \u2502 pip-compile \u2502 all      \u2502 griffe-fieldz        \u2502 build     \u2502\n\u2502      \u2502             \u2502          \u2502 markdown-callouts    \u2502 cov       \u2502\n\u2502      \u2502             \u2502          \u2502 markdown-exec        \u2502 gh-deploy \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs               \u2502 serve     \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-autorefs      \u2502 test      \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-click         \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-gen-files     \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-literate-nav  \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-material      \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-section-index \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocstrings         \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocstrings-python  \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 pymdown-extensions   \u2502           \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n
"},{"location":"contributing/#committing-code","title":"Committing Code","text":"

This project uses pre-commit to run a set of checks on the code before it is committed. The pre-commit hooks are installed by hatch automatically when you run it for the first time.

This project uses semantic-versioning standards, managed by semantic-release. Releases for this project are handled entirely by CI/CD via pull requests being merged into the main branch. Contributions follow the gitmoji standards with conventional commits.

While you can denote other changes on your commit messages with gitmoji, the following commit message emoji prefixes are the only ones to trigger new releases:

Emoji Shortcode Description Semver \ud83d\udca5 :boom: Introduce breaking changes. Major \u2728 :sparkles: Introduce new features. Minor \ud83d\udc1b :bug: Fix a bug. Patch \ud83d\ude91 :ambulance: Critical hotfix. Patch \ud83d\udd12 :lock: Fix security issues. Patch

Most features can be squash merged into a single commit on a pull-request. When merging multiple commits, they will be summarized into a single release.

If you're working on a new feature, your commit message might look like:

\u2728 New Feature Description\n

Bug fix commits would look like this:

\ud83d\udc1b Bug Fix Description\n

If you're working on a feature that introduces breaking changes, your commit message might look like:

\ud83d\udca5 Breaking Change Description\n

Other commits that don't trigger a release might look like this:

\ud83d\udcdd Documentation Update Description\n\ud83d\udc77 CI/CD Update Description\n\ud83e\uddea Testing Changes Description\n\ud83d\ude9a Moving/Renaming Description\n\u2b06\ufe0f Dependency Upgrade Description\n
"},{"location":"contributing/#pre-releases","title":"Pre-Releases","text":"

semantic-release supports pre-releases. To trigger a pre-release, you would merge your pull request into an alpha or beta branch.

"},{"location":"contributing/#specific-release-versions","title":"Specific Release Versions","text":"

In some cases you need more advanced control around what kind of release you need to create. If you need to release a specific version, you can do so by creating a new branch with the version number as the branch name. For example, if the current version is 2.3.2, but you need to release a fix as 1.2.5, you would create a branch named 1.2.x and merge your changes into that branch.

See the semantic-release documentation for more information about branch based releases and other advanced release cases.

"},{"location":"interacting/","title":"LunchMoney","text":"

The LunchMoney client is the main entrypoint for interacting with the Lunch Money API. It defaults to inheriting the LUNCHMONEY_ACCESS_TOKEN environment variable, but can be created with an explicit access_token parameter.

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxxxxxx\")\n
"},{"location":"interacting/#methods","title":"Methods","text":"HTTP Verb Name Description GET get_assets Get Manually Managed Assets GET get_budgets Get Monthly Budgets GET get_categories Get Spending categories GET get_category Get single category GET get_crypto Get Crypto Assets GET get_plaid_accounts Get Plaid Synced Assets GET get_recurring_expenses Get Recurring Expenses GET get_tags Get Spending Tags GET get_transaction Get a Transaction by ID GET get_transactions Get Transactions Using Criteria GET get_user Get Personal User Details POST insert_asset Create a single (manually-managed) asset POST insert_category Create a Spending Category POST insert_category_group Create a Spending Category Group POST insert_into_category_group Add to a Category Group POST insert_transaction_group Create a Transaction Group of Two or More Transactions POST insert_transactions Create One or Many Lunch Money Transactions POST unsplit_transactions Unsplit Transactions PUT upsert_budget Upsert a Budget for a Category and Date PUT update_asset Update a Single Asset PUT update_category Update a single category PUT update_crypto Update a Manual Crypto Asset PUT update_transaction Update a Transaction DELETE remove_budget Unset an Existing Budget for a Particular Category in a Particular Month DELETE remove_category Delete a single category DELETE remove_category_force Forcefully delete a single category DELETE remove_transaction_group Delete a Transaction Group"},{"location":"interacting/#low-level-methods","title":"Low Level Methods","text":"Name Description request Make an HTTP request arequest Make an async HTTP request process_response Process a Lunch Money response and raise any errors make_request Make an HTTP request and process its response amake_request Make an async HTTP request and process its response"},{"location":"interacting/#attributes","title":"Attributes","text":"Name Description async_session Authenticated Async HTTPX Client session Authenticated HTTPX Client"},{"location":"interacting/#class-documentation","title":"Class Documentation","text":"

Lunch Money Python Client.

This class facilitates with connections to the Lunch Money Developer API. Authenticate with an Access Token. If an access token isn't provided one will attempt to be inherited from a LUNCHMONEY_ACCESS_TOKEN environment variable.

Examples:

from __future__ import annotations\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions()\n
"},{"location":"interacting/#lunchable.LunchMoney.async_session","title":"async_session: httpx.AsyncClient cached property","text":"

Lunch Money HTTPX Async Client

Returns:

Type Description AsyncClient"},{"location":"interacting/#lunchable.LunchMoney.session","title":"session: httpx.Client cached property","text":"

Lunch Money HTTPX Client

Returns:

Type Description Client"},{"location":"interacting/#lunchable.LunchMoney.Methods","title":"Methods","text":"

HTTP Request Method Enumerations: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

"},{"location":"interacting/#lunchable.LunchMoney.__init__","title":"__init__(access_token=None)","text":"

Initialize a Lunch Money object with an Access Token.

Tries to inherit from the Environment if one isn't provided

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None"},{"location":"interacting/#lunchable.LunchMoney.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str"},{"location":"interacting/#lunchable.LunchMoney.amake_request","title":"amake_request(method, url_path, params=None, payload=None, **kwargs) async","text":"

Make an async HTTP request and process its response

This method is a wrapper around :meth:.LunchMoney.arequest that also processes the response and checks for any errors.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url_path Union[list[Union[str, int]], str, int]

URL components to make into a URL

required payload Optional[Any]

Data to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Any"},{"location":"interacting/#lunchable.LunchMoney.arequest","title":"arequest(method, url, *, content=None, data=None, json=None, params=None, **kwargs) async","text":"

Make an async HTTP request

This is a simple method :class:.LunchMoney exposes to make HTTP requests. It has the benefit of using an existing httpx.Client as well as as out of the box auth headers that are used to connect to the Lunch Money Developer API.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url Union[URL, str]

URL for the new Request object.

required content Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]

Content to send in the body of the Request.

None data Optional[Mapping[str, Any]]

Dictionary, list of tuples, bytes, or file-like object to send in the body of the Request.

None json Optional[Any]

A JSON serializable Python object to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Response"},{"location":"interacting/#lunchable.LunchMoney.get_assets","title":"get_assets()","text":"

Get Manually Managed Assets

Get a list of all manually-managed assets associated with the user's account.

(https://lunchmoney.dev/#assets-object)

Returns:

Type Description List[AssetsObject]"},{"location":"interacting/#lunchable.LunchMoney.get_budgets","title":"get_budgets(start_date, end_date)","text":"

Get Monthly Budgets

Get full details on the budgets for all categories between a certain time period. The budgeted and spending amounts will be an aggregate across this time period. (https://lunchmoney.dev/#plaid-accounts-object)

Returns:

Type Description List[BudgetObject]"},{"location":"interacting/#lunchable.LunchMoney.get_categories","title":"get_categories()","text":"

Get Spending categories

Use this endpoint to get a list of all categories associated with the user's account. https://lunchmoney.dev/#get-all-categories

Returns:

Type Description List[CategoriesObject]"},{"location":"interacting/#lunchable.LunchMoney.get_category","title":"get_category(category_id)","text":"

Get single category

Use this endpoint to get hydrated details on a single category. Note that if this category is part of a category group, its properties (is_income, exclude_from_budget, exclude_from_totals) will inherit from the category group.

https://lunchmoney.dev/#get-single-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description CategoriesObject"},{"location":"interacting/#lunchable.LunchMoney.get_crypto","title":"get_crypto()","text":"

Get Crypto Assets

Use this endpoint to get a list of all cryptocurrency assets associated with the user's account. Both crypto balances from synced and manual accounts will be returned.

https://lunchmoney.dev/#get-all-crypto

Returns:

Type Description List[CryptoObject]"},{"location":"interacting/#lunchable.LunchMoney.get_plaid_accounts","title":"get_plaid_accounts()","text":"

Get Plaid Synced Assets

Get a list of all synced Plaid accounts associated with the user's account.

Plaid Accounts are individual bank accounts that you have linked to Lunch Money via Plaid. You may link one bank but one bank might contain 4 accounts. Each of these accounts is a Plaid Account. (https://lunchmoney.dev/#plaid-accounts-object)

Returns:

Type Description List[PlaidAccountObject]"},{"location":"interacting/#lunchable.LunchMoney.get_recurring_expenses","title":"get_recurring_expenses(start_date=None, debit_as_negative=False)","text":"

Get Recurring Expenses

Retrieve a list of recurring expenses to expect for a specified period.

Every month, a different set of recurring expenses is expected. This is because recurring expenses can be once a year, twice a year, every 4 months, etc.

If a recurring expense is listed as \u201ctwice a month\u201d, then that recurring expense will be returned twice, each with a different billing date based on when the system believes that recurring expense transaction is to be expected. If the recurring expense is listed as \u201conce a week\u201d, then that recurring expense will be returned in this list as many times as there are weeks for the specified month.

In the same vein, if a recurring expense that began last month is set to \u201cEvery 3 months\u201d, then that recurring expense will not show up in the results for this month.

Parameters:

Name Type Description Default start_date Optional[date]

Date to search. By default will return the first day of the current month

None debit_as_negative bool

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts.

False

Returns:

Type Description List[RecurringExpensesObject]"},{"location":"interacting/#lunchable.LunchMoney.get_tags","title":"get_tags()","text":"

Get Spending Tags

Use this endpoint to get a list of all tags associated with the user's account.

https://lunchmoney.dev/#get-all-tags

Returns:

Type Description List[TagsObject]"},{"location":"interacting/#lunchable.LunchMoney.get_transaction","title":"get_transaction(transaction_id)","text":"

Get a Transaction by ID

Parameters:

Name Type Description Default transaction_id int

Lunch Money Transaction ID

required

Returns:

Type Description TransactionObject

Examples:

Retrieve a single transaction by its ID

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction = lunch.get_transaction(transaction_id=1234)\n

The above code returns a TransactionObject with ID # 1234 (assuming it exists)

"},{"location":"interacting/#lunchable.LunchMoney.get_transactions","title":"get_transactions(start_date=None, end_date=None, tag_id=None, recurring_id=None, plaid_account_id=None, category_id=None, asset_id=None, group_id=None, is_group=None, status=None, offset=None, limit=None, debit_as_negative=None, pending=None, params=None)","text":"

Get Transactions Using Criteria

Use this to retrieve all transactions between a date range. Returns list of Transaction objects. If no query parameters are set, this will return transactions for the current calendar month. If either start_date or end_date are datetime.datetime objects, they will be reduced to dates. If a string is provided, it will be attempted to be parsed as YYYY-MM-DD format.

Parameters:

Name Type Description Default start_date Optional[Union[date, datetime, str]]

Denotes the beginning of the time period to fetch transactions for. Defaults to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.

None end_date Optional[Union[date, datetime, str]]

Denotes the end of the time period you'd like to get transactions for. Defaults to end of current month. Required if start_date exists.

None tag_id Optional[int]

Filter by tag. Only accepts IDs, not names.

None recurring_id Optional[int]

Filter by recurring expense

None plaid_account_id Optional[int]

Filter by Plaid account

None category_id Optional[int]

Filter by category. Will also match category groups.

None asset_id Optional[int]

Filter by asset

None group_id Optional[int]

Filter by group_id (if the transaction is part of a specific group)

None is_group Optional[bool]

Filter by group (returns transaction groups)

None status Optional[str]

Filter by status (Can be cleared or uncleared. For recurring transactions, use recurring)

None offset Optional[int]

Sets the offset for the records returned

None limit Optional[int]

Sets the maximum number of records to return. Note: The server will not respond with any indication that there are more records to be returned. Please check the response length to determine if you should make another call with an offset to fetch more transactions.

None debit_as_negative Optional[bool]

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts. Defaults to false.

None pending Optional[bool]

Pass in true if you'd like to include imported transactions with a pending status.

None params Optional[Dict[str, Any]]

Additional Query String Params

None

Returns:

Type Description List[TransactionObject]

A list of transactions

Examples:

Retrieve a list of TransactionObject

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions = lunch.get_transactions(start_date=\"2020-01-01\",\n                                      end_date=\"2020-01-31\")\n
"},{"location":"interacting/#lunchable.LunchMoney.get_user","title":"get_user()","text":"

Get Personal User Details

Use this endpoint to get details on the current user.

https://lunchmoney.dev/#get-user

Returns:

Type Description UserObject"},{"location":"interacting/#lunchable.LunchMoney.insert_asset","title":"insert_asset(type_name, name=None, subtype_name=None, display_name=None, balance=0.0, balance_as_of=None, currency=None, institution_name=None, closed_on=None, exclude_transactions=False)","text":"

Create a single (manually-managed) asset.

Parameters:

Name Type Description Default type_name str

Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation

required name Optional[str]

Max 45 characters

None subtype_name Optional[str]

Max 25 characters

None display_name Optional[str]

Display name of the asset (as set by user)

None balance float

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point! Defaults to 0.00

0.0 balance_as_of Optional[datetime]

Has no effect if balance is not defined. If balance is defined, but balance_as_of is not supplied or is invalid, current timestamp will be used.

None currency Optional[str]

Three-letter lowercase currency in ISO 4217 format. The code sent must exist in our database. Defaults to user's primary currency.

None institution_name Optional[str]

Max 50 characters

None closed_on Optional[date]

The date this asset was closed

None exclude_transactions bool

If true, this asset will not show up as an option for assignment when creating transactions manually. Defaults to False

False

Returns:

Type Description AssetsObject"},{"location":"interacting/#lunchable.LunchMoney.insert_category","title":"insert_category(name, description=None, is_income=False, exclude_from_budget=False, exclude_from_totals=False)","text":"

Create a Spending Category

Use this to create a single category https://lunchmoney.dev/#create-category

Parameters:

Name Type Description Default name str

Name of category. Must be between 1 and 40 characters.

required description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

False exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

False exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

False

Returns:

Type Description int

ID of the newly created category

"},{"location":"interacting/#lunchable.LunchMoney.insert_category_group","title":"insert_category_group(name, description=None, is_income=False, exclude_from_budget=False, exclude_from_totals=False, category_ids=None, new_categories=None)","text":"

Create a Spending Category Group

Use this endpoint to create a single category group https://lunchmoney.dev/#create-category-group

Parameters:

Name Type Description Default name str

Name of category. Must be between 1 and 40 characters.

required description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

False exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

False exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

False category_ids Optional[List[int]]

Array of category_id to include in the category group.

None new_categories Optional[List[str]]

Array of strings representing new categories to create and subsequently include in the category group.

None

Returns:

Type Description int

ID of the newly created category group

"},{"location":"interacting/#lunchable.LunchMoney.insert_into_category_group","title":"insert_into_category_group(category_group_id, category_ids=None, new_categories=None)","text":"

Add to a Category Group

Use this endpoint to add categories (either existing or new) to a single category group

https://lunchmoney.dev/#add-to-category-group

Parameters:

Name Type Description Default category_group_id int

Id of the Lunch Money Category Group

required category_ids Optional[List[int]]

Array of category_id to include in the category group.

None new_categories Optional[List[str]]

Array of strings representing new categories to create and subsequently include in the category group.

None

Returns:

Type Description CategoriesObject"},{"location":"interacting/#lunchable.LunchMoney.insert_transaction_group","title":"insert_transaction_group(date, payee, transactions, category_id=None, notes=None, tags=None)","text":"

Create a Transaction Group of Two or More Transactions

Returns the ID of the newly created transaction group

Parameters:

Name Type Description Default date date

Date for the grouped transaction

required payee str

Payee name for the grouped transaction

required category_id Optional[int]

Category for the grouped transaction

None notes Optional[str]

Notes for the grouped transaction

None tags Optional[List[int]]

Array of tag IDs for the grouped transaction

None transactions List[int]

Array of transaction IDs to be part of the transaction group

required

Returns:

Type Description int"},{"location":"interacting/#lunchable.LunchMoney.insert_transactions","title":"insert_transactions(transactions, apply_rules=False, skip_duplicates=True, debit_as_negative=False, check_for_recurring=False, skip_balance_update=True)","text":"

Create One or Many Lunch Money Transactions

Use this endpoint to insert many transactions at once. Also accepts a single transaction as well. If a TransactionObject is provided it will be converted into a TransactionInsertObject.

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default transactions ListOrSingleTransactionInsertObject

Transactions to insert. Either a single TransactionInsertObject object or a list of them

required apply_rules bool

If true, will apply account's existing rules to the inserted transactions. Defaults to false.

False skip_duplicates bool

If true, the system will automatically dedupe based on transaction date, payee and amount. Note that deduping by external_id will occur regardless of this flag.

True check_for_recurring bool

if true, will check new transactions for occurrences of new monthly expenses. Defaults to false.

False debit_as_negative bool

If true, will assume negative amount values denote expenses and positive amount values denote credits. Defaults to false.

False skip_balance_update bool

If false, will skip updating balance if an asset_id is present for any of the transactions.

True

Returns:

Type Description List[int]

Examples:

Create a new transaction with a TransactionInsertObject

from lunchable import LunchMoney, TransactionInsertObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\n\nnew_transaction = TransactionInsertObject(payee=\"Example Restaurant\",\n                                          amount=120.00,\n                                          notes=\"Saturday Dinner\")\nnew_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n
"},{"location":"interacting/#lunchable.LunchMoney.make_request","title":"make_request(method, url_path, params=None, payload=None, **kwargs)","text":"

Make an HTTP request and process its response

This method is a wrapper around :meth:.LunchMoney.request that also processes the response and checks for any errors.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url_path Union[list[Union[str, int]], str, int]

URL components to make into a URL

required payload Optional[Any]

Data to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Any"},{"location":"interacting/#lunchable.LunchMoney.process_response","title":"process_response(response) classmethod","text":"

Process a Lunch Money response and raise any errors

This includes 200 responses that are actually errors

Parameters:

Name Type Description Default response Response

An HTTPX Response Object

required"},{"location":"interacting/#lunchable.LunchMoney.remove_budget","title":"remove_budget(start_date, category_id)","text":"

Unset an Existing Budget for a Particular Category in a Particular Month

Parameters:

Name Type Description Default start_date date

Start date for the budget period. Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

required category_id int

Unique identifier for the category

required

Returns:

Type Description bool"},{"location":"interacting/#lunchable.LunchMoney.remove_category","title":"remove_category(category_id)","text":"

Delete a single category

Use this endpoint to delete a single category or category group. This will only work if there are no dependencies, such as existing budgets for the category, categorized transactions, categorized recurring items, etc. If there are dependents, this endpoint will return what the dependents are and how many there are.

https://lunchmoney.dev/#delete-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description bool"},{"location":"interacting/#lunchable.LunchMoney.remove_category_force","title":"remove_category_force(category_id)","text":"

Forcefully delete a single category

Use this endpoint to force delete a single category or category group and along with it, disassociate the category from any transactions, recurring items, budgets, etc.

Note: it is best practice to first try the Delete Category endpoint to ensure you don't accidentally delete any data. Disassociation/deletion of the data arising from this endpoint is irreversible!

https://lunchmoney.dev/#force-delete-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description bool"},{"location":"interacting/#lunchable.LunchMoney.remove_transaction_group","title":"remove_transaction_group(transaction_group_id)","text":"

Delete a Transaction Group

Use this method to delete a transaction group. The transactions within the group will not be removed.

Returns the IDs of the transactions that were part of the deleted group

https://lunchmoney.dev/#delete-transaction-group

Parameters:

Name Type Description Default transaction_group_id int

Transaction Group Identifier

required

Returns:

Type Description List[int]"},{"location":"interacting/#lunchable.LunchMoney.request","title":"request(method, url, *, content=None, data=None, json=None, params=None, **kwargs)","text":"

Make an HTTP request

This is a simple method :class:.LunchMoney exposes to make HTTP requests. It has the benefit of using an existing httpx.Client as well as as out of the box auth headers that are used to connect to the Lunch Money Developer API.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url Union[URL, str]

URL for the new Request object.

required content Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]

Content to send in the body of the Request.

None data Optional[Mapping[str, Any]]

Dictionary, list of tuples, bytes, or file-like object to send in the body of the Request.

None json Optional[Any]

A JSON serializable Python object to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Response

Examples:

A recent use of this method was to delete a Tag (which isn't available via the Developer API yet)

import lunchable\n\nlunch = lunchable.LunchMoney()\n\n# Get All the Tags\nall_tags = lunch.get_tags()\n# Get All The Null Tags (a list of 1 or zero)\nnull_tags = [tag for tag in all_tags if tag.name in [None, \"\"]]\n\n# Create a Cookie dictionary from a browser session\ncookies = {\"cookie_keys\": \"cookie_values\"}\ndel lunch.session.headers[\"authorization\"]\n\nfor null_tag in null_tags:\n    # use the httpx.client embedded in the class to make a request with cookies\n    response = lunch.request(\n        method=lunch.Methods.DELETE,\n        url=f\"https://api.lunchmoney.app/tags/{null_tag.id}\",\n        cookies=cookies\n    )\n    # raise an error for 4XX responses\n    response.raise_for_status()\n
"},{"location":"interacting/#lunchable.LunchMoney.unsplit_transactions","title":"unsplit_transactions(parent_ids, remove_parents=False)","text":"

Unsplit Transactions

Use this endpoint to unsplit one or more transactions.

Returns an array of IDs of deleted transactions

https://lunchmoney.dev/#unsplit-transactions

Parameters:

Name Type Description Default parent_ids List[int]

Array of transaction IDs to unsplit. If one transaction is unsplittable, no transaction will be unsplit.

required remove_parents bool

If true, deletes the original parent transaction as well. Note, this is unreversable!

False

Returns:

Type Description List[int]"},{"location":"interacting/#lunchable.LunchMoney.update_asset","title":"update_asset(asset_id, type_name=None, subtype_name=None, name=None, balance=None, balance_as_of=None, currency=None, institution_name=None)","text":"

Update a Single Asset

Parameters:

Name Type Description Default asset_id int

Asset Identifier

required type_name Optional[str]

Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation

None subtype_name Optional[str]

Max 25 characters

None name Optional[str]

Max 45 characters

None balance Optional[float]

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point!

None balance_as_of Optional[datetime]

Has no effect if balance is not defined. If balance is defined, but balance_as_of is not supplied or is invalid, current timestamp will be used.

None currency Optional[str]

Three-letter lowercase currency in ISO 4217 format. The code sent must exist in our database. Defaults to asset's currency.

None institution_name Optional[str]

Max 50 characters

None

Returns:

Type Description AssetsObject"},{"location":"interacting/#lunchable.LunchMoney.update_category","title":"update_category(category_id, name=None, description=None, is_income=None, exclude_from_budget=None, exclude_from_totals=None, group_id=None)","text":"

Update a single category

Use this endpoint to update the properties for a single category or category group

https://lunchmoney.dev/#update-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required name Optional[str]

Name of category. Must be between 1 and 40 characters.

None description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

None exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

None exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

None group_id Optional[int]

For a category, set the group_id to include it in a category group

None

Returns:

Type Description bool"},{"location":"interacting/#lunchable.LunchMoney.update_crypto","title":"update_crypto(crypto_id, name=None, display_name=None, institution_name=None, balance=None, currency=None)","text":"

Update a Manual Crypto Asset

Use this endpoint to update a single manually-managed crypto asset (does not include assets received from syncing with your wallet/exchange/etc). These are denoted by source: manual from the GET call above.

https://lunchmoney.dev/#update-manual-crypto-asset

Parameters:

Name Type Description Default crypto_id int

ID of the crypto asset to update

required name Optional[str]

Official or full name of the account. Max 45 characters

None display_name Optional[str]

Display name for the account. Max 25 characters

None institution_name Optional[str]

Name of provider that holds the account. Max 50 characters

None balance Optional[float]

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point!

None currency Optional[str]

Cryptocurrency that is supported for manual tracking in our database

None

Returns:

Type Description CryptoObject"},{"location":"interacting/#lunchable.LunchMoney.update_transaction","title":"update_transaction(transaction_id, transaction=None, split=None, debit_as_negative=False, skip_balance_update=True)","text":"

Update a Transaction

Use this endpoint to update a single transaction. You may also use this to split an existing transaction. If a TransactionObject is provided it will be converted into a TransactionUpdateObject.

PUT https://dev.lunchmoney.app/v1/transactions/:transaction_id

Parameters:

Name Type Description Default transaction_id int

Lunch Money Transaction ID

required transaction ListOrSingleTransactionUpdateObject

Object to update with

None split Optional[List[TransactionSplitObject]]

Defines the split of a transaction. You may not split an already-split transaction, recurring transaction, or group transaction.

None debit_as_negative bool

If true, will assume negative amount values denote expenses and positive amount values denote credits. Defaults to false.

False skip_balance_update bool

If false, will skip updating balance if an asset_id is present for any of the transactions.

True

Returns:

Type Description Dict[str, Any]

Examples:

Update a transaction with a TransactionUpdateObject

from datetime import datetime\n\nfrom lunchable import LunchMoney, TransactionUpdateObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction_note = f\"Updated on {datetime.now()}\"\nnotes_update = TransactionUpdateObject(notes=transaction_note)\nresponse = lunch.update_transaction(transaction_id=1234,\n                                    transaction=notes_update)\n

Update a TransactionObject with itself

from datetime import datetime, timedelta\n\nfrom lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction = lunch.get_transaction(transaction_id=1234)\n\ntransaction.notes = f\"Updated on {datetime.now()}\"\ntransaction.date = transaction.date + timedelta(days=1)\nresponse = lunch.update_transaction(transaction_id=transaction.id,\n                                    transaction=transaction)\n
"},{"location":"interacting/#lunchable.LunchMoney.upsert_budget","title":"upsert_budget(start_date, category_id, amount, currency=None)","text":"

Upsert a Budget for a Category and Date

Use this endpoint to update an existing budget or insert a new budget for a particular category and date.

Note: Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

If this is a sub-category, the response will include the updated category group's budget. This is because setting a sub-category may also update the category group's overall budget.

https://lunchmoney.dev/#upsert-budget

Parameters:

Name Type Description Default start_date date

Start date for the budget period. Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

required category_id int

Unique identifier for the category

required amount float

Amount for budget

required currency Optional[str]

Currency for the budgeted amount (optional). If empty, will default to your primary currency

None

Returns:

Type Description Optional[Dict[str, Any]]"},{"location":"usage/","title":"Usage","text":""},{"location":"usage/#installation","title":"Installation","text":"

To use lunchable, first install it using pip:

pip install lunchable\n
"},{"location":"usage/#client","title":"Client","text":"

The LunchMoney client is the main entrypoint for interacting with the Lunch Money API. It defaults to inheriting the LUNCHMONEY_ACCESS_TOKEN environment variable, but can be created with an explicit access_token parameter.

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\n

Read more about Interacting with Lunch Money to see what else you can do.

"},{"location":"usage/#transactions","title":"Transactions","text":""},{"location":"usage/#retrieve-a-list-of-transactionobject","title":"Retrieve a list of TransactionObject","text":"
from __future__ import annotations\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions(\n    start_date=\"2020-01-01\",\n    end_date=\"2020-01-31\"\n)\n
"},{"location":"usage/#retrieve-a-single-transaction-transactionobject","title":"Retrieve a single transaction (TransactionObject)","text":"
from lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction: TransactionObject = lunch.get_transaction(transaction_id=1234)\n

The above code returns a TransactionObject with ID # 1234 (assuming it exists)

"},{"location":"usage/#update-a-transaction-with-a-transactionupdateobject","title":"Update a transaction with a TransactionUpdateObject","text":"
from __future__ import annotations\n\nfrom datetime import datetime\nfrom typing import Any\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionUpdateObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction_note = f\"Updated on {datetime.now()}\"\nnotes_update = TransactionUpdateObject(notes=transaction_note)\nresponse: dict[str, Any] = lunch.update_transaction(\n    transaction_id=1234,\n    transaction=notes_update\n)\n
"},{"location":"usage/#update-a-transactionobject-with-itself","title":"Update a TransactionObject with itself","text":"
from datetime import datetime, timedelta\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction: TransactionObject = lunch.get_transaction(transaction_id=1234)\n\ntransaction.notes = f\"Updated on {datetime.now()}\"\ntransaction.date = transaction.date + timedelta(days=1)\nresponse = lunch.update_transaction(\n    transaction_id=transaction.id,\n    transaction=transaction\n)\n
"},{"location":"usage/#create-a-new-transaction-with-a-transactioninsertobject","title":"Create a new transaction with a TransactionInsertObject","text":"

transactions can be a single TransactionInsertObject or a list of TransactionInsertObject.

from lunchable import LunchMoney\nfrom lunchable.models import TransactionInsertObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\n\nnew_transaction = TransactionInsertObject(\n    payee=\"Example Restaurant\",\n    amount=120.00,\n    notes=\"Saturday Dinner\"\n)\nnew_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n
"},{"location":"usage/#use-the-lunchable-cli","title":"Use the Lunchable CLI","text":"
lunchable transactions get --limit 5\n
"},{"location":"usage/#use-the-lunchable-cli-via-docker","title":"Use the Lunchable CLI via Docker","text":"
docker pull juftin/lunchable\n
docker run \\\n    --env LUNCHMONEY_ACCESS_TOKEN=${LUNCHMONEY_ACCESS_TOKEN} \\\n    juftin/lunchable:latest \\\n    lunchable transactions get --limit 5\n
"},{"location":"plugins/","title":"Plugins","text":"

lunchable supports plugins with other, external, services. See below for what's been built already. If you can't find what you're looking for, consider building it yourself and opening a pull-request.

"},{"location":"plugins/#pushlunch-push-notifications-via-pushover","title":"PushLunch: Push Notifications via Pushover","text":""},{"location":"plugins/#splitlunch-splitwise-integration","title":"SplitLunch: Splitwise Integration","text":""},{"location":"plugins/#primelunch-amazon-transaction-updater","title":"PrimeLunch: Amazon Transaction Updater","text":""},{"location":"plugins/primelunch/","title":"PrimeLunch: Amazon Transaction Updater","text":"

PrimeLunch is a command line tool that supports updating Amazon transaction notes with the items from Amazon itself. This tool uses CSVs generated by the Amazon Order History Reporter plugin on Chrome. Once you've gathered your transactions, export them as a CSV and scan them with the tool. You'll be asked which transactions you'd like to update.

The plugin uses the dollar amounts on the CSV export to match Amazon transactions in LunchMoney. When a matching dollar amount is found, PrimeLunch compares the date window between the transactions to determine if they're really a match.

We're using the Amazon Order History Reporter plugin because it gives us some functionality that Amazon doesn't: exporting Amazon transactions as they're grouped on actual credit card transactions.

"},{"location":"plugins/primelunch/#run-via-the-lunchable-cli","title":"Run via the Lunchable CLI","text":"

You can install lunchable with pip or pipx:

pipx install \"lunchable[primelunch]\"\n
pip install \"lunchable[primelunch]\"\n

The below command runs the PrimeLunch update tool:

lunchable plugins primelunch run -f ~/Downloads/amazon_order_history.csv\n

Windows Users

The commands on this documentation correspond to running on a Mac or Linux Machine. If you are a Windows user take note of the following items:

The below command runs the PrimeLunch update tool using a date window of fourteen days instead of the default seven days (these larger windows are especially useful for finding refunds and recurring purchases):

lunchable plugins primelunch run \\\n   --file ~/Downloads/amazon_order_history.csv \\\n   --window 14\n

Update all transactions without going through the confirmation prompt for each one:

lunchable plugins primelunch run \\\n   --file ~/Downloads/amazon_order_history.csv \\\n   --all\n

Provide a LunchMoney API access token manually (PrimeLunch defaults to inheriting from the LUNCHMONEY_ACCESS_TOKEN environment variable):

lunchable plugins primelunch run \\\n   --file ~/Downloads/amazon_order_history.csv \\\n   --token ABCDEFGHIJKLMNOP\n
"},{"location":"plugins/primelunch/#command-line-documentation","title":"Command Line Documentation","text":""},{"location":"plugins/primelunch/#lunchable-plugins-primelunch-run","title":"lunchable plugins primelunch run","text":"

Run the PrimeLunch Update Process

Usage:

lunchable plugins primelunch run [OPTIONS]\n

Options:

Name Type Description Default -f, --file path File Path of the Amazon Export _required -w, --window integer Allowable time window between Amazon transaction date and credit card transaction date 7 -a, --all boolean Whether to skip the confirmation step and simply update all matched transactions False -t, --token text LunchMoney Access Token - defaults to the LUNCHMONEY_ACCESS_TOKEN environment variable None --help boolean Show this message and exit. False"},{"location":"plugins/primelunch/#references","title":"References","text":"

This lunchable plugin was inspired by the original Lunchable Amazon importer at samwelnella/amazon-transactions-to-lunchmoney.

"},{"location":"plugins/pushlunch/","title":"PushLunch: Push Notifications via Pushover","text":"

PushLunch supports Push Notifications via Pushover. Pushover supports iOS and Android Push notifications. To get started just provide your Pushover User Key directly or via the PUSHOVER_USER_KEY environment variable.

"},{"location":"plugins/pushlunch/#run-via-the-lunchable-cli","title":"Run via the Lunchable CLI","text":"

The below command checks for un-reviewed transactions in the current period and sends them as Push Notifications. The --continuous flag tells it to run forever which will only send you a push notification once for each transaction. By default it will check every 60 minutes, but this can be changed using the --interval argument.

lunchable plugins pushlunch notify --continuous\n
"},{"location":"plugins/pushlunch/#run-via-docker","title":"Run via Docker","text":"
docker run --rm \\\n    --env LUNCHMONEY_ACCESS_TOKEN=${LUNCHMONEY_ACCESS_TOKEN} \\\n    --env PUSHOVER_USER_KEY=${PUSHOVER_USER_KEY} \\\n    juftin/lunchable:latest \\\n    lunchable plugins pushlunch notify --continuous\n
"},{"location":"plugins/pushlunch/#run-via-python","title":"Run via Python","text":"
from lunchable.plugins.pushlunch import PushLunch\n

Lunch Money Pushover Notifications via Lunchable

Source code in lunchable/plugins/pushlunch/pushover.py
class PushLunch(LunchableApp):\n    \"\"\"\n    Lunch Money Pushover Notifications via Lunchable\n    \"\"\"\n\n    pushover_endpoint = \"https://api.pushover.net/1/messages.json\"\n\n    def __init__(\n        self,\n        user_key: Optional[str] = None,\n        app_token: Optional[str] = None,\n        lunchmoney_access_token: Optional[str] = None,\n    ):\n        \"\"\"\n        Initialize\n\n        Parameters\n        ----------\n        user_key : Optional[str]\n            Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n            variable if none defined\n        app_token: Optional[str]\n            Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n            variable. If no token available, the official lunchable app token will be provided\n        lunchmoney_access_token: Optional[str]\n            LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable.\n        \"\"\"\n        super().__init__(access_token=lunchmoney_access_token)\n        self.pushover_session = httpx.Client()\n        self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n        _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n        if app_token is None:\n            app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n        token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n        user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n        if user_key in [None, \"\"]:\n            raise PushLunchError(\n                \"You must provide a Pushover User Key or define it with \"\n                \"a `PUSHOVER_USER_KEY` environment variable\"\n            )\n        self._params = {\"user\": user_key, \"token\": token}\n        self.get_latest_cache(\n            include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n        )\n        self.notified_transactions: List[int] = []\n\n    def send_notification(\n        self,\n        message: str,\n        attachment: Optional[object] = None,\n        device: Optional[str] = None,\n        title: Optional[str] = None,\n        url: Optional[str] = None,\n        url_title: Optional[str] = None,\n        priority: Optional[int] = None,\n        sound: Optional[str] = None,\n        timestamp: Optional[str] = None,\n        html: bool = False,\n    ) -> httpx.Response:\n        \"\"\"\n        Send a Pushover Notification\n\n        Parameters\n        ----------\n        message: Optional[str]\n            your message\n        attachment: Optional[object]\n            an image attachment to send with the message; see attachments for more information\n            on how to upload files\n        device: Optional[str]\n            your user's device name to send the message directly to that device, rather than\n            all of the user's devices (multiple devices may be separated by a comma)\n        title: Optional[str]\n            your message's title, otherwise your app's name is used\n        url: Optional[str]\n            a supplementary URL to show with your message\n        url_title: Optional[str]\n            a title for your supplementary URL, otherwise just the URL is shown\n        priority: Optional[int]\n            send as -2 to generate no notification/alert, -1 to always send as a quiet\n            notification, 1 to display as high-priority and bypass the user's quiet hours,\n            or 2 to also require confirmation from the user\n        sound: Optional[str]\n            the name of one of the sounds supported by device clients to override the\n            user's default sound choice\n        timestamp: Optional[str]\n            a Unix timestamp of your message's date and time to display to the user, rather\n            than the time your message is received by our API\n        html: Union[None, 1]\n            Pass 1 if message contains HTML contents\n\n        Returns\n        -------\n        httpx.Response\n        \"\"\"\n        html_param = 1 if html not in [None, False] else None\n        params_dict = {\n            \"message\": message,\n            \"attachment\": attachment,\n            \"device\": device,\n            \"title\": title,\n            \"url\": url,\n            \"url_title\": url_title,\n            \"priority\": priority,\n            \"sound\": sound,\n            \"timestamp\": timestamp,\n            \"html\": html_param,\n        }\n        params: Dict[str, Any] = {\n            key: value for key, value in params_dict.items() if value is not None\n        }\n        params.update(self._params)\n        response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n        response.raise_for_status()\n        return response\n\n    def post_transaction(\n        self, transaction: TransactionObject\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"\n        Post a Lunch Money Transaction as a Pushover Notification\n\n        Assuming the instance of the\n        class hasn't already posted this particular notification\n\n        Parameters\n        ----------\n        transaction: TransactionObject\n\n        Returns\n        -------\n        Dict[str, Any]\n        \"\"\"\n        if transaction.id in self.notified_transactions:\n            return None\n        if transaction.category_id is None:\n            category = \"N/A\"\n        else:\n            category = self.lunch_data.categories[transaction.category_id].name\n        account_id = transaction.plaid_account_id or transaction.asset_id\n        assert account_id is not None\n        account = self.lunch_data.asset_map[account_id]\n        if isinstance(account, AssetsObject):\n            account_name = account.display_name or account.name\n        else:\n            account_name = account.name\n        transaction_formatted = dedent(\n            f\"\"\"\n        <b>Payee:</b> <i>{transaction.payee}</i>\n        <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n        <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n        <b>Category:</b> <i>{category}</i>\n        <b>Account:</b> <i>{account_name}</i>\n        \"\"\"\n        ).strip()\n        if transaction.currency is not None:\n            transaction_formatted += (\n                f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n            )\n        if transaction.status is not None:\n            transaction_formatted += (\n                f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n            )\n        if transaction.notes is not None:\n            note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n            transaction_formatted += f\"\\n{note}\"\n        if transaction.status == \"uncleared\":\n            url = (\n                '<a href=\"https://my.lunchmoney.app/transactions/'\n                f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n                \"<b>Uncleared Transactions from this Period</b></a>\"\n            )\n            transaction_formatted += f\"\\n\\n{url}\"\n\n        response = self.send_notification(\n            message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n        )\n        self.notified_transactions.append(transaction.id)\n        return loads(response.content)\n\n    @classmethod\n    def _format_float(cls, amount: float) -> str:\n        \"\"\"\n        Format Floats to be pleasant and human readable\n\n        Parameters\n        ----------\n        amount: float\n            Float Amount to be converted into a string\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if amount < 0:\n            float_string = f\"$ ({float(amount):,.2f})\".replace(\"-\", \"\")\n        else:\n            float_string = f\"$ {float(amount):,.2f}\"\n        return float_string\n\n    def notify_uncleared_transactions(\n        self, continuous: bool = False, interval: Optional[int] = None\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n        Parameters\n        ----------\n        continuous : bool\n            Whether to continuously check for more uncleared transactions,\n            waiting a fixed amount in between checks.\n        interval: Optional[int]\n            Sleep Interval in Between Tries - only applies if `continuous` is set.\n            Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if interval is None:\n            interval = 60\n        if continuous is True and interval < 5:\n            logger.warning(\n                \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n            )\n            interval = 5\n        if continuous is True:\n            logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n        uncleared_transactions = []\n        continuous_search = True\n\n        while continuous_search is True:\n            found_transactions = len(self.notified_transactions)\n            uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n            for transaction in uncleared_transactions:\n                self.post_transaction(transaction=transaction)\n            if continuous is True:\n                notified = len(self.notified_transactions)\n                new_transactions = notified - found_transactions\n                logger.info(\n                    \"%s new transactions pushed. %s total.\", new_transactions, notified\n                )\n                sleep(interval * 60)\n            else:\n                continuous_search = False\n\n        return uncleared_transactions\n
"},{"location":"plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.__init__","title":"__init__(user_key=None, app_token=None, lunchmoney_access_token=None)","text":"

Initialize

Parameters:

Name Type Description Default user_key Optional[str]

Pushover User Key. Will attempt to inherit from PUSHOVER_USER_KEY environment variable if none defined

None app_token Optional[str]

Pushover app token, will attempt to inherit from PUSHOVER_APP_TOKEN environment variable. If no token available, the official lunchable app token will be provided

None lunchmoney_access_token Optional[str]

LunchMoney Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable.

None Source code in lunchable/plugins/pushlunch/pushover.py
def __init__(\n    self,\n    user_key: Optional[str] = None,\n    app_token: Optional[str] = None,\n    lunchmoney_access_token: Optional[str] = None,\n):\n    \"\"\"\n    Initialize\n\n    Parameters\n    ----------\n    user_key : Optional[str]\n        Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n        variable if none defined\n    app_token: Optional[str]\n        Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n        variable. If no token available, the official lunchable app token will be provided\n    lunchmoney_access_token: Optional[str]\n        LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable.\n    \"\"\"\n    super().__init__(access_token=lunchmoney_access_token)\n    self.pushover_session = httpx.Client()\n    self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n    _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n    if app_token is None:\n        app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n    token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n    user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n    if user_key in [None, \"\"]:\n        raise PushLunchError(\n            \"You must provide a Pushover User Key or define it with \"\n            \"a `PUSHOVER_USER_KEY` environment variable\"\n        )\n    self._params = {\"user\": user_key, \"token\": token}\n    self.get_latest_cache(\n        include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n    )\n    self.notified_transactions: List[int] = []\n
"},{"location":"plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.notify_uncleared_transactions","title":"notify_uncleared_transactions(continuous=False, interval=None)","text":"

Get the Current Period's Uncleared Transactions and Send a Notification for each

Parameters:

Name Type Description Default continuous bool

Whether to continuously check for more uncleared transactions, waiting a fixed amount in between checks.

False interval Optional[int]

Sleep Interval in Between Tries - only applies if continuous is set. Defaults to 60 (minutes). Cannot be less than 5 (minutes)

None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/pushlunch/pushover.py
def notify_uncleared_transactions(\n    self, continuous: bool = False, interval: Optional[int] = None\n) -> List[TransactionObject]:\n    \"\"\"\n    Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n    Parameters\n    ----------\n    continuous : bool\n        Whether to continuously check for more uncleared transactions,\n        waiting a fixed amount in between checks.\n    interval: Optional[int]\n        Sleep Interval in Between Tries - only applies if `continuous` is set.\n        Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if interval is None:\n        interval = 60\n    if continuous is True and interval < 5:\n        logger.warning(\n            \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n        )\n        interval = 5\n    if continuous is True:\n        logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n    uncleared_transactions = []\n    continuous_search = True\n\n    while continuous_search is True:\n        found_transactions = len(self.notified_transactions)\n        uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n        for transaction in uncleared_transactions:\n            self.post_transaction(transaction=transaction)\n        if continuous is True:\n            notified = len(self.notified_transactions)\n            new_transactions = notified - found_transactions\n            logger.info(\n                \"%s new transactions pushed. %s total.\", new_transactions, notified\n            )\n            sleep(interval * 60)\n        else:\n            continuous_search = False\n\n    return uncleared_transactions\n
"},{"location":"plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.post_transaction","title":"post_transaction(transaction)","text":"

Post a Lunch Money Transaction as a Pushover Notification

Assuming the instance of the class hasn't already posted this particular notification

Parameters:

Name Type Description Default transaction TransactionObject required

Returns:

Type Description Dict[str, Any] Source code in lunchable/plugins/pushlunch/pushover.py
def post_transaction(\n    self, transaction: TransactionObject\n) -> Optional[Dict[str, Any]]:\n    \"\"\"\n    Post a Lunch Money Transaction as a Pushover Notification\n\n    Assuming the instance of the\n    class hasn't already posted this particular notification\n\n    Parameters\n    ----------\n    transaction: TransactionObject\n\n    Returns\n    -------\n    Dict[str, Any]\n    \"\"\"\n    if transaction.id in self.notified_transactions:\n        return None\n    if transaction.category_id is None:\n        category = \"N/A\"\n    else:\n        category = self.lunch_data.categories[transaction.category_id].name\n    account_id = transaction.plaid_account_id or transaction.asset_id\n    assert account_id is not None\n    account = self.lunch_data.asset_map[account_id]\n    if isinstance(account, AssetsObject):\n        account_name = account.display_name or account.name\n    else:\n        account_name = account.name\n    transaction_formatted = dedent(\n        f\"\"\"\n    <b>Payee:</b> <i>{transaction.payee}</i>\n    <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n    <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n    <b>Category:</b> <i>{category}</i>\n    <b>Account:</b> <i>{account_name}</i>\n    \"\"\"\n    ).strip()\n    if transaction.currency is not None:\n        transaction_formatted += (\n            f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n        )\n    if transaction.status is not None:\n        transaction_formatted += (\n            f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n        )\n    if transaction.notes is not None:\n        note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n        transaction_formatted += f\"\\n{note}\"\n    if transaction.status == \"uncleared\":\n        url = (\n            '<a href=\"https://my.lunchmoney.app/transactions/'\n            f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n            \"<b>Uncleared Transactions from this Period</b></a>\"\n        )\n        transaction_formatted += f\"\\n\\n{url}\"\n\n    response = self.send_notification(\n        message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n    )\n    self.notified_transactions.append(transaction.id)\n    return loads(response.content)\n
"},{"location":"plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.send_notification","title":"send_notification(message, attachment=None, device=None, title=None, url=None, url_title=None, priority=None, sound=None, timestamp=None, html=False)","text":"

Send a Pushover Notification

Parameters:

Name Type Description Default message str

your message

required attachment Optional[object]

an image attachment to send with the message; see attachments for more information on how to upload files

None device Optional[str]

your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma)

None title Optional[str]

your message's title, otherwise your app's name is used

None url Optional[str]

a supplementary URL to show with your message

None url_title Optional[str]

a title for your supplementary URL, otherwise just the URL is shown

None priority Optional[int]

send as -2 to generate no notification/alert, -1 to always send as a quiet notification, 1 to display as high-priority and bypass the user's quiet hours, or 2 to also require confirmation from the user

None sound Optional[str]

the name of one of the sounds supported by device clients to override the user's default sound choice

None timestamp Optional[str]

a Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by our API

None html bool

Pass 1 if message contains HTML contents

False

Returns:

Type Description Response Source code in lunchable/plugins/pushlunch/pushover.py
def send_notification(\n    self,\n    message: str,\n    attachment: Optional[object] = None,\n    device: Optional[str] = None,\n    title: Optional[str] = None,\n    url: Optional[str] = None,\n    url_title: Optional[str] = None,\n    priority: Optional[int] = None,\n    sound: Optional[str] = None,\n    timestamp: Optional[str] = None,\n    html: bool = False,\n) -> httpx.Response:\n    \"\"\"\n    Send a Pushover Notification\n\n    Parameters\n    ----------\n    message: Optional[str]\n        your message\n    attachment: Optional[object]\n        an image attachment to send with the message; see attachments for more information\n        on how to upload files\n    device: Optional[str]\n        your user's device name to send the message directly to that device, rather than\n        all of the user's devices (multiple devices may be separated by a comma)\n    title: Optional[str]\n        your message's title, otherwise your app's name is used\n    url: Optional[str]\n        a supplementary URL to show with your message\n    url_title: Optional[str]\n        a title for your supplementary URL, otherwise just the URL is shown\n    priority: Optional[int]\n        send as -2 to generate no notification/alert, -1 to always send as a quiet\n        notification, 1 to display as high-priority and bypass the user's quiet hours,\n        or 2 to also require confirmation from the user\n    sound: Optional[str]\n        the name of one of the sounds supported by device clients to override the\n        user's default sound choice\n    timestamp: Optional[str]\n        a Unix timestamp of your message's date and time to display to the user, rather\n        than the time your message is received by our API\n    html: Union[None, 1]\n        Pass 1 if message contains HTML contents\n\n    Returns\n    -------\n    httpx.Response\n    \"\"\"\n    html_param = 1 if html not in [None, False] else None\n    params_dict = {\n        \"message\": message,\n        \"attachment\": attachment,\n        \"device\": device,\n        \"title\": title,\n        \"url\": url,\n        \"url_title\": url_title,\n        \"priority\": priority,\n        \"sound\": sound,\n        \"timestamp\": timestamp,\n        \"html\": html_param,\n    }\n    params: Dict[str, Any] = {\n        key: value for key, value in params_dict.items() if value is not None\n    }\n    params.update(self._params)\n    response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n    response.raise_for_status()\n    return response\n
"},{"location":"plugins/splitlunch/","title":"SplitLunch: Splitwise Integration","text":""},{"location":"plugins/splitlunch/#integrations","title":"Integrations","text":"

This plugin supports different operations, and some of those operations have prerequisites:

"},{"location":"plugins/splitlunch/#auto-importer","title":"Auto Importer","text":"

It supports the auto-importing of Splitwise expenses into Lunch Money transactions. This requires a manual asset exist in your Lunch Money account with \"Splitwise\" in the Name. Expenses that have been deleted or which don't impact you (i.e. are only between other users in your group) are skipped. By default, payments and expenses for which you are recorded as the payer are skipped as well, but these can be overridden by the --allow-payments and --allow-self-paid CLI flags, respectively.

"},{"location":"plugins/splitlunch/#prerequisites","title":"Prerequisites","text":""},{"location":"plugins/splitlunch/#lunchmoney-splitwise","title":"LunchMoney -> Splitwise","text":"

It supports the creation of Splitwise transactions directly from synced Lunch Money accounts. This syncing requires you create a tag called SplitLunchImport. Transactions with this tag will be created in Splitwise with your \"financial partner\". Once transactions are created in Splitwise they will be split in half in Lunch Money. Half of the split will be marked in the Reimbursement category which must be created.

"},{"location":"plugins/splitlunch/#prerequisites_1","title":"Prerequisites","text":""},{"location":"plugins/splitlunch/#splitlunch","title":"SplitLunch","text":"

It supports a workflow where you mark transactions as split (identical to Lunch Money -> Splitwise) without importing them into Splitwise. This syncing requires you create a tag called SplitLunch and a category named Reimbursement

"},{"location":"plugins/splitlunch/#prerequisites_2","title":"Prerequisites","text":""},{"location":"plugins/splitlunch/#lunchmoney-splitwise-without-splitting","title":"LunchMoney -> Splitwise (without splitting)","text":"

It supports the creation of Splitwise transactions directly from synced Lunch Money accounts. This syncing requires you create a tag called SplitLunchDirectImport. Transactions with this tag will be created in Splitwise with the total completely owed by your \"financial partner\". The entire transaction wil then be categorized as Reimbursement without being split.

"},{"location":"plugins/splitlunch/#prerequisites_3","title":"Prerequisites","text":"

Note: Some of the above scenarios allow for tagging of a Splitwise tag on updated transactions. This tag must be created for this functionality to work.

"},{"location":"plugins/splitlunch/#installation","title":"Installation","text":"
pip install lunchable[splitlunch]\n
"},{"location":"plugins/splitlunch/#run-the-splitlunch-plugin-for-the-lunchable-cli","title":"Run the SplitLunch plugin for the Lunchable CLI","text":"
lunchable plugins splitlunch --help\n
"},{"location":"plugins/splitlunch/#run-the-splitlunch-plugin-for-the-lunchable-cli-via-docker","title":"Run the SplitLunch plugin for the Lunchable CLI via Docker","text":"
docker pull juftin/lunchable\n
docker run \\\n    --env LUNCHMONEY_ACCESS_TOKEN=${LUNCHMONEY_ACCESS_TOKEN} \\\n    --env SPLITWISE_CONSUMER_KEY=${SPLITWISE_CONSUMER_KEY} \\\n    --env SPLITWISE_CONSUMER_SECRET=${SPLITWISE_CONSUMER_SECRET} \\\n    --env SPLITWISE_API_KEY=${SPLITWISE_API_KEY} \\\n    juftin/lunchable:latest \\\n    lunchable plugins splitlunch --help\n
"},{"location":"plugins/splitlunch/#run-via-python","title":"Run via Python","text":"
from lunchable.plugins.splitlunch import SplitLunch\n

Lunchable Plugin For Interacting With Splitwise

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
class SplitLunch(splitwise.Splitwise):\n    \"\"\"\n    Lunchable Plugin For Interacting With Splitwise\n    \"\"\"\n\n    def __init__(\n        self,\n        lunch_money_access_token: Optional[str] = None,\n        financial_partner_id: Optional[int] = None,\n        financial_partner_email: Optional[str] = None,\n        financial_partner_group_id: Optional[int] = None,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n        lunchable_client: Optional[LunchMoney] = None,\n    ):\n        \"\"\"\n        Initialize the Parent Class with some additional properties\n\n        Parameters\n        ----------\n        financial_partner_id: Optional[int]\n            Splitwise User ID of financial partner\n        financial_partner_email: Optional[str]\n            Splitwise linked email address of financial partner\n        financial_partner_group_id: Optional[int]\n            Splitwise Group ID for financial partner transactions\n        consumer_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n            variable\n        consumer_secret: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n            environment variable\n        api_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n            variable.\n        lunch_money_access_token: Optional[str]\n            Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable if not provided.\n        lunchable_client: LunchMoney\n            Instantiated LunchMoney object to use as internal client. One will\n            be created using environment variables otherwise.\n        \"\"\"\n        init_kwargs = self._get_splitwise_init_kwargs(\n            consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n        )\n        super(SplitLunch, self).__init__(**init_kwargs)\n        self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n        self.financial_partner: splitwise.Friend = self.get_friend(\n            friend_id=financial_partner_id, email_address=financial_partner_email\n        )\n        self.financial_group = financial_partner_group_id\n        self.last_check: Optional[datetime.datetime] = None\n        self.lunchable = (\n            LunchMoney(access_token=lunch_money_access_token)\n            if lunchable_client is None\n            else lunchable_client\n        )\n        self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n        self.splitwise_tag = self._none_tag.model_copy()\n        self.splitlunch_tag = self._none_tag.model_copy()\n        self.splitlunch_import_tag = self._none_tag.model_copy()\n        self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n        self._get_splitwise_tags()\n        self.earliest_start_date = datetime.date(1812, 1, 1)\n        today = datetime.date.today()\n        self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n        self.splitwise_asset = self._get_splitwise_asset()\n        self.reimbursement_category = self._get_reimbursement_category()\n\n    def __repr__(self) -> str:\n        \"\"\"\n        String Representation\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return f\"<Splitwise: {self.current_user.email}>\"\n\n    @classmethod\n    def _split_amount(cls, amount: float, splits: int) -> Tuple[float, ...]:\n        \"\"\"\n        Split a money amount into fair shares\n\n        Parameters\n        ----------\n        amount: float\n        splits: int\n\n        Returns\n        -------\n        Tuple[float]\n        \"\"\"\n        try:\n            assert amount == round(amount, 2)\n        except AssertionError as ae:\n            raise SplitLunchError(\n                f\"{amount} caused an error, you must provide a real \" \"spending amount.\"\n            ) from ae\n        equal_shares = round(amount, 2) / splits\n        remainder_dollars = floor(equal_shares)\n        remainder_cents = floor((equal_shares - remainder_dollars) * 100) / 100\n        remainder_left = round(\n            (equal_shares - remainder_dollars - remainder_cents) * splits * 100, 0\n        )\n        owed_amount = remainder_dollars + remainder_cents\n        return_amounts = [owed_amount for _ in range(splits)]\n        for i in range(int(remainder_left)):\n            return_amounts[i] += 0.010\n        shuffle(return_amounts)\n        return tuple([round(item, 2) for item in return_amounts])\n\n    @classmethod\n    def split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n        \"\"\"\n        Split a Transaction into Two\n\n        Split a bill into a tuple of two amounts (and take care\n        of the extra penny if needed)\n\n        Parameters\n        ----------\n        amount: A Currency amount (no more precise than cents)\n\n        Returns\n        -------\n        tuple\n            A tuple is returned with each participant's amount\n        \"\"\"\n        amounts_due = cls._split_amount(amount=amount, splits=2)\n        return amounts_due\n\n    def create_self_paid_expense(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        primary_user_owes, financial_partner_owes = self.split_a_transaction(\n            amount=amount\n        )\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=primary_user_owes)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=financial_partner_owes)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def create_expense_on_behalf_of_partner(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n        This expense will be completely owed by the partner and maked as reimbursed.\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=0.00)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=amount)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def get_friend(\n        self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n    ) -> Optional[splitwise.Friend]:\n        \"\"\"\n        Retrieve a Financial Partner by Email Address\n\n        Parameters\n        ----------\n        email_address: str\n            Email Address of Friend's user in Splitwise\n        friend_id: Optional[int]\n            Splitwise friend ID. Notice the friend ID in the following\n            URL: https://secure.splitwise.com/#/friends/12345678\n\n        Returns\n        -------\n        Optional[splitwise.Friend]\n        \"\"\"\n        friend_list: List[splitwise.Friend] = self.getFriends()\n        if len(friend_list) == 1:\n            return friend_list[0]\n        for friend in friend_list:\n            if friend_id is not None and friend.id == friend_id:\n                return friend\n            elif (\n                email_address is not None\n                and friend.email.lower() == email_address.lower()\n            ):\n                return friend\n        return None\n\n    def get_expenses(\n        self,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        group_id: Optional[int] = None,\n        friendship_id: Optional[int] = None,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        updated_after: Optional[datetime.datetime] = None,\n        updated_before: Optional[datetime.datetime] = None,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Get Splitwise Expenses\n\n        Parameters\n        ----------\n        offset: Optional[int]\n            Number of expenses to be skipped\n        limit: Optional[int]\n            Number of expenses to be returned\n        group_id: Optional[int]\n            GroupID of the expenses\n        friendship_id: Optional[int]\n            FriendshipID of the expenses\n        dated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses later that this date\n        dated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses earlier than this date\n        updated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated after this date\n        updated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated before this date\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        expenses = self.getExpenses(\n            offset=offset,\n            limit=limit,\n            group_id=group_id,\n            friendship_id=friendship_id,\n            dated_after=dated_after,\n            dated_before=dated_before,\n            updated_after=updated_after,\n            updated_before=updated_before,\n        )\n        pydantic_expenses = [\n            self.splitwise_to_pydantic(expense) for expense in expenses\n        ]\n        return pydantic_expenses\n\n    @classmethod\n    def _get_splitwise_init_kwargs(\n        cls,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Get the Splitwise Kwargs\n\n        Parameters\n        ----------\n        consumer_key: Optional[str]\n        consumer_secret: Optional[str]\n        api_key: Optional[str]\n        \"\"\"\n        if consumer_key is None:\n            consumer_key = getenv(\"SPLITWISE_CONSUMER_KEY\")\n        if consumer_secret is None:\n            consumer_secret = getenv(\"SPLITWISE_CONSUMER_SECRET\")\n        if api_key is None:\n            api_key = getenv(\"SPLITWISE_API_KEY\", None)\n        init_kwargs = {\n            \"consumer_key\": consumer_key,\n            \"consumer_secret\": consumer_secret,\n            \"api_key\": api_key,\n        }\n        if consumer_key is None or consumer_secret is None or api_key is None:\n            error_message = (\n                dedent(\n                    \"\"\"\n            You must set your Splitwise credentials explicitly or by assigning\n            the `SPLITWISE_CONSUMER_KEY`, `SPLITWISE_CONSUMER_SECRET`, and the\n            `SPLITWISE_API_KEY`environment variables\n            \"\"\"\n                )\n                .replace(\"\\n\", \" \")\n                .replace(\"  \", \" \")\n            )\n            logger.error(error_message)\n            raise SplitLunchError(error_message)\n        return init_kwargs\n\n    def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n        \"\"\"\n        Convert Splitwise Object to Pydantic\n\n        Parameters\n        ----------\n        expense: splitwise.Expense\n\n        Returns\n        -------\n        SplitLunchExpense\n        \"\"\"\n        financial_impact, self_paid = _get_splitwise_impact(\n            expense=expense, current_user_id=self.current_user.id\n        )\n        expense = SplitLunchExpense(\n            splitwise_id=expense.id,\n            original_amount=expense.cost,\n            financial_impact=financial_impact,\n            self_paid=self_paid,\n            description=expense.description,\n            category=expense.category.name,\n            details=expense.details,\n            payment=expense.payment,\n            date=expense.date,\n            users=[user.id for user in expense.users],\n            created_at=expense.created_at,\n            updated_at=expense.updated_at,\n            deleted_at=expense.deleted_at,\n            deleted=True if expense.deleted_at is not None else False,\n        )\n        return expense\n\n    def _get_splitwise_asset(self) -> Optional[AssetsObject]:\n        \"\"\"\n        Get the Splitwise asset\n\n        Parse a user's Lunch Money accounts and return the manually managed\n        Splitwise account asset object\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        assets = self.lunchable.get_assets()\n        splitwise_assets = []\n        for asset in assets:\n            if (\n                asset.institution_name is not None\n                and \"splitwise\" in asset.institution_name.lower()\n            ):\n                splitwise_assets.append(asset)\n        if len(splitwise_assets) == 0:\n            return None\n        elif len(splitwise_assets) > 1:\n            raise SplitLunchError(\n                \"SplitLunch requires an manually managed Splitwise asset. \"\n                \"Make sure you have a single account where 'Splitwise' \"\n                \"is in the asset's `Institution Name`.\"\n            )\n        else:\n            return splitwise_assets[0]\n\n    def _get_reimbursement_category(self) -> Optional[CategoriesObject]:\n        \"\"\"\n        Get the Reimbusement Category\n\n        Parse a user's Lunch Money categories and return the Reimbursement\n        category\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        categories = self.lunchable.get_categories()\n        reimbursement_list = []\n        for category in categories:\n            if \"reimbursement\" == category.name.strip().lower():\n                reimbursement_list.append(category)\n        if len(reimbursement_list) != 1:\n            return None\n        return reimbursement_list[0]\n\n    def _get_splitwise_tags(self) -> None:\n        \"\"\"\n        Get Lunch Money Tags to Interact with\n\n        Returns\n        -------\n        Dict[str, int]\n        \"\"\"\n        all_tags = self.lunchable.get_tags()\n        for tag in all_tags:\n            if tag.name.lower() == SplitLunchConfig.splitlunch_tag.lower():\n                self.splitlunch_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitwise_tag.lower():\n                self.splitwise_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitlunch_import_tag.lower():\n                self.splitlunch_import_tag = tag\n            elif (\n                tag.name.lower()\n                == SplitLunchConfig.splitlunch_direct_import_tag.lower()\n            ):\n                self.splitlunch_direct_import_tag = tag\n\n    def _raise_nonexistent_tag_error(self, tags: List[str]) -> None:\n        \"\"\"\n        Raise a warning for specific SplitLunch Tags\n\n        tags: List[str]\n            A list of tags to raise the error for\n        \"\"\"\n        if (\n            self.splitlunch_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitwise_tag == self._none_tag\n            and SplitLunchConfig.splitwise_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitwise_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_import_tag}` tag is required. \"\n                f\"This tag is used for creating Splitwise transactions directly from \"\n                f\"Lunch Money transactions. These transactions will be split in half,\"\n                f\"and have one half marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_direct_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_direct_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_direct_import_tag}` tag is \"\n                \"required. This tag is used for creating Splitwise transactions \"\n                \"directly from Lunch Money transactions. These transactions will \"\n                \"be completely owed by your financial partner.\"\n            )\n            raise SplitLunchError(error_message)\n\n    def get_splitlunch_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitlunch\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def get_splitlunch_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitlunch_direct_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(\n            tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n        )\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_direct_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitwise_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitwise\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n        \"\"\"\n        Operate on `SplitLunch` tagged transactions\n\n        Split all transactions with the `SplitLunch` tag in half. One of these\n        new splits will be recategorized to `Reimbursement`. Both new splits will receive\n        the `Splitwise` tag without any preexisting tags.\n        \"\"\"\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        split_transaction_ids = []\n        tagged_objects = self.get_splitlunch_tagged_transactions()\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n            # Generate the First Split\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=transaction.notes,\n                amount=amount_1,\n            )\n            # Generate the second split as a copy, change some properties\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = amount_2\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                \"Splitting transaction: %s -> (%s, %s)\",\n                transaction.amount,\n                amount_1,\n                amount_2,\n            )\n\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            # Tag each of the new transactions generated\n            split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags if transaction.tags is not None else []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag is not None\n                    and tag.name.lower() != self.splitlunch_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return split_transaction_ids\n\n    def make_splitlunch_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchImport` tagged transactions\n\n        Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n        One of these new splits will be recategorized to `Reimbursement`. Both new splits\n        will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n        reapplied.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_self_paid_expense(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=notes,\n                # financial_impact for self-paid transactions will be negative\n                amount=round(\n                    transaction.amount - abs(new_transaction.financial_impact), 2\n                ),\n            )\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = abs(new_transaction.financial_impact)\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                f\"Transaction split by Splitwise: {transaction.amount} -> \"\n                f\"({split_object.amount}, {reimbursement_object.amount})\"\n            )\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": reimbursement_object.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": update_response[\"updated\"],\n                \"split\": update_response[\"split\"],\n            }\n            update_responses.append(formatted_update_response)\n            # Tag each of the new transactions generated\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags or []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return update_responses\n\n    def make_splitlunch_direct_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchDirectImport` tagged transactions\n\n        Send a transaction to Splitwise and then mark the transaction under the\n        `Reimbursement` category. The sum of the transaction will be completely owed\n        by the financial partner.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_expense_on_behalf_of_partner(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            existing_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in existing_tags\n                if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n                tags.append(self.splitwise_tag.name)\n            update = TransactionUpdateObject(\n                category_id=self.reimbursement_category.id, tags=tags, notes=notes\n            )\n            response = self.lunchable.update_transaction(\n                transaction_id=transaction.id, transaction=update\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": transaction.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": response[\"updated\"],\n                \"split\": None,\n            }\n            update_responses.append(formatted_update_response)\n        return update_responses\n\n    def splitwise_to_lunchmoney(\n        self,\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[int]:\n        \"\"\"\n        Ingest Splitwise Expenses into Lunch Money\n\n        This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n        a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n        payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[int]\n            New Lunch Money transaction IDs\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        batch = []\n        new_transaction_ids = []\n        filtered_expenses = self.filter_relevant_splitwise_expenses(\n            expenses=expenses,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        for splitwise_transaction in filtered_expenses:\n            new_lunchmoney_transaction = TransactionInsertObject(\n                date=splitwise_transaction.date.astimezone(tzlocal()),\n                payee=splitwise_transaction.description,\n                amount=splitwise_transaction.financial_impact,\n                asset_id=self.splitwise_asset.id,\n                external_id=splitwise_transaction.splitwise_id,\n            )\n            batch.append(new_lunchmoney_transaction)\n            if len(batch) == 10:\n                new_ids = self.lunchable.insert_transactions(\n                    transactions=batch, apply_rules=True\n                )\n                new_transaction_ids += new_ids\n                batch = []\n        if len(batch) > 0:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n        return new_transaction_ids\n\n    @staticmethod\n    def filter_relevant_splitwise_expenses(\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Filter Expenses in Splitwise into relevant expenses.\n\n        This filtering action is important to understand when seeing why not\n        all transactions from Splitwise end up flowing into Lunch Money.\n\n        1) It filters out deleted expenses\n\n        2) It filters out expenses with a financial impact of 0, implying that the user was not\n        involved in the expense.\n\n        3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n        `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        filtered_expenses = []\n        for splitwise_transaction in expenses:\n            if all(\n                [\n                    splitwise_transaction.deleted is False,\n                    splitwise_transaction.financial_impact != 0.00,\n                    allow_self_paid or (splitwise_transaction.self_paid is False),\n                    allow_payments or (splitwise_transaction.payment is False),\n                ]\n            ):\n                filtered_expenses.append(splitwise_transaction)\n\n        return filtered_expenses\n\n    def get_splitwise_balance(self) -> float:\n        \"\"\"\n        Get the net balance in Splitwise\n\n        Returns\n        -------\n        float\n        \"\"\"\n        groups = self.getGroups()\n        total_balance = 0.00\n        for group in groups:\n            for debt in group.simplified_debts:\n                if debt.fromUser == self.current_user.id:\n                    total_balance -= float(debt.amount)\n                elif debt.toUser == self.current_user.id:\n                    total_balance += float(debt.amount)\n        return total_balance\n\n    def update_splitwise_balance(self) -> AssetsObject:\n        \"\"\"\n        Get and update the Splitwise Asset in Lunch Money\n\n        Returns\n        -------\n        AssetsObject\n            Updated  balance\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        balance = self.get_splitwise_balance()\n        if balance != self.splitwise_asset.balance:\n            updated_asset = self.lunchable.update_asset(\n                asset_id=self.splitwise_asset.id, balance=balance\n            )\n            self.splitwise_asset = updated_asset\n        return self.splitwise_asset\n\n    _deleted_payee = \"[DELETED FROM SPLITWISE]\"\n\n    def get_new_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n    ) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n        \"\"\"\n        Get Splitwise Transactions that don't exist in Lunch Money\n\n        Also return deleted transaction from LunchMoney\n\n        Returns\n        -------\n        Tuple[List[SplitLunchExpense], List[TransactionObject]]\n            New and Deleted Transactions\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        splitlunch_expenses = self.lunchable.get_transactions(\n            asset_id=self.splitwise_asset.id,\n            start_date=datetime.datetime(1800, 1, 1),\n            end_date=datetime.datetime(2300, 12, 31),\n        )\n        splitlunch_ids = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        splitwise_expenses = self.get_expenses(\n            limit=0,\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n        new_ids = splitwise_ids.difference(splitlunch_ids)\n        new_expenses = [\n            expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n        ]\n        deleted_transactions = self.get_deleted_transactions(\n            splitlunch_expenses=splitlunch_expenses,\n            splitwise_transactions=splitwise_expenses,\n        )\n        return new_expenses, deleted_transactions\n\n    def get_deleted_transactions(\n        self,\n        splitlunch_expenses: List[TransactionObject],\n        splitwise_transactions: List[SplitLunchExpense],\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n        Set these transactions to $0.00 and Make a Note\n\n        Parameters\n        ----------\n        splitlunch_expenses: List[TransactionObject]\n        splitwise_transactions: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        existing_transactions = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        deleted_ids = {\n            item.splitwise_id for item in splitwise_transactions if item.deleted is True\n        }\n        untethered_transactions = deleted_ids.intersection(existing_transactions)\n        transactions_to_delete = [\n            tran\n            for tran in splitlunch_expenses\n            if tran.external_id is not None\n            and int(tran.external_id) in untethered_transactions\n            and tran.payee != self._deleted_payee\n        ]\n        return transactions_to_delete\n\n    def refresh_splitwise_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Import New Splitwise Transactions to Lunch Money\n\n        This function get's all transactions from Splitwise, all transactions from\n        your Lunch Money Splitwise account and compares the two.\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        new_transactions, deleted_transactions = self.get_new_transactions(\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        self.splitwise_to_lunchmoney(\n            expenses=new_transactions,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        splitwise_asset = self.update_splitwise_balance()\n        self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n        return {\n            \"balance\": splitwise_asset.balance,\n            \"new\": new_transactions,\n            \"deleted\": deleted_transactions,\n        }\n\n    def handle_deleted_transactions(\n        self,\n        deleted_transactions: List[TransactionObject],\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n        Parameters\n        ----------\n        deleted_transactions: List[TransactionObject]\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        updated_transactions = []\n        for transaction in deleted_transactions:\n            update = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                transaction=TransactionUpdateObject(\n                    amount=0.00,\n                    payee=self._deleted_payee,\n                    notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n                ),\n            )\n            updated_transactions.append(update)\n        return updated_transactions\n\n    def _raise_financial_partner_error(self) -> None:\n        \"\"\"\n        Raise Errors for Financial Partners\n        \"\"\"\n        if self.financial_partner is None:\n            raise SplitLunchError(\n                \"You must designate a financial partner in Splitwise. \"\n                \"This can be done with the partner's Splitwise User ID # \"\n                \"or their email address.\"\n            )\n\n    def _raise_splitwise_asset_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"You must create an asset (aka Account) in Lunch Money with \"\n            \"`Splitwise` in the name. There should only be one account \"\n            \"like this.\"\n        )\n\n    def _raise_category_reimbursement_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"SplitLunch requires a reimbursement Category. \"\n            \"Make sure you have a category entitled `Reimbursement`. \"\n            \"This category will be excluded from budgeting.\"\n            \"Half of split transactions will be created with \"\n            \"this category.\"\n        )\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.__init__","title":"__init__(lunch_money_access_token=None, financial_partner_id=None, financial_partner_email=None, financial_partner_group_id=None, consumer_key=None, consumer_secret=None, api_key=None, lunchable_client=None)","text":"

Initialize the Parent Class with some additional properties

Parameters:

Name Type Description Default financial_partner_id Optional[int]

Splitwise User ID of financial partner

None financial_partner_email Optional[str]

Splitwise linked email address of financial partner

None financial_partner_group_id Optional[int]

Splitwise Group ID for financial partner transactions

None consumer_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_KEY environment variable

None consumer_secret Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_SECRET environment variable

None api_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_API_KEY environment variable.

None lunch_money_access_token Optional[str]

Lunch Money Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable if not provided.

None lunchable_client Optional[LunchMoney]

Instantiated LunchMoney object to use as internal client. One will be created using environment variables otherwise.

None Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __init__(\n    self,\n    lunch_money_access_token: Optional[str] = None,\n    financial_partner_id: Optional[int] = None,\n    financial_partner_email: Optional[str] = None,\n    financial_partner_group_id: Optional[int] = None,\n    consumer_key: Optional[str] = None,\n    consumer_secret: Optional[str] = None,\n    api_key: Optional[str] = None,\n    lunchable_client: Optional[LunchMoney] = None,\n):\n    \"\"\"\n    Initialize the Parent Class with some additional properties\n\n    Parameters\n    ----------\n    financial_partner_id: Optional[int]\n        Splitwise User ID of financial partner\n    financial_partner_email: Optional[str]\n        Splitwise linked email address of financial partner\n    financial_partner_group_id: Optional[int]\n        Splitwise Group ID for financial partner transactions\n    consumer_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n        variable\n    consumer_secret: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n        environment variable\n    api_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n        variable.\n    lunch_money_access_token: Optional[str]\n        Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable if not provided.\n    lunchable_client: LunchMoney\n        Instantiated LunchMoney object to use as internal client. One will\n        be created using environment variables otherwise.\n    \"\"\"\n    init_kwargs = self._get_splitwise_init_kwargs(\n        consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n    )\n    super(SplitLunch, self).__init__(**init_kwargs)\n    self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n    self.financial_partner: splitwise.Friend = self.get_friend(\n        friend_id=financial_partner_id, email_address=financial_partner_email\n    )\n    self.financial_group = financial_partner_group_id\n    self.last_check: Optional[datetime.datetime] = None\n    self.lunchable = (\n        LunchMoney(access_token=lunch_money_access_token)\n        if lunchable_client is None\n        else lunchable_client\n    )\n    self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n    self.splitwise_tag = self._none_tag.model_copy()\n    self.splitlunch_tag = self._none_tag.model_copy()\n    self.splitlunch_import_tag = self._none_tag.model_copy()\n    self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n    self._get_splitwise_tags()\n    self.earliest_start_date = datetime.date(1812, 1, 1)\n    today = datetime.date.today()\n    self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n    self.splitwise_asset = self._get_splitwise_asset()\n    self.reimbursement_category = self._get_reimbursement_category()\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __repr__(self) -> str:\n    \"\"\"\n    String Representation\n\n    Returns\n    -------\n    str\n    \"\"\"\n    return f\"<Splitwise: {self.current_user.email}>\"\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.create_expense_on_behalf_of_partner","title":"create_expense_on_behalf_of_partner(amount, description, date)","text":"

Create and Submit a Splitwise Expense on behalf of your financial partner.

This expense will be completely owed by the partner and maked as reimbursed.

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_expense_on_behalf_of_partner(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n    This expense will be completely owed by the partner and maked as reimbursed.\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=0.00)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=amount)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.create_self_paid_expense","title":"create_self_paid_expense(amount, description, date)","text":"

Create and Submit a Splitwise Expense

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_self_paid_expense(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    primary_user_owes, financial_partner_owes = self.split_a_transaction(\n        amount=amount\n    )\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=primary_user_owes)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=financial_partner_owes)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.filter_relevant_splitwise_expenses","title":"filter_relevant_splitwise_expenses(expenses, allow_self_paid=False, allow_payments=False) staticmethod","text":"

Filter Expenses in Splitwise into relevant expenses.

This filtering action is important to understand when seeing why not all transactions from Splitwise end up flowing into Lunch Money.

1) It filters out deleted expenses

2) It filters out expenses with a financial impact of 0, implying that the user was not involved in the expense.

3) If the --allow-self-paid flag is not provided, it filters out self-paid expenses. A self-paid expense is an expense in Splitwise where you originated the payment. This is excluded because it is assumed that these transactions will have already been imported via a different account.

4) If the --allow-payments flag is not provided, it filters out payments. Payments are excluded because it is assumed that these transactions will have already been imported via a different account.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@staticmethod\ndef filter_relevant_splitwise_expenses(\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Filter Expenses in Splitwise into relevant expenses.\n\n    This filtering action is important to understand when seeing why not\n    all transactions from Splitwise end up flowing into Lunch Money.\n\n    1) It filters out deleted expenses\n\n    2) It filters out expenses with a financial impact of 0, implying that the user was not\n    involved in the expense.\n\n    3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n    `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    filtered_expenses = []\n    for splitwise_transaction in expenses:\n        if all(\n            [\n                splitwise_transaction.deleted is False,\n                splitwise_transaction.financial_impact != 0.00,\n                allow_self_paid or (splitwise_transaction.self_paid is False),\n                allow_payments or (splitwise_transaction.payment is False),\n            ]\n        ):\n            filtered_expenses.append(splitwise_transaction)\n\n    return filtered_expenses\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_deleted_transactions","title":"get_deleted_transactions(splitlunch_expenses, splitwise_transactions)","text":"

Get Splitwise Transactions that exist in Lunch Money but have since been deleted

Set these transactions to $0.00 and Make a Note

Parameters:

Name Type Description Default splitlunch_expenses List[TransactionObject] required splitwise_transactions List[SplitLunchExpense] required

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_deleted_transactions(\n    self,\n    splitlunch_expenses: List[TransactionObject],\n    splitwise_transactions: List[SplitLunchExpense],\n) -> List[TransactionObject]:\n    \"\"\"\n    Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n    Set these transactions to $0.00 and Make a Note\n\n    Parameters\n    ----------\n    splitlunch_expenses: List[TransactionObject]\n    splitwise_transactions: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    existing_transactions = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    deleted_ids = {\n        item.splitwise_id for item in splitwise_transactions if item.deleted is True\n    }\n    untethered_transactions = deleted_ids.intersection(existing_transactions)\n    transactions_to_delete = [\n        tran\n        for tran in splitlunch_expenses\n        if tran.external_id is not None\n        and int(tran.external_id) in untethered_transactions\n        and tran.payee != self._deleted_payee\n    ]\n    return transactions_to_delete\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_expenses","title":"get_expenses(offset=None, limit=None, group_id=None, friendship_id=None, dated_after=None, dated_before=None, updated_after=None, updated_before=None)","text":"

Get Splitwise Expenses

Parameters:

Name Type Description Default offset Optional[int]

Number of expenses to be skipped

None limit Optional[int]

Number of expenses to be returned

None group_id Optional[int]

GroupID of the expenses

None friendship_id Optional[int]

FriendshipID of the expenses

None dated_after Optional[datetime]

ISO 8601 Date time. Return expenses later that this date

None dated_before Optional[datetime]

ISO 8601 Date time. Return expenses earlier than this date

None updated_after Optional[datetime]

ISO 8601 Date time. Return expenses updated after this date

None updated_before Optional[datetime]

ISO 8601 Date time. Return expenses updated before this date

None

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_expenses(\n    self,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    group_id: Optional[int] = None,\n    friendship_id: Optional[int] = None,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    updated_after: Optional[datetime.datetime] = None,\n    updated_before: Optional[datetime.datetime] = None,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Get Splitwise Expenses\n\n    Parameters\n    ----------\n    offset: Optional[int]\n        Number of expenses to be skipped\n    limit: Optional[int]\n        Number of expenses to be returned\n    group_id: Optional[int]\n        GroupID of the expenses\n    friendship_id: Optional[int]\n        FriendshipID of the expenses\n    dated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses later that this date\n    dated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses earlier than this date\n    updated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated after this date\n    updated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated before this date\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    expenses = self.getExpenses(\n        offset=offset,\n        limit=limit,\n        group_id=group_id,\n        friendship_id=friendship_id,\n        dated_after=dated_after,\n        dated_before=dated_before,\n        updated_after=updated_after,\n        updated_before=updated_before,\n    )\n    pydantic_expenses = [\n        self.splitwise_to_pydantic(expense) for expense in expenses\n    ]\n    return pydantic_expenses\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_friend","title":"get_friend(email_address=None, friend_id=None)","text":"

Retrieve a Financial Partner by Email Address

Parameters:

Name Type Description Default email_address Optional[str]

Email Address of Friend's user in Splitwise

None friend_id Optional[int]

Splitwise friend ID. Notice the friend ID in the following URL: https://secure.splitwise.com/#/friends/12345678

None

Returns:

Type Description Optional[Friend] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_friend(\n    self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n) -> Optional[splitwise.Friend]:\n    \"\"\"\n    Retrieve a Financial Partner by Email Address\n\n    Parameters\n    ----------\n    email_address: str\n        Email Address of Friend's user in Splitwise\n    friend_id: Optional[int]\n        Splitwise friend ID. Notice the friend ID in the following\n        URL: https://secure.splitwise.com/#/friends/12345678\n\n    Returns\n    -------\n    Optional[splitwise.Friend]\n    \"\"\"\n    friend_list: List[splitwise.Friend] = self.getFriends()\n    if len(friend_list) == 1:\n        return friend_list[0]\n    for friend in friend_list:\n        if friend_id is not None and friend.id == friend_id:\n            return friend\n        elif (\n            email_address is not None\n            and friend.email.lower() == email_address.lower()\n        ):\n            return friend\n    return None\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_new_transactions","title":"get_new_transactions(dated_after=None, dated_before=None)","text":"

Get Splitwise Transactions that don't exist in Lunch Money

Also return deleted transaction from LunchMoney

Returns:

Type Description Tuple[List[SplitLunchExpense], List[TransactionObject]]

New and Deleted Transactions

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_new_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n    \"\"\"\n    Get Splitwise Transactions that don't exist in Lunch Money\n\n    Also return deleted transaction from LunchMoney\n\n    Returns\n    -------\n    Tuple[List[SplitLunchExpense], List[TransactionObject]]\n        New and Deleted Transactions\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    splitlunch_expenses = self.lunchable.get_transactions(\n        asset_id=self.splitwise_asset.id,\n        start_date=datetime.datetime(1800, 1, 1),\n        end_date=datetime.datetime(2300, 12, 31),\n    )\n    splitlunch_ids = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    splitwise_expenses = self.get_expenses(\n        limit=0,\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n    new_ids = splitwise_ids.difference(splitlunch_ids)\n    new_expenses = [\n        expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n    ]\n    deleted_transactions = self.get_deleted_transactions(\n        splitlunch_expenses=splitlunch_expenses,\n        splitwise_transactions=splitwise_expenses,\n    )\n    return new_expenses, deleted_transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_direct_import_tagged_transactions","title":"get_splitlunch_direct_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchDirectImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_direct_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(\n        tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n    )\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_direct_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_import_tagged_transactions","title":"get_splitlunch_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_tagged_transactions","title":"get_splitlunch_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitlunch\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitlunch\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitwise_balance","title":"get_splitwise_balance()","text":"

Get the net balance in Splitwise

Returns:

Type Description float Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_balance(self) -> float:\n    \"\"\"\n    Get the net balance in Splitwise\n\n    Returns\n    -------\n    float\n    \"\"\"\n    groups = self.getGroups()\n    total_balance = 0.00\n    for group in groups:\n        for debt in group.simplified_debts:\n            if debt.fromUser == self.current_user.id:\n                total_balance -= float(debt.amount)\n            elif debt.toUser == self.current_user.id:\n                total_balance += float(debt.amount)\n    return total_balance\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitwise_tagged_transactions","title":"get_splitwise_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitwise\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitwise\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.handle_deleted_transactions","title":"handle_deleted_transactions(deleted_transactions)","text":"

Update Transactions That Exist in Splitwise, but have been deleted in Splitwise

Parameters:

Name Type Description Default deleted_transactions List[TransactionObject] required

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def handle_deleted_transactions(\n    self,\n    deleted_transactions: List[TransactionObject],\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n    Parameters\n    ----------\n    deleted_transactions: List[TransactionObject]\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    updated_transactions = []\n    for transaction in deleted_transactions:\n        update = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            transaction=TransactionUpdateObject(\n                amount=0.00,\n                payee=self._deleted_payee,\n                notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n            ),\n        )\n        updated_transactions.append(update)\n    return updated_transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch","title":"make_splitlunch(tag_transactions=False)","text":"

Operate on SplitLunch tagged transactions

Split all transactions with the SplitLunch tag in half. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without any preexisting tags.

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n    \"\"\"\n    Operate on `SplitLunch` tagged transactions\n\n    Split all transactions with the `SplitLunch` tag in half. One of these\n    new splits will be recategorized to `Reimbursement`. Both new splits will receive\n    the `Splitwise` tag without any preexisting tags.\n    \"\"\"\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    split_transaction_ids = []\n    tagged_objects = self.get_splitlunch_tagged_transactions()\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n        # Generate the First Split\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=transaction.notes,\n            amount=amount_1,\n        )\n        # Generate the second split as a copy, change some properties\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = amount_2\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            \"Splitting transaction: %s -> (%s, %s)\",\n            transaction.amount,\n            amount_1,\n            amount_2,\n        )\n\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        # Tag each of the new transactions generated\n        split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags if transaction.tags is not None else []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag is not None\n                and tag.name.lower() != self.splitlunch_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return split_transaction_ids\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch_direct_import","title":"make_splitlunch_direct_import(tag_transactions=False)","text":"

Operate on SplitLunchDirectImport tagged transactions

Send a transaction to Splitwise and then mark the transaction under the Reimbursement category. The sum of the transaction will be completely owed by the financial partner.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_direct_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchDirectImport` tagged transactions\n\n    Send a transaction to Splitwise and then mark the transaction under the\n    `Reimbursement` category. The sum of the transaction will be completely owed\n    by the financial partner.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_expense_on_behalf_of_partner(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        existing_tags = transaction.tags or []\n        tags = [\n            tag.name\n            for tag in existing_tags\n            if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n        ]\n        if self.splitwise_tag.name not in tags and tag_transactions is True:\n            self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n            tags.append(self.splitwise_tag.name)\n        update = TransactionUpdateObject(\n            category_id=self.reimbursement_category.id, tags=tags, notes=notes\n        )\n        response = self.lunchable.update_transaction(\n            transaction_id=transaction.id, transaction=update\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": transaction.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": response[\"updated\"],\n            \"split\": None,\n        }\n        update_responses.append(formatted_update_response)\n    return update_responses\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch_import","title":"make_splitlunch_import(tag_transactions=False)","text":"

Operate on SplitLunchImport tagged transactions

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without the SplitLunchImport tag. Any other tags will be reapplied.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchImport` tagged transactions\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Both new splits\n    will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n    reapplied.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_self_paid_expense(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=notes,\n            # financial_impact for self-paid transactions will be negative\n            amount=round(\n                transaction.amount - abs(new_transaction.financial_impact), 2\n            ),\n        )\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = abs(new_transaction.financial_impact)\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            f\"Transaction split by Splitwise: {transaction.amount} -> \"\n            f\"({split_object.amount}, {reimbursement_object.amount})\"\n        )\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": reimbursement_object.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": update_response[\"updated\"],\n            \"split\": update_response[\"split\"],\n        }\n        update_responses.append(formatted_update_response)\n        # Tag each of the new transactions generated\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return update_responses\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.refresh_splitwise_transactions","title":"refresh_splitwise_transactions(dated_after=None, dated_before=None, allow_self_paid=False, allow_payments=False)","text":"

Import New Splitwise Transactions to Lunch Money

This function get's all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two.

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def refresh_splitwise_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> Dict[str, Any]:\n    \"\"\"\n    Import New Splitwise Transactions to Lunch Money\n\n    This function get's all transactions from Splitwise, all transactions from\n    your Lunch Money Splitwise account and compares the two.\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    new_transactions, deleted_transactions = self.get_new_transactions(\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    self.splitwise_to_lunchmoney(\n        expenses=new_transactions,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    splitwise_asset = self.update_splitwise_balance()\n    self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n    return {\n        \"balance\": splitwise_asset.balance,\n        \"new\": new_transactions,\n        \"deleted\": deleted_transactions,\n    }\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.split_a_transaction","title":"split_a_transaction(amount) classmethod","text":"

Split a Transaction into Two

Split a bill into a tuple of two amounts (and take care of the extra penny if needed)

Parameters:

Name Type Description Default amount Union[float, int] required

Returns:

Type Description tuple

A tuple is returned with each participant's amount

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@classmethod\ndef split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n    \"\"\"\n    Split a Transaction into Two\n\n    Split a bill into a tuple of two amounts (and take care\n    of the extra penny if needed)\n\n    Parameters\n    ----------\n    amount: A Currency amount (no more precise than cents)\n\n    Returns\n    -------\n    tuple\n        A tuple is returned with each participant's amount\n    \"\"\"\n    amounts_due = cls._split_amount(amount=amount, splits=2)\n    return amounts_due\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.splitwise_to_lunchmoney","title":"splitwise_to_lunchmoney(expenses, allow_self_paid=False, allow_payments=False)","text":"

Ingest Splitwise Expenses into Lunch Money

This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default, payments and self-paid transactions are also ineligible. Otherwise it will be ignored.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[int]

New Lunch Money transaction IDs

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_lunchmoney(\n    self,\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[int]:\n    \"\"\"\n    Ingest Splitwise Expenses into Lunch Money\n\n    This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n    a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n    payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[int]\n        New Lunch Money transaction IDs\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    batch = []\n    new_transaction_ids = []\n    filtered_expenses = self.filter_relevant_splitwise_expenses(\n        expenses=expenses,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    for splitwise_transaction in filtered_expenses:\n        new_lunchmoney_transaction = TransactionInsertObject(\n            date=splitwise_transaction.date.astimezone(tzlocal()),\n            payee=splitwise_transaction.description,\n            amount=splitwise_transaction.financial_impact,\n            asset_id=self.splitwise_asset.id,\n            external_id=splitwise_transaction.splitwise_id,\n        )\n        batch.append(new_lunchmoney_transaction)\n        if len(batch) == 10:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n            batch = []\n    if len(batch) > 0:\n        new_ids = self.lunchable.insert_transactions(\n            transactions=batch, apply_rules=True\n        )\n        new_transaction_ids += new_ids\n    return new_transaction_ids\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.splitwise_to_pydantic","title":"splitwise_to_pydantic(expense)","text":"

Convert Splitwise Object to Pydantic

Parameters:

Name Type Description Default expense Expense required

Returns:

Type Description SplitLunchExpense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n    \"\"\"\n    Convert Splitwise Object to Pydantic\n\n    Parameters\n    ----------\n    expense: splitwise.Expense\n\n    Returns\n    -------\n    SplitLunchExpense\n    \"\"\"\n    financial_impact, self_paid = _get_splitwise_impact(\n        expense=expense, current_user_id=self.current_user.id\n    )\n    expense = SplitLunchExpense(\n        splitwise_id=expense.id,\n        original_amount=expense.cost,\n        financial_impact=financial_impact,\n        self_paid=self_paid,\n        description=expense.description,\n        category=expense.category.name,\n        details=expense.details,\n        payment=expense.payment,\n        date=expense.date,\n        users=[user.id for user in expense.users],\n        created_at=expense.created_at,\n        updated_at=expense.updated_at,\n        deleted_at=expense.deleted_at,\n        deleted=True if expense.deleted_at is not None else False,\n    )\n    return expense\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.update_splitwise_balance","title":"update_splitwise_balance()","text":"

Get and update the Splitwise Asset in Lunch Money

Returns:

Type Description AssetsObject

Updated balance

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def update_splitwise_balance(self) -> AssetsObject:\n    \"\"\"\n    Get and update the Splitwise Asset in Lunch Money\n\n    Returns\n    -------\n    AssetsObject\n        Updated  balance\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    balance = self.get_splitwise_balance()\n    if balance != self.splitwise_asset.balance:\n        updated_asset = self.lunchable.update_asset(\n            asset_id=self.splitwise_asset.id, balance=balance\n        )\n        self.splitwise_asset = updated_asset\n    return self.splitwise_asset\n
"},{"location":"reference/","title":"lunchable","text":"

Lunch Money Python SDK

"},{"location":"reference/#lunchable.LunchMoney","title":"LunchMoney","text":"

Bases: AssetsClient, BudgetsClient, CategoriesClient, CryptoClient, PlaidAccountsClient, RecurringExpensesClient, TagsClient, TransactionsClient, UserClient

Lunch Money Python Client.

This class facilitates with connections to the Lunch Money Developer API. Authenticate with an Access Token. If an access token isn't provided one will attempt to be inherited from a LUNCHMONEY_ACCESS_TOKEN environment variable.

Examples:

from __future__ import annotations\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions()\n
Source code in lunchable/models/_lunchmoney.py
class LunchMoney(\n    AssetsClient,\n    BudgetsClient,\n    CategoriesClient,\n    CryptoClient,\n    PlaidAccountsClient,\n    RecurringExpensesClient,\n    TagsClient,\n    TransactionsClient,\n    UserClient,\n):\n    \"\"\"\n    Lunch Money Python Client.\n\n    This class facilitates with connections to\n    the [Lunch Money Developer API](https://lunchmoney.dev/). Authenticate\n    with an Access Token. If an access token isn't provided one will attempt to\n    be inherited from a `LUNCHMONEY_ACCESS_TOKEN` environment variable.\n\n    Examples\n    --------\n    ```python\n    from __future__ import annotations\n\n    from lunchable import LunchMoney\n    from lunchable.models import TransactionObject\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transactions: list[TransactionObject] = lunch.get_transactions()\n    ```\n    \"\"\"\n\n    def __init__(self, access_token: Optional[str] = None):\n        \"\"\"\n        Initialize a Lunch Money object with an Access Token.\n\n        Tries to inherit from the Environment if one isn't provided\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n        \"\"\"\n        super(LunchMoney, self).__init__(access_token=access_token)\n
"},{"location":"reference/#lunchable.LunchMoney.__init__","title":"__init__(access_token=None)","text":"

Initialize a Lunch Money object with an Access Token.

Tries to inherit from the Environment if one isn't provided

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None Source code in lunchable/models/_lunchmoney.py
def __init__(self, access_token: Optional[str] = None):\n    \"\"\"\n    Initialize a Lunch Money object with an Access Token.\n\n    Tries to inherit from the Environment if one isn't provided\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n    \"\"\"\n    super(LunchMoney, self).__init__(access_token=access_token)\n
"},{"location":"reference/#lunchable.LunchMoneyError","title":"LunchMoneyError","text":"

Bases: Exception

Base Exception for Lunch Money

Source code in lunchable/exceptions.py
class LunchMoneyError(Exception):\n    \"\"\"\n    Base Exception for Lunch Money\n    \"\"\"\n
"},{"location":"reference/#lunchable.TransactionInsertObject","title":"TransactionInsertObject","text":"

Bases: TransactionBaseObject

Object For Creating New Transactions

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required amount float

Numeric value of amount. i.e. $4.25 should be denoted as 4.25.

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None currency str | None

Three-letter lowercase currency code in ISO 4217 format. The code sent must exist in our database. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account.

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id.

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionInsertObject(TransactionBaseObject):\n    \"\"\"\n    Object For Creating New Transactions\n\n    https://lunchmoney.dev/#insert-transactions\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _amount_description = \"\"\"\n    Numeric value of amount. i.e. $4.25 should be denoted as 4.25.\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated with\n    the same account and must not be a category group.\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code in ISO 4217 format. The code sent must exist\n    in our database. Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account.\n    \"\"\"\n    _recurring_id = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must be associated\n    with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. If recurring_id is provided, the status will\n    automatically be set to recurring or recurring_suggested depending on the type of\n    recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id.\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found, an error\n    will be thrown. Passing in a string will attempt to match by string. If no matching tag\n    name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: datetime.date = Field(description=_date_description)\n    amount: float = Field(description=_amount_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    currency: Optional[str] = Field(\n        None, description=_currency_description, max_length=3\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(\n        None, description=_external_id_description, max_length=75\n    )\n    tags: Optional[List[Union[str, int]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/#lunchable.TransactionInsertObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/#lunchable.TransactionSplitObject","title":"TransactionSplitObject","text":"

Bases: TransactionBaseObject

Object for Splitting Transactions

https://lunchmoney.dev/#split-object

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account.

None notes str | None

Transaction Split Notes.

None amount float

Individual amount of split. Currency will inherit from parent transaction. All amounts must sum up to parent transaction amount.

required Source code in lunchable/models/transactions.py
class TransactionSplitObject(TransactionBaseObject):\n    \"\"\"\n    Object for Splitting Transactions\n\n    https://lunchmoney.dev/#split-object\n    \"\"\"\n\n    _date_description = \"Must be in ISO 8601 format (YYYY-MM-DD).\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account.\n    \"\"\"\n    _notes_description = \"Transaction Split Notes.\"\n    _amount_description = \"\"\"\n    Individual amount of split. Currency will inherit from parent transaction. All\n    amounts must sum up to parent transaction amount.\n    \"\"\"\n\n    date: datetime.date = Field(description=_date_description)\n    category_id: Optional[int] = Field(\n        default=None, description=_category_id_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    amount: float = Field(description=_amount_description)\n
"},{"location":"reference/#lunchable.TransactionUpdateObject","title":"TransactionUpdateObject","text":"

Bases: TransactionBaseObject

Object For Updating Existing Transactions

https://lunchmoney.dev/#update-transaction

Parameters:

Name Type Description Default date date | None

Must be in ISO 8601 format (YYYY-MM-DD).

None category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None amount float | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None currency str | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. Defaults to uncleared If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionUpdateObject(TransactionBaseObject):\n    \"\"\"\n    Object For Updating Existing Transactions\n\n    https://lunchmoney.dev/#update-transaction\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account and must not be a category group.\n    \"\"\"\n    _amount_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id\n    \"\"\"\n    _currency_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id.\n    Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _recurring_id_description = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must\n    be associated with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. Defaults to uncleared If recurring_id is\n    provided, the status will automatically be set to recurring or recurring_suggested\n    depending on the type of recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found,\n    an error will be thrown. Passing in a string will attempt to match by string.\n    If no matching tag name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: Optional[datetime.date] = Field(None, description=_date_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    amount: Optional[float] = Field(None, description=_amount_description)\n    currency: Optional[str] = Field(None, description=_currency_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id_description)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(None, description=_external_id_description)\n    tags: Optional[List[Union[int, str]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/#lunchable.TransactionUpdateObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/_cli/","title":"_cli","text":"

Lunchmoney CLI

"},{"location":"reference/_cli/#lunchable._cli.LunchMoneyContext","title":"LunchMoneyContext","text":"

Bases: LunchableModel

Context Object to PAss Around CLI

Parameters:

Name Type Description Default debug bool required access_token str | None required Source code in lunchable/_cli.py
class LunchMoneyContext(LunchableModel):\n    \"\"\"\n    Context Object to PAss Around CLI\n    \"\"\"\n\n    debug: bool\n    access_token: Optional[str]\n
"},{"location":"reference/_cli/#lunchable._cli.cli","title":"cli(ctx, debug, access_token)","text":"

Interactions with Lunch Money via lunchable \ud83c\udf71

Source code in lunchable/_cli.py
@click.group(invoke_without_command=True)\n@click.version_option(\n    version=lunchable.__version__, prog_name=lunchable.__application__\n)\n@access_token_option\n@debug_option\n@click.pass_context\ndef cli(ctx: click.core.Context, debug: bool, access_token: str) -> None:\n    \"\"\"\n    Interactions with Lunch Money via lunchable \ud83c\udf71\n    \"\"\"\n    ctx.obj = LunchMoneyContext(debug=debug, access_token=access_token)\n    traceback.install(show_locals=debug)\n    set_up_logging(log_level=logging.DEBUG if debug is True else logging.INFO)\n    if ctx.invoked_subcommand is None:\n        click.echo(ctx.get_help())\n
"},{"location":"reference/_cli/#lunchable._cli.http","title":"http(context, url, request, data)","text":"

Interact with the LunchMoney API

lunchable http /v1/transactions

Source code in lunchable/_cli.py
@cli.command()\n@click.argument(\"URL\")\n@click.option(\"-X\", \"--request\", default=\"GET\", help=\"Specify request command to use\")\n@click.option(\"-d\", \"--data\", default=None, help=\"HTTP POST data\")\n@click.pass_obj\ndef http(context: LunchMoneyContext, url: str, request: str, data: str) -> None:\n    \"\"\"\n    Interact with the LunchMoney API\n\n    lunchable http /v1/transactions\n    \"\"\"\n    lunch = LunchMoney(access_token=context.access_token)\n    if not url.startswith(\"http\"):\n        url = url.lstrip(\"/\")\n        url_request = f\"https://dev.lunchmoney.app/{url}\"\n    else:\n        url_request = url\n    resp = lunch.request(\n        method=request,\n        url=url_request,\n        content=data,\n    )\n    try:\n        resp.raise_for_status()\n    except httpx.HTTPError:\n        logger.error(resp)\n        print(resp.text)\n        sys.exit(1)\n    try:\n        response = resp.json()\n    except JSONDecodeError:\n        response = resp.text\n    json_data = to_jsonable_python(response)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.lunchmoney_transactions","title":"lunchmoney_transactions(context, **kwargs)","text":"

Retrieve Lunch Money Transactions

Source code in lunchable/_cli.py
@transactions.command(\"get\")\n@click.option(\n    \"--start-date\",\n    default=None,\n    help=\"Denotes the beginning of the time period to fetch transactions for. Defaults\"\n    \"to beginning of current month. Required if end_date exists. \"\n    \"Format: YYYY-MM-DD.\",\n)\n@click.option(\n    \"--end-date\",\n    default=None,\n    help=\"Denotes the end of the time period you'd like to get transactions for. \"\n    \"Defaults to end of current month. Required if start_date exists.\"\n    \"Format: YYYY-MM-DD.\",\n)\n@click.option(\n    \"--tag-id\", default=None, help=\"Filter by tag. Only accepts IDs, not names.\"\n)\n@click.option(\"--recurring-id\", default=None, help=\"Filter by recurring expense\")\n@click.option(\"--plaid-account-id\", default=None, help=\"Filter by Plaid account\")\n@click.option(\n    \"--category-id\",\n    default=None,\n    help=\"Filter by category. Will also match category groups.\",\n)\n@click.option(\"--asset-id\", default=None, help=\"Filter by asset\")\n@click.option(\n    \"--group-id\",\n    default=None,\n    help=\"Filter by group_id (if the transaction is part of a specific group)\",\n)\n@click.option(\n    \"--is-group\", default=None, help=\"Filter by group (returns transaction groups)\"\n)\n@click.option(\n    \"--status\",\n    default=None,\n    help=\"Filter by status (Can be cleared or uncleared. For recurring \"\n    \"transactions, use recurring)\",\n)\n@click.option(\"--offset\", default=None, help=\"Sets the offset for the records returned\")\n@click.option(\n    \"--limit\",\n    default=None,\n    help=\"Sets the maximum number of records to return. Note: The server will not \"\n    \"respond with any indication that there are more records to be returned. \"\n    \"Please check the response length to determine if you should make another \"\n    \"call with an offset to fetch more transactions.\",\n)\n@click.option(\n    \"--debit-as-negative\",\n    default=None,\n    help=\"Pass in true if you\u2019d like expenses to be returned as negative amounts and \"\n    \"credits as positive amounts. Defaults to false.\",\n)\n@click.option(\n    \"--pending\",\n    is_flag=True,\n    default=None,\n    help=\"Pass in true if you\u2019d like to include imported transactions with a pending status.\",\n)\n@click.pass_obj\ndef lunchmoney_transactions(\n    context: LunchMoneyContext, **kwargs: Dict[str, Any]\n) -> None:\n    \"\"\"\n    Retrieve Lunch Money Transactions\n    \"\"\"\n    lunch = LunchMoney(access_token=context.access_token)\n    transactions = lunch.get_transactions(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(transactions)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.make_splitlunch","title":"make_splitlunch(**kwargs)","text":"

Split all SplitLunch tagged transactions in half.

One of these new splits will be recategorized to Reimbursement.

Source code in lunchable/_cli.py
@splitlunch.command(\"splitlunch\")\n@tag_transactions\ndef make_splitlunch(**kwargs: Union[int, str, bool]) -> None:\n    \"\"\"\n    Split all `SplitLunch` tagged transactions in half.\n\n    One of these new splits will be recategorized to `Reimbursement`.\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    splitlunch = SplitLunch()\n    results = splitlunch.make_splitlunch(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(results)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.make_splitlunch_direct_import","title":"make_splitlunch_direct_import(**kwargs)","text":"

Import SplitLunchDirectImport tagged transactions to Splitwise and Split them in Lunch Money

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Any tags will be reapplied.

Source code in lunchable/_cli.py
@splitlunch.command(\"splitlunch-direct-import\")\n@tag_transactions\n@financial_partner_id\n@financial_partner_email\n@financial_partner_group_id\ndef make_splitlunch_direct_import(**kwargs: Union[int, str, bool]) -> None:\n    \"\"\"\n    Import `SplitLunchDirectImport` tagged transactions to Splitwise and Split them in Lunch Money\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Any tags will be\n    reapplied.\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    financial_partner_id: Optional[int] = kwargs.pop(\"financial_partner_id\")  # type: ignore[assignment]\n    financial_partner_email: Optional[str] = kwargs.pop(\"financial_partner_email\")  # type: ignore[assignment]\n    financial_partner_group_id: Optional[int] = kwargs.pop(\"financial_partner_group_id\")  # type: ignore[assignment]\n    splitlunch = SplitLunch(\n        financial_partner_id=financial_partner_id,\n        financial_partner_email=financial_partner_email,\n        financial_partner_group_id=financial_partner_group_id,\n    )\n    results = splitlunch.make_splitlunch_direct_import(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(results)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.make_splitlunch_import","title":"make_splitlunch_import(**kwargs)","text":"

Import SplitLunchImport tagged transactions to Splitwise and Split them in Lunch Money

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Any tags will be reapplied.

Source code in lunchable/_cli.py
@splitlunch.command(\"splitlunch-import\")\n@tag_transactions\n@financial_partner_id\n@financial_partner_email\n@financial_partner_group_id\ndef make_splitlunch_import(**kwargs: Union[int, str, bool]) -> None:\n    \"\"\"\n    Import `SplitLunchImport` tagged transactions to Splitwise and Split them in Lunch Money\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Any tags will be\n    reapplied.\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    financial_partner_id: Optional[int] = kwargs.pop(\"financial_partner_id\")  # type: ignore[assignment]\n    financial_partner_email: Optional[str] = kwargs.pop(\"financial_partner_email\")  # type: ignore[assignment]\n    financial_partner_group_id: Optional[int] = kwargs.pop(\"financial_partner_group_id\")  # type: ignore[assignment]\n    splitlunch = SplitLunch(\n        financial_partner_id=financial_partner_id,\n        financial_partner_email=financial_partner_email,\n        financial_partner_group_id=financial_partner_group_id,\n    )\n    results = splitlunch.make_splitlunch_import(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(results)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.notify","title":"notify(continuous, interval, user_key)","text":"

Send a Notification for each Uncleared Transaction

Source code in lunchable/_cli.py
@pushlunch.command(\"notify\")\n@click.option(\n    \"--continuous\",\n    is_flag=True,\n    help=\"Whether to continuously check for more uncleared transactions, \"\n    \"waiting a fixed amount in between checks.\",\n)\n@click.option(\n    \"--interval\",\n    default=None,\n    help=\"Sleep Interval in Between Tries - only applies if `continuous` is set. \"\n    \"Defaults to 60 (minutes). Cannot be less than 5 (minutes)\",\n)\n@click.option(\n    \"--user-key\",\n    default=None,\n    help=\"Pushover User Key. Defaults to `PUSHOVER_USER_KEY` env var\",\n)\ndef notify(continuous: bool, interval: int, user_key: str) -> None:\n    \"\"\"\n    Send a Notification for each Uncleared Transaction\n    \"\"\"\n    push = PushLunch(user_key=user_key)\n    if interval is not None:\n        interval = int(interval)\n    push.notify_uncleared_transactions(continuous=continuous, interval=interval)\n
"},{"location":"reference/_cli/#lunchable._cli.plugins","title":"plugins()","text":"

Interact with Lunchable Plugins

Source code in lunchable/_cli.py
@cli.group()\ndef plugins() -> None:\n    \"\"\"\n    Interact with Lunchable Plugins\n    \"\"\"\n
"},{"location":"reference/_cli/#lunchable._cli.primelunch","title":"primelunch()","text":"

PrimeLunch CLI - Syncing LunchMoney with Amazon

Source code in lunchable/_cli.py
@plugins.group()\ndef primelunch() -> None:\n    \"\"\"\n    PrimeLunch CLI - Syncing LunchMoney with Amazon\n    \"\"\"\n
"},{"location":"reference/_cli/#lunchable._cli.pushlunch","title":"pushlunch()","text":"

Push Notifications for Lunch Money: PushLunch \ud83d\udcf2

Source code in lunchable/_cli.py
@plugins.group()\ndef pushlunch() -> None:\n    \"\"\"\n    Push Notifications for Lunch Money: PushLunch \ud83d\udcf2\n    \"\"\"\n    pass\n
"},{"location":"reference/_cli/#lunchable._cli.refresh_splitwise_transactions","title":"refresh_splitwise_transactions(dated_before, dated_after, allow_self_paid, allow_payments)","text":"

Import New Splitwise Transactions to Lunch Money and

This function gets all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two. This also updates the account balance.

Source code in lunchable/_cli.py
@splitlunch.command(\"refresh\")\n@dated_after\n@dated_before\n@click.option(\n    \"--allow-self-paid/--no-allow-self-paid\",\n    default=False,\n    help=\"Allow self-paid expenses to be imported (filtered out by default).\",\n)\n@click.option(\n    \"--allow-payments/--no-allow-payments\",\n    default=False,\n    help=\"Allow payments to be imported (filtered out by default).\",\n)\ndef refresh_splitwise_transactions(\n    dated_before: Optional[datetime.datetime],\n    dated_after: Optional[datetime.datetime],\n    allow_self_paid: bool,\n    allow_payments: bool,\n) -> None:\n    \"\"\"\n    Import New Splitwise Transactions to Lunch Money and\n\n    This function gets all transactions from Splitwise, all transactions from\n    your Lunch Money Splitwise account and compares the two. This also updates\n    the account balance.\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    splitlunch = SplitLunch()\n    response = splitlunch.refresh_splitwise_transactions(\n        dated_before=dated_before,\n        dated_after=dated_after,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    json_data = to_jsonable_python(response)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.splitlunch","title":"splitlunch()","text":"

Splitwise Plugin for lunchable, SplitLunch \ud83d\udcb2\ud83c\udf71

Source code in lunchable/_cli.py
@plugins.group()\ndef splitlunch() -> None:\n    \"\"\"\n    Splitwise Plugin for lunchable, SplitLunch \ud83d\udcb2\ud83c\udf71\n    \"\"\"\n    pass\n
"},{"location":"reference/_cli/#lunchable._cli.splitlunch_expenses","title":"splitlunch_expenses(**kwargs)","text":"

Retrieve Splitwise Expenses

Source code in lunchable/_cli.py
@splitlunch.command(\"expenses\")\n@click.option(\n    \"--limit\", default=None, help=\"Limit the amount of Results. 0 returns everything.\"\n)\n@click.option(\"--offset\", default=None, help=\"Number of expenses to be skipped\")\n@click.option(\"--limit\", default=None, help=\"Number of expenses to be returned\")\n@click.option(\"--group-id\", default=None, help=\"GroupID of the expenses\")\n@click.option(\"--friendship-id\", default=None, help=\"FriendshipID of the expenses\")\n@dated_after\n@dated_before\n@click.option(\n    \"--updated-after\",\n    default=None,\n    help=\"ISO 8601 Date time. Return expenses updated after this date\",\n)\n@click.option(\n    \"--updated-before\",\n    default=None,\n    help=\"ISO 8601 Date time. Return expenses updated before this date\",\n)\ndef splitlunch_expenses(**kwargs: Union[int, str, bool]) -> None:\n    \"\"\"\n    Retrieve Splitwise Expenses\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    splitlunch = SplitLunch()\n    if set(kwargs.values()) == {None}:\n        kwargs[\"limit\"] = 5\n    expenses = splitlunch.get_expenses(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(expenses)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.transactions","title":"transactions()","text":"

Interact with Lunch Money transactions

Source code in lunchable/_cli.py
@cli.group()\ndef transactions() -> None:\n    \"\"\"\n    Interact with Lunch Money transactions\n    \"\"\"\n
"},{"location":"reference/_cli/#lunchable._cli.update_splitwise_balance","title":"update_splitwise_balance()","text":"

Update the Splitwise Asset Balance

Source code in lunchable/_cli.py
@splitlunch.command(\"update-balance\")\ndef update_splitwise_balance() -> None:\n    \"\"\"\n    Update the Splitwise Asset Balance\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    splitlunch = SplitLunch()\n    updated_asset = splitlunch.update_splitwise_balance()\n    json_data = to_jsonable_python(updated_asset)\n    print_json(data=json_data)\n
"},{"location":"reference/_version/","title":"_version","text":"

lunchable Version file

"},{"location":"reference/exceptions/","title":"exceptions","text":"

Lunchmoney Exceptions

"},{"location":"reference/exceptions/#lunchable.exceptions.EnvironmentVariableError","title":"EnvironmentVariableError","text":"

Bases: LunchMoneyError, EnvironmentError

Lunch Money Missing Environment Variable Error

Source code in lunchable/exceptions.py
class EnvironmentVariableError(LunchMoneyError, EnvironmentError):\n    \"\"\"\n    Lunch Money Missing Environment Variable Error\n    \"\"\"\n
"},{"location":"reference/exceptions/#lunchable.exceptions.LunchMoneyError","title":"LunchMoneyError","text":"

Bases: Exception

Base Exception for Lunch Money

Source code in lunchable/exceptions.py
class LunchMoneyError(Exception):\n    \"\"\"\n    Base Exception for Lunch Money\n    \"\"\"\n
"},{"location":"reference/exceptions/#lunchable.exceptions.LunchMoneyHTTPError","title":"LunchMoneyHTTPError","text":"

Bases: LunchMoneyError, HTTPError

Lunch Money HTTP Error

Source code in lunchable/exceptions.py
class LunchMoneyHTTPError(LunchMoneyError, HTTPError):\n    \"\"\"\n    Lunch Money HTTP Error\n    \"\"\"\n
"},{"location":"reference/exceptions/#lunchable.exceptions.LunchMoneyImportError","title":"LunchMoneyImportError","text":"

Bases: LunchMoneyError, ImportError

Lunch Money Import Error

Source code in lunchable/exceptions.py
class LunchMoneyImportError(LunchMoneyError, ImportError):\n    \"\"\"\n    Lunch Money Import Error\n    \"\"\"\n
"},{"location":"reference/_config/","title":"_config","text":"

Lunch Money Config Namespaces and Helpers

"},{"location":"reference/_config/#lunchable._config.APIConfig","title":"APIConfig","text":"

Configuration Helper Class for Connecting to the Lunchmoney API

Source code in lunchable/_config/api_config.py
class APIConfig:\n    \"\"\"\n    Configuration Helper Class for Connecting to the Lunchmoney API\n    \"\"\"\n\n    LUNCHMONEY_SCHEME: str = \"https\"\n    LUNCHMONEY_NETLOC: str = \"dev.lunchmoney.app\"\n    LUNCHMONEY_API_PATH: str = \"v1\"\n\n    LUNCHMONEY_TRANSACTIONS: str = \"transactions\"\n    LUNCHMONEY_TRANSACTION_GROUPS: str = \"group\"\n    LUNCHMONEY_PLAID_ACCOUNTS: str = \"plaid_accounts\"\n    LUNCH_MONEY_RECURRING_EXPENSES: str = \"recurring_expenses\"\n    LUNCHMONEY_BUDGET: str = \"budgets\"\n    LUNCHMONEY_ASSETS: str = \"assets\"\n    LUNCHMONEY_CATEGORIES: str = \"categories\"\n    LUNCHMONEY_TAGS: str = \"tags\"\n    LUNCHMONEY_CRYPTO: str = \"crypto\"\n    LUNCHMONEY_CRYPTO_MANUAL: str = \"manual\"\n    LUNCHMONEY_ME: str = \"me\"\n\n    LUNCHMONEY_CONTENT_TYPE_HEADERS: Dict[str, str] = {\n        \"Content-Type\": \"application/json\"\n    }\n\n    _access_token_environment_variable = \"LUNCHMONEY_ACCESS_TOKEN\"\n\n    @staticmethod\n    def get_access_token(access_token: Optional[str] = None) -> str:\n        \"\"\"\n        Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if access_token is None:\n            logger.info(\n                \"Loading Lunch Money Developer API Access token from environment\"\n            )\n            access_token = getenv(APIConfig._access_token_environment_variable, None)\n        if access_token is None:\n            access_token_error_message = (\n                \"You must provide a Lunch Money Developer API Access Token directly or set your \"\n                f\"{APIConfig._access_token_environment_variable} environment variable.\"\n            )\n            raise EnvironmentVariableError(access_token_error_message)\n        return access_token\n\n    @staticmethod\n    def get_header(access_token: Optional[str] = None) -> Dict[str, str]:\n        \"\"\"\n        Get the header dict to pass to httpx\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n\n        Returns\n        -------\n        Dict[str, str]\n        \"\"\"\n        access_token = APIConfig.get_access_token(access_token=access_token)\n        lunchable_header = {\n            \"Authorization\": f\"Bearer {access_token}\",\n            \"User-Agent\": f\"lunchable/{__version__}\",\n        }\n        lunchable_header.update(APIConfig.LUNCHMONEY_CONTENT_TYPE_HEADERS)\n        return lunchable_header\n\n    @staticmethod\n    def make_url(url_path: Union[List[Union[str, int]], str, int]) -> str:\n        \"\"\"\n        Make a Lunch Money API URL using path parts\n\n        Parameters\n        ----------\n        url_path: Union[List[Union[str, int]], str, int]\n            API Components, if a list join these sequentially\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if isinstance(url_path, str):\n            url_path = [url_path]\n        if not isinstance(url_path, List):\n            raise LunchMoneyError(\n                \"You must provide a string or list of strings to construct a URL\"\n            )\n        path_set = [\n            str(item).lower()\n            for item in url_path\n            if str(item).lower() != APIConfig.LUNCHMONEY_API_PATH\n        ]\n        url = APIConfig._generate_url(\n            scheme=APIConfig.LUNCHMONEY_SCHEME,\n            netloc=APIConfig.LUNCHMONEY_NETLOC,\n            path=\"/\".join([APIConfig.LUNCHMONEY_API_PATH, *path_set]),\n        )\n        return url\n\n    @classmethod\n    def _generate_url(\n        cls,\n        scheme: str,\n        netloc: str,\n        path: str = \"\",\n        params: str = \"\",\n        query: str = \"\",\n        fragment: str = \"\",\n    ) -> str:\n        \"\"\"\n        Build a URL\n\n        Parameters\n        ----------\n        scheme: str\n            URL scheme specifier\n        netloc: str\n            Network location part\n        path: str\n            Hierarchical path\n        params: str\n            Parameters for last path element\n        query: str\n            Query component\n        fragment: str\n            Fragment identifier\n        Returns\n        -------\n        url: str\n            Compiled URL\n        \"\"\"\n        url_components = {\n            \"scheme\": scheme,\n            \"netloc\": netloc,\n            \"path\": path,\n            \"params\": params,\n            \"query\": query,\n            \"fragment\": fragment,\n        }\n        return parse.urlunparse(components=tuple(url_components.values()))\n
"},{"location":"reference/_config/#lunchable._config.APIConfig.get_access_token","title":"get_access_token(access_token=None) staticmethod","text":"

Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None

Returns:

Type Description str Source code in lunchable/_config/api_config.py
@staticmethod\ndef get_access_token(access_token: Optional[str] = None) -> str:\n    \"\"\"\n    Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if access_token is None:\n        logger.info(\n            \"Loading Lunch Money Developer API Access token from environment\"\n        )\n        access_token = getenv(APIConfig._access_token_environment_variable, None)\n    if access_token is None:\n        access_token_error_message = (\n            \"You must provide a Lunch Money Developer API Access Token directly or set your \"\n            f\"{APIConfig._access_token_environment_variable} environment variable.\"\n        )\n        raise EnvironmentVariableError(access_token_error_message)\n    return access_token\n
"},{"location":"reference/_config/#lunchable._config.APIConfig.get_header","title":"get_header(access_token=None) staticmethod","text":"

Get the header dict to pass to httpx

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None

Returns:

Type Description Dict[str, str] Source code in lunchable/_config/api_config.py
@staticmethod\ndef get_header(access_token: Optional[str] = None) -> Dict[str, str]:\n    \"\"\"\n    Get the header dict to pass to httpx\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n\n    Returns\n    -------\n    Dict[str, str]\n    \"\"\"\n    access_token = APIConfig.get_access_token(access_token=access_token)\n    lunchable_header = {\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"User-Agent\": f\"lunchable/{__version__}\",\n    }\n    lunchable_header.update(APIConfig.LUNCHMONEY_CONTENT_TYPE_HEADERS)\n    return lunchable_header\n
"},{"location":"reference/_config/#lunchable._config.APIConfig.make_url","title":"make_url(url_path) staticmethod","text":"

Make a Lunch Money API URL using path parts

Parameters:

Name Type Description Default url_path Union[List[Union[str, int]], str, int]

API Components, if a list join these sequentially

required

Returns:

Type Description str Source code in lunchable/_config/api_config.py
@staticmethod\ndef make_url(url_path: Union[List[Union[str, int]], str, int]) -> str:\n    \"\"\"\n    Make a Lunch Money API URL using path parts\n\n    Parameters\n    ----------\n    url_path: Union[List[Union[str, int]], str, int]\n        API Components, if a list join these sequentially\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if isinstance(url_path, str):\n        url_path = [url_path]\n    if not isinstance(url_path, List):\n        raise LunchMoneyError(\n            \"You must provide a string or list of strings to construct a URL\"\n        )\n    path_set = [\n        str(item).lower()\n        for item in url_path\n        if str(item).lower() != APIConfig.LUNCHMONEY_API_PATH\n    ]\n    url = APIConfig._generate_url(\n        scheme=APIConfig.LUNCHMONEY_SCHEME,\n        netloc=APIConfig.LUNCHMONEY_NETLOC,\n        path=\"/\".join([APIConfig.LUNCHMONEY_API_PATH, *path_set]),\n    )\n    return url\n
"},{"location":"reference/_config/#lunchable._config.FileConfig","title":"FileConfig","text":"

Configuration Namespace for File Paths

Source code in lunchable/_config/file_config.py
class FileConfig:\n    \"\"\"\n    Configuration Namespace for File Paths\n    \"\"\"\n\n    HOME_DIR = Path.home()\n    _file_config_module = Path(__file__).resolve()\n    CONFIG_DIR = _file_config_module.parent\n    LUNCHMONEY_DIR = CONFIG_DIR.parent\n    PROJECT_DIR = LUNCHMONEY_DIR.parent\n    DATA_DIR = LUNCHMONEY_DIR.joinpath(\"data\")\n
"},{"location":"reference/_config/api_config/","title":"api_config","text":"

API Configuration Helper

"},{"location":"reference/_config/api_config/#lunchable._config.api_config.APIConfig","title":"APIConfig","text":"

Configuration Helper Class for Connecting to the Lunchmoney API

Source code in lunchable/_config/api_config.py
class APIConfig:\n    \"\"\"\n    Configuration Helper Class for Connecting to the Lunchmoney API\n    \"\"\"\n\n    LUNCHMONEY_SCHEME: str = \"https\"\n    LUNCHMONEY_NETLOC: str = \"dev.lunchmoney.app\"\n    LUNCHMONEY_API_PATH: str = \"v1\"\n\n    LUNCHMONEY_TRANSACTIONS: str = \"transactions\"\n    LUNCHMONEY_TRANSACTION_GROUPS: str = \"group\"\n    LUNCHMONEY_PLAID_ACCOUNTS: str = \"plaid_accounts\"\n    LUNCH_MONEY_RECURRING_EXPENSES: str = \"recurring_expenses\"\n    LUNCHMONEY_BUDGET: str = \"budgets\"\n    LUNCHMONEY_ASSETS: str = \"assets\"\n    LUNCHMONEY_CATEGORIES: str = \"categories\"\n    LUNCHMONEY_TAGS: str = \"tags\"\n    LUNCHMONEY_CRYPTO: str = \"crypto\"\n    LUNCHMONEY_CRYPTO_MANUAL: str = \"manual\"\n    LUNCHMONEY_ME: str = \"me\"\n\n    LUNCHMONEY_CONTENT_TYPE_HEADERS: Dict[str, str] = {\n        \"Content-Type\": \"application/json\"\n    }\n\n    _access_token_environment_variable = \"LUNCHMONEY_ACCESS_TOKEN\"\n\n    @staticmethod\n    def get_access_token(access_token: Optional[str] = None) -> str:\n        \"\"\"\n        Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if access_token is None:\n            logger.info(\n                \"Loading Lunch Money Developer API Access token from environment\"\n            )\n            access_token = getenv(APIConfig._access_token_environment_variable, None)\n        if access_token is None:\n            access_token_error_message = (\n                \"You must provide a Lunch Money Developer API Access Token directly or set your \"\n                f\"{APIConfig._access_token_environment_variable} environment variable.\"\n            )\n            raise EnvironmentVariableError(access_token_error_message)\n        return access_token\n\n    @staticmethod\n    def get_header(access_token: Optional[str] = None) -> Dict[str, str]:\n        \"\"\"\n        Get the header dict to pass to httpx\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n\n        Returns\n        -------\n        Dict[str, str]\n        \"\"\"\n        access_token = APIConfig.get_access_token(access_token=access_token)\n        lunchable_header = {\n            \"Authorization\": f\"Bearer {access_token}\",\n            \"User-Agent\": f\"lunchable/{__version__}\",\n        }\n        lunchable_header.update(APIConfig.LUNCHMONEY_CONTENT_TYPE_HEADERS)\n        return lunchable_header\n\n    @staticmethod\n    def make_url(url_path: Union[List[Union[str, int]], str, int]) -> str:\n        \"\"\"\n        Make a Lunch Money API URL using path parts\n\n        Parameters\n        ----------\n        url_path: Union[List[Union[str, int]], str, int]\n            API Components, if a list join these sequentially\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if isinstance(url_path, str):\n            url_path = [url_path]\n        if not isinstance(url_path, List):\n            raise LunchMoneyError(\n                \"You must provide a string or list of strings to construct a URL\"\n            )\n        path_set = [\n            str(item).lower()\n            for item in url_path\n            if str(item).lower() != APIConfig.LUNCHMONEY_API_PATH\n        ]\n        url = APIConfig._generate_url(\n            scheme=APIConfig.LUNCHMONEY_SCHEME,\n            netloc=APIConfig.LUNCHMONEY_NETLOC,\n            path=\"/\".join([APIConfig.LUNCHMONEY_API_PATH, *path_set]),\n        )\n        return url\n\n    @classmethod\n    def _generate_url(\n        cls,\n        scheme: str,\n        netloc: str,\n        path: str = \"\",\n        params: str = \"\",\n        query: str = \"\",\n        fragment: str = \"\",\n    ) -> str:\n        \"\"\"\n        Build a URL\n\n        Parameters\n        ----------\n        scheme: str\n            URL scheme specifier\n        netloc: str\n            Network location part\n        path: str\n            Hierarchical path\n        params: str\n            Parameters for last path element\n        query: str\n            Query component\n        fragment: str\n            Fragment identifier\n        Returns\n        -------\n        url: str\n            Compiled URL\n        \"\"\"\n        url_components = {\n            \"scheme\": scheme,\n            \"netloc\": netloc,\n            \"path\": path,\n            \"params\": params,\n            \"query\": query,\n            \"fragment\": fragment,\n        }\n        return parse.urlunparse(components=tuple(url_components.values()))\n
"},{"location":"reference/_config/api_config/#lunchable._config.api_config.APIConfig.get_access_token","title":"get_access_token(access_token=None) staticmethod","text":"

Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None

Returns:

Type Description str Source code in lunchable/_config/api_config.py
@staticmethod\ndef get_access_token(access_token: Optional[str] = None) -> str:\n    \"\"\"\n    Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if access_token is None:\n        logger.info(\n            \"Loading Lunch Money Developer API Access token from environment\"\n        )\n        access_token = getenv(APIConfig._access_token_environment_variable, None)\n    if access_token is None:\n        access_token_error_message = (\n            \"You must provide a Lunch Money Developer API Access Token directly or set your \"\n            f\"{APIConfig._access_token_environment_variable} environment variable.\"\n        )\n        raise EnvironmentVariableError(access_token_error_message)\n    return access_token\n
"},{"location":"reference/_config/api_config/#lunchable._config.api_config.APIConfig.get_header","title":"get_header(access_token=None) staticmethod","text":"

Get the header dict to pass to httpx

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None

Returns:

Type Description Dict[str, str] Source code in lunchable/_config/api_config.py
@staticmethod\ndef get_header(access_token: Optional[str] = None) -> Dict[str, str]:\n    \"\"\"\n    Get the header dict to pass to httpx\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n\n    Returns\n    -------\n    Dict[str, str]\n    \"\"\"\n    access_token = APIConfig.get_access_token(access_token=access_token)\n    lunchable_header = {\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"User-Agent\": f\"lunchable/{__version__}\",\n    }\n    lunchable_header.update(APIConfig.LUNCHMONEY_CONTENT_TYPE_HEADERS)\n    return lunchable_header\n
"},{"location":"reference/_config/api_config/#lunchable._config.api_config.APIConfig.make_url","title":"make_url(url_path) staticmethod","text":"

Make a Lunch Money API URL using path parts

Parameters:

Name Type Description Default url_path Union[List[Union[str, int]], str, int]

API Components, if a list join these sequentially

required

Returns:

Type Description str Source code in lunchable/_config/api_config.py
@staticmethod\ndef make_url(url_path: Union[List[Union[str, int]], str, int]) -> str:\n    \"\"\"\n    Make a Lunch Money API URL using path parts\n\n    Parameters\n    ----------\n    url_path: Union[List[Union[str, int]], str, int]\n        API Components, if a list join these sequentially\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if isinstance(url_path, str):\n        url_path = [url_path]\n    if not isinstance(url_path, List):\n        raise LunchMoneyError(\n            \"You must provide a string or list of strings to construct a URL\"\n        )\n    path_set = [\n        str(item).lower()\n        for item in url_path\n        if str(item).lower() != APIConfig.LUNCHMONEY_API_PATH\n    ]\n    url = APIConfig._generate_url(\n        scheme=APIConfig.LUNCHMONEY_SCHEME,\n        netloc=APIConfig.LUNCHMONEY_NETLOC,\n        path=\"/\".join([APIConfig.LUNCHMONEY_API_PATH, *path_set]),\n    )\n    return url\n
"},{"location":"reference/_config/file_config/","title":"file_config","text":"

File Path Helper

"},{"location":"reference/_config/file_config/#lunchable._config.file_config.FileConfig","title":"FileConfig","text":"

Configuration Namespace for File Paths

Source code in lunchable/_config/file_config.py
class FileConfig:\n    \"\"\"\n    Configuration Namespace for File Paths\n    \"\"\"\n\n    HOME_DIR = Path.home()\n    _file_config_module = Path(__file__).resolve()\n    CONFIG_DIR = _file_config_module.parent\n    LUNCHMONEY_DIR = CONFIG_DIR.parent\n    PROJECT_DIR = LUNCHMONEY_DIR.parent\n    DATA_DIR = LUNCHMONEY_DIR.joinpath(\"data\")\n
"},{"location":"reference/_config/logging_config/","title":"logging_config","text":"

Dynamic Logging Configuration

"},{"location":"reference/_config/logging_config/#lunchable._config.logging_config.get_log_handler","title":"get_log_handler(log_level=None)","text":"

Determine which logging handler should be used

Parameters:

Name Type Description Default log_level Optional[int]

Which logging level should be used. If none is provided the LOG_LEVEL environment variable will be used, defaulting to \"INFO\".

None

Returns:

Type Description Tuple[Handler, Union[int, str]] Source code in lunchable/_config/logging_config.py
def get_log_handler(\n    log_level: Optional[int] = None,\n) -> Tuple[logging.Handler, Union[int, str]]:\n    \"\"\"\n    Determine which logging handler should be used\n\n    Parameters\n    ----------\n    log_level: Optional[int]\n        Which logging level should be used. If none is provided the LOG_LEVEL environment\n        variable will be used, defaulting to \"INFO\".\n\n    Returns\n    -------\n    Tuple[logging.Handler, Union[int, str]]\n    \"\"\"\n    if log_level is None:\n        log_level = logging.getLevelName(getenv(\"LOG_LEVEL\", \"INFO\").upper())\n    rich_handler = RichHandler(\n        level=log_level,\n        rich_tracebacks=True,\n        omit_repeated_times=False,\n        show_path=False,\n        tracebacks_suppress=[click],\n        console=Console(stderr=True),\n    )\n    httpx_logger = logging.getLogger(\"httpx\")\n    if log_level != logging.DEBUG:\n        httpx_logger.setLevel(logging.WARNING)\n    python_handler = logging.StreamHandler()\n    python_formatter = logging.Formatter(\"%(asctime)s [%(levelname)8s]: %(message)s\")\n    python_handler.setFormatter(python_formatter)\n    python_handler.setLevel(log_level)\n    _log_dict = {\n        \"rich\": rich_handler,\n        \"python\": python_handler,\n    }\n    if getenv(\"PYTEST_CURRENT_TEST\", None) is not None:\n        handler = \"python\"\n    else:\n        handler = LOG_HANDLER\n    log_handler: logging.Handler = _log_dict.get(handler, rich_handler)\n    return log_handler, log_level\n
"},{"location":"reference/_config/logging_config/#lunchable._config.logging_config.set_up_logging","title":"set_up_logging(log_level=None)","text":"

Set Up a Root Logger

Parameters:

Name Type Description Default log_level Optional[int]

Which logging level should be used. If none is provided the LOG_LEVEL environment variable will be used, defaulting to \"INFO\".

None Source code in lunchable/_config/logging_config.py
def set_up_logging(log_level: Optional[int] = None) -> None:\n    \"\"\"\n    Set Up a Root Logger\n\n    Parameters\n    ----------\n    log_level: Optional[int]\n        Which logging level should be used. If none is provided the LOG_LEVEL environment\n        variable will be used, defaulting to \"INFO\".\n    \"\"\"\n    log_handler, level_to_log = get_log_handler(log_level=log_level)\n    logging.root.handlers = [log_handler]\n    if isinstance(log_handler, RichHandler):\n        rich_formatter = logging.Formatter(\n            datefmt=\"[%Y-%m-%d %H:%M:%S]\", fmt=\"%(message)s\"\n        )\n        logging.root.handlers[0].setFormatter(rich_formatter)\n        level_to_log = logging.NOTSET\n    logging.root.setLevel(level_to_log)\n
"},{"location":"reference/models/","title":"models","text":"

Lunch Money Python SDK and Associated Objects

"},{"location":"reference/models/#lunchable.models.AssetsObject","title":"AssetsObject","text":"

Bases: LunchableModel

Manually Managed Asset Objects

Assets in Lunch Money are similar to plaid-accounts except that they are manually managed.

https://lunchmoney.dev/#assets-object

Parameters:

Name Type Description Default id int

Unique identifier for asset

required type_name str

Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan, cryptocurrency, investment, other, credit, real estate]

required subtype_name str | None

Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]

None name str

Name of the asset

required display_name str | None

Display name of the asset (as set by user)

None balance float

Current balance of the asset in numeric format to 4 decimal places

required balance_as_of datetime

Date/time the balance was last updated in ISO 8601 extended format

required closed_on date | None

The date this asset was closed (optional)

None currency str

Three-letter lowercase currency code of the balance in ISO 4217 format

required institution_name str | None

Name of institution holding the asset

None exclude_transactions bool

If true, this asset will not show up as an option for assignment when creating transactions manually

False created_at datetime

Date/time the asset was created in ISO 8601 extended format

required Source code in lunchable/models/assets.py
class AssetsObject(LunchableModel):\n    \"\"\"\n    Manually Managed Asset Objects\n\n    Assets in Lunch Money are similar to `plaid-accounts` except that they are manually managed.\n\n    https://lunchmoney.dev/#assets-object\n    \"\"\"\n\n    _type_name_description = \"\"\"\n    Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan,\n    cryptocurrency, investment, other, credit, real estate]\n    \"\"\"\n    _subtype_name_description = \"\"\"\n    Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]\n    \"\"\"\n    _balance_description = (\n        \"Current balance of the asset in numeric format to 4 decimal places\"\n    )\n    _balance_as_of_description = \"\"\"\n    Date/time the balance was last updated in ISO 8601 extended format\n    \"\"\"\n    _closed_on_description = \"The date this asset was closed (optional)\"\n    _currency_description = (\n        \"Three-letter lowercase currency code of the balance in ISO 4217 format\"\n    )\n    _created_at_description = (\n        \"Date/time the asset was created in ISO 8601 extended format\"\n    )\n    _exclude_transactions_description = (\n        \"If true, this asset will not show up as an \"\n        \"option for assignment when creating \"\n        \"transactions manually\"\n    )\n\n    id: int = Field(description=\"Unique identifier for asset\")\n    type_name: str = Field(description=_type_name_description)\n    subtype_name: Optional[str] = Field(None, description=_subtype_name_description)\n    name: str = Field(description=\"Name of the asset\")\n    display_name: Optional[str] = Field(\n        None, description=\"Display name of the asset (as set by user)\"\n    )\n    balance: float = Field(description=_balance_description)\n    balance_as_of: datetime.datetime = Field(description=_balance_as_of_description)\n    closed_on: Optional[datetime.date] = Field(None, description=_closed_on_description)\n    currency: str = Field(description=_currency_description)\n    institution_name: Optional[str] = Field(\n        None, description=\"Name of institution holding the asset\"\n    )\n    exclude_transactions: bool = Field(\n        default=False, description=_exclude_transactions_description\n    )\n    created_at: datetime.datetime = Field(description=_created_at_description)\n
"},{"location":"reference/models/#lunchable.models.BudgetObject","title":"BudgetObject","text":"

Bases: LunchableModel

Monthly Budget Per Category Object

https://lunchmoney.dev/#budget-object

Parameters:

Name Type Description Default category_name str

Name of the category

required category_id int | None

Unique identifier for category

None category_group_name str | None

Name of the category group, if applicable

None group_id int | None

Unique identifier for category group

None is_group bool | None

If true, this category is a group

None is_income bool

If true, this category is an income category (category properties are set in the app via the Categories page)

required exclude_from_budget bool

If true, this category is excluded from budget (category properties are set in the app via the Categories page)

required exclude_from_totals bool

If true, this category is excluded from totals (category properties are set in the app via the Categories page)

required data Dict[date, BudgetDataObject]

For each month with budget or category spending data, there is a data object with the key set to the month in format YYYY-MM-DD. For properties, see Data object below.

required config BudgetConfigObject | None

Object representing the category's budget suggestion configuration

None Source code in lunchable/models/budgets.py
class BudgetObject(LunchableModel):\n    \"\"\"\n    Monthly Budget Per Category Object\n\n    https://lunchmoney.dev/#budget-object\n    \"\"\"\n\n    _category_group_name_description = \"Name of the category group, if applicable\"\n    _is_income_description = \"\"\"\n    If true, this category is an income category (category properties\n    are set in the app via the Categories page)\n    \"\"\"\n    _exclude_from_budget_description = \"\"\"\n    If true, this category is excluded from budget (category\n    properties are set in the app via the Categories page)\n    \"\"\"\n    _exclude_from_totals_description = \"\"\"\n    If true, this category is excluded from totals (category\n    properties are set in the app via the Categories page)\n    \"\"\"\n    _data_description = \"\"\"\n    For each month with budget or category spending data, there is a data object with the key\n    set to the month in format YYYY-MM-DD. For properties, see Data object below.\n    \"\"\"\n    _config_description = \"\"\"\n    Object representing the category's budget suggestion configuration\n    \"\"\"\n\n    category_name: str = Field(description=\"Name of the category\")\n    category_id: Optional[int] = Field(\n        None, description=\"Unique identifier for category\"\n    )\n    category_group_name: Optional[str] = Field(\n        None, description=_category_group_name_description\n    )\n    group_id: Optional[int] = Field(\n        None, description=\"Unique identifier for category group\"\n    )\n    is_group: Optional[bool] = Field(\n        None, description=\"If true, this category is a group\"\n    )\n    is_income: bool = Field(description=_is_income_description)\n    exclude_from_budget: bool = Field(description=_exclude_from_budget_description)\n    exclude_from_totals: bool = Field(description=_exclude_from_totals_description)\n    data: Dict[datetime.date, BudgetDataObject] = Field(description=_data_description)\n    config: Optional[BudgetConfigObject] = Field(None, description=_config_description)\n
"},{"location":"reference/models/#lunchable.models.CategoriesObject","title":"CategoriesObject","text":"

Bases: LunchableModel

Lunch Money Spending Categories

https://lunchmoney.dev/#categories-object

Parameters:

Name Type Description Default id int

A unique identifier for the category.

required name str

The name of the category. Must be between 1 and 40 characters.

required description str | None

The description of the category. Must not exceed 140 characters.

None is_income bool

If true, the transactions in this category will be treated as income.

required exclude_from_budget bool

If true, the transactions in this category will be excluded from the budget.

required exclude_from_totals bool

If true, the transactions in this category will be excluded from totals.

required updated_at datetime | None

The date and time of when the category was last updated (in the ISO 8601 extended format).

None created_at datetime | None

The date and time of when the category was created (in the ISO 8601 extended format).

None is_group bool

If true, the category is a group that can be a parent to other categories.

required group_id int | None

The ID of a category group (or null if the category doesn't belong to a category group).

None children List[CategoryChild] | None

For category groups, this will populate with the categories nested within and include id, name, description and created_at fields.

None Source code in lunchable/models/categories.py
class CategoriesObject(LunchableModel):\n    \"\"\"\n    Lunch Money Spending Categories\n\n    https://lunchmoney.dev/#categories-object\n    \"\"\"\n\n    _name_description = \"The name of the category. Must be between 1 and 40 characters.\"\n    _description_description = (\n        \"The description of the category. Must not exceed 140 characters.\"\n    )\n    _is_income_description = (\n        \"If true, the transactions in this category will be treated as income.\"\n    )\n    _exclude_from_budget_description = \"\"\"\n    If true, the transactions in this category will be excluded from the budget.\n    \"\"\"\n    _exclude_from_totals_description = \"\"\"\n    If true, the transactions in this category will be excluded from totals.\n    \"\"\"\n    _updated_at_description = \"\"\"\n    The date and time of when the category was last updated (in the ISO 8601 extended format).\n    \"\"\"\n    _created_at_description = \"\"\"\n    The date and time of when the category was created (in the ISO 8601 extended format).\n    \"\"\"\n    _is_group_description = \"\"\"\n    If true, the category is a group that can be a parent to other categories.\n    \"\"\"\n    _group_id_description = \"\"\"\n    The ID of a category group (or null if the category doesn't belong to a category group).\n    \"\"\"\n    _children_description = (\n        \"For category groups, this will populate with the \"\n        \"categories nested within and include id, name, \"\n        \"description and created_at fields.\"\n    )\n\n    id: int = Field(description=\"A unique identifier for the category.\")\n    name: str = Field(min_length=1, max_length=40, description=_name_description)\n    description: Optional[str] = Field(\n        None, max_length=140, description=_description_description\n    )\n    is_income: bool = Field(description=_is_income_description)\n    exclude_from_budget: bool = Field(description=_exclude_from_budget_description)\n    exclude_from_totals: bool = Field(description=_exclude_from_totals_description)\n    updated_at: Optional[datetime.datetime] = Field(\n        None, description=_updated_at_description\n    )\n    created_at: Optional[datetime.datetime] = Field(\n        None, description=_created_at_description\n    )\n    is_group: bool = Field(description=_is_group_description)\n    group_id: Optional[int] = Field(None, description=_group_id_description)\n    children: Optional[List[CategoryChild]] = Field(\n        None, description=_children_description\n    )\n
"},{"location":"reference/models/#lunchable.models.CryptoObject","title":"CryptoObject","text":"

Bases: LunchableModel

Crypto Asset Object

https://lunchmoney.dev/#crypto-object

Parameters:

Name Type Description Default id int

Unique identifier for a manual crypto account (no ID for synced accounts)

required zabo_account_id int | None

Unique identifier for a synced crypto account (no ID for manual accounts, multiple currencies may have the same zabo_account_id)

None source str

synced (this account is synced via a wallet, exchange, etc.) or manual (this account balance is managed manually)

required name str

Name of the crypto asset

required display_name str | None

Display name of the crypto asset (as set by user)

None balance float

Current balance

required balance_as_of datetime | None

Date/time the balance was last updated in ISO 8601 extended format

None currency str | None

Abbreviation for the cryptocurrency

None status str | None

The current status of the crypto account. Either active or in error.

None institution_name str | None

Name of provider holding the asset

None created_at datetime

Date/time the asset was created in ISO 8601 extended format

required Source code in lunchable/models/crypto.py
class CryptoObject(LunchableModel):\n    \"\"\"\n    Crypto Asset Object\n\n    https://lunchmoney.dev/#crypto-object\n    \"\"\"\n\n    _id_description = (\n        \"Unique identifier for a manual crypto account (no ID for synced accounts)\"\n    )\n    _zabo_account_id_description = \"\"\"\n    Unique identifier for a synced crypto account (no ID for manual accounts,\n    multiple currencies may have the same zabo_account_id)\n    \"\"\"\n    _source_description = \"\"\"\n    `synced` (this account is synced via a wallet, exchange, etc.) or `manual` (this account\n    balance is managed manually)\n    \"\"\"\n    _display_name_description = \"Display name of the crypto asset (as set by user)\"\n    _balance_as_of_description = \"\"\"\n    Date/time the balance was last updated in ISO 8601 extended format\n    \"\"\"\n    _status_description = (\n        \"The current status of the crypto account. Either active or in error.\"\n    )\n    _created_at_description = (\n        \"Date/time the asset was created in ISO 8601 extended format\"\n    )\n\n    id: int = Field(description=_id_description)\n    zabo_account_id: Optional[int] = Field(\n        None, description=_zabo_account_id_description\n    )\n    source: str = Field(description=_source_description)\n    name: str = Field(description=\"Name of the crypto asset\")\n    display_name: Optional[str] = Field(None, description=_display_name_description)\n    balance: float = Field(description=\"Current balance\")\n    balance_as_of: Optional[datetime.datetime] = Field(\n        None, description=_balance_as_of_description\n    )\n    currency: Optional[str] = Field(\n        None, description=\"Abbreviation for the cryptocurrency\"\n    )\n    status: Optional[str] = Field(None, description=_status_description)\n    institution_name: Optional[str] = Field(\n        default=None, description=\"Name of provider holding the asset\"\n    )\n    created_at: datetime.datetime = Field(description=_created_at_description)\n
"},{"location":"reference/models/#lunchable.models.LunchableModel","title":"LunchableModel","text":"

Bases: BaseModel

Hashable Pydantic Model

Source code in lunchable/models/_base.py
class LunchableModel(BaseModel):\n    \"\"\"\n    Hashable Pydantic Model\n    \"\"\"\n\n    def __hash__(self) -> int:\n        \"\"\"\n        Hash Method for Pydantic BaseModels\n        \"\"\"\n        return hash((type(self), *tuple(self.__dict__.values())))\n
"},{"location":"reference/models/#lunchable.models.LunchableModel.__hash__","title":"__hash__()","text":"

Hash Method for Pydantic BaseModels

Source code in lunchable/models/_base.py
def __hash__(self) -> int:\n    \"\"\"\n    Hash Method for Pydantic BaseModels\n    \"\"\"\n    return hash((type(self), *tuple(self.__dict__.values())))\n
"},{"location":"reference/models/#lunchable.models.PlaidAccountObject","title":"PlaidAccountObject","text":"

Bases: LunchableModel

Assets synced from Plaid

Similar to AssetObjects, these accounts are linked to remote sources in Plaid.

https://lunchmoney.dev/#plaid-accounts-object

Parameters:

Name Type Description Default id int

Unique identifier of Plaid account

required date_linked date

Date account was first linked in ISO 8601 extended format

required name str

Name of the account. Can be overridden by the user. Field is originally set by Plaid\")

required type str

Primary type of account. Typically one of: [credit, depository, brokerage, cash, loan, investment]. This field is set by Plaid and cannot be altered.

required subtype str

Optional subtype name of account. This field is set by Plaid and cannot be altered

required mask str | None

Mask (last 3 to 4 digits of account) of account. This field is set by Plaid and cannot be altered

None institution_name str

Name of institution associated with account. This field is set by Plaid and cannot be altered

required status str

Denotes the current status of the account within Lunch Money. Must be one of: active (Account is active and in good state), inactive (Account marked inactive from user. No transactions fetched or balance update for this account), relink (Account needs to be relinked with Plaid), syncing (Account is awaiting first import of transactions), error (Account is in error with Plaid), not found (Account is in error with Plaid), not supported (Account is in error with Plaid)

required last_import datetime | None

Date of last imported transaction in ISO 8601 extended format (not necessarily date of last attempted import)

None balance float | None

Current balance of the account in numeric format to 4 decimal places. This field is set by Plaid and cannot be altered

None currency str

Currency of account balance in ISO 4217 format. This field is set by Plaid and cannot be altered

required balance_last_update datetime

Date balance was last updated in ISO 8601 extended format. This field is set by Plaid and cannot be altered

required limit int | None

Optional credit limit of the account. This field is set by Plaid and cannot be altered

None Source code in lunchable/models/plaid_accounts.py
class PlaidAccountObject(LunchableModel):\n    \"\"\"\n    Assets synced from Plaid\n\n    Similar to AssetObjects, these accounts are linked to remote sources in Plaid.\n\n    https://lunchmoney.dev/#plaid-accounts-object\n    \"\"\"\n\n    _date_linked_description = (\n        \"Date account was first linked in ISO 8601 extended format\"\n    )\n    _name_description = \"\"\"\n    Name of the account. Can be overridden by the user. Field is originally set by Plaid\")\n    \"\"\"\n    _type_description = \"\"\"\n    Primary type of account. Typically one of: [credit, depository, brokerage, cash,\n    loan, investment]. This field is set by Plaid and cannot be altered.\n    \"\"\"\n    _subtype_description = \"\"\"\n    Optional subtype name of account. This field is set by Plaid and cannot be altered\n    \"\"\"\n    _mask_description = \"\"\"\n    Mask (last 3 to 4 digits of account) of account. This field is set by\n    Plaid and cannot be altered\n    \"\"\"\n    _institution_name_description = \"\"\"\n    Name of institution associated with account. This field is set by\n    Plaid and cannot be altered\n    \"\"\"\n    _status_description = \"\"\"\n    Denotes the current status of the account within Lunch Money. Must be one of:\n    active (Account is active and in good state),\n    inactive (Account marked inactive from user. No transactions fetched or\n    balance update for this account),\n    relink (Account needs to be relinked with Plaid),\n    syncing (Account is awaiting first import of transactions),\n    error (Account is in error with Plaid),\n    not found (Account is in error with Plaid),\n    not supported (Account is in error with Plaid)\n    \"\"\"\n    _last_import_description = \"\"\"\n    Date of last imported transaction in ISO 8601 extended format (not necessarily\n    date of last attempted import)\n    \"\"\"\n    _balance_description = \"\"\"\n    Current balance of the account in numeric format to 4 decimal places. This field is\n    set by Plaid and cannot be altered\n    \"\"\"\n    _currency_description = \"\"\"\n    Currency of account balance in ISO 4217 format. This field is set by Plaid\n    and cannot be altered\n    \"\"\"\n    _balance_last_update_description = \"\"\"\n    Date balance was last updated in ISO 8601 extended format. This field is set\n    by Plaid and cannot be altered\n    \"\"\"\n    _limit_description = \"\"\"\n    Optional credit limit of the account. This field is set by Plaid and cannot be altered\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier of Plaid account\")\n    date_linked: datetime.date = Field(description=_date_linked_description)\n    name: str = Field(description=_name_description)\n    type: str = Field(description=_type_description)\n    subtype: str = Field(description=_subtype_description)\n    mask: Optional[str] = Field(None, description=_mask_description)\n    institution_name: str = Field(description=_institution_name_description)\n    status: str = Field(description=_status_description)\n    last_import: Optional[datetime.datetime] = Field(\n        None, description=_last_import_description\n    )\n    balance: Optional[float] = Field(None, description=_balance_description)\n    currency: str = Field(description=_currency_description)\n    balance_last_update: datetime.datetime = Field(\n        description=_balance_last_update_description\n    )\n    limit: Optional[int] = Field(None, description=_limit_description)\n
"},{"location":"reference/models/#lunchable.models.RecurringExpensesObject","title":"RecurringExpensesObject","text":"

Bases: LunchableModel

Recurring Expenses Object

https://lunchmoney.dev/#recurring-expenses-object

Parameters:

Name Type Description Default id int

Unique identifier for recurring expense

required start_date date | None

Denotes when recurring expense starts occurring in ISO 8601 format. If null, then this recurring expense will show up for all time before end_date

None end_date date | None

Denotes when recurring expense stops occurring in ISO 8601 format. If null, then this recurring expense has no set end date and will show up for all months after start_date

None cadence str

One of: [monthly, twice a month, once a week, every 3 months, every 4 months, twice a year, yearly]

required payee str

Payee of the recurring expense

required amount float

Amount of the recurring expense in numeric format to 4 decimal places

required currency str

Three-letter lowercase currency code for the recurring expense in ISO 4217 format

required description str | None

If any, represents the user-entered description of the recurring expense

None billing_date date

Expected billing date for this recurring expense for this month in ISO 8601 format

required type str

\" This can be one of two values: cleared (The recurring expense has been reviewed by the user), suggested (The recurring expense is suggested by the system; the user has yet to review/clear it)

required original_name str | None

If any, represents the original name of the recurring expense as denoted by the transaction that triggered its creation

None source str

This can be one of three values: manual (User created this recurring expense manually from the Recurring Expenses page), transaction (User created this by converting a transaction from the Transactions page), system (Recurring expense was created by the system on transaction import). Some older recurring expenses may not have a source.

required plaid_account_id int | None

If any, denotes the plaid account associated with the creation of this \" recurring expense (see Plaid Accounts)\"

None asset_id int | None

If any, denotes the manually-managed account (i.e. asset) associated with the creation of this recurring expense (see Assets)

None transaction_id int | None

If any, denotes the unique identifier for the associated transaction matching this recurring expense for the current time period

None category_id int | None

If any, denotes the unique identifier for the associated category to this recurring expense

None Source code in lunchable/models/recurring_expenses.py
class RecurringExpensesObject(LunchableModel):\n    \"\"\"\n    Recurring Expenses Object\n\n    https://lunchmoney.dev/#recurring-expenses-object\n    \"\"\"\n\n    _id_description = \"Unique identifier for recurring expense\"\n    _start_date_description = \"\"\"\n    Denotes when recurring expense starts occurring in ISO 8601 format.\n    If null, then this recurring expense will show up for all time\n    before end_date\n    \"\"\"\n    _end_date_description = \"\"\"\n    Denotes when recurring expense stops occurring in ISO 8601 format.\n    If null, then this recurring expense has no set end date and will\n    show up for all months after start_date\n    \"\"\"\n    _cadence_description = \"\"\"\n    One of: [monthly, twice a month, once a week, every 3 months, every 4 months,\n    twice a year, yearly]\n    \"\"\"\n    _amount_description = (\n        \"Amount of the recurring expense in numeric format to 4 decimal places\"\n    )\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code for the recurring expense in ISO 4217 format\n    \"\"\"\n    _description_description = \"\"\"\n    If any, represents the user-entered description of the recurring expense\n    \"\"\"\n    _billing_date_description = \"\"\"\n    Expected billing date for this recurring expense for this month in ISO 8601 format\n    \"\"\"\n    _type_description = \"\"\"\"\n    This can be one of two values: cleared (The recurring expense has been reviewed\n    by the user), suggested (The recurring expense is suggested by the system;\n    the user has yet to review/clear it)\n    \"\"\"\n    _original_name_description = \"\"\"\n    If any, represents the original name of the recurring expense as\n    denoted by the transaction that triggered its creation\n    \"\"\"\n    _source_description = \"\"\"\n    This can be one of three values: manual (User created this recurring expense\n    manually from the Recurring Expenses page), transaction (User created this by\n    converting a transaction from the Transactions page), system (Recurring expense\n    was created by the system on transaction import). Some older recurring expenses\n    may not have a source.\n    \"\"\"\n    _plaid_account_id_description = \"\"\"\n    If any, denotes the plaid account associated with the creation of this \"\n    recurring expense (see Plaid Accounts)\"\n    \"\"\"\n    _asset_id_description = \"\"\"\n    If any, denotes the manually-managed account (i.e. asset) associated with the\n    creation of this recurring expense (see Assets)\n    \"\"\"\n    _transaction_id_description = \"\"\"\n    If any, denotes the unique identifier for the associated transaction matching\n    this recurring expense for the current time period\n    \"\"\"\n    _category_id_description = \"\"\"\n    If any, denotes the unique identifier for the associated category to this recurring expense\n    \"\"\"\n\n    id: int = Field(description=_id_description)\n    start_date: Optional[datetime.date] = Field(\n        None, description=_start_date_description\n    )\n    end_date: Optional[datetime.date] = Field(None, description=_end_date_description)\n    cadence: str = Field(description=_cadence_description)\n    payee: str = Field(description=\"Payee of the recurring expense\")\n    amount: float = Field(description=_amount_description)\n    currency: str = Field(max_length=3, description=_currency_description)\n    description: Optional[str] = Field(None, description=_description_description)\n    billing_date: datetime.date = Field(description=_billing_date_description)\n    type: str = Field(description=_type_description)\n    original_name: Optional[str] = Field(None, description=_original_name_description)\n    source: str = Field(description=_source_description)\n    plaid_account_id: Optional[int] = Field(\n        None, description=_plaid_account_id_description\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    transaction_id: Optional[int] = Field(None, description=_transaction_id_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n
"},{"location":"reference/models/#lunchable.models.TagsObject","title":"TagsObject","text":"

Bases: LunchableModel

Lunchmoney Tags object

https://lunchmoney.dev/#tags-object

Parameters:

Name Type Description Default id int

Unique identifier for tag

required name str

User-defined name of tag

required description str | None

User-defined description of tag

None Source code in lunchable/models/tags.py
class TagsObject(LunchableModel):\n    \"\"\"\n    Lunchmoney Tags object\n\n    https://lunchmoney.dev/#tags-object\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier for tag\")\n    name: str = Field(description=\"User-defined name of tag\", min_length=1)\n    description: Optional[str] = Field(\n        None, description=\"User-defined description of tag\"\n    )\n
"},{"location":"reference/models/#lunchable.models.TransactionBaseObject","title":"TransactionBaseObject","text":"

Bases: LunchableModel

Base Model For All Transactions to Inherit From

Source code in lunchable/models/transactions.py
class TransactionBaseObject(LunchableModel):\n    \"\"\"\n    Base Model For All Transactions to Inherit From\n    \"\"\"\n\n    pass\n
"},{"location":"reference/models/#lunchable.models.TransactionInsertObject","title":"TransactionInsertObject","text":"

Bases: TransactionBaseObject

Object For Creating New Transactions

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required amount float

Numeric value of amount. i.e. $4.25 should be denoted as 4.25.

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None currency str | None

Three-letter lowercase currency code in ISO 4217 format. The code sent must exist in our database. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account.

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id.

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionInsertObject(TransactionBaseObject):\n    \"\"\"\n    Object For Creating New Transactions\n\n    https://lunchmoney.dev/#insert-transactions\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _amount_description = \"\"\"\n    Numeric value of amount. i.e. $4.25 should be denoted as 4.25.\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated with\n    the same account and must not be a category group.\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code in ISO 4217 format. The code sent must exist\n    in our database. Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account.\n    \"\"\"\n    _recurring_id = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must be associated\n    with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. If recurring_id is provided, the status will\n    automatically be set to recurring or recurring_suggested depending on the type of\n    recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id.\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found, an error\n    will be thrown. Passing in a string will attempt to match by string. If no matching tag\n    name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: datetime.date = Field(description=_date_description)\n    amount: float = Field(description=_amount_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    currency: Optional[str] = Field(\n        None, description=_currency_description, max_length=3\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(\n        None, description=_external_id_description, max_length=75\n    )\n    tags: Optional[List[Union[str, int]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/models/#lunchable.models.TransactionInsertObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/models/#lunchable.models.TransactionObject","title":"TransactionObject","text":"

Bases: TransactionBaseObject

Universal Lunch Money Transaction Object

https://lunchmoney.dev/#transaction-object

Parameters:

Name Type Description Default id int

Unique identifier for transaction

required date date

Date of transaction in ISO 8601 format

required payee str | None

Name of payee If recurring_id is not null, this field will show the payee of associated recurring expense instead of the original transaction payee

None amount float

Amount of the transaction in numeric format to 4 decimal places

required currency str | None

Three-letter lowercase currency code of the transaction in ISO 4217 format

None notes str | None

User-entered transaction notes If recurring_id is not null, this field will be description of associated recurring expense

None category_id int | None

Unique identifier of associated category (see Categories)

None asset_id int | None

Unique identifier of associated manually-managed account (see Assets) Note: plaid_account_id and asset_id cannot both exist for a transaction

None plaid_account_id int | None

Unique identifier of associated Plaid account (see Plaid Accounts) Note: plaid_account_id and asset_id cannot both exist for a transaction

None status str | None

One of the following: cleared: User has reviewed the transaction | uncleared: User has not yet reviewed the transaction | recurring: Transaction is linked to a recurring expense | recurring_suggested: Transaction is listed as a suggested transaction for an existing recurring expense | pending: Imported transaction is marked as pending. This should be a temporary state. User intervention is required to change this to recurring.

None parent_id int | None

Exists if this is a split transaction. Denotes the transaction ID of the original transaction. Note that the parent transaction is not returned in this call.

None is_group bool | None

True if this transaction represents a group of transactions. If so, amount and currency represent the totalled amount of transactions bearing this transaction's id as their group_id. Amount is calculated based on the user's primary currency.

None group_id int | None

Exists if this transaction is part of a group. Denotes the parent's transaction ID

None tags List[TagsObject] | None

Array of Tag objects

None external_id str | None

User-defined external ID for any manually-entered or imported transaction. External ID cannot be accessed or changed for Plaid-imported transactions. External ID must be unique by asset_id. Max 75 characters.

None original_name str | None

The transactions original name before any payee name updates. For synced transactions, this is the raw original payee name from your bank.

None type str | None

(for synced investment transactions only) The transaction type as set by Plaid for investment transactions. Possible values include: buy, sell, cash, transfer and more

None subtype str | None

(for synced investment transactions only) The transaction type as set by Plaid for investment transactions. Possible values include: management fee, withdrawal, dividend, deposit and more

None fees str | None

(for synced investment transactions only) The fees as set by Plaid for investment transactions.

None price str | None

(for synced investment transactions only) The price as set by Plaid for investment transactions.

None quantity str | None

(for synced investment transactions only) The quantity as set by Plaid for investment transactions.

None Source code in lunchable/models/transactions.py
class TransactionObject(TransactionBaseObject):\n    \"\"\"\n    Universal Lunch Money Transaction Object\n\n    https://lunchmoney.dev/#transaction-object\n    \"\"\"\n\n    _amount_description = \"\"\"\n    Amount of the transaction in numeric format to 4 decimal places\n    \"\"\"\n    _payee_description = \"\"\"\n    Name of payee If recurring_id is not null, this field will show the payee\n    of associated recurring expense instead of the original transaction payee\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code of the transaction in ISO 4217 format\n    \"\"\"\n    _notes_description = \"\"\"\n    User-entered transaction notes If recurring_id is not null, this field will\n    be description of associated recurring expense\n    \"\"\"\n    _category_description = \"\"\"\n    Unique identifier of associated category (see Categories)\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier of associated manually-managed account (see Assets)\n    Note: plaid_account_id and asset_id cannot both exist for a transaction\n    \"\"\"\n    _plaid_account_id_description = \"\"\"\n    Unique identifier of associated Plaid account (see Plaid Accounts) Note:\n    plaid_account_id and asset_id cannot both exist for a transaction\n    \"\"\"\n    _status_description = \"\"\"\n    One of the following: cleared: User has reviewed the transaction | uncleared:\n    User has not yet reviewed the transaction | recurring: Transaction is linked\n    to a recurring expense | recurring_suggested: Transaction is listed as a\n    suggested transaction for an existing recurring expense | pending: Imported\n    transaction is marked as pending. This should be a temporary state. User intervention\n    is required to change this to recurring.\n    \"\"\"\n    _parent_id_description = \"\"\"\n    Exists if this is a split transaction. Denotes the transaction ID of the original\n    transaction. Note that the parent transaction is not returned in this call.\n    \"\"\"\n    _is_group_description = \"\"\"\n    True if this transaction represents a group of transactions. If so, amount\n    and currency represent the totalled amount of transactions bearing this\n    transaction's id as their group_id. Amount is calculated based on the\n    user's primary currency.\n    \"\"\"\n    _group_id_description = \"\"\"\n    Exists if this transaction is part of a group. Denotes the parent's transaction ID\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for any manually-entered or imported transaction.\n    External ID cannot be accessed or changed for Plaid-imported transactions.\n    External ID must be unique by asset_id. Max 75 characters.\n    \"\"\"\n    _original_name_description = \"\"\"\n    The transactions original name before any payee name updates. For synced transactions,\n    this is the raw original payee name from your bank.\n    \"\"\"\n    _type_description = \"\"\"\n    (for synced investment transactions only) The transaction type as set by\n    Plaid for investment transactions. Possible values include: buy, sell, cash,\n    transfer and more\n    \"\"\"\n    _subtype_description = \"\"\"\n    (for synced investment transactions only) The transaction type as set by Plaid\n    for investment transactions. Possible values include: management fee, withdrawal,\n    dividend, deposit and more\n    \"\"\"\n    _fees_description = \"\"\"\n    (for synced investment transactions only) The fees as set by Plaid for investment\n    transactions.\n    \"\"\"\n    _price_description = \"\"\"\n    (for synced investment transactions only) The price as set by Plaid for investment\n    transactions.\n    \"\"\"\n    _quantity_description = \"\"\"\n    (for synced investment transactions only) The quantity as set by Plaid for investment\n    transactions.\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier for transaction\")\n    date: datetime.date = Field(description=\"Date of transaction in ISO 8601 format\")\n    payee: Optional[str] = Field(None, description=_payee_description)\n    amount: float = Field(description=_amount_description)\n    currency: Optional[str] = Field(\n        None, max_length=3, description=_currency_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    category_id: Optional[int] = Field(None, description=_category_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    plaid_account_id: Optional[int] = Field(\n        None, description=_plaid_account_id_description\n    )\n    status: Optional[str] = Field(None, description=_status_description)\n    parent_id: Optional[int] = Field(None, description=_parent_id_description)\n    is_group: Optional[bool] = Field(None, description=_is_group_description)\n    group_id: Optional[int] = Field(None, description=_group_id_description)\n    tags: Optional[List[TagsObject]] = Field(None, description=\"Array of Tag objects\")\n    external_id: Optional[str] = Field(\n        None, max_length=75, description=_external_id_description\n    )\n    original_name: Optional[str] = Field(None, description=_original_name_description)\n    type: Optional[str] = Field(None, description=_type_description)\n    subtype: Optional[str] = Field(None, description=_subtype_description)\n    fees: Optional[str] = Field(None, description=_fees_description)\n    price: Optional[str] = Field(None, description=_price_description)\n    quantity: Optional[str] = Field(None, description=_quantity_description)\n\n    def get_update_object(self) -> TransactionUpdateObject:\n        \"\"\"\n        Return a TransactionUpdateObject\n\n        Return a TransactionUpdateObject to update an expense. Simply\n        change one of the properties and perform an `update_transaction` with\n        your Lunchable object.\n\n        Returns\n        -------\n        TransactionUpdateObject\n        \"\"\"\n        update_dict = self.model_dump()\n        try:\n            TransactionUpdateObject.StatusEnum(self.status)\n        except ValueError:\n            update_dict[\"status\"] = None\n        update_object = TransactionUpdateObject.model_validate(update_dict)\n        if update_object.tags is not None:\n            tags = [] if self.tags is None else self.tags\n            update_object.tags = [tag.name for tag in tags]\n        return update_object\n\n    def get_insert_object(self) -> TransactionInsertObject:\n        \"\"\"\n        Return a TransactionInsertObject\n\n        Return a TransactionInsertObject to update an expense. Simply\n        change some of the properties and perform an `insert_transactions` with\n        your Lunchable object.\n\n        Returns\n        -------\n        TransactionInsertObject\n        \"\"\"\n        insert_dict = self.model_dump()\n        try:\n            TransactionInsertObject.StatusEnum(self.status)\n        except ValueError:\n            insert_dict[\"status\"] = None\n        insert_object = TransactionInsertObject.model_validate(insert_dict)\n        if insert_object.tags is not None:\n            tags = [] if self.tags is None else self.tags\n            insert_object.tags = [tag.name for tag in tags]\n        return insert_object\n
"},{"location":"reference/models/#lunchable.models.TransactionObject.get_insert_object","title":"get_insert_object()","text":"

Return a TransactionInsertObject

Return a TransactionInsertObject to update an expense. Simply change some of the properties and perform an insert_transactions with your Lunchable object.

Returns:

Type Description TransactionInsertObject Source code in lunchable/models/transactions.py
def get_insert_object(self) -> TransactionInsertObject:\n    \"\"\"\n    Return a TransactionInsertObject\n\n    Return a TransactionInsertObject to update an expense. Simply\n    change some of the properties and perform an `insert_transactions` with\n    your Lunchable object.\n\n    Returns\n    -------\n    TransactionInsertObject\n    \"\"\"\n    insert_dict = self.model_dump()\n    try:\n        TransactionInsertObject.StatusEnum(self.status)\n    except ValueError:\n        insert_dict[\"status\"] = None\n    insert_object = TransactionInsertObject.model_validate(insert_dict)\n    if insert_object.tags is not None:\n        tags = [] if self.tags is None else self.tags\n        insert_object.tags = [tag.name for tag in tags]\n    return insert_object\n
"},{"location":"reference/models/#lunchable.models.TransactionObject.get_update_object","title":"get_update_object()","text":"

Return a TransactionUpdateObject

Return a TransactionUpdateObject to update an expense. Simply change one of the properties and perform an update_transaction with your Lunchable object.

Returns:

Type Description TransactionUpdateObject Source code in lunchable/models/transactions.py
def get_update_object(self) -> TransactionUpdateObject:\n    \"\"\"\n    Return a TransactionUpdateObject\n\n    Return a TransactionUpdateObject to update an expense. Simply\n    change one of the properties and perform an `update_transaction` with\n    your Lunchable object.\n\n    Returns\n    -------\n    TransactionUpdateObject\n    \"\"\"\n    update_dict = self.model_dump()\n    try:\n        TransactionUpdateObject.StatusEnum(self.status)\n    except ValueError:\n        update_dict[\"status\"] = None\n    update_object = TransactionUpdateObject.model_validate(update_dict)\n    if update_object.tags is not None:\n        tags = [] if self.tags is None else self.tags\n        update_object.tags = [tag.name for tag in tags]\n    return update_object\n
"},{"location":"reference/models/#lunchable.models.TransactionSplitObject","title":"TransactionSplitObject","text":"

Bases: TransactionBaseObject

Object for Splitting Transactions

https://lunchmoney.dev/#split-object

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account.

None notes str | None

Transaction Split Notes.

None amount float

Individual amount of split. Currency will inherit from parent transaction. All amounts must sum up to parent transaction amount.

required Source code in lunchable/models/transactions.py
class TransactionSplitObject(TransactionBaseObject):\n    \"\"\"\n    Object for Splitting Transactions\n\n    https://lunchmoney.dev/#split-object\n    \"\"\"\n\n    _date_description = \"Must be in ISO 8601 format (YYYY-MM-DD).\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account.\n    \"\"\"\n    _notes_description = \"Transaction Split Notes.\"\n    _amount_description = \"\"\"\n    Individual amount of split. Currency will inherit from parent transaction. All\n    amounts must sum up to parent transaction amount.\n    \"\"\"\n\n    date: datetime.date = Field(description=_date_description)\n    category_id: Optional[int] = Field(\n        default=None, description=_category_id_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    amount: float = Field(description=_amount_description)\n
"},{"location":"reference/models/#lunchable.models.TransactionUpdateObject","title":"TransactionUpdateObject","text":"

Bases: TransactionBaseObject

Object For Updating Existing Transactions

https://lunchmoney.dev/#update-transaction

Parameters:

Name Type Description Default date date | None

Must be in ISO 8601 format (YYYY-MM-DD).

None category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None amount float | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None currency str | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. Defaults to uncleared If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionUpdateObject(TransactionBaseObject):\n    \"\"\"\n    Object For Updating Existing Transactions\n\n    https://lunchmoney.dev/#update-transaction\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account and must not be a category group.\n    \"\"\"\n    _amount_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id\n    \"\"\"\n    _currency_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id.\n    Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _recurring_id_description = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must\n    be associated with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. Defaults to uncleared If recurring_id is\n    provided, the status will automatically be set to recurring or recurring_suggested\n    depending on the type of recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found,\n    an error will be thrown. Passing in a string will attempt to match by string.\n    If no matching tag name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: Optional[datetime.date] = Field(None, description=_date_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    amount: Optional[float] = Field(None, description=_amount_description)\n    currency: Optional[str] = Field(None, description=_currency_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id_description)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(None, description=_external_id_description)\n    tags: Optional[List[Union[int, str]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/models/#lunchable.models.TransactionUpdateObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/models/#lunchable.models.UserObject","title":"UserObject","text":"

Bases: LunchableModel

The LunchMoney User object

https://lunchmoney.dev/#user-object

Parameters:

Name Type Description Default user_id int

Unique identifier for user

required user_name str

User's' name

required user_email str

User's' Email

required account_id int

Unique identifier for the associated budgeting account

required budget_name str

Name of the associated budgeting account

required api_key_label str | None

User-defined label of the developer API key used. Returns null if nothing has been set.

None Source code in lunchable/models/user.py
class UserObject(LunchableModel):\n    \"\"\"\n    The LunchMoney `User` object\n\n    https://lunchmoney.dev/#user-object\n    \"\"\"\n\n    user_id: int = Field(description=\"Unique identifier for user\")\n    user_name: str = Field(description=\"User's' name\")\n    user_email: str = Field(description=\"User's' Email\")\n    account_id: int = Field(\n        description=\"Unique identifier for the associated budgeting account\"\n    )\n    budget_name: str = Field(description=\"Name of the associated budgeting account\")\n    api_key_label: Optional[str] = Field(\n        None,\n        description=\"User-defined label of the developer API key used. \"\n        \"Returns null if nothing has been set.\",\n    )\n
"},{"location":"reference/models/_base/","title":"_base","text":"

Base Pydantic Object for Containers

"},{"location":"reference/models/_base/#lunchable.models._base.LunchableModel","title":"LunchableModel","text":"

Bases: BaseModel

Hashable Pydantic Model

Source code in lunchable/models/_base.py
class LunchableModel(BaseModel):\n    \"\"\"\n    Hashable Pydantic Model\n    \"\"\"\n\n    def __hash__(self) -> int:\n        \"\"\"\n        Hash Method for Pydantic BaseModels\n        \"\"\"\n        return hash((type(self), *tuple(self.__dict__.values())))\n
"},{"location":"reference/models/_base/#lunchable.models._base.LunchableModel.__hash__","title":"__hash__()","text":"

Hash Method for Pydantic BaseModels

Source code in lunchable/models/_base.py
def __hash__(self) -> int:\n    \"\"\"\n    Hash Method for Pydantic BaseModels\n    \"\"\"\n    return hash((type(self), *tuple(self.__dict__.values())))\n
"},{"location":"reference/models/_core/","title":"_core","text":"

Lunchmoney SDK Core

"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient","title":"LunchMoneyAPIClient","text":"

Core API Client Class

Source code in lunchable/models/_core.py
class LunchMoneyAPIClient:\n    \"\"\"\n    Core API Client Class\n    \"\"\"\n\n    class Methods:\n        \"\"\"\n        HTTP Request Method Enumerations: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE\n        \"\"\"\n\n        # This Helper Namespace Organizes and Tracks HTTP Requests by Method\n        GET = \"GET\"\n        OPTIONS = \"OPTIONS\"\n        HEAD = \"HEAD\"\n        POST = \"POST\"\n        PUT = \"PUT\"\n        PATCH = \"PATCH\"\n        DELETE = \"DELETE\"\n\n    def __init__(self, access_token: str | None = None) -> None:\n        \"\"\"\n        Initialize a Lunch Money object with an Access Token.\n\n        Tries to inherit from the Environment if one isn't provided\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n        \"\"\"\n        self.access_token = APIConfig.get_access_token(access_token=access_token)\n\n    def __repr__(self) -> str:\n        \"\"\"\n        String Representation\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return \"<LunchMoney: httpx.Client>\"\n\n    @cached_property\n    def session(self) -> httpx.Client:\n        \"\"\"\n        Lunch Money HTTPX Client\n\n        Returns\n        -------\n        httpx.Client\n        \"\"\"\n        return LunchMoneyClient(access_token=self.access_token)\n\n    @cached_property\n    def async_session(self) -> httpx.AsyncClient:\n        \"\"\"\n        Lunch Money HTTPX Async Client\n\n        Returns\n        -------\n        httpx.AsyncClient\n        \"\"\"\n        return LunchMoneyAsyncClient(access_token=self.access_token)\n\n    def request(\n        self,\n        method: str,\n        url: Union[httpx.URL, str],\n        *,\n        content: Optional[\n            Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\n        ] = None,\n        data: Optional[Mapping[str, Any]] = None,\n        json: Optional[Any] = None,\n        params: Optional[Mapping[str, Any]] = None,\n        **kwargs: Any,\n    ) -> httpx.Response:\n        \"\"\"\n        Make an HTTP request\n\n        This is a simple method :class:`.LunchMoney` exposes to make HTTP requests. It\n        has the benefit of using an existing `httpx.Client` as well as as out of the box\n        auth headers that are used to connect to the Lunch Money Developer API.\n\n        Parameters\n        ----------\n        method: str\n            requests method: GET, OPTIONS, HEAD, POST, PUT,\n            PATCH, or DELETE\n        url: Union[httpx.URL, str]\n            URL for the new Request object.\n        content: Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]\n            Content to send in the body of the Request.\n        data: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples, bytes, or file-like object to send\n            in the body of the Request.\n        json: Optional[Any]\n            A JSON serializable Python object to send in the body of the Request.\n        params: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples or bytes to send in the query\n            string for the Request.\n        **kwargs: Any\n            Additional arguments to send to the request method.\n\n        Returns\n        -------\n        httpx.Response\n\n        Examples\n        --------\n        A recent use of this method was to delete a Tag (which isn't available via the\n        Developer API yet)\n\n        ```python\n        import lunchable\n\n        lunch = lunchable.LunchMoney()\n\n        # Get All the Tags\n        all_tags = lunch.get_tags()\n        # Get All The Null Tags (a list of 1 or zero)\n        null_tags = [tag for tag in all_tags if tag.name in [None, \"\"]]\n\n        # Create a Cookie dictionary from a browser session\n        cookies = {\"cookie_keys\": \"cookie_values\"}\n        del lunch.session.headers[\"authorization\"]\n\n        for null_tag in null_tags:\n            # use the httpx.client embedded in the class to make a request with cookies\n            response = lunch.request(\n                method=lunch.Methods.DELETE,\n                url=f\"https://api.lunchmoney.app/tags/{null_tag.id}\",\n                cookies=cookies\n            )\n            # raise an error for 4XX responses\n            response.raise_for_status()\n        ```\n        \"\"\"\n        response = self.session.request(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            json=json,\n            params=params,\n            **kwargs,\n        )\n        return response\n\n    async def arequest(\n        self,\n        method: str,\n        url: Union[httpx.URL, str],\n        *,\n        content: Optional[\n            Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\n        ] = None,\n        data: Optional[Mapping[str, Any]] = None,\n        json: Optional[Any] = None,\n        params: Optional[Mapping[str, Any]] = None,\n        **kwargs: Any,\n    ) -> httpx.Response:\n        \"\"\"\n        Make an async HTTP request\n\n        This is a simple method :class:`.LunchMoney` exposes to make HTTP requests. It\n        has the benefit of using an existing `httpx.Client` as well as as out of the box\n        auth headers that are used to connect to the Lunch Money Developer API.\n\n        Parameters\n        ----------\n        method: str\n            requests method: GET, OPTIONS, HEAD, POST, PUT,\n            PATCH, or DELETE\n        url: Union[httpx.URL, str]\n            URL for the new Request object.\n        content: Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]\n            Content to send in the body of the Request.\n        data: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples, bytes, or file-like object to send\n            in the body of the Request.\n        json: Optional[Any]\n            A JSON serializable Python object to send in the body of the Request.\n        params: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples or bytes to send in the query\n            string for the Request.\n        **kwargs: Any\n            Additional arguments to send to the request method.\n\n        Returns\n        -------\n        httpx.Response\n        \"\"\"\n        response = self.async_session.request(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            json=json,\n            params=params,\n            **kwargs,\n        )\n        return await response\n\n    @classmethod\n    def process_response(cls, response: httpx.Response) -> Any:\n        \"\"\"\n        Process a Lunch Money response and raise any errors\n\n        This includes 200 responses that are actually errors\n\n        Parameters\n        ----------\n        response: httpx.Response\n            An HTTPX Response Object\n        \"\"\"\n        try:\n            response.raise_for_status()\n        except httpx.HTTPError as he:\n            logger.exception(he)\n            logger.error(response.text)\n            raise LunchMoneyHTTPError(response.text) from he\n        if response.content:\n            returned_data = response.json()\n        else:\n            returned_data = None\n        if isinstance(returned_data, dict) and any(\n            [\"error\" in returned_data.keys(), \"errors\" in returned_data.keys()]\n        ):\n            try:\n                errors = returned_data[\"error\"]\n            except KeyError:\n                errors = returned_data[\"errors\"]\n            logger.exception(errors)\n            raise LunchMoneyHTTPError(errors)\n        return returned_data\n\n    def make_request(\n        self,\n        method: str,\n        url_path: Union[list[Union[str, int]], str, int],\n        params: Optional[Mapping[str, Any]] = None,\n        payload: Optional[Any] = None,\n        **kwargs: Any,\n    ) -> Any:\n        \"\"\"\n        Make an HTTP request and `process` its response\n\n        This method is a wrapper around :meth:`.LunchMoney.request` that\n        also processes the response and checks for any errors.\n\n        Parameters\n        ----------\n        method: str\n            requests method: GET, OPTIONS, HEAD, POST, PUT,\n            PATCH, or DELETE\n        url_path: Union[List[Union[str, int]], str, int]\n            URL components to make into a URL\n        payload: Optional[Mapping[str, Any]]\n            Data to send in the body of the Request.\n        params: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples or bytes to send in the query\n            string for the Request.\n        **kwargs: Any\n            Additional arguments to send to the request method.\n\n        Returns\n        -------\n        Any\n        \"\"\"\n        url = APIConfig.make_url(url_path=url_path)\n        json_safe_payload = pydantic_core.to_json(payload) if payload else None\n        json_safe_params = pydantic_core.to_jsonable_python(params)\n        response = self.request(\n            method=method,\n            url=url,\n            params=json_safe_params,\n            content=json_safe_payload,\n            **kwargs,\n        )\n        data = self.process_response(response=response)\n        return data\n\n    async def amake_request(\n        self,\n        method: str,\n        url_path: Union[list[Union[str, int]], str, int],\n        params: Optional[Mapping[str, Any]] = None,\n        payload: Optional[Any] = None,\n        **kwargs: Any,\n    ) -> Any:\n        \"\"\"\n        Make an async HTTP request and `process` its response\n\n        This method is a wrapper around :meth:`.LunchMoney.arequest` that\n        also processes the response and checks for any errors.\n\n        Parameters\n        ----------\n        method: str\n            requests method: GET, OPTIONS, HEAD, POST, PUT,\n            PATCH, or DELETE\n        url_path: Union[List[Union[str, int]], str, int]\n            URL components to make into a URL\n        payload: Optional[Mapping[str, Any]]\n            Data to send in the body of the Request.\n        params: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples or bytes to send in the query\n            string for the Request.\n        **kwargs: Any\n            Additional arguments to send to the request method.\n\n        Returns\n        -------\n        Any\n        \"\"\"\n        url = APIConfig.make_url(url_path=url_path)\n        json_safe_payload = pydantic_core.to_jsonable_python(payload)\n        json_safe_params = pydantic_core.to_jsonable_python(params)\n        response = await self.arequest(\n            method=method,\n            url=url,\n            params=json_safe_params,\n            data=json_safe_payload,\n            **kwargs,\n        )\n        data = self.process_response(response=response)\n        return data\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.async_session","title":"async_session: httpx.AsyncClient cached property","text":"

Lunch Money HTTPX Async Client

Returns:

Type Description AsyncClient"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.session","title":"session: httpx.Client cached property","text":"

Lunch Money HTTPX Client

Returns:

Type Description Client"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.Methods","title":"Methods","text":"

HTTP Request Method Enumerations: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

Source code in lunchable/models/_core.py
class Methods:\n    \"\"\"\n    HTTP Request Method Enumerations: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE\n    \"\"\"\n\n    # This Helper Namespace Organizes and Tracks HTTP Requests by Method\n    GET = \"GET\"\n    OPTIONS = \"OPTIONS\"\n    HEAD = \"HEAD\"\n    POST = \"POST\"\n    PUT = \"PUT\"\n    PATCH = \"PATCH\"\n    DELETE = \"DELETE\"\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.__init__","title":"__init__(access_token=None)","text":"

Initialize a Lunch Money object with an Access Token.

Tries to inherit from the Environment if one isn't provided

Parameters:

Name Type Description Default access_token str | None

Lunchmoney Developer API Access Token

None Source code in lunchable/models/_core.py
def __init__(self, access_token: str | None = None) -> None:\n    \"\"\"\n    Initialize a Lunch Money object with an Access Token.\n\n    Tries to inherit from the Environment if one isn't provided\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n    \"\"\"\n    self.access_token = APIConfig.get_access_token(access_token=access_token)\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str Source code in lunchable/models/_core.py
def __repr__(self) -> str:\n    \"\"\"\n    String Representation\n\n    Returns\n    -------\n    str\n    \"\"\"\n    return \"<LunchMoney: httpx.Client>\"\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.amake_request","title":"amake_request(method, url_path, params=None, payload=None, **kwargs) async","text":"

Make an async HTTP request and process its response

This method is a wrapper around :meth:.LunchMoney.arequest that also processes the response and checks for any errors.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url_path Union[list[Union[str, int]], str, int]

URL components to make into a URL

required payload Optional[Any]

Data to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Any Source code in lunchable/models/_core.py
async def amake_request(\n    self,\n    method: str,\n    url_path: Union[list[Union[str, int]], str, int],\n    params: Optional[Mapping[str, Any]] = None,\n    payload: Optional[Any] = None,\n    **kwargs: Any,\n) -> Any:\n    \"\"\"\n    Make an async HTTP request and `process` its response\n\n    This method is a wrapper around :meth:`.LunchMoney.arequest` that\n    also processes the response and checks for any errors.\n\n    Parameters\n    ----------\n    method: str\n        requests method: GET, OPTIONS, HEAD, POST, PUT,\n        PATCH, or DELETE\n    url_path: Union[List[Union[str, int]], str, int]\n        URL components to make into a URL\n    payload: Optional[Mapping[str, Any]]\n        Data to send in the body of the Request.\n    params: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples or bytes to send in the query\n        string for the Request.\n    **kwargs: Any\n        Additional arguments to send to the request method.\n\n    Returns\n    -------\n    Any\n    \"\"\"\n    url = APIConfig.make_url(url_path=url_path)\n    json_safe_payload = pydantic_core.to_jsonable_python(payload)\n    json_safe_params = pydantic_core.to_jsonable_python(params)\n    response = await self.arequest(\n        method=method,\n        url=url,\n        params=json_safe_params,\n        data=json_safe_payload,\n        **kwargs,\n    )\n    data = self.process_response(response=response)\n    return data\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.arequest","title":"arequest(method, url, *, content=None, data=None, json=None, params=None, **kwargs) async","text":"

Make an async HTTP request

This is a simple method :class:.LunchMoney exposes to make HTTP requests. It has the benefit of using an existing httpx.Client as well as as out of the box auth headers that are used to connect to the Lunch Money Developer API.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url Union[URL, str]

URL for the new Request object.

required content Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]

Content to send in the body of the Request.

None data Optional[Mapping[str, Any]]

Dictionary, list of tuples, bytes, or file-like object to send in the body of the Request.

None json Optional[Any]

A JSON serializable Python object to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Response Source code in lunchable/models/_core.py
async def arequest(\n    self,\n    method: str,\n    url: Union[httpx.URL, str],\n    *,\n    content: Optional[\n        Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\n    ] = None,\n    data: Optional[Mapping[str, Any]] = None,\n    json: Optional[Any] = None,\n    params: Optional[Mapping[str, Any]] = None,\n    **kwargs: Any,\n) -> httpx.Response:\n    \"\"\"\n    Make an async HTTP request\n\n    This is a simple method :class:`.LunchMoney` exposes to make HTTP requests. It\n    has the benefit of using an existing `httpx.Client` as well as as out of the box\n    auth headers that are used to connect to the Lunch Money Developer API.\n\n    Parameters\n    ----------\n    method: str\n        requests method: GET, OPTIONS, HEAD, POST, PUT,\n        PATCH, or DELETE\n    url: Union[httpx.URL, str]\n        URL for the new Request object.\n    content: Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]\n        Content to send in the body of the Request.\n    data: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples, bytes, or file-like object to send\n        in the body of the Request.\n    json: Optional[Any]\n        A JSON serializable Python object to send in the body of the Request.\n    params: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples or bytes to send in the query\n        string for the Request.\n    **kwargs: Any\n        Additional arguments to send to the request method.\n\n    Returns\n    -------\n    httpx.Response\n    \"\"\"\n    response = self.async_session.request(\n        method=method,\n        url=url,\n        content=content,\n        data=data,\n        json=json,\n        params=params,\n        **kwargs,\n    )\n    return await response\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.make_request","title":"make_request(method, url_path, params=None, payload=None, **kwargs)","text":"

Make an HTTP request and process its response

This method is a wrapper around :meth:.LunchMoney.request that also processes the response and checks for any errors.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url_path Union[list[Union[str, int]], str, int]

URL components to make into a URL

required payload Optional[Any]

Data to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Any Source code in lunchable/models/_core.py
def make_request(\n    self,\n    method: str,\n    url_path: Union[list[Union[str, int]], str, int],\n    params: Optional[Mapping[str, Any]] = None,\n    payload: Optional[Any] = None,\n    **kwargs: Any,\n) -> Any:\n    \"\"\"\n    Make an HTTP request and `process` its response\n\n    This method is a wrapper around :meth:`.LunchMoney.request` that\n    also processes the response and checks for any errors.\n\n    Parameters\n    ----------\n    method: str\n        requests method: GET, OPTIONS, HEAD, POST, PUT,\n        PATCH, or DELETE\n    url_path: Union[List[Union[str, int]], str, int]\n        URL components to make into a URL\n    payload: Optional[Mapping[str, Any]]\n        Data to send in the body of the Request.\n    params: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples or bytes to send in the query\n        string for the Request.\n    **kwargs: Any\n        Additional arguments to send to the request method.\n\n    Returns\n    -------\n    Any\n    \"\"\"\n    url = APIConfig.make_url(url_path=url_path)\n    json_safe_payload = pydantic_core.to_json(payload) if payload else None\n    json_safe_params = pydantic_core.to_jsonable_python(params)\n    response = self.request(\n        method=method,\n        url=url,\n        params=json_safe_params,\n        content=json_safe_payload,\n        **kwargs,\n    )\n    data = self.process_response(response=response)\n    return data\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.process_response","title":"process_response(response) classmethod","text":"

Process a Lunch Money response and raise any errors

This includes 200 responses that are actually errors

Parameters:

Name Type Description Default response Response

An HTTPX Response Object

required Source code in lunchable/models/_core.py
@classmethod\ndef process_response(cls, response: httpx.Response) -> Any:\n    \"\"\"\n    Process a Lunch Money response and raise any errors\n\n    This includes 200 responses that are actually errors\n\n    Parameters\n    ----------\n    response: httpx.Response\n        An HTTPX Response Object\n    \"\"\"\n    try:\n        response.raise_for_status()\n    except httpx.HTTPError as he:\n        logger.exception(he)\n        logger.error(response.text)\n        raise LunchMoneyHTTPError(response.text) from he\n    if response.content:\n        returned_data = response.json()\n    else:\n        returned_data = None\n    if isinstance(returned_data, dict) and any(\n        [\"error\" in returned_data.keys(), \"errors\" in returned_data.keys()]\n    ):\n        try:\n            errors = returned_data[\"error\"]\n        except KeyError:\n            errors = returned_data[\"errors\"]\n        logger.exception(errors)\n        raise LunchMoneyHTTPError(errors)\n    return returned_data\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.request","title":"request(method, url, *, content=None, data=None, json=None, params=None, **kwargs)","text":"

Make an HTTP request

This is a simple method :class:.LunchMoney exposes to make HTTP requests. It has the benefit of using an existing httpx.Client as well as as out of the box auth headers that are used to connect to the Lunch Money Developer API.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url Union[URL, str]

URL for the new Request object.

required content Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]

Content to send in the body of the Request.

None data Optional[Mapping[str, Any]]

Dictionary, list of tuples, bytes, or file-like object to send in the body of the Request.

None json Optional[Any]

A JSON serializable Python object to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Response

Examples:

A recent use of this method was to delete a Tag (which isn't available via the Developer API yet)

import lunchable\n\nlunch = lunchable.LunchMoney()\n\n# Get All the Tags\nall_tags = lunch.get_tags()\n# Get All The Null Tags (a list of 1 or zero)\nnull_tags = [tag for tag in all_tags if tag.name in [None, \"\"]]\n\n# Create a Cookie dictionary from a browser session\ncookies = {\"cookie_keys\": \"cookie_values\"}\ndel lunch.session.headers[\"authorization\"]\n\nfor null_tag in null_tags:\n    # use the httpx.client embedded in the class to make a request with cookies\n    response = lunch.request(\n        method=lunch.Methods.DELETE,\n        url=f\"https://api.lunchmoney.app/tags/{null_tag.id}\",\n        cookies=cookies\n    )\n    # raise an error for 4XX responses\n    response.raise_for_status()\n
Source code in lunchable/models/_core.py
def request(\n    self,\n    method: str,\n    url: Union[httpx.URL, str],\n    *,\n    content: Optional[\n        Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\n    ] = None,\n    data: Optional[Mapping[str, Any]] = None,\n    json: Optional[Any] = None,\n    params: Optional[Mapping[str, Any]] = None,\n    **kwargs: Any,\n) -> httpx.Response:\n    \"\"\"\n    Make an HTTP request\n\n    This is a simple method :class:`.LunchMoney` exposes to make HTTP requests. It\n    has the benefit of using an existing `httpx.Client` as well as as out of the box\n    auth headers that are used to connect to the Lunch Money Developer API.\n\n    Parameters\n    ----------\n    method: str\n        requests method: GET, OPTIONS, HEAD, POST, PUT,\n        PATCH, or DELETE\n    url: Union[httpx.URL, str]\n        URL for the new Request object.\n    content: Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]\n        Content to send in the body of the Request.\n    data: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples, bytes, or file-like object to send\n        in the body of the Request.\n    json: Optional[Any]\n        A JSON serializable Python object to send in the body of the Request.\n    params: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples or bytes to send in the query\n        string for the Request.\n    **kwargs: Any\n        Additional arguments to send to the request method.\n\n    Returns\n    -------\n    httpx.Response\n\n    Examples\n    --------\n    A recent use of this method was to delete a Tag (which isn't available via the\n    Developer API yet)\n\n    ```python\n    import lunchable\n\n    lunch = lunchable.LunchMoney()\n\n    # Get All the Tags\n    all_tags = lunch.get_tags()\n    # Get All The Null Tags (a list of 1 or zero)\n    null_tags = [tag for tag in all_tags if tag.name in [None, \"\"]]\n\n    # Create a Cookie dictionary from a browser session\n    cookies = {\"cookie_keys\": \"cookie_values\"}\n    del lunch.session.headers[\"authorization\"]\n\n    for null_tag in null_tags:\n        # use the httpx.client embedded in the class to make a request with cookies\n        response = lunch.request(\n            method=lunch.Methods.DELETE,\n            url=f\"https://api.lunchmoney.app/tags/{null_tag.id}\",\n            cookies=cookies\n        )\n        # raise an error for 4XX responses\n        response.raise_for_status()\n    ```\n    \"\"\"\n    response = self.session.request(\n        method=method,\n        url=url,\n        content=content,\n        data=data,\n        json=json,\n        params=params,\n        **kwargs,\n    )\n    return response\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAsyncClient","title":"LunchMoneyAsyncClient","text":"

Bases: AsyncClient

API Async HTTP Client

Source code in lunchable/models/_core.py
class LunchMoneyAsyncClient(httpx.AsyncClient):\n    \"\"\"\n    API Async HTTP Client\n    \"\"\"\n\n    def __init__(self, access_token: str | None = None) -> None:\n        super().__init__()\n        api_headers = APIConfig.get_header(access_token=access_token)\n        self.headers.update(api_headers)\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyClient","title":"LunchMoneyClient","text":"

Bases: Client

API HTTP Client

Source code in lunchable/models/_core.py
class LunchMoneyClient(Client):\n    \"\"\"\n    API HTTP Client\n    \"\"\"\n\n    def __init__(self, access_token: str | None = None) -> None:\n        super().__init__()\n        api_headers = APIConfig.get_header(access_token=access_token)\n        self.headers.update(api_headers)\n
"},{"location":"reference/models/_lunchmoney/","title":"_lunchmoney","text":"

Lunch Money Python Client

This Module Leverages Class Inheritance to distribute API Methods Across a series of clients. Ultimately, everything inherits from the lunchable.models.core.LunchMoneyAPIClient class which facilitates interacting with the API.

For example: to see source code on interactions with the \"transactions\" API endpoint you will refer to the TransactionsClient object.

"},{"location":"reference/models/_lunchmoney/#lunchable.models._lunchmoney.LunchMoney","title":"LunchMoney","text":"

Bases: AssetsClient, BudgetsClient, CategoriesClient, CryptoClient, PlaidAccountsClient, RecurringExpensesClient, TagsClient, TransactionsClient, UserClient

Lunch Money Python Client.

This class facilitates with connections to the Lunch Money Developer API. Authenticate with an Access Token. If an access token isn't provided one will attempt to be inherited from a LUNCHMONEY_ACCESS_TOKEN environment variable.

Examples:

from __future__ import annotations\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions()\n
Source code in lunchable/models/_lunchmoney.py
class LunchMoney(\n    AssetsClient,\n    BudgetsClient,\n    CategoriesClient,\n    CryptoClient,\n    PlaidAccountsClient,\n    RecurringExpensesClient,\n    TagsClient,\n    TransactionsClient,\n    UserClient,\n):\n    \"\"\"\n    Lunch Money Python Client.\n\n    This class facilitates with connections to\n    the [Lunch Money Developer API](https://lunchmoney.dev/). Authenticate\n    with an Access Token. If an access token isn't provided one will attempt to\n    be inherited from a `LUNCHMONEY_ACCESS_TOKEN` environment variable.\n\n    Examples\n    --------\n    ```python\n    from __future__ import annotations\n\n    from lunchable import LunchMoney\n    from lunchable.models import TransactionObject\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transactions: list[TransactionObject] = lunch.get_transactions()\n    ```\n    \"\"\"\n\n    def __init__(self, access_token: Optional[str] = None):\n        \"\"\"\n        Initialize a Lunch Money object with an Access Token.\n\n        Tries to inherit from the Environment if one isn't provided\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n        \"\"\"\n        super(LunchMoney, self).__init__(access_token=access_token)\n
"},{"location":"reference/models/_lunchmoney/#lunchable.models._lunchmoney.LunchMoney.__init__","title":"__init__(access_token=None)","text":"

Initialize a Lunch Money object with an Access Token.

Tries to inherit from the Environment if one isn't provided

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None Source code in lunchable/models/_lunchmoney.py
def __init__(self, access_token: Optional[str] = None):\n    \"\"\"\n    Initialize a Lunch Money object with an Access Token.\n\n    Tries to inherit from the Environment if one isn't provided\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n    \"\"\"\n    super(LunchMoney, self).__init__(access_token=access_token)\n
"},{"location":"reference/models/assets/","title":"assets","text":"

Lunch Money - Assets

https://lunchmoney.dev/#assets

"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsClient","title":"AssetsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Assets Interactions

Source code in lunchable/models/assets.py
class AssetsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Assets Interactions\n    \"\"\"\n\n    def get_assets(self) -> List[AssetsObject]:\n        \"\"\"\n        Get Manually Managed Assets\n\n        Get a list of all manually-managed assets associated with the user's account.\n\n        (https://lunchmoney.dev/#assets-object)\n\n        Returns\n        -------\n        List[AssetsObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=[APIConfig.LUNCHMONEY_ASSETS]\n        )\n        assets = response_data.get(APIConfig.LUNCHMONEY_ASSETS)\n        asset_objects = [AssetsObject.model_validate(item) for item in assets]\n        return asset_objects\n\n    def update_asset(\n        self,\n        asset_id: int,\n        type_name: Optional[str] = None,\n        subtype_name: Optional[str] = None,\n        name: Optional[str] = None,\n        balance: Optional[float] = None,\n        balance_as_of: Optional[datetime.datetime] = None,\n        currency: Optional[str] = None,\n        institution_name: Optional[str] = None,\n    ) -> AssetsObject:\n        \"\"\"\n        Update a Single Asset\n\n        Parameters\n        ----------\n        asset_id: int\n            Asset Identifier\n        type_name: Optional[str]\n            Must be one of: cash, credit, investment, other, real estate, loan, vehicle,\n            cryptocurrency, employee compensation\n        subtype_name: Optional[str]\n            Max 25 characters\n        name: Optional[str]\n            Max 45 characters\n        balance: Optional[float]\n            Numeric value of the current balance of the account. Do not include any special\n            characters aside from a decimal point!\n        balance_as_of: Optional[datetime.datetime]\n            Has no effect if balance is not defined. If balance is defined, but balance_as_of\n            is not supplied or is invalid, current timestamp will be used.\n        currency: Optional[str]\n            Three-letter lowercase currency in ISO 4217 format. The code sent must exist in\n            our database. Defaults to asset's currency.\n        institution_name: Optional[str]\n            Max 50 characters\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        payload = _AssetsParamsPut(\n            type_name=type_name,\n            subtype_name=subtype_name,\n            name=name,\n            balance=balance,\n            balance_as_of=balance_as_of,\n            currency=currency,\n            institution_name=institution_name,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[APIConfig.LUNCHMONEY_ASSETS, asset_id],\n            payload=payload,\n        )\n        asset = AssetsObject.model_validate(response_data)\n        return asset\n\n    def insert_asset(\n        self,\n        type_name: str,\n        name: Optional[str] = None,\n        subtype_name: Optional[str] = None,\n        display_name: Optional[str] = None,\n        balance: float = 0.00,\n        balance_as_of: Optional[datetime.datetime] = None,\n        currency: Optional[str] = None,\n        institution_name: Optional[str] = None,\n        closed_on: Optional[datetime.date] = None,\n        exclude_transactions: bool = False,\n    ) -> AssetsObject:\n        \"\"\"\n        Create a single (manually-managed) asset.\n\n        Parameters\n        ----------\n        type_name: Optional[str]\n            Must be one of: cash, credit, investment, other, real estate, loan, vehicle,\n            cryptocurrency, employee compensation\n        name: Optional[str]\n            Max 45 characters\n        subtype_name: Optional[str]\n            Max 25 characters\n        display_name: Optional[str]\n            Display name of the asset (as set by user)\n        balance: float\n            Numeric value of the current balance of the account. Do not include any\n            special characters aside from a decimal point! Defaults to `0.00`\n        balance_as_of: Optional[datetime.datetime]\n            Has no effect if balance is not defined. If balance is defined, but\n            balance_as_of is not supplied or is invalid, current timestamp will be used.\n        currency: Optional[str]\n            Three-letter lowercase currency in ISO 4217 format. The code sent must exist\n            in our database. Defaults to user's primary currency.\n        institution_name: Optional[str]\n            Max 50 characters\n        closed_on: Optional[datetime.date]\n            The date this asset was closed\n        exclude_transactions: bool\n            If true, this asset will not show up as an option for assignment when\n            creating transactions manually. Defaults to False\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        payload = _AssetsParamsPost(\n            type_name=type_name,\n            subtype_name=subtype_name,\n            name=name,\n            display_name=display_name,\n            balance=balance,\n            balance_as_of=balance_as_of,\n            currency=currency,\n            institution_name=institution_name,\n            closed_on=closed_on,\n            exclude_transactions=exclude_transactions,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[APIConfig.LUNCHMONEY_ASSETS],\n            payload=payload,\n        )\n        asset = AssetsObject.model_validate(response_data)\n        return asset\n
"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsClient.get_assets","title":"get_assets()","text":"

Get Manually Managed Assets

Get a list of all manually-managed assets associated with the user's account.

(https://lunchmoney.dev/#assets-object)

Returns:

Type Description List[AssetsObject] Source code in lunchable/models/assets.py
def get_assets(self) -> List[AssetsObject]:\n    \"\"\"\n    Get Manually Managed Assets\n\n    Get a list of all manually-managed assets associated with the user's account.\n\n    (https://lunchmoney.dev/#assets-object)\n\n    Returns\n    -------\n    List[AssetsObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=[APIConfig.LUNCHMONEY_ASSETS]\n    )\n    assets = response_data.get(APIConfig.LUNCHMONEY_ASSETS)\n    asset_objects = [AssetsObject.model_validate(item) for item in assets]\n    return asset_objects\n
"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsClient.insert_asset","title":"insert_asset(type_name, name=None, subtype_name=None, display_name=None, balance=0.0, balance_as_of=None, currency=None, institution_name=None, closed_on=None, exclude_transactions=False)","text":"

Create a single (manually-managed) asset.

Parameters:

Name Type Description Default type_name str

Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation

required name Optional[str]

Max 45 characters

None subtype_name Optional[str]

Max 25 characters

None display_name Optional[str]

Display name of the asset (as set by user)

None balance float

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point! Defaults to 0.00

0.0 balance_as_of Optional[datetime]

Has no effect if balance is not defined. If balance is defined, but balance_as_of is not supplied or is invalid, current timestamp will be used.

None currency Optional[str]

Three-letter lowercase currency in ISO 4217 format. The code sent must exist in our database. Defaults to user's primary currency.

None institution_name Optional[str]

Max 50 characters

None closed_on Optional[date]

The date this asset was closed

None exclude_transactions bool

If true, this asset will not show up as an option for assignment when creating transactions manually. Defaults to False

False

Returns:

Type Description AssetsObject Source code in lunchable/models/assets.py
def insert_asset(\n    self,\n    type_name: str,\n    name: Optional[str] = None,\n    subtype_name: Optional[str] = None,\n    display_name: Optional[str] = None,\n    balance: float = 0.00,\n    balance_as_of: Optional[datetime.datetime] = None,\n    currency: Optional[str] = None,\n    institution_name: Optional[str] = None,\n    closed_on: Optional[datetime.date] = None,\n    exclude_transactions: bool = False,\n) -> AssetsObject:\n    \"\"\"\n    Create a single (manually-managed) asset.\n\n    Parameters\n    ----------\n    type_name: Optional[str]\n        Must be one of: cash, credit, investment, other, real estate, loan, vehicle,\n        cryptocurrency, employee compensation\n    name: Optional[str]\n        Max 45 characters\n    subtype_name: Optional[str]\n        Max 25 characters\n    display_name: Optional[str]\n        Display name of the asset (as set by user)\n    balance: float\n        Numeric value of the current balance of the account. Do not include any\n        special characters aside from a decimal point! Defaults to `0.00`\n    balance_as_of: Optional[datetime.datetime]\n        Has no effect if balance is not defined. If balance is defined, but\n        balance_as_of is not supplied or is invalid, current timestamp will be used.\n    currency: Optional[str]\n        Three-letter lowercase currency in ISO 4217 format. The code sent must exist\n        in our database. Defaults to user's primary currency.\n    institution_name: Optional[str]\n        Max 50 characters\n    closed_on: Optional[datetime.date]\n        The date this asset was closed\n    exclude_transactions: bool\n        If true, this asset will not show up as an option for assignment when\n        creating transactions manually. Defaults to False\n\n    Returns\n    -------\n    AssetsObject\n    \"\"\"\n    payload = _AssetsParamsPost(\n        type_name=type_name,\n        subtype_name=subtype_name,\n        name=name,\n        display_name=display_name,\n        balance=balance,\n        balance_as_of=balance_as_of,\n        currency=currency,\n        institution_name=institution_name,\n        closed_on=closed_on,\n        exclude_transactions=exclude_transactions,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[APIConfig.LUNCHMONEY_ASSETS],\n        payload=payload,\n    )\n    asset = AssetsObject.model_validate(response_data)\n    return asset\n
"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsClient.update_asset","title":"update_asset(asset_id, type_name=None, subtype_name=None, name=None, balance=None, balance_as_of=None, currency=None, institution_name=None)","text":"

Update a Single Asset

Parameters:

Name Type Description Default asset_id int

Asset Identifier

required type_name Optional[str]

Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation

None subtype_name Optional[str]

Max 25 characters

None name Optional[str]

Max 45 characters

None balance Optional[float]

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point!

None balance_as_of Optional[datetime]

Has no effect if balance is not defined. If balance is defined, but balance_as_of is not supplied or is invalid, current timestamp will be used.

None currency Optional[str]

Three-letter lowercase currency in ISO 4217 format. The code sent must exist in our database. Defaults to asset's currency.

None institution_name Optional[str]

Max 50 characters

None

Returns:

Type Description AssetsObject Source code in lunchable/models/assets.py
def update_asset(\n    self,\n    asset_id: int,\n    type_name: Optional[str] = None,\n    subtype_name: Optional[str] = None,\n    name: Optional[str] = None,\n    balance: Optional[float] = None,\n    balance_as_of: Optional[datetime.datetime] = None,\n    currency: Optional[str] = None,\n    institution_name: Optional[str] = None,\n) -> AssetsObject:\n    \"\"\"\n    Update a Single Asset\n\n    Parameters\n    ----------\n    asset_id: int\n        Asset Identifier\n    type_name: Optional[str]\n        Must be one of: cash, credit, investment, other, real estate, loan, vehicle,\n        cryptocurrency, employee compensation\n    subtype_name: Optional[str]\n        Max 25 characters\n    name: Optional[str]\n        Max 45 characters\n    balance: Optional[float]\n        Numeric value of the current balance of the account. Do not include any special\n        characters aside from a decimal point!\n    balance_as_of: Optional[datetime.datetime]\n        Has no effect if balance is not defined. If balance is defined, but balance_as_of\n        is not supplied or is invalid, current timestamp will be used.\n    currency: Optional[str]\n        Three-letter lowercase currency in ISO 4217 format. The code sent must exist in\n        our database. Defaults to asset's currency.\n    institution_name: Optional[str]\n        Max 50 characters\n\n    Returns\n    -------\n    AssetsObject\n    \"\"\"\n    payload = _AssetsParamsPut(\n        type_name=type_name,\n        subtype_name=subtype_name,\n        name=name,\n        balance=balance,\n        balance_as_of=balance_as_of,\n        currency=currency,\n        institution_name=institution_name,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[APIConfig.LUNCHMONEY_ASSETS, asset_id],\n        payload=payload,\n    )\n    asset = AssetsObject.model_validate(response_data)\n    return asset\n
"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsObject","title":"AssetsObject","text":"

Bases: LunchableModel

Manually Managed Asset Objects

Assets in Lunch Money are similar to plaid-accounts except that they are manually managed.

https://lunchmoney.dev/#assets-object

Parameters:

Name Type Description Default id int

Unique identifier for asset

required type_name str

Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan, cryptocurrency, investment, other, credit, real estate]

required subtype_name str | None

Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]

None name str

Name of the asset

required display_name str | None

Display name of the asset (as set by user)

None balance float

Current balance of the asset in numeric format to 4 decimal places

required balance_as_of datetime

Date/time the balance was last updated in ISO 8601 extended format

required closed_on date | None

The date this asset was closed (optional)

None currency str

Three-letter lowercase currency code of the balance in ISO 4217 format

required institution_name str | None

Name of institution holding the asset

None exclude_transactions bool

If true, this asset will not show up as an option for assignment when creating transactions manually

False created_at datetime

Date/time the asset was created in ISO 8601 extended format

required Source code in lunchable/models/assets.py
class AssetsObject(LunchableModel):\n    \"\"\"\n    Manually Managed Asset Objects\n\n    Assets in Lunch Money are similar to `plaid-accounts` except that they are manually managed.\n\n    https://lunchmoney.dev/#assets-object\n    \"\"\"\n\n    _type_name_description = \"\"\"\n    Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan,\n    cryptocurrency, investment, other, credit, real estate]\n    \"\"\"\n    _subtype_name_description = \"\"\"\n    Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]\n    \"\"\"\n    _balance_description = (\n        \"Current balance of the asset in numeric format to 4 decimal places\"\n    )\n    _balance_as_of_description = \"\"\"\n    Date/time the balance was last updated in ISO 8601 extended format\n    \"\"\"\n    _closed_on_description = \"The date this asset was closed (optional)\"\n    _currency_description = (\n        \"Three-letter lowercase currency code of the balance in ISO 4217 format\"\n    )\n    _created_at_description = (\n        \"Date/time the asset was created in ISO 8601 extended format\"\n    )\n    _exclude_transactions_description = (\n        \"If true, this asset will not show up as an \"\n        \"option for assignment when creating \"\n        \"transactions manually\"\n    )\n\n    id: int = Field(description=\"Unique identifier for asset\")\n    type_name: str = Field(description=_type_name_description)\n    subtype_name: Optional[str] = Field(None, description=_subtype_name_description)\n    name: str = Field(description=\"Name of the asset\")\n    display_name: Optional[str] = Field(\n        None, description=\"Display name of the asset (as set by user)\"\n    )\n    balance: float = Field(description=_balance_description)\n    balance_as_of: datetime.datetime = Field(description=_balance_as_of_description)\n    closed_on: Optional[datetime.date] = Field(None, description=_closed_on_description)\n    currency: str = Field(description=_currency_description)\n    institution_name: Optional[str] = Field(\n        None, description=\"Name of institution holding the asset\"\n    )\n    exclude_transactions: bool = Field(\n        default=False, description=_exclude_transactions_description\n    )\n    created_at: datetime.datetime = Field(description=_created_at_description)\n
"},{"location":"reference/models/budgets/","title":"budgets","text":"

Lunch Money - Budgets

https://lunchmoney.dev/#budget

"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetConfigObject","title":"BudgetConfigObject","text":"

Bases: LunchableModel

Budget Configuration Object

Parameters:

Name Type Description Default config_id int required cadence str required amount float | None None currency str | None None to_base float | None None auto_suggest str required Source code in lunchable/models/budgets.py
class BudgetConfigObject(LunchableModel):\n    \"\"\"\n    Budget Configuration Object\n    \"\"\"\n\n    config_id: int\n    cadence: str\n    amount: Optional[float] = None\n    currency: Optional[str] = None\n    to_base: Optional[float] = None\n    auto_suggest: str\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetDataObject","title":"BudgetDataObject","text":"

Bases: LunchableModel

Data Object within a Budget

Parameters:

Name Type Description Default budget_amount float | None None budget_currency str | None None budget_to_base float | None None spending_to_base float 0.0 num_transactions int 0 Source code in lunchable/models/budgets.py
class BudgetDataObject(LunchableModel):\n    \"\"\"\n    Data Object within a Budget\n    \"\"\"\n\n    budget_amount: Optional[float] = None\n    budget_currency: Optional[str] = None\n    budget_to_base: Optional[float] = None\n    spending_to_base: float = 0.00\n    num_transactions: int = 0\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetObject","title":"BudgetObject","text":"

Bases: LunchableModel

Monthly Budget Per Category Object

https://lunchmoney.dev/#budget-object

Parameters:

Name Type Description Default category_name str

Name of the category

required category_id int | None

Unique identifier for category

None category_group_name str | None

Name of the category group, if applicable

None group_id int | None

Unique identifier for category group

None is_group bool | None

If true, this category is a group

None is_income bool

If true, this category is an income category (category properties are set in the app via the Categories page)

required exclude_from_budget bool

If true, this category is excluded from budget (category properties are set in the app via the Categories page)

required exclude_from_totals bool

If true, this category is excluded from totals (category properties are set in the app via the Categories page)

required data Dict[date, BudgetDataObject]

For each month with budget or category spending data, there is a data object with the key set to the month in format YYYY-MM-DD. For properties, see Data object below.

required config BudgetConfigObject | None

Object representing the category's budget suggestion configuration

None Source code in lunchable/models/budgets.py
class BudgetObject(LunchableModel):\n    \"\"\"\n    Monthly Budget Per Category Object\n\n    https://lunchmoney.dev/#budget-object\n    \"\"\"\n\n    _category_group_name_description = \"Name of the category group, if applicable\"\n    _is_income_description = \"\"\"\n    If true, this category is an income category (category properties\n    are set in the app via the Categories page)\n    \"\"\"\n    _exclude_from_budget_description = \"\"\"\n    If true, this category is excluded from budget (category\n    properties are set in the app via the Categories page)\n    \"\"\"\n    _exclude_from_totals_description = \"\"\"\n    If true, this category is excluded from totals (category\n    properties are set in the app via the Categories page)\n    \"\"\"\n    _data_description = \"\"\"\n    For each month with budget or category spending data, there is a data object with the key\n    set to the month in format YYYY-MM-DD. For properties, see Data object below.\n    \"\"\"\n    _config_description = \"\"\"\n    Object representing the category's budget suggestion configuration\n    \"\"\"\n\n    category_name: str = Field(description=\"Name of the category\")\n    category_id: Optional[int] = Field(\n        None, description=\"Unique identifier for category\"\n    )\n    category_group_name: Optional[str] = Field(\n        None, description=_category_group_name_description\n    )\n    group_id: Optional[int] = Field(\n        None, description=\"Unique identifier for category group\"\n    )\n    is_group: Optional[bool] = Field(\n        None, description=\"If true, this category is a group\"\n    )\n    is_income: bool = Field(description=_is_income_description)\n    exclude_from_budget: bool = Field(description=_exclude_from_budget_description)\n    exclude_from_totals: bool = Field(description=_exclude_from_totals_description)\n    data: Dict[datetime.date, BudgetDataObject] = Field(description=_data_description)\n    config: Optional[BudgetConfigObject] = Field(None, description=_config_description)\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetParamsGet","title":"BudgetParamsGet","text":"

Bases: LunchableModel

https://lunchmoney.dev/#get-budget-summary

Parameters:

Name Type Description Default start_date date required end_date date required Source code in lunchable/models/budgets.py
class BudgetParamsGet(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#get-budget-summary\n    \"\"\"\n\n    start_date: datetime.date\n    end_date: datetime.date\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetParamsPut","title":"BudgetParamsPut","text":"

Bases: LunchableModel

https://lunchmoney.dev/#upsert-budget

Parameters:

Name Type Description Default start_date date required category_id int required amount float required currency str | None None Source code in lunchable/models/budgets.py
class BudgetParamsPut(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#upsert-budget\n    \"\"\"\n\n    start_date: datetime.date\n    category_id: int\n    amount: float\n    currency: Optional[str] = None\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetParamsRemove","title":"BudgetParamsRemove","text":"

Bases: LunchableModel

https://lunchmoney.dev/#remove-budget

Parameters:

Name Type Description Default start_date date required category_id int required Source code in lunchable/models/budgets.py
class BudgetParamsRemove(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#remove-budget\n    \"\"\"\n\n    start_date: datetime.date\n    category_id: int\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetsClient","title":"BudgetsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Budget Interactions

Source code in lunchable/models/budgets.py
class BudgetsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Budget Interactions\n    \"\"\"\n\n    def get_budgets(\n        self, start_date: datetime.date, end_date: datetime.date\n    ) -> List[BudgetObject]:\n        \"\"\"\n        Get Monthly Budgets\n\n        Get full details on the budgets for all categories between a certain time\n        period. The budgeted and spending amounts will be an aggregate across this\n        time period. (https://lunchmoney.dev/#plaid-accounts-object)\n\n        Returns\n        -------\n        List[BudgetObject]\n        \"\"\"\n        params = BudgetParamsGet(start_date=start_date, end_date=end_date).model_dump()\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=[APIConfig.LUNCHMONEY_BUDGET],\n            params=params,\n        )\n        budget_objects = [BudgetObject.model_validate(item) for item in response_data]\n        return budget_objects\n\n    def upsert_budget(\n        self,\n        start_date: datetime.date,\n        category_id: int,\n        amount: float,\n        currency: Optional[str] = None,\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"\n        Upsert a Budget for a Category and Date\n\n        Use this endpoint to update an existing budget or insert a new budget for\n        a particular category and date.\n\n        Note: Lunch Money currently only supports monthly budgets, so your date must\n        always be the start of a month (eg. 2021-04-01)\n\n        If this is a sub-category, the response will include the updated category\n        group's budget. This is because setting a sub-category may also update\n        the category group's overall budget.\n\n        https://lunchmoney.dev/#upsert-budget\n\n        Parameters\n        ----------\n        start_date : date\n            Start date for the budget period. Lunch Money currently only supports monthly budgets,\n            so your date must always be the start of a month (eg. 2021-04-01)\n        category_id: int\n            Unique identifier for the category\n        amount: float\n            Amount for budget\n        currency: Optional[str]\n            Currency for the budgeted amount (optional). If empty, will default to your primary\n            currency\n\n        Returns\n        -------\n        Optional[Dict[str, Any]]\n        \"\"\"\n        body = BudgetParamsPut(\n            start_date=start_date,\n            category_id=category_id,\n            amount=amount,\n            currency=currency,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[APIConfig.LUNCHMONEY_BUDGET],\n            payload=body,\n        )\n\n        return response_data[\"category_group\"]\n\n    def remove_budget(self, start_date: datetime.date, category_id: int) -> bool:\n        \"\"\"\n        Unset an Existing Budget for a Particular Category in a Particular Month\n\n        Parameters\n        ----------\n        start_date : date\n            Start date for the budget period. Lunch Money currently only supports monthly budgets,\n            so your date must always be the start of a month (eg. 2021-04-01)\n        category_id: int\n            Unique identifier for the category\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        params = BudgetParamsRemove(\n            start_date=start_date, category_id=category_id\n        ).model_dump()\n        response_data = self.make_request(\n            method=self.Methods.DELETE,\n            url_path=[APIConfig.LUNCHMONEY_BUDGET],\n            params=params,\n        )\n        return response_data\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetsClient.get_budgets","title":"get_budgets(start_date, end_date)","text":"

Get Monthly Budgets

Get full details on the budgets for all categories between a certain time period. The budgeted and spending amounts will be an aggregate across this time period. (https://lunchmoney.dev/#plaid-accounts-object)

Returns:

Type Description List[BudgetObject] Source code in lunchable/models/budgets.py
def get_budgets(\n    self, start_date: datetime.date, end_date: datetime.date\n) -> List[BudgetObject]:\n    \"\"\"\n    Get Monthly Budgets\n\n    Get full details on the budgets for all categories between a certain time\n    period. The budgeted and spending amounts will be an aggregate across this\n    time period. (https://lunchmoney.dev/#plaid-accounts-object)\n\n    Returns\n    -------\n    List[BudgetObject]\n    \"\"\"\n    params = BudgetParamsGet(start_date=start_date, end_date=end_date).model_dump()\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=[APIConfig.LUNCHMONEY_BUDGET],\n        params=params,\n    )\n    budget_objects = [BudgetObject.model_validate(item) for item in response_data]\n    return budget_objects\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetsClient.remove_budget","title":"remove_budget(start_date, category_id)","text":"

Unset an Existing Budget for a Particular Category in a Particular Month

Parameters:

Name Type Description Default start_date date

Start date for the budget period. Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

required category_id int

Unique identifier for the category

required

Returns:

Type Description bool Source code in lunchable/models/budgets.py
def remove_budget(self, start_date: datetime.date, category_id: int) -> bool:\n    \"\"\"\n    Unset an Existing Budget for a Particular Category in a Particular Month\n\n    Parameters\n    ----------\n    start_date : date\n        Start date for the budget period. Lunch Money currently only supports monthly budgets,\n        so your date must always be the start of a month (eg. 2021-04-01)\n    category_id: int\n        Unique identifier for the category\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    params = BudgetParamsRemove(\n        start_date=start_date, category_id=category_id\n    ).model_dump()\n    response_data = self.make_request(\n        method=self.Methods.DELETE,\n        url_path=[APIConfig.LUNCHMONEY_BUDGET],\n        params=params,\n    )\n    return response_data\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetsClient.upsert_budget","title":"upsert_budget(start_date, category_id, amount, currency=None)","text":"

Upsert a Budget for a Category and Date

Use this endpoint to update an existing budget or insert a new budget for a particular category and date.

Note: Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

If this is a sub-category, the response will include the updated category group's budget. This is because setting a sub-category may also update the category group's overall budget.

https://lunchmoney.dev/#upsert-budget

Parameters:

Name Type Description Default start_date date

Start date for the budget period. Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

required category_id int

Unique identifier for the category

required amount float

Amount for budget

required currency Optional[str]

Currency for the budgeted amount (optional). If empty, will default to your primary currency

None

Returns:

Type Description Optional[Dict[str, Any]] Source code in lunchable/models/budgets.py
def upsert_budget(\n    self,\n    start_date: datetime.date,\n    category_id: int,\n    amount: float,\n    currency: Optional[str] = None,\n) -> Optional[Dict[str, Any]]:\n    \"\"\"\n    Upsert a Budget for a Category and Date\n\n    Use this endpoint to update an existing budget or insert a new budget for\n    a particular category and date.\n\n    Note: Lunch Money currently only supports monthly budgets, so your date must\n    always be the start of a month (eg. 2021-04-01)\n\n    If this is a sub-category, the response will include the updated category\n    group's budget. This is because setting a sub-category may also update\n    the category group's overall budget.\n\n    https://lunchmoney.dev/#upsert-budget\n\n    Parameters\n    ----------\n    start_date : date\n        Start date for the budget period. Lunch Money currently only supports monthly budgets,\n        so your date must always be the start of a month (eg. 2021-04-01)\n    category_id: int\n        Unique identifier for the category\n    amount: float\n        Amount for budget\n    currency: Optional[str]\n        Currency for the budgeted amount (optional). If empty, will default to your primary\n        currency\n\n    Returns\n    -------\n    Optional[Dict[str, Any]]\n    \"\"\"\n    body = BudgetParamsPut(\n        start_date=start_date,\n        category_id=category_id,\n        amount=amount,\n        currency=currency,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[APIConfig.LUNCHMONEY_BUDGET],\n        payload=body,\n    )\n\n    return response_data[\"category_group\"]\n
"},{"location":"reference/models/categories/","title":"categories","text":"

Lunch Money - Categories

https://lunchmoney.dev/#categories

"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient","title":"CategoriesClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Categories Interactions

Source code in lunchable/models/categories.py
class CategoriesClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Categories Interactions\n    \"\"\"\n\n    def get_categories(self) -> List[CategoriesObject]:\n        \"\"\"\n        Get Spending categories\n\n        Use this endpoint to get a list of all categories associated with the user's account.\n        https://lunchmoney.dev/#get-all-categories\n\n        Returns\n        -------\n        List[CategoriesObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_CATEGORIES\n        )\n        categories = response_data[\"categories\"]\n        category_objects = [\n            CategoriesObject.model_validate(item) for item in categories\n        ]\n        return category_objects\n\n    def insert_category(\n        self,\n        name: str,\n        description: Optional[str] = None,\n        is_income: Optional[bool] = False,\n        exclude_from_budget: Optional[bool] = False,\n        exclude_from_totals: Optional[bool] = False,\n    ) -> int:\n        \"\"\"\n        Create a Spending Category\n\n        Use this to create a single category\n        https://lunchmoney.dev/#create-category\n\n        Parameters\n        ----------\n        name: str\n            Name of category. Must be between 1 and 40 characters.\n        description: Optional[str]\n            Description of category. Must be less than 140 categories. Defaults to None.\n        is_income: Optional[bool]\n            Whether or not transactions in this category should be treated as income.\n            Defaults to False.\n        exclude_from_budget: Optional[bool]\n            Whether or not transactions in this category should be excluded from budgets.\n            Defaults to False.\n        exclude_from_totals: Optional[bool]\n            Whether or not transactions in this category should be excluded from\n            calculated totals. Defaults to False.\n\n        Returns\n        -------\n        int\n            ID of the newly created category\n        \"\"\"\n        category_body = ModelCreateCategory(\n            name=name,\n            description=description,\n            is_income=is_income,\n            exclude_from_budget=exclude_from_budget,\n            exclude_from_totals=exclude_from_totals,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=APIConfig.LUNCHMONEY_CATEGORIES,\n            payload=category_body,\n        )\n        return response_data[\"category_id\"]\n\n    def get_category(self, category_id: int) -> CategoriesObject:\n        \"\"\"\n        Get single category\n\n        Use this endpoint to get hydrated details on a single category. Note that if\n        this category is part of a category group, its properties (is_income,\n        exclude_from_budget, exclude_from_totals) will inherit from the category group.\n\n        https://lunchmoney.dev/#get-single-category\n\n        Parameters\n        ----------\n        category_id : int\n            Id of the Lunch Money Category\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n        )\n        return CategoriesObject.model_validate(response_data)\n\n    def remove_category(self, category_id: int) -> bool:\n        \"\"\"\n        Delete a single category\n\n        Use this endpoint to delete a single category or category group. This will\n        only work if there are no dependencies, such as existing budgets for the\n        category, categorized transactions, categorized recurring items, etc. If\n        there are dependents, this endpoint will return what the dependents are\n        and how many there are.\n\n        https://lunchmoney.dev/#delete-category\n\n        Parameters\n        ----------\n        category_id : int\n            Id of the Lunch Money Category\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.DELETE,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n        )\n        if response_data is not True:\n            raise LunchMoneyError(\n                f\"That Category ({category_id}) has Dependents: \"\n                f\"{json.dumps(response_data, indent=4)}\"\n            )\n        return response_data\n\n    def remove_category_force(self, category_id: int) -> bool:\n        \"\"\"\n        Forcefully delete a single category\n\n        Use this endpoint to force delete a single category or category group and\n        along with it, disassociate the category from any transactions, recurring\n        items, budgets, etc.\n\n        Note: it is best practice to first try the Delete Category endpoint to ensure\n        you don't accidentally delete any data. Disassociation/deletion of the data\n        arising from this endpoint is irreversible!\n\n        https://lunchmoney.dev/#force-delete-category\n\n        Parameters\n        ----------\n        category_id : int\n            Id of the Lunch Money Category\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.DELETE,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id, \"force\"],\n        )\n        return response_data\n\n    def update_category(\n        self,\n        category_id: int,\n        name: Optional[str] = None,\n        description: Optional[str] = None,\n        is_income: Optional[bool] = None,\n        exclude_from_budget: Optional[bool] = None,\n        exclude_from_totals: Optional[bool] = None,\n        group_id: Optional[int] = None,\n    ) -> bool:\n        \"\"\"\n        Update a single category\n\n        Use this endpoint to update the properties for a single category or category group\n\n        https://lunchmoney.dev/#update-category\n\n        Parameters\n        ----------\n        category_id : int\n            Id of the Lunch Money Category\n        name: str\n            Name of category. Must be between 1 and 40 characters.\n        description: Optional[str]\n            Description of category. Must be less than 140 categories. Defaults to None.\n        is_income: Optional[bool]\n            Whether or not transactions in this category should be treated as income.\n            Defaults to False.\n        exclude_from_budget: Optional[bool]\n            Whether or not transactions in this category should be excluded from budgets.\n            Defaults to False.\n        exclude_from_totals: Optional[bool]\n            Whether or not transactions in this category should be excluded from\n            calculated totals. Defaults to False.\n        group_id: Optional[int]\n            For a category, set the group_id to include it in a category group\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        payload = _CategoriesParamsPut(\n            name=name,\n            description=description,\n            is_income=is_income,\n            exclude_from_budget=exclude_from_budget,\n            exclude_from_totals=exclude_from_totals,\n            group_id=group_id,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n            payload=payload,\n        )\n        return response_data\n\n    def insert_category_group(\n        self,\n        name: str,\n        description: Optional[str] = None,\n        is_income: Optional[bool] = False,\n        exclude_from_budget: Optional[bool] = False,\n        exclude_from_totals: Optional[bool] = False,\n        category_ids: Optional[List[int]] = None,\n        new_categories: Optional[List[str]] = None,\n    ) -> int:\n        \"\"\"\n        Create a Spending Category Group\n\n        Use this endpoint to create a single category group\n        https://lunchmoney.dev/#create-category-group\n\n        Parameters\n        ----------\n        name: str\n            Name of category. Must be between 1 and 40 characters.\n        description: Optional[str]\n            Description of category. Must be less than 140 categories. Defaults to None.\n        is_income: Optional[bool]\n            Whether or not transactions in this category should be treated as income.\n            Defaults to False.\n        exclude_from_budget: Optional[bool]\n            Whether or not transactions in this category should be excluded from budgets.\n            Defaults to False.\n        exclude_from_totals: Optional[bool]\n            Whether or not transactions in this category should be excluded from\n            calculated totals. Defaults to False.\n        category_ids: Optional[List[int]]\n            Array of category_id to include in the category group.\n        new_categories: Optional[List[str]]\n            Array of strings representing new categories to create and subsequently\n            include in the category group.\n\n        Returns\n        -------\n        int\n            ID of the newly created category group\n        \"\"\"\n        payload = _CategoriesParamsPost(\n            name=name,\n            description=description,\n            is_income=is_income,\n            exclude_from_budget=exclude_from_budget,\n            exclude_from_totals=exclude_from_totals,\n            category_ids=category_ids,\n            new_categories=new_categories,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, \"group\"],\n            payload=payload,\n        )\n        return response_data[\"category_id\"]\n\n    def insert_into_category_group(\n        self,\n        category_group_id: int,\n        category_ids: Optional[List[int]] = None,\n        new_categories: Optional[List[str]] = None,\n    ) -> CategoriesObject:\n        \"\"\"\n        Add to a Category Group\n\n        Use this endpoint to add categories (either existing or new) to a single\n        category group\n\n        https://lunchmoney.dev/#add-to-category-group\n\n        Parameters\n        ----------\n        category_group_id: int\n            Id of the Lunch Money Category Group\n        category_ids: Optional[List[int]]\n            Array of category_id to include in the category group.\n        new_categories: Optional[List[str]]\n            Array of strings representing new categories to create and subsequently\n            include in the category group.\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        payload = _CategoriesAddParamsPost(\n            category_ids=category_ids, new_categories=new_categories\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[\n                APIConfig.LUNCHMONEY_CATEGORIES,\n                \"group\",\n                category_group_id,\n                \"add\",\n            ],\n            payload=payload,\n        )\n        return CategoriesObject.model_validate(response_data)\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.get_categories","title":"get_categories()","text":"

Get Spending categories

Use this endpoint to get a list of all categories associated with the user's account. https://lunchmoney.dev/#get-all-categories

Returns:

Type Description List[CategoriesObject] Source code in lunchable/models/categories.py
def get_categories(self) -> List[CategoriesObject]:\n    \"\"\"\n    Get Spending categories\n\n    Use this endpoint to get a list of all categories associated with the user's account.\n    https://lunchmoney.dev/#get-all-categories\n\n    Returns\n    -------\n    List[CategoriesObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_CATEGORIES\n    )\n    categories = response_data[\"categories\"]\n    category_objects = [\n        CategoriesObject.model_validate(item) for item in categories\n    ]\n    return category_objects\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.get_category","title":"get_category(category_id)","text":"

Get single category

Use this endpoint to get hydrated details on a single category. Note that if this category is part of a category group, its properties (is_income, exclude_from_budget, exclude_from_totals) will inherit from the category group.

https://lunchmoney.dev/#get-single-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description CategoriesObject Source code in lunchable/models/categories.py
def get_category(self, category_id: int) -> CategoriesObject:\n    \"\"\"\n    Get single category\n\n    Use this endpoint to get hydrated details on a single category. Note that if\n    this category is part of a category group, its properties (is_income,\n    exclude_from_budget, exclude_from_totals) will inherit from the category group.\n\n    https://lunchmoney.dev/#get-single-category\n\n    Parameters\n    ----------\n    category_id : int\n        Id of the Lunch Money Category\n\n    Returns\n    -------\n    CategoriesObject\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n    )\n    return CategoriesObject.model_validate(response_data)\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.insert_category","title":"insert_category(name, description=None, is_income=False, exclude_from_budget=False, exclude_from_totals=False)","text":"

Create a Spending Category

Use this to create a single category https://lunchmoney.dev/#create-category

Parameters:

Name Type Description Default name str

Name of category. Must be between 1 and 40 characters.

required description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

False exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

False exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

False

Returns:

Type Description int

ID of the newly created category

Source code in lunchable/models/categories.py
def insert_category(\n    self,\n    name: str,\n    description: Optional[str] = None,\n    is_income: Optional[bool] = False,\n    exclude_from_budget: Optional[bool] = False,\n    exclude_from_totals: Optional[bool] = False,\n) -> int:\n    \"\"\"\n    Create a Spending Category\n\n    Use this to create a single category\n    https://lunchmoney.dev/#create-category\n\n    Parameters\n    ----------\n    name: str\n        Name of category. Must be between 1 and 40 characters.\n    description: Optional[str]\n        Description of category. Must be less than 140 categories. Defaults to None.\n    is_income: Optional[bool]\n        Whether or not transactions in this category should be treated as income.\n        Defaults to False.\n    exclude_from_budget: Optional[bool]\n        Whether or not transactions in this category should be excluded from budgets.\n        Defaults to False.\n    exclude_from_totals: Optional[bool]\n        Whether or not transactions in this category should be excluded from\n        calculated totals. Defaults to False.\n\n    Returns\n    -------\n    int\n        ID of the newly created category\n    \"\"\"\n    category_body = ModelCreateCategory(\n        name=name,\n        description=description,\n        is_income=is_income,\n        exclude_from_budget=exclude_from_budget,\n        exclude_from_totals=exclude_from_totals,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=APIConfig.LUNCHMONEY_CATEGORIES,\n        payload=category_body,\n    )\n    return response_data[\"category_id\"]\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.insert_category_group","title":"insert_category_group(name, description=None, is_income=False, exclude_from_budget=False, exclude_from_totals=False, category_ids=None, new_categories=None)","text":"

Create a Spending Category Group

Use this endpoint to create a single category group https://lunchmoney.dev/#create-category-group

Parameters:

Name Type Description Default name str

Name of category. Must be between 1 and 40 characters.

required description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

False exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

False exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

False category_ids Optional[List[int]]

Array of category_id to include in the category group.

None new_categories Optional[List[str]]

Array of strings representing new categories to create and subsequently include in the category group.

None

Returns:

Type Description int

ID of the newly created category group

Source code in lunchable/models/categories.py
def insert_category_group(\n    self,\n    name: str,\n    description: Optional[str] = None,\n    is_income: Optional[bool] = False,\n    exclude_from_budget: Optional[bool] = False,\n    exclude_from_totals: Optional[bool] = False,\n    category_ids: Optional[List[int]] = None,\n    new_categories: Optional[List[str]] = None,\n) -> int:\n    \"\"\"\n    Create a Spending Category Group\n\n    Use this endpoint to create a single category group\n    https://lunchmoney.dev/#create-category-group\n\n    Parameters\n    ----------\n    name: str\n        Name of category. Must be between 1 and 40 characters.\n    description: Optional[str]\n        Description of category. Must be less than 140 categories. Defaults to None.\n    is_income: Optional[bool]\n        Whether or not transactions in this category should be treated as income.\n        Defaults to False.\n    exclude_from_budget: Optional[bool]\n        Whether or not transactions in this category should be excluded from budgets.\n        Defaults to False.\n    exclude_from_totals: Optional[bool]\n        Whether or not transactions in this category should be excluded from\n        calculated totals. Defaults to False.\n    category_ids: Optional[List[int]]\n        Array of category_id to include in the category group.\n    new_categories: Optional[List[str]]\n        Array of strings representing new categories to create and subsequently\n        include in the category group.\n\n    Returns\n    -------\n    int\n        ID of the newly created category group\n    \"\"\"\n    payload = _CategoriesParamsPost(\n        name=name,\n        description=description,\n        is_income=is_income,\n        exclude_from_budget=exclude_from_budget,\n        exclude_from_totals=exclude_from_totals,\n        category_ids=category_ids,\n        new_categories=new_categories,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, \"group\"],\n        payload=payload,\n    )\n    return response_data[\"category_id\"]\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.insert_into_category_group","title":"insert_into_category_group(category_group_id, category_ids=None, new_categories=None)","text":"

Add to a Category Group

Use this endpoint to add categories (either existing or new) to a single category group

https://lunchmoney.dev/#add-to-category-group

Parameters:

Name Type Description Default category_group_id int

Id of the Lunch Money Category Group

required category_ids Optional[List[int]]

Array of category_id to include in the category group.

None new_categories Optional[List[str]]

Array of strings representing new categories to create and subsequently include in the category group.

None

Returns:

Type Description CategoriesObject Source code in lunchable/models/categories.py
def insert_into_category_group(\n    self,\n    category_group_id: int,\n    category_ids: Optional[List[int]] = None,\n    new_categories: Optional[List[str]] = None,\n) -> CategoriesObject:\n    \"\"\"\n    Add to a Category Group\n\n    Use this endpoint to add categories (either existing or new) to a single\n    category group\n\n    https://lunchmoney.dev/#add-to-category-group\n\n    Parameters\n    ----------\n    category_group_id: int\n        Id of the Lunch Money Category Group\n    category_ids: Optional[List[int]]\n        Array of category_id to include in the category group.\n    new_categories: Optional[List[str]]\n        Array of strings representing new categories to create and subsequently\n        include in the category group.\n\n    Returns\n    -------\n    CategoriesObject\n    \"\"\"\n    payload = _CategoriesAddParamsPost(\n        category_ids=category_ids, new_categories=new_categories\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[\n            APIConfig.LUNCHMONEY_CATEGORIES,\n            \"group\",\n            category_group_id,\n            \"add\",\n        ],\n        payload=payload,\n    )\n    return CategoriesObject.model_validate(response_data)\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.remove_category","title":"remove_category(category_id)","text":"

Delete a single category

Use this endpoint to delete a single category or category group. This will only work if there are no dependencies, such as existing budgets for the category, categorized transactions, categorized recurring items, etc. If there are dependents, this endpoint will return what the dependents are and how many there are.

https://lunchmoney.dev/#delete-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description bool Source code in lunchable/models/categories.py
def remove_category(self, category_id: int) -> bool:\n    \"\"\"\n    Delete a single category\n\n    Use this endpoint to delete a single category or category group. This will\n    only work if there are no dependencies, such as existing budgets for the\n    category, categorized transactions, categorized recurring items, etc. If\n    there are dependents, this endpoint will return what the dependents are\n    and how many there are.\n\n    https://lunchmoney.dev/#delete-category\n\n    Parameters\n    ----------\n    category_id : int\n        Id of the Lunch Money Category\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.DELETE,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n    )\n    if response_data is not True:\n        raise LunchMoneyError(\n            f\"That Category ({category_id}) has Dependents: \"\n            f\"{json.dumps(response_data, indent=4)}\"\n        )\n    return response_data\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.remove_category_force","title":"remove_category_force(category_id)","text":"

Forcefully delete a single category

Use this endpoint to force delete a single category or category group and along with it, disassociate the category from any transactions, recurring items, budgets, etc.

Note: it is best practice to first try the Delete Category endpoint to ensure you don't accidentally delete any data. Disassociation/deletion of the data arising from this endpoint is irreversible!

https://lunchmoney.dev/#force-delete-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description bool Source code in lunchable/models/categories.py
def remove_category_force(self, category_id: int) -> bool:\n    \"\"\"\n    Forcefully delete a single category\n\n    Use this endpoint to force delete a single category or category group and\n    along with it, disassociate the category from any transactions, recurring\n    items, budgets, etc.\n\n    Note: it is best practice to first try the Delete Category endpoint to ensure\n    you don't accidentally delete any data. Disassociation/deletion of the data\n    arising from this endpoint is irreversible!\n\n    https://lunchmoney.dev/#force-delete-category\n\n    Parameters\n    ----------\n    category_id : int\n        Id of the Lunch Money Category\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.DELETE,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id, \"force\"],\n    )\n    return response_data\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.update_category","title":"update_category(category_id, name=None, description=None, is_income=None, exclude_from_budget=None, exclude_from_totals=None, group_id=None)","text":"

Update a single category

Use this endpoint to update the properties for a single category or category group

https://lunchmoney.dev/#update-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required name Optional[str]

Name of category. Must be between 1 and 40 characters.

None description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

None exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

None exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

None group_id Optional[int]

For a category, set the group_id to include it in a category group

None

Returns:

Type Description bool Source code in lunchable/models/categories.py
def update_category(\n    self,\n    category_id: int,\n    name: Optional[str] = None,\n    description: Optional[str] = None,\n    is_income: Optional[bool] = None,\n    exclude_from_budget: Optional[bool] = None,\n    exclude_from_totals: Optional[bool] = None,\n    group_id: Optional[int] = None,\n) -> bool:\n    \"\"\"\n    Update a single category\n\n    Use this endpoint to update the properties for a single category or category group\n\n    https://lunchmoney.dev/#update-category\n\n    Parameters\n    ----------\n    category_id : int\n        Id of the Lunch Money Category\n    name: str\n        Name of category. Must be between 1 and 40 characters.\n    description: Optional[str]\n        Description of category. Must be less than 140 categories. Defaults to None.\n    is_income: Optional[bool]\n        Whether or not transactions in this category should be treated as income.\n        Defaults to False.\n    exclude_from_budget: Optional[bool]\n        Whether or not transactions in this category should be excluded from budgets.\n        Defaults to False.\n    exclude_from_totals: Optional[bool]\n        Whether or not transactions in this category should be excluded from\n        calculated totals. Defaults to False.\n    group_id: Optional[int]\n        For a category, set the group_id to include it in a category group\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    payload = _CategoriesParamsPut(\n        name=name,\n        description=description,\n        is_income=is_income,\n        exclude_from_budget=exclude_from_budget,\n        exclude_from_totals=exclude_from_totals,\n        group_id=group_id,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n        payload=payload,\n    )\n    return response_data\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesObject","title":"CategoriesObject","text":"

Bases: LunchableModel

Lunch Money Spending Categories

https://lunchmoney.dev/#categories-object

Parameters:

Name Type Description Default id int

A unique identifier for the category.

required name str

The name of the category. Must be between 1 and 40 characters.

required description str | None

The description of the category. Must not exceed 140 characters.

None is_income bool

If true, the transactions in this category will be treated as income.

required exclude_from_budget bool

If true, the transactions in this category will be excluded from the budget.

required exclude_from_totals bool

If true, the transactions in this category will be excluded from totals.

required updated_at datetime | None

The date and time of when the category was last updated (in the ISO 8601 extended format).

None created_at datetime | None

The date and time of when the category was created (in the ISO 8601 extended format).

None is_group bool

If true, the category is a group that can be a parent to other categories.

required group_id int | None

The ID of a category group (or null if the category doesn't belong to a category group).

None children List[CategoryChild] | None

For category groups, this will populate with the categories nested within and include id, name, description and created_at fields.

None Source code in lunchable/models/categories.py
class CategoriesObject(LunchableModel):\n    \"\"\"\n    Lunch Money Spending Categories\n\n    https://lunchmoney.dev/#categories-object\n    \"\"\"\n\n    _name_description = \"The name of the category. Must be between 1 and 40 characters.\"\n    _description_description = (\n        \"The description of the category. Must not exceed 140 characters.\"\n    )\n    _is_income_description = (\n        \"If true, the transactions in this category will be treated as income.\"\n    )\n    _exclude_from_budget_description = \"\"\"\n    If true, the transactions in this category will be excluded from the budget.\n    \"\"\"\n    _exclude_from_totals_description = \"\"\"\n    If true, the transactions in this category will be excluded from totals.\n    \"\"\"\n    _updated_at_description = \"\"\"\n    The date and time of when the category was last updated (in the ISO 8601 extended format).\n    \"\"\"\n    _created_at_description = \"\"\"\n    The date and time of when the category was created (in the ISO 8601 extended format).\n    \"\"\"\n    _is_group_description = \"\"\"\n    If true, the category is a group that can be a parent to other categories.\n    \"\"\"\n    _group_id_description = \"\"\"\n    The ID of a category group (or null if the category doesn't belong to a category group).\n    \"\"\"\n    _children_description = (\n        \"For category groups, this will populate with the \"\n        \"categories nested within and include id, name, \"\n        \"description and created_at fields.\"\n    )\n\n    id: int = Field(description=\"A unique identifier for the category.\")\n    name: str = Field(min_length=1, max_length=40, description=_name_description)\n    description: Optional[str] = Field(\n        None, max_length=140, description=_description_description\n    )\n    is_income: bool = Field(description=_is_income_description)\n    exclude_from_budget: bool = Field(description=_exclude_from_budget_description)\n    exclude_from_totals: bool = Field(description=_exclude_from_totals_description)\n    updated_at: Optional[datetime.datetime] = Field(\n        None, description=_updated_at_description\n    )\n    created_at: Optional[datetime.datetime] = Field(\n        None, description=_created_at_description\n    )\n    is_group: bool = Field(description=_is_group_description)\n    group_id: Optional[int] = Field(None, description=_group_id_description)\n    children: Optional[List[CategoryChild]] = Field(\n        None, description=_children_description\n    )\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoryChild","title":"CategoryChild","text":"

Bases: LunchableModel

Child Entry on the Category Object

Parameters:

Name Type Description Default id int required name str required description str | None None created_at datetime | None None Source code in lunchable/models/categories.py
class CategoryChild(LunchableModel):\n    \"\"\"\n    Child Entry on the Category Object\n    \"\"\"\n\n    id: int\n    name: str = Field(min_length=1, max_length=40)\n    description: Optional[str] = Field(None, max_length=140)\n    created_at: Optional[datetime.datetime] = None\n
"},{"location":"reference/models/categories/#lunchable.models.categories.ModelCreateCategory","title":"ModelCreateCategory","text":"

Bases: LunchableModel

https://lunchmoney.dev/#create-category

Parameters:

Name Type Description Default name str required description str | None None is_income bool | None False exclude_from_budget bool | None False exclude_from_totals bool | None False Source code in lunchable/models/categories.py
class ModelCreateCategory(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#create-category\n    \"\"\"\n\n    name: str\n    description: Optional[str] = None\n    is_income: Optional[bool] = False\n    exclude_from_budget: Optional[bool] = False\n    exclude_from_totals: Optional[bool] = False\n
"},{"location":"reference/models/crypto/","title":"crypto","text":"

Lunch Money - Crypto

https://lunchmoney.dev/#crypto

"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoClient","title":"CryptoClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Tag Interactions

Source code in lunchable/models/crypto.py
class CryptoClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Tag Interactions\n    \"\"\"\n\n    def get_crypto(self) -> List[CryptoObject]:\n        \"\"\"\n        Get Crypto Assets\n\n        Use this endpoint to get a list of all cryptocurrency assets associated\n        with the user's account. Both crypto balances from synced and manual\n        accounts will be returned.\n\n        https://lunchmoney.dev/#get-all-crypto\n\n        Returns\n        -------\n        List[CryptoObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_CRYPTO\n        )\n        crypto_data = response_data[\"crypto\"]\n        crypto_objects = [CryptoObject.model_validate(item) for item in crypto_data]\n        return crypto_objects\n\n    def update_crypto(\n        self,\n        crypto_id: int,\n        name: Optional[str] = None,\n        display_name: Optional[str] = None,\n        institution_name: Optional[str] = None,\n        balance: Optional[float] = None,\n        currency: Optional[str] = None,\n    ) -> CryptoObject:\n        \"\"\"\n        Update a Manual Crypto Asset\n\n        Use this endpoint to update a single manually-managed crypto asset (does not include\n        assets received from syncing with your wallet/exchange/etc). These are denoted by\n        source: manual from the GET call above.\n\n        https://lunchmoney.dev/#update-manual-crypto-asset\n\n        Parameters\n        ----------\n        crypto_id: int\n            ID of the crypto asset to update\n        name: Optional[str]\n            Official or full name of the account. Max 45 characters\n        display_name: Optional[str]\n            Display name for the account. Max 25 characters\n        institution_name: Optional[str]\n            Name of provider that holds the account. Max 50 characters\n        balance: Optional[float]\n            Numeric value of the current balance of the account. Do not include any\n            special characters aside from a decimal point!\n        currency: Optional[str]\n            Cryptocurrency that is supported for manual tracking in our database\n\n        Returns\n        -------\n        CryptoObject\n        \"\"\"\n        crypto_body = CryptoParamsPut(\n            name=name,\n            display_name=display_name,\n            institution_name=institution_name,\n            balance=balance,\n            currency=currency,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[\n                APIConfig.LUNCHMONEY_CRYPTO,\n                APIConfig.LUNCHMONEY_CRYPTO_MANUAL,\n                crypto_id,\n            ],\n            payload=crypto_body,\n        )\n        crypto = CryptoObject.model_validate(response_data)\n        return crypto\n
"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoClient.get_crypto","title":"get_crypto()","text":"

Get Crypto Assets

Use this endpoint to get a list of all cryptocurrency assets associated with the user's account. Both crypto balances from synced and manual accounts will be returned.

https://lunchmoney.dev/#get-all-crypto

Returns:

Type Description List[CryptoObject] Source code in lunchable/models/crypto.py
def get_crypto(self) -> List[CryptoObject]:\n    \"\"\"\n    Get Crypto Assets\n\n    Use this endpoint to get a list of all cryptocurrency assets associated\n    with the user's account. Both crypto balances from synced and manual\n    accounts will be returned.\n\n    https://lunchmoney.dev/#get-all-crypto\n\n    Returns\n    -------\n    List[CryptoObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_CRYPTO\n    )\n    crypto_data = response_data[\"crypto\"]\n    crypto_objects = [CryptoObject.model_validate(item) for item in crypto_data]\n    return crypto_objects\n
"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoClient.update_crypto","title":"update_crypto(crypto_id, name=None, display_name=None, institution_name=None, balance=None, currency=None)","text":"

Update a Manual Crypto Asset

Use this endpoint to update a single manually-managed crypto asset (does not include assets received from syncing with your wallet/exchange/etc). These are denoted by source: manual from the GET call above.

https://lunchmoney.dev/#update-manual-crypto-asset

Parameters:

Name Type Description Default crypto_id int

ID of the crypto asset to update

required name Optional[str]

Official or full name of the account. Max 45 characters

None display_name Optional[str]

Display name for the account. Max 25 characters

None institution_name Optional[str]

Name of provider that holds the account. Max 50 characters

None balance Optional[float]

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point!

None currency Optional[str]

Cryptocurrency that is supported for manual tracking in our database

None

Returns:

Type Description CryptoObject Source code in lunchable/models/crypto.py
def update_crypto(\n    self,\n    crypto_id: int,\n    name: Optional[str] = None,\n    display_name: Optional[str] = None,\n    institution_name: Optional[str] = None,\n    balance: Optional[float] = None,\n    currency: Optional[str] = None,\n) -> CryptoObject:\n    \"\"\"\n    Update a Manual Crypto Asset\n\n    Use this endpoint to update a single manually-managed crypto asset (does not include\n    assets received from syncing with your wallet/exchange/etc). These are denoted by\n    source: manual from the GET call above.\n\n    https://lunchmoney.dev/#update-manual-crypto-asset\n\n    Parameters\n    ----------\n    crypto_id: int\n        ID of the crypto asset to update\n    name: Optional[str]\n        Official or full name of the account. Max 45 characters\n    display_name: Optional[str]\n        Display name for the account. Max 25 characters\n    institution_name: Optional[str]\n        Name of provider that holds the account. Max 50 characters\n    balance: Optional[float]\n        Numeric value of the current balance of the account. Do not include any\n        special characters aside from a decimal point!\n    currency: Optional[str]\n        Cryptocurrency that is supported for manual tracking in our database\n\n    Returns\n    -------\n    CryptoObject\n    \"\"\"\n    crypto_body = CryptoParamsPut(\n        name=name,\n        display_name=display_name,\n        institution_name=institution_name,\n        balance=balance,\n        currency=currency,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[\n            APIConfig.LUNCHMONEY_CRYPTO,\n            APIConfig.LUNCHMONEY_CRYPTO_MANUAL,\n            crypto_id,\n        ],\n        payload=crypto_body,\n    )\n    crypto = CryptoObject.model_validate(response_data)\n    return crypto\n
"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoObject","title":"CryptoObject","text":"

Bases: LunchableModel

Crypto Asset Object

https://lunchmoney.dev/#crypto-object

Parameters:

Name Type Description Default id int

Unique identifier for a manual crypto account (no ID for synced accounts)

required zabo_account_id int | None

Unique identifier for a synced crypto account (no ID for manual accounts, multiple currencies may have the same zabo_account_id)

None source str

synced (this account is synced via a wallet, exchange, etc.) or manual (this account balance is managed manually)

required name str

Name of the crypto asset

required display_name str | None

Display name of the crypto asset (as set by user)

None balance float

Current balance

required balance_as_of datetime | None

Date/time the balance was last updated in ISO 8601 extended format

None currency str | None

Abbreviation for the cryptocurrency

None status str | None

The current status of the crypto account. Either active or in error.

None institution_name str | None

Name of provider holding the asset

None created_at datetime

Date/time the asset was created in ISO 8601 extended format

required Source code in lunchable/models/crypto.py
class CryptoObject(LunchableModel):\n    \"\"\"\n    Crypto Asset Object\n\n    https://lunchmoney.dev/#crypto-object\n    \"\"\"\n\n    _id_description = (\n        \"Unique identifier for a manual crypto account (no ID for synced accounts)\"\n    )\n    _zabo_account_id_description = \"\"\"\n    Unique identifier for a synced crypto account (no ID for manual accounts,\n    multiple currencies may have the same zabo_account_id)\n    \"\"\"\n    _source_description = \"\"\"\n    `synced` (this account is synced via a wallet, exchange, etc.) or `manual` (this account\n    balance is managed manually)\n    \"\"\"\n    _display_name_description = \"Display name of the crypto asset (as set by user)\"\n    _balance_as_of_description = \"\"\"\n    Date/time the balance was last updated in ISO 8601 extended format\n    \"\"\"\n    _status_description = (\n        \"The current status of the crypto account. Either active or in error.\"\n    )\n    _created_at_description = (\n        \"Date/time the asset was created in ISO 8601 extended format\"\n    )\n\n    id: int = Field(description=_id_description)\n    zabo_account_id: Optional[int] = Field(\n        None, description=_zabo_account_id_description\n    )\n    source: str = Field(description=_source_description)\n    name: str = Field(description=\"Name of the crypto asset\")\n    display_name: Optional[str] = Field(None, description=_display_name_description)\n    balance: float = Field(description=\"Current balance\")\n    balance_as_of: Optional[datetime.datetime] = Field(\n        None, description=_balance_as_of_description\n    )\n    currency: Optional[str] = Field(\n        None, description=\"Abbreviation for the cryptocurrency\"\n    )\n    status: Optional[str] = Field(None, description=_status_description)\n    institution_name: Optional[str] = Field(\n        default=None, description=\"Name of provider holding the asset\"\n    )\n    created_at: datetime.datetime = Field(description=_created_at_description)\n
"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoParamsPut","title":"CryptoParamsPut","text":"

Bases: LunchableModel

https://lunchmoney.dev/#update-manual-crypto-asset

Parameters:

Name Type Description Default name str | None None display_name str | None None institution_name str | None None balance float | None None currency str | None None Source code in lunchable/models/crypto.py
class CryptoParamsPut(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#update-manual-crypto-asset\n    \"\"\"\n\n    name: Optional[str] = None\n    display_name: Optional[str] = None\n    institution_name: Optional[str] = None\n    balance: Optional[float] = None\n    currency: Optional[str] = None\n
"},{"location":"reference/models/plaid_accounts/","title":"plaid_accounts","text":"

Lunch Money - Plaid Accounts

https://lunchmoney.dev/#plaid-accounts

"},{"location":"reference/models/plaid_accounts/#lunchable.models.plaid_accounts.PlaidAccountObject","title":"PlaidAccountObject","text":"

Bases: LunchableModel

Assets synced from Plaid

Similar to AssetObjects, these accounts are linked to remote sources in Plaid.

https://lunchmoney.dev/#plaid-accounts-object

Parameters:

Name Type Description Default id int

Unique identifier of Plaid account

required date_linked date

Date account was first linked in ISO 8601 extended format

required name str

Name of the account. Can be overridden by the user. Field is originally set by Plaid\")

required type str

Primary type of account. Typically one of: [credit, depository, brokerage, cash, loan, investment]. This field is set by Plaid and cannot be altered.

required subtype str

Optional subtype name of account. This field is set by Plaid and cannot be altered

required mask str | None

Mask (last 3 to 4 digits of account) of account. This field is set by Plaid and cannot be altered

None institution_name str

Name of institution associated with account. This field is set by Plaid and cannot be altered

required status str

Denotes the current status of the account within Lunch Money. Must be one of: active (Account is active and in good state), inactive (Account marked inactive from user. No transactions fetched or balance update for this account), relink (Account needs to be relinked with Plaid), syncing (Account is awaiting first import of transactions), error (Account is in error with Plaid), not found (Account is in error with Plaid), not supported (Account is in error with Plaid)

required last_import datetime | None

Date of last imported transaction in ISO 8601 extended format (not necessarily date of last attempted import)

None balance float | None

Current balance of the account in numeric format to 4 decimal places. This field is set by Plaid and cannot be altered

None currency str

Currency of account balance in ISO 4217 format. This field is set by Plaid and cannot be altered

required balance_last_update datetime

Date balance was last updated in ISO 8601 extended format. This field is set by Plaid and cannot be altered

required limit int | None

Optional credit limit of the account. This field is set by Plaid and cannot be altered

None Source code in lunchable/models/plaid_accounts.py
class PlaidAccountObject(LunchableModel):\n    \"\"\"\n    Assets synced from Plaid\n\n    Similar to AssetObjects, these accounts are linked to remote sources in Plaid.\n\n    https://lunchmoney.dev/#plaid-accounts-object\n    \"\"\"\n\n    _date_linked_description = (\n        \"Date account was first linked in ISO 8601 extended format\"\n    )\n    _name_description = \"\"\"\n    Name of the account. Can be overridden by the user. Field is originally set by Plaid\")\n    \"\"\"\n    _type_description = \"\"\"\n    Primary type of account. Typically one of: [credit, depository, brokerage, cash,\n    loan, investment]. This field is set by Plaid and cannot be altered.\n    \"\"\"\n    _subtype_description = \"\"\"\n    Optional subtype name of account. This field is set by Plaid and cannot be altered\n    \"\"\"\n    _mask_description = \"\"\"\n    Mask (last 3 to 4 digits of account) of account. This field is set by\n    Plaid and cannot be altered\n    \"\"\"\n    _institution_name_description = \"\"\"\n    Name of institution associated with account. This field is set by\n    Plaid and cannot be altered\n    \"\"\"\n    _status_description = \"\"\"\n    Denotes the current status of the account within Lunch Money. Must be one of:\n    active (Account is active and in good state),\n    inactive (Account marked inactive from user. No transactions fetched or\n    balance update for this account),\n    relink (Account needs to be relinked with Plaid),\n    syncing (Account is awaiting first import of transactions),\n    error (Account is in error with Plaid),\n    not found (Account is in error with Plaid),\n    not supported (Account is in error with Plaid)\n    \"\"\"\n    _last_import_description = \"\"\"\n    Date of last imported transaction in ISO 8601 extended format (not necessarily\n    date of last attempted import)\n    \"\"\"\n    _balance_description = \"\"\"\n    Current balance of the account in numeric format to 4 decimal places. This field is\n    set by Plaid and cannot be altered\n    \"\"\"\n    _currency_description = \"\"\"\n    Currency of account balance in ISO 4217 format. This field is set by Plaid\n    and cannot be altered\n    \"\"\"\n    _balance_last_update_description = \"\"\"\n    Date balance was last updated in ISO 8601 extended format. This field is set\n    by Plaid and cannot be altered\n    \"\"\"\n    _limit_description = \"\"\"\n    Optional credit limit of the account. This field is set by Plaid and cannot be altered\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier of Plaid account\")\n    date_linked: datetime.date = Field(description=_date_linked_description)\n    name: str = Field(description=_name_description)\n    type: str = Field(description=_type_description)\n    subtype: str = Field(description=_subtype_description)\n    mask: Optional[str] = Field(None, description=_mask_description)\n    institution_name: str = Field(description=_institution_name_description)\n    status: str = Field(description=_status_description)\n    last_import: Optional[datetime.datetime] = Field(\n        None, description=_last_import_description\n    )\n    balance: Optional[float] = Field(None, description=_balance_description)\n    currency: str = Field(description=_currency_description)\n    balance_last_update: datetime.datetime = Field(\n        description=_balance_last_update_description\n    )\n    limit: Optional[int] = Field(None, description=_limit_description)\n
"},{"location":"reference/models/plaid_accounts/#lunchable.models.plaid_accounts.PlaidAccountsClient","title":"PlaidAccountsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Plaid Accounts Interactions

Source code in lunchable/models/plaid_accounts.py
class PlaidAccountsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Plaid Accounts Interactions\n    \"\"\"\n\n    def get_plaid_accounts(self) -> List[PlaidAccountObject]:\n        \"\"\"\n        Get Plaid Synced Assets\n\n        Get a list of all synced Plaid accounts associated with the user's account.\n\n        Plaid Accounts are individual bank accounts that you have linked to Lunch Money via Plaid.\n        You may link one bank but one bank might contain 4 accounts. Each of these\n        accounts is a Plaid Account. (https://lunchmoney.dev/#plaid-accounts-object)\n\n        Returns\n        -------\n        List[PlaidAccountObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_PLAID_ACCOUNTS\n        )\n        accounts = response_data.get(APIConfig.LUNCHMONEY_PLAID_ACCOUNTS)\n        account_objects = [PlaidAccountObject.model_validate(item) for item in accounts]\n        return account_objects\n
"},{"location":"reference/models/plaid_accounts/#lunchable.models.plaid_accounts.PlaidAccountsClient.get_plaid_accounts","title":"get_plaid_accounts()","text":"

Get Plaid Synced Assets

Get a list of all synced Plaid accounts associated with the user's account.

Plaid Accounts are individual bank accounts that you have linked to Lunch Money via Plaid. You may link one bank but one bank might contain 4 accounts. Each of these accounts is a Plaid Account. (https://lunchmoney.dev/#plaid-accounts-object)

Returns:

Type Description List[PlaidAccountObject] Source code in lunchable/models/plaid_accounts.py
def get_plaid_accounts(self) -> List[PlaidAccountObject]:\n    \"\"\"\n    Get Plaid Synced Assets\n\n    Get a list of all synced Plaid accounts associated with the user's account.\n\n    Plaid Accounts are individual bank accounts that you have linked to Lunch Money via Plaid.\n    You may link one bank but one bank might contain 4 accounts. Each of these\n    accounts is a Plaid Account. (https://lunchmoney.dev/#plaid-accounts-object)\n\n    Returns\n    -------\n    List[PlaidAccountObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_PLAID_ACCOUNTS\n    )\n    accounts = response_data.get(APIConfig.LUNCHMONEY_PLAID_ACCOUNTS)\n    account_objects = [PlaidAccountObject.model_validate(item) for item in accounts]\n    return account_objects\n
"},{"location":"reference/models/recurring_expenses/","title":"recurring_expenses","text":"

Lunch Money - Recurring Expenses

https://lunchmoney.dev/#recurring-expenses

"},{"location":"reference/models/recurring_expenses/#lunchable.models.recurring_expenses.RecurringExpenseParamsGet","title":"RecurringExpenseParamsGet","text":"

Bases: LunchableModel

https://lunchmoney.dev/#get-recurring-expenses

Parameters:

Name Type Description Default start_date date required debit_as_negative bool required Source code in lunchable/models/recurring_expenses.py
class RecurringExpenseParamsGet(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#get-recurring-expenses\n    \"\"\"\n\n    start_date: datetime.date\n    debit_as_negative: bool\n
"},{"location":"reference/models/recurring_expenses/#lunchable.models.recurring_expenses.RecurringExpensesClient","title":"RecurringExpensesClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Recurring Expenses Interactions

Source code in lunchable/models/recurring_expenses.py
class RecurringExpensesClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Recurring Expenses Interactions\n    \"\"\"\n\n    def get_recurring_expenses(\n        self,\n        start_date: Optional[datetime.date] = None,\n        debit_as_negative: bool = False,\n    ) -> List[RecurringExpensesObject]:\n        \"\"\"\n        Get Recurring Expenses\n\n        Retrieve a list of recurring expenses to expect for a specified period.\n\n        Every month, a different set of recurring expenses is expected. This is because recurring\n        expenses can be once a year, twice a year, every 4 months, etc.\n\n        If a recurring expense is listed as \u201ctwice a month\u201d, then that recurring expense will be\n        returned twice, each with a different billing date based on when the system believes that\n        recurring expense transaction is to be expected. If the recurring expense is listed as\n        \u201conce a week\u201d, then that recurring expense will be returned in this list as many times as\n        there are weeks for the specified month.\n\n        In the same vein, if a recurring expense that began last month is set to \u201cEvery 3\n        months\u201d, then that recurring expense will not show up in the results for this month.\n\n        Parameters\n        ----------\n        start_date : Optional[datetime.date]\n            Date to search. By default will return the first day of the current month\n        debit_as_negative: bool\n            Pass in true if you'd like expenses to be returned as negative amounts and credits as\n            positive amounts.\n\n        Returns\n        -------\n        List[RecurringExpensesObject]\n        \"\"\"\n        if start_date is None:\n            start_date = datetime.datetime.now().date().replace(day=1)\n        params = RecurringExpenseParamsGet(\n            start_date=start_date, debit_as_negative=debit_as_negative\n        ).model_dump()\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=[APIConfig.LUNCH_MONEY_RECURRING_EXPENSES],\n            params=params,\n        )\n        recurring_expenses = response_data.get(APIConfig.LUNCH_MONEY_RECURRING_EXPENSES)\n        recurring_expenses_objects = [\n            RecurringExpensesObject.model_validate(item) for item in recurring_expenses\n        ]\n        logger.debug(\n            \"%s RecurringExpensesObjects retrieved\", len(recurring_expenses_objects)\n        )\n        return recurring_expenses_objects\n
"},{"location":"reference/models/recurring_expenses/#lunchable.models.recurring_expenses.RecurringExpensesClient.get_recurring_expenses","title":"get_recurring_expenses(start_date=None, debit_as_negative=False)","text":"

Get Recurring Expenses

Retrieve a list of recurring expenses to expect for a specified period.

Every month, a different set of recurring expenses is expected. This is because recurring expenses can be once a year, twice a year, every 4 months, etc.

If a recurring expense is listed as \u201ctwice a month\u201d, then that recurring expense will be returned twice, each with a different billing date based on when the system believes that recurring expense transaction is to be expected. If the recurring expense is listed as \u201conce a week\u201d, then that recurring expense will be returned in this list as many times as there are weeks for the specified month.

In the same vein, if a recurring expense that began last month is set to \u201cEvery 3 months\u201d, then that recurring expense will not show up in the results for this month.

Parameters:

Name Type Description Default start_date Optional[date]

Date to search. By default will return the first day of the current month

None debit_as_negative bool

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts.

False

Returns:

Type Description List[RecurringExpensesObject] Source code in lunchable/models/recurring_expenses.py
def get_recurring_expenses(\n    self,\n    start_date: Optional[datetime.date] = None,\n    debit_as_negative: bool = False,\n) -> List[RecurringExpensesObject]:\n    \"\"\"\n    Get Recurring Expenses\n\n    Retrieve a list of recurring expenses to expect for a specified period.\n\n    Every month, a different set of recurring expenses is expected. This is because recurring\n    expenses can be once a year, twice a year, every 4 months, etc.\n\n    If a recurring expense is listed as \u201ctwice a month\u201d, then that recurring expense will be\n    returned twice, each with a different billing date based on when the system believes that\n    recurring expense transaction is to be expected. If the recurring expense is listed as\n    \u201conce a week\u201d, then that recurring expense will be returned in this list as many times as\n    there are weeks for the specified month.\n\n    In the same vein, if a recurring expense that began last month is set to \u201cEvery 3\n    months\u201d, then that recurring expense will not show up in the results for this month.\n\n    Parameters\n    ----------\n    start_date : Optional[datetime.date]\n        Date to search. By default will return the first day of the current month\n    debit_as_negative: bool\n        Pass in true if you'd like expenses to be returned as negative amounts and credits as\n        positive amounts.\n\n    Returns\n    -------\n    List[RecurringExpensesObject]\n    \"\"\"\n    if start_date is None:\n        start_date = datetime.datetime.now().date().replace(day=1)\n    params = RecurringExpenseParamsGet(\n        start_date=start_date, debit_as_negative=debit_as_negative\n    ).model_dump()\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=[APIConfig.LUNCH_MONEY_RECURRING_EXPENSES],\n        params=params,\n    )\n    recurring_expenses = response_data.get(APIConfig.LUNCH_MONEY_RECURRING_EXPENSES)\n    recurring_expenses_objects = [\n        RecurringExpensesObject.model_validate(item) for item in recurring_expenses\n    ]\n    logger.debug(\n        \"%s RecurringExpensesObjects retrieved\", len(recurring_expenses_objects)\n    )\n    return recurring_expenses_objects\n
"},{"location":"reference/models/recurring_expenses/#lunchable.models.recurring_expenses.RecurringExpensesObject","title":"RecurringExpensesObject","text":"

Bases: LunchableModel

Recurring Expenses Object

https://lunchmoney.dev/#recurring-expenses-object

Parameters:

Name Type Description Default id int

Unique identifier for recurring expense

required start_date date | None

Denotes when recurring expense starts occurring in ISO 8601 format. If null, then this recurring expense will show up for all time before end_date

None end_date date | None

Denotes when recurring expense stops occurring in ISO 8601 format. If null, then this recurring expense has no set end date and will show up for all months after start_date

None cadence str

One of: [monthly, twice a month, once a week, every 3 months, every 4 months, twice a year, yearly]

required payee str

Payee of the recurring expense

required amount float

Amount of the recurring expense in numeric format to 4 decimal places

required currency str

Three-letter lowercase currency code for the recurring expense in ISO 4217 format

required description str | None

If any, represents the user-entered description of the recurring expense

None billing_date date

Expected billing date for this recurring expense for this month in ISO 8601 format

required type str

\" This can be one of two values: cleared (The recurring expense has been reviewed by the user), suggested (The recurring expense is suggested by the system; the user has yet to review/clear it)

required original_name str | None

If any, represents the original name of the recurring expense as denoted by the transaction that triggered its creation

None source str

This can be one of three values: manual (User created this recurring expense manually from the Recurring Expenses page), transaction (User created this by converting a transaction from the Transactions page), system (Recurring expense was created by the system on transaction import). Some older recurring expenses may not have a source.

required plaid_account_id int | None

If any, denotes the plaid account associated with the creation of this \" recurring expense (see Plaid Accounts)\"

None asset_id int | None

If any, denotes the manually-managed account (i.e. asset) associated with the creation of this recurring expense (see Assets)

None transaction_id int | None

If any, denotes the unique identifier for the associated transaction matching this recurring expense for the current time period

None category_id int | None

If any, denotes the unique identifier for the associated category to this recurring expense

None Source code in lunchable/models/recurring_expenses.py
class RecurringExpensesObject(LunchableModel):\n    \"\"\"\n    Recurring Expenses Object\n\n    https://lunchmoney.dev/#recurring-expenses-object\n    \"\"\"\n\n    _id_description = \"Unique identifier for recurring expense\"\n    _start_date_description = \"\"\"\n    Denotes when recurring expense starts occurring in ISO 8601 format.\n    If null, then this recurring expense will show up for all time\n    before end_date\n    \"\"\"\n    _end_date_description = \"\"\"\n    Denotes when recurring expense stops occurring in ISO 8601 format.\n    If null, then this recurring expense has no set end date and will\n    show up for all months after start_date\n    \"\"\"\n    _cadence_description = \"\"\"\n    One of: [monthly, twice a month, once a week, every 3 months, every 4 months,\n    twice a year, yearly]\n    \"\"\"\n    _amount_description = (\n        \"Amount of the recurring expense in numeric format to 4 decimal places\"\n    )\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code for the recurring expense in ISO 4217 format\n    \"\"\"\n    _description_description = \"\"\"\n    If any, represents the user-entered description of the recurring expense\n    \"\"\"\n    _billing_date_description = \"\"\"\n    Expected billing date for this recurring expense for this month in ISO 8601 format\n    \"\"\"\n    _type_description = \"\"\"\"\n    This can be one of two values: cleared (The recurring expense has been reviewed\n    by the user), suggested (The recurring expense is suggested by the system;\n    the user has yet to review/clear it)\n    \"\"\"\n    _original_name_description = \"\"\"\n    If any, represents the original name of the recurring expense as\n    denoted by the transaction that triggered its creation\n    \"\"\"\n    _source_description = \"\"\"\n    This can be one of three values: manual (User created this recurring expense\n    manually from the Recurring Expenses page), transaction (User created this by\n    converting a transaction from the Transactions page), system (Recurring expense\n    was created by the system on transaction import). Some older recurring expenses\n    may not have a source.\n    \"\"\"\n    _plaid_account_id_description = \"\"\"\n    If any, denotes the plaid account associated with the creation of this \"\n    recurring expense (see Plaid Accounts)\"\n    \"\"\"\n    _asset_id_description = \"\"\"\n    If any, denotes the manually-managed account (i.e. asset) associated with the\n    creation of this recurring expense (see Assets)\n    \"\"\"\n    _transaction_id_description = \"\"\"\n    If any, denotes the unique identifier for the associated transaction matching\n    this recurring expense for the current time period\n    \"\"\"\n    _category_id_description = \"\"\"\n    If any, denotes the unique identifier for the associated category to this recurring expense\n    \"\"\"\n\n    id: int = Field(description=_id_description)\n    start_date: Optional[datetime.date] = Field(\n        None, description=_start_date_description\n    )\n    end_date: Optional[datetime.date] = Field(None, description=_end_date_description)\n    cadence: str = Field(description=_cadence_description)\n    payee: str = Field(description=\"Payee of the recurring expense\")\n    amount: float = Field(description=_amount_description)\n    currency: str = Field(max_length=3, description=_currency_description)\n    description: Optional[str] = Field(None, description=_description_description)\n    billing_date: datetime.date = Field(description=_billing_date_description)\n    type: str = Field(description=_type_description)\n    original_name: Optional[str] = Field(None, description=_original_name_description)\n    source: str = Field(description=_source_description)\n    plaid_account_id: Optional[int] = Field(\n        None, description=_plaid_account_id_description\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    transaction_id: Optional[int] = Field(None, description=_transaction_id_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n
"},{"location":"reference/models/tags/","title":"tags","text":"

Lunch Money - Tags

https://lunchmoney.dev/#tags

"},{"location":"reference/models/tags/#lunchable.models.tags.TagsClient","title":"TagsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Tag Interactions

Source code in lunchable/models/tags.py
class TagsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Tag Interactions\n    \"\"\"\n\n    def get_tags(self) -> List[TagsObject]:\n        \"\"\"\n        Get Spending Tags\n\n        Use this endpoint to get a list of all tags associated with the\n        user's account.\n\n        https://lunchmoney.dev/#get-all-tags\n\n        Returns\n        -------\n        List[TagsObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_TAGS\n        )\n        tag_objects = [TagsObject.model_validate(item) for item in response_data]\n        return tag_objects\n
"},{"location":"reference/models/tags/#lunchable.models.tags.TagsClient.get_tags","title":"get_tags()","text":"

Get Spending Tags

Use this endpoint to get a list of all tags associated with the user's account.

https://lunchmoney.dev/#get-all-tags

Returns:

Type Description List[TagsObject] Source code in lunchable/models/tags.py
def get_tags(self) -> List[TagsObject]:\n    \"\"\"\n    Get Spending Tags\n\n    Use this endpoint to get a list of all tags associated with the\n    user's account.\n\n    https://lunchmoney.dev/#get-all-tags\n\n    Returns\n    -------\n    List[TagsObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_TAGS\n    )\n    tag_objects = [TagsObject.model_validate(item) for item in response_data]\n    return tag_objects\n
"},{"location":"reference/models/tags/#lunchable.models.tags.TagsObject","title":"TagsObject","text":"

Bases: LunchableModel

Lunchmoney Tags object

https://lunchmoney.dev/#tags-object

Parameters:

Name Type Description Default id int

Unique identifier for tag

required name str

User-defined name of tag

required description str | None

User-defined description of tag

None Source code in lunchable/models/tags.py
class TagsObject(LunchableModel):\n    \"\"\"\n    Lunchmoney Tags object\n\n    https://lunchmoney.dev/#tags-object\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier for tag\")\n    name: str = Field(description=\"User-defined name of tag\", min_length=1)\n    description: Optional[str] = Field(\n        None, description=\"User-defined description of tag\"\n    )\n
"},{"location":"reference/models/transactions/","title":"transactions","text":"

Lunch Money - Transactions

https://lunchmoney.dev/#transactions

"},{"location":"reference/models/transactions/#lunchable.models.transactions.FullStatusEnum","title":"FullStatusEnum","text":"

Bases: str, Enum

Status Options

Source code in lunchable/models/transactions.py
class FullStatusEnum(str, Enum):\n    \"\"\"\n    Status Options\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n    recurring = \"recurring\"\n    recurring_suggested = \"recurring_suggested\"\n    pending = \"pending\"\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionBaseObject","title":"TransactionBaseObject","text":"

Bases: LunchableModel

Base Model For All Transactions to Inherit From

Source code in lunchable/models/transactions.py
class TransactionBaseObject(LunchableModel):\n    \"\"\"\n    Base Model For All Transactions to Inherit From\n    \"\"\"\n\n    pass\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionInsertObject","title":"TransactionInsertObject","text":"

Bases: TransactionBaseObject

Object For Creating New Transactions

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required amount float

Numeric value of amount. i.e. $4.25 should be denoted as 4.25.

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None currency str | None

Three-letter lowercase currency code in ISO 4217 format. The code sent must exist in our database. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account.

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id.

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionInsertObject(TransactionBaseObject):\n    \"\"\"\n    Object For Creating New Transactions\n\n    https://lunchmoney.dev/#insert-transactions\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _amount_description = \"\"\"\n    Numeric value of amount. i.e. $4.25 should be denoted as 4.25.\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated with\n    the same account and must not be a category group.\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code in ISO 4217 format. The code sent must exist\n    in our database. Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account.\n    \"\"\"\n    _recurring_id = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must be associated\n    with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. If recurring_id is provided, the status will\n    automatically be set to recurring or recurring_suggested depending on the type of\n    recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id.\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found, an error\n    will be thrown. Passing in a string will attempt to match by string. If no matching tag\n    name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: datetime.date = Field(description=_date_description)\n    amount: float = Field(description=_amount_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    currency: Optional[str] = Field(\n        None, description=_currency_description, max_length=3\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(\n        None, description=_external_id_description, max_length=75\n    )\n    tags: Optional[List[Union[str, int]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionInsertObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionObject","title":"TransactionObject","text":"

Bases: TransactionBaseObject

Universal Lunch Money Transaction Object

https://lunchmoney.dev/#transaction-object

Parameters:

Name Type Description Default id int

Unique identifier for transaction

required date date

Date of transaction in ISO 8601 format

required payee str | None

Name of payee If recurring_id is not null, this field will show the payee of associated recurring expense instead of the original transaction payee

None amount float

Amount of the transaction in numeric format to 4 decimal places

required currency str | None

Three-letter lowercase currency code of the transaction in ISO 4217 format

None notes str | None

User-entered transaction notes If recurring_id is not null, this field will be description of associated recurring expense

None category_id int | None

Unique identifier of associated category (see Categories)

None asset_id int | None

Unique identifier of associated manually-managed account (see Assets) Note: plaid_account_id and asset_id cannot both exist for a transaction

None plaid_account_id int | None

Unique identifier of associated Plaid account (see Plaid Accounts) Note: plaid_account_id and asset_id cannot both exist for a transaction

None status str | None

One of the following: cleared: User has reviewed the transaction | uncleared: User has not yet reviewed the transaction | recurring: Transaction is linked to a recurring expense | recurring_suggested: Transaction is listed as a suggested transaction for an existing recurring expense | pending: Imported transaction is marked as pending. This should be a temporary state. User intervention is required to change this to recurring.

None parent_id int | None

Exists if this is a split transaction. Denotes the transaction ID of the original transaction. Note that the parent transaction is not returned in this call.

None is_group bool | None

True if this transaction represents a group of transactions. If so, amount and currency represent the totalled amount of transactions bearing this transaction's id as their group_id. Amount is calculated based on the user's primary currency.

None group_id int | None

Exists if this transaction is part of a group. Denotes the parent's transaction ID

None tags List[TagsObject] | None

Array of Tag objects

None external_id str | None

User-defined external ID for any manually-entered or imported transaction. External ID cannot be accessed or changed for Plaid-imported transactions. External ID must be unique by asset_id. Max 75 characters.

None original_name str | None

The transactions original name before any payee name updates. For synced transactions, this is the raw original payee name from your bank.

None type str | None

(for synced investment transactions only) The transaction type as set by Plaid for investment transactions. Possible values include: buy, sell, cash, transfer and more

None subtype str | None

(for synced investment transactions only) The transaction type as set by Plaid for investment transactions. Possible values include: management fee, withdrawal, dividend, deposit and more

None fees str | None

(for synced investment transactions only) The fees as set by Plaid for investment transactions.

None price str | None

(for synced investment transactions only) The price as set by Plaid for investment transactions.

None quantity str | None

(for synced investment transactions only) The quantity as set by Plaid for investment transactions.

None Source code in lunchable/models/transactions.py
class TransactionObject(TransactionBaseObject):\n    \"\"\"\n    Universal Lunch Money Transaction Object\n\n    https://lunchmoney.dev/#transaction-object\n    \"\"\"\n\n    _amount_description = \"\"\"\n    Amount of the transaction in numeric format to 4 decimal places\n    \"\"\"\n    _payee_description = \"\"\"\n    Name of payee If recurring_id is not null, this field will show the payee\n    of associated recurring expense instead of the original transaction payee\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code of the transaction in ISO 4217 format\n    \"\"\"\n    _notes_description = \"\"\"\n    User-entered transaction notes If recurring_id is not null, this field will\n    be description of associated recurring expense\n    \"\"\"\n    _category_description = \"\"\"\n    Unique identifier of associated category (see Categories)\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier of associated manually-managed account (see Assets)\n    Note: plaid_account_id and asset_id cannot both exist for a transaction\n    \"\"\"\n    _plaid_account_id_description = \"\"\"\n    Unique identifier of associated Plaid account (see Plaid Accounts) Note:\n    plaid_account_id and asset_id cannot both exist for a transaction\n    \"\"\"\n    _status_description = \"\"\"\n    One of the following: cleared: User has reviewed the transaction | uncleared:\n    User has not yet reviewed the transaction | recurring: Transaction is linked\n    to a recurring expense | recurring_suggested: Transaction is listed as a\n    suggested transaction for an existing recurring expense | pending: Imported\n    transaction is marked as pending. This should be a temporary state. User intervention\n    is required to change this to recurring.\n    \"\"\"\n    _parent_id_description = \"\"\"\n    Exists if this is a split transaction. Denotes the transaction ID of the original\n    transaction. Note that the parent transaction is not returned in this call.\n    \"\"\"\n    _is_group_description = \"\"\"\n    True if this transaction represents a group of transactions. If so, amount\n    and currency represent the totalled amount of transactions bearing this\n    transaction's id as their group_id. Amount is calculated based on the\n    user's primary currency.\n    \"\"\"\n    _group_id_description = \"\"\"\n    Exists if this transaction is part of a group. Denotes the parent's transaction ID\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for any manually-entered or imported transaction.\n    External ID cannot be accessed or changed for Plaid-imported transactions.\n    External ID must be unique by asset_id. Max 75 characters.\n    \"\"\"\n    _original_name_description = \"\"\"\n    The transactions original name before any payee name updates. For synced transactions,\n    this is the raw original payee name from your bank.\n    \"\"\"\n    _type_description = \"\"\"\n    (for synced investment transactions only) The transaction type as set by\n    Plaid for investment transactions. Possible values include: buy, sell, cash,\n    transfer and more\n    \"\"\"\n    _subtype_description = \"\"\"\n    (for synced investment transactions only) The transaction type as set by Plaid\n    for investment transactions. Possible values include: management fee, withdrawal,\n    dividend, deposit and more\n    \"\"\"\n    _fees_description = \"\"\"\n    (for synced investment transactions only) The fees as set by Plaid for investment\n    transactions.\n    \"\"\"\n    _price_description = \"\"\"\n    (for synced investment transactions only) The price as set by Plaid for investment\n    transactions.\n    \"\"\"\n    _quantity_description = \"\"\"\n    (for synced investment transactions only) The quantity as set by Plaid for investment\n    transactions.\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier for transaction\")\n    date: datetime.date = Field(description=\"Date of transaction in ISO 8601 format\")\n    payee: Optional[str] = Field(None, description=_payee_description)\n    amount: float = Field(description=_amount_description)\n    currency: Optional[str] = Field(\n        None, max_length=3, description=_currency_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    category_id: Optional[int] = Field(None, description=_category_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    plaid_account_id: Optional[int] = Field(\n        None, description=_plaid_account_id_description\n    )\n    status: Optional[str] = Field(None, description=_status_description)\n    parent_id: Optional[int] = Field(None, description=_parent_id_description)\n    is_group: Optional[bool] = Field(None, description=_is_group_description)\n    group_id: Optional[int] = Field(None, description=_group_id_description)\n    tags: Optional[List[TagsObject]] = Field(None, description=\"Array of Tag objects\")\n    external_id: Optional[str] = Field(\n        None, max_length=75, description=_external_id_description\n    )\n    original_name: Optional[str] = Field(None, description=_original_name_description)\n    type: Optional[str] = Field(None, description=_type_description)\n    subtype: Optional[str] = Field(None, description=_subtype_description)\n    fees: Optional[str] = Field(None, description=_fees_description)\n    price: Optional[str] = Field(None, description=_price_description)\n    quantity: Optional[str] = Field(None, description=_quantity_description)\n\n    def get_update_object(self) -> TransactionUpdateObject:\n        \"\"\"\n        Return a TransactionUpdateObject\n\n        Return a TransactionUpdateObject to update an expense. Simply\n        change one of the properties and perform an `update_transaction` with\n        your Lunchable object.\n\n        Returns\n        -------\n        TransactionUpdateObject\n        \"\"\"\n        update_dict = self.model_dump()\n        try:\n            TransactionUpdateObject.StatusEnum(self.status)\n        except ValueError:\n            update_dict[\"status\"] = None\n        update_object = TransactionUpdateObject.model_validate(update_dict)\n        if update_object.tags is not None:\n            tags = [] if self.tags is None else self.tags\n            update_object.tags = [tag.name for tag in tags]\n        return update_object\n\n    def get_insert_object(self) -> TransactionInsertObject:\n        \"\"\"\n        Return a TransactionInsertObject\n\n        Return a TransactionInsertObject to update an expense. Simply\n        change some of the properties and perform an `insert_transactions` with\n        your Lunchable object.\n\n        Returns\n        -------\n        TransactionInsertObject\n        \"\"\"\n        insert_dict = self.model_dump()\n        try:\n            TransactionInsertObject.StatusEnum(self.status)\n        except ValueError:\n            insert_dict[\"status\"] = None\n        insert_object = TransactionInsertObject.model_validate(insert_dict)\n        if insert_object.tags is not None:\n            tags = [] if self.tags is None else self.tags\n            insert_object.tags = [tag.name for tag in tags]\n        return insert_object\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionObject.get_insert_object","title":"get_insert_object()","text":"

Return a TransactionInsertObject

Return a TransactionInsertObject to update an expense. Simply change some of the properties and perform an insert_transactions with your Lunchable object.

Returns:

Type Description TransactionInsertObject Source code in lunchable/models/transactions.py
def get_insert_object(self) -> TransactionInsertObject:\n    \"\"\"\n    Return a TransactionInsertObject\n\n    Return a TransactionInsertObject to update an expense. Simply\n    change some of the properties and perform an `insert_transactions` with\n    your Lunchable object.\n\n    Returns\n    -------\n    TransactionInsertObject\n    \"\"\"\n    insert_dict = self.model_dump()\n    try:\n        TransactionInsertObject.StatusEnum(self.status)\n    except ValueError:\n        insert_dict[\"status\"] = None\n    insert_object = TransactionInsertObject.model_validate(insert_dict)\n    if insert_object.tags is not None:\n        tags = [] if self.tags is None else self.tags\n        insert_object.tags = [tag.name for tag in tags]\n    return insert_object\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionObject.get_update_object","title":"get_update_object()","text":"

Return a TransactionUpdateObject

Return a TransactionUpdateObject to update an expense. Simply change one of the properties and perform an update_transaction with your Lunchable object.

Returns:

Type Description TransactionUpdateObject Source code in lunchable/models/transactions.py
def get_update_object(self) -> TransactionUpdateObject:\n    \"\"\"\n    Return a TransactionUpdateObject\n\n    Return a TransactionUpdateObject to update an expense. Simply\n    change one of the properties and perform an `update_transaction` with\n    your Lunchable object.\n\n    Returns\n    -------\n    TransactionUpdateObject\n    \"\"\"\n    update_dict = self.model_dump()\n    try:\n        TransactionUpdateObject.StatusEnum(self.status)\n    except ValueError:\n        update_dict[\"status\"] = None\n    update_object = TransactionUpdateObject.model_validate(update_dict)\n    if update_object.tags is not None:\n        tags = [] if self.tags is None else self.tags\n        update_object.tags = [tag.name for tag in tags]\n    return update_object\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionSplitObject","title":"TransactionSplitObject","text":"

Bases: TransactionBaseObject

Object for Splitting Transactions

https://lunchmoney.dev/#split-object

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account.

None notes str | None

Transaction Split Notes.

None amount float

Individual amount of split. Currency will inherit from parent transaction. All amounts must sum up to parent transaction amount.

required Source code in lunchable/models/transactions.py
class TransactionSplitObject(TransactionBaseObject):\n    \"\"\"\n    Object for Splitting Transactions\n\n    https://lunchmoney.dev/#split-object\n    \"\"\"\n\n    _date_description = \"Must be in ISO 8601 format (YYYY-MM-DD).\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account.\n    \"\"\"\n    _notes_description = \"Transaction Split Notes.\"\n    _amount_description = \"\"\"\n    Individual amount of split. Currency will inherit from parent transaction. All\n    amounts must sum up to parent transaction amount.\n    \"\"\"\n\n    date: datetime.date = Field(description=_date_description)\n    category_id: Optional[int] = Field(\n        default=None, description=_category_id_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    amount: float = Field(description=_amount_description)\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionUpdateObject","title":"TransactionUpdateObject","text":"

Bases: TransactionBaseObject

Object For Updating Existing Transactions

https://lunchmoney.dev/#update-transaction

Parameters:

Name Type Description Default date date | None

Must be in ISO 8601 format (YYYY-MM-DD).

None category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None amount float | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None currency str | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. Defaults to uncleared If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionUpdateObject(TransactionBaseObject):\n    \"\"\"\n    Object For Updating Existing Transactions\n\n    https://lunchmoney.dev/#update-transaction\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account and must not be a category group.\n    \"\"\"\n    _amount_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id\n    \"\"\"\n    _currency_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id.\n    Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _recurring_id_description = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must\n    be associated with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. Defaults to uncleared If recurring_id is\n    provided, the status will automatically be set to recurring or recurring_suggested\n    depending on the type of recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found,\n    an error will be thrown. Passing in a string will attempt to match by string.\n    If no matching tag name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: Optional[datetime.date] = Field(None, description=_date_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    amount: Optional[float] = Field(None, description=_amount_description)\n    currency: Optional[str] = Field(None, description=_currency_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id_description)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(None, description=_external_id_description)\n    tags: Optional[List[Union[int, str]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionUpdateObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient","title":"TransactionsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Transactions Interactions

Source code in lunchable/models/transactions.py
class TransactionsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Transactions Interactions\n    \"\"\"\n\n    def get_transactions(\n        self,\n        start_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n        end_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n        tag_id: Optional[int] = None,\n        recurring_id: Optional[int] = None,\n        plaid_account_id: Optional[int] = None,\n        category_id: Optional[int] = None,\n        asset_id: Optional[int] = None,\n        group_id: Optional[int] = None,\n        is_group: Optional[bool] = None,\n        status: Optional[str] = None,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        debit_as_negative: Optional[bool] = None,\n        pending: Optional[bool] = None,\n        params: Optional[Dict[str, Any]] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get Transactions Using Criteria\n\n        Use this to retrieve all transactions between a date range. Returns list of Transaction\n        objects. If no query parameters are set, this will return transactions for the\n        current calendar month. If either start_date or end_date are datetime.datetime objects,\n        they will be reduced to dates. If a string is provided, it will be attempted to be parsed\n        as YYYY-MM-DD format.\n\n        Parameters\n        ----------\n        start_date: Optional[Union[datetime.date, datetime.datetime, str]]\n            Denotes the beginning of the time period to fetch transactions for. Defaults\n            to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.\n        end_date: Optional[Union[datetime.date, datetime.datetime, str]]\n            Denotes the end of the time period you'd like to get transactions for.\n            Defaults to end of current month. Required if start_date exists.\n        tag_id: Optional[int]\n            Filter by tag. Only accepts IDs, not names.\n        recurring_id: Optional[int]\n            Filter by recurring expense\n        plaid_account_id: Optional[int]\n            Filter by Plaid account\n        category_id: Optional[int]\n            Filter by category. Will also match category groups.\n        asset_id: Optional[int]\n            Filter by asset\n        group_id: Optional[int]\n            Filter by group_id (if the transaction is part of a specific group)\n        is_group: Optional[bool]\n            Filter by group (returns transaction groups)\n        status: Optional[str]\n            Filter by status (Can be cleared or uncleared. For recurring\n            transactions, use recurring)\n        offset: Optional[int]\n            Sets the offset for the records returned\n        limit: Optional[int]\n            Sets the maximum number of records to return. Note: The server will not\n            respond with any indication that there are more records to be returned.\n            Please check the response length to determine if you should make another\n            call with an offset to fetch more transactions.\n        debit_as_negative: Optional[bool]\n            Pass in true if you'd like expenses to be returned as negative amounts and\n            credits as positive amounts. Defaults to false.\n        pending: Optional[bool]\n            Pass in true if you'd like to include imported transactions with a pending status.\n        params: Optional[dict]\n            Additional Query String Params\n\n        Returns\n        -------\n        List[TransactionObject]\n            A list of transactions\n\n        Examples\n        --------\n        Retrieve a list of\n        [TransactionObject][lunchable.models.transactions.TransactionObject]\n\n        ```python\n        from lunchable import LunchMoney\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n        transactions = lunch.get_transactions(start_date=\"2020-01-01\",\n                                              end_date=\"2020-01-31\")\n        ```\n        \"\"\"\n        search_params = _TransactionParamsGet(\n            tag_id=tag_id,\n            recurring_id=recurring_id,\n            plaid_account_id=plaid_account_id,\n            category_id=category_id,\n            asset_id=asset_id,\n            group_id=group_id,\n            is_group=is_group,\n            status=status,\n            offset=offset,\n            limit=limit,\n            start_date=start_date,\n            end_date=end_date,\n            debit_as_negative=debit_as_negative,\n            pending=pending,\n        ).model_dump(exclude_none=True)\n        search_params.update(params if params is not None else {})\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=APIConfig.LUNCHMONEY_TRANSACTIONS,\n            params=search_params,\n        )\n        transactions = response_data[APIConfig.LUNCHMONEY_TRANSACTIONS]\n        transaction_objects = [\n            TransactionObject.model_validate(item) for item in transactions\n        ]\n        return transaction_objects\n\n    def get_transaction(self, transaction_id: int) -> TransactionObject:\n        \"\"\"\n        Get a Transaction by ID\n\n        Parameters\n        ----------\n        transaction_id: int\n            Lunch Money Transaction ID\n\n        Returns\n        -------\n        TransactionObject\n\n        Examples\n        --------\n        Retrieve a single transaction by its ID\n\n        ```python\n        from lunchable import LunchMoney\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n        transaction = lunch.get_transaction(transaction_id=1234)\n        ```\n\n        The above code returns a\n        [TransactionObject][lunchable.models.transactions.TransactionObject]\n        with ID # 1234 (assuming it exists)\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, transaction_id],\n        )\n        return TransactionObject.model_validate(response_data)\n\n    ListOrSingleTransactionUpdateObject = Optional[\n        Union[TransactionUpdateObject, TransactionObject]\n    ]\n\n    ListOrSingleTransactionInsertObject = Union[\n        TransactionObject,\n        TransactionInsertObject,\n        List[TransactionObject],\n        List[TransactionInsertObject],\n        List[Union[TransactionObject, TransactionInsertObject]],\n    ]\n\n    def update_transaction(\n        self,\n        transaction_id: int,\n        transaction: ListOrSingleTransactionUpdateObject = None,\n        split: Optional[List[TransactionSplitObject]] = None,\n        debit_as_negative: bool = False,\n        skip_balance_update: bool = True,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Update a Transaction\n\n        Use this endpoint to update a single transaction. You may also use this\n        to split an existing transaction. If a TransactionObject is provided it will be\n        converted into a TransactionUpdateObject.\n\n        PUT https://dev.lunchmoney.app/v1/transactions/:transaction_id\n\n        Parameters\n        ----------\n        transaction_id: int\n            Lunch Money Transaction ID\n        transaction: ListOrSingleTransactionUpdateObject\n            Object to update with\n        split: Optional[List[TransactionSplitObject]]\n            Defines the split of a transaction. You may not split an already-split\n            transaction, recurring transaction, or group transaction.\n        debit_as_negative: bool\n            If true, will assume negative amount values denote expenses and\n            positive amount values denote credits. Defaults to false.\n        skip_balance_update: bool\n            If false, will skip updating balance if an asset_id\n            is present for any of the transactions.\n\n        Returns\n        -------\n        Dict[str, Any]\n\n        Examples\n        --------\n        Update a transaction with a\n        [TransactionUpdateObject][lunchable.models.transactions.TransactionUpdateObject]\n\n        ```python\n        from datetime import datetime\n\n        from lunchable import LunchMoney, TransactionUpdateObject\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n        transaction_note = f\"Updated on {datetime.now()}\"\n        notes_update = TransactionUpdateObject(notes=transaction_note)\n        response = lunch.update_transaction(transaction_id=1234,\n                                            transaction=notes_update)\n        ```\n\n        Update a\n        [TransactionObject][lunchable.models.transactions.TransactionObject]\n        with itself\n\n        ```python\n        from datetime import datetime, timedelta\n\n        from lunchable import LunchMoney\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n        transaction = lunch.get_transaction(transaction_id=1234)\n\n        transaction.notes = f\"Updated on {datetime.now()}\"\n        transaction.date = transaction.date + timedelta(days=1)\n        response = lunch.update_transaction(transaction_id=transaction.id,\n                                            transaction=transaction)\n        ```\n        \"\"\"\n        payload = _TransactionUpdateParamsPut(\n            split=split,\n            debit_as_negative=debit_as_negative,\n            skip_balance_update=skip_balance_update,\n        ).model_dump(exclude_none=True)\n        if transaction is None and split is None:\n            raise LunchMoneyError(\"You must update the transaction or provide a split\")\n        elif transaction is not None:\n            if isinstance(transaction, TransactionObject):\n                transaction = transaction.get_update_object()\n            payload[\"transaction\"] = transaction.model_dump(exclude_unset=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, transaction_id],\n            payload=payload,\n        )\n        return response_data\n\n    def insert_transactions(\n        self,\n        transactions: ListOrSingleTransactionInsertObject,\n        apply_rules: bool = False,\n        skip_duplicates: bool = True,\n        debit_as_negative: bool = False,\n        check_for_recurring: bool = False,\n        skip_balance_update: bool = True,\n    ) -> List[int]:\n        \"\"\"\n        Create One or Many Lunch Money Transactions\n\n        Use this endpoint to insert many transactions at once. Also accepts\n        a single transaction as well. If a TransactionObject is provided it will be\n        converted into a TransactionInsertObject.\n\n        https://lunchmoney.dev/#insert-transactions\n\n        Parameters\n        ----------\n        transactions: ListOrSingleTransactionTypeObject\n            Transactions to insert. Either a single TransactionInsertObject object or\n            a list of them\n        apply_rules: bool\n            If true, will apply account's existing rules to the inserted transactions.\n            Defaults to false.\n        skip_duplicates: bool\n            If true, the system will automatically dedupe based on transaction date,\n            payee and amount. Note that deduping by external_id will occur regardless\n            of this flag.\n        check_for_recurring: bool\n            if true, will check new transactions for occurrences of new monthly expenses.\n            Defaults to false.\n        debit_as_negative: bool\n            If true, will assume negative amount values denote expenses and\n            positive amount values denote credits. Defaults to false.\n        skip_balance_update: bool\n            If false, will skip updating balance if an asset_id\n            is present for any of the transactions.\n\n        Returns\n        -------\n        List[int]\n\n        Examples\n        --------\n        Create a new transaction with a\n        [TransactionInsertObject][lunchable.models.transactions.TransactionInsertObject]\n\n        ```python\n        from lunchable import LunchMoney, TransactionInsertObject\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n\n        new_transaction = TransactionInsertObject(payee=\"Example Restaurant\",\n                                                  amount=120.00,\n                                                  notes=\"Saturday Dinner\")\n        new_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n        ```\n        \"\"\"\n        insert_objects = []\n        if not isinstance(transactions, list):\n            transactions = [transactions]\n        for item in transactions:\n            if isinstance(item, TransactionObject):\n                insert_objects.append(item.get_insert_object())\n            elif isinstance(item, TransactionInsertObject):\n                insert_objects.append(item)\n            else:\n                raise LunchMoneyError(\n                    \"Only TransactionObjects or TransactionInsertObjects are \"\n                    \"supported by this function.\"\n                )\n        payload = _TransactionInsertParamsPost(\n            transactions=insert_objects,\n            apply_rules=apply_rules,\n            skip_duplicates=skip_duplicates,\n            check_for_recurring=check_for_recurring,\n            debit_as_negative=debit_as_negative,\n            skip_balance_update=skip_balance_update,\n        ).model_dump(exclude_unset=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=APIConfig.LUNCHMONEY_TRANSACTIONS,\n            payload=payload,\n        )\n        ids: List[int] = response_data[\"ids\"] if response_data else []\n        return ids\n\n    def insert_transaction_group(\n        self,\n        date: datetime.date,\n        payee: str,\n        transactions: List[int],\n        category_id: Optional[int] = None,\n        notes: Optional[str] = None,\n        tags: Optional[List[int]] = None,\n    ) -> int:\n        \"\"\"\n        Create a Transaction Group of Two or More Transactions\n\n        Returns the ID of the newly created transaction group\n\n        Parameters\n        ----------\n        date: datetime.date\n            Date for the grouped transaction\n        payee: str\n            Payee name for the grouped transaction\n        category_id: Optional[int]\n            Category for the grouped transaction\n        notes: Optional[str]\n            Notes for the grouped transaction\n        tags: Optional[List[int]]\n            Array of tag IDs for the grouped transaction\n        transactions: Optional[List[int]]\n            Array of transaction IDs to be part of the transaction group\n\n        Returns\n        -------\n        int\n        \"\"\"\n        if len(transactions) < 2:\n            raise LunchMoneyError(\n                \"You must include 2 or more transactions \" \"in the Transaction Group\"\n            )\n        transaction_params = _TransactionGroupParamsPost(\n            date=date,\n            payee=payee,\n            category_id=category_id,\n            notes=notes,\n            tags=tags,\n            transactions=transactions,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[\n                APIConfig.LUNCHMONEY_TRANSACTIONS,\n                APIConfig.LUNCHMONEY_TRANSACTION_GROUPS,\n            ],\n            payload=transaction_params,\n        )\n        return response_data\n\n    def remove_transaction_group(self, transaction_group_id: int) -> List[int]:\n        \"\"\"\n        Delete a Transaction Group\n\n        Use this method to delete a transaction group. The transactions within the\n        group will not be removed.\n\n        Returns the IDs of the transactions that were part of the deleted group\n\n        https://lunchmoney.dev/#delete-transaction-group\n\n        Parameters\n        ----------\n        transaction_group_id: int\n            Transaction Group Identifier\n\n        Returns\n        -------\n        List[int]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.DELETE,\n            url_path=[\n                APIConfig.LUNCHMONEY_TRANSACTIONS,\n                APIConfig.LUNCHMONEY_TRANSACTION_GROUPS,\n                transaction_group_id,\n            ],\n        )\n        return response_data[\"transactions\"]\n\n    def unsplit_transactions(\n        self, parent_ids: List[int], remove_parents: bool = False\n    ) -> List[int]:\n        \"\"\"\n        Unsplit Transactions\n\n        Use this endpoint to unsplit one or more transactions.\n\n        Returns an array of IDs of deleted transactions\n\n        https://lunchmoney.dev/#unsplit-transactions\n\n        Parameters\n        ----------\n        parent_ids: List[int]\n            Array of transaction IDs to unsplit. If one transaction is unsplittable,\n            no transaction will be unsplit.\n        remove_parents: bool\n            If true, deletes the original parent transaction as well. Note,\n            this is unreversable!\n\n        Returns\n        -------\n        List[int]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, \"unsplit\"],\n            payload=_TransactionsUnsplitPost(\n                parent_ids=parent_ids, remove_parents=remove_parents\n            ).model_dump(exclude_none=True),\n        )\n        return response_data\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.get_transaction","title":"get_transaction(transaction_id)","text":"

Get a Transaction by ID

Parameters:

Name Type Description Default transaction_id int

Lunch Money Transaction ID

required

Returns:

Type Description TransactionObject

Examples:

Retrieve a single transaction by its ID

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction = lunch.get_transaction(transaction_id=1234)\n

The above code returns a TransactionObject with ID # 1234 (assuming it exists)

Source code in lunchable/models/transactions.py
def get_transaction(self, transaction_id: int) -> TransactionObject:\n    \"\"\"\n    Get a Transaction by ID\n\n    Parameters\n    ----------\n    transaction_id: int\n        Lunch Money Transaction ID\n\n    Returns\n    -------\n    TransactionObject\n\n    Examples\n    --------\n    Retrieve a single transaction by its ID\n\n    ```python\n    from lunchable import LunchMoney\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transaction = lunch.get_transaction(transaction_id=1234)\n    ```\n\n    The above code returns a\n    [TransactionObject][lunchable.models.transactions.TransactionObject]\n    with ID # 1234 (assuming it exists)\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, transaction_id],\n    )\n    return TransactionObject.model_validate(response_data)\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.get_transactions","title":"get_transactions(start_date=None, end_date=None, tag_id=None, recurring_id=None, plaid_account_id=None, category_id=None, asset_id=None, group_id=None, is_group=None, status=None, offset=None, limit=None, debit_as_negative=None, pending=None, params=None)","text":"

Get Transactions Using Criteria

Use this to retrieve all transactions between a date range. Returns list of Transaction objects. If no query parameters are set, this will return transactions for the current calendar month. If either start_date or end_date are datetime.datetime objects, they will be reduced to dates. If a string is provided, it will be attempted to be parsed as YYYY-MM-DD format.

Parameters:

Name Type Description Default start_date Optional[Union[date, datetime, str]]

Denotes the beginning of the time period to fetch transactions for. Defaults to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.

None end_date Optional[Union[date, datetime, str]]

Denotes the end of the time period you'd like to get transactions for. Defaults to end of current month. Required if start_date exists.

None tag_id Optional[int]

Filter by tag. Only accepts IDs, not names.

None recurring_id Optional[int]

Filter by recurring expense

None plaid_account_id Optional[int]

Filter by Plaid account

None category_id Optional[int]

Filter by category. Will also match category groups.

None asset_id Optional[int]

Filter by asset

None group_id Optional[int]

Filter by group_id (if the transaction is part of a specific group)

None is_group Optional[bool]

Filter by group (returns transaction groups)

None status Optional[str]

Filter by status (Can be cleared or uncleared. For recurring transactions, use recurring)

None offset Optional[int]

Sets the offset for the records returned

None limit Optional[int]

Sets the maximum number of records to return. Note: The server will not respond with any indication that there are more records to be returned. Please check the response length to determine if you should make another call with an offset to fetch more transactions.

None debit_as_negative Optional[bool]

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts. Defaults to false.

None pending Optional[bool]

Pass in true if you'd like to include imported transactions with a pending status.

None params Optional[Dict[str, Any]]

Additional Query String Params

None

Returns:

Type Description List[TransactionObject]

A list of transactions

Examples:

Retrieve a list of TransactionObject

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions = lunch.get_transactions(start_date=\"2020-01-01\",\n                                      end_date=\"2020-01-31\")\n
Source code in lunchable/models/transactions.py
def get_transactions(\n    self,\n    start_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n    end_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n    tag_id: Optional[int] = None,\n    recurring_id: Optional[int] = None,\n    plaid_account_id: Optional[int] = None,\n    category_id: Optional[int] = None,\n    asset_id: Optional[int] = None,\n    group_id: Optional[int] = None,\n    is_group: Optional[bool] = None,\n    status: Optional[str] = None,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    debit_as_negative: Optional[bool] = None,\n    pending: Optional[bool] = None,\n    params: Optional[Dict[str, Any]] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Get Transactions Using Criteria\n\n    Use this to retrieve all transactions between a date range. Returns list of Transaction\n    objects. If no query parameters are set, this will return transactions for the\n    current calendar month. If either start_date or end_date are datetime.datetime objects,\n    they will be reduced to dates. If a string is provided, it will be attempted to be parsed\n    as YYYY-MM-DD format.\n\n    Parameters\n    ----------\n    start_date: Optional[Union[datetime.date, datetime.datetime, str]]\n        Denotes the beginning of the time period to fetch transactions for. Defaults\n        to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.\n    end_date: Optional[Union[datetime.date, datetime.datetime, str]]\n        Denotes the end of the time period you'd like to get transactions for.\n        Defaults to end of current month. Required if start_date exists.\n    tag_id: Optional[int]\n        Filter by tag. Only accepts IDs, not names.\n    recurring_id: Optional[int]\n        Filter by recurring expense\n    plaid_account_id: Optional[int]\n        Filter by Plaid account\n    category_id: Optional[int]\n        Filter by category. Will also match category groups.\n    asset_id: Optional[int]\n        Filter by asset\n    group_id: Optional[int]\n        Filter by group_id (if the transaction is part of a specific group)\n    is_group: Optional[bool]\n        Filter by group (returns transaction groups)\n    status: Optional[str]\n        Filter by status (Can be cleared or uncleared. For recurring\n        transactions, use recurring)\n    offset: Optional[int]\n        Sets the offset for the records returned\n    limit: Optional[int]\n        Sets the maximum number of records to return. Note: The server will not\n        respond with any indication that there are more records to be returned.\n        Please check the response length to determine if you should make another\n        call with an offset to fetch more transactions.\n    debit_as_negative: Optional[bool]\n        Pass in true if you'd like expenses to be returned as negative amounts and\n        credits as positive amounts. Defaults to false.\n    pending: Optional[bool]\n        Pass in true if you'd like to include imported transactions with a pending status.\n    params: Optional[dict]\n        Additional Query String Params\n\n    Returns\n    -------\n    List[TransactionObject]\n        A list of transactions\n\n    Examples\n    --------\n    Retrieve a list of\n    [TransactionObject][lunchable.models.transactions.TransactionObject]\n\n    ```python\n    from lunchable import LunchMoney\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transactions = lunch.get_transactions(start_date=\"2020-01-01\",\n                                          end_date=\"2020-01-31\")\n    ```\n    \"\"\"\n    search_params = _TransactionParamsGet(\n        tag_id=tag_id,\n        recurring_id=recurring_id,\n        plaid_account_id=plaid_account_id,\n        category_id=category_id,\n        asset_id=asset_id,\n        group_id=group_id,\n        is_group=is_group,\n        status=status,\n        offset=offset,\n        limit=limit,\n        start_date=start_date,\n        end_date=end_date,\n        debit_as_negative=debit_as_negative,\n        pending=pending,\n    ).model_dump(exclude_none=True)\n    search_params.update(params if params is not None else {})\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=APIConfig.LUNCHMONEY_TRANSACTIONS,\n        params=search_params,\n    )\n    transactions = response_data[APIConfig.LUNCHMONEY_TRANSACTIONS]\n    transaction_objects = [\n        TransactionObject.model_validate(item) for item in transactions\n    ]\n    return transaction_objects\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.insert_transaction_group","title":"insert_transaction_group(date, payee, transactions, category_id=None, notes=None, tags=None)","text":"

Create a Transaction Group of Two or More Transactions

Returns the ID of the newly created transaction group

Parameters:

Name Type Description Default date date

Date for the grouped transaction

required payee str

Payee name for the grouped transaction

required category_id Optional[int]

Category for the grouped transaction

None notes Optional[str]

Notes for the grouped transaction

None tags Optional[List[int]]

Array of tag IDs for the grouped transaction

None transactions List[int]

Array of transaction IDs to be part of the transaction group

required

Returns:

Type Description int Source code in lunchable/models/transactions.py
def insert_transaction_group(\n    self,\n    date: datetime.date,\n    payee: str,\n    transactions: List[int],\n    category_id: Optional[int] = None,\n    notes: Optional[str] = None,\n    tags: Optional[List[int]] = None,\n) -> int:\n    \"\"\"\n    Create a Transaction Group of Two or More Transactions\n\n    Returns the ID of the newly created transaction group\n\n    Parameters\n    ----------\n    date: datetime.date\n        Date for the grouped transaction\n    payee: str\n        Payee name for the grouped transaction\n    category_id: Optional[int]\n        Category for the grouped transaction\n    notes: Optional[str]\n        Notes for the grouped transaction\n    tags: Optional[List[int]]\n        Array of tag IDs for the grouped transaction\n    transactions: Optional[List[int]]\n        Array of transaction IDs to be part of the transaction group\n\n    Returns\n    -------\n    int\n    \"\"\"\n    if len(transactions) < 2:\n        raise LunchMoneyError(\n            \"You must include 2 or more transactions \" \"in the Transaction Group\"\n        )\n    transaction_params = _TransactionGroupParamsPost(\n        date=date,\n        payee=payee,\n        category_id=category_id,\n        notes=notes,\n        tags=tags,\n        transactions=transactions,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[\n            APIConfig.LUNCHMONEY_TRANSACTIONS,\n            APIConfig.LUNCHMONEY_TRANSACTION_GROUPS,\n        ],\n        payload=transaction_params,\n    )\n    return response_data\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.insert_transactions","title":"insert_transactions(transactions, apply_rules=False, skip_duplicates=True, debit_as_negative=False, check_for_recurring=False, skip_balance_update=True)","text":"

Create One or Many Lunch Money Transactions

Use this endpoint to insert many transactions at once. Also accepts a single transaction as well. If a TransactionObject is provided it will be converted into a TransactionInsertObject.

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default transactions ListOrSingleTransactionInsertObject

Transactions to insert. Either a single TransactionInsertObject object or a list of them

required apply_rules bool

If true, will apply account's existing rules to the inserted transactions. Defaults to false.

False skip_duplicates bool

If true, the system will automatically dedupe based on transaction date, payee and amount. Note that deduping by external_id will occur regardless of this flag.

True check_for_recurring bool

if true, will check new transactions for occurrences of new monthly expenses. Defaults to false.

False debit_as_negative bool

If true, will assume negative amount values denote expenses and positive amount values denote credits. Defaults to false.

False skip_balance_update bool

If false, will skip updating balance if an asset_id is present for any of the transactions.

True

Returns:

Type Description List[int]

Examples:

Create a new transaction with a TransactionInsertObject

from lunchable import LunchMoney, TransactionInsertObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\n\nnew_transaction = TransactionInsertObject(payee=\"Example Restaurant\",\n                                          amount=120.00,\n                                          notes=\"Saturday Dinner\")\nnew_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n
Source code in lunchable/models/transactions.py
def insert_transactions(\n    self,\n    transactions: ListOrSingleTransactionInsertObject,\n    apply_rules: bool = False,\n    skip_duplicates: bool = True,\n    debit_as_negative: bool = False,\n    check_for_recurring: bool = False,\n    skip_balance_update: bool = True,\n) -> List[int]:\n    \"\"\"\n    Create One or Many Lunch Money Transactions\n\n    Use this endpoint to insert many transactions at once. Also accepts\n    a single transaction as well. If a TransactionObject is provided it will be\n    converted into a TransactionInsertObject.\n\n    https://lunchmoney.dev/#insert-transactions\n\n    Parameters\n    ----------\n    transactions: ListOrSingleTransactionTypeObject\n        Transactions to insert. Either a single TransactionInsertObject object or\n        a list of them\n    apply_rules: bool\n        If true, will apply account's existing rules to the inserted transactions.\n        Defaults to false.\n    skip_duplicates: bool\n        If true, the system will automatically dedupe based on transaction date,\n        payee and amount. Note that deduping by external_id will occur regardless\n        of this flag.\n    check_for_recurring: bool\n        if true, will check new transactions for occurrences of new monthly expenses.\n        Defaults to false.\n    debit_as_negative: bool\n        If true, will assume negative amount values denote expenses and\n        positive amount values denote credits. Defaults to false.\n    skip_balance_update: bool\n        If false, will skip updating balance if an asset_id\n        is present for any of the transactions.\n\n    Returns\n    -------\n    List[int]\n\n    Examples\n    --------\n    Create a new transaction with a\n    [TransactionInsertObject][lunchable.models.transactions.TransactionInsertObject]\n\n    ```python\n    from lunchable import LunchMoney, TransactionInsertObject\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n\n    new_transaction = TransactionInsertObject(payee=\"Example Restaurant\",\n                                              amount=120.00,\n                                              notes=\"Saturday Dinner\")\n    new_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n    ```\n    \"\"\"\n    insert_objects = []\n    if not isinstance(transactions, list):\n        transactions = [transactions]\n    for item in transactions:\n        if isinstance(item, TransactionObject):\n            insert_objects.append(item.get_insert_object())\n        elif isinstance(item, TransactionInsertObject):\n            insert_objects.append(item)\n        else:\n            raise LunchMoneyError(\n                \"Only TransactionObjects or TransactionInsertObjects are \"\n                \"supported by this function.\"\n            )\n    payload = _TransactionInsertParamsPost(\n        transactions=insert_objects,\n        apply_rules=apply_rules,\n        skip_duplicates=skip_duplicates,\n        check_for_recurring=check_for_recurring,\n        debit_as_negative=debit_as_negative,\n        skip_balance_update=skip_balance_update,\n    ).model_dump(exclude_unset=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=APIConfig.LUNCHMONEY_TRANSACTIONS,\n        payload=payload,\n    )\n    ids: List[int] = response_data[\"ids\"] if response_data else []\n    return ids\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.remove_transaction_group","title":"remove_transaction_group(transaction_group_id)","text":"

Delete a Transaction Group

Use this method to delete a transaction group. The transactions within the group will not be removed.

Returns the IDs of the transactions that were part of the deleted group

https://lunchmoney.dev/#delete-transaction-group

Parameters:

Name Type Description Default transaction_group_id int

Transaction Group Identifier

required

Returns:

Type Description List[int] Source code in lunchable/models/transactions.py
def remove_transaction_group(self, transaction_group_id: int) -> List[int]:\n    \"\"\"\n    Delete a Transaction Group\n\n    Use this method to delete a transaction group. The transactions within the\n    group will not be removed.\n\n    Returns the IDs of the transactions that were part of the deleted group\n\n    https://lunchmoney.dev/#delete-transaction-group\n\n    Parameters\n    ----------\n    transaction_group_id: int\n        Transaction Group Identifier\n\n    Returns\n    -------\n    List[int]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.DELETE,\n        url_path=[\n            APIConfig.LUNCHMONEY_TRANSACTIONS,\n            APIConfig.LUNCHMONEY_TRANSACTION_GROUPS,\n            transaction_group_id,\n        ],\n    )\n    return response_data[\"transactions\"]\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.unsplit_transactions","title":"unsplit_transactions(parent_ids, remove_parents=False)","text":"

Unsplit Transactions

Use this endpoint to unsplit one or more transactions.

Returns an array of IDs of deleted transactions

https://lunchmoney.dev/#unsplit-transactions

Parameters:

Name Type Description Default parent_ids List[int]

Array of transaction IDs to unsplit. If one transaction is unsplittable, no transaction will be unsplit.

required remove_parents bool

If true, deletes the original parent transaction as well. Note, this is unreversable!

False

Returns:

Type Description List[int] Source code in lunchable/models/transactions.py
def unsplit_transactions(\n    self, parent_ids: List[int], remove_parents: bool = False\n) -> List[int]:\n    \"\"\"\n    Unsplit Transactions\n\n    Use this endpoint to unsplit one or more transactions.\n\n    Returns an array of IDs of deleted transactions\n\n    https://lunchmoney.dev/#unsplit-transactions\n\n    Parameters\n    ----------\n    parent_ids: List[int]\n        Array of transaction IDs to unsplit. If one transaction is unsplittable,\n        no transaction will be unsplit.\n    remove_parents: bool\n        If true, deletes the original parent transaction as well. Note,\n        this is unreversable!\n\n    Returns\n    -------\n    List[int]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, \"unsplit\"],\n        payload=_TransactionsUnsplitPost(\n            parent_ids=parent_ids, remove_parents=remove_parents\n        ).model_dump(exclude_none=True),\n    )\n    return response_data\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.update_transaction","title":"update_transaction(transaction_id, transaction=None, split=None, debit_as_negative=False, skip_balance_update=True)","text":"

Update a Transaction

Use this endpoint to update a single transaction. You may also use this to split an existing transaction. If a TransactionObject is provided it will be converted into a TransactionUpdateObject.

PUT https://dev.lunchmoney.app/v1/transactions/:transaction_id

Parameters:

Name Type Description Default transaction_id int

Lunch Money Transaction ID

required transaction ListOrSingleTransactionUpdateObject

Object to update with

None split Optional[List[TransactionSplitObject]]

Defines the split of a transaction. You may not split an already-split transaction, recurring transaction, or group transaction.

None debit_as_negative bool

If true, will assume negative amount values denote expenses and positive amount values denote credits. Defaults to false.

False skip_balance_update bool

If false, will skip updating balance if an asset_id is present for any of the transactions.

True

Returns:

Type Description Dict[str, Any]

Examples:

Update a transaction with a TransactionUpdateObject

from datetime import datetime\n\nfrom lunchable import LunchMoney, TransactionUpdateObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction_note = f\"Updated on {datetime.now()}\"\nnotes_update = TransactionUpdateObject(notes=transaction_note)\nresponse = lunch.update_transaction(transaction_id=1234,\n                                    transaction=notes_update)\n

Update a TransactionObject with itself

from datetime import datetime, timedelta\n\nfrom lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction = lunch.get_transaction(transaction_id=1234)\n\ntransaction.notes = f\"Updated on {datetime.now()}\"\ntransaction.date = transaction.date + timedelta(days=1)\nresponse = lunch.update_transaction(transaction_id=transaction.id,\n                                    transaction=transaction)\n
Source code in lunchable/models/transactions.py
def update_transaction(\n    self,\n    transaction_id: int,\n    transaction: ListOrSingleTransactionUpdateObject = None,\n    split: Optional[List[TransactionSplitObject]] = None,\n    debit_as_negative: bool = False,\n    skip_balance_update: bool = True,\n) -> Dict[str, Any]:\n    \"\"\"\n    Update a Transaction\n\n    Use this endpoint to update a single transaction. You may also use this\n    to split an existing transaction. If a TransactionObject is provided it will be\n    converted into a TransactionUpdateObject.\n\n    PUT https://dev.lunchmoney.app/v1/transactions/:transaction_id\n\n    Parameters\n    ----------\n    transaction_id: int\n        Lunch Money Transaction ID\n    transaction: ListOrSingleTransactionUpdateObject\n        Object to update with\n    split: Optional[List[TransactionSplitObject]]\n        Defines the split of a transaction. You may not split an already-split\n        transaction, recurring transaction, or group transaction.\n    debit_as_negative: bool\n        If true, will assume negative amount values denote expenses and\n        positive amount values denote credits. Defaults to false.\n    skip_balance_update: bool\n        If false, will skip updating balance if an asset_id\n        is present for any of the transactions.\n\n    Returns\n    -------\n    Dict[str, Any]\n\n    Examples\n    --------\n    Update a transaction with a\n    [TransactionUpdateObject][lunchable.models.transactions.TransactionUpdateObject]\n\n    ```python\n    from datetime import datetime\n\n    from lunchable import LunchMoney, TransactionUpdateObject\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transaction_note = f\"Updated on {datetime.now()}\"\n    notes_update = TransactionUpdateObject(notes=transaction_note)\n    response = lunch.update_transaction(transaction_id=1234,\n                                        transaction=notes_update)\n    ```\n\n    Update a\n    [TransactionObject][lunchable.models.transactions.TransactionObject]\n    with itself\n\n    ```python\n    from datetime import datetime, timedelta\n\n    from lunchable import LunchMoney\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transaction = lunch.get_transaction(transaction_id=1234)\n\n    transaction.notes = f\"Updated on {datetime.now()}\"\n    transaction.date = transaction.date + timedelta(days=1)\n    response = lunch.update_transaction(transaction_id=transaction.id,\n                                        transaction=transaction)\n    ```\n    \"\"\"\n    payload = _TransactionUpdateParamsPut(\n        split=split,\n        debit_as_negative=debit_as_negative,\n        skip_balance_update=skip_balance_update,\n    ).model_dump(exclude_none=True)\n    if transaction is None and split is None:\n        raise LunchMoneyError(\"You must update the transaction or provide a split\")\n    elif transaction is not None:\n        if isinstance(transaction, TransactionObject):\n            transaction = transaction.get_update_object()\n        payload[\"transaction\"] = transaction.model_dump(exclude_unset=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, transaction_id],\n        payload=payload,\n    )\n    return response_data\n
"},{"location":"reference/models/user/","title":"user","text":"

Lunch Money - User

https://lunchmoney.dev/#user

"},{"location":"reference/models/user/#lunchable.models.user.UserClient","title":"UserClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Interactions for Non Finance Operations

Source code in lunchable/models/user.py
class UserClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Interactions for Non Finance Operations\n    \"\"\"\n\n    def get_user(self) -> UserObject:\n        \"\"\"\n        Get Personal User Details\n\n        Use this endpoint to get details on the current user.\n\n        https://lunchmoney.dev/#get-user\n\n        Returns\n        -------\n        UserObject\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_ME\n        )\n        me = UserObject.model_validate(response_data)\n        return me\n
"},{"location":"reference/models/user/#lunchable.models.user.UserClient.get_user","title":"get_user()","text":"

Get Personal User Details

Use this endpoint to get details on the current user.

https://lunchmoney.dev/#get-user

Returns:

Type Description UserObject Source code in lunchable/models/user.py
def get_user(self) -> UserObject:\n    \"\"\"\n    Get Personal User Details\n\n    Use this endpoint to get details on the current user.\n\n    https://lunchmoney.dev/#get-user\n\n    Returns\n    -------\n    UserObject\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_ME\n    )\n    me = UserObject.model_validate(response_data)\n    return me\n
"},{"location":"reference/models/user/#lunchable.models.user.UserObject","title":"UserObject","text":"

Bases: LunchableModel

The LunchMoney User object

https://lunchmoney.dev/#user-object

Parameters:

Name Type Description Default user_id int

Unique identifier for user

required user_name str

User's' name

required user_email str

User's' Email

required account_id int

Unique identifier for the associated budgeting account

required budget_name str

Name of the associated budgeting account

required api_key_label str | None

User-defined label of the developer API key used. Returns null if nothing has been set.

None Source code in lunchable/models/user.py
class UserObject(LunchableModel):\n    \"\"\"\n    The LunchMoney `User` object\n\n    https://lunchmoney.dev/#user-object\n    \"\"\"\n\n    user_id: int = Field(description=\"Unique identifier for user\")\n    user_name: str = Field(description=\"User's' name\")\n    user_email: str = Field(description=\"User's' Email\")\n    account_id: int = Field(\n        description=\"Unique identifier for the associated budgeting account\"\n    )\n    budget_name: str = Field(description=\"Name of the associated budgeting account\")\n    api_key_label: Optional[str] = Field(\n        None,\n        description=\"User-defined label of the developer API key used. \"\n        \"Returns null if nothing has been set.\",\n    )\n
"},{"location":"reference/plugins/","title":"plugins","text":"

Optional Plugins for LunchMoney

"},{"location":"reference/plugins/#lunchable.plugins.LunchableApp","title":"LunchableApp","text":"

Bases: BaseLunchableApp

Pre-Built Lunchable App

This app comes with a data property which represents all the base data the app should need. Extend the data_models property to items like TransactionObjects to interact with transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableApp(BaseLunchableApp):\n    \"\"\"\n    Pre-Built Lunchable App\n\n    This app comes with a `data` property which represents all the base data\n    the app should need. Extend the `data_models` property to items like\n    `TransactionObject`s to interact with transactions\n    \"\"\"\n\n    @property\n    def lunchable_models(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Which Data Should this app get\n        \"\"\"\n        return []\n\n    @property\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Built-In Models to Populate Most LunchableApp instances\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n        return [\n            LunchableDataModel(\n                model=CategoriesObject, function=self.lunch.get_categories\n            ),\n            LunchableDataModel(\n                model=PlaidAccountObject,\n                function=self.lunch.get_plaid_accounts,\n            ),\n            LunchableDataModel(\n                model=AssetsObject,\n                function=self.lunch.get_assets,\n            ),\n            LunchableDataModel(\n                model=TagsObject,\n                function=self.lunch.get_tags,\n            ),\n            LunchableDataModel(\n                model=UserObject,\n                function=self.lunch.get_user,\n            ),\n            LunchableDataModel(\n                model=CryptoObject,\n                function=self.lunch.get_crypto,\n            ),\n        ]\n
"},{"location":"reference/plugins/#lunchable.plugins.LunchableApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] property","text":"

Built-In Models to Populate Most LunchableApp instances

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/#lunchable.plugins.LunchableApp.lunchable_models","title":"lunchable_models: List[LunchableDataModel] property","text":"

Which Data Should this app get

"},{"location":"reference/plugins/#lunchable.plugins.LunchableTransactionsApp","title":"LunchableTransactionsApp","text":"

Bases: LunchableTransactionsBaseApp

Pre-Built Lunchable App with the last 365 days worth of transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableTransactionsApp(LunchableTransactionsBaseApp):\n    \"\"\"\n    Pre-Built Lunchable App with the last 365 days worth of transactions\n    \"\"\"\n\n    @property\n    def start_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a Start Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year - 100)\n\n    @property\n    def end_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a End Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year + 100)\n
"},{"location":"reference/plugins/#lunchable.plugins.LunchableTransactionsApp.end_date","title":"end_date: datetime.date property","text":"

LunchableTransactionsApp requires a End Date

Returns:

Type Description date"},{"location":"reference/plugins/#lunchable.plugins.LunchableTransactionsApp.start_date","title":"start_date: datetime.date property","text":"

LunchableTransactionsApp requires a Start Date

Returns:

Type Description date"},{"location":"reference/plugins/base/","title":"base","text":"

Base Apps for Plugins

"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableApp","title":"LunchableApp","text":"

Bases: BaseLunchableApp

Pre-Built Lunchable App

This app comes with a data property which represents all the base data the app should need. Extend the data_models property to items like TransactionObjects to interact with transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableApp(BaseLunchableApp):\n    \"\"\"\n    Pre-Built Lunchable App\n\n    This app comes with a `data` property which represents all the base data\n    the app should need. Extend the `data_models` property to items like\n    `TransactionObject`s to interact with transactions\n    \"\"\"\n\n    @property\n    def lunchable_models(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Which Data Should this app get\n        \"\"\"\n        return []\n\n    @property\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Built-In Models to Populate Most LunchableApp instances\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n        return [\n            LunchableDataModel(\n                model=CategoriesObject, function=self.lunch.get_categories\n            ),\n            LunchableDataModel(\n                model=PlaidAccountObject,\n                function=self.lunch.get_plaid_accounts,\n            ),\n            LunchableDataModel(\n                model=AssetsObject,\n                function=self.lunch.get_assets,\n            ),\n            LunchableDataModel(\n                model=TagsObject,\n                function=self.lunch.get_tags,\n            ),\n            LunchableDataModel(\n                model=UserObject,\n                function=self.lunch.get_user,\n            ),\n            LunchableDataModel(\n                model=CryptoObject,\n                function=self.lunch.get_crypto,\n            ),\n        ]\n
"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] property","text":"

Built-In Models to Populate Most LunchableApp instances

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableApp.lunchable_models","title":"lunchable_models: List[LunchableDataModel] property","text":"

Which Data Should this app get

"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableTransactionsApp","title":"LunchableTransactionsApp","text":"

Bases: LunchableTransactionsBaseApp

Pre-Built Lunchable App with the last 365 days worth of transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableTransactionsApp(LunchableTransactionsBaseApp):\n    \"\"\"\n    Pre-Built Lunchable App with the last 365 days worth of transactions\n    \"\"\"\n\n    @property\n    def start_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a Start Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year - 100)\n\n    @property\n    def end_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a End Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year + 100)\n
"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableTransactionsApp.end_date","title":"end_date: datetime.date property","text":"

LunchableTransactionsApp requires a End Date

Returns:

Type Description date"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableTransactionsApp.start_date","title":"start_date: datetime.date property","text":"

LunchableTransactionsApp requires a Start Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/","title":"base_app","text":"

Base App Class

"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp","title":"BaseLunchableApp","text":"

Bases: ABC

Abstract Base Class for Lunchable Apps

Source code in lunchable/plugins/base/base_app.py
class BaseLunchableApp(ABC):\n    \"\"\"\n    Abstract Base Class for Lunchable Apps\n    \"\"\"\n\n    __lunchable_object_mapping__: Dict[str, str] = {\n        PlaidAccountObject.__name__: \"plaid_accounts\",\n        TransactionObject.__name__: \"transactions\",\n        CategoriesObject.__name__: \"categories\",\n        AssetsObject.__name__: \"assets\",\n        TagsObject.__name__: \"tags\",\n        UserObject.__name__: \"user\",\n        CryptoObject.__name__: \"crypto\",\n    }\n\n    @property\n    @abstractmethod\n    def lunchable_models(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Every LunchableApp should define which data objects it depends on\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n\n    @property\n    @abstractmethod\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Every LunchableApp should define which data objects are built-in\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n\n    def __init__(self, cache_time: int = 0, access_token: Optional[str] = None):\n        \"\"\"\n        Lunchable App Initialization\n\n        Parameters\n        ----------\n        cache_time: int\n            Amount of time until the cache should be refreshed\n            (in seconds). Defaults to 0 which always polls for the latest data\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n        \"\"\"\n        self.lunch = LunchMoney(access_token=access_token)\n        self.lunch_data = LunchableDataContainer()\n        self.data_dir = FileConfig.DATA_DIR.joinpath(self.__class__.__name__).joinpath(\n            sha256(self.lunch.access_token.encode(\"utf-8\")).hexdigest()\n        )\n        self.cache_time = cache_time\n        if self.cache_time > 0:\n            self.data_dir.mkdir(exist_ok=True, parents=True)\n\n    def _cache_single_object(\n        self,\n        model: Type[LunchableModelType],\n        function: Callable[[Any], Any],\n        kwargs: Optional[Dict[str, Any]] = None,\n        force: bool = False,\n    ) -> Union[LunchableModelType, List[LunchableModelType]]:\n        \"\"\"\n        Cache a Core Lunchable Data Object\n\n        Parameters\n        ----------\n        model: Type[LunchableModel]\n        function: Callable\n        kwargs: Optional[Dict[str, Any]]\n        force: bool\n\n        Returns\n        -------\n        Any\n        \"\"\"\n        if kwargs is None:\n            kwargs = {}\n        data_file = self.data_dir.joinpath(f\"{model.__name__}.lunch\")\n        if force is True:\n            refresh = True\n        elif self.cache_time > 0 and data_file.exists():\n            modified_time = datetime.datetime.fromtimestamp(\n                data_file.stat().st_mtime, tz=datetime.timezone.utc\n            )\n            current_time = datetime.datetime.now(tz=datetime.timezone.utc)\n            file_age = current_time - modified_time\n            refresh = file_age > datetime.timedelta(seconds=self.cache_time)\n        else:\n            refresh = True\n        if refresh is True:\n            data_objects = function(**kwargs)  # type: ignore[call-arg]\n            if self.cache_time > 0:\n                plain_data: str = json.dumps(data_objects, default=pydantic_encoder)\n                base64_data: bytes = base64.b64encode(plain_data.encode(\"utf-8\"))\n                data_file.write_bytes(base64_data)\n        else:\n            file_text: bytes = data_file.read_bytes()\n            json_body: bytes = base64.b64decode(file_text)\n            json_data: Union[Dict[str, Any], List[Dict[str, Any]]] = json.loads(\n                json_body.decode(\"utf-8\")\n            )\n            if isinstance(json_data, dict):\n                data_objects = model.model_validate(json_data)\n            else:\n                data_objects = [model.model_validate(item) for item in json_data]\n        return data_objects\n\n    def get_latest_cache(\n        self,\n        include: Optional[List[Type[LunchableModel]]] = None,\n        exclude: Optional[List[Type[LunchableModel]]] = None,\n        force: bool = False,\n    ) -> None:\n        \"\"\"\n        Cache the Underlying Data Objects\n\n        Parameters\n        ----------\n        include : Optional[List[Type[LunchableModel]]]\n            Models to refresh cache for (instead of all of them)\n        exclude : Optional[List[Type[LunchableModel]]]\n            Models to skip cache refreshing\n        force: bool\n            Whether to force the cache\n\n        Returns\n        -------\n        None\n        \"\"\"\n        models_to_process = self.lunchable_models + self.__builtin_data_models__\n        if include is not None:\n            new_models_to_process: List[LunchableDataModel] = []\n            data_model_mapping = {item.model: item for item in models_to_process}\n            for model_class in include:\n                new_models_to_process.append(data_model_mapping[model_class])\n            models_to_process = new_models_to_process\n        exclusions = exclude if exclude is not None else []\n        for data_model in models_to_process:\n            if data_model.model in exclusions:\n                continue\n            cache = self._cache_single_object(\n                model=data_model.model,\n                function=data_model.function,\n                kwargs=data_model.kwargs,\n                force=force,\n            )\n            cache_attribute: Union[Dict[int, LunchableModel], LunchableModel]\n            if isinstance(cache, list):\n                cache_attribute = {item.id: item for item in cache}\n            else:\n                cache_attribute = cache\n            setattr(\n                self.lunch_data,\n                self.__lunchable_object_mapping__[data_model.model.__name__],\n                cache_attribute,\n            )\n\n    def refresh_transactions(\n        self,\n        start_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n        end_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n        tag_id: Optional[int] = None,\n        recurring_id: Optional[int] = None,\n        plaid_account_id: Optional[int] = None,\n        category_id: Optional[int] = None,\n        asset_id: Optional[int] = None,\n        group_id: Optional[int] = None,\n        is_group: Optional[bool] = None,\n        status: Optional[str] = None,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        debit_as_negative: Optional[bool] = None,\n        pending: Optional[bool] = None,\n        params: Optional[Dict[str, Any]] = None,\n    ) -> Dict[int, TransactionObject]:\n        \"\"\"\n        Refresh App data with the latest transactions\n\n        Parameters\n        ----------\n        start_date: Optional[Union[datetime.date, datetime.datetime, str]]\n            Denotes the beginning of the time period to fetch transactions for. Defaults\n            to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.\n        end_date: Optional[Union[datetime.date, datetime.datetime, str]]\n            Denotes the end of the time period you'd like to get transactions for.\n            Defaults to end of current month. Required if start_date exists.\n        tag_id: Optional[int]\n            Filter by tag. Only accepts IDs, not names.\n        recurring_id: Optional[int]\n            Filter by recurring expense\n        plaid_account_id: Optional[int]\n            Filter by Plaid account\n        category_id: Optional[int]\n            Filter by category. Will also match category groups.\n        asset_id: Optional[int]\n            Filter by asset\n        group_id: Optional[int]\n            Filter by group_id (if the transaction is part of a specific group)\n        is_group: Optional[bool]\n            Filter by group (returns transaction groups)\n        status: Optional[str]\n            Filter by status (Can be cleared or uncleared. For recurring\n            transactions, use recurring)\n        offset: Optional[int]\n            Sets the offset for the records returned\n        limit: Optional[int]\n            Sets the maximum number of records to return. Note: The server will not\n            respond with any indication that there are more records to be returned.\n            Please check the response length to determine if you should make another\n            call with an offset to fetch more transactions.\n        debit_as_negative: Optional[bool]\n            Pass in true if you'd like expenses to be returned as negative amounts and\n            credits as positive amounts. Defaults to false.\n        pending: Optional[bool]\n            Pass in true if you'd like to include imported transactions with a pending status.\n        params: Optional[dict]\n            Additional Query String Params\n\n        Returns\n        -------\n        Dict[int, TransactionObject]\n        \"\"\"\n        transactions = self.lunch.get_transactions(\n            start_date=start_date, end_date=end_date, status=status\n        )\n        transaction_map = {item.id: item for item in transactions}\n        self.lunch_data.transactions = transaction_map\n        return transaction_map\n\n    def delete_cache(self) -> None:\n        \"\"\"\n        Delete any corresponding cache files\n        \"\"\"\n        if self.data_dir.exists():\n            shutil.rmtree(self.data_dir)\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] abstractmethod property","text":"

Every LunchableApp should define which data objects are built-in

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.lunchable_models","title":"lunchable_models: List[LunchableDataModel] abstractmethod property","text":"

Every LunchableApp should define which data objects it depends on

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.__init__","title":"__init__(cache_time=0, access_token=None)","text":"

Lunchable App Initialization

Parameters:

Name Type Description Default cache_time int

Amount of time until the cache should be refreshed (in seconds). Defaults to 0 which always polls for the latest data

0 access_token Optional[str]

Lunchmoney Developer API Access Token

None Source code in lunchable/plugins/base/base_app.py
def __init__(self, cache_time: int = 0, access_token: Optional[str] = None):\n    \"\"\"\n    Lunchable App Initialization\n\n    Parameters\n    ----------\n    cache_time: int\n        Amount of time until the cache should be refreshed\n        (in seconds). Defaults to 0 which always polls for the latest data\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n    \"\"\"\n    self.lunch = LunchMoney(access_token=access_token)\n    self.lunch_data = LunchableDataContainer()\n    self.data_dir = FileConfig.DATA_DIR.joinpath(self.__class__.__name__).joinpath(\n        sha256(self.lunch.access_token.encode(\"utf-8\")).hexdigest()\n    )\n    self.cache_time = cache_time\n    if self.cache_time > 0:\n        self.data_dir.mkdir(exist_ok=True, parents=True)\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.delete_cache","title":"delete_cache()","text":"

Delete any corresponding cache files

Source code in lunchable/plugins/base/base_app.py
def delete_cache(self) -> None:\n    \"\"\"\n    Delete any corresponding cache files\n    \"\"\"\n    if self.data_dir.exists():\n        shutil.rmtree(self.data_dir)\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.get_latest_cache","title":"get_latest_cache(include=None, exclude=None, force=False)","text":"

Cache the Underlying Data Objects

Parameters:

Name Type Description Default include Optional[List[Type[LunchableModel]]]

Models to refresh cache for (instead of all of them)

None exclude Optional[List[Type[LunchableModel]]]

Models to skip cache refreshing

None force bool

Whether to force the cache

False

Returns:

Type Description None Source code in lunchable/plugins/base/base_app.py
def get_latest_cache(\n    self,\n    include: Optional[List[Type[LunchableModel]]] = None,\n    exclude: Optional[List[Type[LunchableModel]]] = None,\n    force: bool = False,\n) -> None:\n    \"\"\"\n    Cache the Underlying Data Objects\n\n    Parameters\n    ----------\n    include : Optional[List[Type[LunchableModel]]]\n        Models to refresh cache for (instead of all of them)\n    exclude : Optional[List[Type[LunchableModel]]]\n        Models to skip cache refreshing\n    force: bool\n        Whether to force the cache\n\n    Returns\n    -------\n    None\n    \"\"\"\n    models_to_process = self.lunchable_models + self.__builtin_data_models__\n    if include is not None:\n        new_models_to_process: List[LunchableDataModel] = []\n        data_model_mapping = {item.model: item for item in models_to_process}\n        for model_class in include:\n            new_models_to_process.append(data_model_mapping[model_class])\n        models_to_process = new_models_to_process\n    exclusions = exclude if exclude is not None else []\n    for data_model in models_to_process:\n        if data_model.model in exclusions:\n            continue\n        cache = self._cache_single_object(\n            model=data_model.model,\n            function=data_model.function,\n            kwargs=data_model.kwargs,\n            force=force,\n        )\n        cache_attribute: Union[Dict[int, LunchableModel], LunchableModel]\n        if isinstance(cache, list):\n            cache_attribute = {item.id: item for item in cache}\n        else:\n            cache_attribute = cache\n        setattr(\n            self.lunch_data,\n            self.__lunchable_object_mapping__[data_model.model.__name__],\n            cache_attribute,\n        )\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.refresh_transactions","title":"refresh_transactions(start_date=None, end_date=None, tag_id=None, recurring_id=None, plaid_account_id=None, category_id=None, asset_id=None, group_id=None, is_group=None, status=None, offset=None, limit=None, debit_as_negative=None, pending=None, params=None)","text":"

Refresh App data with the latest transactions

Parameters:

Name Type Description Default start_date Optional[Union[date, datetime, str]]

Denotes the beginning of the time period to fetch transactions for. Defaults to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.

None end_date Optional[Union[date, datetime, str]]

Denotes the end of the time period you'd like to get transactions for. Defaults to end of current month. Required if start_date exists.

None tag_id Optional[int]

Filter by tag. Only accepts IDs, not names.

None recurring_id Optional[int]

Filter by recurring expense

None plaid_account_id Optional[int]

Filter by Plaid account

None category_id Optional[int]

Filter by category. Will also match category groups.

None asset_id Optional[int]

Filter by asset

None group_id Optional[int]

Filter by group_id (if the transaction is part of a specific group)

None is_group Optional[bool]

Filter by group (returns transaction groups)

None status Optional[str]

Filter by status (Can be cleared or uncleared. For recurring transactions, use recurring)

None offset Optional[int]

Sets the offset for the records returned

None limit Optional[int]

Sets the maximum number of records to return. Note: The server will not respond with any indication that there are more records to be returned. Please check the response length to determine if you should make another call with an offset to fetch more transactions.

None debit_as_negative Optional[bool]

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts. Defaults to false.

None pending Optional[bool]

Pass in true if you'd like to include imported transactions with a pending status.

None params Optional[Dict[str, Any]]

Additional Query String Params

None

Returns:

Type Description Dict[int, TransactionObject] Source code in lunchable/plugins/base/base_app.py
def refresh_transactions(\n    self,\n    start_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n    end_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n    tag_id: Optional[int] = None,\n    recurring_id: Optional[int] = None,\n    plaid_account_id: Optional[int] = None,\n    category_id: Optional[int] = None,\n    asset_id: Optional[int] = None,\n    group_id: Optional[int] = None,\n    is_group: Optional[bool] = None,\n    status: Optional[str] = None,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    debit_as_negative: Optional[bool] = None,\n    pending: Optional[bool] = None,\n    params: Optional[Dict[str, Any]] = None,\n) -> Dict[int, TransactionObject]:\n    \"\"\"\n    Refresh App data with the latest transactions\n\n    Parameters\n    ----------\n    start_date: Optional[Union[datetime.date, datetime.datetime, str]]\n        Denotes the beginning of the time period to fetch transactions for. Defaults\n        to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.\n    end_date: Optional[Union[datetime.date, datetime.datetime, str]]\n        Denotes the end of the time period you'd like to get transactions for.\n        Defaults to end of current month. Required if start_date exists.\n    tag_id: Optional[int]\n        Filter by tag. Only accepts IDs, not names.\n    recurring_id: Optional[int]\n        Filter by recurring expense\n    plaid_account_id: Optional[int]\n        Filter by Plaid account\n    category_id: Optional[int]\n        Filter by category. Will also match category groups.\n    asset_id: Optional[int]\n        Filter by asset\n    group_id: Optional[int]\n        Filter by group_id (if the transaction is part of a specific group)\n    is_group: Optional[bool]\n        Filter by group (returns transaction groups)\n    status: Optional[str]\n        Filter by status (Can be cleared or uncleared. For recurring\n        transactions, use recurring)\n    offset: Optional[int]\n        Sets the offset for the records returned\n    limit: Optional[int]\n        Sets the maximum number of records to return. Note: The server will not\n        respond with any indication that there are more records to be returned.\n        Please check the response length to determine if you should make another\n        call with an offset to fetch more transactions.\n    debit_as_negative: Optional[bool]\n        Pass in true if you'd like expenses to be returned as negative amounts and\n        credits as positive amounts. Defaults to false.\n    pending: Optional[bool]\n        Pass in true if you'd like to include imported transactions with a pending status.\n    params: Optional[dict]\n        Additional Query String Params\n\n    Returns\n    -------\n    Dict[int, TransactionObject]\n    \"\"\"\n    transactions = self.lunch.get_transactions(\n        start_date=start_date, end_date=end_date, status=status\n    )\n    transaction_map = {item.id: item for item in transactions}\n    self.lunch_data.transactions = transaction_map\n    return transaction_map\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableApp","title":"LunchableApp","text":"

Bases: BaseLunchableApp

Pre-Built Lunchable App

This app comes with a data property which represents all the base data the app should need. Extend the data_models property to items like TransactionObjects to interact with transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableApp(BaseLunchableApp):\n    \"\"\"\n    Pre-Built Lunchable App\n\n    This app comes with a `data` property which represents all the base data\n    the app should need. Extend the `data_models` property to items like\n    `TransactionObject`s to interact with transactions\n    \"\"\"\n\n    @property\n    def lunchable_models(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Which Data Should this app get\n        \"\"\"\n        return []\n\n    @property\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Built-In Models to Populate Most LunchableApp instances\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n        return [\n            LunchableDataModel(\n                model=CategoriesObject, function=self.lunch.get_categories\n            ),\n            LunchableDataModel(\n                model=PlaidAccountObject,\n                function=self.lunch.get_plaid_accounts,\n            ),\n            LunchableDataModel(\n                model=AssetsObject,\n                function=self.lunch.get_assets,\n            ),\n            LunchableDataModel(\n                model=TagsObject,\n                function=self.lunch.get_tags,\n            ),\n            LunchableDataModel(\n                model=UserObject,\n                function=self.lunch.get_user,\n            ),\n            LunchableDataModel(\n                model=CryptoObject,\n                function=self.lunch.get_crypto,\n            ),\n        ]\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] property","text":"

Built-In Models to Populate Most LunchableApp instances

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableApp.lunchable_models","title":"lunchable_models: List[LunchableDataModel] property","text":"

Which Data Should this app get

"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer","title":"LunchableDataContainer","text":"

Bases: BaseModel

Data Container for Lunchable App Data

Parameters:

Name Type Description Default plaid_accounts Dict[int, PlaidAccountObject] {} transactions Dict[int, TransactionObject] {} categories Dict[int, CategoriesObject] {} assets Dict[int, AssetsObject] {} tags Dict[int, TagsObject] {} user UserObject UserObject(user_id=0, user_name='', user_email='', account_id=0, budget_name='', api_key_label=None) crypto Dict[int, CryptoObject] {} Source code in lunchable/plugins/base/base_app.py
class LunchableDataContainer(BaseModel):\n    \"\"\"\n    Data Container for Lunchable App Data\n    \"\"\"\n\n    plaid_accounts: Dict[int, PlaidAccountObject] = {}\n    transactions: Dict[int, TransactionObject] = {}\n    categories: Dict[int, CategoriesObject] = {}\n    assets: Dict[int, AssetsObject] = {}\n    tags: Dict[int, TagsObject] = {}\n    user: UserObject = UserObject(\n        user_id=0, user_name=\"\", user_email=\"\", account_id=0, budget_name=\"\"\n    )\n    crypto: Dict[int, CryptoObject] = {}\n\n    @property\n    def asset_map(self) -> Dict[int, Union[PlaidAccountObject, AssetsObject]]:\n        \"\"\"\n        Asset Mapping Across Plaid Accounts and Assets\n\n        Returns\n        -------\n        Dict[int, Union[PlaidAccountObject, AssetsObject]]\n        \"\"\"\n        asset_map: Dict[int, Union[PlaidAccountObject, AssetsObject]] = {}\n        asset_map.update(self.plaid_accounts)\n        asset_map.update(self.assets)\n        return asset_map\n\n    @property\n    def plaid_accounts_list(self) -> List[PlaidAccountObject]:\n        \"\"\"\n        List of Plaid Accounts\n\n        Returns\n        -------\n        List[PlaidAccountObject]\n        \"\"\"\n        return list(self.plaid_accounts.values())\n\n    @property\n    def assets_list(self) -> List[AssetsObject]:\n        \"\"\"\n        List of Assets\n\n        Returns\n        -------\n        List[AssetsObject]\n        \"\"\"\n        return list(self.assets.values())\n\n    @property\n    def transactions_list(self) -> List[TransactionObject]:\n        \"\"\"\n        List of Transactions\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        return list(self.transactions.values())\n\n    @property\n    def categories_list(self) -> List[CategoriesObject]:\n        \"\"\"\n        List of Categories\n\n        Returns\n        -------\n        List[CategoriesObject]\n        \"\"\"\n        return list(self.categories.values())\n\n    @property\n    def tags_list(self) -> List[TagsObject]:\n        \"\"\"\n        List of Tags\n\n        Returns\n        -------\n        List[TagsObject]\n        \"\"\"\n        return list(self.tags.values())\n\n    @property\n    def crypto_list(self) -> List[CryptoObject]:\n        \"\"\"\n        List of Crypto\n\n        Returns\n        -------\n        List[CryptoObject]\n        \"\"\"\n        return list(self.crypto.values())\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.asset_map","title":"asset_map: Dict[int, Union[PlaidAccountObject, AssetsObject]] property","text":"

Asset Mapping Across Plaid Accounts and Assets

Returns:

Type Description Dict[int, Union[PlaidAccountObject, AssetsObject]]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.assets_list","title":"assets_list: List[AssetsObject] property","text":"

List of Assets

Returns:

Type Description List[AssetsObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.categories_list","title":"categories_list: List[CategoriesObject] property","text":"

List of Categories

Returns:

Type Description List[CategoriesObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.crypto_list","title":"crypto_list: List[CryptoObject] property","text":"

List of Crypto

Returns:

Type Description List[CryptoObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.plaid_accounts_list","title":"plaid_accounts_list: List[PlaidAccountObject] property","text":"

List of Plaid Accounts

Returns:

Type Description List[PlaidAccountObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.tags_list","title":"tags_list: List[TagsObject] property","text":"

List of Tags

Returns:

Type Description List[TagsObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.transactions_list","title":"transactions_list: List[TransactionObject] property","text":"

List of Transactions

Returns:

Type Description List[TransactionObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataModel","title":"LunchableDataModel","text":"

Bases: LunchableModel

Core Data Model Defining App Dependencies

Parameters:

Name Type Description Default model Type[LunchableModel] required function Callable[list, Any] required kwargs Dict[str, Any] {} Source code in lunchable/plugins/base/base_app.py
class LunchableDataModel(LunchableModel):\n    \"\"\"\n    Core Data Model Defining App Dependencies\n    \"\"\"\n\n    model: Type[LunchableModel]\n    function: Callable[[Any], Any]\n    kwargs: Dict[str, Any] = {}\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsApp","title":"LunchableTransactionsApp","text":"

Bases: LunchableTransactionsBaseApp

Pre-Built Lunchable App with the last 365 days worth of transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableTransactionsApp(LunchableTransactionsBaseApp):\n    \"\"\"\n    Pre-Built Lunchable App with the last 365 days worth of transactions\n    \"\"\"\n\n    @property\n    def start_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a Start Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year - 100)\n\n    @property\n    def end_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a End Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year + 100)\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsApp.end_date","title":"end_date: datetime.date property","text":"

LunchableTransactionsApp requires a End Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsApp.start_date","title":"start_date: datetime.date property","text":"

LunchableTransactionsApp requires a Start Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp","title":"LunchableTransactionsBaseApp","text":"

Bases: LunchableApp, ABC

LunchableApp supporting transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableTransactionsBaseApp(LunchableApp, ABC):\n    \"\"\"\n    LunchableApp supporting transactions\n    \"\"\"\n\n    data_models: List[LunchableDataModel] = []\n\n    @property\n    @abstractmethod\n    def start_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a Start Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n\n    @property\n    @abstractmethod\n    def end_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a End Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n\n    @property\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Which Data Should this app get\n        \"\"\"\n        return [\n            *super().__builtin_data_models__,\n            LunchableDataModel(\n                model=TransactionObject,\n                function=self.lunch.get_transactions,\n                kwargs={\"start_date\": self.start_date, \"end_date\": self.end_date},\n            ),\n        ]\n\n    def refresh_transactions(  # type: ignore[override]\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> Dict[int, TransactionObject]:\n        \"\"\"\n        Refresh App data with the latest transactions\n\n        Returns\n        -------\n        Dict[int, TransactionObject]\n        \"\"\"\n        transactions = self.lunch.get_transactions(\n            start_date=start_date if start_date is not None else self.start_date,\n            end_date=end_date if end_date is not None else self.end_date,\n        )\n        transaction_map = {item.id: item for item in transactions}\n        self.lunch_data.transactions = transaction_map\n        return transaction_map\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] property","text":"

Which Data Should this app get

"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp.end_date","title":"end_date: datetime.date abstractmethod property","text":"

LunchableTransactionsApp requires a End Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp.start_date","title":"start_date: datetime.date abstractmethod property","text":"

LunchableTransactionsApp requires a Start Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp.refresh_transactions","title":"refresh_transactions(start_date=None, end_date=None)","text":"

Refresh App data with the latest transactions

Returns:

Type Description Dict[int, TransactionObject] Source code in lunchable/plugins/base/base_app.py
def refresh_transactions(  # type: ignore[override]\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> Dict[int, TransactionObject]:\n    \"\"\"\n    Refresh App data with the latest transactions\n\n    Returns\n    -------\n    Dict[int, TransactionObject]\n    \"\"\"\n    transactions = self.lunch.get_transactions(\n        start_date=start_date if start_date is not None else self.start_date,\n        end_date=end_date if end_date is not None else self.end_date,\n    )\n    transaction_map = {item.id: item for item in transactions}\n    self.lunch_data.transactions = transaction_map\n    return transaction_map\n
"},{"location":"reference/plugins/base/pandas_app/","title":"pandas_app","text":"

Apps with Pandas Support

"},{"location":"reference/plugins/base/pandas_app/#lunchable.plugins.base.pandas_app.LunchablePandasApp","title":"LunchablePandasApp","text":"

Bases: LunchableApp

LunchableApp with Pandas Super Powers

Source code in lunchable/plugins/base/pandas_app.py
class LunchablePandasApp(LunchableApp):\n    \"\"\"\n    LunchableApp with Pandas Super Powers\n    \"\"\"\n\n    @staticmethod\n    def models_to_df(models: Iterable[LunchableModel]) -> pd.DataFrame:\n        \"\"\"\n        Convert Transactions Array to DataFrame\n\n        Parameters\n        ----------\n        models: List[LunchableModel]\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        if not isinstance(models, list):\n            models = list(models)\n        return pd.DataFrame(\n            [item.model_dump() for item in models],\n            columns=models[0].__fields__.keys(),\n        )\n\n    @staticmethod\n    def df_to_models(\n        df: pd.DataFrame, model_type: Type[LunchableModelType]\n    ) -> List[LunchableModelType]:\n        \"\"\"\n        Convert DataFrame to Transaction Array\n\n        Parameters\n        ----------\n        df: pd.DataFrame\n        model_type: Type[LunchableModel]\n\n        Returns\n        -------\n        List[LunchableModel]\n        \"\"\"\n        array_df = df.copy()\n        array_df = array_df.fillna(np.NaN).replace([np.NaN], [None])\n        model_array = array_df.to_dict(orient=\"records\")\n        return [model_type.model_validate(item) for item in model_array]\n
"},{"location":"reference/plugins/base/pandas_app/#lunchable.plugins.base.pandas_app.LunchablePandasApp.df_to_models","title":"df_to_models(df, model_type) staticmethod","text":"

Convert DataFrame to Transaction Array

Parameters:

Name Type Description Default df DataFrame required model_type Type[LunchableModelType] required

Returns:

Type Description List[LunchableModel] Source code in lunchable/plugins/base/pandas_app.py
@staticmethod\ndef df_to_models(\n    df: pd.DataFrame, model_type: Type[LunchableModelType]\n) -> List[LunchableModelType]:\n    \"\"\"\n    Convert DataFrame to Transaction Array\n\n    Parameters\n    ----------\n    df: pd.DataFrame\n    model_type: Type[LunchableModel]\n\n    Returns\n    -------\n    List[LunchableModel]\n    \"\"\"\n    array_df = df.copy()\n    array_df = array_df.fillna(np.NaN).replace([np.NaN], [None])\n    model_array = array_df.to_dict(orient=\"records\")\n    return [model_type.model_validate(item) for item in model_array]\n
"},{"location":"reference/plugins/base/pandas_app/#lunchable.plugins.base.pandas_app.LunchablePandasApp.models_to_df","title":"models_to_df(models) staticmethod","text":"

Convert Transactions Array to DataFrame

Parameters:

Name Type Description Default models Iterable[LunchableModel] required

Returns:

Type Description DataFrame Source code in lunchable/plugins/base/pandas_app.py
@staticmethod\ndef models_to_df(models: Iterable[LunchableModel]) -> pd.DataFrame:\n    \"\"\"\n    Convert Transactions Array to DataFrame\n\n    Parameters\n    ----------\n    models: List[LunchableModel]\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    if not isinstance(models, list):\n        models = list(models)\n    return pd.DataFrame(\n        [item.model_dump() for item in models],\n        columns=models[0].__fields__.keys(),\n    )\n
"},{"location":"reference/plugins/base/pandas_app/#lunchable.plugins.base.pandas_app.LunchablePandasTransactionsApp","title":"LunchablePandasTransactionsApp","text":"

Bases: LunchableTransactionsApp, LunchablePandasApp

LunchableTransactionsApp with Pandas Super Powers

Source code in lunchable/plugins/base/pandas_app.py
class LunchablePandasTransactionsApp(LunchableTransactionsApp, LunchablePandasApp):\n    \"\"\"\n    LunchableTransactionsApp with Pandas Super Powers\n    \"\"\"\n
"},{"location":"reference/plugins/primelunch/","title":"primelunch","text":"

PrimeLunch Plugin

"},{"location":"reference/plugins/primelunch/primelunch/","title":"primelunch","text":"

PrimeLunch Utils

"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch","title":"PrimeLunch","text":"

Bases: LunchablePandasApp

PrimeLunch: Amazon Notes Updater

Source code in lunchable/plugins/primelunch/primelunch.py
class PrimeLunch(LunchablePandasApp):\n    \"\"\"\n    PrimeLunch: Amazon Notes Updater\n    \"\"\"\n\n    def __init__(\n        self,\n        file_path: Union[str, os.PathLike[str], pathlib.Path],\n        time_window: int = 7,\n        access_token: Optional[str] = None,\n    ) -> None:\n        \"\"\"\n        Initialize and set internal data\n        \"\"\"\n        super().__init__(cache_time=0, access_token=access_token)\n        self.file_path = pathlib.Path(file_path)\n        self.time_window = time_window\n\n    def amazon_to_df(self) -> pd.DataFrame:\n        \"\"\"\n        Read an Amazon Data File to a DataFrame\n\n        This is pretty simple, except duplicate header rows need to be cleaned\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        dt64: np.dtype[datetime64] = np.dtype(\"datetime64[ns]\")\n        expected_columns = {\n            \"order id\": str,\n            \"items\": str,\n            \"to\": str,\n            \"date\": dt64,\n            \"total\": np.float64,\n            \"shipping\": np.float64,\n            \"gift\": np.float64,\n            \"refund\": np.float64,\n            \"payments\": str,\n        }\n        amazon_df = pd.read_csv(\n            self.file_path,\n            usecols=expected_columns.keys(),\n        )\n        header_row_eval = pd.concat(\n            [amazon_df[item] == item for item in expected_columns.keys()], axis=1\n        ).all(axis=1)\n        duplicate_header_rows = np.where(header_row_eval)[0]\n        amazon_df.drop(duplicate_header_rows, axis=0, inplace=True)\n        amazon_df[\"total\"] = (\n            amazon_df[\"total\"].astype(\"string\").str.replace(\",\", \"\").astype(np.float64)\n        )\n        amazon_df = amazon_df.astype(dtype=expected_columns, copy=True, errors=\"raise\")\n        logger.info(\"Amazon Data File loaded: %s\", self.file_path)\n        return amazon_df\n\n    @classmethod\n    def filter_amazon_transactions(cls, df: pd.DataFrame) -> pd.DataFrame:\n        \"\"\"\n        Filter a DataFrame to Amazon Transactions\n\n        Parameters\n        ----------\n        df: pd.DataFrame\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        amazon_transactions = df.copy()\n        amazon_transactions[\"original_name\"] = amazon_transactions[\n            \"original_name\"\n        ].fillna(\"\")\n        amazon_transactions = amazon_transactions[\n            amazon_transactions.payee.str.match(\n                r\"(?i)(Amazon|AMZN|Whole Foods)(\\s?(Prime|Marketplace|MKTP)|\\.\\w+)?\",\n                case=False,\n            )\n            | amazon_transactions.original_name.str.match(\n                r\"(?i)(Amazon|AMZN|Whole Foods)(\\s?(Prime|Marketplace|MKTP)|\\.\\w+)?\",\n                case=False,\n            )\n        ]\n        return amazon_transactions\n\n    @classmethod\n    def deduplicate_matched(cls, df: pd.DataFrame) -> pd.DataFrame:\n        \"\"\"\n        Deduplicate Multiple Connections Made\n\n        Parameters\n        ----------\n        df: pd.DataFrame\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        deduped = df.copy()\n        deduped[\"duplicated\"] = deduped.duplicated(subset=[\"id\"], keep=False)\n        deduped = deduped[deduped[\"duplicated\"] == False]  # noqa:E712\n        return deduped\n\n    @classmethod\n    def _extract_total_from_payments(cls, df: pd.DataFrame) -> pd.DataFrame:\n        \"\"\"\n        Extract the Credit Card Payments from the payments column\n\n        There is quite a bit of data manipulation going on here. We\n        need to extract meaningful credit card transaction info from strings like this:\n\n        Visa ending in 9470: September 11, 2022: $29.57; \\\n        Visa ending in 9470: September 11, 2022: $2.22;\n        \"\"\"\n        extracted = df.copy()\n        extracted[\"new_total\"] = extracted[\"payments\"].str.rstrip(\";\").str.split(\";\")\n        exploded_totals = extracted.explode(\"new_total\", ignore_index=True)\n        exploded_totals = exploded_totals[\n            exploded_totals[\"new_total\"].str.strip() != \"\"\n        ]\n        currency_matcher = r\"(?:[\\\u00a3\\$\\\u20ac]{1}[,\\d]+.?\\d*)\"\n        exploded_totals[\"parsed_total\"] = exploded_totals[\"new_total\"].str.findall(\n            currency_matcher\n        )\n        exploded_totals = exploded_totals.explode(\"parsed_total\", ignore_index=True)\n        exploded_totals[\"parsed_total\"] = exploded_totals[\"parsed_total\"].str.replace(\n            \"[^0-9.]\", \"\", regex=True\n        )\n        exploded_totals[\"parsed_total\"] = exploded_totals[\"parsed_total\"].astype(\n            np.float64\n        )\n        exploded_totals = exploded_totals[~exploded_totals[\"parsed_total\"].isnull()]\n        exploded_totals[\"total\"] = np.where(\n            ~exploded_totals[\"parsed_total\"].isnull(),\n            exploded_totals[\"parsed_total\"],\n            exploded_totals[\"total\"],\n        )\n        return exploded_totals\n\n    @classmethod\n    def _extract_extra_from_orders(cls, df: pd.DataFrame) -> pd.DataFrame:\n        \"\"\"\n        Extract the Credit Card Refunds and Whole Foods Orders\n        \"\"\"\n        refunds = df.copy()\n        refunded_data = refunds[refunds[\"refund\"] > 0].copy()\n        refunded_data[\"total\"] = -refunded_data[\"refund\"]\n        refunded_data[\"items\"] = \"REFUND: \" + refunded_data[\"items\"]\n        complete_amazon_data = pd.concat([refunds, refunded_data], ignore_index=True)\n        complete_amazon_data[\"items\"] = np.where(\n            complete_amazon_data[\"to\"].str.startswith(\"Whole Foods\"),\n            \"Whole Foods Groceries\",\n            complete_amazon_data[\"items\"],\n        )\n        return complete_amazon_data\n\n    @classmethod\n    def merge_transactions(\n        cls, amazon: pd.DataFrame, transactions: pd.DataFrame, time_range: int = 7\n    ) -> pd.DataFrame:\n        \"\"\"\n        Merge Amazon Transactions and LunchMoney Transaction\n\n        Parameters\n        ----------\n        amazon: pd.DataFrame\n        transactions: pd.DataFrame\n        time_range: int\n            Number of days used to connect credit card transactions with\n            Amazon transactions\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        exploded_totals = cls._extract_total_from_payments(df=amazon)\n        complete_amazon_data = cls._extract_extra_from_orders(df=exploded_totals)\n        merged_data = transactions.copy()\n        merged_data = merged_data.merge(\n            complete_amazon_data,\n            how=\"inner\",\n            left_on=[\"amount\"],\n            right_on=[\"total\"],\n            suffixes=(None, \"_amazon\"),\n        )\n        merged_data[\"start_date\"] = merged_data[\"date_amazon\"]\n        merged_data[\"end_date\"] = merged_data[\"date_amazon\"] + datetime.timedelta(\n            days=time_range\n        )\n        merged_data.query(\n            \"start_date <= date <= end_date\",\n            inplace=True,\n        )\n        merged_data[\"notes\"] = merged_data[\"items\"]\n        deduplicated = cls.deduplicate_matched(df=merged_data)\n        logger.info(\"%s Matching Amazon Transactions Identified\", len(deduplicated))\n        return deduplicated[TransactionObject.__fields__.keys()]\n\n    def cache_transactions(\n        self, start_date: datetime.date, end_date: datetime.date\n    ) -> dict[int, TransactionObject]:\n        \"\"\"\n        Cache Transactions to Memory\n\n        Parameters\n        ----------\n        start_date : datetime.date\n        end_date : datetime.date\n\n        Returns\n        -------\n        Dict[int, TransactionObject]\n        \"\"\"\n        end_cache_date = end_date + datetime.timedelta(days=self.time_window)\n        logger.info(\n            \"Fetching LunchMoney transactions between %s and %s\",\n            start_date,\n            end_cache_date,\n        )\n        self.get_latest_cache(include=[CategoriesObject, UserObject])\n        self.refresh_transactions(start_date=start_date, end_date=end_cache_date)\n        logger.info(\n            'Scanning LunchMoney Budget: \"%s\"',\n            html.unescape(self.lunch_data.user.budget_name),\n        )\n        logger.info(\n            \"%s transactions returned from LunchMoney\",\n            len(self.lunch_data.transactions),\n        )\n        return self.lunch_data.transactions\n\n    def print_transaction(\n        self, transaction: TransactionObject, former_transaction: TransactionObject\n    ) -> None:\n        \"\"\"\n        Print a Transaction for interactive input\n        \"\"\"\n        transaction_table = table.Table(show_header=False)\n        notes_table = table.Table(show_header=False)\n        transaction_table.add_row(\"\ud83d\uded2 Transaction ID\", str(former_transaction.id))\n        transaction_table.add_row(\"\ud83c\udfe6 Payee\", former_transaction.payee)\n        transaction_table.add_row(\"\ud83d\udcc5 Date\", str(former_transaction.date))\n        transaction_table.add_row(\n            \"\ud83d\udcb0 Amount\", self.format_currency(amount=former_transaction.amount)\n        )\n        if former_transaction.category_id is not None:\n            transaction_table.add_row(\n                \"\ud83d\udcca Category\",\n                self.lunch_data.categories[former_transaction.category_id].name,\n            )\n        if (\n            former_transaction.original_name is not None\n            and former_transaction.original_name != former_transaction.payee\n        ):\n            transaction_table.add_row(\n                \"\ud83c\udfe6 Original Payee\", former_transaction.original_name\n            )\n        if former_transaction.notes is not None:\n            transaction_table.add_row(\"\ud83d\udcdd Notes\", former_transaction.notes)\n        notes_table.add_row(\n            \"\ud83d\uddd2  Amazon Notes\",\n            transaction.notes.strip(),  # type: ignore[union-attr]\n        )\n        print()\n        print(transaction_table)\n        print(notes_table)\n\n    def update_transaction(\n        self, transaction: TransactionObject, confirm: bool = True\n    ) -> Optional[dict[str, Any]]:\n        \"\"\"\n        Update a Transaction's Notes if they've changed\n\n        Parameters\n        ----------\n        transaction: TransactionObject\n        confirm: bool\n\n        Returns\n        -------\n        Optional[Dict[str, Any]]\n        \"\"\"\n        former_transaction = self.lunch_data.transactions[transaction.id]\n        response = None\n        stripped_notes = transaction.notes.strip()  # type: ignore[union-attr]\n        acceptable_length = min(349, len(stripped_notes))\n        new_notes = stripped_notes[:acceptable_length]\n        if former_transaction.notes != new_notes:\n            confirmation = True\n            if confirm is True:\n                self.print_transaction(\n                    transaction=transaction, former_transaction=former_transaction\n                )\n                confirmation = Confirm.ask(\n                    f\"\\t\u2753 Should we update transaction #{transaction.id}?\"\n                )\n            if confirmation is True:\n                response = self.lunch.update_transaction(\n                    transaction_id=transaction.id,\n                    transaction=TransactionUpdateObject(notes=new_notes),\n                )\n                if confirm is True:\n                    print(f\"\\t\u2705 Transaction #{transaction.id} updated\")\n        return response\n\n    def process_transactions(self, confirm: bool = True) -> None:\n        \"\"\"\n        Run the End-to-End Process\n        \"\"\"\n        logger.info(\n            \"Beginning search to match Amazon and LunchMoney - using %s day window\",\n            self.time_window,\n        )\n        amazon_df = self.amazon_to_df()\n        min_date = amazon_df[\"date\"].min().to_pydatetime().date()\n        max_date = amazon_df[\"date\"].max().to_pydatetime().date()\n        logger.info(\n            \"%s Amazon transactions loaded ranging from %s to %s\",\n            len(amazon_df),\n            min_date,\n            max_date,\n        )\n        self.cache_transactions(start_date=min_date, end_date=max_date)\n        transaction_df = self.models_to_df(\n            models=self.lunch_data.transactions.values(),\n        )\n        amazon_transaction_df = self.filter_amazon_transactions(df=transaction_df)\n        merged_data = self.merge_transactions(\n            transactions=amazon_transaction_df,\n            amazon=amazon_df,\n            time_range=self.time_window,\n        )\n        updated_transactions = self.df_to_models(\n            df=merged_data, model_type=TransactionObject\n        )\n        responses = []\n        for item in updated_transactions:\n            resp = self.update_transaction(transaction=item, confirm=confirm)\n            if resp is not None:\n                responses.append(resp)\n        logger.info(\"%s LunchMoney transactions updated\", len(responses))\n\n    @staticmethod\n    def format_currency(amount: float) -> str:\n        \"\"\"\n        Format currency amounts to be pleasant and human readable\n\n        Parameters\n        ----------\n        amount: float\n            Float Amount to be converted into a string\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if amount < 0:\n            float_string = f\"[bold red]$ ({float(abs(amount)):,.2f})[/bold red]\"\n        else:\n            float_string = f\"[bold green]$ {float(amount):,.2f}[/bold green]\"\n        return float_string\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.__init__","title":"__init__(file_path, time_window=7, access_token=None)","text":"

Initialize and set internal data

Source code in lunchable/plugins/primelunch/primelunch.py
def __init__(\n    self,\n    file_path: Union[str, os.PathLike[str], pathlib.Path],\n    time_window: int = 7,\n    access_token: Optional[str] = None,\n) -> None:\n    \"\"\"\n    Initialize and set internal data\n    \"\"\"\n    super().__init__(cache_time=0, access_token=access_token)\n    self.file_path = pathlib.Path(file_path)\n    self.time_window = time_window\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.amazon_to_df","title":"amazon_to_df()","text":"

Read an Amazon Data File to a DataFrame

This is pretty simple, except duplicate header rows need to be cleaned

Returns:

Type Description DataFrame Source code in lunchable/plugins/primelunch/primelunch.py
def amazon_to_df(self) -> pd.DataFrame:\n    \"\"\"\n    Read an Amazon Data File to a DataFrame\n\n    This is pretty simple, except duplicate header rows need to be cleaned\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    dt64: np.dtype[datetime64] = np.dtype(\"datetime64[ns]\")\n    expected_columns = {\n        \"order id\": str,\n        \"items\": str,\n        \"to\": str,\n        \"date\": dt64,\n        \"total\": np.float64,\n        \"shipping\": np.float64,\n        \"gift\": np.float64,\n        \"refund\": np.float64,\n        \"payments\": str,\n    }\n    amazon_df = pd.read_csv(\n        self.file_path,\n        usecols=expected_columns.keys(),\n    )\n    header_row_eval = pd.concat(\n        [amazon_df[item] == item for item in expected_columns.keys()], axis=1\n    ).all(axis=1)\n    duplicate_header_rows = np.where(header_row_eval)[0]\n    amazon_df.drop(duplicate_header_rows, axis=0, inplace=True)\n    amazon_df[\"total\"] = (\n        amazon_df[\"total\"].astype(\"string\").str.replace(\",\", \"\").astype(np.float64)\n    )\n    amazon_df = amazon_df.astype(dtype=expected_columns, copy=True, errors=\"raise\")\n    logger.info(\"Amazon Data File loaded: %s\", self.file_path)\n    return amazon_df\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.cache_transactions","title":"cache_transactions(start_date, end_date)","text":"

Cache Transactions to Memory

Parameters:

Name Type Description Default start_date date required end_date date required

Returns:

Type Description Dict[int, TransactionObject] Source code in lunchable/plugins/primelunch/primelunch.py
def cache_transactions(\n    self, start_date: datetime.date, end_date: datetime.date\n) -> dict[int, TransactionObject]:\n    \"\"\"\n    Cache Transactions to Memory\n\n    Parameters\n    ----------\n    start_date : datetime.date\n    end_date : datetime.date\n\n    Returns\n    -------\n    Dict[int, TransactionObject]\n    \"\"\"\n    end_cache_date = end_date + datetime.timedelta(days=self.time_window)\n    logger.info(\n        \"Fetching LunchMoney transactions between %s and %s\",\n        start_date,\n        end_cache_date,\n    )\n    self.get_latest_cache(include=[CategoriesObject, UserObject])\n    self.refresh_transactions(start_date=start_date, end_date=end_cache_date)\n    logger.info(\n        'Scanning LunchMoney Budget: \"%s\"',\n        html.unescape(self.lunch_data.user.budget_name),\n    )\n    logger.info(\n        \"%s transactions returned from LunchMoney\",\n        len(self.lunch_data.transactions),\n    )\n    return self.lunch_data.transactions\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.deduplicate_matched","title":"deduplicate_matched(df) classmethod","text":"

Deduplicate Multiple Connections Made

Parameters:

Name Type Description Default df DataFrame required

Returns:

Type Description DataFrame Source code in lunchable/plugins/primelunch/primelunch.py
@classmethod\ndef deduplicate_matched(cls, df: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Deduplicate Multiple Connections Made\n\n    Parameters\n    ----------\n    df: pd.DataFrame\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    deduped = df.copy()\n    deduped[\"duplicated\"] = deduped.duplicated(subset=[\"id\"], keep=False)\n    deduped = deduped[deduped[\"duplicated\"] == False]  # noqa:E712\n    return deduped\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.filter_amazon_transactions","title":"filter_amazon_transactions(df) classmethod","text":"

Filter a DataFrame to Amazon Transactions

Parameters:

Name Type Description Default df DataFrame required

Returns:

Type Description DataFrame Source code in lunchable/plugins/primelunch/primelunch.py
@classmethod\ndef filter_amazon_transactions(cls, df: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Filter a DataFrame to Amazon Transactions\n\n    Parameters\n    ----------\n    df: pd.DataFrame\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    amazon_transactions = df.copy()\n    amazon_transactions[\"original_name\"] = amazon_transactions[\n        \"original_name\"\n    ].fillna(\"\")\n    amazon_transactions = amazon_transactions[\n        amazon_transactions.payee.str.match(\n            r\"(?i)(Amazon|AMZN|Whole Foods)(\\s?(Prime|Marketplace|MKTP)|\\.\\w+)?\",\n            case=False,\n        )\n        | amazon_transactions.original_name.str.match(\n            r\"(?i)(Amazon|AMZN|Whole Foods)(\\s?(Prime|Marketplace|MKTP)|\\.\\w+)?\",\n            case=False,\n        )\n    ]\n    return amazon_transactions\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.format_currency","title":"format_currency(amount) staticmethod","text":"

Format currency amounts to be pleasant and human readable

Parameters:

Name Type Description Default amount float

Float Amount to be converted into a string

required

Returns:

Type Description str Source code in lunchable/plugins/primelunch/primelunch.py
@staticmethod\ndef format_currency(amount: float) -> str:\n    \"\"\"\n    Format currency amounts to be pleasant and human readable\n\n    Parameters\n    ----------\n    amount: float\n        Float Amount to be converted into a string\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if amount < 0:\n        float_string = f\"[bold red]$ ({float(abs(amount)):,.2f})[/bold red]\"\n    else:\n        float_string = f\"[bold green]$ {float(amount):,.2f}[/bold green]\"\n    return float_string\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.merge_transactions","title":"merge_transactions(amazon, transactions, time_range=7) classmethod","text":"

Merge Amazon Transactions and LunchMoney Transaction

Parameters:

Name Type Description Default amazon DataFrame required transactions DataFrame required time_range int

Number of days used to connect credit card transactions with Amazon transactions

7

Returns:

Type Description DataFrame Source code in lunchable/plugins/primelunch/primelunch.py
@classmethod\ndef merge_transactions(\n    cls, amazon: pd.DataFrame, transactions: pd.DataFrame, time_range: int = 7\n) -> pd.DataFrame:\n    \"\"\"\n    Merge Amazon Transactions and LunchMoney Transaction\n\n    Parameters\n    ----------\n    amazon: pd.DataFrame\n    transactions: pd.DataFrame\n    time_range: int\n        Number of days used to connect credit card transactions with\n        Amazon transactions\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    exploded_totals = cls._extract_total_from_payments(df=amazon)\n    complete_amazon_data = cls._extract_extra_from_orders(df=exploded_totals)\n    merged_data = transactions.copy()\n    merged_data = merged_data.merge(\n        complete_amazon_data,\n        how=\"inner\",\n        left_on=[\"amount\"],\n        right_on=[\"total\"],\n        suffixes=(None, \"_amazon\"),\n    )\n    merged_data[\"start_date\"] = merged_data[\"date_amazon\"]\n    merged_data[\"end_date\"] = merged_data[\"date_amazon\"] + datetime.timedelta(\n        days=time_range\n    )\n    merged_data.query(\n        \"start_date <= date <= end_date\",\n        inplace=True,\n    )\n    merged_data[\"notes\"] = merged_data[\"items\"]\n    deduplicated = cls.deduplicate_matched(df=merged_data)\n    logger.info(\"%s Matching Amazon Transactions Identified\", len(deduplicated))\n    return deduplicated[TransactionObject.__fields__.keys()]\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.print_transaction","title":"print_transaction(transaction, former_transaction)","text":"

Print a Transaction for interactive input

Source code in lunchable/plugins/primelunch/primelunch.py
def print_transaction(\n    self, transaction: TransactionObject, former_transaction: TransactionObject\n) -> None:\n    \"\"\"\n    Print a Transaction for interactive input\n    \"\"\"\n    transaction_table = table.Table(show_header=False)\n    notes_table = table.Table(show_header=False)\n    transaction_table.add_row(\"\ud83d\uded2 Transaction ID\", str(former_transaction.id))\n    transaction_table.add_row(\"\ud83c\udfe6 Payee\", former_transaction.payee)\n    transaction_table.add_row(\"\ud83d\udcc5 Date\", str(former_transaction.date))\n    transaction_table.add_row(\n        \"\ud83d\udcb0 Amount\", self.format_currency(amount=former_transaction.amount)\n    )\n    if former_transaction.category_id is not None:\n        transaction_table.add_row(\n            \"\ud83d\udcca Category\",\n            self.lunch_data.categories[former_transaction.category_id].name,\n        )\n    if (\n        former_transaction.original_name is not None\n        and former_transaction.original_name != former_transaction.payee\n    ):\n        transaction_table.add_row(\n            \"\ud83c\udfe6 Original Payee\", former_transaction.original_name\n        )\n    if former_transaction.notes is not None:\n        transaction_table.add_row(\"\ud83d\udcdd Notes\", former_transaction.notes)\n    notes_table.add_row(\n        \"\ud83d\uddd2  Amazon Notes\",\n        transaction.notes.strip(),  # type: ignore[union-attr]\n    )\n    print()\n    print(transaction_table)\n    print(notes_table)\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.process_transactions","title":"process_transactions(confirm=True)","text":"

Run the End-to-End Process

Source code in lunchable/plugins/primelunch/primelunch.py
def process_transactions(self, confirm: bool = True) -> None:\n    \"\"\"\n    Run the End-to-End Process\n    \"\"\"\n    logger.info(\n        \"Beginning search to match Amazon and LunchMoney - using %s day window\",\n        self.time_window,\n    )\n    amazon_df = self.amazon_to_df()\n    min_date = amazon_df[\"date\"].min().to_pydatetime().date()\n    max_date = amazon_df[\"date\"].max().to_pydatetime().date()\n    logger.info(\n        \"%s Amazon transactions loaded ranging from %s to %s\",\n        len(amazon_df),\n        min_date,\n        max_date,\n    )\n    self.cache_transactions(start_date=min_date, end_date=max_date)\n    transaction_df = self.models_to_df(\n        models=self.lunch_data.transactions.values(),\n    )\n    amazon_transaction_df = self.filter_amazon_transactions(df=transaction_df)\n    merged_data = self.merge_transactions(\n        transactions=amazon_transaction_df,\n        amazon=amazon_df,\n        time_range=self.time_window,\n    )\n    updated_transactions = self.df_to_models(\n        df=merged_data, model_type=TransactionObject\n    )\n    responses = []\n    for item in updated_transactions:\n        resp = self.update_transaction(transaction=item, confirm=confirm)\n        if resp is not None:\n            responses.append(resp)\n    logger.info(\"%s LunchMoney transactions updated\", len(responses))\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.update_transaction","title":"update_transaction(transaction, confirm=True)","text":"

Update a Transaction's Notes if they've changed

Parameters:

Name Type Description Default transaction TransactionObject required confirm bool True

Returns:

Type Description Optional[Dict[str, Any]] Source code in lunchable/plugins/primelunch/primelunch.py
def update_transaction(\n    self, transaction: TransactionObject, confirm: bool = True\n) -> Optional[dict[str, Any]]:\n    \"\"\"\n    Update a Transaction's Notes if they've changed\n\n    Parameters\n    ----------\n    transaction: TransactionObject\n    confirm: bool\n\n    Returns\n    -------\n    Optional[Dict[str, Any]]\n    \"\"\"\n    former_transaction = self.lunch_data.transactions[transaction.id]\n    response = None\n    stripped_notes = transaction.notes.strip()  # type: ignore[union-attr]\n    acceptable_length = min(349, len(stripped_notes))\n    new_notes = stripped_notes[:acceptable_length]\n    if former_transaction.notes != new_notes:\n        confirmation = True\n        if confirm is True:\n            self.print_transaction(\n                transaction=transaction, former_transaction=former_transaction\n            )\n            confirmation = Confirm.ask(\n                f\"\\t\u2753 Should we update transaction #{transaction.id}?\"\n            )\n        if confirmation is True:\n            response = self.lunch.update_transaction(\n                transaction_id=transaction.id,\n                transaction=TransactionUpdateObject(notes=new_notes),\n            )\n            if confirm is True:\n                print(f\"\\t\u2705 Transaction #{transaction.id} updated\")\n    return response\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.run_primelunch","title":"run_primelunch(csv_file, window, update_all, access_token)","text":"

Run the PrimeLunch Update Process

Source code in lunchable/plugins/primelunch/primelunch.py
@click.command(\"run\")\n@click.option(\n    \"-f\",\n    \"--file\",\n    \"csv_file\",\n    type=click.Path(exists=True, resolve_path=True),\n    help=\"File Path of the Amazon Export\",\n    required=True,\n)\n@click.option(\n    \"-w\",\n    \"--window\",\n    \"window\",\n    type=click.INT,\n    help=\"Allowable time window between Amazon transaction date and \"\n    \"credit card transaction date\",\n    default=7,\n)\n@click.option(\n    \"-a\",\n    \"--all\",\n    \"update_all\",\n    is_flag=True,\n    type=click.BOOL,\n    help=\"Whether to skip the confirmation step and simply update all matched \"\n    \"transactions\",\n    default=False,\n)\n@click.option(\n    \"-t\",\n    \"--token\",\n    \"access_token\",\n    type=click.STRING,\n    help=\"LunchMoney Access Token - defaults to the LUNCHMONEY_ACCESS_TOKEN environment variable\",\n    envvar=\"LUNCHMONEY_ACCESS_TOKEN\",\n)\ndef run_primelunch(\n    csv_file: str, window: int, update_all: bool, access_token: str\n) -> None:\n    \"\"\"\n    Run the PrimeLunch Update Process\n    \"\"\"\n    primelunch = PrimeLunch(\n        file_path=csv_file, time_window=window, access_token=access_token\n    )\n    primelunch.process_transactions(confirm=not update_all)\n
"},{"location":"reference/plugins/pushlunch/","title":"pushlunch","text":"

PushLunch lunchable Plugin

"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch","title":"PushLunch","text":"

Bases: LunchableApp

Lunch Money Pushover Notifications via Lunchable

Source code in lunchable/plugins/pushlunch/pushover.py
class PushLunch(LunchableApp):\n    \"\"\"\n    Lunch Money Pushover Notifications via Lunchable\n    \"\"\"\n\n    pushover_endpoint = \"https://api.pushover.net/1/messages.json\"\n\n    def __init__(\n        self,\n        user_key: Optional[str] = None,\n        app_token: Optional[str] = None,\n        lunchmoney_access_token: Optional[str] = None,\n    ):\n        \"\"\"\n        Initialize\n\n        Parameters\n        ----------\n        user_key : Optional[str]\n            Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n            variable if none defined\n        app_token: Optional[str]\n            Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n            variable. If no token available, the official lunchable app token will be provided\n        lunchmoney_access_token: Optional[str]\n            LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable.\n        \"\"\"\n        super().__init__(access_token=lunchmoney_access_token)\n        self.pushover_session = httpx.Client()\n        self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n        _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n        if app_token is None:\n            app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n        token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n        user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n        if user_key in [None, \"\"]:\n            raise PushLunchError(\n                \"You must provide a Pushover User Key or define it with \"\n                \"a `PUSHOVER_USER_KEY` environment variable\"\n            )\n        self._params = {\"user\": user_key, \"token\": token}\n        self.get_latest_cache(\n            include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n        )\n        self.notified_transactions: List[int] = []\n\n    def send_notification(\n        self,\n        message: str,\n        attachment: Optional[object] = None,\n        device: Optional[str] = None,\n        title: Optional[str] = None,\n        url: Optional[str] = None,\n        url_title: Optional[str] = None,\n        priority: Optional[int] = None,\n        sound: Optional[str] = None,\n        timestamp: Optional[str] = None,\n        html: bool = False,\n    ) -> httpx.Response:\n        \"\"\"\n        Send a Pushover Notification\n\n        Parameters\n        ----------\n        message: Optional[str]\n            your message\n        attachment: Optional[object]\n            an image attachment to send with the message; see attachments for more information\n            on how to upload files\n        device: Optional[str]\n            your user's device name to send the message directly to that device, rather than\n            all of the user's devices (multiple devices may be separated by a comma)\n        title: Optional[str]\n            your message's title, otherwise your app's name is used\n        url: Optional[str]\n            a supplementary URL to show with your message\n        url_title: Optional[str]\n            a title for your supplementary URL, otherwise just the URL is shown\n        priority: Optional[int]\n            send as -2 to generate no notification/alert, -1 to always send as a quiet\n            notification, 1 to display as high-priority and bypass the user's quiet hours,\n            or 2 to also require confirmation from the user\n        sound: Optional[str]\n            the name of one of the sounds supported by device clients to override the\n            user's default sound choice\n        timestamp: Optional[str]\n            a Unix timestamp of your message's date and time to display to the user, rather\n            than the time your message is received by our API\n        html: Union[None, 1]\n            Pass 1 if message contains HTML contents\n\n        Returns\n        -------\n        httpx.Response\n        \"\"\"\n        html_param = 1 if html not in [None, False] else None\n        params_dict = {\n            \"message\": message,\n            \"attachment\": attachment,\n            \"device\": device,\n            \"title\": title,\n            \"url\": url,\n            \"url_title\": url_title,\n            \"priority\": priority,\n            \"sound\": sound,\n            \"timestamp\": timestamp,\n            \"html\": html_param,\n        }\n        params: Dict[str, Any] = {\n            key: value for key, value in params_dict.items() if value is not None\n        }\n        params.update(self._params)\n        response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n        response.raise_for_status()\n        return response\n\n    def post_transaction(\n        self, transaction: TransactionObject\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"\n        Post a Lunch Money Transaction as a Pushover Notification\n\n        Assuming the instance of the\n        class hasn't already posted this particular notification\n\n        Parameters\n        ----------\n        transaction: TransactionObject\n\n        Returns\n        -------\n        Dict[str, Any]\n        \"\"\"\n        if transaction.id in self.notified_transactions:\n            return None\n        if transaction.category_id is None:\n            category = \"N/A\"\n        else:\n            category = self.lunch_data.categories[transaction.category_id].name\n        account_id = transaction.plaid_account_id or transaction.asset_id\n        assert account_id is not None\n        account = self.lunch_data.asset_map[account_id]\n        if isinstance(account, AssetsObject):\n            account_name = account.display_name or account.name\n        else:\n            account_name = account.name\n        transaction_formatted = dedent(\n            f\"\"\"\n        <b>Payee:</b> <i>{transaction.payee}</i>\n        <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n        <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n        <b>Category:</b> <i>{category}</i>\n        <b>Account:</b> <i>{account_name}</i>\n        \"\"\"\n        ).strip()\n        if transaction.currency is not None:\n            transaction_formatted += (\n                f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n            )\n        if transaction.status is not None:\n            transaction_formatted += (\n                f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n            )\n        if transaction.notes is not None:\n            note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n            transaction_formatted += f\"\\n{note}\"\n        if transaction.status == \"uncleared\":\n            url = (\n                '<a href=\"https://my.lunchmoney.app/transactions/'\n                f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n                \"<b>Uncleared Transactions from this Period</b></a>\"\n            )\n            transaction_formatted += f\"\\n\\n{url}\"\n\n        response = self.send_notification(\n            message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n        )\n        self.notified_transactions.append(transaction.id)\n        return loads(response.content)\n\n    @classmethod\n    def _format_float(cls, amount: float) -> str:\n        \"\"\"\n        Format Floats to be pleasant and human readable\n\n        Parameters\n        ----------\n        amount: float\n            Float Amount to be converted into a string\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if amount < 0:\n            float_string = f\"$ ({float(amount):,.2f})\".replace(\"-\", \"\")\n        else:\n            float_string = f\"$ {float(amount):,.2f}\"\n        return float_string\n\n    def notify_uncleared_transactions(\n        self, continuous: bool = False, interval: Optional[int] = None\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n        Parameters\n        ----------\n        continuous : bool\n            Whether to continuously check for more uncleared transactions,\n            waiting a fixed amount in between checks.\n        interval: Optional[int]\n            Sleep Interval in Between Tries - only applies if `continuous` is set.\n            Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if interval is None:\n            interval = 60\n        if continuous is True and interval < 5:\n            logger.warning(\n                \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n            )\n            interval = 5\n        if continuous is True:\n            logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n        uncleared_transactions = []\n        continuous_search = True\n\n        while continuous_search is True:\n            found_transactions = len(self.notified_transactions)\n            uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n            for transaction in uncleared_transactions:\n                self.post_transaction(transaction=transaction)\n            if continuous is True:\n                notified = len(self.notified_transactions)\n                new_transactions = notified - found_transactions\n                logger.info(\n                    \"%s new transactions pushed. %s total.\", new_transactions, notified\n                )\n                sleep(interval * 60)\n            else:\n                continuous_search = False\n\n        return uncleared_transactions\n
"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.__init__","title":"__init__(user_key=None, app_token=None, lunchmoney_access_token=None)","text":"

Initialize

Parameters:

Name Type Description Default user_key Optional[str]

Pushover User Key. Will attempt to inherit from PUSHOVER_USER_KEY environment variable if none defined

None app_token Optional[str]

Pushover app token, will attempt to inherit from PUSHOVER_APP_TOKEN environment variable. If no token available, the official lunchable app token will be provided

None lunchmoney_access_token Optional[str]

LunchMoney Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable.

None Source code in lunchable/plugins/pushlunch/pushover.py
def __init__(\n    self,\n    user_key: Optional[str] = None,\n    app_token: Optional[str] = None,\n    lunchmoney_access_token: Optional[str] = None,\n):\n    \"\"\"\n    Initialize\n\n    Parameters\n    ----------\n    user_key : Optional[str]\n        Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n        variable if none defined\n    app_token: Optional[str]\n        Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n        variable. If no token available, the official lunchable app token will be provided\n    lunchmoney_access_token: Optional[str]\n        LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable.\n    \"\"\"\n    super().__init__(access_token=lunchmoney_access_token)\n    self.pushover_session = httpx.Client()\n    self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n    _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n    if app_token is None:\n        app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n    token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n    user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n    if user_key in [None, \"\"]:\n        raise PushLunchError(\n            \"You must provide a Pushover User Key or define it with \"\n            \"a `PUSHOVER_USER_KEY` environment variable\"\n        )\n    self._params = {\"user\": user_key, \"token\": token}\n    self.get_latest_cache(\n        include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n    )\n    self.notified_transactions: List[int] = []\n
"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.notify_uncleared_transactions","title":"notify_uncleared_transactions(continuous=False, interval=None)","text":"

Get the Current Period's Uncleared Transactions and Send a Notification for each

Parameters:

Name Type Description Default continuous bool

Whether to continuously check for more uncleared transactions, waiting a fixed amount in between checks.

False interval Optional[int]

Sleep Interval in Between Tries - only applies if continuous is set. Defaults to 60 (minutes). Cannot be less than 5 (minutes)

None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/pushlunch/pushover.py
def notify_uncleared_transactions(\n    self, continuous: bool = False, interval: Optional[int] = None\n) -> List[TransactionObject]:\n    \"\"\"\n    Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n    Parameters\n    ----------\n    continuous : bool\n        Whether to continuously check for more uncleared transactions,\n        waiting a fixed amount in between checks.\n    interval: Optional[int]\n        Sleep Interval in Between Tries - only applies if `continuous` is set.\n        Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if interval is None:\n        interval = 60\n    if continuous is True and interval < 5:\n        logger.warning(\n            \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n        )\n        interval = 5\n    if continuous is True:\n        logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n    uncleared_transactions = []\n    continuous_search = True\n\n    while continuous_search is True:\n        found_transactions = len(self.notified_transactions)\n        uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n        for transaction in uncleared_transactions:\n            self.post_transaction(transaction=transaction)\n        if continuous is True:\n            notified = len(self.notified_transactions)\n            new_transactions = notified - found_transactions\n            logger.info(\n                \"%s new transactions pushed. %s total.\", new_transactions, notified\n            )\n            sleep(interval * 60)\n        else:\n            continuous_search = False\n\n    return uncleared_transactions\n
"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.post_transaction","title":"post_transaction(transaction)","text":"

Post a Lunch Money Transaction as a Pushover Notification

Assuming the instance of the class hasn't already posted this particular notification

Parameters:

Name Type Description Default transaction TransactionObject required

Returns:

Type Description Dict[str, Any] Source code in lunchable/plugins/pushlunch/pushover.py
def post_transaction(\n    self, transaction: TransactionObject\n) -> Optional[Dict[str, Any]]:\n    \"\"\"\n    Post a Lunch Money Transaction as a Pushover Notification\n\n    Assuming the instance of the\n    class hasn't already posted this particular notification\n\n    Parameters\n    ----------\n    transaction: TransactionObject\n\n    Returns\n    -------\n    Dict[str, Any]\n    \"\"\"\n    if transaction.id in self.notified_transactions:\n        return None\n    if transaction.category_id is None:\n        category = \"N/A\"\n    else:\n        category = self.lunch_data.categories[transaction.category_id].name\n    account_id = transaction.plaid_account_id or transaction.asset_id\n    assert account_id is not None\n    account = self.lunch_data.asset_map[account_id]\n    if isinstance(account, AssetsObject):\n        account_name = account.display_name or account.name\n    else:\n        account_name = account.name\n    transaction_formatted = dedent(\n        f\"\"\"\n    <b>Payee:</b> <i>{transaction.payee}</i>\n    <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n    <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n    <b>Category:</b> <i>{category}</i>\n    <b>Account:</b> <i>{account_name}</i>\n    \"\"\"\n    ).strip()\n    if transaction.currency is not None:\n        transaction_formatted += (\n            f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n        )\n    if transaction.status is not None:\n        transaction_formatted += (\n            f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n        )\n    if transaction.notes is not None:\n        note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n        transaction_formatted += f\"\\n{note}\"\n    if transaction.status == \"uncleared\":\n        url = (\n            '<a href=\"https://my.lunchmoney.app/transactions/'\n            f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n            \"<b>Uncleared Transactions from this Period</b></a>\"\n        )\n        transaction_formatted += f\"\\n\\n{url}\"\n\n    response = self.send_notification(\n        message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n    )\n    self.notified_transactions.append(transaction.id)\n    return loads(response.content)\n
"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.send_notification","title":"send_notification(message, attachment=None, device=None, title=None, url=None, url_title=None, priority=None, sound=None, timestamp=None, html=False)","text":"

Send a Pushover Notification

Parameters:

Name Type Description Default message str

your message

required attachment Optional[object]

an image attachment to send with the message; see attachments for more information on how to upload files

None device Optional[str]

your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma)

None title Optional[str]

your message's title, otherwise your app's name is used

None url Optional[str]

a supplementary URL to show with your message

None url_title Optional[str]

a title for your supplementary URL, otherwise just the URL is shown

None priority Optional[int]

send as -2 to generate no notification/alert, -1 to always send as a quiet notification, 1 to display as high-priority and bypass the user's quiet hours, or 2 to also require confirmation from the user

None sound Optional[str]

the name of one of the sounds supported by device clients to override the user's default sound choice

None timestamp Optional[str]

a Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by our API

None html bool

Pass 1 if message contains HTML contents

False

Returns:

Type Description Response Source code in lunchable/plugins/pushlunch/pushover.py
def send_notification(\n    self,\n    message: str,\n    attachment: Optional[object] = None,\n    device: Optional[str] = None,\n    title: Optional[str] = None,\n    url: Optional[str] = None,\n    url_title: Optional[str] = None,\n    priority: Optional[int] = None,\n    sound: Optional[str] = None,\n    timestamp: Optional[str] = None,\n    html: bool = False,\n) -> httpx.Response:\n    \"\"\"\n    Send a Pushover Notification\n\n    Parameters\n    ----------\n    message: Optional[str]\n        your message\n    attachment: Optional[object]\n        an image attachment to send with the message; see attachments for more information\n        on how to upload files\n    device: Optional[str]\n        your user's device name to send the message directly to that device, rather than\n        all of the user's devices (multiple devices may be separated by a comma)\n    title: Optional[str]\n        your message's title, otherwise your app's name is used\n    url: Optional[str]\n        a supplementary URL to show with your message\n    url_title: Optional[str]\n        a title for your supplementary URL, otherwise just the URL is shown\n    priority: Optional[int]\n        send as -2 to generate no notification/alert, -1 to always send as a quiet\n        notification, 1 to display as high-priority and bypass the user's quiet hours,\n        or 2 to also require confirmation from the user\n    sound: Optional[str]\n        the name of one of the sounds supported by device clients to override the\n        user's default sound choice\n    timestamp: Optional[str]\n        a Unix timestamp of your message's date and time to display to the user, rather\n        than the time your message is received by our API\n    html: Union[None, 1]\n        Pass 1 if message contains HTML contents\n\n    Returns\n    -------\n    httpx.Response\n    \"\"\"\n    html_param = 1 if html not in [None, False] else None\n    params_dict = {\n        \"message\": message,\n        \"attachment\": attachment,\n        \"device\": device,\n        \"title\": title,\n        \"url\": url,\n        \"url_title\": url_title,\n        \"priority\": priority,\n        \"sound\": sound,\n        \"timestamp\": timestamp,\n        \"html\": html_param,\n    }\n    params: Dict[str, Any] = {\n        key: value for key, value in params_dict.items() if value is not None\n    }\n    params.update(self._params)\n    response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n    response.raise_for_status()\n    return response\n
"},{"location":"reference/plugins/pushlunch/pushover/","title":"pushover","text":"

Pushover Notifications via lunchable

"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch","title":"PushLunch","text":"

Bases: LunchableApp

Lunch Money Pushover Notifications via Lunchable

Source code in lunchable/plugins/pushlunch/pushover.py
class PushLunch(LunchableApp):\n    \"\"\"\n    Lunch Money Pushover Notifications via Lunchable\n    \"\"\"\n\n    pushover_endpoint = \"https://api.pushover.net/1/messages.json\"\n\n    def __init__(\n        self,\n        user_key: Optional[str] = None,\n        app_token: Optional[str] = None,\n        lunchmoney_access_token: Optional[str] = None,\n    ):\n        \"\"\"\n        Initialize\n\n        Parameters\n        ----------\n        user_key : Optional[str]\n            Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n            variable if none defined\n        app_token: Optional[str]\n            Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n            variable. If no token available, the official lunchable app token will be provided\n        lunchmoney_access_token: Optional[str]\n            LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable.\n        \"\"\"\n        super().__init__(access_token=lunchmoney_access_token)\n        self.pushover_session = httpx.Client()\n        self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n        _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n        if app_token is None:\n            app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n        token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n        user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n        if user_key in [None, \"\"]:\n            raise PushLunchError(\n                \"You must provide a Pushover User Key or define it with \"\n                \"a `PUSHOVER_USER_KEY` environment variable\"\n            )\n        self._params = {\"user\": user_key, \"token\": token}\n        self.get_latest_cache(\n            include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n        )\n        self.notified_transactions: List[int] = []\n\n    def send_notification(\n        self,\n        message: str,\n        attachment: Optional[object] = None,\n        device: Optional[str] = None,\n        title: Optional[str] = None,\n        url: Optional[str] = None,\n        url_title: Optional[str] = None,\n        priority: Optional[int] = None,\n        sound: Optional[str] = None,\n        timestamp: Optional[str] = None,\n        html: bool = False,\n    ) -> httpx.Response:\n        \"\"\"\n        Send a Pushover Notification\n\n        Parameters\n        ----------\n        message: Optional[str]\n            your message\n        attachment: Optional[object]\n            an image attachment to send with the message; see attachments for more information\n            on how to upload files\n        device: Optional[str]\n            your user's device name to send the message directly to that device, rather than\n            all of the user's devices (multiple devices may be separated by a comma)\n        title: Optional[str]\n            your message's title, otherwise your app's name is used\n        url: Optional[str]\n            a supplementary URL to show with your message\n        url_title: Optional[str]\n            a title for your supplementary URL, otherwise just the URL is shown\n        priority: Optional[int]\n            send as -2 to generate no notification/alert, -1 to always send as a quiet\n            notification, 1 to display as high-priority and bypass the user's quiet hours,\n            or 2 to also require confirmation from the user\n        sound: Optional[str]\n            the name of one of the sounds supported by device clients to override the\n            user's default sound choice\n        timestamp: Optional[str]\n            a Unix timestamp of your message's date and time to display to the user, rather\n            than the time your message is received by our API\n        html: Union[None, 1]\n            Pass 1 if message contains HTML contents\n\n        Returns\n        -------\n        httpx.Response\n        \"\"\"\n        html_param = 1 if html not in [None, False] else None\n        params_dict = {\n            \"message\": message,\n            \"attachment\": attachment,\n            \"device\": device,\n            \"title\": title,\n            \"url\": url,\n            \"url_title\": url_title,\n            \"priority\": priority,\n            \"sound\": sound,\n            \"timestamp\": timestamp,\n            \"html\": html_param,\n        }\n        params: Dict[str, Any] = {\n            key: value for key, value in params_dict.items() if value is not None\n        }\n        params.update(self._params)\n        response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n        response.raise_for_status()\n        return response\n\n    def post_transaction(\n        self, transaction: TransactionObject\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"\n        Post a Lunch Money Transaction as a Pushover Notification\n\n        Assuming the instance of the\n        class hasn't already posted this particular notification\n\n        Parameters\n        ----------\n        transaction: TransactionObject\n\n        Returns\n        -------\n        Dict[str, Any]\n        \"\"\"\n        if transaction.id in self.notified_transactions:\n            return None\n        if transaction.category_id is None:\n            category = \"N/A\"\n        else:\n            category = self.lunch_data.categories[transaction.category_id].name\n        account_id = transaction.plaid_account_id or transaction.asset_id\n        assert account_id is not None\n        account = self.lunch_data.asset_map[account_id]\n        if isinstance(account, AssetsObject):\n            account_name = account.display_name or account.name\n        else:\n            account_name = account.name\n        transaction_formatted = dedent(\n            f\"\"\"\n        <b>Payee:</b> <i>{transaction.payee}</i>\n        <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n        <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n        <b>Category:</b> <i>{category}</i>\n        <b>Account:</b> <i>{account_name}</i>\n        \"\"\"\n        ).strip()\n        if transaction.currency is not None:\n            transaction_formatted += (\n                f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n            )\n        if transaction.status is not None:\n            transaction_formatted += (\n                f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n            )\n        if transaction.notes is not None:\n            note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n            transaction_formatted += f\"\\n{note}\"\n        if transaction.status == \"uncleared\":\n            url = (\n                '<a href=\"https://my.lunchmoney.app/transactions/'\n                f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n                \"<b>Uncleared Transactions from this Period</b></a>\"\n            )\n            transaction_formatted += f\"\\n\\n{url}\"\n\n        response = self.send_notification(\n            message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n        )\n        self.notified_transactions.append(transaction.id)\n        return loads(response.content)\n\n    @classmethod\n    def _format_float(cls, amount: float) -> str:\n        \"\"\"\n        Format Floats to be pleasant and human readable\n\n        Parameters\n        ----------\n        amount: float\n            Float Amount to be converted into a string\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if amount < 0:\n            float_string = f\"$ ({float(amount):,.2f})\".replace(\"-\", \"\")\n        else:\n            float_string = f\"$ {float(amount):,.2f}\"\n        return float_string\n\n    def notify_uncleared_transactions(\n        self, continuous: bool = False, interval: Optional[int] = None\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n        Parameters\n        ----------\n        continuous : bool\n            Whether to continuously check for more uncleared transactions,\n            waiting a fixed amount in between checks.\n        interval: Optional[int]\n            Sleep Interval in Between Tries - only applies if `continuous` is set.\n            Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if interval is None:\n            interval = 60\n        if continuous is True and interval < 5:\n            logger.warning(\n                \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n            )\n            interval = 5\n        if continuous is True:\n            logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n        uncleared_transactions = []\n        continuous_search = True\n\n        while continuous_search is True:\n            found_transactions = len(self.notified_transactions)\n            uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n            for transaction in uncleared_transactions:\n                self.post_transaction(transaction=transaction)\n            if continuous is True:\n                notified = len(self.notified_transactions)\n                new_transactions = notified - found_transactions\n                logger.info(\n                    \"%s new transactions pushed. %s total.\", new_transactions, notified\n                )\n                sleep(interval * 60)\n            else:\n                continuous_search = False\n\n        return uncleared_transactions\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch.__init__","title":"__init__(user_key=None, app_token=None, lunchmoney_access_token=None)","text":"

Initialize

Parameters:

Name Type Description Default user_key Optional[str]

Pushover User Key. Will attempt to inherit from PUSHOVER_USER_KEY environment variable if none defined

None app_token Optional[str]

Pushover app token, will attempt to inherit from PUSHOVER_APP_TOKEN environment variable. If no token available, the official lunchable app token will be provided

None lunchmoney_access_token Optional[str]

LunchMoney Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable.

None Source code in lunchable/plugins/pushlunch/pushover.py
def __init__(\n    self,\n    user_key: Optional[str] = None,\n    app_token: Optional[str] = None,\n    lunchmoney_access_token: Optional[str] = None,\n):\n    \"\"\"\n    Initialize\n\n    Parameters\n    ----------\n    user_key : Optional[str]\n        Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n        variable if none defined\n    app_token: Optional[str]\n        Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n        variable. If no token available, the official lunchable app token will be provided\n    lunchmoney_access_token: Optional[str]\n        LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable.\n    \"\"\"\n    super().__init__(access_token=lunchmoney_access_token)\n    self.pushover_session = httpx.Client()\n    self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n    _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n    if app_token is None:\n        app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n    token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n    user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n    if user_key in [None, \"\"]:\n        raise PushLunchError(\n            \"You must provide a Pushover User Key or define it with \"\n            \"a `PUSHOVER_USER_KEY` environment variable\"\n        )\n    self._params = {\"user\": user_key, \"token\": token}\n    self.get_latest_cache(\n        include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n    )\n    self.notified_transactions: List[int] = []\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch.notify_uncleared_transactions","title":"notify_uncleared_transactions(continuous=False, interval=None)","text":"

Get the Current Period's Uncleared Transactions and Send a Notification for each

Parameters:

Name Type Description Default continuous bool

Whether to continuously check for more uncleared transactions, waiting a fixed amount in between checks.

False interval Optional[int]

Sleep Interval in Between Tries - only applies if continuous is set. Defaults to 60 (minutes). Cannot be less than 5 (minutes)

None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/pushlunch/pushover.py
def notify_uncleared_transactions(\n    self, continuous: bool = False, interval: Optional[int] = None\n) -> List[TransactionObject]:\n    \"\"\"\n    Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n    Parameters\n    ----------\n    continuous : bool\n        Whether to continuously check for more uncleared transactions,\n        waiting a fixed amount in between checks.\n    interval: Optional[int]\n        Sleep Interval in Between Tries - only applies if `continuous` is set.\n        Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if interval is None:\n        interval = 60\n    if continuous is True and interval < 5:\n        logger.warning(\n            \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n        )\n        interval = 5\n    if continuous is True:\n        logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n    uncleared_transactions = []\n    continuous_search = True\n\n    while continuous_search is True:\n        found_transactions = len(self.notified_transactions)\n        uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n        for transaction in uncleared_transactions:\n            self.post_transaction(transaction=transaction)\n        if continuous is True:\n            notified = len(self.notified_transactions)\n            new_transactions = notified - found_transactions\n            logger.info(\n                \"%s new transactions pushed. %s total.\", new_transactions, notified\n            )\n            sleep(interval * 60)\n        else:\n            continuous_search = False\n\n    return uncleared_transactions\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch.post_transaction","title":"post_transaction(transaction)","text":"

Post a Lunch Money Transaction as a Pushover Notification

Assuming the instance of the class hasn't already posted this particular notification

Parameters:

Name Type Description Default transaction TransactionObject required

Returns:

Type Description Dict[str, Any] Source code in lunchable/plugins/pushlunch/pushover.py
def post_transaction(\n    self, transaction: TransactionObject\n) -> Optional[Dict[str, Any]]:\n    \"\"\"\n    Post a Lunch Money Transaction as a Pushover Notification\n\n    Assuming the instance of the\n    class hasn't already posted this particular notification\n\n    Parameters\n    ----------\n    transaction: TransactionObject\n\n    Returns\n    -------\n    Dict[str, Any]\n    \"\"\"\n    if transaction.id in self.notified_transactions:\n        return None\n    if transaction.category_id is None:\n        category = \"N/A\"\n    else:\n        category = self.lunch_data.categories[transaction.category_id].name\n    account_id = transaction.plaid_account_id or transaction.asset_id\n    assert account_id is not None\n    account = self.lunch_data.asset_map[account_id]\n    if isinstance(account, AssetsObject):\n        account_name = account.display_name or account.name\n    else:\n        account_name = account.name\n    transaction_formatted = dedent(\n        f\"\"\"\n    <b>Payee:</b> <i>{transaction.payee}</i>\n    <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n    <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n    <b>Category:</b> <i>{category}</i>\n    <b>Account:</b> <i>{account_name}</i>\n    \"\"\"\n    ).strip()\n    if transaction.currency is not None:\n        transaction_formatted += (\n            f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n        )\n    if transaction.status is not None:\n        transaction_formatted += (\n            f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n        )\n    if transaction.notes is not None:\n        note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n        transaction_formatted += f\"\\n{note}\"\n    if transaction.status == \"uncleared\":\n        url = (\n            '<a href=\"https://my.lunchmoney.app/transactions/'\n            f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n            \"<b>Uncleared Transactions from this Period</b></a>\"\n        )\n        transaction_formatted += f\"\\n\\n{url}\"\n\n    response = self.send_notification(\n        message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n    )\n    self.notified_transactions.append(transaction.id)\n    return loads(response.content)\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch.send_notification","title":"send_notification(message, attachment=None, device=None, title=None, url=None, url_title=None, priority=None, sound=None, timestamp=None, html=False)","text":"

Send a Pushover Notification

Parameters:

Name Type Description Default message str

your message

required attachment Optional[object]

an image attachment to send with the message; see attachments for more information on how to upload files

None device Optional[str]

your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma)

None title Optional[str]

your message's title, otherwise your app's name is used

None url Optional[str]

a supplementary URL to show with your message

None url_title Optional[str]

a title for your supplementary URL, otherwise just the URL is shown

None priority Optional[int]

send as -2 to generate no notification/alert, -1 to always send as a quiet notification, 1 to display as high-priority and bypass the user's quiet hours, or 2 to also require confirmation from the user

None sound Optional[str]

the name of one of the sounds supported by device clients to override the user's default sound choice

None timestamp Optional[str]

a Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by our API

None html bool

Pass 1 if message contains HTML contents

False

Returns:

Type Description Response Source code in lunchable/plugins/pushlunch/pushover.py
def send_notification(\n    self,\n    message: str,\n    attachment: Optional[object] = None,\n    device: Optional[str] = None,\n    title: Optional[str] = None,\n    url: Optional[str] = None,\n    url_title: Optional[str] = None,\n    priority: Optional[int] = None,\n    sound: Optional[str] = None,\n    timestamp: Optional[str] = None,\n    html: bool = False,\n) -> httpx.Response:\n    \"\"\"\n    Send a Pushover Notification\n\n    Parameters\n    ----------\n    message: Optional[str]\n        your message\n    attachment: Optional[object]\n        an image attachment to send with the message; see attachments for more information\n        on how to upload files\n    device: Optional[str]\n        your user's device name to send the message directly to that device, rather than\n        all of the user's devices (multiple devices may be separated by a comma)\n    title: Optional[str]\n        your message's title, otherwise your app's name is used\n    url: Optional[str]\n        a supplementary URL to show with your message\n    url_title: Optional[str]\n        a title for your supplementary URL, otherwise just the URL is shown\n    priority: Optional[int]\n        send as -2 to generate no notification/alert, -1 to always send as a quiet\n        notification, 1 to display as high-priority and bypass the user's quiet hours,\n        or 2 to also require confirmation from the user\n    sound: Optional[str]\n        the name of one of the sounds supported by device clients to override the\n        user's default sound choice\n    timestamp: Optional[str]\n        a Unix timestamp of your message's date and time to display to the user, rather\n        than the time your message is received by our API\n    html: Union[None, 1]\n        Pass 1 if message contains HTML contents\n\n    Returns\n    -------\n    httpx.Response\n    \"\"\"\n    html_param = 1 if html not in [None, False] else None\n    params_dict = {\n        \"message\": message,\n        \"attachment\": attachment,\n        \"device\": device,\n        \"title\": title,\n        \"url\": url,\n        \"url_title\": url_title,\n        \"priority\": priority,\n        \"sound\": sound,\n        \"timestamp\": timestamp,\n        \"html\": html_param,\n    }\n    params: Dict[str, Any] = {\n        key: value for key, value in params_dict.items() if value is not None\n    }\n    params.update(self._params)\n    response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n    response.raise_for_status()\n    return response\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunchError","title":"PushLunchError","text":"

Bases: Exception

PushLunch Exception

Source code in lunchable/plugins/pushlunch/pushover.py
class PushLunchError(Exception):\n    \"\"\"\n    PushLunch Exception\n    \"\"\"\n\n    pass\n
"},{"location":"reference/plugins/splitlunch/","title":"splitlunch","text":"

Splitwise Plugin for Lunchmoney

"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch","title":"SplitLunch","text":"

Bases: Splitwise

Lunchable Plugin For Interacting With Splitwise

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
class SplitLunch(splitwise.Splitwise):\n    \"\"\"\n    Lunchable Plugin For Interacting With Splitwise\n    \"\"\"\n\n    def __init__(\n        self,\n        lunch_money_access_token: Optional[str] = None,\n        financial_partner_id: Optional[int] = None,\n        financial_partner_email: Optional[str] = None,\n        financial_partner_group_id: Optional[int] = None,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n        lunchable_client: Optional[LunchMoney] = None,\n    ):\n        \"\"\"\n        Initialize the Parent Class with some additional properties\n\n        Parameters\n        ----------\n        financial_partner_id: Optional[int]\n            Splitwise User ID of financial partner\n        financial_partner_email: Optional[str]\n            Splitwise linked email address of financial partner\n        financial_partner_group_id: Optional[int]\n            Splitwise Group ID for financial partner transactions\n        consumer_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n            variable\n        consumer_secret: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n            environment variable\n        api_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n            variable.\n        lunch_money_access_token: Optional[str]\n            Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable if not provided.\n        lunchable_client: LunchMoney\n            Instantiated LunchMoney object to use as internal client. One will\n            be created using environment variables otherwise.\n        \"\"\"\n        init_kwargs = self._get_splitwise_init_kwargs(\n            consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n        )\n        super(SplitLunch, self).__init__(**init_kwargs)\n        self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n        self.financial_partner: splitwise.Friend = self.get_friend(\n            friend_id=financial_partner_id, email_address=financial_partner_email\n        )\n        self.financial_group = financial_partner_group_id\n        self.last_check: Optional[datetime.datetime] = None\n        self.lunchable = (\n            LunchMoney(access_token=lunch_money_access_token)\n            if lunchable_client is None\n            else lunchable_client\n        )\n        self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n        self.splitwise_tag = self._none_tag.model_copy()\n        self.splitlunch_tag = self._none_tag.model_copy()\n        self.splitlunch_import_tag = self._none_tag.model_copy()\n        self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n        self._get_splitwise_tags()\n        self.earliest_start_date = datetime.date(1812, 1, 1)\n        today = datetime.date.today()\n        self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n        self.splitwise_asset = self._get_splitwise_asset()\n        self.reimbursement_category = self._get_reimbursement_category()\n\n    def __repr__(self) -> str:\n        \"\"\"\n        String Representation\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return f\"<Splitwise: {self.current_user.email}>\"\n\n    @classmethod\n    def _split_amount(cls, amount: float, splits: int) -> Tuple[float, ...]:\n        \"\"\"\n        Split a money amount into fair shares\n\n        Parameters\n        ----------\n        amount: float\n        splits: int\n\n        Returns\n        -------\n        Tuple[float]\n        \"\"\"\n        try:\n            assert amount == round(amount, 2)\n        except AssertionError as ae:\n            raise SplitLunchError(\n                f\"{amount} caused an error, you must provide a real \" \"spending amount.\"\n            ) from ae\n        equal_shares = round(amount, 2) / splits\n        remainder_dollars = floor(equal_shares)\n        remainder_cents = floor((equal_shares - remainder_dollars) * 100) / 100\n        remainder_left = round(\n            (equal_shares - remainder_dollars - remainder_cents) * splits * 100, 0\n        )\n        owed_amount = remainder_dollars + remainder_cents\n        return_amounts = [owed_amount for _ in range(splits)]\n        for i in range(int(remainder_left)):\n            return_amounts[i] += 0.010\n        shuffle(return_amounts)\n        return tuple([round(item, 2) for item in return_amounts])\n\n    @classmethod\n    def split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n        \"\"\"\n        Split a Transaction into Two\n\n        Split a bill into a tuple of two amounts (and take care\n        of the extra penny if needed)\n\n        Parameters\n        ----------\n        amount: A Currency amount (no more precise than cents)\n\n        Returns\n        -------\n        tuple\n            A tuple is returned with each participant's amount\n        \"\"\"\n        amounts_due = cls._split_amount(amount=amount, splits=2)\n        return amounts_due\n\n    def create_self_paid_expense(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        primary_user_owes, financial_partner_owes = self.split_a_transaction(\n            amount=amount\n        )\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=primary_user_owes)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=financial_partner_owes)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def create_expense_on_behalf_of_partner(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n        This expense will be completely owed by the partner and maked as reimbursed.\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=0.00)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=amount)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def get_friend(\n        self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n    ) -> Optional[splitwise.Friend]:\n        \"\"\"\n        Retrieve a Financial Partner by Email Address\n\n        Parameters\n        ----------\n        email_address: str\n            Email Address of Friend's user in Splitwise\n        friend_id: Optional[int]\n            Splitwise friend ID. Notice the friend ID in the following\n            URL: https://secure.splitwise.com/#/friends/12345678\n\n        Returns\n        -------\n        Optional[splitwise.Friend]\n        \"\"\"\n        friend_list: List[splitwise.Friend] = self.getFriends()\n        if len(friend_list) == 1:\n            return friend_list[0]\n        for friend in friend_list:\n            if friend_id is not None and friend.id == friend_id:\n                return friend\n            elif (\n                email_address is not None\n                and friend.email.lower() == email_address.lower()\n            ):\n                return friend\n        return None\n\n    def get_expenses(\n        self,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        group_id: Optional[int] = None,\n        friendship_id: Optional[int] = None,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        updated_after: Optional[datetime.datetime] = None,\n        updated_before: Optional[datetime.datetime] = None,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Get Splitwise Expenses\n\n        Parameters\n        ----------\n        offset: Optional[int]\n            Number of expenses to be skipped\n        limit: Optional[int]\n            Number of expenses to be returned\n        group_id: Optional[int]\n            GroupID of the expenses\n        friendship_id: Optional[int]\n            FriendshipID of the expenses\n        dated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses later that this date\n        dated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses earlier than this date\n        updated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated after this date\n        updated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated before this date\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        expenses = self.getExpenses(\n            offset=offset,\n            limit=limit,\n            group_id=group_id,\n            friendship_id=friendship_id,\n            dated_after=dated_after,\n            dated_before=dated_before,\n            updated_after=updated_after,\n            updated_before=updated_before,\n        )\n        pydantic_expenses = [\n            self.splitwise_to_pydantic(expense) for expense in expenses\n        ]\n        return pydantic_expenses\n\n    @classmethod\n    def _get_splitwise_init_kwargs(\n        cls,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Get the Splitwise Kwargs\n\n        Parameters\n        ----------\n        consumer_key: Optional[str]\n        consumer_secret: Optional[str]\n        api_key: Optional[str]\n        \"\"\"\n        if consumer_key is None:\n            consumer_key = getenv(\"SPLITWISE_CONSUMER_KEY\")\n        if consumer_secret is None:\n            consumer_secret = getenv(\"SPLITWISE_CONSUMER_SECRET\")\n        if api_key is None:\n            api_key = getenv(\"SPLITWISE_API_KEY\", None)\n        init_kwargs = {\n            \"consumer_key\": consumer_key,\n            \"consumer_secret\": consumer_secret,\n            \"api_key\": api_key,\n        }\n        if consumer_key is None or consumer_secret is None or api_key is None:\n            error_message = (\n                dedent(\n                    \"\"\"\n            You must set your Splitwise credentials explicitly or by assigning\n            the `SPLITWISE_CONSUMER_KEY`, `SPLITWISE_CONSUMER_SECRET`, and the\n            `SPLITWISE_API_KEY`environment variables\n            \"\"\"\n                )\n                .replace(\"\\n\", \" \")\n                .replace(\"  \", \" \")\n            )\n            logger.error(error_message)\n            raise SplitLunchError(error_message)\n        return init_kwargs\n\n    def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n        \"\"\"\n        Convert Splitwise Object to Pydantic\n\n        Parameters\n        ----------\n        expense: splitwise.Expense\n\n        Returns\n        -------\n        SplitLunchExpense\n        \"\"\"\n        financial_impact, self_paid = _get_splitwise_impact(\n            expense=expense, current_user_id=self.current_user.id\n        )\n        expense = SplitLunchExpense(\n            splitwise_id=expense.id,\n            original_amount=expense.cost,\n            financial_impact=financial_impact,\n            self_paid=self_paid,\n            description=expense.description,\n            category=expense.category.name,\n            details=expense.details,\n            payment=expense.payment,\n            date=expense.date,\n            users=[user.id for user in expense.users],\n            created_at=expense.created_at,\n            updated_at=expense.updated_at,\n            deleted_at=expense.deleted_at,\n            deleted=True if expense.deleted_at is not None else False,\n        )\n        return expense\n\n    def _get_splitwise_asset(self) -> Optional[AssetsObject]:\n        \"\"\"\n        Get the Splitwise asset\n\n        Parse a user's Lunch Money accounts and return the manually managed\n        Splitwise account asset object\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        assets = self.lunchable.get_assets()\n        splitwise_assets = []\n        for asset in assets:\n            if (\n                asset.institution_name is not None\n                and \"splitwise\" in asset.institution_name.lower()\n            ):\n                splitwise_assets.append(asset)\n        if len(splitwise_assets) == 0:\n            return None\n        elif len(splitwise_assets) > 1:\n            raise SplitLunchError(\n                \"SplitLunch requires an manually managed Splitwise asset. \"\n                \"Make sure you have a single account where 'Splitwise' \"\n                \"is in the asset's `Institution Name`.\"\n            )\n        else:\n            return splitwise_assets[0]\n\n    def _get_reimbursement_category(self) -> Optional[CategoriesObject]:\n        \"\"\"\n        Get the Reimbusement Category\n\n        Parse a user's Lunch Money categories and return the Reimbursement\n        category\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        categories = self.lunchable.get_categories()\n        reimbursement_list = []\n        for category in categories:\n            if \"reimbursement\" == category.name.strip().lower():\n                reimbursement_list.append(category)\n        if len(reimbursement_list) != 1:\n            return None\n        return reimbursement_list[0]\n\n    def _get_splitwise_tags(self) -> None:\n        \"\"\"\n        Get Lunch Money Tags to Interact with\n\n        Returns\n        -------\n        Dict[str, int]\n        \"\"\"\n        all_tags = self.lunchable.get_tags()\n        for tag in all_tags:\n            if tag.name.lower() == SplitLunchConfig.splitlunch_tag.lower():\n                self.splitlunch_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitwise_tag.lower():\n                self.splitwise_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitlunch_import_tag.lower():\n                self.splitlunch_import_tag = tag\n            elif (\n                tag.name.lower()\n                == SplitLunchConfig.splitlunch_direct_import_tag.lower()\n            ):\n                self.splitlunch_direct_import_tag = tag\n\n    def _raise_nonexistent_tag_error(self, tags: List[str]) -> None:\n        \"\"\"\n        Raise a warning for specific SplitLunch Tags\n\n        tags: List[str]\n            A list of tags to raise the error for\n        \"\"\"\n        if (\n            self.splitlunch_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitwise_tag == self._none_tag\n            and SplitLunchConfig.splitwise_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitwise_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_import_tag}` tag is required. \"\n                f\"This tag is used for creating Splitwise transactions directly from \"\n                f\"Lunch Money transactions. These transactions will be split in half,\"\n                f\"and have one half marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_direct_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_direct_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_direct_import_tag}` tag is \"\n                \"required. This tag is used for creating Splitwise transactions \"\n                \"directly from Lunch Money transactions. These transactions will \"\n                \"be completely owed by your financial partner.\"\n            )\n            raise SplitLunchError(error_message)\n\n    def get_splitlunch_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitlunch\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def get_splitlunch_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitlunch_direct_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(\n            tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n        )\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_direct_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitwise_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitwise\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n        \"\"\"\n        Operate on `SplitLunch` tagged transactions\n\n        Split all transactions with the `SplitLunch` tag in half. One of these\n        new splits will be recategorized to `Reimbursement`. Both new splits will receive\n        the `Splitwise` tag without any preexisting tags.\n        \"\"\"\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        split_transaction_ids = []\n        tagged_objects = self.get_splitlunch_tagged_transactions()\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n            # Generate the First Split\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=transaction.notes,\n                amount=amount_1,\n            )\n            # Generate the second split as a copy, change some properties\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = amount_2\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                \"Splitting transaction: %s -> (%s, %s)\",\n                transaction.amount,\n                amount_1,\n                amount_2,\n            )\n\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            # Tag each of the new transactions generated\n            split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags if transaction.tags is not None else []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag is not None\n                    and tag.name.lower() != self.splitlunch_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return split_transaction_ids\n\n    def make_splitlunch_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchImport` tagged transactions\n\n        Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n        One of these new splits will be recategorized to `Reimbursement`. Both new splits\n        will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n        reapplied.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_self_paid_expense(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=notes,\n                # financial_impact for self-paid transactions will be negative\n                amount=round(\n                    transaction.amount - abs(new_transaction.financial_impact), 2\n                ),\n            )\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = abs(new_transaction.financial_impact)\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                f\"Transaction split by Splitwise: {transaction.amount} -> \"\n                f\"({split_object.amount}, {reimbursement_object.amount})\"\n            )\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": reimbursement_object.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": update_response[\"updated\"],\n                \"split\": update_response[\"split\"],\n            }\n            update_responses.append(formatted_update_response)\n            # Tag each of the new transactions generated\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags or []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return update_responses\n\n    def make_splitlunch_direct_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchDirectImport` tagged transactions\n\n        Send a transaction to Splitwise and then mark the transaction under the\n        `Reimbursement` category. The sum of the transaction will be completely owed\n        by the financial partner.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_expense_on_behalf_of_partner(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            existing_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in existing_tags\n                if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n                tags.append(self.splitwise_tag.name)\n            update = TransactionUpdateObject(\n                category_id=self.reimbursement_category.id, tags=tags, notes=notes\n            )\n            response = self.lunchable.update_transaction(\n                transaction_id=transaction.id, transaction=update\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": transaction.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": response[\"updated\"],\n                \"split\": None,\n            }\n            update_responses.append(formatted_update_response)\n        return update_responses\n\n    def splitwise_to_lunchmoney(\n        self,\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[int]:\n        \"\"\"\n        Ingest Splitwise Expenses into Lunch Money\n\n        This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n        a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n        payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[int]\n            New Lunch Money transaction IDs\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        batch = []\n        new_transaction_ids = []\n        filtered_expenses = self.filter_relevant_splitwise_expenses(\n            expenses=expenses,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        for splitwise_transaction in filtered_expenses:\n            new_lunchmoney_transaction = TransactionInsertObject(\n                date=splitwise_transaction.date.astimezone(tzlocal()),\n                payee=splitwise_transaction.description,\n                amount=splitwise_transaction.financial_impact,\n                asset_id=self.splitwise_asset.id,\n                external_id=splitwise_transaction.splitwise_id,\n            )\n            batch.append(new_lunchmoney_transaction)\n            if len(batch) == 10:\n                new_ids = self.lunchable.insert_transactions(\n                    transactions=batch, apply_rules=True\n                )\n                new_transaction_ids += new_ids\n                batch = []\n        if len(batch) > 0:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n        return new_transaction_ids\n\n    @staticmethod\n    def filter_relevant_splitwise_expenses(\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Filter Expenses in Splitwise into relevant expenses.\n\n        This filtering action is important to understand when seeing why not\n        all transactions from Splitwise end up flowing into Lunch Money.\n\n        1) It filters out deleted expenses\n\n        2) It filters out expenses with a financial impact of 0, implying that the user was not\n        involved in the expense.\n\n        3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n        `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        filtered_expenses = []\n        for splitwise_transaction in expenses:\n            if all(\n                [\n                    splitwise_transaction.deleted is False,\n                    splitwise_transaction.financial_impact != 0.00,\n                    allow_self_paid or (splitwise_transaction.self_paid is False),\n                    allow_payments or (splitwise_transaction.payment is False),\n                ]\n            ):\n                filtered_expenses.append(splitwise_transaction)\n\n        return filtered_expenses\n\n    def get_splitwise_balance(self) -> float:\n        \"\"\"\n        Get the net balance in Splitwise\n\n        Returns\n        -------\n        float\n        \"\"\"\n        groups = self.getGroups()\n        total_balance = 0.00\n        for group in groups:\n            for debt in group.simplified_debts:\n                if debt.fromUser == self.current_user.id:\n                    total_balance -= float(debt.amount)\n                elif debt.toUser == self.current_user.id:\n                    total_balance += float(debt.amount)\n        return total_balance\n\n    def update_splitwise_balance(self) -> AssetsObject:\n        \"\"\"\n        Get and update the Splitwise Asset in Lunch Money\n\n        Returns\n        -------\n        AssetsObject\n            Updated  balance\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        balance = self.get_splitwise_balance()\n        if balance != self.splitwise_asset.balance:\n            updated_asset = self.lunchable.update_asset(\n                asset_id=self.splitwise_asset.id, balance=balance\n            )\n            self.splitwise_asset = updated_asset\n        return self.splitwise_asset\n\n    _deleted_payee = \"[DELETED FROM SPLITWISE]\"\n\n    def get_new_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n    ) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n        \"\"\"\n        Get Splitwise Transactions that don't exist in Lunch Money\n\n        Also return deleted transaction from LunchMoney\n\n        Returns\n        -------\n        Tuple[List[SplitLunchExpense], List[TransactionObject]]\n            New and Deleted Transactions\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        splitlunch_expenses = self.lunchable.get_transactions(\n            asset_id=self.splitwise_asset.id,\n            start_date=datetime.datetime(1800, 1, 1),\n            end_date=datetime.datetime(2300, 12, 31),\n        )\n        splitlunch_ids = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        splitwise_expenses = self.get_expenses(\n            limit=0,\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n        new_ids = splitwise_ids.difference(splitlunch_ids)\n        new_expenses = [\n            expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n        ]\n        deleted_transactions = self.get_deleted_transactions(\n            splitlunch_expenses=splitlunch_expenses,\n            splitwise_transactions=splitwise_expenses,\n        )\n        return new_expenses, deleted_transactions\n\n    def get_deleted_transactions(\n        self,\n        splitlunch_expenses: List[TransactionObject],\n        splitwise_transactions: List[SplitLunchExpense],\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n        Set these transactions to $0.00 and Make a Note\n\n        Parameters\n        ----------\n        splitlunch_expenses: List[TransactionObject]\n        splitwise_transactions: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        existing_transactions = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        deleted_ids = {\n            item.splitwise_id for item in splitwise_transactions if item.deleted is True\n        }\n        untethered_transactions = deleted_ids.intersection(existing_transactions)\n        transactions_to_delete = [\n            tran\n            for tran in splitlunch_expenses\n            if tran.external_id is not None\n            and int(tran.external_id) in untethered_transactions\n            and tran.payee != self._deleted_payee\n        ]\n        return transactions_to_delete\n\n    def refresh_splitwise_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Import New Splitwise Transactions to Lunch Money\n\n        This function get's all transactions from Splitwise, all transactions from\n        your Lunch Money Splitwise account and compares the two.\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        new_transactions, deleted_transactions = self.get_new_transactions(\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        self.splitwise_to_lunchmoney(\n            expenses=new_transactions,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        splitwise_asset = self.update_splitwise_balance()\n        self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n        return {\n            \"balance\": splitwise_asset.balance,\n            \"new\": new_transactions,\n            \"deleted\": deleted_transactions,\n        }\n\n    def handle_deleted_transactions(\n        self,\n        deleted_transactions: List[TransactionObject],\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n        Parameters\n        ----------\n        deleted_transactions: List[TransactionObject]\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        updated_transactions = []\n        for transaction in deleted_transactions:\n            update = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                transaction=TransactionUpdateObject(\n                    amount=0.00,\n                    payee=self._deleted_payee,\n                    notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n                ),\n            )\n            updated_transactions.append(update)\n        return updated_transactions\n\n    def _raise_financial_partner_error(self) -> None:\n        \"\"\"\n        Raise Errors for Financial Partners\n        \"\"\"\n        if self.financial_partner is None:\n            raise SplitLunchError(\n                \"You must designate a financial partner in Splitwise. \"\n                \"This can be done with the partner's Splitwise User ID # \"\n                \"or their email address.\"\n            )\n\n    def _raise_splitwise_asset_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"You must create an asset (aka Account) in Lunch Money with \"\n            \"`Splitwise` in the name. There should only be one account \"\n            \"like this.\"\n        )\n\n    def _raise_category_reimbursement_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"SplitLunch requires a reimbursement Category. \"\n            \"Make sure you have a category entitled `Reimbursement`. \"\n            \"This category will be excluded from budgeting.\"\n            \"Half of split transactions will be created with \"\n            \"this category.\"\n        )\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.__init__","title":"__init__(lunch_money_access_token=None, financial_partner_id=None, financial_partner_email=None, financial_partner_group_id=None, consumer_key=None, consumer_secret=None, api_key=None, lunchable_client=None)","text":"

Initialize the Parent Class with some additional properties

Parameters:

Name Type Description Default financial_partner_id Optional[int]

Splitwise User ID of financial partner

None financial_partner_email Optional[str]

Splitwise linked email address of financial partner

None financial_partner_group_id Optional[int]

Splitwise Group ID for financial partner transactions

None consumer_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_KEY environment variable

None consumer_secret Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_SECRET environment variable

None api_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_API_KEY environment variable.

None lunch_money_access_token Optional[str]

Lunch Money Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable if not provided.

None lunchable_client Optional[LunchMoney]

Instantiated LunchMoney object to use as internal client. One will be created using environment variables otherwise.

None Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __init__(\n    self,\n    lunch_money_access_token: Optional[str] = None,\n    financial_partner_id: Optional[int] = None,\n    financial_partner_email: Optional[str] = None,\n    financial_partner_group_id: Optional[int] = None,\n    consumer_key: Optional[str] = None,\n    consumer_secret: Optional[str] = None,\n    api_key: Optional[str] = None,\n    lunchable_client: Optional[LunchMoney] = None,\n):\n    \"\"\"\n    Initialize the Parent Class with some additional properties\n\n    Parameters\n    ----------\n    financial_partner_id: Optional[int]\n        Splitwise User ID of financial partner\n    financial_partner_email: Optional[str]\n        Splitwise linked email address of financial partner\n    financial_partner_group_id: Optional[int]\n        Splitwise Group ID for financial partner transactions\n    consumer_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n        variable\n    consumer_secret: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n        environment variable\n    api_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n        variable.\n    lunch_money_access_token: Optional[str]\n        Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable if not provided.\n    lunchable_client: LunchMoney\n        Instantiated LunchMoney object to use as internal client. One will\n        be created using environment variables otherwise.\n    \"\"\"\n    init_kwargs = self._get_splitwise_init_kwargs(\n        consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n    )\n    super(SplitLunch, self).__init__(**init_kwargs)\n    self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n    self.financial_partner: splitwise.Friend = self.get_friend(\n        friend_id=financial_partner_id, email_address=financial_partner_email\n    )\n    self.financial_group = financial_partner_group_id\n    self.last_check: Optional[datetime.datetime] = None\n    self.lunchable = (\n        LunchMoney(access_token=lunch_money_access_token)\n        if lunchable_client is None\n        else lunchable_client\n    )\n    self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n    self.splitwise_tag = self._none_tag.model_copy()\n    self.splitlunch_tag = self._none_tag.model_copy()\n    self.splitlunch_import_tag = self._none_tag.model_copy()\n    self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n    self._get_splitwise_tags()\n    self.earliest_start_date = datetime.date(1812, 1, 1)\n    today = datetime.date.today()\n    self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n    self.splitwise_asset = self._get_splitwise_asset()\n    self.reimbursement_category = self._get_reimbursement_category()\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __repr__(self) -> str:\n    \"\"\"\n    String Representation\n\n    Returns\n    -------\n    str\n    \"\"\"\n    return f\"<Splitwise: {self.current_user.email}>\"\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.create_expense_on_behalf_of_partner","title":"create_expense_on_behalf_of_partner(amount, description, date)","text":"

Create and Submit a Splitwise Expense on behalf of your financial partner.

This expense will be completely owed by the partner and maked as reimbursed.

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_expense_on_behalf_of_partner(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n    This expense will be completely owed by the partner and maked as reimbursed.\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=0.00)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=amount)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.create_self_paid_expense","title":"create_self_paid_expense(amount, description, date)","text":"

Create and Submit a Splitwise Expense

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_self_paid_expense(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    primary_user_owes, financial_partner_owes = self.split_a_transaction(\n        amount=amount\n    )\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=primary_user_owes)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=financial_partner_owes)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.filter_relevant_splitwise_expenses","title":"filter_relevant_splitwise_expenses(expenses, allow_self_paid=False, allow_payments=False) staticmethod","text":"

Filter Expenses in Splitwise into relevant expenses.

This filtering action is important to understand when seeing why not all transactions from Splitwise end up flowing into Lunch Money.

1) It filters out deleted expenses

2) It filters out expenses with a financial impact of 0, implying that the user was not involved in the expense.

3) If the --allow-self-paid flag is not provided, it filters out self-paid expenses. A self-paid expense is an expense in Splitwise where you originated the payment. This is excluded because it is assumed that these transactions will have already been imported via a different account.

4) If the --allow-payments flag is not provided, it filters out payments. Payments are excluded because it is assumed that these transactions will have already been imported via a different account.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@staticmethod\ndef filter_relevant_splitwise_expenses(\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Filter Expenses in Splitwise into relevant expenses.\n\n    This filtering action is important to understand when seeing why not\n    all transactions from Splitwise end up flowing into Lunch Money.\n\n    1) It filters out deleted expenses\n\n    2) It filters out expenses with a financial impact of 0, implying that the user was not\n    involved in the expense.\n\n    3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n    `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    filtered_expenses = []\n    for splitwise_transaction in expenses:\n        if all(\n            [\n                splitwise_transaction.deleted is False,\n                splitwise_transaction.financial_impact != 0.00,\n                allow_self_paid or (splitwise_transaction.self_paid is False),\n                allow_payments or (splitwise_transaction.payment is False),\n            ]\n        ):\n            filtered_expenses.append(splitwise_transaction)\n\n    return filtered_expenses\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_deleted_transactions","title":"get_deleted_transactions(splitlunch_expenses, splitwise_transactions)","text":"

Get Splitwise Transactions that exist in Lunch Money but have since been deleted

Set these transactions to $0.00 and Make a Note

Parameters:

Name Type Description Default splitlunch_expenses List[TransactionObject] required splitwise_transactions List[SplitLunchExpense] required

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_deleted_transactions(\n    self,\n    splitlunch_expenses: List[TransactionObject],\n    splitwise_transactions: List[SplitLunchExpense],\n) -> List[TransactionObject]:\n    \"\"\"\n    Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n    Set these transactions to $0.00 and Make a Note\n\n    Parameters\n    ----------\n    splitlunch_expenses: List[TransactionObject]\n    splitwise_transactions: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    existing_transactions = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    deleted_ids = {\n        item.splitwise_id for item in splitwise_transactions if item.deleted is True\n    }\n    untethered_transactions = deleted_ids.intersection(existing_transactions)\n    transactions_to_delete = [\n        tran\n        for tran in splitlunch_expenses\n        if tran.external_id is not None\n        and int(tran.external_id) in untethered_transactions\n        and tran.payee != self._deleted_payee\n    ]\n    return transactions_to_delete\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_expenses","title":"get_expenses(offset=None, limit=None, group_id=None, friendship_id=None, dated_after=None, dated_before=None, updated_after=None, updated_before=None)","text":"

Get Splitwise Expenses

Parameters:

Name Type Description Default offset Optional[int]

Number of expenses to be skipped

None limit Optional[int]

Number of expenses to be returned

None group_id Optional[int]

GroupID of the expenses

None friendship_id Optional[int]

FriendshipID of the expenses

None dated_after Optional[datetime]

ISO 8601 Date time. Return expenses later that this date

None dated_before Optional[datetime]

ISO 8601 Date time. Return expenses earlier than this date

None updated_after Optional[datetime]

ISO 8601 Date time. Return expenses updated after this date

None updated_before Optional[datetime]

ISO 8601 Date time. Return expenses updated before this date

None

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_expenses(\n    self,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    group_id: Optional[int] = None,\n    friendship_id: Optional[int] = None,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    updated_after: Optional[datetime.datetime] = None,\n    updated_before: Optional[datetime.datetime] = None,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Get Splitwise Expenses\n\n    Parameters\n    ----------\n    offset: Optional[int]\n        Number of expenses to be skipped\n    limit: Optional[int]\n        Number of expenses to be returned\n    group_id: Optional[int]\n        GroupID of the expenses\n    friendship_id: Optional[int]\n        FriendshipID of the expenses\n    dated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses later that this date\n    dated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses earlier than this date\n    updated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated after this date\n    updated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated before this date\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    expenses = self.getExpenses(\n        offset=offset,\n        limit=limit,\n        group_id=group_id,\n        friendship_id=friendship_id,\n        dated_after=dated_after,\n        dated_before=dated_before,\n        updated_after=updated_after,\n        updated_before=updated_before,\n    )\n    pydantic_expenses = [\n        self.splitwise_to_pydantic(expense) for expense in expenses\n    ]\n    return pydantic_expenses\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_friend","title":"get_friend(email_address=None, friend_id=None)","text":"

Retrieve a Financial Partner by Email Address

Parameters:

Name Type Description Default email_address Optional[str]

Email Address of Friend's user in Splitwise

None friend_id Optional[int]

Splitwise friend ID. Notice the friend ID in the following URL: https://secure.splitwise.com/#/friends/12345678

None

Returns:

Type Description Optional[Friend] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_friend(\n    self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n) -> Optional[splitwise.Friend]:\n    \"\"\"\n    Retrieve a Financial Partner by Email Address\n\n    Parameters\n    ----------\n    email_address: str\n        Email Address of Friend's user in Splitwise\n    friend_id: Optional[int]\n        Splitwise friend ID. Notice the friend ID in the following\n        URL: https://secure.splitwise.com/#/friends/12345678\n\n    Returns\n    -------\n    Optional[splitwise.Friend]\n    \"\"\"\n    friend_list: List[splitwise.Friend] = self.getFriends()\n    if len(friend_list) == 1:\n        return friend_list[0]\n    for friend in friend_list:\n        if friend_id is not None and friend.id == friend_id:\n            return friend\n        elif (\n            email_address is not None\n            and friend.email.lower() == email_address.lower()\n        ):\n            return friend\n    return None\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_new_transactions","title":"get_new_transactions(dated_after=None, dated_before=None)","text":"

Get Splitwise Transactions that don't exist in Lunch Money

Also return deleted transaction from LunchMoney

Returns:

Type Description Tuple[List[SplitLunchExpense], List[TransactionObject]]

New and Deleted Transactions

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_new_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n    \"\"\"\n    Get Splitwise Transactions that don't exist in Lunch Money\n\n    Also return deleted transaction from LunchMoney\n\n    Returns\n    -------\n    Tuple[List[SplitLunchExpense], List[TransactionObject]]\n        New and Deleted Transactions\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    splitlunch_expenses = self.lunchable.get_transactions(\n        asset_id=self.splitwise_asset.id,\n        start_date=datetime.datetime(1800, 1, 1),\n        end_date=datetime.datetime(2300, 12, 31),\n    )\n    splitlunch_ids = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    splitwise_expenses = self.get_expenses(\n        limit=0,\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n    new_ids = splitwise_ids.difference(splitlunch_ids)\n    new_expenses = [\n        expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n    ]\n    deleted_transactions = self.get_deleted_transactions(\n        splitlunch_expenses=splitlunch_expenses,\n        splitwise_transactions=splitwise_expenses,\n    )\n    return new_expenses, deleted_transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_direct_import_tagged_transactions","title":"get_splitlunch_direct_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchDirectImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_direct_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(\n        tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n    )\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_direct_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_import_tagged_transactions","title":"get_splitlunch_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_tagged_transactions","title":"get_splitlunch_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitlunch\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitlunch\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitwise_balance","title":"get_splitwise_balance()","text":"

Get the net balance in Splitwise

Returns:

Type Description float Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_balance(self) -> float:\n    \"\"\"\n    Get the net balance in Splitwise\n\n    Returns\n    -------\n    float\n    \"\"\"\n    groups = self.getGroups()\n    total_balance = 0.00\n    for group in groups:\n        for debt in group.simplified_debts:\n            if debt.fromUser == self.current_user.id:\n                total_balance -= float(debt.amount)\n            elif debt.toUser == self.current_user.id:\n                total_balance += float(debt.amount)\n    return total_balance\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitwise_tagged_transactions","title":"get_splitwise_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitwise\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitwise\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.handle_deleted_transactions","title":"handle_deleted_transactions(deleted_transactions)","text":"

Update Transactions That Exist in Splitwise, but have been deleted in Splitwise

Parameters:

Name Type Description Default deleted_transactions List[TransactionObject] required

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def handle_deleted_transactions(\n    self,\n    deleted_transactions: List[TransactionObject],\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n    Parameters\n    ----------\n    deleted_transactions: List[TransactionObject]\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    updated_transactions = []\n    for transaction in deleted_transactions:\n        update = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            transaction=TransactionUpdateObject(\n                amount=0.00,\n                payee=self._deleted_payee,\n                notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n            ),\n        )\n        updated_transactions.append(update)\n    return updated_transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch","title":"make_splitlunch(tag_transactions=False)","text":"

Operate on SplitLunch tagged transactions

Split all transactions with the SplitLunch tag in half. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without any preexisting tags.

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n    \"\"\"\n    Operate on `SplitLunch` tagged transactions\n\n    Split all transactions with the `SplitLunch` tag in half. One of these\n    new splits will be recategorized to `Reimbursement`. Both new splits will receive\n    the `Splitwise` tag without any preexisting tags.\n    \"\"\"\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    split_transaction_ids = []\n    tagged_objects = self.get_splitlunch_tagged_transactions()\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n        # Generate the First Split\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=transaction.notes,\n            amount=amount_1,\n        )\n        # Generate the second split as a copy, change some properties\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = amount_2\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            \"Splitting transaction: %s -> (%s, %s)\",\n            transaction.amount,\n            amount_1,\n            amount_2,\n        )\n\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        # Tag each of the new transactions generated\n        split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags if transaction.tags is not None else []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag is not None\n                and tag.name.lower() != self.splitlunch_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return split_transaction_ids\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch_direct_import","title":"make_splitlunch_direct_import(tag_transactions=False)","text":"

Operate on SplitLunchDirectImport tagged transactions

Send a transaction to Splitwise and then mark the transaction under the Reimbursement category. The sum of the transaction will be completely owed by the financial partner.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_direct_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchDirectImport` tagged transactions\n\n    Send a transaction to Splitwise and then mark the transaction under the\n    `Reimbursement` category. The sum of the transaction will be completely owed\n    by the financial partner.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_expense_on_behalf_of_partner(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        existing_tags = transaction.tags or []\n        tags = [\n            tag.name\n            for tag in existing_tags\n            if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n        ]\n        if self.splitwise_tag.name not in tags and tag_transactions is True:\n            self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n            tags.append(self.splitwise_tag.name)\n        update = TransactionUpdateObject(\n            category_id=self.reimbursement_category.id, tags=tags, notes=notes\n        )\n        response = self.lunchable.update_transaction(\n            transaction_id=transaction.id, transaction=update\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": transaction.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": response[\"updated\"],\n            \"split\": None,\n        }\n        update_responses.append(formatted_update_response)\n    return update_responses\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch_import","title":"make_splitlunch_import(tag_transactions=False)","text":"

Operate on SplitLunchImport tagged transactions

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without the SplitLunchImport tag. Any other tags will be reapplied.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchImport` tagged transactions\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Both new splits\n    will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n    reapplied.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_self_paid_expense(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=notes,\n            # financial_impact for self-paid transactions will be negative\n            amount=round(\n                transaction.amount - abs(new_transaction.financial_impact), 2\n            ),\n        )\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = abs(new_transaction.financial_impact)\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            f\"Transaction split by Splitwise: {transaction.amount} -> \"\n            f\"({split_object.amount}, {reimbursement_object.amount})\"\n        )\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": reimbursement_object.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": update_response[\"updated\"],\n            \"split\": update_response[\"split\"],\n        }\n        update_responses.append(formatted_update_response)\n        # Tag each of the new transactions generated\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return update_responses\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.refresh_splitwise_transactions","title":"refresh_splitwise_transactions(dated_after=None, dated_before=None, allow_self_paid=False, allow_payments=False)","text":"

Import New Splitwise Transactions to Lunch Money

This function get's all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two.

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def refresh_splitwise_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> Dict[str, Any]:\n    \"\"\"\n    Import New Splitwise Transactions to Lunch Money\n\n    This function get's all transactions from Splitwise, all transactions from\n    your Lunch Money Splitwise account and compares the two.\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    new_transactions, deleted_transactions = self.get_new_transactions(\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    self.splitwise_to_lunchmoney(\n        expenses=new_transactions,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    splitwise_asset = self.update_splitwise_balance()\n    self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n    return {\n        \"balance\": splitwise_asset.balance,\n        \"new\": new_transactions,\n        \"deleted\": deleted_transactions,\n    }\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.split_a_transaction","title":"split_a_transaction(amount) classmethod","text":"

Split a Transaction into Two

Split a bill into a tuple of two amounts (and take care of the extra penny if needed)

Parameters:

Name Type Description Default amount Union[float, int] required

Returns:

Type Description tuple

A tuple is returned with each participant's amount

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@classmethod\ndef split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n    \"\"\"\n    Split a Transaction into Two\n\n    Split a bill into a tuple of two amounts (and take care\n    of the extra penny if needed)\n\n    Parameters\n    ----------\n    amount: A Currency amount (no more precise than cents)\n\n    Returns\n    -------\n    tuple\n        A tuple is returned with each participant's amount\n    \"\"\"\n    amounts_due = cls._split_amount(amount=amount, splits=2)\n    return amounts_due\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.splitwise_to_lunchmoney","title":"splitwise_to_lunchmoney(expenses, allow_self_paid=False, allow_payments=False)","text":"

Ingest Splitwise Expenses into Lunch Money

This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default, payments and self-paid transactions are also ineligible. Otherwise it will be ignored.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[int]

New Lunch Money transaction IDs

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_lunchmoney(\n    self,\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[int]:\n    \"\"\"\n    Ingest Splitwise Expenses into Lunch Money\n\n    This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n    a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n    payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[int]\n        New Lunch Money transaction IDs\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    batch = []\n    new_transaction_ids = []\n    filtered_expenses = self.filter_relevant_splitwise_expenses(\n        expenses=expenses,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    for splitwise_transaction in filtered_expenses:\n        new_lunchmoney_transaction = TransactionInsertObject(\n            date=splitwise_transaction.date.astimezone(tzlocal()),\n            payee=splitwise_transaction.description,\n            amount=splitwise_transaction.financial_impact,\n            asset_id=self.splitwise_asset.id,\n            external_id=splitwise_transaction.splitwise_id,\n        )\n        batch.append(new_lunchmoney_transaction)\n        if len(batch) == 10:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n            batch = []\n    if len(batch) > 0:\n        new_ids = self.lunchable.insert_transactions(\n            transactions=batch, apply_rules=True\n        )\n        new_transaction_ids += new_ids\n    return new_transaction_ids\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.splitwise_to_pydantic","title":"splitwise_to_pydantic(expense)","text":"

Convert Splitwise Object to Pydantic

Parameters:

Name Type Description Default expense Expense required

Returns:

Type Description SplitLunchExpense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n    \"\"\"\n    Convert Splitwise Object to Pydantic\n\n    Parameters\n    ----------\n    expense: splitwise.Expense\n\n    Returns\n    -------\n    SplitLunchExpense\n    \"\"\"\n    financial_impact, self_paid = _get_splitwise_impact(\n        expense=expense, current_user_id=self.current_user.id\n    )\n    expense = SplitLunchExpense(\n        splitwise_id=expense.id,\n        original_amount=expense.cost,\n        financial_impact=financial_impact,\n        self_paid=self_paid,\n        description=expense.description,\n        category=expense.category.name,\n        details=expense.details,\n        payment=expense.payment,\n        date=expense.date,\n        users=[user.id for user in expense.users],\n        created_at=expense.created_at,\n        updated_at=expense.updated_at,\n        deleted_at=expense.deleted_at,\n        deleted=True if expense.deleted_at is not None else False,\n    )\n    return expense\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.update_splitwise_balance","title":"update_splitwise_balance()","text":"

Get and update the Splitwise Asset in Lunch Money

Returns:

Type Description AssetsObject

Updated balance

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def update_splitwise_balance(self) -> AssetsObject:\n    \"\"\"\n    Get and update the Splitwise Asset in Lunch Money\n\n    Returns\n    -------\n    AssetsObject\n        Updated  balance\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    balance = self.get_splitwise_balance()\n    if balance != self.splitwise_asset.balance:\n        updated_asset = self.lunchable.update_asset(\n            asset_id=self.splitwise_asset.id, balance=balance\n        )\n        self.splitwise_asset = updated_asset\n    return self.splitwise_asset\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunchError","title":"SplitLunchError","text":"

Bases: LunchMoneyError

Split Lunch Errors

Source code in lunchable/plugins/splitlunch/exceptions.py
class SplitLunchError(LunchMoneyError):\n    \"\"\"\n    Split Lunch Errors\n    \"\"\"\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunchExpense","title":"SplitLunchExpense","text":"

Bases: LunchableModel

SplitLunch Object for Splitwise Expenses

Parameters:

Name Type Description Default splitwise_id int required original_amount float required self_paid bool required financial_impact float required description str required category str required details str | None None payment bool required date datetime required users List[int] required created_at datetime required updated_at datetime required deleted_at datetime | None None deleted bool required Source code in lunchable/plugins/splitlunch/models.py
class SplitLunchExpense(LunchableModel):\n    \"\"\"\n    SplitLunch Object for Splitwise Expenses\n    \"\"\"\n\n    splitwise_id: int\n    original_amount: float\n    self_paid: bool\n    financial_impact: float\n    description: str\n    category: str\n    details: Optional[str] = None\n    payment: bool\n    date: datetime.datetime\n    users: List[int]\n    created_at: datetime.datetime\n    updated_at: datetime.datetime\n    deleted_at: Optional[datetime.datetime] = None\n    deleted: bool\n
"},{"location":"reference/plugins/splitlunch/_config/","title":"_config","text":"

SplitLunch Configuration Helpers

"},{"location":"reference/plugins/splitlunch/_config/#lunchable.plugins.splitlunch._config.SplitLunchConfig","title":"SplitLunchConfig","text":"

Configuration Namespace for SplitLunch

Source code in lunchable/plugins/splitlunch/_config.py
class SplitLunchConfig:\n    \"\"\"\n    Configuration Namespace for SplitLunch\n    \"\"\"\n\n    splitlunch_tag: str = \"SplitLunch\"\n    splitwise_tag: str = \"Splitwise\"\n    splitlunch_import_tag: str = \"SplitLunchImport\"\n    splitlunch_direct_import_tag: str = \"SplitLunchDirectImport\"\n
"},{"location":"reference/plugins/splitlunch/exceptions/","title":"exceptions","text":"

SplitLunch Exceptions

"},{"location":"reference/plugins/splitlunch/exceptions/#lunchable.plugins.splitlunch.exceptions.SplitLunchError","title":"SplitLunchError","text":"

Bases: LunchMoneyError

Split Lunch Errors

Source code in lunchable/plugins/splitlunch/exceptions.py
class SplitLunchError(LunchMoneyError):\n    \"\"\"\n    Split Lunch Errors\n    \"\"\"\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/","title":"lunchmoney_splitwise","text":"

Lunchable Plugin for Splitwise

"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch","title":"SplitLunch","text":"

Bases: Splitwise

Lunchable Plugin For Interacting With Splitwise

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
class SplitLunch(splitwise.Splitwise):\n    \"\"\"\n    Lunchable Plugin For Interacting With Splitwise\n    \"\"\"\n\n    def __init__(\n        self,\n        lunch_money_access_token: Optional[str] = None,\n        financial_partner_id: Optional[int] = None,\n        financial_partner_email: Optional[str] = None,\n        financial_partner_group_id: Optional[int] = None,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n        lunchable_client: Optional[LunchMoney] = None,\n    ):\n        \"\"\"\n        Initialize the Parent Class with some additional properties\n\n        Parameters\n        ----------\n        financial_partner_id: Optional[int]\n            Splitwise User ID of financial partner\n        financial_partner_email: Optional[str]\n            Splitwise linked email address of financial partner\n        financial_partner_group_id: Optional[int]\n            Splitwise Group ID for financial partner transactions\n        consumer_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n            variable\n        consumer_secret: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n            environment variable\n        api_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n            variable.\n        lunch_money_access_token: Optional[str]\n            Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable if not provided.\n        lunchable_client: LunchMoney\n            Instantiated LunchMoney object to use as internal client. One will\n            be created using environment variables otherwise.\n        \"\"\"\n        init_kwargs = self._get_splitwise_init_kwargs(\n            consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n        )\n        super(SplitLunch, self).__init__(**init_kwargs)\n        self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n        self.financial_partner: splitwise.Friend = self.get_friend(\n            friend_id=financial_partner_id, email_address=financial_partner_email\n        )\n        self.financial_group = financial_partner_group_id\n        self.last_check: Optional[datetime.datetime] = None\n        self.lunchable = (\n            LunchMoney(access_token=lunch_money_access_token)\n            if lunchable_client is None\n            else lunchable_client\n        )\n        self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n        self.splitwise_tag = self._none_tag.model_copy()\n        self.splitlunch_tag = self._none_tag.model_copy()\n        self.splitlunch_import_tag = self._none_tag.model_copy()\n        self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n        self._get_splitwise_tags()\n        self.earliest_start_date = datetime.date(1812, 1, 1)\n        today = datetime.date.today()\n        self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n        self.splitwise_asset = self._get_splitwise_asset()\n        self.reimbursement_category = self._get_reimbursement_category()\n\n    def __repr__(self) -> str:\n        \"\"\"\n        String Representation\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return f\"<Splitwise: {self.current_user.email}>\"\n\n    @classmethod\n    def _split_amount(cls, amount: float, splits: int) -> Tuple[float, ...]:\n        \"\"\"\n        Split a money amount into fair shares\n\n        Parameters\n        ----------\n        amount: float\n        splits: int\n\n        Returns\n        -------\n        Tuple[float]\n        \"\"\"\n        try:\n            assert amount == round(amount, 2)\n        except AssertionError as ae:\n            raise SplitLunchError(\n                f\"{amount} caused an error, you must provide a real \" \"spending amount.\"\n            ) from ae\n        equal_shares = round(amount, 2) / splits\n        remainder_dollars = floor(equal_shares)\n        remainder_cents = floor((equal_shares - remainder_dollars) * 100) / 100\n        remainder_left = round(\n            (equal_shares - remainder_dollars - remainder_cents) * splits * 100, 0\n        )\n        owed_amount = remainder_dollars + remainder_cents\n        return_amounts = [owed_amount for _ in range(splits)]\n        for i in range(int(remainder_left)):\n            return_amounts[i] += 0.010\n        shuffle(return_amounts)\n        return tuple([round(item, 2) for item in return_amounts])\n\n    @classmethod\n    def split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n        \"\"\"\n        Split a Transaction into Two\n\n        Split a bill into a tuple of two amounts (and take care\n        of the extra penny if needed)\n\n        Parameters\n        ----------\n        amount: A Currency amount (no more precise than cents)\n\n        Returns\n        -------\n        tuple\n            A tuple is returned with each participant's amount\n        \"\"\"\n        amounts_due = cls._split_amount(amount=amount, splits=2)\n        return amounts_due\n\n    def create_self_paid_expense(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        primary_user_owes, financial_partner_owes = self.split_a_transaction(\n            amount=amount\n        )\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=primary_user_owes)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=financial_partner_owes)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def create_expense_on_behalf_of_partner(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n        This expense will be completely owed by the partner and maked as reimbursed.\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=0.00)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=amount)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def get_friend(\n        self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n    ) -> Optional[splitwise.Friend]:\n        \"\"\"\n        Retrieve a Financial Partner by Email Address\n\n        Parameters\n        ----------\n        email_address: str\n            Email Address of Friend's user in Splitwise\n        friend_id: Optional[int]\n            Splitwise friend ID. Notice the friend ID in the following\n            URL: https://secure.splitwise.com/#/friends/12345678\n\n        Returns\n        -------\n        Optional[splitwise.Friend]\n        \"\"\"\n        friend_list: List[splitwise.Friend] = self.getFriends()\n        if len(friend_list) == 1:\n            return friend_list[0]\n        for friend in friend_list:\n            if friend_id is not None and friend.id == friend_id:\n                return friend\n            elif (\n                email_address is not None\n                and friend.email.lower() == email_address.lower()\n            ):\n                return friend\n        return None\n\n    def get_expenses(\n        self,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        group_id: Optional[int] = None,\n        friendship_id: Optional[int] = None,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        updated_after: Optional[datetime.datetime] = None,\n        updated_before: Optional[datetime.datetime] = None,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Get Splitwise Expenses\n\n        Parameters\n        ----------\n        offset: Optional[int]\n            Number of expenses to be skipped\n        limit: Optional[int]\n            Number of expenses to be returned\n        group_id: Optional[int]\n            GroupID of the expenses\n        friendship_id: Optional[int]\n            FriendshipID of the expenses\n        dated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses later that this date\n        dated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses earlier than this date\n        updated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated after this date\n        updated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated before this date\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        expenses = self.getExpenses(\n            offset=offset,\n            limit=limit,\n            group_id=group_id,\n            friendship_id=friendship_id,\n            dated_after=dated_after,\n            dated_before=dated_before,\n            updated_after=updated_after,\n            updated_before=updated_before,\n        )\n        pydantic_expenses = [\n            self.splitwise_to_pydantic(expense) for expense in expenses\n        ]\n        return pydantic_expenses\n\n    @classmethod\n    def _get_splitwise_init_kwargs(\n        cls,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Get the Splitwise Kwargs\n\n        Parameters\n        ----------\n        consumer_key: Optional[str]\n        consumer_secret: Optional[str]\n        api_key: Optional[str]\n        \"\"\"\n        if consumer_key is None:\n            consumer_key = getenv(\"SPLITWISE_CONSUMER_KEY\")\n        if consumer_secret is None:\n            consumer_secret = getenv(\"SPLITWISE_CONSUMER_SECRET\")\n        if api_key is None:\n            api_key = getenv(\"SPLITWISE_API_KEY\", None)\n        init_kwargs = {\n            \"consumer_key\": consumer_key,\n            \"consumer_secret\": consumer_secret,\n            \"api_key\": api_key,\n        }\n        if consumer_key is None or consumer_secret is None or api_key is None:\n            error_message = (\n                dedent(\n                    \"\"\"\n            You must set your Splitwise credentials explicitly or by assigning\n            the `SPLITWISE_CONSUMER_KEY`, `SPLITWISE_CONSUMER_SECRET`, and the\n            `SPLITWISE_API_KEY`environment variables\n            \"\"\"\n                )\n                .replace(\"\\n\", \" \")\n                .replace(\"  \", \" \")\n            )\n            logger.error(error_message)\n            raise SplitLunchError(error_message)\n        return init_kwargs\n\n    def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n        \"\"\"\n        Convert Splitwise Object to Pydantic\n\n        Parameters\n        ----------\n        expense: splitwise.Expense\n\n        Returns\n        -------\n        SplitLunchExpense\n        \"\"\"\n        financial_impact, self_paid = _get_splitwise_impact(\n            expense=expense, current_user_id=self.current_user.id\n        )\n        expense = SplitLunchExpense(\n            splitwise_id=expense.id,\n            original_amount=expense.cost,\n            financial_impact=financial_impact,\n            self_paid=self_paid,\n            description=expense.description,\n            category=expense.category.name,\n            details=expense.details,\n            payment=expense.payment,\n            date=expense.date,\n            users=[user.id for user in expense.users],\n            created_at=expense.created_at,\n            updated_at=expense.updated_at,\n            deleted_at=expense.deleted_at,\n            deleted=True if expense.deleted_at is not None else False,\n        )\n        return expense\n\n    def _get_splitwise_asset(self) -> Optional[AssetsObject]:\n        \"\"\"\n        Get the Splitwise asset\n\n        Parse a user's Lunch Money accounts and return the manually managed\n        Splitwise account asset object\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        assets = self.lunchable.get_assets()\n        splitwise_assets = []\n        for asset in assets:\n            if (\n                asset.institution_name is not None\n                and \"splitwise\" in asset.institution_name.lower()\n            ):\n                splitwise_assets.append(asset)\n        if len(splitwise_assets) == 0:\n            return None\n        elif len(splitwise_assets) > 1:\n            raise SplitLunchError(\n                \"SplitLunch requires an manually managed Splitwise asset. \"\n                \"Make sure you have a single account where 'Splitwise' \"\n                \"is in the asset's `Institution Name`.\"\n            )\n        else:\n            return splitwise_assets[0]\n\n    def _get_reimbursement_category(self) -> Optional[CategoriesObject]:\n        \"\"\"\n        Get the Reimbusement Category\n\n        Parse a user's Lunch Money categories and return the Reimbursement\n        category\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        categories = self.lunchable.get_categories()\n        reimbursement_list = []\n        for category in categories:\n            if \"reimbursement\" == category.name.strip().lower():\n                reimbursement_list.append(category)\n        if len(reimbursement_list) != 1:\n            return None\n        return reimbursement_list[0]\n\n    def _get_splitwise_tags(self) -> None:\n        \"\"\"\n        Get Lunch Money Tags to Interact with\n\n        Returns\n        -------\n        Dict[str, int]\n        \"\"\"\n        all_tags = self.lunchable.get_tags()\n        for tag in all_tags:\n            if tag.name.lower() == SplitLunchConfig.splitlunch_tag.lower():\n                self.splitlunch_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitwise_tag.lower():\n                self.splitwise_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitlunch_import_tag.lower():\n                self.splitlunch_import_tag = tag\n            elif (\n                tag.name.lower()\n                == SplitLunchConfig.splitlunch_direct_import_tag.lower()\n            ):\n                self.splitlunch_direct_import_tag = tag\n\n    def _raise_nonexistent_tag_error(self, tags: List[str]) -> None:\n        \"\"\"\n        Raise a warning for specific SplitLunch Tags\n\n        tags: List[str]\n            A list of tags to raise the error for\n        \"\"\"\n        if (\n            self.splitlunch_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitwise_tag == self._none_tag\n            and SplitLunchConfig.splitwise_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitwise_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_import_tag}` tag is required. \"\n                f\"This tag is used for creating Splitwise transactions directly from \"\n                f\"Lunch Money transactions. These transactions will be split in half,\"\n                f\"and have one half marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_direct_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_direct_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_direct_import_tag}` tag is \"\n                \"required. This tag is used for creating Splitwise transactions \"\n                \"directly from Lunch Money transactions. These transactions will \"\n                \"be completely owed by your financial partner.\"\n            )\n            raise SplitLunchError(error_message)\n\n    def get_splitlunch_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitlunch\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def get_splitlunch_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitlunch_direct_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(\n            tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n        )\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_direct_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitwise_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitwise\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n        \"\"\"\n        Operate on `SplitLunch` tagged transactions\n\n        Split all transactions with the `SplitLunch` tag in half. One of these\n        new splits will be recategorized to `Reimbursement`. Both new splits will receive\n        the `Splitwise` tag without any preexisting tags.\n        \"\"\"\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        split_transaction_ids = []\n        tagged_objects = self.get_splitlunch_tagged_transactions()\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n            # Generate the First Split\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=transaction.notes,\n                amount=amount_1,\n            )\n            # Generate the second split as a copy, change some properties\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = amount_2\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                \"Splitting transaction: %s -> (%s, %s)\",\n                transaction.amount,\n                amount_1,\n                amount_2,\n            )\n\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            # Tag each of the new transactions generated\n            split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags if transaction.tags is not None else []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag is not None\n                    and tag.name.lower() != self.splitlunch_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return split_transaction_ids\n\n    def make_splitlunch_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchImport` tagged transactions\n\n        Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n        One of these new splits will be recategorized to `Reimbursement`. Both new splits\n        will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n        reapplied.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_self_paid_expense(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=notes,\n                # financial_impact for self-paid transactions will be negative\n                amount=round(\n                    transaction.amount - abs(new_transaction.financial_impact), 2\n                ),\n            )\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = abs(new_transaction.financial_impact)\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                f\"Transaction split by Splitwise: {transaction.amount} -> \"\n                f\"({split_object.amount}, {reimbursement_object.amount})\"\n            )\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": reimbursement_object.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": update_response[\"updated\"],\n                \"split\": update_response[\"split\"],\n            }\n            update_responses.append(formatted_update_response)\n            # Tag each of the new transactions generated\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags or []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return update_responses\n\n    def make_splitlunch_direct_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchDirectImport` tagged transactions\n\n        Send a transaction to Splitwise and then mark the transaction under the\n        `Reimbursement` category. The sum of the transaction will be completely owed\n        by the financial partner.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_expense_on_behalf_of_partner(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            existing_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in existing_tags\n                if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n                tags.append(self.splitwise_tag.name)\n            update = TransactionUpdateObject(\n                category_id=self.reimbursement_category.id, tags=tags, notes=notes\n            )\n            response = self.lunchable.update_transaction(\n                transaction_id=transaction.id, transaction=update\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": transaction.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": response[\"updated\"],\n                \"split\": None,\n            }\n            update_responses.append(formatted_update_response)\n        return update_responses\n\n    def splitwise_to_lunchmoney(\n        self,\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[int]:\n        \"\"\"\n        Ingest Splitwise Expenses into Lunch Money\n\n        This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n        a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n        payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[int]\n            New Lunch Money transaction IDs\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        batch = []\n        new_transaction_ids = []\n        filtered_expenses = self.filter_relevant_splitwise_expenses(\n            expenses=expenses,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        for splitwise_transaction in filtered_expenses:\n            new_lunchmoney_transaction = TransactionInsertObject(\n                date=splitwise_transaction.date.astimezone(tzlocal()),\n                payee=splitwise_transaction.description,\n                amount=splitwise_transaction.financial_impact,\n                asset_id=self.splitwise_asset.id,\n                external_id=splitwise_transaction.splitwise_id,\n            )\n            batch.append(new_lunchmoney_transaction)\n            if len(batch) == 10:\n                new_ids = self.lunchable.insert_transactions(\n                    transactions=batch, apply_rules=True\n                )\n                new_transaction_ids += new_ids\n                batch = []\n        if len(batch) > 0:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n        return new_transaction_ids\n\n    @staticmethod\n    def filter_relevant_splitwise_expenses(\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Filter Expenses in Splitwise into relevant expenses.\n\n        This filtering action is important to understand when seeing why not\n        all transactions from Splitwise end up flowing into Lunch Money.\n\n        1) It filters out deleted expenses\n\n        2) It filters out expenses with a financial impact of 0, implying that the user was not\n        involved in the expense.\n\n        3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n        `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        filtered_expenses = []\n        for splitwise_transaction in expenses:\n            if all(\n                [\n                    splitwise_transaction.deleted is False,\n                    splitwise_transaction.financial_impact != 0.00,\n                    allow_self_paid or (splitwise_transaction.self_paid is False),\n                    allow_payments or (splitwise_transaction.payment is False),\n                ]\n            ):\n                filtered_expenses.append(splitwise_transaction)\n\n        return filtered_expenses\n\n    def get_splitwise_balance(self) -> float:\n        \"\"\"\n        Get the net balance in Splitwise\n\n        Returns\n        -------\n        float\n        \"\"\"\n        groups = self.getGroups()\n        total_balance = 0.00\n        for group in groups:\n            for debt in group.simplified_debts:\n                if debt.fromUser == self.current_user.id:\n                    total_balance -= float(debt.amount)\n                elif debt.toUser == self.current_user.id:\n                    total_balance += float(debt.amount)\n        return total_balance\n\n    def update_splitwise_balance(self) -> AssetsObject:\n        \"\"\"\n        Get and update the Splitwise Asset in Lunch Money\n\n        Returns\n        -------\n        AssetsObject\n            Updated  balance\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        balance = self.get_splitwise_balance()\n        if balance != self.splitwise_asset.balance:\n            updated_asset = self.lunchable.update_asset(\n                asset_id=self.splitwise_asset.id, balance=balance\n            )\n            self.splitwise_asset = updated_asset\n        return self.splitwise_asset\n\n    _deleted_payee = \"[DELETED FROM SPLITWISE]\"\n\n    def get_new_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n    ) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n        \"\"\"\n        Get Splitwise Transactions that don't exist in Lunch Money\n\n        Also return deleted transaction from LunchMoney\n\n        Returns\n        -------\n        Tuple[List[SplitLunchExpense], List[TransactionObject]]\n            New and Deleted Transactions\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        splitlunch_expenses = self.lunchable.get_transactions(\n            asset_id=self.splitwise_asset.id,\n            start_date=datetime.datetime(1800, 1, 1),\n            end_date=datetime.datetime(2300, 12, 31),\n        )\n        splitlunch_ids = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        splitwise_expenses = self.get_expenses(\n            limit=0,\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n        new_ids = splitwise_ids.difference(splitlunch_ids)\n        new_expenses = [\n            expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n        ]\n        deleted_transactions = self.get_deleted_transactions(\n            splitlunch_expenses=splitlunch_expenses,\n            splitwise_transactions=splitwise_expenses,\n        )\n        return new_expenses, deleted_transactions\n\n    def get_deleted_transactions(\n        self,\n        splitlunch_expenses: List[TransactionObject],\n        splitwise_transactions: List[SplitLunchExpense],\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n        Set these transactions to $0.00 and Make a Note\n\n        Parameters\n        ----------\n        splitlunch_expenses: List[TransactionObject]\n        splitwise_transactions: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        existing_transactions = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        deleted_ids = {\n            item.splitwise_id for item in splitwise_transactions if item.deleted is True\n        }\n        untethered_transactions = deleted_ids.intersection(existing_transactions)\n        transactions_to_delete = [\n            tran\n            for tran in splitlunch_expenses\n            if tran.external_id is not None\n            and int(tran.external_id) in untethered_transactions\n            and tran.payee != self._deleted_payee\n        ]\n        return transactions_to_delete\n\n    def refresh_splitwise_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Import New Splitwise Transactions to Lunch Money\n\n        This function get's all transactions from Splitwise, all transactions from\n        your Lunch Money Splitwise account and compares the two.\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        new_transactions, deleted_transactions = self.get_new_transactions(\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        self.splitwise_to_lunchmoney(\n            expenses=new_transactions,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        splitwise_asset = self.update_splitwise_balance()\n        self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n        return {\n            \"balance\": splitwise_asset.balance,\n            \"new\": new_transactions,\n            \"deleted\": deleted_transactions,\n        }\n\n    def handle_deleted_transactions(\n        self,\n        deleted_transactions: List[TransactionObject],\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n        Parameters\n        ----------\n        deleted_transactions: List[TransactionObject]\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        updated_transactions = []\n        for transaction in deleted_transactions:\n            update = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                transaction=TransactionUpdateObject(\n                    amount=0.00,\n                    payee=self._deleted_payee,\n                    notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n                ),\n            )\n            updated_transactions.append(update)\n        return updated_transactions\n\n    def _raise_financial_partner_error(self) -> None:\n        \"\"\"\n        Raise Errors for Financial Partners\n        \"\"\"\n        if self.financial_partner is None:\n            raise SplitLunchError(\n                \"You must designate a financial partner in Splitwise. \"\n                \"This can be done with the partner's Splitwise User ID # \"\n                \"or their email address.\"\n            )\n\n    def _raise_splitwise_asset_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"You must create an asset (aka Account) in Lunch Money with \"\n            \"`Splitwise` in the name. There should only be one account \"\n            \"like this.\"\n        )\n\n    def _raise_category_reimbursement_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"SplitLunch requires a reimbursement Category. \"\n            \"Make sure you have a category entitled `Reimbursement`. \"\n            \"This category will be excluded from budgeting.\"\n            \"Half of split transactions will be created with \"\n            \"this category.\"\n        )\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.__init__","title":"__init__(lunch_money_access_token=None, financial_partner_id=None, financial_partner_email=None, financial_partner_group_id=None, consumer_key=None, consumer_secret=None, api_key=None, lunchable_client=None)","text":"

Initialize the Parent Class with some additional properties

Parameters:

Name Type Description Default financial_partner_id Optional[int]

Splitwise User ID of financial partner

None financial_partner_email Optional[str]

Splitwise linked email address of financial partner

None financial_partner_group_id Optional[int]

Splitwise Group ID for financial partner transactions

None consumer_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_KEY environment variable

None consumer_secret Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_SECRET environment variable

None api_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_API_KEY environment variable.

None lunch_money_access_token Optional[str]

Lunch Money Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable if not provided.

None lunchable_client Optional[LunchMoney]

Instantiated LunchMoney object to use as internal client. One will be created using environment variables otherwise.

None Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __init__(\n    self,\n    lunch_money_access_token: Optional[str] = None,\n    financial_partner_id: Optional[int] = None,\n    financial_partner_email: Optional[str] = None,\n    financial_partner_group_id: Optional[int] = None,\n    consumer_key: Optional[str] = None,\n    consumer_secret: Optional[str] = None,\n    api_key: Optional[str] = None,\n    lunchable_client: Optional[LunchMoney] = None,\n):\n    \"\"\"\n    Initialize the Parent Class with some additional properties\n\n    Parameters\n    ----------\n    financial_partner_id: Optional[int]\n        Splitwise User ID of financial partner\n    financial_partner_email: Optional[str]\n        Splitwise linked email address of financial partner\n    financial_partner_group_id: Optional[int]\n        Splitwise Group ID for financial partner transactions\n    consumer_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n        variable\n    consumer_secret: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n        environment variable\n    api_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n        variable.\n    lunch_money_access_token: Optional[str]\n        Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable if not provided.\n    lunchable_client: LunchMoney\n        Instantiated LunchMoney object to use as internal client. One will\n        be created using environment variables otherwise.\n    \"\"\"\n    init_kwargs = self._get_splitwise_init_kwargs(\n        consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n    )\n    super(SplitLunch, self).__init__(**init_kwargs)\n    self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n    self.financial_partner: splitwise.Friend = self.get_friend(\n        friend_id=financial_partner_id, email_address=financial_partner_email\n    )\n    self.financial_group = financial_partner_group_id\n    self.last_check: Optional[datetime.datetime] = None\n    self.lunchable = (\n        LunchMoney(access_token=lunch_money_access_token)\n        if lunchable_client is None\n        else lunchable_client\n    )\n    self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n    self.splitwise_tag = self._none_tag.model_copy()\n    self.splitlunch_tag = self._none_tag.model_copy()\n    self.splitlunch_import_tag = self._none_tag.model_copy()\n    self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n    self._get_splitwise_tags()\n    self.earliest_start_date = datetime.date(1812, 1, 1)\n    today = datetime.date.today()\n    self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n    self.splitwise_asset = self._get_splitwise_asset()\n    self.reimbursement_category = self._get_reimbursement_category()\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __repr__(self) -> str:\n    \"\"\"\n    String Representation\n\n    Returns\n    -------\n    str\n    \"\"\"\n    return f\"<Splitwise: {self.current_user.email}>\"\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.create_expense_on_behalf_of_partner","title":"create_expense_on_behalf_of_partner(amount, description, date)","text":"

Create and Submit a Splitwise Expense on behalf of your financial partner.

This expense will be completely owed by the partner and maked as reimbursed.

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_expense_on_behalf_of_partner(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n    This expense will be completely owed by the partner and maked as reimbursed.\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=0.00)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=amount)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.create_self_paid_expense","title":"create_self_paid_expense(amount, description, date)","text":"

Create and Submit a Splitwise Expense

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_self_paid_expense(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    primary_user_owes, financial_partner_owes = self.split_a_transaction(\n        amount=amount\n    )\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=primary_user_owes)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=financial_partner_owes)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.filter_relevant_splitwise_expenses","title":"filter_relevant_splitwise_expenses(expenses, allow_self_paid=False, allow_payments=False) staticmethod","text":"

Filter Expenses in Splitwise into relevant expenses.

This filtering action is important to understand when seeing why not all transactions from Splitwise end up flowing into Lunch Money.

1) It filters out deleted expenses

2) It filters out expenses with a financial impact of 0, implying that the user was not involved in the expense.

3) If the --allow-self-paid flag is not provided, it filters out self-paid expenses. A self-paid expense is an expense in Splitwise where you originated the payment. This is excluded because it is assumed that these transactions will have already been imported via a different account.

4) If the --allow-payments flag is not provided, it filters out payments. Payments are excluded because it is assumed that these transactions will have already been imported via a different account.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@staticmethod\ndef filter_relevant_splitwise_expenses(\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Filter Expenses in Splitwise into relevant expenses.\n\n    This filtering action is important to understand when seeing why not\n    all transactions from Splitwise end up flowing into Lunch Money.\n\n    1) It filters out deleted expenses\n\n    2) It filters out expenses with a financial impact of 0, implying that the user was not\n    involved in the expense.\n\n    3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n    `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    filtered_expenses = []\n    for splitwise_transaction in expenses:\n        if all(\n            [\n                splitwise_transaction.deleted is False,\n                splitwise_transaction.financial_impact != 0.00,\n                allow_self_paid or (splitwise_transaction.self_paid is False),\n                allow_payments or (splitwise_transaction.payment is False),\n            ]\n        ):\n            filtered_expenses.append(splitwise_transaction)\n\n    return filtered_expenses\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_deleted_transactions","title":"get_deleted_transactions(splitlunch_expenses, splitwise_transactions)","text":"

Get Splitwise Transactions that exist in Lunch Money but have since been deleted

Set these transactions to $0.00 and Make a Note

Parameters:

Name Type Description Default splitlunch_expenses List[TransactionObject] required splitwise_transactions List[SplitLunchExpense] required

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_deleted_transactions(\n    self,\n    splitlunch_expenses: List[TransactionObject],\n    splitwise_transactions: List[SplitLunchExpense],\n) -> List[TransactionObject]:\n    \"\"\"\n    Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n    Set these transactions to $0.00 and Make a Note\n\n    Parameters\n    ----------\n    splitlunch_expenses: List[TransactionObject]\n    splitwise_transactions: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    existing_transactions = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    deleted_ids = {\n        item.splitwise_id for item in splitwise_transactions if item.deleted is True\n    }\n    untethered_transactions = deleted_ids.intersection(existing_transactions)\n    transactions_to_delete = [\n        tran\n        for tran in splitlunch_expenses\n        if tran.external_id is not None\n        and int(tran.external_id) in untethered_transactions\n        and tran.payee != self._deleted_payee\n    ]\n    return transactions_to_delete\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_expenses","title":"get_expenses(offset=None, limit=None, group_id=None, friendship_id=None, dated_after=None, dated_before=None, updated_after=None, updated_before=None)","text":"

Get Splitwise Expenses

Parameters:

Name Type Description Default offset Optional[int]

Number of expenses to be skipped

None limit Optional[int]

Number of expenses to be returned

None group_id Optional[int]

GroupID of the expenses

None friendship_id Optional[int]

FriendshipID of the expenses

None dated_after Optional[datetime]

ISO 8601 Date time. Return expenses later that this date

None dated_before Optional[datetime]

ISO 8601 Date time. Return expenses earlier than this date

None updated_after Optional[datetime]

ISO 8601 Date time. Return expenses updated after this date

None updated_before Optional[datetime]

ISO 8601 Date time. Return expenses updated before this date

None

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_expenses(\n    self,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    group_id: Optional[int] = None,\n    friendship_id: Optional[int] = None,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    updated_after: Optional[datetime.datetime] = None,\n    updated_before: Optional[datetime.datetime] = None,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Get Splitwise Expenses\n\n    Parameters\n    ----------\n    offset: Optional[int]\n        Number of expenses to be skipped\n    limit: Optional[int]\n        Number of expenses to be returned\n    group_id: Optional[int]\n        GroupID of the expenses\n    friendship_id: Optional[int]\n        FriendshipID of the expenses\n    dated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses later that this date\n    dated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses earlier than this date\n    updated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated after this date\n    updated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated before this date\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    expenses = self.getExpenses(\n        offset=offset,\n        limit=limit,\n        group_id=group_id,\n        friendship_id=friendship_id,\n        dated_after=dated_after,\n        dated_before=dated_before,\n        updated_after=updated_after,\n        updated_before=updated_before,\n    )\n    pydantic_expenses = [\n        self.splitwise_to_pydantic(expense) for expense in expenses\n    ]\n    return pydantic_expenses\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_friend","title":"get_friend(email_address=None, friend_id=None)","text":"

Retrieve a Financial Partner by Email Address

Parameters:

Name Type Description Default email_address Optional[str]

Email Address of Friend's user in Splitwise

None friend_id Optional[int]

Splitwise friend ID. Notice the friend ID in the following URL: https://secure.splitwise.com/#/friends/12345678

None

Returns:

Type Description Optional[Friend] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_friend(\n    self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n) -> Optional[splitwise.Friend]:\n    \"\"\"\n    Retrieve a Financial Partner by Email Address\n\n    Parameters\n    ----------\n    email_address: str\n        Email Address of Friend's user in Splitwise\n    friend_id: Optional[int]\n        Splitwise friend ID. Notice the friend ID in the following\n        URL: https://secure.splitwise.com/#/friends/12345678\n\n    Returns\n    -------\n    Optional[splitwise.Friend]\n    \"\"\"\n    friend_list: List[splitwise.Friend] = self.getFriends()\n    if len(friend_list) == 1:\n        return friend_list[0]\n    for friend in friend_list:\n        if friend_id is not None and friend.id == friend_id:\n            return friend\n        elif (\n            email_address is not None\n            and friend.email.lower() == email_address.lower()\n        ):\n            return friend\n    return None\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_new_transactions","title":"get_new_transactions(dated_after=None, dated_before=None)","text":"

Get Splitwise Transactions that don't exist in Lunch Money

Also return deleted transaction from LunchMoney

Returns:

Type Description Tuple[List[SplitLunchExpense], List[TransactionObject]]

New and Deleted Transactions

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_new_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n    \"\"\"\n    Get Splitwise Transactions that don't exist in Lunch Money\n\n    Also return deleted transaction from LunchMoney\n\n    Returns\n    -------\n    Tuple[List[SplitLunchExpense], List[TransactionObject]]\n        New and Deleted Transactions\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    splitlunch_expenses = self.lunchable.get_transactions(\n        asset_id=self.splitwise_asset.id,\n        start_date=datetime.datetime(1800, 1, 1),\n        end_date=datetime.datetime(2300, 12, 31),\n    )\n    splitlunch_ids = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    splitwise_expenses = self.get_expenses(\n        limit=0,\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n    new_ids = splitwise_ids.difference(splitlunch_ids)\n    new_expenses = [\n        expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n    ]\n    deleted_transactions = self.get_deleted_transactions(\n        splitlunch_expenses=splitlunch_expenses,\n        splitwise_transactions=splitwise_expenses,\n    )\n    return new_expenses, deleted_transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitlunch_direct_import_tagged_transactions","title":"get_splitlunch_direct_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchDirectImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_direct_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(\n        tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n    )\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_direct_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitlunch_import_tagged_transactions","title":"get_splitlunch_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitlunch_tagged_transactions","title":"get_splitlunch_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitlunch\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitlunch\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitwise_balance","title":"get_splitwise_balance()","text":"

Get the net balance in Splitwise

Returns:

Type Description float Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_balance(self) -> float:\n    \"\"\"\n    Get the net balance in Splitwise\n\n    Returns\n    -------\n    float\n    \"\"\"\n    groups = self.getGroups()\n    total_balance = 0.00\n    for group in groups:\n        for debt in group.simplified_debts:\n            if debt.fromUser == self.current_user.id:\n                total_balance -= float(debt.amount)\n            elif debt.toUser == self.current_user.id:\n                total_balance += float(debt.amount)\n    return total_balance\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitwise_tagged_transactions","title":"get_splitwise_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitwise\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitwise\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.handle_deleted_transactions","title":"handle_deleted_transactions(deleted_transactions)","text":"

Update Transactions That Exist in Splitwise, but have been deleted in Splitwise

Parameters:

Name Type Description Default deleted_transactions List[TransactionObject] required

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def handle_deleted_transactions(\n    self,\n    deleted_transactions: List[TransactionObject],\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n    Parameters\n    ----------\n    deleted_transactions: List[TransactionObject]\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    updated_transactions = []\n    for transaction in deleted_transactions:\n        update = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            transaction=TransactionUpdateObject(\n                amount=0.00,\n                payee=self._deleted_payee,\n                notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n            ),\n        )\n        updated_transactions.append(update)\n    return updated_transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.make_splitlunch","title":"make_splitlunch(tag_transactions=False)","text":"

Operate on SplitLunch tagged transactions

Split all transactions with the SplitLunch tag in half. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without any preexisting tags.

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n    \"\"\"\n    Operate on `SplitLunch` tagged transactions\n\n    Split all transactions with the `SplitLunch` tag in half. One of these\n    new splits will be recategorized to `Reimbursement`. Both new splits will receive\n    the `Splitwise` tag without any preexisting tags.\n    \"\"\"\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    split_transaction_ids = []\n    tagged_objects = self.get_splitlunch_tagged_transactions()\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n        # Generate the First Split\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=transaction.notes,\n            amount=amount_1,\n        )\n        # Generate the second split as a copy, change some properties\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = amount_2\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            \"Splitting transaction: %s -> (%s, %s)\",\n            transaction.amount,\n            amount_1,\n            amount_2,\n        )\n\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        # Tag each of the new transactions generated\n        split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags if transaction.tags is not None else []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag is not None\n                and tag.name.lower() != self.splitlunch_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return split_transaction_ids\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.make_splitlunch_direct_import","title":"make_splitlunch_direct_import(tag_transactions=False)","text":"

Operate on SplitLunchDirectImport tagged transactions

Send a transaction to Splitwise and then mark the transaction under the Reimbursement category. The sum of the transaction will be completely owed by the financial partner.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_direct_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchDirectImport` tagged transactions\n\n    Send a transaction to Splitwise and then mark the transaction under the\n    `Reimbursement` category. The sum of the transaction will be completely owed\n    by the financial partner.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_expense_on_behalf_of_partner(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        existing_tags = transaction.tags or []\n        tags = [\n            tag.name\n            for tag in existing_tags\n            if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n        ]\n        if self.splitwise_tag.name not in tags and tag_transactions is True:\n            self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n            tags.append(self.splitwise_tag.name)\n        update = TransactionUpdateObject(\n            category_id=self.reimbursement_category.id, tags=tags, notes=notes\n        )\n        response = self.lunchable.update_transaction(\n            transaction_id=transaction.id, transaction=update\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": transaction.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": response[\"updated\"],\n            \"split\": None,\n        }\n        update_responses.append(formatted_update_response)\n    return update_responses\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.make_splitlunch_import","title":"make_splitlunch_import(tag_transactions=False)","text":"

Operate on SplitLunchImport tagged transactions

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without the SplitLunchImport tag. Any other tags will be reapplied.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchImport` tagged transactions\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Both new splits\n    will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n    reapplied.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_self_paid_expense(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=notes,\n            # financial_impact for self-paid transactions will be negative\n            amount=round(\n                transaction.amount - abs(new_transaction.financial_impact), 2\n            ),\n        )\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = abs(new_transaction.financial_impact)\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            f\"Transaction split by Splitwise: {transaction.amount} -> \"\n            f\"({split_object.amount}, {reimbursement_object.amount})\"\n        )\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": reimbursement_object.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": update_response[\"updated\"],\n            \"split\": update_response[\"split\"],\n        }\n        update_responses.append(formatted_update_response)\n        # Tag each of the new transactions generated\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return update_responses\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.refresh_splitwise_transactions","title":"refresh_splitwise_transactions(dated_after=None, dated_before=None, allow_self_paid=False, allow_payments=False)","text":"

Import New Splitwise Transactions to Lunch Money

This function get's all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two.

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def refresh_splitwise_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> Dict[str, Any]:\n    \"\"\"\n    Import New Splitwise Transactions to Lunch Money\n\n    This function get's all transactions from Splitwise, all transactions from\n    your Lunch Money Splitwise account and compares the two.\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    new_transactions, deleted_transactions = self.get_new_transactions(\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    self.splitwise_to_lunchmoney(\n        expenses=new_transactions,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    splitwise_asset = self.update_splitwise_balance()\n    self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n    return {\n        \"balance\": splitwise_asset.balance,\n        \"new\": new_transactions,\n        \"deleted\": deleted_transactions,\n    }\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.split_a_transaction","title":"split_a_transaction(amount) classmethod","text":"

Split a Transaction into Two

Split a bill into a tuple of two amounts (and take care of the extra penny if needed)

Parameters:

Name Type Description Default amount Union[float, int] required

Returns:

Type Description tuple

A tuple is returned with each participant's amount

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@classmethod\ndef split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n    \"\"\"\n    Split a Transaction into Two\n\n    Split a bill into a tuple of two amounts (and take care\n    of the extra penny if needed)\n\n    Parameters\n    ----------\n    amount: A Currency amount (no more precise than cents)\n\n    Returns\n    -------\n    tuple\n        A tuple is returned with each participant's amount\n    \"\"\"\n    amounts_due = cls._split_amount(amount=amount, splits=2)\n    return amounts_due\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.splitwise_to_lunchmoney","title":"splitwise_to_lunchmoney(expenses, allow_self_paid=False, allow_payments=False)","text":"

Ingest Splitwise Expenses into Lunch Money

This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default, payments and self-paid transactions are also ineligible. Otherwise it will be ignored.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[int]

New Lunch Money transaction IDs

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_lunchmoney(\n    self,\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[int]:\n    \"\"\"\n    Ingest Splitwise Expenses into Lunch Money\n\n    This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n    a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n    payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[int]\n        New Lunch Money transaction IDs\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    batch = []\n    new_transaction_ids = []\n    filtered_expenses = self.filter_relevant_splitwise_expenses(\n        expenses=expenses,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    for splitwise_transaction in filtered_expenses:\n        new_lunchmoney_transaction = TransactionInsertObject(\n            date=splitwise_transaction.date.astimezone(tzlocal()),\n            payee=splitwise_transaction.description,\n            amount=splitwise_transaction.financial_impact,\n            asset_id=self.splitwise_asset.id,\n            external_id=splitwise_transaction.splitwise_id,\n        )\n        batch.append(new_lunchmoney_transaction)\n        if len(batch) == 10:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n            batch = []\n    if len(batch) > 0:\n        new_ids = self.lunchable.insert_transactions(\n            transactions=batch, apply_rules=True\n        )\n        new_transaction_ids += new_ids\n    return new_transaction_ids\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.splitwise_to_pydantic","title":"splitwise_to_pydantic(expense)","text":"

Convert Splitwise Object to Pydantic

Parameters:

Name Type Description Default expense Expense required

Returns:

Type Description SplitLunchExpense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n    \"\"\"\n    Convert Splitwise Object to Pydantic\n\n    Parameters\n    ----------\n    expense: splitwise.Expense\n\n    Returns\n    -------\n    SplitLunchExpense\n    \"\"\"\n    financial_impact, self_paid = _get_splitwise_impact(\n        expense=expense, current_user_id=self.current_user.id\n    )\n    expense = SplitLunchExpense(\n        splitwise_id=expense.id,\n        original_amount=expense.cost,\n        financial_impact=financial_impact,\n        self_paid=self_paid,\n        description=expense.description,\n        category=expense.category.name,\n        details=expense.details,\n        payment=expense.payment,\n        date=expense.date,\n        users=[user.id for user in expense.users],\n        created_at=expense.created_at,\n        updated_at=expense.updated_at,\n        deleted_at=expense.deleted_at,\n        deleted=True if expense.deleted_at is not None else False,\n    )\n    return expense\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.update_splitwise_balance","title":"update_splitwise_balance()","text":"

Get and update the Splitwise Asset in Lunch Money

Returns:

Type Description AssetsObject

Updated balance

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def update_splitwise_balance(self) -> AssetsObject:\n    \"\"\"\n    Get and update the Splitwise Asset in Lunch Money\n\n    Returns\n    -------\n    AssetsObject\n        Updated  balance\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    balance = self.get_splitwise_balance()\n    if balance != self.splitwise_asset.balance:\n        updated_asset = self.lunchable.update_asset(\n            asset_id=self.splitwise_asset.id, balance=balance\n        )\n        self.splitwise_asset = updated_asset\n    return self.splitwise_asset\n
"},{"location":"reference/plugins/splitlunch/models/","title":"models","text":"

SplitLunch Data Models

"},{"location":"reference/plugins/splitlunch/models/#lunchable.plugins.splitlunch.models.SplitLunchExpense","title":"SplitLunchExpense","text":"

Bases: LunchableModel

SplitLunch Object for Splitwise Expenses

Parameters:

Name Type Description Default splitwise_id int required original_amount float required self_paid bool required financial_impact float required description str required category str required details str | None None payment bool required date datetime required users List[int] required created_at datetime required updated_at datetime required deleted_at datetime | None None deleted bool required Source code in lunchable/plugins/splitlunch/models.py
class SplitLunchExpense(LunchableModel):\n    \"\"\"\n    SplitLunch Object for Splitwise Expenses\n    \"\"\"\n\n    splitwise_id: int\n    original_amount: float\n    self_paid: bool\n    financial_impact: float\n    description: str\n    category: str\n    details: Optional[str] = None\n    payment: bool\n    date: datetime.datetime\n    users: List[int]\n    created_at: datetime.datetime\n    updated_at: datetime.datetime\n    deleted_at: Optional[datetime.datetime] = None\n    deleted: bool\n
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home \ud83c\udfe0","text":"lunchable

lunchable is a Python Client for the Lunch Money Developer API. It's built on top of pydantic and httpx, it offers an intuitive API, a simple CLI, complete coverage of all endpoints, and plugins to other external services.

"},{"location":"#installation","title":"Installation","text":"
pip install lunchable\n
"},{"location":"#usage","title":"Usage","text":"
from __future__ import annotations\n\nfrom typing import Any\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions()\n\nfirst_transaction: TransactionObject = transactions[0]\ntransaction_as_dict: dict[str, Any] = first_transaction.model_dump()\n
export LUNCHMONEY_ACCESS_TOKEN=\"xxxxxxxxxxx\"\nlunchable transactions get --limit 5\nlunchable http -X GET https://dev.lunchmoney.app/v1/assets\n
"},{"location":"cli/","title":"lunchable CLI","text":"
lunchable --help\n
Usage: lunchable [OPTIONS] COMMAND [ARGS]...\n\n  Interactions with Lunch Money via lunchable \ud83c\udf71\n\nOptions:\n  --version             Show the version and exit.\n  --access-token TEXT   LunchMoney Developer API Access Token\n  --debug / --no-debug  Enable extra debugging output\n  --help                Show this message and exit.\n\nCommands:\n  http          Interact with the LunchMoney API\n  plugins       Interact with Lunchable Plugins\n  transactions  Interact with Lunch Money transactions\n
"},{"location":"cli/#lunchable","title":"lunchable","text":"

Interactions with Lunch Money via lunchable \ud83c\udf71

Usage:

lunchable [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --version boolean Show the version and exit. False --access-token text LunchMoney Developer API Access Token None --debug / --no-debug boolean Enable extra debugging output False --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-http","title":"lunchable http","text":"

Interact with the LunchMoney API

lunchable http /v1/transactions

Usage:

lunchable http [OPTIONS] URL\n

Options:

Name Type Description Default -X, --request text Specify request command to use GET -d, --data text HTTP POST data None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins","title":"lunchable plugins","text":"

Interact with Lunchable Plugins

Usage:

lunchable plugins [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-plugins-primelunch","title":"lunchable plugins primelunch","text":"

PrimeLunch CLI - Syncing LunchMoney with Amazon

Usage:

lunchable plugins primelunch [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-plugins-primelunch-run","title":"lunchable plugins primelunch run","text":"

Run the PrimeLunch Update Process

Usage:

lunchable plugins primelunch run [OPTIONS]\n

Options:

Name Type Description Default -f, --file path File Path of the Amazon Export _required -w, --window integer Allowable time window between Amazon transaction date and credit card transaction date 7 -a, --all boolean Whether to skip the confirmation step and simply update all matched transactions False -t, --token text LunchMoney Access Token - defaults to the LUNCHMONEY_ACCESS_TOKEN environment variable None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-pushlunch","title":"lunchable plugins pushlunch","text":"

Push Notifications for Lunch Money: PushLunch \ud83d\udcf2

Usage:

lunchable plugins pushlunch [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-plugins-pushlunch-notify","title":"lunchable plugins pushlunch notify","text":"

Send a Notification for each Uncleared Transaction

Usage:

lunchable plugins pushlunch notify [OPTIONS]\n

Options:

Name Type Description Default --continuous boolean Whether to continuously check for more uncleared transactions, waiting a fixed amount in between checks. False --interval text Sleep Interval in Between Tries - only applies if continuous is set. Defaults to 60 (minutes). Cannot be less than 5 (minutes) None --user-key text Pushover User Key. Defaults to PUSHOVER_USER_KEY env var None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch","title":"lunchable plugins splitlunch","text":"

Splitwise Plugin for lunchable, SplitLunch \ud83d\udcb2\ud83c\udf71

Usage:

lunchable plugins splitlunch [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-plugins-splitlunch-expenses","title":"lunchable plugins splitlunch expenses","text":"

Retrieve Splitwise Expenses

Usage:

lunchable plugins splitlunch expenses [OPTIONS]\n

Options:

Name Type Description Default --limit text Limit the amount of Results. 0 returns everything. None --offset text Number of expenses to be skipped None --limit text Number of expenses to be returned None --group-id text GroupID of the expenses None --friendship-id text FriendshipID of the expenses None --dated-after text ISO 8601 Date time. Return expenses later that this date None --dated-before text ISO 8601 Date time. Return expenses earlier than this date None --updated-after text ISO 8601 Date time. Return expenses updated after this date None --updated-before text ISO 8601 Date time. Return expenses updated before this date None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-refresh","title":"lunchable plugins splitlunch refresh","text":"

Import New Splitwise Transactions to Lunch Money and

This function gets all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two. This also updates the account balance.

Usage:

lunchable plugins splitlunch refresh [OPTIONS]\n

Options:

Name Type Description Default --dated-after text ISO 8601 Date time. Return expenses later that this date None --dated-before text ISO 8601 Date time. Return expenses earlier than this date None --allow-self-paid / --no-allow-self-paid boolean Allow self-paid expenses to be imported (filtered out by default). False --allow-payments / --no-allow-payments boolean Allow payments to be imported (filtered out by default). False --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-splitlunch","title":"lunchable plugins splitlunch splitlunch","text":"

Split all SplitLunch tagged transactions in half.

One of these new splits will be recategorized to Reimbursement.

Usage:

lunchable plugins splitlunch splitlunch [OPTIONS]\n

Options:

Name Type Description Default --tag-transactions boolean Tag the resulting transactions with a Splitwise tag. False --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-splitlunch-direct-import","title":"lunchable plugins splitlunch splitlunch-direct-import","text":"

Import SplitLunchDirectImport tagged transactions to Splitwise and Split them in Lunch Money

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Any tags will be reapplied.

Usage:

lunchable plugins splitlunch splitlunch-direct-import [OPTIONS]\n

Options:

Name Type Description Default --tag-transactions boolean Tag the resulting transactions with a Splitwise tag. False --financial-partner-id integer Splitwise ID of your financial partner. None --financial-partner-email text Splitwise Email Address of your financial partner. None --financial-partner-group-id integer Splitwise Group ID for financial partner transactions. None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-splitlunch-import","title":"lunchable plugins splitlunch splitlunch-import","text":"

Import SplitLunchImport tagged transactions to Splitwise and Split them in Lunch Money

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Any tags will be reapplied.

Usage:

lunchable plugins splitlunch splitlunch-import [OPTIONS]\n

Options:

Name Type Description Default --tag-transactions boolean Tag the resulting transactions with a Splitwise tag. False --financial-partner-id integer Splitwise ID of your financial partner. None --financial-partner-email text Splitwise Email Address of your financial partner. None --financial-partner-group-id integer Splitwise Group ID for financial partner transactions. None --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-plugins-splitlunch-update-balance","title":"lunchable plugins splitlunch update-balance","text":"

Update the Splitwise Asset Balance

Usage:

lunchable plugins splitlunch update-balance [OPTIONS]\n

Options:

Name Type Description Default --help boolean Show this message and exit. False"},{"location":"cli/#lunchable-transactions","title":"lunchable transactions","text":"

Interact with Lunch Money transactions

Usage:

lunchable transactions [OPTIONS] COMMAND [ARGS]...\n

Options:

Name Type Description Default --help boolean Show this message and exit. False

Subcommands

"},{"location":"cli/#lunchable-transactions-get","title":"lunchable transactions get","text":"

Retrieve Lunch Money Transactions

Usage:

lunchable transactions get [OPTIONS]\n

Options:

Name Type Description Default --start-date text Denotes the beginning of the time period to fetch transactions for. Defaultsto beginning of current month. Required if end_date exists. Format: YYYY-MM-DD. None --end-date text Denotes the end of the time period you'd like to get transactions for. Defaults to end of current month. Required if start_date exists.Format: YYYY-MM-DD. None --tag-id text Filter by tag. Only accepts IDs, not names. None --recurring-id text Filter by recurring expense None --plaid-account-id text Filter by Plaid account None --category-id text Filter by category. Will also match category groups. None --asset-id text Filter by asset None --group-id text Filter by group_id (if the transaction is part of a specific group) None --is-group text Filter by group (returns transaction groups) None --status text Filter by status (Can be cleared or uncleared. For recurring transactions, use recurring) None --offset text Sets the offset for the records returned None --limit text Sets the maximum number of records to return. Note: The server will not respond with any indication that there are more records to be returned. Please check the response length to determine if you should make another call with an offset to fetch more transactions. None --debit-as-negative text Pass in true if you\u2019d like expenses to be returned as negative amounts and credits as positive amounts. Defaults to false. None --pending boolean Pass in true if you\u2019d like to include imported transactions with a pending status. None --help boolean Show this message and exit. False"},{"location":"contributing/","title":"Contributing","text":""},{"location":"contributing/#environment-setup","title":"Environment Setup","text":"

pipx

This documentaion uses pipx to install and manage non-project command line tools like hatch and pre-commit. If you don't already have pipx installed, make sure to see their documentation. If you prefer not to use pipx, you can use pip instead.

  1. Install hatch

    pipx install hatch\n

    pre-commit

    Hatch will attempt to set up pre-commit hooks for you using pre-commit. If you don't already, make sure to install pre-commit as well: pipx install pre-commit

  2. Build the Virtual Environment

    hatch env create\n
  3. If you need to, you can link a hatch virtual environment to your IDE. They can be located by name with the env find command:

    hatch env find test\n
  4. Activate the Virtual Environment

    hatch shell\n
"},{"location":"contributing/#using-hatch","title":"Using Hatch","text":""},{"location":"contributing/#hatch-cheat-sheet","title":"Hatch Cheat Sheet","text":"Command Description Command Notes Run Tests hatch run cov Runs tests with pytest and coverage Run Formatting hatch run lint:fmt Runs ruff code formatter Run Linting hatch run lint:all Runs ruff and mypy linters / type checkers Run Type Checking hatch run lint:typing Runs mypy type checker Serve the Documentation hatch run docs:serve Serve the documentation using MkDocs Run the pre-commit Hooks hatch run lint:precommit Runs the pre-commit hooks on all files"},{"location":"contributing/#hatch-explanation","title":"Hatch Explanation","text":"

Hatch is a Python package manager. It's most basic use is as a standardized build-system. However, hatch also has some extra features which this project takes advantage of. These features include virtual environment management and the organization of common scripts like linting and testing. All the operations in hatch take place in one of its managed virtual environments.

Hatch has a variety of environments, to see them simply ask hatch:

hatch CLIOutput
hatch env show\n
                                   Standalone                                   \n\u250f\u2533\u2501\u2501\u2501\u2533\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2513\n\u2503\u2503 \u2026 \u2503\u2503 Dependenc\u2026 \u2503 Environment variables                                 \u2503 \u2026 \u2503\n\u2521\u2547\u2501\u2501\u2501\u2547\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2529\n\u2502\u2502 \u2026 \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u251c\u253c\u2500\u2500\u2500\u253c\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524\n\u2502\u2502 \u2026 \u2502\u2502 griffe-fi\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 markdown-\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 markdown-\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 mkdocs     \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-au\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-cl\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-ge\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-li\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-ma\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocs-se\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocstri\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 mkdocstri\u2026 \u2502                                                       \u2502   \u2502\n\u2502\u2502   \u2502\u2502 pymdown-e\u2026 \u2502                                                       \u2502   \u2502\n\u251c\u253c\u2500\u2500\u2500\u253c\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524\n\u2502\u2502 \u2026 \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u251c\u253c\u2500\u2500\u2500\u253c\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524\n\u2502\u2502 \u2026 \u2502\u2502 mypy>=1.6\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 pydantic   \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 ruff~=0.1\u2026 \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502            \u2502                                                       \u2502 \u2026 \u2502\n\u251c\u253c\u2500\u2500\u2500\u253c\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524\n\u2502\u2502 \u2026 \u2502\u2502 coverage[\u2026 \u2502 LUNCHMONEY_ACCESS_TOKEN=LUNCHMONEY_ACCESS_TOKEN_PLAC\u2026 \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 pytest     \u2502 PUSHOVER_USER_KEY=PUSHOVER_USER_KEY_PLACEHOLDER       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 pytest-mo\u2026 \u2502 SENSITIVE_REQUEST_STRINGS=SENSITIVE_REQUEST_STRINGS_\u2026 \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502 vcrpy~=5.\u2026 \u2502 SPLITWISE_API_KEY=SPLITWISE_API_KEY_PLACEHOLDER       \u2502 \u2026 \u2502\n\u2502\u2502   \u2502\u2502            \u2502 SPLITWISE_CONSUMER_KEY=SPLITWISE_CONSUMER_KEY_PLACEH\u2026 \u2502   \u2502\n\u2502\u2502   \u2502\u2502            \u2502 SPLITWISE_CONSUMER_SECRET=SPLITWISE_CONSUMER_SECRET_\u2026 \u2502   \u2502\n\u2514\u2534\u2500\u2500\u2500\u2534\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2518\n                                    Matrices                                    \n\u250f\u2533\u2501\u2501\u2533\u2501\u2533\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2513\n\u2503\u2503  \u2503 \u2503\u2503 Depend\u2026 \u2503 Environment variables                                      \u2503\u2503\n\u2521\u2547\u2501\u2501\u2547\u2501\u2547\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2529\n\u2502\u2502  \u2502 \u2502\u2502 covera\u2026 \u2502 LUNCHMONEY_ACCESS_TOKEN={env:LUNCHMONEY_ACCESS_TOKEN:LUNC\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502 pytest  \u2502 PUSHOVER_USER_KEY={env:PUSHOVER_USER_KEY:PUSHOVER_USER_KE\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502 pytest\u2026 \u2502 SENSITIVE_REQUEST_STRINGS={env:SENSITIVE_REQUEST_STRINGS:\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502 vcrpy~\u2026 \u2502 SPLITWISE_API_KEY={env:SPLITWISE_API_KEY:SPLITWISE_API_KE\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502         \u2502 SPLITWISE_CONSUMER_KEY={env:SPLITWISE_CONSUMER_KEY:SPLITW\u2026 \u2502\u2502\n\u2502\u2502  \u2502 \u2502\u2502         \u2502 SPLITWISE_CONSUMER_SECRET={env:SPLITWISE_CONSUMER_SECRET:\u2026 \u2502\u2502\n\u2514\u2534\u2500\u2500\u2534\u2500\u2534\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2518\n

That above command will tell you that there are five environments that you can use:

Each of these environments has a set of commands that you can run. To see the commands for a specific environment, run:

hatch CLIOutput
hatch env show default\n
                  Standalone                  \n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503 Name    \u2503 Type        \u2503 Features \u2503 Scripts \u2503\n\u2521\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2529\n\u2502 default \u2502 pip-compile \u2502 all      \u2502 cov     \u2502\n\u2502         \u2502             \u2502          \u2502 test    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n

Here we can see that the default environment has the following commands:

The one that we're interested in is cov, which will run the tests for the project.

hatch run cov\n

Since cov is in the default environment, we can run it without specifying the environment. However, to run the serve command in the docs environment, we need to specify the environment:

hatch run docs:serve\n

You can see what scripts are available using the env show command

hatch CLIOutput
hatch env show docs\n
                             Standalone                             \n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503 Name \u2503 Type        \u2503 Features \u2503 Dependencies         \u2503 Scripts   \u2503\n\u2521\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2529\n\u2502 docs \u2502 pip-compile \u2502 all      \u2502 griffe-fieldz        \u2502 build     \u2502\n\u2502      \u2502             \u2502          \u2502 markdown-callouts    \u2502 gh-deploy \u2502\n\u2502      \u2502             \u2502          \u2502 markdown-exec        \u2502 serve     \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs               \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-autorefs      \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-click         \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-gen-files     \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-literate-nav  \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-material      \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocs-section-index \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocstrings         \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 mkdocstrings-python  \u2502           \u2502\n\u2502      \u2502             \u2502          \u2502 pymdown-extensions   \u2502           \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n
"},{"location":"contributing/#committing-code","title":"Committing Code","text":"

This project uses pre-commit to run a set of checks on the code before it is committed. The pre-commit hooks are installed by hatch automatically when you run it for the first time.

This project uses semantic-versioning standards, managed by semantic-release. Releases for this project are handled entirely by CI/CD via pull requests being merged into the main branch. Contributions follow the gitmoji standards with conventional commits.

While you can denote other changes on your commit messages with gitmoji, the following commit message emoji prefixes are the only ones to trigger new releases:

Emoji Shortcode Description Semver \ud83d\udca5 :boom: Introduce breaking changes. Major \u2728 :sparkles: Introduce new features. Minor \ud83d\udc1b :bug: Fix a bug. Patch \ud83d\ude91 :ambulance: Critical hotfix. Patch \ud83d\udd12 :lock: Fix security issues. Patch

Most features can be squash merged into a single commit on a pull-request. When merging multiple commits, they will be summarized into a single release.

If you're working on a new feature, your commit message might look like:

\u2728 New Feature Description\n

Bug fix commits would look like this:

\ud83d\udc1b Bug Fix Description\n

If you're working on a feature that introduces breaking changes, your commit message might look like:

\ud83d\udca5 Breaking Change Description\n

Other commits that don't trigger a release might look like this:

\ud83d\udcdd Documentation Update Description\n\ud83d\udc77 CI/CD Update Description\n\ud83e\uddea Testing Changes Description\n\ud83d\ude9a Moving/Renaming Description\n\u2b06\ufe0f Dependency Upgrade Description\n
"},{"location":"contributing/#pre-releases","title":"Pre-Releases","text":"

semantic-release supports pre-releases. To trigger a pre-release, you would merge your pull request into an alpha or beta branch.

"},{"location":"contributing/#specific-release-versions","title":"Specific Release Versions","text":"

In some cases you need more advanced control around what kind of release you need to create. If you need to release a specific version, you can do so by creating a new branch with the version number as the branch name. For example, if the current version is 2.3.2, but you need to release a fix as 1.2.5, you would create a branch named 1.2.x and merge your changes into that branch.

See the semantic-release documentation for more information about branch based releases and other advanced release cases.

"},{"location":"interacting/","title":"LunchMoney","text":"

The LunchMoney client is the main entrypoint for interacting with the Lunch Money API. It defaults to inheriting the LUNCHMONEY_ACCESS_TOKEN environment variable, but can be created with an explicit access_token parameter.

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxxxxxx\")\n
"},{"location":"interacting/#methods","title":"Methods","text":"HTTP Verb Name Description GET get_assets Get Manually Managed Assets GET get_budgets Get Monthly Budgets GET get_categories Get Spending categories GET get_category Get single category GET get_crypto Get Crypto Assets GET get_plaid_accounts Get Plaid Synced Assets GET get_recurring_expenses Get Recurring Expenses GET get_tags Get Spending Tags GET get_transaction Get a Transaction by ID GET get_transactions Get Transactions Using Criteria GET get_user Get Personal User Details POST insert_asset Create a single (manually-managed) asset POST insert_category Create a Spending Category POST insert_category_group Create a Spending Category Group POST insert_into_category_group Add to a Category Group POST insert_transaction_group Create a Transaction Group of Two or More Transactions POST insert_transactions Create One or Many Lunch Money Transactions POST unsplit_transactions Unsplit Transactions PUT upsert_budget Upsert a Budget for a Category and Date PUT update_asset Update a Single Asset PUT update_category Update a single category PUT update_crypto Update a Manual Crypto Asset PUT update_transaction Update a Transaction DELETE remove_budget Unset an Existing Budget for a Particular Category in a Particular Month DELETE remove_category Delete a single category DELETE remove_category_force Forcefully delete a single category DELETE remove_transaction_group Delete a Transaction Group"},{"location":"interacting/#low-level-methods","title":"Low Level Methods","text":"Name Description request Make an HTTP request arequest Make an async HTTP request process_response Process a Lunch Money response and raise any errors make_request Make an HTTP request and process its response amake_request Make an async HTTP request and process its response"},{"location":"interacting/#attributes","title":"Attributes","text":"Name Description async_session Authenticated Async HTTPX Client session Authenticated HTTPX Client"},{"location":"interacting/#class-documentation","title":"Class Documentation","text":"

Lunch Money Python Client.

This class facilitates with connections to the Lunch Money Developer API. Authenticate with an Access Token. If an access token isn't provided one will attempt to be inherited from a LUNCHMONEY_ACCESS_TOKEN environment variable.

Examples:

from __future__ import annotations\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions()\n
"},{"location":"interacting/#lunchable.LunchMoney.async_session","title":"async_session: httpx.AsyncClient cached property","text":"

Lunch Money HTTPX Async Client

Returns:

Type Description AsyncClient"},{"location":"interacting/#lunchable.LunchMoney.session","title":"session: httpx.Client cached property","text":"

Lunch Money HTTPX Client

Returns:

Type Description Client"},{"location":"interacting/#lunchable.LunchMoney.Methods","title":"Methods","text":"

HTTP Request Method Enumerations: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

"},{"location":"interacting/#lunchable.LunchMoney.__init__","title":"__init__(access_token=None)","text":"

Initialize a Lunch Money object with an Access Token.

Tries to inherit from the Environment if one isn't provided

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None"},{"location":"interacting/#lunchable.LunchMoney.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str"},{"location":"interacting/#lunchable.LunchMoney.amake_request","title":"amake_request(method, url_path, params=None, payload=None, **kwargs) async","text":"

Make an async HTTP request and process its response

This method is a wrapper around :meth:.LunchMoney.arequest that also processes the response and checks for any errors.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url_path Union[list[Union[str, int]], str, int]

URL components to make into a URL

required payload Optional[Any]

Data to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Any"},{"location":"interacting/#lunchable.LunchMoney.arequest","title":"arequest(method, url, *, content=None, data=None, json=None, params=None, **kwargs) async","text":"

Make an async HTTP request

This is a simple method :class:.LunchMoney exposes to make HTTP requests. It has the benefit of using an existing httpx.Client as well as as out of the box auth headers that are used to connect to the Lunch Money Developer API.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url Union[URL, str]

URL for the new Request object.

required content Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]

Content to send in the body of the Request.

None data Optional[Mapping[str, Any]]

Dictionary, list of tuples, bytes, or file-like object to send in the body of the Request.

None json Optional[Any]

A JSON serializable Python object to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Response"},{"location":"interacting/#lunchable.LunchMoney.get_assets","title":"get_assets()","text":"

Get Manually Managed Assets

Get a list of all manually-managed assets associated with the user's account.

(https://lunchmoney.dev/#assets-object)

Returns:

Type Description List[AssetsObject]"},{"location":"interacting/#lunchable.LunchMoney.get_budgets","title":"get_budgets(start_date, end_date)","text":"

Get Monthly Budgets

Get full details on the budgets for all categories between a certain time period. The budgeted and spending amounts will be an aggregate across this time period. (https://lunchmoney.dev/#plaid-accounts-object)

Returns:

Type Description List[BudgetObject]"},{"location":"interacting/#lunchable.LunchMoney.get_categories","title":"get_categories()","text":"

Get Spending categories

Use this endpoint to get a list of all categories associated with the user's account. https://lunchmoney.dev/#get-all-categories

Returns:

Type Description List[CategoriesObject]"},{"location":"interacting/#lunchable.LunchMoney.get_category","title":"get_category(category_id)","text":"

Get single category

Use this endpoint to get hydrated details on a single category. Note that if this category is part of a category group, its properties (is_income, exclude_from_budget, exclude_from_totals) will inherit from the category group.

https://lunchmoney.dev/#get-single-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description CategoriesObject"},{"location":"interacting/#lunchable.LunchMoney.get_crypto","title":"get_crypto()","text":"

Get Crypto Assets

Use this endpoint to get a list of all cryptocurrency assets associated with the user's account. Both crypto balances from synced and manual accounts will be returned.

https://lunchmoney.dev/#get-all-crypto

Returns:

Type Description List[CryptoObject]"},{"location":"interacting/#lunchable.LunchMoney.get_plaid_accounts","title":"get_plaid_accounts()","text":"

Get Plaid Synced Assets

Get a list of all synced Plaid accounts associated with the user's account.

Plaid Accounts are individual bank accounts that you have linked to Lunch Money via Plaid. You may link one bank but one bank might contain 4 accounts. Each of these accounts is a Plaid Account. (https://lunchmoney.dev/#plaid-accounts-object)

Returns:

Type Description List[PlaidAccountObject]"},{"location":"interacting/#lunchable.LunchMoney.get_recurring_expenses","title":"get_recurring_expenses(start_date=None, debit_as_negative=False)","text":"

Get Recurring Expenses

Retrieve a list of recurring expenses to expect for a specified period.

Every month, a different set of recurring expenses is expected. This is because recurring expenses can be once a year, twice a year, every 4 months, etc.

If a recurring expense is listed as \u201ctwice a month\u201d, then that recurring expense will be returned twice, each with a different billing date based on when the system believes that recurring expense transaction is to be expected. If the recurring expense is listed as \u201conce a week\u201d, then that recurring expense will be returned in this list as many times as there are weeks for the specified month.

In the same vein, if a recurring expense that began last month is set to \u201cEvery 3 months\u201d, then that recurring expense will not show up in the results for this month.

Parameters:

Name Type Description Default start_date Optional[date]

Date to search. By default will return the first day of the current month

None debit_as_negative bool

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts.

False

Returns:

Type Description List[RecurringExpensesObject]"},{"location":"interacting/#lunchable.LunchMoney.get_tags","title":"get_tags()","text":"

Get Spending Tags

Use this endpoint to get a list of all tags associated with the user's account.

https://lunchmoney.dev/#get-all-tags

Returns:

Type Description List[TagsObject]"},{"location":"interacting/#lunchable.LunchMoney.get_transaction","title":"get_transaction(transaction_id)","text":"

Get a Transaction by ID

Parameters:

Name Type Description Default transaction_id int

Lunch Money Transaction ID

required

Returns:

Type Description TransactionObject

Examples:

Retrieve a single transaction by its ID

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction = lunch.get_transaction(transaction_id=1234)\n

The above code returns a TransactionObject with ID # 1234 (assuming it exists)

"},{"location":"interacting/#lunchable.LunchMoney.get_transactions","title":"get_transactions(start_date=None, end_date=None, tag_id=None, recurring_id=None, plaid_account_id=None, category_id=None, asset_id=None, group_id=None, is_group=None, status=None, offset=None, limit=None, debit_as_negative=None, pending=None, params=None)","text":"

Get Transactions Using Criteria

Use this to retrieve all transactions between a date range. Returns list of Transaction objects. If no query parameters are set, this will return transactions for the current calendar month. If either start_date or end_date are datetime.datetime objects, they will be reduced to dates. If a string is provided, it will be attempted to be parsed as YYYY-MM-DD format.

Parameters:

Name Type Description Default start_date Optional[Union[date, datetime, str]]

Denotes the beginning of the time period to fetch transactions for. Defaults to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.

None end_date Optional[Union[date, datetime, str]]

Denotes the end of the time period you'd like to get transactions for. Defaults to end of current month. Required if start_date exists.

None tag_id Optional[int]

Filter by tag. Only accepts IDs, not names.

None recurring_id Optional[int]

Filter by recurring expense

None plaid_account_id Optional[int]

Filter by Plaid account

None category_id Optional[int]

Filter by category. Will also match category groups.

None asset_id Optional[int]

Filter by asset

None group_id Optional[int]

Filter by group_id (if the transaction is part of a specific group)

None is_group Optional[bool]

Filter by group (returns transaction groups)

None status Optional[str]

Filter by status (Can be cleared or uncleared. For recurring transactions, use recurring)

None offset Optional[int]

Sets the offset for the records returned

None limit Optional[int]

Sets the maximum number of records to return. Note: The server will not respond with any indication that there are more records to be returned. Please check the response length to determine if you should make another call with an offset to fetch more transactions.

None debit_as_negative Optional[bool]

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts. Defaults to false.

None pending Optional[bool]

Pass in true if you'd like to include imported transactions with a pending status.

None params Optional[Dict[str, Any]]

Additional Query String Params

None

Returns:

Type Description List[TransactionObject]

A list of transactions

Examples:

Retrieve a list of TransactionObject

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions = lunch.get_transactions(start_date=\"2020-01-01\",\n                                      end_date=\"2020-01-31\")\n
"},{"location":"interacting/#lunchable.LunchMoney.get_user","title":"get_user()","text":"

Get Personal User Details

Use this endpoint to get details on the current user.

https://lunchmoney.dev/#get-user

Returns:

Type Description UserObject"},{"location":"interacting/#lunchable.LunchMoney.insert_asset","title":"insert_asset(type_name, name=None, subtype_name=None, display_name=None, balance=0.0, balance_as_of=None, currency=None, institution_name=None, closed_on=None, exclude_transactions=False)","text":"

Create a single (manually-managed) asset.

Parameters:

Name Type Description Default type_name str

Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation

required name Optional[str]

Max 45 characters

None subtype_name Optional[str]

Max 25 characters

None display_name Optional[str]

Display name of the asset (as set by user)

None balance float

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point! Defaults to 0.00

0.0 balance_as_of Optional[datetime]

Has no effect if balance is not defined. If balance is defined, but balance_as_of is not supplied or is invalid, current timestamp will be used.

None currency Optional[str]

Three-letter lowercase currency in ISO 4217 format. The code sent must exist in our database. Defaults to user's primary currency.

None institution_name Optional[str]

Max 50 characters

None closed_on Optional[date]

The date this asset was closed

None exclude_transactions bool

If true, this asset will not show up as an option for assignment when creating transactions manually. Defaults to False

False

Returns:

Type Description AssetsObject"},{"location":"interacting/#lunchable.LunchMoney.insert_category","title":"insert_category(name, description=None, is_income=False, exclude_from_budget=False, exclude_from_totals=False)","text":"

Create a Spending Category

Use this to create a single category https://lunchmoney.dev/#create-category

Parameters:

Name Type Description Default name str

Name of category. Must be between 1 and 40 characters.

required description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

False exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

False exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

False

Returns:

Type Description int

ID of the newly created category

"},{"location":"interacting/#lunchable.LunchMoney.insert_category_group","title":"insert_category_group(name, description=None, is_income=False, exclude_from_budget=False, exclude_from_totals=False, category_ids=None, new_categories=None)","text":"

Create a Spending Category Group

Use this endpoint to create a single category group https://lunchmoney.dev/#create-category-group

Parameters:

Name Type Description Default name str

Name of category. Must be between 1 and 40 characters.

required description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

False exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

False exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

False category_ids Optional[List[int]]

Array of category_id to include in the category group.

None new_categories Optional[List[str]]

Array of strings representing new categories to create and subsequently include in the category group.

None

Returns:

Type Description int

ID of the newly created category group

"},{"location":"interacting/#lunchable.LunchMoney.insert_into_category_group","title":"insert_into_category_group(category_group_id, category_ids=None, new_categories=None)","text":"

Add to a Category Group

Use this endpoint to add categories (either existing or new) to a single category group

https://lunchmoney.dev/#add-to-category-group

Parameters:

Name Type Description Default category_group_id int

Id of the Lunch Money Category Group

required category_ids Optional[List[int]]

Array of category_id to include in the category group.

None new_categories Optional[List[str]]

Array of strings representing new categories to create and subsequently include in the category group.

None

Returns:

Type Description CategoriesObject"},{"location":"interacting/#lunchable.LunchMoney.insert_transaction_group","title":"insert_transaction_group(date, payee, transactions, category_id=None, notes=None, tags=None)","text":"

Create a Transaction Group of Two or More Transactions

Returns the ID of the newly created transaction group

Parameters:

Name Type Description Default date date

Date for the grouped transaction

required payee str

Payee name for the grouped transaction

required category_id Optional[int]

Category for the grouped transaction

None notes Optional[str]

Notes for the grouped transaction

None tags Optional[List[int]]

Array of tag IDs for the grouped transaction

None transactions List[int]

Array of transaction IDs to be part of the transaction group

required

Returns:

Type Description int"},{"location":"interacting/#lunchable.LunchMoney.insert_transactions","title":"insert_transactions(transactions, apply_rules=False, skip_duplicates=True, debit_as_negative=False, check_for_recurring=False, skip_balance_update=True)","text":"

Create One or Many Lunch Money Transactions

Use this endpoint to insert many transactions at once. Also accepts a single transaction as well. If a TransactionObject is provided it will be converted into a TransactionInsertObject.

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default transactions ListOrSingleTransactionInsertObject

Transactions to insert. Either a single TransactionInsertObject object or a list of them

required apply_rules bool

If true, will apply account's existing rules to the inserted transactions. Defaults to false.

False skip_duplicates bool

If true, the system will automatically dedupe based on transaction date, payee and amount. Note that deduping by external_id will occur regardless of this flag.

True check_for_recurring bool

if true, will check new transactions for occurrences of new monthly expenses. Defaults to false.

False debit_as_negative bool

If true, will assume negative amount values denote expenses and positive amount values denote credits. Defaults to false.

False skip_balance_update bool

If false, will skip updating balance if an asset_id is present for any of the transactions.

True

Returns:

Type Description List[int]

Examples:

Create a new transaction with a TransactionInsertObject

from lunchable import LunchMoney, TransactionInsertObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\n\nnew_transaction = TransactionInsertObject(payee=\"Example Restaurant\",\n                                          amount=120.00,\n                                          notes=\"Saturday Dinner\")\nnew_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n
"},{"location":"interacting/#lunchable.LunchMoney.make_request","title":"make_request(method, url_path, params=None, payload=None, **kwargs)","text":"

Make an HTTP request and process its response

This method is a wrapper around :meth:.LunchMoney.request that also processes the response and checks for any errors.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url_path Union[list[Union[str, int]], str, int]

URL components to make into a URL

required payload Optional[Any]

Data to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Any"},{"location":"interacting/#lunchable.LunchMoney.process_response","title":"process_response(response) classmethod","text":"

Process a Lunch Money response and raise any errors

This includes 200 responses that are actually errors

Parameters:

Name Type Description Default response Response

An HTTPX Response Object

required"},{"location":"interacting/#lunchable.LunchMoney.remove_budget","title":"remove_budget(start_date, category_id)","text":"

Unset an Existing Budget for a Particular Category in a Particular Month

Parameters:

Name Type Description Default start_date date

Start date for the budget period. Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

required category_id int

Unique identifier for the category

required

Returns:

Type Description bool"},{"location":"interacting/#lunchable.LunchMoney.remove_category","title":"remove_category(category_id)","text":"

Delete a single category

Use this endpoint to delete a single category or category group. This will only work if there are no dependencies, such as existing budgets for the category, categorized transactions, categorized recurring items, etc. If there are dependents, this endpoint will return what the dependents are and how many there are.

https://lunchmoney.dev/#delete-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description bool"},{"location":"interacting/#lunchable.LunchMoney.remove_category_force","title":"remove_category_force(category_id)","text":"

Forcefully delete a single category

Use this endpoint to force delete a single category or category group and along with it, disassociate the category from any transactions, recurring items, budgets, etc.

Note: it is best practice to first try the Delete Category endpoint to ensure you don't accidentally delete any data. Disassociation/deletion of the data arising from this endpoint is irreversible!

https://lunchmoney.dev/#force-delete-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description bool"},{"location":"interacting/#lunchable.LunchMoney.remove_transaction_group","title":"remove_transaction_group(transaction_group_id)","text":"

Delete a Transaction Group

Use this method to delete a transaction group. The transactions within the group will not be removed.

Returns the IDs of the transactions that were part of the deleted group

https://lunchmoney.dev/#delete-transaction-group

Parameters:

Name Type Description Default transaction_group_id int

Transaction Group Identifier

required

Returns:

Type Description List[int]"},{"location":"interacting/#lunchable.LunchMoney.request","title":"request(method, url, *, content=None, data=None, json=None, params=None, **kwargs)","text":"

Make an HTTP request

This is a simple method :class:.LunchMoney exposes to make HTTP requests. It has the benefit of using an existing httpx.Client as well as as out of the box auth headers that are used to connect to the Lunch Money Developer API.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url Union[URL, str]

URL for the new Request object.

required content Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]

Content to send in the body of the Request.

None data Optional[Mapping[str, Any]]

Dictionary, list of tuples, bytes, or file-like object to send in the body of the Request.

None json Optional[Any]

A JSON serializable Python object to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Response

Examples:

A recent use of this method was to delete a Tag (which isn't available via the Developer API yet)

import lunchable\n\nlunch = lunchable.LunchMoney()\n\n# Get All the Tags\nall_tags = lunch.get_tags()\n# Get All The Null Tags (a list of 1 or zero)\nnull_tags = [tag for tag in all_tags if tag.name in [None, \"\"]]\n\n# Create a Cookie dictionary from a browser session\ncookies = {\"cookie_keys\": \"cookie_values\"}\ndel lunch.session.headers[\"authorization\"]\n\nfor null_tag in null_tags:\n    # use the httpx.client embedded in the class to make a request with cookies\n    response = lunch.request(\n        method=lunch.Methods.DELETE,\n        url=f\"https://api.lunchmoney.app/tags/{null_tag.id}\",\n        cookies=cookies\n    )\n    # raise an error for 4XX responses\n    response.raise_for_status()\n
"},{"location":"interacting/#lunchable.LunchMoney.unsplit_transactions","title":"unsplit_transactions(parent_ids, remove_parents=False)","text":"

Unsplit Transactions

Use this endpoint to unsplit one or more transactions.

Returns an array of IDs of deleted transactions

https://lunchmoney.dev/#unsplit-transactions

Parameters:

Name Type Description Default parent_ids List[int]

Array of transaction IDs to unsplit. If one transaction is unsplittable, no transaction will be unsplit.

required remove_parents bool

If true, deletes the original parent transaction as well. Note, this is unreversable!

False

Returns:

Type Description List[int]"},{"location":"interacting/#lunchable.LunchMoney.update_asset","title":"update_asset(asset_id, type_name=None, subtype_name=None, name=None, balance=None, balance_as_of=None, currency=None, institution_name=None)","text":"

Update a Single Asset

Parameters:

Name Type Description Default asset_id int

Asset Identifier

required type_name Optional[str]

Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation

None subtype_name Optional[str]

Max 25 characters

None name Optional[str]

Max 45 characters

None balance Optional[float]

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point!

None balance_as_of Optional[datetime]

Has no effect if balance is not defined. If balance is defined, but balance_as_of is not supplied or is invalid, current timestamp will be used.

None currency Optional[str]

Three-letter lowercase currency in ISO 4217 format. The code sent must exist in our database. Defaults to asset's currency.

None institution_name Optional[str]

Max 50 characters

None

Returns:

Type Description AssetsObject"},{"location":"interacting/#lunchable.LunchMoney.update_category","title":"update_category(category_id, name=None, description=None, is_income=None, exclude_from_budget=None, exclude_from_totals=None, group_id=None)","text":"

Update a single category

Use this endpoint to update the properties for a single category or category group

https://lunchmoney.dev/#update-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required name Optional[str]

Name of category. Must be between 1 and 40 characters.

None description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

None exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

None exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

None group_id Optional[int]

For a category, set the group_id to include it in a category group

None

Returns:

Type Description bool"},{"location":"interacting/#lunchable.LunchMoney.update_crypto","title":"update_crypto(crypto_id, name=None, display_name=None, institution_name=None, balance=None, currency=None)","text":"

Update a Manual Crypto Asset

Use this endpoint to update a single manually-managed crypto asset (does not include assets received from syncing with your wallet/exchange/etc). These are denoted by source: manual from the GET call above.

https://lunchmoney.dev/#update-manual-crypto-asset

Parameters:

Name Type Description Default crypto_id int

ID of the crypto asset to update

required name Optional[str]

Official or full name of the account. Max 45 characters

None display_name Optional[str]

Display name for the account. Max 25 characters

None institution_name Optional[str]

Name of provider that holds the account. Max 50 characters

None balance Optional[float]

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point!

None currency Optional[str]

Cryptocurrency that is supported for manual tracking in our database

None

Returns:

Type Description CryptoObject"},{"location":"interacting/#lunchable.LunchMoney.update_transaction","title":"update_transaction(transaction_id, transaction=None, split=None, debit_as_negative=False, skip_balance_update=True)","text":"

Update a Transaction

Use this endpoint to update a single transaction. You may also use this to split an existing transaction. If a TransactionObject is provided it will be converted into a TransactionUpdateObject.

PUT https://dev.lunchmoney.app/v1/transactions/:transaction_id

Parameters:

Name Type Description Default transaction_id int

Lunch Money Transaction ID

required transaction ListOrSingleTransactionUpdateObject

Object to update with

None split Optional[List[TransactionSplitObject]]

Defines the split of a transaction. You may not split an already-split transaction, recurring transaction, or group transaction.

None debit_as_negative bool

If true, will assume negative amount values denote expenses and positive amount values denote credits. Defaults to false.

False skip_balance_update bool

If false, will skip updating balance if an asset_id is present for any of the transactions.

True

Returns:

Type Description Dict[str, Any]

Examples:

Update a transaction with a TransactionUpdateObject

from datetime import datetime\n\nfrom lunchable import LunchMoney, TransactionUpdateObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction_note = f\"Updated on {datetime.now()}\"\nnotes_update = TransactionUpdateObject(notes=transaction_note)\nresponse = lunch.update_transaction(transaction_id=1234,\n                                    transaction=notes_update)\n

Update a TransactionObject with itself

from datetime import datetime, timedelta\n\nfrom lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction = lunch.get_transaction(transaction_id=1234)\n\ntransaction.notes = f\"Updated on {datetime.now()}\"\ntransaction.date = transaction.date + timedelta(days=1)\nresponse = lunch.update_transaction(transaction_id=transaction.id,\n                                    transaction=transaction)\n
"},{"location":"interacting/#lunchable.LunchMoney.upsert_budget","title":"upsert_budget(start_date, category_id, amount, currency=None)","text":"

Upsert a Budget for a Category and Date

Use this endpoint to update an existing budget or insert a new budget for a particular category and date.

Note: Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

If this is a sub-category, the response will include the updated category group's budget. This is because setting a sub-category may also update the category group's overall budget.

https://lunchmoney.dev/#upsert-budget

Parameters:

Name Type Description Default start_date date

Start date for the budget period. Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

required category_id int

Unique identifier for the category

required amount float

Amount for budget

required currency Optional[str]

Currency for the budgeted amount (optional). If empty, will default to your primary currency

None

Returns:

Type Description Optional[Dict[str, Any]]"},{"location":"usage/","title":"Usage","text":""},{"location":"usage/#installation","title":"Installation","text":"

To use lunchable, first install it using pip:

pip install lunchable\n
"},{"location":"usage/#client","title":"Client","text":"

The LunchMoney client is the main entrypoint for interacting with the Lunch Money API. It defaults to inheriting the LUNCHMONEY_ACCESS_TOKEN environment variable, but can be created with an explicit access_token parameter.

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\n

Read more about Interacting with Lunch Money to see what else you can do.

"},{"location":"usage/#transactions","title":"Transactions","text":""},{"location":"usage/#retrieve-a-list-of-transactionobject","title":"Retrieve a list of TransactionObject","text":"
from __future__ import annotations\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions(\n    start_date=\"2020-01-01\",\n    end_date=\"2020-01-31\"\n)\n
"},{"location":"usage/#retrieve-a-single-transaction-transactionobject","title":"Retrieve a single transaction (TransactionObject)","text":"
from lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction: TransactionObject = lunch.get_transaction(transaction_id=1234)\n

The above code returns a TransactionObject with ID # 1234 (assuming it exists)

"},{"location":"usage/#update-a-transaction-with-a-transactionupdateobject","title":"Update a transaction with a TransactionUpdateObject","text":"
from __future__ import annotations\n\nfrom datetime import datetime\nfrom typing import Any\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionUpdateObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction_note = f\"Updated on {datetime.now()}\"\nnotes_update = TransactionUpdateObject(notes=transaction_note)\nresponse: dict[str, Any] = lunch.update_transaction(\n    transaction_id=1234,\n    transaction=notes_update\n)\n
"},{"location":"usage/#update-a-transactionobject-with-itself","title":"Update a TransactionObject with itself","text":"
from datetime import datetime, timedelta\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction: TransactionObject = lunch.get_transaction(transaction_id=1234)\n\ntransaction.notes = f\"Updated on {datetime.now()}\"\ntransaction.date = transaction.date + timedelta(days=1)\nresponse = lunch.update_transaction(\n    transaction_id=transaction.id,\n    transaction=transaction\n)\n
"},{"location":"usage/#create-a-new-transaction-with-a-transactioninsertobject","title":"Create a new transaction with a TransactionInsertObject","text":"

transactions can be a single TransactionInsertObject or a list of TransactionInsertObject.

from lunchable import LunchMoney\nfrom lunchable.models import TransactionInsertObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\n\nnew_transaction = TransactionInsertObject(\n    payee=\"Example Restaurant\",\n    amount=120.00,\n    notes=\"Saturday Dinner\"\n)\nnew_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n
"},{"location":"usage/#use-the-lunchable-cli","title":"Use the Lunchable CLI","text":"
lunchable transactions get --limit 5\n
"},{"location":"usage/#use-the-lunchable-cli-via-docker","title":"Use the Lunchable CLI via Docker","text":"
docker pull juftin/lunchable\n
docker run \\\n    --env LUNCHMONEY_ACCESS_TOKEN=${LUNCHMONEY_ACCESS_TOKEN} \\\n    juftin/lunchable:latest \\\n    lunchable transactions get --limit 5\n
"},{"location":"plugins/","title":"Plugins","text":"

lunchable supports plugins with other, external, services. See below for what's been built already. If you can't find what you're looking for, consider building it yourself and opening a pull-request.

"},{"location":"plugins/#pushlunch-push-notifications-via-pushover","title":"PushLunch: Push Notifications via Pushover","text":""},{"location":"plugins/#splitlunch-splitwise-integration","title":"SplitLunch: Splitwise Integration","text":""},{"location":"plugins/#primelunch-amazon-transaction-updater","title":"PrimeLunch: Amazon Transaction Updater","text":""},{"location":"plugins/primelunch/","title":"PrimeLunch: Amazon Transaction Updater","text":"

PrimeLunch is a command line tool that supports updating Amazon transaction notes with the items from Amazon itself. This tool uses CSVs generated by the Amazon Order History Reporter plugin on Chrome. Once you've gathered your transactions, export them as a CSV and scan them with the tool. You'll be asked which transactions you'd like to update.

The plugin uses the dollar amounts on the CSV export to match Amazon transactions in LunchMoney. When a matching dollar amount is found, PrimeLunch compares the date window between the transactions to determine if they're really a match.

We're using the Amazon Order History Reporter plugin because it gives us some functionality that Amazon doesn't: exporting Amazon transactions as they're grouped on actual credit card transactions.

"},{"location":"plugins/primelunch/#run-via-the-lunchable-cli","title":"Run via the Lunchable CLI","text":"

You can install lunchable with pip or pipx:

pipx install \"lunchable[primelunch]\"\n
pip install \"lunchable[primelunch]\"\n

The below command runs the PrimeLunch update tool:

lunchable plugins primelunch run -f ~/Downloads/amazon_order_history.csv\n

Windows Users

The commands on this documentation correspond to running on a Mac or Linux Machine. If you are a Windows user take note of the following items:

The below command runs the PrimeLunch update tool using a date window of fourteen days instead of the default seven days (these larger windows are especially useful for finding refunds and recurring purchases):

lunchable plugins primelunch run \\\n   --file ~/Downloads/amazon_order_history.csv \\\n   --window 14\n

Update all transactions without going through the confirmation prompt for each one:

lunchable plugins primelunch run \\\n   --file ~/Downloads/amazon_order_history.csv \\\n   --all\n

Provide a LunchMoney API access token manually (PrimeLunch defaults to inheriting from the LUNCHMONEY_ACCESS_TOKEN environment variable):

lunchable plugins primelunch run \\\n   --file ~/Downloads/amazon_order_history.csv \\\n   --token ABCDEFGHIJKLMNOP\n
"},{"location":"plugins/primelunch/#command-line-documentation","title":"Command Line Documentation","text":""},{"location":"plugins/primelunch/#lunchable-plugins-primelunch-run","title":"lunchable plugins primelunch run","text":"

Run the PrimeLunch Update Process

Usage:

lunchable plugins primelunch run [OPTIONS]\n

Options:

Name Type Description Default -f, --file path File Path of the Amazon Export _required -w, --window integer Allowable time window between Amazon transaction date and credit card transaction date 7 -a, --all boolean Whether to skip the confirmation step and simply update all matched transactions False -t, --token text LunchMoney Access Token - defaults to the LUNCHMONEY_ACCESS_TOKEN environment variable None --help boolean Show this message and exit. False"},{"location":"plugins/primelunch/#references","title":"References","text":"

This lunchable plugin was inspired by the original Lunchable Amazon importer at samwelnella/amazon-transactions-to-lunchmoney.

"},{"location":"plugins/pushlunch/","title":"PushLunch: Push Notifications via Pushover","text":"

PushLunch supports Push Notifications via Pushover. Pushover supports iOS and Android Push notifications. To get started just provide your Pushover User Key directly or via the PUSHOVER_USER_KEY environment variable.

"},{"location":"plugins/pushlunch/#run-via-the-lunchable-cli","title":"Run via the Lunchable CLI","text":"

The below command checks for un-reviewed transactions in the current period and sends them as Push Notifications. The --continuous flag tells it to run forever which will only send you a push notification once for each transaction. By default it will check every 60 minutes, but this can be changed using the --interval argument.

lunchable plugins pushlunch notify --continuous\n
"},{"location":"plugins/pushlunch/#run-via-docker","title":"Run via Docker","text":"
docker run --rm \\\n    --env LUNCHMONEY_ACCESS_TOKEN=${LUNCHMONEY_ACCESS_TOKEN} \\\n    --env PUSHOVER_USER_KEY=${PUSHOVER_USER_KEY} \\\n    juftin/lunchable:latest \\\n    lunchable plugins pushlunch notify --continuous\n
"},{"location":"plugins/pushlunch/#run-via-python","title":"Run via Python","text":"
from lunchable.plugins.pushlunch import PushLunch\n

Lunch Money Pushover Notifications via Lunchable

Source code in lunchable/plugins/pushlunch/pushover.py
class PushLunch(LunchableApp):\n    \"\"\"\n    Lunch Money Pushover Notifications via Lunchable\n    \"\"\"\n\n    pushover_endpoint = \"https://api.pushover.net/1/messages.json\"\n\n    def __init__(\n        self,\n        user_key: Optional[str] = None,\n        app_token: Optional[str] = None,\n        lunchmoney_access_token: Optional[str] = None,\n    ):\n        \"\"\"\n        Initialize\n\n        Parameters\n        ----------\n        user_key : Optional[str]\n            Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n            variable if none defined\n        app_token: Optional[str]\n            Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n            variable. If no token available, the official lunchable app token will be provided\n        lunchmoney_access_token: Optional[str]\n            LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable.\n        \"\"\"\n        super().__init__(access_token=lunchmoney_access_token)\n        self.pushover_session = httpx.Client()\n        self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n        _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n        if app_token is None:\n            app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n        token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n        user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n        if user_key in [None, \"\"]:\n            raise PushLunchError(\n                \"You must provide a Pushover User Key or define it with \"\n                \"a `PUSHOVER_USER_KEY` environment variable\"\n            )\n        self._params = {\"user\": user_key, \"token\": token}\n        self.get_latest_cache(\n            include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n        )\n        self.notified_transactions: List[int] = []\n\n    def send_notification(\n        self,\n        message: str,\n        attachment: Optional[object] = None,\n        device: Optional[str] = None,\n        title: Optional[str] = None,\n        url: Optional[str] = None,\n        url_title: Optional[str] = None,\n        priority: Optional[int] = None,\n        sound: Optional[str] = None,\n        timestamp: Optional[str] = None,\n        html: bool = False,\n    ) -> httpx.Response:\n        \"\"\"\n        Send a Pushover Notification\n\n        Parameters\n        ----------\n        message: Optional[str]\n            your message\n        attachment: Optional[object]\n            an image attachment to send with the message; see attachments for more information\n            on how to upload files\n        device: Optional[str]\n            your user's device name to send the message directly to that device, rather than\n            all of the user's devices (multiple devices may be separated by a comma)\n        title: Optional[str]\n            your message's title, otherwise your app's name is used\n        url: Optional[str]\n            a supplementary URL to show with your message\n        url_title: Optional[str]\n            a title for your supplementary URL, otherwise just the URL is shown\n        priority: Optional[int]\n            send as -2 to generate no notification/alert, -1 to always send as a quiet\n            notification, 1 to display as high-priority and bypass the user's quiet hours,\n            or 2 to also require confirmation from the user\n        sound: Optional[str]\n            the name of one of the sounds supported by device clients to override the\n            user's default sound choice\n        timestamp: Optional[str]\n            a Unix timestamp of your message's date and time to display to the user, rather\n            than the time your message is received by our API\n        html: Union[None, 1]\n            Pass 1 if message contains HTML contents\n\n        Returns\n        -------\n        httpx.Response\n        \"\"\"\n        html_param = 1 if html not in [None, False] else None\n        params_dict = {\n            \"message\": message,\n            \"attachment\": attachment,\n            \"device\": device,\n            \"title\": title,\n            \"url\": url,\n            \"url_title\": url_title,\n            \"priority\": priority,\n            \"sound\": sound,\n            \"timestamp\": timestamp,\n            \"html\": html_param,\n        }\n        params: Dict[str, Any] = {\n            key: value for key, value in params_dict.items() if value is not None\n        }\n        params.update(self._params)\n        response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n        response.raise_for_status()\n        return response\n\n    def post_transaction(\n        self, transaction: TransactionObject\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"\n        Post a Lunch Money Transaction as a Pushover Notification\n\n        Assuming the instance of the\n        class hasn't already posted this particular notification\n\n        Parameters\n        ----------\n        transaction: TransactionObject\n\n        Returns\n        -------\n        Dict[str, Any]\n        \"\"\"\n        if transaction.id in self.notified_transactions:\n            return None\n        if transaction.category_id is None:\n            category = \"N/A\"\n        else:\n            category = self.lunch_data.categories[transaction.category_id].name\n        account_id = transaction.plaid_account_id or transaction.asset_id\n        assert account_id is not None\n        account = self.lunch_data.asset_map[account_id]\n        if isinstance(account, AssetsObject):\n            account_name = account.display_name or account.name\n        else:\n            account_name = account.name\n        transaction_formatted = dedent(\n            f\"\"\"\n        <b>Payee:</b> <i>{transaction.payee}</i>\n        <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n        <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n        <b>Category:</b> <i>{category}</i>\n        <b>Account:</b> <i>{account_name}</i>\n        \"\"\"\n        ).strip()\n        if transaction.currency is not None:\n            transaction_formatted += (\n                f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n            )\n        if transaction.status is not None:\n            transaction_formatted += (\n                f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n            )\n        if transaction.notes is not None:\n            note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n            transaction_formatted += f\"\\n{note}\"\n        if transaction.status == \"uncleared\":\n            url = (\n                '<a href=\"https://my.lunchmoney.app/transactions/'\n                f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n                \"<b>Uncleared Transactions from this Period</b></a>\"\n            )\n            transaction_formatted += f\"\\n\\n{url}\"\n\n        response = self.send_notification(\n            message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n        )\n        self.notified_transactions.append(transaction.id)\n        return loads(response.content)\n\n    @classmethod\n    def _format_float(cls, amount: float) -> str:\n        \"\"\"\n        Format Floats to be pleasant and human readable\n\n        Parameters\n        ----------\n        amount: float\n            Float Amount to be converted into a string\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if amount < 0:\n            float_string = f\"$ ({float(amount):,.2f})\".replace(\"-\", \"\")\n        else:\n            float_string = f\"$ {float(amount):,.2f}\"\n        return float_string\n\n    def notify_uncleared_transactions(\n        self, continuous: bool = False, interval: Optional[int] = None\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n        Parameters\n        ----------\n        continuous : bool\n            Whether to continuously check for more uncleared transactions,\n            waiting a fixed amount in between checks.\n        interval: Optional[int]\n            Sleep Interval in Between Tries - only applies if `continuous` is set.\n            Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if interval is None:\n            interval = 60\n        if continuous is True and interval < 5:\n            logger.warning(\n                \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n            )\n            interval = 5\n        if continuous is True:\n            logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n        uncleared_transactions = []\n        continuous_search = True\n\n        while continuous_search is True:\n            found_transactions = len(self.notified_transactions)\n            uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n            for transaction in uncleared_transactions:\n                self.post_transaction(transaction=transaction)\n            if continuous is True:\n                notified = len(self.notified_transactions)\n                new_transactions = notified - found_transactions\n                logger.info(\n                    \"%s new transactions pushed. %s total.\", new_transactions, notified\n                )\n                sleep(interval * 60)\n            else:\n                continuous_search = False\n\n        return uncleared_transactions\n
"},{"location":"plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.__init__","title":"__init__(user_key=None, app_token=None, lunchmoney_access_token=None)","text":"

Initialize

Parameters:

Name Type Description Default user_key Optional[str]

Pushover User Key. Will attempt to inherit from PUSHOVER_USER_KEY environment variable if none defined

None app_token Optional[str]

Pushover app token, will attempt to inherit from PUSHOVER_APP_TOKEN environment variable. If no token available, the official lunchable app token will be provided

None lunchmoney_access_token Optional[str]

LunchMoney Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable.

None Source code in lunchable/plugins/pushlunch/pushover.py
def __init__(\n    self,\n    user_key: Optional[str] = None,\n    app_token: Optional[str] = None,\n    lunchmoney_access_token: Optional[str] = None,\n):\n    \"\"\"\n    Initialize\n\n    Parameters\n    ----------\n    user_key : Optional[str]\n        Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n        variable if none defined\n    app_token: Optional[str]\n        Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n        variable. If no token available, the official lunchable app token will be provided\n    lunchmoney_access_token: Optional[str]\n        LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable.\n    \"\"\"\n    super().__init__(access_token=lunchmoney_access_token)\n    self.pushover_session = httpx.Client()\n    self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n    _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n    if app_token is None:\n        app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n    token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n    user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n    if user_key in [None, \"\"]:\n        raise PushLunchError(\n            \"You must provide a Pushover User Key or define it with \"\n            \"a `PUSHOVER_USER_KEY` environment variable\"\n        )\n    self._params = {\"user\": user_key, \"token\": token}\n    self.get_latest_cache(\n        include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n    )\n    self.notified_transactions: List[int] = []\n
"},{"location":"plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.notify_uncleared_transactions","title":"notify_uncleared_transactions(continuous=False, interval=None)","text":"

Get the Current Period's Uncleared Transactions and Send a Notification for each

Parameters:

Name Type Description Default continuous bool

Whether to continuously check for more uncleared transactions, waiting a fixed amount in between checks.

False interval Optional[int]

Sleep Interval in Between Tries - only applies if continuous is set. Defaults to 60 (minutes). Cannot be less than 5 (minutes)

None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/pushlunch/pushover.py
def notify_uncleared_transactions(\n    self, continuous: bool = False, interval: Optional[int] = None\n) -> List[TransactionObject]:\n    \"\"\"\n    Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n    Parameters\n    ----------\n    continuous : bool\n        Whether to continuously check for more uncleared transactions,\n        waiting a fixed amount in between checks.\n    interval: Optional[int]\n        Sleep Interval in Between Tries - only applies if `continuous` is set.\n        Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if interval is None:\n        interval = 60\n    if continuous is True and interval < 5:\n        logger.warning(\n            \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n        )\n        interval = 5\n    if continuous is True:\n        logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n    uncleared_transactions = []\n    continuous_search = True\n\n    while continuous_search is True:\n        found_transactions = len(self.notified_transactions)\n        uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n        for transaction in uncleared_transactions:\n            self.post_transaction(transaction=transaction)\n        if continuous is True:\n            notified = len(self.notified_transactions)\n            new_transactions = notified - found_transactions\n            logger.info(\n                \"%s new transactions pushed. %s total.\", new_transactions, notified\n            )\n            sleep(interval * 60)\n        else:\n            continuous_search = False\n\n    return uncleared_transactions\n
"},{"location":"plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.post_transaction","title":"post_transaction(transaction)","text":"

Post a Lunch Money Transaction as a Pushover Notification

Assuming the instance of the class hasn't already posted this particular notification

Parameters:

Name Type Description Default transaction TransactionObject required

Returns:

Type Description Dict[str, Any] Source code in lunchable/plugins/pushlunch/pushover.py
def post_transaction(\n    self, transaction: TransactionObject\n) -> Optional[Dict[str, Any]]:\n    \"\"\"\n    Post a Lunch Money Transaction as a Pushover Notification\n\n    Assuming the instance of the\n    class hasn't already posted this particular notification\n\n    Parameters\n    ----------\n    transaction: TransactionObject\n\n    Returns\n    -------\n    Dict[str, Any]\n    \"\"\"\n    if transaction.id in self.notified_transactions:\n        return None\n    if transaction.category_id is None:\n        category = \"N/A\"\n    else:\n        category = self.lunch_data.categories[transaction.category_id].name\n    account_id = transaction.plaid_account_id or transaction.asset_id\n    assert account_id is not None\n    account = self.lunch_data.asset_map[account_id]\n    if isinstance(account, AssetsObject):\n        account_name = account.display_name or account.name\n    else:\n        account_name = account.name\n    transaction_formatted = dedent(\n        f\"\"\"\n    <b>Payee:</b> <i>{transaction.payee}</i>\n    <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n    <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n    <b>Category:</b> <i>{category}</i>\n    <b>Account:</b> <i>{account_name}</i>\n    \"\"\"\n    ).strip()\n    if transaction.currency is not None:\n        transaction_formatted += (\n            f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n        )\n    if transaction.status is not None:\n        transaction_formatted += (\n            f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n        )\n    if transaction.notes is not None:\n        note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n        transaction_formatted += f\"\\n{note}\"\n    if transaction.status == \"uncleared\":\n        url = (\n            '<a href=\"https://my.lunchmoney.app/transactions/'\n            f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n            \"<b>Uncleared Transactions from this Period</b></a>\"\n        )\n        transaction_formatted += f\"\\n\\n{url}\"\n\n    response = self.send_notification(\n        message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n    )\n    self.notified_transactions.append(transaction.id)\n    return loads(response.content)\n
"},{"location":"plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.send_notification","title":"send_notification(message, attachment=None, device=None, title=None, url=None, url_title=None, priority=None, sound=None, timestamp=None, html=False)","text":"

Send a Pushover Notification

Parameters:

Name Type Description Default message str

your message

required attachment Optional[object]

an image attachment to send with the message; see attachments for more information on how to upload files

None device Optional[str]

your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma)

None title Optional[str]

your message's title, otherwise your app's name is used

None url Optional[str]

a supplementary URL to show with your message

None url_title Optional[str]

a title for your supplementary URL, otherwise just the URL is shown

None priority Optional[int]

send as -2 to generate no notification/alert, -1 to always send as a quiet notification, 1 to display as high-priority and bypass the user's quiet hours, or 2 to also require confirmation from the user

None sound Optional[str]

the name of one of the sounds supported by device clients to override the user's default sound choice

None timestamp Optional[str]

a Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by our API

None html bool

Pass 1 if message contains HTML contents

False

Returns:

Type Description Response Source code in lunchable/plugins/pushlunch/pushover.py
def send_notification(\n    self,\n    message: str,\n    attachment: Optional[object] = None,\n    device: Optional[str] = None,\n    title: Optional[str] = None,\n    url: Optional[str] = None,\n    url_title: Optional[str] = None,\n    priority: Optional[int] = None,\n    sound: Optional[str] = None,\n    timestamp: Optional[str] = None,\n    html: bool = False,\n) -> httpx.Response:\n    \"\"\"\n    Send a Pushover Notification\n\n    Parameters\n    ----------\n    message: Optional[str]\n        your message\n    attachment: Optional[object]\n        an image attachment to send with the message; see attachments for more information\n        on how to upload files\n    device: Optional[str]\n        your user's device name to send the message directly to that device, rather than\n        all of the user's devices (multiple devices may be separated by a comma)\n    title: Optional[str]\n        your message's title, otherwise your app's name is used\n    url: Optional[str]\n        a supplementary URL to show with your message\n    url_title: Optional[str]\n        a title for your supplementary URL, otherwise just the URL is shown\n    priority: Optional[int]\n        send as -2 to generate no notification/alert, -1 to always send as a quiet\n        notification, 1 to display as high-priority and bypass the user's quiet hours,\n        or 2 to also require confirmation from the user\n    sound: Optional[str]\n        the name of one of the sounds supported by device clients to override the\n        user's default sound choice\n    timestamp: Optional[str]\n        a Unix timestamp of your message's date and time to display to the user, rather\n        than the time your message is received by our API\n    html: Union[None, 1]\n        Pass 1 if message contains HTML contents\n\n    Returns\n    -------\n    httpx.Response\n    \"\"\"\n    html_param = 1 if html not in [None, False] else None\n    params_dict = {\n        \"message\": message,\n        \"attachment\": attachment,\n        \"device\": device,\n        \"title\": title,\n        \"url\": url,\n        \"url_title\": url_title,\n        \"priority\": priority,\n        \"sound\": sound,\n        \"timestamp\": timestamp,\n        \"html\": html_param,\n    }\n    params: Dict[str, Any] = {\n        key: value for key, value in params_dict.items() if value is not None\n    }\n    params.update(self._params)\n    response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n    response.raise_for_status()\n    return response\n
"},{"location":"plugins/splitlunch/","title":"SplitLunch: Splitwise Integration","text":""},{"location":"plugins/splitlunch/#integrations","title":"Integrations","text":"

This plugin supports different operations, and some of those operations have prerequisites:

"},{"location":"plugins/splitlunch/#auto-importer","title":"Auto Importer","text":"

It supports the auto-importing of Splitwise expenses into Lunch Money transactions. This requires a manual asset exist in your Lunch Money account with \"Splitwise\" in the Name. Expenses that have been deleted or which don't impact you (i.e. are only between other users in your group) are skipped. By default, payments and expenses for which you are recorded as the payer are skipped as well, but these can be overridden by the --allow-payments and --allow-self-paid CLI flags, respectively.

"},{"location":"plugins/splitlunch/#prerequisites","title":"Prerequisites","text":""},{"location":"plugins/splitlunch/#lunchmoney-splitwise","title":"LunchMoney -> Splitwise","text":"

It supports the creation of Splitwise transactions directly from synced Lunch Money accounts. This syncing requires you create a tag called SplitLunchImport. Transactions with this tag will be created in Splitwise with your \"financial partner\". Once transactions are created in Splitwise they will be split in half in Lunch Money. Half of the split will be marked in the Reimbursement category which must be created.

"},{"location":"plugins/splitlunch/#prerequisites_1","title":"Prerequisites","text":""},{"location":"plugins/splitlunch/#splitlunch","title":"SplitLunch","text":"

It supports a workflow where you mark transactions as split (identical to Lunch Money -> Splitwise) without importing them into Splitwise. This syncing requires you create a tag called SplitLunch and a category named Reimbursement

"},{"location":"plugins/splitlunch/#prerequisites_2","title":"Prerequisites","text":""},{"location":"plugins/splitlunch/#lunchmoney-splitwise-without-splitting","title":"LunchMoney -> Splitwise (without splitting)","text":"

It supports the creation of Splitwise transactions directly from synced Lunch Money accounts. This syncing requires you create a tag called SplitLunchDirectImport. Transactions with this tag will be created in Splitwise with the total completely owed by your \"financial partner\". The entire transaction wil then be categorized as Reimbursement without being split.

"},{"location":"plugins/splitlunch/#prerequisites_3","title":"Prerequisites","text":"

Note: Some of the above scenarios allow for tagging of a Splitwise tag on updated transactions. This tag must be created for this functionality to work.

"},{"location":"plugins/splitlunch/#installation","title":"Installation","text":"
pip install lunchable[splitlunch]\n
"},{"location":"plugins/splitlunch/#run-the-splitlunch-plugin-for-the-lunchable-cli","title":"Run the SplitLunch plugin for the Lunchable CLI","text":"
lunchable plugins splitlunch --help\n
"},{"location":"plugins/splitlunch/#run-the-splitlunch-plugin-for-the-lunchable-cli-via-docker","title":"Run the SplitLunch plugin for the Lunchable CLI via Docker","text":"
docker pull juftin/lunchable\n
docker run \\\n    --env LUNCHMONEY_ACCESS_TOKEN=${LUNCHMONEY_ACCESS_TOKEN} \\\n    --env SPLITWISE_CONSUMER_KEY=${SPLITWISE_CONSUMER_KEY} \\\n    --env SPLITWISE_CONSUMER_SECRET=${SPLITWISE_CONSUMER_SECRET} \\\n    --env SPLITWISE_API_KEY=${SPLITWISE_API_KEY} \\\n    juftin/lunchable:latest \\\n    lunchable plugins splitlunch --help\n
"},{"location":"plugins/splitlunch/#run-via-python","title":"Run via Python","text":"
from lunchable.plugins.splitlunch import SplitLunch\n

Lunchable Plugin For Interacting With Splitwise

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
class SplitLunch(splitwise.Splitwise):\n    \"\"\"\n    Lunchable Plugin For Interacting With Splitwise\n    \"\"\"\n\n    def __init__(\n        self,\n        lunch_money_access_token: Optional[str] = None,\n        financial_partner_id: Optional[int] = None,\n        financial_partner_email: Optional[str] = None,\n        financial_partner_group_id: Optional[int] = None,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n        lunchable_client: Optional[LunchMoney] = None,\n    ):\n        \"\"\"\n        Initialize the Parent Class with some additional properties\n\n        Parameters\n        ----------\n        financial_partner_id: Optional[int]\n            Splitwise User ID of financial partner\n        financial_partner_email: Optional[str]\n            Splitwise linked email address of financial partner\n        financial_partner_group_id: Optional[int]\n            Splitwise Group ID for financial partner transactions\n        consumer_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n            variable\n        consumer_secret: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n            environment variable\n        api_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n            variable.\n        lunch_money_access_token: Optional[str]\n            Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable if not provided.\n        lunchable_client: LunchMoney\n            Instantiated LunchMoney object to use as internal client. One will\n            be created using environment variables otherwise.\n        \"\"\"\n        init_kwargs = self._get_splitwise_init_kwargs(\n            consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n        )\n        super(SplitLunch, self).__init__(**init_kwargs)\n        self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n        self.financial_partner: splitwise.Friend = self.get_friend(\n            friend_id=financial_partner_id, email_address=financial_partner_email\n        )\n        self.financial_group = financial_partner_group_id\n        self.last_check: Optional[datetime.datetime] = None\n        self.lunchable = (\n            LunchMoney(access_token=lunch_money_access_token)\n            if lunchable_client is None\n            else lunchable_client\n        )\n        self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n        self.splitwise_tag = self._none_tag.model_copy()\n        self.splitlunch_tag = self._none_tag.model_copy()\n        self.splitlunch_import_tag = self._none_tag.model_copy()\n        self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n        self._get_splitwise_tags()\n        self.earliest_start_date = datetime.date(1812, 1, 1)\n        today = datetime.date.today()\n        self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n        self.splitwise_asset = self._get_splitwise_asset()\n        self.reimbursement_category = self._get_reimbursement_category()\n\n    def __repr__(self) -> str:\n        \"\"\"\n        String Representation\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return f\"<Splitwise: {self.current_user.email}>\"\n\n    @classmethod\n    def _split_amount(cls, amount: float, splits: int) -> Tuple[float, ...]:\n        \"\"\"\n        Split a money amount into fair shares\n\n        Parameters\n        ----------\n        amount: float\n        splits: int\n\n        Returns\n        -------\n        Tuple[float]\n        \"\"\"\n        try:\n            assert amount == round(amount, 2)\n        except AssertionError as ae:\n            raise SplitLunchError(\n                f\"{amount} caused an error, you must provide a real \" \"spending amount.\"\n            ) from ae\n        equal_shares = round(amount, 2) / splits\n        remainder_dollars = floor(equal_shares)\n        remainder_cents = floor((equal_shares - remainder_dollars) * 100) / 100\n        remainder_left = round(\n            (equal_shares - remainder_dollars - remainder_cents) * splits * 100, 0\n        )\n        owed_amount = remainder_dollars + remainder_cents\n        return_amounts = [owed_amount for _ in range(splits)]\n        for i in range(int(remainder_left)):\n            return_amounts[i] += 0.010\n        shuffle(return_amounts)\n        return tuple([round(item, 2) for item in return_amounts])\n\n    @classmethod\n    def split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n        \"\"\"\n        Split a Transaction into Two\n\n        Split a bill into a tuple of two amounts (and take care\n        of the extra penny if needed)\n\n        Parameters\n        ----------\n        amount: A Currency amount (no more precise than cents)\n\n        Returns\n        -------\n        tuple\n            A tuple is returned with each participant's amount\n        \"\"\"\n        amounts_due = cls._split_amount(amount=amount, splits=2)\n        return amounts_due\n\n    def create_self_paid_expense(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        primary_user_owes, financial_partner_owes = self.split_a_transaction(\n            amount=amount\n        )\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=primary_user_owes)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=financial_partner_owes)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def create_expense_on_behalf_of_partner(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n        This expense will be completely owed by the partner and maked as reimbursed.\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=0.00)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=amount)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def get_friend(\n        self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n    ) -> Optional[splitwise.Friend]:\n        \"\"\"\n        Retrieve a Financial Partner by Email Address\n\n        Parameters\n        ----------\n        email_address: str\n            Email Address of Friend's user in Splitwise\n        friend_id: Optional[int]\n            Splitwise friend ID. Notice the friend ID in the following\n            URL: https://secure.splitwise.com/#/friends/12345678\n\n        Returns\n        -------\n        Optional[splitwise.Friend]\n        \"\"\"\n        friend_list: List[splitwise.Friend] = self.getFriends()\n        if len(friend_list) == 1:\n            return friend_list[0]\n        for friend in friend_list:\n            if friend_id is not None and friend.id == friend_id:\n                return friend\n            elif (\n                email_address is not None\n                and friend.email.lower() == email_address.lower()\n            ):\n                return friend\n        return None\n\n    def get_expenses(\n        self,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        group_id: Optional[int] = None,\n        friendship_id: Optional[int] = None,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        updated_after: Optional[datetime.datetime] = None,\n        updated_before: Optional[datetime.datetime] = None,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Get Splitwise Expenses\n\n        Parameters\n        ----------\n        offset: Optional[int]\n            Number of expenses to be skipped\n        limit: Optional[int]\n            Number of expenses to be returned\n        group_id: Optional[int]\n            GroupID of the expenses\n        friendship_id: Optional[int]\n            FriendshipID of the expenses\n        dated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses later that this date\n        dated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses earlier than this date\n        updated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated after this date\n        updated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated before this date\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        expenses = self.getExpenses(\n            offset=offset,\n            limit=limit,\n            group_id=group_id,\n            friendship_id=friendship_id,\n            dated_after=dated_after,\n            dated_before=dated_before,\n            updated_after=updated_after,\n            updated_before=updated_before,\n        )\n        pydantic_expenses = [\n            self.splitwise_to_pydantic(expense) for expense in expenses\n        ]\n        return pydantic_expenses\n\n    @classmethod\n    def _get_splitwise_init_kwargs(\n        cls,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Get the Splitwise Kwargs\n\n        Parameters\n        ----------\n        consumer_key: Optional[str]\n        consumer_secret: Optional[str]\n        api_key: Optional[str]\n        \"\"\"\n        if consumer_key is None:\n            consumer_key = getenv(\"SPLITWISE_CONSUMER_KEY\")\n        if consumer_secret is None:\n            consumer_secret = getenv(\"SPLITWISE_CONSUMER_SECRET\")\n        if api_key is None:\n            api_key = getenv(\"SPLITWISE_API_KEY\", None)\n        init_kwargs = {\n            \"consumer_key\": consumer_key,\n            \"consumer_secret\": consumer_secret,\n            \"api_key\": api_key,\n        }\n        if consumer_key is None or consumer_secret is None or api_key is None:\n            error_message = (\n                dedent(\n                    \"\"\"\n            You must set your Splitwise credentials explicitly or by assigning\n            the `SPLITWISE_CONSUMER_KEY`, `SPLITWISE_CONSUMER_SECRET`, and the\n            `SPLITWISE_API_KEY`environment variables\n            \"\"\"\n                )\n                .replace(\"\\n\", \" \")\n                .replace(\"  \", \" \")\n            )\n            logger.error(error_message)\n            raise SplitLunchError(error_message)\n        return init_kwargs\n\n    def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n        \"\"\"\n        Convert Splitwise Object to Pydantic\n\n        Parameters\n        ----------\n        expense: splitwise.Expense\n\n        Returns\n        -------\n        SplitLunchExpense\n        \"\"\"\n        financial_impact, self_paid = _get_splitwise_impact(\n            expense=expense, current_user_id=self.current_user.id\n        )\n        expense = SplitLunchExpense(\n            splitwise_id=expense.id,\n            original_amount=expense.cost,\n            financial_impact=financial_impact,\n            self_paid=self_paid,\n            description=expense.description,\n            category=expense.category.name,\n            details=expense.details,\n            payment=expense.payment,\n            date=expense.date,\n            users=[user.id for user in expense.users],\n            created_at=expense.created_at,\n            updated_at=expense.updated_at,\n            deleted_at=expense.deleted_at,\n            deleted=True if expense.deleted_at is not None else False,\n        )\n        return expense\n\n    def _get_splitwise_asset(self) -> Optional[AssetsObject]:\n        \"\"\"\n        Get the Splitwise asset\n\n        Parse a user's Lunch Money accounts and return the manually managed\n        Splitwise account asset object\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        assets = self.lunchable.get_assets()\n        splitwise_assets = []\n        for asset in assets:\n            if (\n                asset.institution_name is not None\n                and \"splitwise\" in asset.institution_name.lower()\n            ):\n                splitwise_assets.append(asset)\n        if len(splitwise_assets) == 0:\n            return None\n        elif len(splitwise_assets) > 1:\n            raise SplitLunchError(\n                \"SplitLunch requires an manually managed Splitwise asset. \"\n                \"Make sure you have a single account where 'Splitwise' \"\n                \"is in the asset's `Institution Name`.\"\n            )\n        else:\n            return splitwise_assets[0]\n\n    def _get_reimbursement_category(self) -> Optional[CategoriesObject]:\n        \"\"\"\n        Get the Reimbusement Category\n\n        Parse a user's Lunch Money categories and return the Reimbursement\n        category\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        categories = self.lunchable.get_categories()\n        reimbursement_list = []\n        for category in categories:\n            if \"reimbursement\" == category.name.strip().lower():\n                reimbursement_list.append(category)\n        if len(reimbursement_list) != 1:\n            return None\n        return reimbursement_list[0]\n\n    def _get_splitwise_tags(self) -> None:\n        \"\"\"\n        Get Lunch Money Tags to Interact with\n\n        Returns\n        -------\n        Dict[str, int]\n        \"\"\"\n        all_tags = self.lunchable.get_tags()\n        for tag in all_tags:\n            if tag.name.lower() == SplitLunchConfig.splitlunch_tag.lower():\n                self.splitlunch_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitwise_tag.lower():\n                self.splitwise_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitlunch_import_tag.lower():\n                self.splitlunch_import_tag = tag\n            elif (\n                tag.name.lower()\n                == SplitLunchConfig.splitlunch_direct_import_tag.lower()\n            ):\n                self.splitlunch_direct_import_tag = tag\n\n    def _raise_nonexistent_tag_error(self, tags: List[str]) -> None:\n        \"\"\"\n        Raise a warning for specific SplitLunch Tags\n\n        tags: List[str]\n            A list of tags to raise the error for\n        \"\"\"\n        if (\n            self.splitlunch_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitwise_tag == self._none_tag\n            and SplitLunchConfig.splitwise_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitwise_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_import_tag}` tag is required. \"\n                f\"This tag is used for creating Splitwise transactions directly from \"\n                f\"Lunch Money transactions. These transactions will be split in half,\"\n                f\"and have one half marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_direct_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_direct_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_direct_import_tag}` tag is \"\n                \"required. This tag is used for creating Splitwise transactions \"\n                \"directly from Lunch Money transactions. These transactions will \"\n                \"be completely owed by your financial partner.\"\n            )\n            raise SplitLunchError(error_message)\n\n    def get_splitlunch_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitlunch\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def get_splitlunch_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitlunch_direct_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(\n            tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n        )\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_direct_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitwise_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitwise\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n        \"\"\"\n        Operate on `SplitLunch` tagged transactions\n\n        Split all transactions with the `SplitLunch` tag in half. One of these\n        new splits will be recategorized to `Reimbursement`. Both new splits will receive\n        the `Splitwise` tag without any preexisting tags.\n        \"\"\"\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        split_transaction_ids = []\n        tagged_objects = self.get_splitlunch_tagged_transactions()\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n            # Generate the First Split\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=transaction.notes,\n                amount=amount_1,\n            )\n            # Generate the second split as a copy, change some properties\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = amount_2\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                \"Splitting transaction: %s -> (%s, %s)\",\n                transaction.amount,\n                amount_1,\n                amount_2,\n            )\n\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            # Tag each of the new transactions generated\n            split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags if transaction.tags is not None else []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag is not None\n                    and tag.name.lower() != self.splitlunch_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return split_transaction_ids\n\n    def make_splitlunch_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchImport` tagged transactions\n\n        Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n        One of these new splits will be recategorized to `Reimbursement`. Both new splits\n        will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n        reapplied.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_self_paid_expense(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=notes,\n                # financial_impact for self-paid transactions will be negative\n                amount=round(\n                    transaction.amount - abs(new_transaction.financial_impact), 2\n                ),\n            )\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = abs(new_transaction.financial_impact)\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                f\"Transaction split by Splitwise: {transaction.amount} -> \"\n                f\"({split_object.amount}, {reimbursement_object.amount})\"\n            )\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": reimbursement_object.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": update_response[\"updated\"],\n                \"split\": update_response[\"split\"],\n            }\n            update_responses.append(formatted_update_response)\n            # Tag each of the new transactions generated\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags or []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return update_responses\n\n    def make_splitlunch_direct_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchDirectImport` tagged transactions\n\n        Send a transaction to Splitwise and then mark the transaction under the\n        `Reimbursement` category. The sum of the transaction will be completely owed\n        by the financial partner.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_expense_on_behalf_of_partner(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            existing_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in existing_tags\n                if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n                tags.append(self.splitwise_tag.name)\n            update = TransactionUpdateObject(\n                category_id=self.reimbursement_category.id, tags=tags, notes=notes\n            )\n            response = self.lunchable.update_transaction(\n                transaction_id=transaction.id, transaction=update\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": transaction.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": response[\"updated\"],\n                \"split\": None,\n            }\n            update_responses.append(formatted_update_response)\n        return update_responses\n\n    def splitwise_to_lunchmoney(\n        self,\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[int]:\n        \"\"\"\n        Ingest Splitwise Expenses into Lunch Money\n\n        This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n        a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n        payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[int]\n            New Lunch Money transaction IDs\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        batch = []\n        new_transaction_ids = []\n        filtered_expenses = self.filter_relevant_splitwise_expenses(\n            expenses=expenses,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        for splitwise_transaction in filtered_expenses:\n            new_lunchmoney_transaction = TransactionInsertObject(\n                date=splitwise_transaction.date.astimezone(tzlocal()),\n                payee=splitwise_transaction.description,\n                amount=splitwise_transaction.financial_impact,\n                asset_id=self.splitwise_asset.id,\n                external_id=splitwise_transaction.splitwise_id,\n            )\n            batch.append(new_lunchmoney_transaction)\n            if len(batch) == 10:\n                new_ids = self.lunchable.insert_transactions(\n                    transactions=batch, apply_rules=True\n                )\n                new_transaction_ids += new_ids\n                batch = []\n        if len(batch) > 0:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n        return new_transaction_ids\n\n    @staticmethod\n    def filter_relevant_splitwise_expenses(\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Filter Expenses in Splitwise into relevant expenses.\n\n        This filtering action is important to understand when seeing why not\n        all transactions from Splitwise end up flowing into Lunch Money.\n\n        1) It filters out deleted expenses\n\n        2) It filters out expenses with a financial impact of 0, implying that the user was not\n        involved in the expense.\n\n        3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n        `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        filtered_expenses = []\n        for splitwise_transaction in expenses:\n            if all(\n                [\n                    splitwise_transaction.deleted is False,\n                    splitwise_transaction.financial_impact != 0.00,\n                    allow_self_paid or (splitwise_transaction.self_paid is False),\n                    allow_payments or (splitwise_transaction.payment is False),\n                ]\n            ):\n                filtered_expenses.append(splitwise_transaction)\n\n        return filtered_expenses\n\n    def get_splitwise_balance(self) -> float:\n        \"\"\"\n        Get the net balance in Splitwise\n\n        Returns\n        -------\n        float\n        \"\"\"\n        groups = self.getGroups()\n        total_balance = 0.00\n        for group in groups:\n            for debt in group.simplified_debts:\n                if debt.fromUser == self.current_user.id:\n                    total_balance -= float(debt.amount)\n                elif debt.toUser == self.current_user.id:\n                    total_balance += float(debt.amount)\n        return total_balance\n\n    def update_splitwise_balance(self) -> AssetsObject:\n        \"\"\"\n        Get and update the Splitwise Asset in Lunch Money\n\n        Returns\n        -------\n        AssetsObject\n            Updated  balance\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        balance = self.get_splitwise_balance()\n        if balance != self.splitwise_asset.balance:\n            updated_asset = self.lunchable.update_asset(\n                asset_id=self.splitwise_asset.id, balance=balance\n            )\n            self.splitwise_asset = updated_asset\n        return self.splitwise_asset\n\n    _deleted_payee = \"[DELETED FROM SPLITWISE]\"\n\n    def get_new_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n    ) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n        \"\"\"\n        Get Splitwise Transactions that don't exist in Lunch Money\n\n        Also return deleted transaction from LunchMoney\n\n        Returns\n        -------\n        Tuple[List[SplitLunchExpense], List[TransactionObject]]\n            New and Deleted Transactions\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        splitlunch_expenses = self.lunchable.get_transactions(\n            asset_id=self.splitwise_asset.id,\n            start_date=datetime.datetime(1800, 1, 1),\n            end_date=datetime.datetime(2300, 12, 31),\n        )\n        splitlunch_ids = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        splitwise_expenses = self.get_expenses(\n            limit=0,\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n        new_ids = splitwise_ids.difference(splitlunch_ids)\n        new_expenses = [\n            expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n        ]\n        deleted_transactions = self.get_deleted_transactions(\n            splitlunch_expenses=splitlunch_expenses,\n            splitwise_transactions=splitwise_expenses,\n        )\n        return new_expenses, deleted_transactions\n\n    def get_deleted_transactions(\n        self,\n        splitlunch_expenses: List[TransactionObject],\n        splitwise_transactions: List[SplitLunchExpense],\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n        Set these transactions to $0.00 and Make a Note\n\n        Parameters\n        ----------\n        splitlunch_expenses: List[TransactionObject]\n        splitwise_transactions: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        existing_transactions = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        deleted_ids = {\n            item.splitwise_id for item in splitwise_transactions if item.deleted is True\n        }\n        untethered_transactions = deleted_ids.intersection(existing_transactions)\n        transactions_to_delete = [\n            tran\n            for tran in splitlunch_expenses\n            if tran.external_id is not None\n            and int(tran.external_id) in untethered_transactions\n            and tran.payee != self._deleted_payee\n        ]\n        return transactions_to_delete\n\n    def refresh_splitwise_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Import New Splitwise Transactions to Lunch Money\n\n        This function get's all transactions from Splitwise, all transactions from\n        your Lunch Money Splitwise account and compares the two.\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        new_transactions, deleted_transactions = self.get_new_transactions(\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        self.splitwise_to_lunchmoney(\n            expenses=new_transactions,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        splitwise_asset = self.update_splitwise_balance()\n        self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n        return {\n            \"balance\": splitwise_asset.balance,\n            \"new\": new_transactions,\n            \"deleted\": deleted_transactions,\n        }\n\n    def handle_deleted_transactions(\n        self,\n        deleted_transactions: List[TransactionObject],\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n        Parameters\n        ----------\n        deleted_transactions: List[TransactionObject]\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        updated_transactions = []\n        for transaction in deleted_transactions:\n            update = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                transaction=TransactionUpdateObject(\n                    amount=0.00,\n                    payee=self._deleted_payee,\n                    notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n                ),\n            )\n            updated_transactions.append(update)\n        return updated_transactions\n\n    def _raise_financial_partner_error(self) -> None:\n        \"\"\"\n        Raise Errors for Financial Partners\n        \"\"\"\n        if self.financial_partner is None:\n            raise SplitLunchError(\n                \"You must designate a financial partner in Splitwise. \"\n                \"This can be done with the partner's Splitwise User ID # \"\n                \"or their email address.\"\n            )\n\n    def _raise_splitwise_asset_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"You must create an asset (aka Account) in Lunch Money with \"\n            \"`Splitwise` in the name. There should only be one account \"\n            \"like this.\"\n        )\n\n    def _raise_category_reimbursement_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"SplitLunch requires a reimbursement Category. \"\n            \"Make sure you have a category entitled `Reimbursement`. \"\n            \"This category will be excluded from budgeting.\"\n            \"Half of split transactions will be created with \"\n            \"this category.\"\n        )\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.__init__","title":"__init__(lunch_money_access_token=None, financial_partner_id=None, financial_partner_email=None, financial_partner_group_id=None, consumer_key=None, consumer_secret=None, api_key=None, lunchable_client=None)","text":"

Initialize the Parent Class with some additional properties

Parameters:

Name Type Description Default financial_partner_id Optional[int]

Splitwise User ID of financial partner

None financial_partner_email Optional[str]

Splitwise linked email address of financial partner

None financial_partner_group_id Optional[int]

Splitwise Group ID for financial partner transactions

None consumer_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_KEY environment variable

None consumer_secret Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_SECRET environment variable

None api_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_API_KEY environment variable.

None lunch_money_access_token Optional[str]

Lunch Money Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable if not provided.

None lunchable_client Optional[LunchMoney]

Instantiated LunchMoney object to use as internal client. One will be created using environment variables otherwise.

None Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __init__(\n    self,\n    lunch_money_access_token: Optional[str] = None,\n    financial_partner_id: Optional[int] = None,\n    financial_partner_email: Optional[str] = None,\n    financial_partner_group_id: Optional[int] = None,\n    consumer_key: Optional[str] = None,\n    consumer_secret: Optional[str] = None,\n    api_key: Optional[str] = None,\n    lunchable_client: Optional[LunchMoney] = None,\n):\n    \"\"\"\n    Initialize the Parent Class with some additional properties\n\n    Parameters\n    ----------\n    financial_partner_id: Optional[int]\n        Splitwise User ID of financial partner\n    financial_partner_email: Optional[str]\n        Splitwise linked email address of financial partner\n    financial_partner_group_id: Optional[int]\n        Splitwise Group ID for financial partner transactions\n    consumer_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n        variable\n    consumer_secret: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n        environment variable\n    api_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n        variable.\n    lunch_money_access_token: Optional[str]\n        Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable if not provided.\n    lunchable_client: LunchMoney\n        Instantiated LunchMoney object to use as internal client. One will\n        be created using environment variables otherwise.\n    \"\"\"\n    init_kwargs = self._get_splitwise_init_kwargs(\n        consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n    )\n    super(SplitLunch, self).__init__(**init_kwargs)\n    self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n    self.financial_partner: splitwise.Friend = self.get_friend(\n        friend_id=financial_partner_id, email_address=financial_partner_email\n    )\n    self.financial_group = financial_partner_group_id\n    self.last_check: Optional[datetime.datetime] = None\n    self.lunchable = (\n        LunchMoney(access_token=lunch_money_access_token)\n        if lunchable_client is None\n        else lunchable_client\n    )\n    self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n    self.splitwise_tag = self._none_tag.model_copy()\n    self.splitlunch_tag = self._none_tag.model_copy()\n    self.splitlunch_import_tag = self._none_tag.model_copy()\n    self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n    self._get_splitwise_tags()\n    self.earliest_start_date = datetime.date(1812, 1, 1)\n    today = datetime.date.today()\n    self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n    self.splitwise_asset = self._get_splitwise_asset()\n    self.reimbursement_category = self._get_reimbursement_category()\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __repr__(self) -> str:\n    \"\"\"\n    String Representation\n\n    Returns\n    -------\n    str\n    \"\"\"\n    return f\"<Splitwise: {self.current_user.email}>\"\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.create_expense_on_behalf_of_partner","title":"create_expense_on_behalf_of_partner(amount, description, date)","text":"

Create and Submit a Splitwise Expense on behalf of your financial partner.

This expense will be completely owed by the partner and maked as reimbursed.

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_expense_on_behalf_of_partner(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n    This expense will be completely owed by the partner and maked as reimbursed.\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=0.00)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=amount)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.create_self_paid_expense","title":"create_self_paid_expense(amount, description, date)","text":"

Create and Submit a Splitwise Expense

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_self_paid_expense(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    primary_user_owes, financial_partner_owes = self.split_a_transaction(\n        amount=amount\n    )\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=primary_user_owes)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=financial_partner_owes)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.filter_relevant_splitwise_expenses","title":"filter_relevant_splitwise_expenses(expenses, allow_self_paid=False, allow_payments=False) staticmethod","text":"

Filter Expenses in Splitwise into relevant expenses.

This filtering action is important to understand when seeing why not all transactions from Splitwise end up flowing into Lunch Money.

1) It filters out deleted expenses

2) It filters out expenses with a financial impact of 0, implying that the user was not involved in the expense.

3) If the --allow-self-paid flag is not provided, it filters out self-paid expenses. A self-paid expense is an expense in Splitwise where you originated the payment. This is excluded because it is assumed that these transactions will have already been imported via a different account.

4) If the --allow-payments flag is not provided, it filters out payments. Payments are excluded because it is assumed that these transactions will have already been imported via a different account.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@staticmethod\ndef filter_relevant_splitwise_expenses(\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Filter Expenses in Splitwise into relevant expenses.\n\n    This filtering action is important to understand when seeing why not\n    all transactions from Splitwise end up flowing into Lunch Money.\n\n    1) It filters out deleted expenses\n\n    2) It filters out expenses with a financial impact of 0, implying that the user was not\n    involved in the expense.\n\n    3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n    `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    filtered_expenses = []\n    for splitwise_transaction in expenses:\n        if all(\n            [\n                splitwise_transaction.deleted is False,\n                splitwise_transaction.financial_impact != 0.00,\n                allow_self_paid or (splitwise_transaction.self_paid is False),\n                allow_payments or (splitwise_transaction.payment is False),\n            ]\n        ):\n            filtered_expenses.append(splitwise_transaction)\n\n    return filtered_expenses\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_deleted_transactions","title":"get_deleted_transactions(splitlunch_expenses, splitwise_transactions)","text":"

Get Splitwise Transactions that exist in Lunch Money but have since been deleted

Set these transactions to $0.00 and Make a Note

Parameters:

Name Type Description Default splitlunch_expenses List[TransactionObject] required splitwise_transactions List[SplitLunchExpense] required

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_deleted_transactions(\n    self,\n    splitlunch_expenses: List[TransactionObject],\n    splitwise_transactions: List[SplitLunchExpense],\n) -> List[TransactionObject]:\n    \"\"\"\n    Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n    Set these transactions to $0.00 and Make a Note\n\n    Parameters\n    ----------\n    splitlunch_expenses: List[TransactionObject]\n    splitwise_transactions: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    existing_transactions = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    deleted_ids = {\n        item.splitwise_id for item in splitwise_transactions if item.deleted is True\n    }\n    untethered_transactions = deleted_ids.intersection(existing_transactions)\n    transactions_to_delete = [\n        tran\n        for tran in splitlunch_expenses\n        if tran.external_id is not None\n        and int(tran.external_id) in untethered_transactions\n        and tran.payee != self._deleted_payee\n    ]\n    return transactions_to_delete\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_expenses","title":"get_expenses(offset=None, limit=None, group_id=None, friendship_id=None, dated_after=None, dated_before=None, updated_after=None, updated_before=None)","text":"

Get Splitwise Expenses

Parameters:

Name Type Description Default offset Optional[int]

Number of expenses to be skipped

None limit Optional[int]

Number of expenses to be returned

None group_id Optional[int]

GroupID of the expenses

None friendship_id Optional[int]

FriendshipID of the expenses

None dated_after Optional[datetime]

ISO 8601 Date time. Return expenses later that this date

None dated_before Optional[datetime]

ISO 8601 Date time. Return expenses earlier than this date

None updated_after Optional[datetime]

ISO 8601 Date time. Return expenses updated after this date

None updated_before Optional[datetime]

ISO 8601 Date time. Return expenses updated before this date

None

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_expenses(\n    self,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    group_id: Optional[int] = None,\n    friendship_id: Optional[int] = None,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    updated_after: Optional[datetime.datetime] = None,\n    updated_before: Optional[datetime.datetime] = None,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Get Splitwise Expenses\n\n    Parameters\n    ----------\n    offset: Optional[int]\n        Number of expenses to be skipped\n    limit: Optional[int]\n        Number of expenses to be returned\n    group_id: Optional[int]\n        GroupID of the expenses\n    friendship_id: Optional[int]\n        FriendshipID of the expenses\n    dated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses later that this date\n    dated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses earlier than this date\n    updated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated after this date\n    updated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated before this date\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    expenses = self.getExpenses(\n        offset=offset,\n        limit=limit,\n        group_id=group_id,\n        friendship_id=friendship_id,\n        dated_after=dated_after,\n        dated_before=dated_before,\n        updated_after=updated_after,\n        updated_before=updated_before,\n    )\n    pydantic_expenses = [\n        self.splitwise_to_pydantic(expense) for expense in expenses\n    ]\n    return pydantic_expenses\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_friend","title":"get_friend(email_address=None, friend_id=None)","text":"

Retrieve a Financial Partner by Email Address

Parameters:

Name Type Description Default email_address Optional[str]

Email Address of Friend's user in Splitwise

None friend_id Optional[int]

Splitwise friend ID. Notice the friend ID in the following URL: https://secure.splitwise.com/#/friends/12345678

None

Returns:

Type Description Optional[Friend] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_friend(\n    self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n) -> Optional[splitwise.Friend]:\n    \"\"\"\n    Retrieve a Financial Partner by Email Address\n\n    Parameters\n    ----------\n    email_address: str\n        Email Address of Friend's user in Splitwise\n    friend_id: Optional[int]\n        Splitwise friend ID. Notice the friend ID in the following\n        URL: https://secure.splitwise.com/#/friends/12345678\n\n    Returns\n    -------\n    Optional[splitwise.Friend]\n    \"\"\"\n    friend_list: List[splitwise.Friend] = self.getFriends()\n    if len(friend_list) == 1:\n        return friend_list[0]\n    for friend in friend_list:\n        if friend_id is not None and friend.id == friend_id:\n            return friend\n        elif (\n            email_address is not None\n            and friend.email.lower() == email_address.lower()\n        ):\n            return friend\n    return None\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_new_transactions","title":"get_new_transactions(dated_after=None, dated_before=None)","text":"

Get Splitwise Transactions that don't exist in Lunch Money

Also return deleted transaction from LunchMoney

Returns:

Type Description Tuple[List[SplitLunchExpense], List[TransactionObject]]

New and Deleted Transactions

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_new_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n    \"\"\"\n    Get Splitwise Transactions that don't exist in Lunch Money\n\n    Also return deleted transaction from LunchMoney\n\n    Returns\n    -------\n    Tuple[List[SplitLunchExpense], List[TransactionObject]]\n        New and Deleted Transactions\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    splitlunch_expenses = self.lunchable.get_transactions(\n        asset_id=self.splitwise_asset.id,\n        start_date=datetime.datetime(1800, 1, 1),\n        end_date=datetime.datetime(2300, 12, 31),\n    )\n    splitlunch_ids = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    splitwise_expenses = self.get_expenses(\n        limit=0,\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n    new_ids = splitwise_ids.difference(splitlunch_ids)\n    new_expenses = [\n        expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n    ]\n    deleted_transactions = self.get_deleted_transactions(\n        splitlunch_expenses=splitlunch_expenses,\n        splitwise_transactions=splitwise_expenses,\n    )\n    return new_expenses, deleted_transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_direct_import_tagged_transactions","title":"get_splitlunch_direct_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchDirectImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_direct_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(\n        tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n    )\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_direct_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_import_tagged_transactions","title":"get_splitlunch_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_tagged_transactions","title":"get_splitlunch_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitlunch\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitlunch\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitwise_balance","title":"get_splitwise_balance()","text":"

Get the net balance in Splitwise

Returns:

Type Description float Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_balance(self) -> float:\n    \"\"\"\n    Get the net balance in Splitwise\n\n    Returns\n    -------\n    float\n    \"\"\"\n    groups = self.getGroups()\n    total_balance = 0.00\n    for group in groups:\n        for debt in group.simplified_debts:\n            if debt.fromUser == self.current_user.id:\n                total_balance -= float(debt.amount)\n            elif debt.toUser == self.current_user.id:\n                total_balance += float(debt.amount)\n    return total_balance\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitwise_tagged_transactions","title":"get_splitwise_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitwise\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitwise\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.handle_deleted_transactions","title":"handle_deleted_transactions(deleted_transactions)","text":"

Update Transactions That Exist in Splitwise, but have been deleted in Splitwise

Parameters:

Name Type Description Default deleted_transactions List[TransactionObject] required

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def handle_deleted_transactions(\n    self,\n    deleted_transactions: List[TransactionObject],\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n    Parameters\n    ----------\n    deleted_transactions: List[TransactionObject]\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    updated_transactions = []\n    for transaction in deleted_transactions:\n        update = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            transaction=TransactionUpdateObject(\n                amount=0.00,\n                payee=self._deleted_payee,\n                notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n            ),\n        )\n        updated_transactions.append(update)\n    return updated_transactions\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch","title":"make_splitlunch(tag_transactions=False)","text":"

Operate on SplitLunch tagged transactions

Split all transactions with the SplitLunch tag in half. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without any preexisting tags.

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n    \"\"\"\n    Operate on `SplitLunch` tagged transactions\n\n    Split all transactions with the `SplitLunch` tag in half. One of these\n    new splits will be recategorized to `Reimbursement`. Both new splits will receive\n    the `Splitwise` tag without any preexisting tags.\n    \"\"\"\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    split_transaction_ids = []\n    tagged_objects = self.get_splitlunch_tagged_transactions()\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n        # Generate the First Split\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=transaction.notes,\n            amount=amount_1,\n        )\n        # Generate the second split as a copy, change some properties\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = amount_2\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            \"Splitting transaction: %s -> (%s, %s)\",\n            transaction.amount,\n            amount_1,\n            amount_2,\n        )\n\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        # Tag each of the new transactions generated\n        split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags if transaction.tags is not None else []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag is not None\n                and tag.name.lower() != self.splitlunch_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return split_transaction_ids\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch_direct_import","title":"make_splitlunch_direct_import(tag_transactions=False)","text":"

Operate on SplitLunchDirectImport tagged transactions

Send a transaction to Splitwise and then mark the transaction under the Reimbursement category. The sum of the transaction will be completely owed by the financial partner.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_direct_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchDirectImport` tagged transactions\n\n    Send a transaction to Splitwise and then mark the transaction under the\n    `Reimbursement` category. The sum of the transaction will be completely owed\n    by the financial partner.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_expense_on_behalf_of_partner(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        existing_tags = transaction.tags or []\n        tags = [\n            tag.name\n            for tag in existing_tags\n            if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n        ]\n        if self.splitwise_tag.name not in tags and tag_transactions is True:\n            self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n            tags.append(self.splitwise_tag.name)\n        update = TransactionUpdateObject(\n            category_id=self.reimbursement_category.id, tags=tags, notes=notes\n        )\n        response = self.lunchable.update_transaction(\n            transaction_id=transaction.id, transaction=update\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": transaction.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": response[\"updated\"],\n            \"split\": None,\n        }\n        update_responses.append(formatted_update_response)\n    return update_responses\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch_import","title":"make_splitlunch_import(tag_transactions=False)","text":"

Operate on SplitLunchImport tagged transactions

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without the SplitLunchImport tag. Any other tags will be reapplied.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchImport` tagged transactions\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Both new splits\n    will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n    reapplied.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_self_paid_expense(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=notes,\n            # financial_impact for self-paid transactions will be negative\n            amount=round(\n                transaction.amount - abs(new_transaction.financial_impact), 2\n            ),\n        )\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = abs(new_transaction.financial_impact)\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            f\"Transaction split by Splitwise: {transaction.amount} -> \"\n            f\"({split_object.amount}, {reimbursement_object.amount})\"\n        )\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": reimbursement_object.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": update_response[\"updated\"],\n            \"split\": update_response[\"split\"],\n        }\n        update_responses.append(formatted_update_response)\n        # Tag each of the new transactions generated\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return update_responses\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.refresh_splitwise_transactions","title":"refresh_splitwise_transactions(dated_after=None, dated_before=None, allow_self_paid=False, allow_payments=False)","text":"

Import New Splitwise Transactions to Lunch Money

This function get's all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two.

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def refresh_splitwise_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> Dict[str, Any]:\n    \"\"\"\n    Import New Splitwise Transactions to Lunch Money\n\n    This function get's all transactions from Splitwise, all transactions from\n    your Lunch Money Splitwise account and compares the two.\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    new_transactions, deleted_transactions = self.get_new_transactions(\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    self.splitwise_to_lunchmoney(\n        expenses=new_transactions,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    splitwise_asset = self.update_splitwise_balance()\n    self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n    return {\n        \"balance\": splitwise_asset.balance,\n        \"new\": new_transactions,\n        \"deleted\": deleted_transactions,\n    }\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.split_a_transaction","title":"split_a_transaction(amount) classmethod","text":"

Split a Transaction into Two

Split a bill into a tuple of two amounts (and take care of the extra penny if needed)

Parameters:

Name Type Description Default amount Union[float, int] required

Returns:

Type Description tuple

A tuple is returned with each participant's amount

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@classmethod\ndef split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n    \"\"\"\n    Split a Transaction into Two\n\n    Split a bill into a tuple of two amounts (and take care\n    of the extra penny if needed)\n\n    Parameters\n    ----------\n    amount: A Currency amount (no more precise than cents)\n\n    Returns\n    -------\n    tuple\n        A tuple is returned with each participant's amount\n    \"\"\"\n    amounts_due = cls._split_amount(amount=amount, splits=2)\n    return amounts_due\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.splitwise_to_lunchmoney","title":"splitwise_to_lunchmoney(expenses, allow_self_paid=False, allow_payments=False)","text":"

Ingest Splitwise Expenses into Lunch Money

This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default, payments and self-paid transactions are also ineligible. Otherwise it will be ignored.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[int]

New Lunch Money transaction IDs

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_lunchmoney(\n    self,\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[int]:\n    \"\"\"\n    Ingest Splitwise Expenses into Lunch Money\n\n    This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n    a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n    payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[int]\n        New Lunch Money transaction IDs\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    batch = []\n    new_transaction_ids = []\n    filtered_expenses = self.filter_relevant_splitwise_expenses(\n        expenses=expenses,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    for splitwise_transaction in filtered_expenses:\n        new_lunchmoney_transaction = TransactionInsertObject(\n            date=splitwise_transaction.date.astimezone(tzlocal()),\n            payee=splitwise_transaction.description,\n            amount=splitwise_transaction.financial_impact,\n            asset_id=self.splitwise_asset.id,\n            external_id=splitwise_transaction.splitwise_id,\n        )\n        batch.append(new_lunchmoney_transaction)\n        if len(batch) == 10:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n            batch = []\n    if len(batch) > 0:\n        new_ids = self.lunchable.insert_transactions(\n            transactions=batch, apply_rules=True\n        )\n        new_transaction_ids += new_ids\n    return new_transaction_ids\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.splitwise_to_pydantic","title":"splitwise_to_pydantic(expense)","text":"

Convert Splitwise Object to Pydantic

Parameters:

Name Type Description Default expense Expense required

Returns:

Type Description SplitLunchExpense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n    \"\"\"\n    Convert Splitwise Object to Pydantic\n\n    Parameters\n    ----------\n    expense: splitwise.Expense\n\n    Returns\n    -------\n    SplitLunchExpense\n    \"\"\"\n    financial_impact, self_paid = _get_splitwise_impact(\n        expense=expense, current_user_id=self.current_user.id\n    )\n    expense = SplitLunchExpense(\n        splitwise_id=expense.id,\n        original_amount=expense.cost,\n        financial_impact=financial_impact,\n        self_paid=self_paid,\n        description=expense.description,\n        category=expense.category.name,\n        details=expense.details,\n        payment=expense.payment,\n        date=expense.date,\n        users=[user.id for user in expense.users],\n        created_at=expense.created_at,\n        updated_at=expense.updated_at,\n        deleted_at=expense.deleted_at,\n        deleted=True if expense.deleted_at is not None else False,\n    )\n    return expense\n
"},{"location":"plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.update_splitwise_balance","title":"update_splitwise_balance()","text":"

Get and update the Splitwise Asset in Lunch Money

Returns:

Type Description AssetsObject

Updated balance

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def update_splitwise_balance(self) -> AssetsObject:\n    \"\"\"\n    Get and update the Splitwise Asset in Lunch Money\n\n    Returns\n    -------\n    AssetsObject\n        Updated  balance\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    balance = self.get_splitwise_balance()\n    if balance != self.splitwise_asset.balance:\n        updated_asset = self.lunchable.update_asset(\n            asset_id=self.splitwise_asset.id, balance=balance\n        )\n        self.splitwise_asset = updated_asset\n    return self.splitwise_asset\n
"},{"location":"reference/","title":"lunchable","text":"

Lunch Money Python SDK

"},{"location":"reference/#lunchable.LunchMoney","title":"LunchMoney","text":"

Bases: AssetsClient, BudgetsClient, CategoriesClient, CryptoClient, PlaidAccountsClient, RecurringExpensesClient, TagsClient, TransactionsClient, UserClient

Lunch Money Python Client.

This class facilitates with connections to the Lunch Money Developer API. Authenticate with an Access Token. If an access token isn't provided one will attempt to be inherited from a LUNCHMONEY_ACCESS_TOKEN environment variable.

Examples:

from __future__ import annotations\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions()\n
Source code in lunchable/models/_lunchmoney.py
class LunchMoney(\n    AssetsClient,\n    BudgetsClient,\n    CategoriesClient,\n    CryptoClient,\n    PlaidAccountsClient,\n    RecurringExpensesClient,\n    TagsClient,\n    TransactionsClient,\n    UserClient,\n):\n    \"\"\"\n    Lunch Money Python Client.\n\n    This class facilitates with connections to\n    the [Lunch Money Developer API](https://lunchmoney.dev/). Authenticate\n    with an Access Token. If an access token isn't provided one will attempt to\n    be inherited from a `LUNCHMONEY_ACCESS_TOKEN` environment variable.\n\n    Examples\n    --------\n    ```python\n    from __future__ import annotations\n\n    from lunchable import LunchMoney\n    from lunchable.models import TransactionObject\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transactions: list[TransactionObject] = lunch.get_transactions()\n    ```\n    \"\"\"\n\n    def __init__(self, access_token: Optional[str] = None):\n        \"\"\"\n        Initialize a Lunch Money object with an Access Token.\n\n        Tries to inherit from the Environment if one isn't provided\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n        \"\"\"\n        super(LunchMoney, self).__init__(access_token=access_token)\n
"},{"location":"reference/#lunchable.LunchMoney.__init__","title":"__init__(access_token=None)","text":"

Initialize a Lunch Money object with an Access Token.

Tries to inherit from the Environment if one isn't provided

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None Source code in lunchable/models/_lunchmoney.py
def __init__(self, access_token: Optional[str] = None):\n    \"\"\"\n    Initialize a Lunch Money object with an Access Token.\n\n    Tries to inherit from the Environment if one isn't provided\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n    \"\"\"\n    super(LunchMoney, self).__init__(access_token=access_token)\n
"},{"location":"reference/#lunchable.LunchMoneyError","title":"LunchMoneyError","text":"

Bases: Exception

Base Exception for Lunch Money

Source code in lunchable/exceptions.py
class LunchMoneyError(Exception):\n    \"\"\"\n    Base Exception for Lunch Money\n    \"\"\"\n
"},{"location":"reference/#lunchable.TransactionInsertObject","title":"TransactionInsertObject","text":"

Bases: TransactionBaseObject

Object For Creating New Transactions

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required amount float

Numeric value of amount. i.e. $4.25 should be denoted as 4.25.

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None currency str | None

Three-letter lowercase currency code in ISO 4217 format. The code sent must exist in our database. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account.

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id.

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionInsertObject(TransactionBaseObject):\n    \"\"\"\n    Object For Creating New Transactions\n\n    https://lunchmoney.dev/#insert-transactions\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _amount_description = \"\"\"\n    Numeric value of amount. i.e. $4.25 should be denoted as 4.25.\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated with\n    the same account and must not be a category group.\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code in ISO 4217 format. The code sent must exist\n    in our database. Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account.\n    \"\"\"\n    _recurring_id = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must be associated\n    with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. If recurring_id is provided, the status will\n    automatically be set to recurring or recurring_suggested depending on the type of\n    recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id.\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found, an error\n    will be thrown. Passing in a string will attempt to match by string. If no matching tag\n    name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: datetime.date = Field(description=_date_description)\n    amount: float = Field(description=_amount_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    currency: Optional[str] = Field(\n        None, description=_currency_description, max_length=3\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(\n        None, description=_external_id_description, max_length=75\n    )\n    tags: Optional[List[Union[str, int]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/#lunchable.TransactionInsertObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/#lunchable.TransactionSplitObject","title":"TransactionSplitObject","text":"

Bases: TransactionBaseObject

Object for Splitting Transactions

https://lunchmoney.dev/#split-object

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account.

None notes str | None

Transaction Split Notes.

None amount float

Individual amount of split. Currency will inherit from parent transaction. All amounts must sum up to parent transaction amount.

required Source code in lunchable/models/transactions.py
class TransactionSplitObject(TransactionBaseObject):\n    \"\"\"\n    Object for Splitting Transactions\n\n    https://lunchmoney.dev/#split-object\n    \"\"\"\n\n    _date_description = \"Must be in ISO 8601 format (YYYY-MM-DD).\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account.\n    \"\"\"\n    _notes_description = \"Transaction Split Notes.\"\n    _amount_description = \"\"\"\n    Individual amount of split. Currency will inherit from parent transaction. All\n    amounts must sum up to parent transaction amount.\n    \"\"\"\n\n    date: datetime.date = Field(description=_date_description)\n    category_id: Optional[int] = Field(\n        default=None, description=_category_id_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    amount: float = Field(description=_amount_description)\n
"},{"location":"reference/#lunchable.TransactionUpdateObject","title":"TransactionUpdateObject","text":"

Bases: TransactionBaseObject

Object For Updating Existing Transactions

https://lunchmoney.dev/#update-transaction

Parameters:

Name Type Description Default date date | None

Must be in ISO 8601 format (YYYY-MM-DD).

None category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None amount float | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None currency str | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. Defaults to uncleared If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionUpdateObject(TransactionBaseObject):\n    \"\"\"\n    Object For Updating Existing Transactions\n\n    https://lunchmoney.dev/#update-transaction\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account and must not be a category group.\n    \"\"\"\n    _amount_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id\n    \"\"\"\n    _currency_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id.\n    Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _recurring_id_description = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must\n    be associated with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. Defaults to uncleared If recurring_id is\n    provided, the status will automatically be set to recurring or recurring_suggested\n    depending on the type of recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found,\n    an error will be thrown. Passing in a string will attempt to match by string.\n    If no matching tag name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: Optional[datetime.date] = Field(None, description=_date_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    amount: Optional[float] = Field(None, description=_amount_description)\n    currency: Optional[str] = Field(None, description=_currency_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id_description)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(None, description=_external_id_description)\n    tags: Optional[List[Union[int, str]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/#lunchable.TransactionUpdateObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/_cli/","title":"_cli","text":"

Lunchmoney CLI

"},{"location":"reference/_cli/#lunchable._cli.LunchMoneyContext","title":"LunchMoneyContext","text":"

Bases: LunchableModel

Context Object to PAss Around CLI

Parameters:

Name Type Description Default debug bool required access_token str | None required Source code in lunchable/_cli.py
class LunchMoneyContext(LunchableModel):\n    \"\"\"\n    Context Object to PAss Around CLI\n    \"\"\"\n\n    debug: bool\n    access_token: Optional[str]\n
"},{"location":"reference/_cli/#lunchable._cli.cli","title":"cli(ctx, debug, access_token)","text":"

Interactions with Lunch Money via lunchable \ud83c\udf71

Source code in lunchable/_cli.py
@click.group(invoke_without_command=True)\n@click.version_option(\n    version=lunchable.__version__, prog_name=lunchable.__application__\n)\n@access_token_option\n@debug_option\n@click.pass_context\ndef cli(ctx: click.core.Context, debug: bool, access_token: str) -> None:\n    \"\"\"\n    Interactions with Lunch Money via lunchable \ud83c\udf71\n    \"\"\"\n    ctx.obj = LunchMoneyContext(debug=debug, access_token=access_token)\n    traceback.install(show_locals=debug)\n    set_up_logging(log_level=logging.DEBUG if debug is True else logging.INFO)\n    if ctx.invoked_subcommand is None:\n        click.echo(ctx.get_help())\n
"},{"location":"reference/_cli/#lunchable._cli.http","title":"http(context, url, request, data)","text":"

Interact with the LunchMoney API

lunchable http /v1/transactions

Source code in lunchable/_cli.py
@cli.command()\n@click.argument(\"URL\")\n@click.option(\"-X\", \"--request\", default=\"GET\", help=\"Specify request command to use\")\n@click.option(\"-d\", \"--data\", default=None, help=\"HTTP POST data\")\n@click.pass_obj\ndef http(context: LunchMoneyContext, url: str, request: str, data: str) -> None:\n    \"\"\"\n    Interact with the LunchMoney API\n\n    lunchable http /v1/transactions\n    \"\"\"\n    lunch = LunchMoney(access_token=context.access_token)\n    if not url.startswith(\"http\"):\n        url = url.lstrip(\"/\")\n        url_request = f\"https://dev.lunchmoney.app/{url}\"\n    else:\n        url_request = url\n    resp = lunch.request(\n        method=request,\n        url=url_request,\n        content=data,\n    )\n    try:\n        resp.raise_for_status()\n    except httpx.HTTPError:\n        logger.error(resp)\n        print(resp.text)\n        sys.exit(1)\n    try:\n        response = resp.json()\n    except JSONDecodeError:\n        response = resp.text\n    json_data = to_jsonable_python(response)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.lunchmoney_transactions","title":"lunchmoney_transactions(context, **kwargs)","text":"

Retrieve Lunch Money Transactions

Source code in lunchable/_cli.py
@transactions.command(\"get\")\n@click.option(\n    \"--start-date\",\n    default=None,\n    help=\"Denotes the beginning of the time period to fetch transactions for. Defaults\"\n    \"to beginning of current month. Required if end_date exists. \"\n    \"Format: YYYY-MM-DD.\",\n)\n@click.option(\n    \"--end-date\",\n    default=None,\n    help=\"Denotes the end of the time period you'd like to get transactions for. \"\n    \"Defaults to end of current month. Required if start_date exists.\"\n    \"Format: YYYY-MM-DD.\",\n)\n@click.option(\n    \"--tag-id\", default=None, help=\"Filter by tag. Only accepts IDs, not names.\"\n)\n@click.option(\"--recurring-id\", default=None, help=\"Filter by recurring expense\")\n@click.option(\"--plaid-account-id\", default=None, help=\"Filter by Plaid account\")\n@click.option(\n    \"--category-id\",\n    default=None,\n    help=\"Filter by category. Will also match category groups.\",\n)\n@click.option(\"--asset-id\", default=None, help=\"Filter by asset\")\n@click.option(\n    \"--group-id\",\n    default=None,\n    help=\"Filter by group_id (if the transaction is part of a specific group)\",\n)\n@click.option(\n    \"--is-group\", default=None, help=\"Filter by group (returns transaction groups)\"\n)\n@click.option(\n    \"--status\",\n    default=None,\n    help=\"Filter by status (Can be cleared or uncleared. For recurring \"\n    \"transactions, use recurring)\",\n)\n@click.option(\"--offset\", default=None, help=\"Sets the offset for the records returned\")\n@click.option(\n    \"--limit\",\n    default=None,\n    help=\"Sets the maximum number of records to return. Note: The server will not \"\n    \"respond with any indication that there are more records to be returned. \"\n    \"Please check the response length to determine if you should make another \"\n    \"call with an offset to fetch more transactions.\",\n)\n@click.option(\n    \"--debit-as-negative\",\n    default=None,\n    help=\"Pass in true if you\u2019d like expenses to be returned as negative amounts and \"\n    \"credits as positive amounts. Defaults to false.\",\n)\n@click.option(\n    \"--pending\",\n    is_flag=True,\n    default=None,\n    help=\"Pass in true if you\u2019d like to include imported transactions with a pending status.\",\n)\n@click.pass_obj\ndef lunchmoney_transactions(\n    context: LunchMoneyContext, **kwargs: Dict[str, Any]\n) -> None:\n    \"\"\"\n    Retrieve Lunch Money Transactions\n    \"\"\"\n    lunch = LunchMoney(access_token=context.access_token)\n    transactions = lunch.get_transactions(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(transactions)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.make_splitlunch","title":"make_splitlunch(**kwargs)","text":"

Split all SplitLunch tagged transactions in half.

One of these new splits will be recategorized to Reimbursement.

Source code in lunchable/_cli.py
@splitlunch.command(\"splitlunch\")\n@tag_transactions\ndef make_splitlunch(**kwargs: Union[int, str, bool]) -> None:\n    \"\"\"\n    Split all `SplitLunch` tagged transactions in half.\n\n    One of these new splits will be recategorized to `Reimbursement`.\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    splitlunch = SplitLunch()\n    results = splitlunch.make_splitlunch(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(results)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.make_splitlunch_direct_import","title":"make_splitlunch_direct_import(**kwargs)","text":"

Import SplitLunchDirectImport tagged transactions to Splitwise and Split them in Lunch Money

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Any tags will be reapplied.

Source code in lunchable/_cli.py
@splitlunch.command(\"splitlunch-direct-import\")\n@tag_transactions\n@financial_partner_id\n@financial_partner_email\n@financial_partner_group_id\ndef make_splitlunch_direct_import(**kwargs: Union[int, str, bool]) -> None:\n    \"\"\"\n    Import `SplitLunchDirectImport` tagged transactions to Splitwise and Split them in Lunch Money\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Any tags will be\n    reapplied.\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    financial_partner_id: Optional[int] = kwargs.pop(\"financial_partner_id\")  # type: ignore[assignment]\n    financial_partner_email: Optional[str] = kwargs.pop(\"financial_partner_email\")  # type: ignore[assignment]\n    financial_partner_group_id: Optional[int] = kwargs.pop(\"financial_partner_group_id\")  # type: ignore[assignment]\n    splitlunch = SplitLunch(\n        financial_partner_id=financial_partner_id,\n        financial_partner_email=financial_partner_email,\n        financial_partner_group_id=financial_partner_group_id,\n    )\n    results = splitlunch.make_splitlunch_direct_import(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(results)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.make_splitlunch_import","title":"make_splitlunch_import(**kwargs)","text":"

Import SplitLunchImport tagged transactions to Splitwise and Split them in Lunch Money

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Any tags will be reapplied.

Source code in lunchable/_cli.py
@splitlunch.command(\"splitlunch-import\")\n@tag_transactions\n@financial_partner_id\n@financial_partner_email\n@financial_partner_group_id\ndef make_splitlunch_import(**kwargs: Union[int, str, bool]) -> None:\n    \"\"\"\n    Import `SplitLunchImport` tagged transactions to Splitwise and Split them in Lunch Money\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Any tags will be\n    reapplied.\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    financial_partner_id: Optional[int] = kwargs.pop(\"financial_partner_id\")  # type: ignore[assignment]\n    financial_partner_email: Optional[str] = kwargs.pop(\"financial_partner_email\")  # type: ignore[assignment]\n    financial_partner_group_id: Optional[int] = kwargs.pop(\"financial_partner_group_id\")  # type: ignore[assignment]\n    splitlunch = SplitLunch(\n        financial_partner_id=financial_partner_id,\n        financial_partner_email=financial_partner_email,\n        financial_partner_group_id=financial_partner_group_id,\n    )\n    results = splitlunch.make_splitlunch_import(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(results)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.notify","title":"notify(continuous, interval, user_key)","text":"

Send a Notification for each Uncleared Transaction

Source code in lunchable/_cli.py
@pushlunch.command(\"notify\")\n@click.option(\n    \"--continuous\",\n    is_flag=True,\n    help=\"Whether to continuously check for more uncleared transactions, \"\n    \"waiting a fixed amount in between checks.\",\n)\n@click.option(\n    \"--interval\",\n    default=None,\n    help=\"Sleep Interval in Between Tries - only applies if `continuous` is set. \"\n    \"Defaults to 60 (minutes). Cannot be less than 5 (minutes)\",\n)\n@click.option(\n    \"--user-key\",\n    default=None,\n    help=\"Pushover User Key. Defaults to `PUSHOVER_USER_KEY` env var\",\n)\ndef notify(continuous: bool, interval: int, user_key: str) -> None:\n    \"\"\"\n    Send a Notification for each Uncleared Transaction\n    \"\"\"\n    push = PushLunch(user_key=user_key)\n    if interval is not None:\n        interval = int(interval)\n    push.notify_uncleared_transactions(continuous=continuous, interval=interval)\n
"},{"location":"reference/_cli/#lunchable._cli.plugins","title":"plugins()","text":"

Interact with Lunchable Plugins

Source code in lunchable/_cli.py
@cli.group()\ndef plugins() -> None:\n    \"\"\"\n    Interact with Lunchable Plugins\n    \"\"\"\n
"},{"location":"reference/_cli/#lunchable._cli.primelunch","title":"primelunch()","text":"

PrimeLunch CLI - Syncing LunchMoney with Amazon

Source code in lunchable/_cli.py
@plugins.group()\ndef primelunch() -> None:\n    \"\"\"\n    PrimeLunch CLI - Syncing LunchMoney with Amazon\n    \"\"\"\n
"},{"location":"reference/_cli/#lunchable._cli.pushlunch","title":"pushlunch()","text":"

Push Notifications for Lunch Money: PushLunch \ud83d\udcf2

Source code in lunchable/_cli.py
@plugins.group()\ndef pushlunch() -> None:\n    \"\"\"\n    Push Notifications for Lunch Money: PushLunch \ud83d\udcf2\n    \"\"\"\n    pass\n
"},{"location":"reference/_cli/#lunchable._cli.refresh_splitwise_transactions","title":"refresh_splitwise_transactions(dated_before, dated_after, allow_self_paid, allow_payments)","text":"

Import New Splitwise Transactions to Lunch Money and

This function gets all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two. This also updates the account balance.

Source code in lunchable/_cli.py
@splitlunch.command(\"refresh\")\n@dated_after\n@dated_before\n@click.option(\n    \"--allow-self-paid/--no-allow-self-paid\",\n    default=False,\n    help=\"Allow self-paid expenses to be imported (filtered out by default).\",\n)\n@click.option(\n    \"--allow-payments/--no-allow-payments\",\n    default=False,\n    help=\"Allow payments to be imported (filtered out by default).\",\n)\ndef refresh_splitwise_transactions(\n    dated_before: Optional[datetime.datetime],\n    dated_after: Optional[datetime.datetime],\n    allow_self_paid: bool,\n    allow_payments: bool,\n) -> None:\n    \"\"\"\n    Import New Splitwise Transactions to Lunch Money and\n\n    This function gets all transactions from Splitwise, all transactions from\n    your Lunch Money Splitwise account and compares the two. This also updates\n    the account balance.\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    splitlunch = SplitLunch()\n    response = splitlunch.refresh_splitwise_transactions(\n        dated_before=dated_before,\n        dated_after=dated_after,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    json_data = to_jsonable_python(response)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.splitlunch","title":"splitlunch()","text":"

Splitwise Plugin for lunchable, SplitLunch \ud83d\udcb2\ud83c\udf71

Source code in lunchable/_cli.py
@plugins.group()\ndef splitlunch() -> None:\n    \"\"\"\n    Splitwise Plugin for lunchable, SplitLunch \ud83d\udcb2\ud83c\udf71\n    \"\"\"\n    pass\n
"},{"location":"reference/_cli/#lunchable._cli.splitlunch_expenses","title":"splitlunch_expenses(**kwargs)","text":"

Retrieve Splitwise Expenses

Source code in lunchable/_cli.py
@splitlunch.command(\"expenses\")\n@click.option(\n    \"--limit\", default=None, help=\"Limit the amount of Results. 0 returns everything.\"\n)\n@click.option(\"--offset\", default=None, help=\"Number of expenses to be skipped\")\n@click.option(\"--limit\", default=None, help=\"Number of expenses to be returned\")\n@click.option(\"--group-id\", default=None, help=\"GroupID of the expenses\")\n@click.option(\"--friendship-id\", default=None, help=\"FriendshipID of the expenses\")\n@dated_after\n@dated_before\n@click.option(\n    \"--updated-after\",\n    default=None,\n    help=\"ISO 8601 Date time. Return expenses updated after this date\",\n)\n@click.option(\n    \"--updated-before\",\n    default=None,\n    help=\"ISO 8601 Date time. Return expenses updated before this date\",\n)\ndef splitlunch_expenses(**kwargs: Union[int, str, bool]) -> None:\n    \"\"\"\n    Retrieve Splitwise Expenses\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    splitlunch = SplitLunch()\n    if set(kwargs.values()) == {None}:\n        kwargs[\"limit\"] = 5\n    expenses = splitlunch.get_expenses(**kwargs)  # type: ignore[arg-type]\n    json_data = to_jsonable_python(expenses)\n    print_json(data=json_data)\n
"},{"location":"reference/_cli/#lunchable._cli.transactions","title":"transactions()","text":"

Interact with Lunch Money transactions

Source code in lunchable/_cli.py
@cli.group()\ndef transactions() -> None:\n    \"\"\"\n    Interact with Lunch Money transactions\n    \"\"\"\n
"},{"location":"reference/_cli/#lunchable._cli.update_splitwise_balance","title":"update_splitwise_balance()","text":"

Update the Splitwise Asset Balance

Source code in lunchable/_cli.py
@splitlunch.command(\"update-balance\")\ndef update_splitwise_balance() -> None:\n    \"\"\"\n    Update the Splitwise Asset Balance\n    \"\"\"\n    from lunchable.plugins.splitlunch import SplitLunch\n\n    splitlunch = SplitLunch()\n    updated_asset = splitlunch.update_splitwise_balance()\n    json_data = to_jsonable_python(updated_asset)\n    print_json(data=json_data)\n
"},{"location":"reference/_version/","title":"_version","text":"

lunchable Version file

"},{"location":"reference/exceptions/","title":"exceptions","text":"

Lunchmoney Exceptions

"},{"location":"reference/exceptions/#lunchable.exceptions.EnvironmentVariableError","title":"EnvironmentVariableError","text":"

Bases: LunchMoneyError, EnvironmentError

Lunch Money Missing Environment Variable Error

Source code in lunchable/exceptions.py
class EnvironmentVariableError(LunchMoneyError, EnvironmentError):\n    \"\"\"\n    Lunch Money Missing Environment Variable Error\n    \"\"\"\n
"},{"location":"reference/exceptions/#lunchable.exceptions.LunchMoneyError","title":"LunchMoneyError","text":"

Bases: Exception

Base Exception for Lunch Money

Source code in lunchable/exceptions.py
class LunchMoneyError(Exception):\n    \"\"\"\n    Base Exception for Lunch Money\n    \"\"\"\n
"},{"location":"reference/exceptions/#lunchable.exceptions.LunchMoneyHTTPError","title":"LunchMoneyHTTPError","text":"

Bases: LunchMoneyError, HTTPError

Lunch Money HTTP Error

Source code in lunchable/exceptions.py
class LunchMoneyHTTPError(LunchMoneyError, HTTPError):\n    \"\"\"\n    Lunch Money HTTP Error\n    \"\"\"\n
"},{"location":"reference/exceptions/#lunchable.exceptions.LunchMoneyImportError","title":"LunchMoneyImportError","text":"

Bases: LunchMoneyError, ImportError

Lunch Money Import Error

Source code in lunchable/exceptions.py
class LunchMoneyImportError(LunchMoneyError, ImportError):\n    \"\"\"\n    Lunch Money Import Error\n    \"\"\"\n
"},{"location":"reference/_config/","title":"_config","text":"

Lunch Money Config Namespaces and Helpers

"},{"location":"reference/_config/#lunchable._config.APIConfig","title":"APIConfig","text":"

Configuration Helper Class for Connecting to the Lunchmoney API

Source code in lunchable/_config/api_config.py
class APIConfig:\n    \"\"\"\n    Configuration Helper Class for Connecting to the Lunchmoney API\n    \"\"\"\n\n    LUNCHMONEY_SCHEME: str = \"https\"\n    LUNCHMONEY_NETLOC: str = \"dev.lunchmoney.app\"\n    LUNCHMONEY_API_PATH: str = \"v1\"\n\n    LUNCHMONEY_TRANSACTIONS: str = \"transactions\"\n    LUNCHMONEY_TRANSACTION_GROUPS: str = \"group\"\n    LUNCHMONEY_PLAID_ACCOUNTS: str = \"plaid_accounts\"\n    LUNCH_MONEY_RECURRING_EXPENSES: str = \"recurring_expenses\"\n    LUNCHMONEY_BUDGET: str = \"budgets\"\n    LUNCHMONEY_ASSETS: str = \"assets\"\n    LUNCHMONEY_CATEGORIES: str = \"categories\"\n    LUNCHMONEY_TAGS: str = \"tags\"\n    LUNCHMONEY_CRYPTO: str = \"crypto\"\n    LUNCHMONEY_CRYPTO_MANUAL: str = \"manual\"\n    LUNCHMONEY_ME: str = \"me\"\n\n    LUNCHMONEY_CONTENT_TYPE_HEADERS: Dict[str, str] = {\n        \"Content-Type\": \"application/json\"\n    }\n\n    _access_token_environment_variable = \"LUNCHMONEY_ACCESS_TOKEN\"\n\n    @staticmethod\n    def get_access_token(access_token: Optional[str] = None) -> str:\n        \"\"\"\n        Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if access_token is None:\n            logger.info(\n                \"Loading Lunch Money Developer API Access token from environment\"\n            )\n            access_token = getenv(APIConfig._access_token_environment_variable, None)\n        if access_token is None:\n            access_token_error_message = (\n                \"You must provide a Lunch Money Developer API Access Token directly or set your \"\n                f\"{APIConfig._access_token_environment_variable} environment variable.\"\n            )\n            raise EnvironmentVariableError(access_token_error_message)\n        return access_token\n\n    @staticmethod\n    def get_header(access_token: Optional[str] = None) -> Dict[str, str]:\n        \"\"\"\n        Get the header dict to pass to httpx\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n\n        Returns\n        -------\n        Dict[str, str]\n        \"\"\"\n        access_token = APIConfig.get_access_token(access_token=access_token)\n        lunchable_header = {\n            \"Authorization\": f\"Bearer {access_token}\",\n            \"User-Agent\": f\"lunchable/{__version__}\",\n        }\n        lunchable_header.update(APIConfig.LUNCHMONEY_CONTENT_TYPE_HEADERS)\n        return lunchable_header\n\n    @staticmethod\n    def make_url(url_path: Union[List[Union[str, int]], str, int]) -> str:\n        \"\"\"\n        Make a Lunch Money API URL using path parts\n\n        Parameters\n        ----------\n        url_path: Union[List[Union[str, int]], str, int]\n            API Components, if a list join these sequentially\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if isinstance(url_path, str):\n            url_path = [url_path]\n        if not isinstance(url_path, List):\n            raise LunchMoneyError(\n                \"You must provide a string or list of strings to construct a URL\"\n            )\n        path_set = [\n            str(item).lower()\n            for item in url_path\n            if str(item).lower() != APIConfig.LUNCHMONEY_API_PATH\n        ]\n        url = APIConfig._generate_url(\n            scheme=APIConfig.LUNCHMONEY_SCHEME,\n            netloc=APIConfig.LUNCHMONEY_NETLOC,\n            path=\"/\".join([APIConfig.LUNCHMONEY_API_PATH, *path_set]),\n        )\n        return url\n\n    @classmethod\n    def _generate_url(\n        cls,\n        scheme: str,\n        netloc: str,\n        path: str = \"\",\n        params: str = \"\",\n        query: str = \"\",\n        fragment: str = \"\",\n    ) -> str:\n        \"\"\"\n        Build a URL\n\n        Parameters\n        ----------\n        scheme: str\n            URL scheme specifier\n        netloc: str\n            Network location part\n        path: str\n            Hierarchical path\n        params: str\n            Parameters for last path element\n        query: str\n            Query component\n        fragment: str\n            Fragment identifier\n        Returns\n        -------\n        url: str\n            Compiled URL\n        \"\"\"\n        url_components = {\n            \"scheme\": scheme,\n            \"netloc\": netloc,\n            \"path\": path,\n            \"params\": params,\n            \"query\": query,\n            \"fragment\": fragment,\n        }\n        return parse.urlunparse(components=tuple(url_components.values()))\n
"},{"location":"reference/_config/#lunchable._config.APIConfig.get_access_token","title":"get_access_token(access_token=None) staticmethod","text":"

Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None

Returns:

Type Description str Source code in lunchable/_config/api_config.py
@staticmethod\ndef get_access_token(access_token: Optional[str] = None) -> str:\n    \"\"\"\n    Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if access_token is None:\n        logger.info(\n            \"Loading Lunch Money Developer API Access token from environment\"\n        )\n        access_token = getenv(APIConfig._access_token_environment_variable, None)\n    if access_token is None:\n        access_token_error_message = (\n            \"You must provide a Lunch Money Developer API Access Token directly or set your \"\n            f\"{APIConfig._access_token_environment_variable} environment variable.\"\n        )\n        raise EnvironmentVariableError(access_token_error_message)\n    return access_token\n
"},{"location":"reference/_config/#lunchable._config.APIConfig.get_header","title":"get_header(access_token=None) staticmethod","text":"

Get the header dict to pass to httpx

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None

Returns:

Type Description Dict[str, str] Source code in lunchable/_config/api_config.py
@staticmethod\ndef get_header(access_token: Optional[str] = None) -> Dict[str, str]:\n    \"\"\"\n    Get the header dict to pass to httpx\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n\n    Returns\n    -------\n    Dict[str, str]\n    \"\"\"\n    access_token = APIConfig.get_access_token(access_token=access_token)\n    lunchable_header = {\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"User-Agent\": f\"lunchable/{__version__}\",\n    }\n    lunchable_header.update(APIConfig.LUNCHMONEY_CONTENT_TYPE_HEADERS)\n    return lunchable_header\n
"},{"location":"reference/_config/#lunchable._config.APIConfig.make_url","title":"make_url(url_path) staticmethod","text":"

Make a Lunch Money API URL using path parts

Parameters:

Name Type Description Default url_path Union[List[Union[str, int]], str, int]

API Components, if a list join these sequentially

required

Returns:

Type Description str Source code in lunchable/_config/api_config.py
@staticmethod\ndef make_url(url_path: Union[List[Union[str, int]], str, int]) -> str:\n    \"\"\"\n    Make a Lunch Money API URL using path parts\n\n    Parameters\n    ----------\n    url_path: Union[List[Union[str, int]], str, int]\n        API Components, if a list join these sequentially\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if isinstance(url_path, str):\n        url_path = [url_path]\n    if not isinstance(url_path, List):\n        raise LunchMoneyError(\n            \"You must provide a string or list of strings to construct a URL\"\n        )\n    path_set = [\n        str(item).lower()\n        for item in url_path\n        if str(item).lower() != APIConfig.LUNCHMONEY_API_PATH\n    ]\n    url = APIConfig._generate_url(\n        scheme=APIConfig.LUNCHMONEY_SCHEME,\n        netloc=APIConfig.LUNCHMONEY_NETLOC,\n        path=\"/\".join([APIConfig.LUNCHMONEY_API_PATH, *path_set]),\n    )\n    return url\n
"},{"location":"reference/_config/#lunchable._config.FileConfig","title":"FileConfig","text":"

Configuration Namespace for File Paths

Source code in lunchable/_config/file_config.py
class FileConfig:\n    \"\"\"\n    Configuration Namespace for File Paths\n    \"\"\"\n\n    HOME_DIR = Path.home()\n    _file_config_module = Path(__file__).resolve()\n    CONFIG_DIR = _file_config_module.parent\n    LUNCHMONEY_DIR = CONFIG_DIR.parent\n    PROJECT_DIR = LUNCHMONEY_DIR.parent\n    DATA_DIR = LUNCHMONEY_DIR.joinpath(\"data\")\n
"},{"location":"reference/_config/api_config/","title":"api_config","text":"

API Configuration Helper

"},{"location":"reference/_config/api_config/#lunchable._config.api_config.APIConfig","title":"APIConfig","text":"

Configuration Helper Class for Connecting to the Lunchmoney API

Source code in lunchable/_config/api_config.py
class APIConfig:\n    \"\"\"\n    Configuration Helper Class for Connecting to the Lunchmoney API\n    \"\"\"\n\n    LUNCHMONEY_SCHEME: str = \"https\"\n    LUNCHMONEY_NETLOC: str = \"dev.lunchmoney.app\"\n    LUNCHMONEY_API_PATH: str = \"v1\"\n\n    LUNCHMONEY_TRANSACTIONS: str = \"transactions\"\n    LUNCHMONEY_TRANSACTION_GROUPS: str = \"group\"\n    LUNCHMONEY_PLAID_ACCOUNTS: str = \"plaid_accounts\"\n    LUNCH_MONEY_RECURRING_EXPENSES: str = \"recurring_expenses\"\n    LUNCHMONEY_BUDGET: str = \"budgets\"\n    LUNCHMONEY_ASSETS: str = \"assets\"\n    LUNCHMONEY_CATEGORIES: str = \"categories\"\n    LUNCHMONEY_TAGS: str = \"tags\"\n    LUNCHMONEY_CRYPTO: str = \"crypto\"\n    LUNCHMONEY_CRYPTO_MANUAL: str = \"manual\"\n    LUNCHMONEY_ME: str = \"me\"\n\n    LUNCHMONEY_CONTENT_TYPE_HEADERS: Dict[str, str] = {\n        \"Content-Type\": \"application/json\"\n    }\n\n    _access_token_environment_variable = \"LUNCHMONEY_ACCESS_TOKEN\"\n\n    @staticmethod\n    def get_access_token(access_token: Optional[str] = None) -> str:\n        \"\"\"\n        Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if access_token is None:\n            logger.info(\n                \"Loading Lunch Money Developer API Access token from environment\"\n            )\n            access_token = getenv(APIConfig._access_token_environment_variable, None)\n        if access_token is None:\n            access_token_error_message = (\n                \"You must provide a Lunch Money Developer API Access Token directly or set your \"\n                f\"{APIConfig._access_token_environment_variable} environment variable.\"\n            )\n            raise EnvironmentVariableError(access_token_error_message)\n        return access_token\n\n    @staticmethod\n    def get_header(access_token: Optional[str] = None) -> Dict[str, str]:\n        \"\"\"\n        Get the header dict to pass to httpx\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n\n        Returns\n        -------\n        Dict[str, str]\n        \"\"\"\n        access_token = APIConfig.get_access_token(access_token=access_token)\n        lunchable_header = {\n            \"Authorization\": f\"Bearer {access_token}\",\n            \"User-Agent\": f\"lunchable/{__version__}\",\n        }\n        lunchable_header.update(APIConfig.LUNCHMONEY_CONTENT_TYPE_HEADERS)\n        return lunchable_header\n\n    @staticmethod\n    def make_url(url_path: Union[List[Union[str, int]], str, int]) -> str:\n        \"\"\"\n        Make a Lunch Money API URL using path parts\n\n        Parameters\n        ----------\n        url_path: Union[List[Union[str, int]], str, int]\n            API Components, if a list join these sequentially\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if isinstance(url_path, str):\n            url_path = [url_path]\n        if not isinstance(url_path, List):\n            raise LunchMoneyError(\n                \"You must provide a string or list of strings to construct a URL\"\n            )\n        path_set = [\n            str(item).lower()\n            for item in url_path\n            if str(item).lower() != APIConfig.LUNCHMONEY_API_PATH\n        ]\n        url = APIConfig._generate_url(\n            scheme=APIConfig.LUNCHMONEY_SCHEME,\n            netloc=APIConfig.LUNCHMONEY_NETLOC,\n            path=\"/\".join([APIConfig.LUNCHMONEY_API_PATH, *path_set]),\n        )\n        return url\n\n    @classmethod\n    def _generate_url(\n        cls,\n        scheme: str,\n        netloc: str,\n        path: str = \"\",\n        params: str = \"\",\n        query: str = \"\",\n        fragment: str = \"\",\n    ) -> str:\n        \"\"\"\n        Build a URL\n\n        Parameters\n        ----------\n        scheme: str\n            URL scheme specifier\n        netloc: str\n            Network location part\n        path: str\n            Hierarchical path\n        params: str\n            Parameters for last path element\n        query: str\n            Query component\n        fragment: str\n            Fragment identifier\n        Returns\n        -------\n        url: str\n            Compiled URL\n        \"\"\"\n        url_components = {\n            \"scheme\": scheme,\n            \"netloc\": netloc,\n            \"path\": path,\n            \"params\": params,\n            \"query\": query,\n            \"fragment\": fragment,\n        }\n        return parse.urlunparse(components=tuple(url_components.values()))\n
"},{"location":"reference/_config/api_config/#lunchable._config.api_config.APIConfig.get_access_token","title":"get_access_token(access_token=None) staticmethod","text":"

Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None

Returns:

Type Description str Source code in lunchable/_config/api_config.py
@staticmethod\ndef get_access_token(access_token: Optional[str] = None) -> str:\n    \"\"\"\n    Method for Resolving Access Tokens: Hardcoded -> .env File -> Env Var\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if access_token is None:\n        logger.info(\n            \"Loading Lunch Money Developer API Access token from environment\"\n        )\n        access_token = getenv(APIConfig._access_token_environment_variable, None)\n    if access_token is None:\n        access_token_error_message = (\n            \"You must provide a Lunch Money Developer API Access Token directly or set your \"\n            f\"{APIConfig._access_token_environment_variable} environment variable.\"\n        )\n        raise EnvironmentVariableError(access_token_error_message)\n    return access_token\n
"},{"location":"reference/_config/api_config/#lunchable._config.api_config.APIConfig.get_header","title":"get_header(access_token=None) staticmethod","text":"

Get the header dict to pass to httpx

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None

Returns:

Type Description Dict[str, str] Source code in lunchable/_config/api_config.py
@staticmethod\ndef get_header(access_token: Optional[str] = None) -> Dict[str, str]:\n    \"\"\"\n    Get the header dict to pass to httpx\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n\n    Returns\n    -------\n    Dict[str, str]\n    \"\"\"\n    access_token = APIConfig.get_access_token(access_token=access_token)\n    lunchable_header = {\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"User-Agent\": f\"lunchable/{__version__}\",\n    }\n    lunchable_header.update(APIConfig.LUNCHMONEY_CONTENT_TYPE_HEADERS)\n    return lunchable_header\n
"},{"location":"reference/_config/api_config/#lunchable._config.api_config.APIConfig.make_url","title":"make_url(url_path) staticmethod","text":"

Make a Lunch Money API URL using path parts

Parameters:

Name Type Description Default url_path Union[List[Union[str, int]], str, int]

API Components, if a list join these sequentially

required

Returns:

Type Description str Source code in lunchable/_config/api_config.py
@staticmethod\ndef make_url(url_path: Union[List[Union[str, int]], str, int]) -> str:\n    \"\"\"\n    Make a Lunch Money API URL using path parts\n\n    Parameters\n    ----------\n    url_path: Union[List[Union[str, int]], str, int]\n        API Components, if a list join these sequentially\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if isinstance(url_path, str):\n        url_path = [url_path]\n    if not isinstance(url_path, List):\n        raise LunchMoneyError(\n            \"You must provide a string or list of strings to construct a URL\"\n        )\n    path_set = [\n        str(item).lower()\n        for item in url_path\n        if str(item).lower() != APIConfig.LUNCHMONEY_API_PATH\n    ]\n    url = APIConfig._generate_url(\n        scheme=APIConfig.LUNCHMONEY_SCHEME,\n        netloc=APIConfig.LUNCHMONEY_NETLOC,\n        path=\"/\".join([APIConfig.LUNCHMONEY_API_PATH, *path_set]),\n    )\n    return url\n
"},{"location":"reference/_config/file_config/","title":"file_config","text":"

File Path Helper

"},{"location":"reference/_config/file_config/#lunchable._config.file_config.FileConfig","title":"FileConfig","text":"

Configuration Namespace for File Paths

Source code in lunchable/_config/file_config.py
class FileConfig:\n    \"\"\"\n    Configuration Namespace for File Paths\n    \"\"\"\n\n    HOME_DIR = Path.home()\n    _file_config_module = Path(__file__).resolve()\n    CONFIG_DIR = _file_config_module.parent\n    LUNCHMONEY_DIR = CONFIG_DIR.parent\n    PROJECT_DIR = LUNCHMONEY_DIR.parent\n    DATA_DIR = LUNCHMONEY_DIR.joinpath(\"data\")\n
"},{"location":"reference/_config/logging_config/","title":"logging_config","text":"

Dynamic Logging Configuration

"},{"location":"reference/_config/logging_config/#lunchable._config.logging_config.get_log_handler","title":"get_log_handler(log_level=None)","text":"

Determine which logging handler should be used

Parameters:

Name Type Description Default log_level Optional[int]

Which logging level should be used. If none is provided the LOG_LEVEL environment variable will be used, defaulting to \"INFO\".

None

Returns:

Type Description Tuple[Handler, Union[int, str]] Source code in lunchable/_config/logging_config.py
def get_log_handler(\n    log_level: Optional[int] = None,\n) -> Tuple[logging.Handler, Union[int, str]]:\n    \"\"\"\n    Determine which logging handler should be used\n\n    Parameters\n    ----------\n    log_level: Optional[int]\n        Which logging level should be used. If none is provided the LOG_LEVEL environment\n        variable will be used, defaulting to \"INFO\".\n\n    Returns\n    -------\n    Tuple[logging.Handler, Union[int, str]]\n    \"\"\"\n    if log_level is None:\n        log_level = logging.getLevelName(getenv(\"LOG_LEVEL\", \"INFO\").upper())\n    rich_handler = RichHandler(\n        level=log_level,\n        rich_tracebacks=True,\n        omit_repeated_times=False,\n        show_path=False,\n        tracebacks_suppress=[click],\n        console=Console(stderr=True),\n    )\n    httpx_logger = logging.getLogger(\"httpx\")\n    if log_level != logging.DEBUG:\n        httpx_logger.setLevel(logging.WARNING)\n    python_handler = logging.StreamHandler()\n    python_formatter = logging.Formatter(\"%(asctime)s [%(levelname)8s]: %(message)s\")\n    python_handler.setFormatter(python_formatter)\n    python_handler.setLevel(log_level)\n    _log_dict = {\n        \"rich\": rich_handler,\n        \"python\": python_handler,\n    }\n    if getenv(\"PYTEST_CURRENT_TEST\", None) is not None:\n        handler = \"python\"\n    else:\n        handler = LOG_HANDLER\n    log_handler: logging.Handler = _log_dict.get(handler, rich_handler)\n    return log_handler, log_level\n
"},{"location":"reference/_config/logging_config/#lunchable._config.logging_config.set_up_logging","title":"set_up_logging(log_level=None)","text":"

Set Up a Root Logger

Parameters:

Name Type Description Default log_level Optional[int]

Which logging level should be used. If none is provided the LOG_LEVEL environment variable will be used, defaulting to \"INFO\".

None Source code in lunchable/_config/logging_config.py
def set_up_logging(log_level: Optional[int] = None) -> None:\n    \"\"\"\n    Set Up a Root Logger\n\n    Parameters\n    ----------\n    log_level: Optional[int]\n        Which logging level should be used. If none is provided the LOG_LEVEL environment\n        variable will be used, defaulting to \"INFO\".\n    \"\"\"\n    log_handler, level_to_log = get_log_handler(log_level=log_level)\n    logging.root.handlers = [log_handler]\n    if isinstance(log_handler, RichHandler):\n        rich_formatter = logging.Formatter(\n            datefmt=\"[%Y-%m-%d %H:%M:%S]\", fmt=\"%(message)s\"\n        )\n        logging.root.handlers[0].setFormatter(rich_formatter)\n        level_to_log = logging.NOTSET\n    logging.root.setLevel(level_to_log)\n
"},{"location":"reference/models/","title":"models","text":"

Lunch Money Python SDK and Associated Objects

"},{"location":"reference/models/#lunchable.models.AssetsObject","title":"AssetsObject","text":"

Bases: LunchableModel

Manually Managed Asset Objects

Assets in Lunch Money are similar to plaid-accounts except that they are manually managed.

https://lunchmoney.dev/#assets-object

Parameters:

Name Type Description Default id int

Unique identifier for asset

required type_name str

Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan, cryptocurrency, investment, other, credit, real estate]

required subtype_name str | None

Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]

None name str

Name of the asset

required display_name str | None

Display name of the asset (as set by user)

None balance float

Current balance of the asset in numeric format to 4 decimal places

required balance_as_of datetime

Date/time the balance was last updated in ISO 8601 extended format

required closed_on date | None

The date this asset was closed (optional)

None currency str

Three-letter lowercase currency code of the balance in ISO 4217 format

required institution_name str | None

Name of institution holding the asset

None exclude_transactions bool

If true, this asset will not show up as an option for assignment when creating transactions manually

False created_at datetime

Date/time the asset was created in ISO 8601 extended format

required Source code in lunchable/models/assets.py
class AssetsObject(LunchableModel):\n    \"\"\"\n    Manually Managed Asset Objects\n\n    Assets in Lunch Money are similar to `plaid-accounts` except that they are manually managed.\n\n    https://lunchmoney.dev/#assets-object\n    \"\"\"\n\n    _type_name_description = \"\"\"\n    Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan,\n    cryptocurrency, investment, other, credit, real estate]\n    \"\"\"\n    _subtype_name_description = \"\"\"\n    Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]\n    \"\"\"\n    _balance_description = (\n        \"Current balance of the asset in numeric format to 4 decimal places\"\n    )\n    _balance_as_of_description = \"\"\"\n    Date/time the balance was last updated in ISO 8601 extended format\n    \"\"\"\n    _closed_on_description = \"The date this asset was closed (optional)\"\n    _currency_description = (\n        \"Three-letter lowercase currency code of the balance in ISO 4217 format\"\n    )\n    _created_at_description = (\n        \"Date/time the asset was created in ISO 8601 extended format\"\n    )\n    _exclude_transactions_description = (\n        \"If true, this asset will not show up as an \"\n        \"option for assignment when creating \"\n        \"transactions manually\"\n    )\n\n    id: int = Field(description=\"Unique identifier for asset\")\n    type_name: str = Field(description=_type_name_description)\n    subtype_name: Optional[str] = Field(None, description=_subtype_name_description)\n    name: str = Field(description=\"Name of the asset\")\n    display_name: Optional[str] = Field(\n        None, description=\"Display name of the asset (as set by user)\"\n    )\n    balance: float = Field(description=_balance_description)\n    balance_as_of: datetime.datetime = Field(description=_balance_as_of_description)\n    closed_on: Optional[datetime.date] = Field(None, description=_closed_on_description)\n    currency: str = Field(description=_currency_description)\n    institution_name: Optional[str] = Field(\n        None, description=\"Name of institution holding the asset\"\n    )\n    exclude_transactions: bool = Field(\n        default=False, description=_exclude_transactions_description\n    )\n    created_at: datetime.datetime = Field(description=_created_at_description)\n
"},{"location":"reference/models/#lunchable.models.BudgetObject","title":"BudgetObject","text":"

Bases: LunchableModel

Monthly Budget Per Category Object

https://lunchmoney.dev/#budget-object

Parameters:

Name Type Description Default category_name str

Name of the category

required category_id int | None

Unique identifier for category

None category_group_name str | None

Name of the category group, if applicable

None group_id int | None

Unique identifier for category group

None is_group bool | None

If true, this category is a group

None is_income bool

If true, this category is an income category (category properties are set in the app via the Categories page)

required exclude_from_budget bool

If true, this category is excluded from budget (category properties are set in the app via the Categories page)

required exclude_from_totals bool

If true, this category is excluded from totals (category properties are set in the app via the Categories page)

required data Dict[date, BudgetDataObject]

For each month with budget or category spending data, there is a data object with the key set to the month in format YYYY-MM-DD. For properties, see Data object below.

required config BudgetConfigObject | None

Object representing the category's budget suggestion configuration

None Source code in lunchable/models/budgets.py
class BudgetObject(LunchableModel):\n    \"\"\"\n    Monthly Budget Per Category Object\n\n    https://lunchmoney.dev/#budget-object\n    \"\"\"\n\n    _category_group_name_description = \"Name of the category group, if applicable\"\n    _is_income_description = \"\"\"\n    If true, this category is an income category (category properties\n    are set in the app via the Categories page)\n    \"\"\"\n    _exclude_from_budget_description = \"\"\"\n    If true, this category is excluded from budget (category\n    properties are set in the app via the Categories page)\n    \"\"\"\n    _exclude_from_totals_description = \"\"\"\n    If true, this category is excluded from totals (category\n    properties are set in the app via the Categories page)\n    \"\"\"\n    _data_description = \"\"\"\n    For each month with budget or category spending data, there is a data object with the key\n    set to the month in format YYYY-MM-DD. For properties, see Data object below.\n    \"\"\"\n    _config_description = \"\"\"\n    Object representing the category's budget suggestion configuration\n    \"\"\"\n\n    category_name: str = Field(description=\"Name of the category\")\n    category_id: Optional[int] = Field(\n        None, description=\"Unique identifier for category\"\n    )\n    category_group_name: Optional[str] = Field(\n        None, description=_category_group_name_description\n    )\n    group_id: Optional[int] = Field(\n        None, description=\"Unique identifier for category group\"\n    )\n    is_group: Optional[bool] = Field(\n        None, description=\"If true, this category is a group\"\n    )\n    is_income: bool = Field(description=_is_income_description)\n    exclude_from_budget: bool = Field(description=_exclude_from_budget_description)\n    exclude_from_totals: bool = Field(description=_exclude_from_totals_description)\n    data: Dict[datetime.date, BudgetDataObject] = Field(description=_data_description)\n    config: Optional[BudgetConfigObject] = Field(None, description=_config_description)\n
"},{"location":"reference/models/#lunchable.models.CategoriesObject","title":"CategoriesObject","text":"

Bases: LunchableModel

Lunch Money Spending Categories

https://lunchmoney.dev/#categories-object

Parameters:

Name Type Description Default id int

A unique identifier for the category.

required name str

The name of the category. Must be between 1 and 40 characters.

required description str | None

The description of the category. Must not exceed 140 characters.

None is_income bool

If true, the transactions in this category will be treated as income.

required exclude_from_budget bool

If true, the transactions in this category will be excluded from the budget.

required exclude_from_totals bool

If true, the transactions in this category will be excluded from totals.

required updated_at datetime | None

The date and time of when the category was last updated (in the ISO 8601 extended format).

None created_at datetime | None

The date and time of when the category was created (in the ISO 8601 extended format).

None is_group bool

If true, the category is a group that can be a parent to other categories.

required group_id int | None

The ID of a category group (or null if the category doesn't belong to a category group).

None children List[CategoryChild] | None

For category groups, this will populate with the categories nested within and include id, name, description and created_at fields.

None Source code in lunchable/models/categories.py
class CategoriesObject(LunchableModel):\n    \"\"\"\n    Lunch Money Spending Categories\n\n    https://lunchmoney.dev/#categories-object\n    \"\"\"\n\n    _name_description = \"The name of the category. Must be between 1 and 40 characters.\"\n    _description_description = (\n        \"The description of the category. Must not exceed 140 characters.\"\n    )\n    _is_income_description = (\n        \"If true, the transactions in this category will be treated as income.\"\n    )\n    _exclude_from_budget_description = \"\"\"\n    If true, the transactions in this category will be excluded from the budget.\n    \"\"\"\n    _exclude_from_totals_description = \"\"\"\n    If true, the transactions in this category will be excluded from totals.\n    \"\"\"\n    _updated_at_description = \"\"\"\n    The date and time of when the category was last updated (in the ISO 8601 extended format).\n    \"\"\"\n    _created_at_description = \"\"\"\n    The date and time of when the category was created (in the ISO 8601 extended format).\n    \"\"\"\n    _is_group_description = \"\"\"\n    If true, the category is a group that can be a parent to other categories.\n    \"\"\"\n    _group_id_description = \"\"\"\n    The ID of a category group (or null if the category doesn't belong to a category group).\n    \"\"\"\n    _children_description = (\n        \"For category groups, this will populate with the \"\n        \"categories nested within and include id, name, \"\n        \"description and created_at fields.\"\n    )\n\n    id: int = Field(description=\"A unique identifier for the category.\")\n    name: str = Field(min_length=1, max_length=40, description=_name_description)\n    description: Optional[str] = Field(\n        None, max_length=140, description=_description_description\n    )\n    is_income: bool = Field(description=_is_income_description)\n    exclude_from_budget: bool = Field(description=_exclude_from_budget_description)\n    exclude_from_totals: bool = Field(description=_exclude_from_totals_description)\n    updated_at: Optional[datetime.datetime] = Field(\n        None, description=_updated_at_description\n    )\n    created_at: Optional[datetime.datetime] = Field(\n        None, description=_created_at_description\n    )\n    is_group: bool = Field(description=_is_group_description)\n    group_id: Optional[int] = Field(None, description=_group_id_description)\n    children: Optional[List[CategoryChild]] = Field(\n        None, description=_children_description\n    )\n
"},{"location":"reference/models/#lunchable.models.CryptoObject","title":"CryptoObject","text":"

Bases: LunchableModel

Crypto Asset Object

https://lunchmoney.dev/#crypto-object

Parameters:

Name Type Description Default id int

Unique identifier for a manual crypto account (no ID for synced accounts)

required zabo_account_id int | None

Unique identifier for a synced crypto account (no ID for manual accounts, multiple currencies may have the same zabo_account_id)

None source str

synced (this account is synced via a wallet, exchange, etc.) or manual (this account balance is managed manually)

required name str

Name of the crypto asset

required display_name str | None

Display name of the crypto asset (as set by user)

None balance float

Current balance

required balance_as_of datetime | None

Date/time the balance was last updated in ISO 8601 extended format

None currency str | None

Abbreviation for the cryptocurrency

None status str | None

The current status of the crypto account. Either active or in error.

None institution_name str | None

Name of provider holding the asset

None created_at datetime

Date/time the asset was created in ISO 8601 extended format

required Source code in lunchable/models/crypto.py
class CryptoObject(LunchableModel):\n    \"\"\"\n    Crypto Asset Object\n\n    https://lunchmoney.dev/#crypto-object\n    \"\"\"\n\n    _id_description = (\n        \"Unique identifier for a manual crypto account (no ID for synced accounts)\"\n    )\n    _zabo_account_id_description = \"\"\"\n    Unique identifier for a synced crypto account (no ID for manual accounts,\n    multiple currencies may have the same zabo_account_id)\n    \"\"\"\n    _source_description = \"\"\"\n    `synced` (this account is synced via a wallet, exchange, etc.) or `manual` (this account\n    balance is managed manually)\n    \"\"\"\n    _display_name_description = \"Display name of the crypto asset (as set by user)\"\n    _balance_as_of_description = \"\"\"\n    Date/time the balance was last updated in ISO 8601 extended format\n    \"\"\"\n    _status_description = (\n        \"The current status of the crypto account. Either active or in error.\"\n    )\n    _created_at_description = (\n        \"Date/time the asset was created in ISO 8601 extended format\"\n    )\n\n    id: int = Field(description=_id_description)\n    zabo_account_id: Optional[int] = Field(\n        None, description=_zabo_account_id_description\n    )\n    source: str = Field(description=_source_description)\n    name: str = Field(description=\"Name of the crypto asset\")\n    display_name: Optional[str] = Field(None, description=_display_name_description)\n    balance: float = Field(description=\"Current balance\")\n    balance_as_of: Optional[datetime.datetime] = Field(\n        None, description=_balance_as_of_description\n    )\n    currency: Optional[str] = Field(\n        None, description=\"Abbreviation for the cryptocurrency\"\n    )\n    status: Optional[str] = Field(None, description=_status_description)\n    institution_name: Optional[str] = Field(\n        default=None, description=\"Name of provider holding the asset\"\n    )\n    created_at: datetime.datetime = Field(description=_created_at_description)\n
"},{"location":"reference/models/#lunchable.models.LunchableModel","title":"LunchableModel","text":"

Bases: BaseModel

Hashable Pydantic Model

Source code in lunchable/models/_base.py
class LunchableModel(BaseModel):\n    \"\"\"\n    Hashable Pydantic Model\n    \"\"\"\n\n    def __hash__(self) -> int:\n        \"\"\"\n        Hash Method for Pydantic BaseModels\n        \"\"\"\n        return hash((type(self), *tuple(self.__dict__.values())))\n
"},{"location":"reference/models/#lunchable.models.LunchableModel.__hash__","title":"__hash__()","text":"

Hash Method for Pydantic BaseModels

Source code in lunchable/models/_base.py
def __hash__(self) -> int:\n    \"\"\"\n    Hash Method for Pydantic BaseModels\n    \"\"\"\n    return hash((type(self), *tuple(self.__dict__.values())))\n
"},{"location":"reference/models/#lunchable.models.PlaidAccountObject","title":"PlaidAccountObject","text":"

Bases: LunchableModel

Assets synced from Plaid

Similar to AssetObjects, these accounts are linked to remote sources in Plaid.

https://lunchmoney.dev/#plaid-accounts-object

Parameters:

Name Type Description Default id int

Unique identifier of Plaid account

required date_linked date

Date account was first linked in ISO 8601 extended format

required name str

Name of the account. Can be overridden by the user. Field is originally set by Plaid\")

required type str

Primary type of account. Typically one of: [credit, depository, brokerage, cash, loan, investment]. This field is set by Plaid and cannot be altered.

required subtype str

Optional subtype name of account. This field is set by Plaid and cannot be altered

required mask str | None

Mask (last 3 to 4 digits of account) of account. This field is set by Plaid and cannot be altered

None institution_name str

Name of institution associated with account. This field is set by Plaid and cannot be altered

required status str

Denotes the current status of the account within Lunch Money. Must be one of: active (Account is active and in good state), inactive (Account marked inactive from user. No transactions fetched or balance update for this account), relink (Account needs to be relinked with Plaid), syncing (Account is awaiting first import of transactions), error (Account is in error with Plaid), not found (Account is in error with Plaid), not supported (Account is in error with Plaid)

required last_import datetime | None

Date of last imported transaction in ISO 8601 extended format (not necessarily date of last attempted import)

None balance float | None

Current balance of the account in numeric format to 4 decimal places. This field is set by Plaid and cannot be altered

None currency str

Currency of account balance in ISO 4217 format. This field is set by Plaid and cannot be altered

required balance_last_update datetime

Date balance was last updated in ISO 8601 extended format. This field is set by Plaid and cannot be altered

required limit int | None

Optional credit limit of the account. This field is set by Plaid and cannot be altered

None Source code in lunchable/models/plaid_accounts.py
class PlaidAccountObject(LunchableModel):\n    \"\"\"\n    Assets synced from Plaid\n\n    Similar to AssetObjects, these accounts are linked to remote sources in Plaid.\n\n    https://lunchmoney.dev/#plaid-accounts-object\n    \"\"\"\n\n    _date_linked_description = (\n        \"Date account was first linked in ISO 8601 extended format\"\n    )\n    _name_description = \"\"\"\n    Name of the account. Can be overridden by the user. Field is originally set by Plaid\")\n    \"\"\"\n    _type_description = \"\"\"\n    Primary type of account. Typically one of: [credit, depository, brokerage, cash,\n    loan, investment]. This field is set by Plaid and cannot be altered.\n    \"\"\"\n    _subtype_description = \"\"\"\n    Optional subtype name of account. This field is set by Plaid and cannot be altered\n    \"\"\"\n    _mask_description = \"\"\"\n    Mask (last 3 to 4 digits of account) of account. This field is set by\n    Plaid and cannot be altered\n    \"\"\"\n    _institution_name_description = \"\"\"\n    Name of institution associated with account. This field is set by\n    Plaid and cannot be altered\n    \"\"\"\n    _status_description = \"\"\"\n    Denotes the current status of the account within Lunch Money. Must be one of:\n    active (Account is active and in good state),\n    inactive (Account marked inactive from user. No transactions fetched or\n    balance update for this account),\n    relink (Account needs to be relinked with Plaid),\n    syncing (Account is awaiting first import of transactions),\n    error (Account is in error with Plaid),\n    not found (Account is in error with Plaid),\n    not supported (Account is in error with Plaid)\n    \"\"\"\n    _last_import_description = \"\"\"\n    Date of last imported transaction in ISO 8601 extended format (not necessarily\n    date of last attempted import)\n    \"\"\"\n    _balance_description = \"\"\"\n    Current balance of the account in numeric format to 4 decimal places. This field is\n    set by Plaid and cannot be altered\n    \"\"\"\n    _currency_description = \"\"\"\n    Currency of account balance in ISO 4217 format. This field is set by Plaid\n    and cannot be altered\n    \"\"\"\n    _balance_last_update_description = \"\"\"\n    Date balance was last updated in ISO 8601 extended format. This field is set\n    by Plaid and cannot be altered\n    \"\"\"\n    _limit_description = \"\"\"\n    Optional credit limit of the account. This field is set by Plaid and cannot be altered\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier of Plaid account\")\n    date_linked: datetime.date = Field(description=_date_linked_description)\n    name: str = Field(description=_name_description)\n    type: str = Field(description=_type_description)\n    subtype: str = Field(description=_subtype_description)\n    mask: Optional[str] = Field(None, description=_mask_description)\n    institution_name: str = Field(description=_institution_name_description)\n    status: str = Field(description=_status_description)\n    last_import: Optional[datetime.datetime] = Field(\n        None, description=_last_import_description\n    )\n    balance: Optional[float] = Field(None, description=_balance_description)\n    currency: str = Field(description=_currency_description)\n    balance_last_update: datetime.datetime = Field(\n        description=_balance_last_update_description\n    )\n    limit: Optional[int] = Field(None, description=_limit_description)\n
"},{"location":"reference/models/#lunchable.models.RecurringExpensesObject","title":"RecurringExpensesObject","text":"

Bases: LunchableModel

Recurring Expenses Object

https://lunchmoney.dev/#recurring-expenses-object

Parameters:

Name Type Description Default id int

Unique identifier for recurring expense

required start_date date | None

Denotes when recurring expense starts occurring in ISO 8601 format. If null, then this recurring expense will show up for all time before end_date

None end_date date | None

Denotes when recurring expense stops occurring in ISO 8601 format. If null, then this recurring expense has no set end date and will show up for all months after start_date

None cadence str

One of: [monthly, twice a month, once a week, every 3 months, every 4 months, twice a year, yearly]

required payee str

Payee of the recurring expense

required amount float

Amount of the recurring expense in numeric format to 4 decimal places

required currency str

Three-letter lowercase currency code for the recurring expense in ISO 4217 format

required description str | None

If any, represents the user-entered description of the recurring expense

None billing_date date

Expected billing date for this recurring expense for this month in ISO 8601 format

required type str

\" This can be one of two values: cleared (The recurring expense has been reviewed by the user), suggested (The recurring expense is suggested by the system; the user has yet to review/clear it)

required original_name str | None

If any, represents the original name of the recurring expense as denoted by the transaction that triggered its creation

None source str

This can be one of three values: manual (User created this recurring expense manually from the Recurring Expenses page), transaction (User created this by converting a transaction from the Transactions page), system (Recurring expense was created by the system on transaction import). Some older recurring expenses may not have a source.

required plaid_account_id int | None

If any, denotes the plaid account associated with the creation of this \" recurring expense (see Plaid Accounts)\"

None asset_id int | None

If any, denotes the manually-managed account (i.e. asset) associated with the creation of this recurring expense (see Assets)

None transaction_id int | None

If any, denotes the unique identifier for the associated transaction matching this recurring expense for the current time period

None category_id int | None

If any, denotes the unique identifier for the associated category to this recurring expense

None Source code in lunchable/models/recurring_expenses.py
class RecurringExpensesObject(LunchableModel):\n    \"\"\"\n    Recurring Expenses Object\n\n    https://lunchmoney.dev/#recurring-expenses-object\n    \"\"\"\n\n    _id_description = \"Unique identifier for recurring expense\"\n    _start_date_description = \"\"\"\n    Denotes when recurring expense starts occurring in ISO 8601 format.\n    If null, then this recurring expense will show up for all time\n    before end_date\n    \"\"\"\n    _end_date_description = \"\"\"\n    Denotes when recurring expense stops occurring in ISO 8601 format.\n    If null, then this recurring expense has no set end date and will\n    show up for all months after start_date\n    \"\"\"\n    _cadence_description = \"\"\"\n    One of: [monthly, twice a month, once a week, every 3 months, every 4 months,\n    twice a year, yearly]\n    \"\"\"\n    _amount_description = (\n        \"Amount of the recurring expense in numeric format to 4 decimal places\"\n    )\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code for the recurring expense in ISO 4217 format\n    \"\"\"\n    _description_description = \"\"\"\n    If any, represents the user-entered description of the recurring expense\n    \"\"\"\n    _billing_date_description = \"\"\"\n    Expected billing date for this recurring expense for this month in ISO 8601 format\n    \"\"\"\n    _type_description = \"\"\"\"\n    This can be one of two values: cleared (The recurring expense has been reviewed\n    by the user), suggested (The recurring expense is suggested by the system;\n    the user has yet to review/clear it)\n    \"\"\"\n    _original_name_description = \"\"\"\n    If any, represents the original name of the recurring expense as\n    denoted by the transaction that triggered its creation\n    \"\"\"\n    _source_description = \"\"\"\n    This can be one of three values: manual (User created this recurring expense\n    manually from the Recurring Expenses page), transaction (User created this by\n    converting a transaction from the Transactions page), system (Recurring expense\n    was created by the system on transaction import). Some older recurring expenses\n    may not have a source.\n    \"\"\"\n    _plaid_account_id_description = \"\"\"\n    If any, denotes the plaid account associated with the creation of this \"\n    recurring expense (see Plaid Accounts)\"\n    \"\"\"\n    _asset_id_description = \"\"\"\n    If any, denotes the manually-managed account (i.e. asset) associated with the\n    creation of this recurring expense (see Assets)\n    \"\"\"\n    _transaction_id_description = \"\"\"\n    If any, denotes the unique identifier for the associated transaction matching\n    this recurring expense for the current time period\n    \"\"\"\n    _category_id_description = \"\"\"\n    If any, denotes the unique identifier for the associated category to this recurring expense\n    \"\"\"\n\n    id: int = Field(description=_id_description)\n    start_date: Optional[datetime.date] = Field(\n        None, description=_start_date_description\n    )\n    end_date: Optional[datetime.date] = Field(None, description=_end_date_description)\n    cadence: str = Field(description=_cadence_description)\n    payee: str = Field(description=\"Payee of the recurring expense\")\n    amount: float = Field(description=_amount_description)\n    currency: str = Field(max_length=3, description=_currency_description)\n    description: Optional[str] = Field(None, description=_description_description)\n    billing_date: datetime.date = Field(description=_billing_date_description)\n    type: str = Field(description=_type_description)\n    original_name: Optional[str] = Field(None, description=_original_name_description)\n    source: str = Field(description=_source_description)\n    plaid_account_id: Optional[int] = Field(\n        None, description=_plaid_account_id_description\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    transaction_id: Optional[int] = Field(None, description=_transaction_id_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n
"},{"location":"reference/models/#lunchable.models.TagsObject","title":"TagsObject","text":"

Bases: LunchableModel

Lunchmoney Tags object

https://lunchmoney.dev/#tags-object

Parameters:

Name Type Description Default id int

Unique identifier for tag

required name str

User-defined name of tag

required description str | None

User-defined description of tag

None Source code in lunchable/models/tags.py
class TagsObject(LunchableModel):\n    \"\"\"\n    Lunchmoney Tags object\n\n    https://lunchmoney.dev/#tags-object\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier for tag\")\n    name: str = Field(description=\"User-defined name of tag\", min_length=1)\n    description: Optional[str] = Field(\n        None, description=\"User-defined description of tag\"\n    )\n
"},{"location":"reference/models/#lunchable.models.TransactionBaseObject","title":"TransactionBaseObject","text":"

Bases: LunchableModel

Base Model For All Transactions to Inherit From

Source code in lunchable/models/transactions.py
class TransactionBaseObject(LunchableModel):\n    \"\"\"\n    Base Model For All Transactions to Inherit From\n    \"\"\"\n\n    pass\n
"},{"location":"reference/models/#lunchable.models.TransactionInsertObject","title":"TransactionInsertObject","text":"

Bases: TransactionBaseObject

Object For Creating New Transactions

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required amount float

Numeric value of amount. i.e. $4.25 should be denoted as 4.25.

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None currency str | None

Three-letter lowercase currency code in ISO 4217 format. The code sent must exist in our database. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account.

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id.

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionInsertObject(TransactionBaseObject):\n    \"\"\"\n    Object For Creating New Transactions\n\n    https://lunchmoney.dev/#insert-transactions\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _amount_description = \"\"\"\n    Numeric value of amount. i.e. $4.25 should be denoted as 4.25.\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated with\n    the same account and must not be a category group.\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code in ISO 4217 format. The code sent must exist\n    in our database. Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account.\n    \"\"\"\n    _recurring_id = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must be associated\n    with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. If recurring_id is provided, the status will\n    automatically be set to recurring or recurring_suggested depending on the type of\n    recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id.\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found, an error\n    will be thrown. Passing in a string will attempt to match by string. If no matching tag\n    name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: datetime.date = Field(description=_date_description)\n    amount: float = Field(description=_amount_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    currency: Optional[str] = Field(\n        None, description=_currency_description, max_length=3\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(\n        None, description=_external_id_description, max_length=75\n    )\n    tags: Optional[List[Union[str, int]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/models/#lunchable.models.TransactionInsertObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/models/#lunchable.models.TransactionObject","title":"TransactionObject","text":"

Bases: TransactionBaseObject

Universal Lunch Money Transaction Object

https://lunchmoney.dev/#transaction-object

Parameters:

Name Type Description Default id int

Unique identifier for transaction

required date date

Date of transaction in ISO 8601 format

required payee str | None

Name of payee If recurring_id is not null, this field will show the payee of associated recurring expense instead of the original transaction payee

None amount float

Amount of the transaction in numeric format to 4 decimal places

required currency str | None

Three-letter lowercase currency code of the transaction in ISO 4217 format

None notes str | None

User-entered transaction notes If recurring_id is not null, this field will be description of associated recurring expense

None category_id int | None

Unique identifier of associated category (see Categories)

None asset_id int | None

Unique identifier of associated manually-managed account (see Assets) Note: plaid_account_id and asset_id cannot both exist for a transaction

None plaid_account_id int | None

Unique identifier of associated Plaid account (see Plaid Accounts) Note: plaid_account_id and asset_id cannot both exist for a transaction

None status str | None

One of the following: cleared: User has reviewed the transaction | uncleared: User has not yet reviewed the transaction | recurring: Transaction is linked to a recurring expense | recurring_suggested: Transaction is listed as a suggested transaction for an existing recurring expense | pending: Imported transaction is marked as pending. This should be a temporary state. User intervention is required to change this to recurring.

None parent_id int | None

Exists if this is a split transaction. Denotes the transaction ID of the original transaction. Note that the parent transaction is not returned in this call.

None is_group bool | None

True if this transaction represents a group of transactions. If so, amount and currency represent the totalled amount of transactions bearing this transaction's id as their group_id. Amount is calculated based on the user's primary currency.

None group_id int | None

Exists if this transaction is part of a group. Denotes the parent's transaction ID

None tags List[TagsObject] | None

Array of Tag objects

None external_id str | None

User-defined external ID for any manually-entered or imported transaction. External ID cannot be accessed or changed for Plaid-imported transactions. External ID must be unique by asset_id. Max 75 characters.

None original_name str | None

The transactions original name before any payee name updates. For synced transactions, this is the raw original payee name from your bank.

None type str | None

(for synced investment transactions only) The transaction type as set by Plaid for investment transactions. Possible values include: buy, sell, cash, transfer and more

None subtype str | None

(for synced investment transactions only) The transaction type as set by Plaid for investment transactions. Possible values include: management fee, withdrawal, dividend, deposit and more

None fees str | None

(for synced investment transactions only) The fees as set by Plaid for investment transactions.

None price str | None

(for synced investment transactions only) The price as set by Plaid for investment transactions.

None quantity str | None

(for synced investment transactions only) The quantity as set by Plaid for investment transactions.

None Source code in lunchable/models/transactions.py
class TransactionObject(TransactionBaseObject):\n    \"\"\"\n    Universal Lunch Money Transaction Object\n\n    https://lunchmoney.dev/#transaction-object\n    \"\"\"\n\n    _amount_description = \"\"\"\n    Amount of the transaction in numeric format to 4 decimal places\n    \"\"\"\n    _payee_description = \"\"\"\n    Name of payee If recurring_id is not null, this field will show the payee\n    of associated recurring expense instead of the original transaction payee\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code of the transaction in ISO 4217 format\n    \"\"\"\n    _notes_description = \"\"\"\n    User-entered transaction notes If recurring_id is not null, this field will\n    be description of associated recurring expense\n    \"\"\"\n    _category_description = \"\"\"\n    Unique identifier of associated category (see Categories)\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier of associated manually-managed account (see Assets)\n    Note: plaid_account_id and asset_id cannot both exist for a transaction\n    \"\"\"\n    _plaid_account_id_description = \"\"\"\n    Unique identifier of associated Plaid account (see Plaid Accounts) Note:\n    plaid_account_id and asset_id cannot both exist for a transaction\n    \"\"\"\n    _status_description = \"\"\"\n    One of the following: cleared: User has reviewed the transaction | uncleared:\n    User has not yet reviewed the transaction | recurring: Transaction is linked\n    to a recurring expense | recurring_suggested: Transaction is listed as a\n    suggested transaction for an existing recurring expense | pending: Imported\n    transaction is marked as pending. This should be a temporary state. User intervention\n    is required to change this to recurring.\n    \"\"\"\n    _parent_id_description = \"\"\"\n    Exists if this is a split transaction. Denotes the transaction ID of the original\n    transaction. Note that the parent transaction is not returned in this call.\n    \"\"\"\n    _is_group_description = \"\"\"\n    True if this transaction represents a group of transactions. If so, amount\n    and currency represent the totalled amount of transactions bearing this\n    transaction's id as their group_id. Amount is calculated based on the\n    user's primary currency.\n    \"\"\"\n    _group_id_description = \"\"\"\n    Exists if this transaction is part of a group. Denotes the parent's transaction ID\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for any manually-entered or imported transaction.\n    External ID cannot be accessed or changed for Plaid-imported transactions.\n    External ID must be unique by asset_id. Max 75 characters.\n    \"\"\"\n    _original_name_description = \"\"\"\n    The transactions original name before any payee name updates. For synced transactions,\n    this is the raw original payee name from your bank.\n    \"\"\"\n    _type_description = \"\"\"\n    (for synced investment transactions only) The transaction type as set by\n    Plaid for investment transactions. Possible values include: buy, sell, cash,\n    transfer and more\n    \"\"\"\n    _subtype_description = \"\"\"\n    (for synced investment transactions only) The transaction type as set by Plaid\n    for investment transactions. Possible values include: management fee, withdrawal,\n    dividend, deposit and more\n    \"\"\"\n    _fees_description = \"\"\"\n    (for synced investment transactions only) The fees as set by Plaid for investment\n    transactions.\n    \"\"\"\n    _price_description = \"\"\"\n    (for synced investment transactions only) The price as set by Plaid for investment\n    transactions.\n    \"\"\"\n    _quantity_description = \"\"\"\n    (for synced investment transactions only) The quantity as set by Plaid for investment\n    transactions.\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier for transaction\")\n    date: datetime.date = Field(description=\"Date of transaction in ISO 8601 format\")\n    payee: Optional[str] = Field(None, description=_payee_description)\n    amount: float = Field(description=_amount_description)\n    currency: Optional[str] = Field(\n        None, max_length=3, description=_currency_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    category_id: Optional[int] = Field(None, description=_category_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    plaid_account_id: Optional[int] = Field(\n        None, description=_plaid_account_id_description\n    )\n    status: Optional[str] = Field(None, description=_status_description)\n    parent_id: Optional[int] = Field(None, description=_parent_id_description)\n    is_group: Optional[bool] = Field(None, description=_is_group_description)\n    group_id: Optional[int] = Field(None, description=_group_id_description)\n    tags: Optional[List[TagsObject]] = Field(None, description=\"Array of Tag objects\")\n    external_id: Optional[str] = Field(\n        None, max_length=75, description=_external_id_description\n    )\n    original_name: Optional[str] = Field(None, description=_original_name_description)\n    type: Optional[str] = Field(None, description=_type_description)\n    subtype: Optional[str] = Field(None, description=_subtype_description)\n    fees: Optional[str] = Field(None, description=_fees_description)\n    price: Optional[str] = Field(None, description=_price_description)\n    quantity: Optional[str] = Field(None, description=_quantity_description)\n\n    def get_update_object(self) -> TransactionUpdateObject:\n        \"\"\"\n        Return a TransactionUpdateObject\n\n        Return a TransactionUpdateObject to update an expense. Simply\n        change one of the properties and perform an `update_transaction` with\n        your Lunchable object.\n\n        Returns\n        -------\n        TransactionUpdateObject\n        \"\"\"\n        update_dict = self.model_dump()\n        try:\n            TransactionUpdateObject.StatusEnum(self.status)\n        except ValueError:\n            update_dict[\"status\"] = None\n        update_object = TransactionUpdateObject.model_validate(update_dict)\n        if update_object.tags is not None:\n            tags = [] if self.tags is None else self.tags\n            update_object.tags = [tag.name for tag in tags]\n        return update_object\n\n    def get_insert_object(self) -> TransactionInsertObject:\n        \"\"\"\n        Return a TransactionInsertObject\n\n        Return a TransactionInsertObject to update an expense. Simply\n        change some of the properties and perform an `insert_transactions` with\n        your Lunchable object.\n\n        Returns\n        -------\n        TransactionInsertObject\n        \"\"\"\n        insert_dict = self.model_dump()\n        try:\n            TransactionInsertObject.StatusEnum(self.status)\n        except ValueError:\n            insert_dict[\"status\"] = None\n        insert_object = TransactionInsertObject.model_validate(insert_dict)\n        if insert_object.tags is not None:\n            tags = [] if self.tags is None else self.tags\n            insert_object.tags = [tag.name for tag in tags]\n        return insert_object\n
"},{"location":"reference/models/#lunchable.models.TransactionObject.get_insert_object","title":"get_insert_object()","text":"

Return a TransactionInsertObject

Return a TransactionInsertObject to update an expense. Simply change some of the properties and perform an insert_transactions with your Lunchable object.

Returns:

Type Description TransactionInsertObject Source code in lunchable/models/transactions.py
def get_insert_object(self) -> TransactionInsertObject:\n    \"\"\"\n    Return a TransactionInsertObject\n\n    Return a TransactionInsertObject to update an expense. Simply\n    change some of the properties and perform an `insert_transactions` with\n    your Lunchable object.\n\n    Returns\n    -------\n    TransactionInsertObject\n    \"\"\"\n    insert_dict = self.model_dump()\n    try:\n        TransactionInsertObject.StatusEnum(self.status)\n    except ValueError:\n        insert_dict[\"status\"] = None\n    insert_object = TransactionInsertObject.model_validate(insert_dict)\n    if insert_object.tags is not None:\n        tags = [] if self.tags is None else self.tags\n        insert_object.tags = [tag.name for tag in tags]\n    return insert_object\n
"},{"location":"reference/models/#lunchable.models.TransactionObject.get_update_object","title":"get_update_object()","text":"

Return a TransactionUpdateObject

Return a TransactionUpdateObject to update an expense. Simply change one of the properties and perform an update_transaction with your Lunchable object.

Returns:

Type Description TransactionUpdateObject Source code in lunchable/models/transactions.py
def get_update_object(self) -> TransactionUpdateObject:\n    \"\"\"\n    Return a TransactionUpdateObject\n\n    Return a TransactionUpdateObject to update an expense. Simply\n    change one of the properties and perform an `update_transaction` with\n    your Lunchable object.\n\n    Returns\n    -------\n    TransactionUpdateObject\n    \"\"\"\n    update_dict = self.model_dump()\n    try:\n        TransactionUpdateObject.StatusEnum(self.status)\n    except ValueError:\n        update_dict[\"status\"] = None\n    update_object = TransactionUpdateObject.model_validate(update_dict)\n    if update_object.tags is not None:\n        tags = [] if self.tags is None else self.tags\n        update_object.tags = [tag.name for tag in tags]\n    return update_object\n
"},{"location":"reference/models/#lunchable.models.TransactionSplitObject","title":"TransactionSplitObject","text":"

Bases: TransactionBaseObject

Object for Splitting Transactions

https://lunchmoney.dev/#split-object

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account.

None notes str | None

Transaction Split Notes.

None amount float

Individual amount of split. Currency will inherit from parent transaction. All amounts must sum up to parent transaction amount.

required Source code in lunchable/models/transactions.py
class TransactionSplitObject(TransactionBaseObject):\n    \"\"\"\n    Object for Splitting Transactions\n\n    https://lunchmoney.dev/#split-object\n    \"\"\"\n\n    _date_description = \"Must be in ISO 8601 format (YYYY-MM-DD).\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account.\n    \"\"\"\n    _notes_description = \"Transaction Split Notes.\"\n    _amount_description = \"\"\"\n    Individual amount of split. Currency will inherit from parent transaction. All\n    amounts must sum up to parent transaction amount.\n    \"\"\"\n\n    date: datetime.date = Field(description=_date_description)\n    category_id: Optional[int] = Field(\n        default=None, description=_category_id_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    amount: float = Field(description=_amount_description)\n
"},{"location":"reference/models/#lunchable.models.TransactionUpdateObject","title":"TransactionUpdateObject","text":"

Bases: TransactionBaseObject

Object For Updating Existing Transactions

https://lunchmoney.dev/#update-transaction

Parameters:

Name Type Description Default date date | None

Must be in ISO 8601 format (YYYY-MM-DD).

None category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None amount float | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None currency str | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. Defaults to uncleared If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionUpdateObject(TransactionBaseObject):\n    \"\"\"\n    Object For Updating Existing Transactions\n\n    https://lunchmoney.dev/#update-transaction\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account and must not be a category group.\n    \"\"\"\n    _amount_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id\n    \"\"\"\n    _currency_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id.\n    Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _recurring_id_description = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must\n    be associated with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. Defaults to uncleared If recurring_id is\n    provided, the status will automatically be set to recurring or recurring_suggested\n    depending on the type of recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found,\n    an error will be thrown. Passing in a string will attempt to match by string.\n    If no matching tag name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: Optional[datetime.date] = Field(None, description=_date_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    amount: Optional[float] = Field(None, description=_amount_description)\n    currency: Optional[str] = Field(None, description=_currency_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id_description)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(None, description=_external_id_description)\n    tags: Optional[List[Union[int, str]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/models/#lunchable.models.TransactionUpdateObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/models/#lunchable.models.UserObject","title":"UserObject","text":"

Bases: LunchableModel

The LunchMoney User object

https://lunchmoney.dev/#user-object

Parameters:

Name Type Description Default user_id int

Unique identifier for user

required user_name str

User's' name

required user_email str

User's' Email

required account_id int

Unique identifier for the associated budgeting account

required budget_name str

Name of the associated budgeting account

required api_key_label str | None

User-defined label of the developer API key used. Returns null if nothing has been set.

None Source code in lunchable/models/user.py
class UserObject(LunchableModel):\n    \"\"\"\n    The LunchMoney `User` object\n\n    https://lunchmoney.dev/#user-object\n    \"\"\"\n\n    user_id: int = Field(description=\"Unique identifier for user\")\n    user_name: str = Field(description=\"User's' name\")\n    user_email: str = Field(description=\"User's' Email\")\n    account_id: int = Field(\n        description=\"Unique identifier for the associated budgeting account\"\n    )\n    budget_name: str = Field(description=\"Name of the associated budgeting account\")\n    api_key_label: Optional[str] = Field(\n        None,\n        description=\"User-defined label of the developer API key used. \"\n        \"Returns null if nothing has been set.\",\n    )\n
"},{"location":"reference/models/_base/","title":"_base","text":"

Base Pydantic Object for Containers

"},{"location":"reference/models/_base/#lunchable.models._base.LunchableModel","title":"LunchableModel","text":"

Bases: BaseModel

Hashable Pydantic Model

Source code in lunchable/models/_base.py
class LunchableModel(BaseModel):\n    \"\"\"\n    Hashable Pydantic Model\n    \"\"\"\n\n    def __hash__(self) -> int:\n        \"\"\"\n        Hash Method for Pydantic BaseModels\n        \"\"\"\n        return hash((type(self), *tuple(self.__dict__.values())))\n
"},{"location":"reference/models/_base/#lunchable.models._base.LunchableModel.__hash__","title":"__hash__()","text":"

Hash Method for Pydantic BaseModels

Source code in lunchable/models/_base.py
def __hash__(self) -> int:\n    \"\"\"\n    Hash Method for Pydantic BaseModels\n    \"\"\"\n    return hash((type(self), *tuple(self.__dict__.values())))\n
"},{"location":"reference/models/_core/","title":"_core","text":"

Lunchmoney SDK Core

"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient","title":"LunchMoneyAPIClient","text":"

Core API Client Class

Source code in lunchable/models/_core.py
class LunchMoneyAPIClient:\n    \"\"\"\n    Core API Client Class\n    \"\"\"\n\n    class Methods:\n        \"\"\"\n        HTTP Request Method Enumerations: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE\n        \"\"\"\n\n        # This Helper Namespace Organizes and Tracks HTTP Requests by Method\n        GET = \"GET\"\n        OPTIONS = \"OPTIONS\"\n        HEAD = \"HEAD\"\n        POST = \"POST\"\n        PUT = \"PUT\"\n        PATCH = \"PATCH\"\n        DELETE = \"DELETE\"\n\n    def __init__(self, access_token: str | None = None) -> None:\n        \"\"\"\n        Initialize a Lunch Money object with an Access Token.\n\n        Tries to inherit from the Environment if one isn't provided\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n        \"\"\"\n        self.access_token = APIConfig.get_access_token(access_token=access_token)\n\n    def __repr__(self) -> str:\n        \"\"\"\n        String Representation\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return \"<LunchMoney: httpx.Client>\"\n\n    @cached_property\n    def session(self) -> httpx.Client:\n        \"\"\"\n        Lunch Money HTTPX Client\n\n        Returns\n        -------\n        httpx.Client\n        \"\"\"\n        return LunchMoneyClient(access_token=self.access_token)\n\n    @cached_property\n    def async_session(self) -> httpx.AsyncClient:\n        \"\"\"\n        Lunch Money HTTPX Async Client\n\n        Returns\n        -------\n        httpx.AsyncClient\n        \"\"\"\n        return LunchMoneyAsyncClient(access_token=self.access_token)\n\n    def request(\n        self,\n        method: str,\n        url: Union[httpx.URL, str],\n        *,\n        content: Optional[\n            Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\n        ] = None,\n        data: Optional[Mapping[str, Any]] = None,\n        json: Optional[Any] = None,\n        params: Optional[Mapping[str, Any]] = None,\n        **kwargs: Any,\n    ) -> httpx.Response:\n        \"\"\"\n        Make an HTTP request\n\n        This is a simple method :class:`.LunchMoney` exposes to make HTTP requests. It\n        has the benefit of using an existing `httpx.Client` as well as as out of the box\n        auth headers that are used to connect to the Lunch Money Developer API.\n\n        Parameters\n        ----------\n        method: str\n            requests method: GET, OPTIONS, HEAD, POST, PUT,\n            PATCH, or DELETE\n        url: Union[httpx.URL, str]\n            URL for the new Request object.\n        content: Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]\n            Content to send in the body of the Request.\n        data: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples, bytes, or file-like object to send\n            in the body of the Request.\n        json: Optional[Any]\n            A JSON serializable Python object to send in the body of the Request.\n        params: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples or bytes to send in the query\n            string for the Request.\n        **kwargs: Any\n            Additional arguments to send to the request method.\n\n        Returns\n        -------\n        httpx.Response\n\n        Examples\n        --------\n        A recent use of this method was to delete a Tag (which isn't available via the\n        Developer API yet)\n\n        ```python\n        import lunchable\n\n        lunch = lunchable.LunchMoney()\n\n        # Get All the Tags\n        all_tags = lunch.get_tags()\n        # Get All The Null Tags (a list of 1 or zero)\n        null_tags = [tag for tag in all_tags if tag.name in [None, \"\"]]\n\n        # Create a Cookie dictionary from a browser session\n        cookies = {\"cookie_keys\": \"cookie_values\"}\n        del lunch.session.headers[\"authorization\"]\n\n        for null_tag in null_tags:\n            # use the httpx.client embedded in the class to make a request with cookies\n            response = lunch.request(\n                method=lunch.Methods.DELETE,\n                url=f\"https://api.lunchmoney.app/tags/{null_tag.id}\",\n                cookies=cookies\n            )\n            # raise an error for 4XX responses\n            response.raise_for_status()\n        ```\n        \"\"\"\n        response = self.session.request(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            json=json,\n            params=params,\n            **kwargs,\n        )\n        return response\n\n    async def arequest(\n        self,\n        method: str,\n        url: Union[httpx.URL, str],\n        *,\n        content: Optional[\n            Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\n        ] = None,\n        data: Optional[Mapping[str, Any]] = None,\n        json: Optional[Any] = None,\n        params: Optional[Mapping[str, Any]] = None,\n        **kwargs: Any,\n    ) -> httpx.Response:\n        \"\"\"\n        Make an async HTTP request\n\n        This is a simple method :class:`.LunchMoney` exposes to make HTTP requests. It\n        has the benefit of using an existing `httpx.Client` as well as as out of the box\n        auth headers that are used to connect to the Lunch Money Developer API.\n\n        Parameters\n        ----------\n        method: str\n            requests method: GET, OPTIONS, HEAD, POST, PUT,\n            PATCH, or DELETE\n        url: Union[httpx.URL, str]\n            URL for the new Request object.\n        content: Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]\n            Content to send in the body of the Request.\n        data: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples, bytes, or file-like object to send\n            in the body of the Request.\n        json: Optional[Any]\n            A JSON serializable Python object to send in the body of the Request.\n        params: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples or bytes to send in the query\n            string for the Request.\n        **kwargs: Any\n            Additional arguments to send to the request method.\n\n        Returns\n        -------\n        httpx.Response\n        \"\"\"\n        response = self.async_session.request(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            json=json,\n            params=params,\n            **kwargs,\n        )\n        return await response\n\n    @classmethod\n    def process_response(cls, response: httpx.Response) -> Any:\n        \"\"\"\n        Process a Lunch Money response and raise any errors\n\n        This includes 200 responses that are actually errors\n\n        Parameters\n        ----------\n        response: httpx.Response\n            An HTTPX Response Object\n        \"\"\"\n        try:\n            response.raise_for_status()\n        except httpx.HTTPError as he:\n            logger.exception(he)\n            logger.error(response.text)\n            raise LunchMoneyHTTPError(response.text) from he\n        if response.content:\n            returned_data = response.json()\n        else:\n            returned_data = None\n        if isinstance(returned_data, dict) and any(\n            [\"error\" in returned_data.keys(), \"errors\" in returned_data.keys()]\n        ):\n            try:\n                errors = returned_data[\"error\"]\n            except KeyError:\n                errors = returned_data[\"errors\"]\n            logger.exception(errors)\n            raise LunchMoneyHTTPError(errors)\n        return returned_data\n\n    def make_request(\n        self,\n        method: str,\n        url_path: Union[list[Union[str, int]], str, int],\n        params: Optional[Mapping[str, Any]] = None,\n        payload: Optional[Any] = None,\n        **kwargs: Any,\n    ) -> Any:\n        \"\"\"\n        Make an HTTP request and `process` its response\n\n        This method is a wrapper around :meth:`.LunchMoney.request` that\n        also processes the response and checks for any errors.\n\n        Parameters\n        ----------\n        method: str\n            requests method: GET, OPTIONS, HEAD, POST, PUT,\n            PATCH, or DELETE\n        url_path: Union[List[Union[str, int]], str, int]\n            URL components to make into a URL\n        payload: Optional[Mapping[str, Any]]\n            Data to send in the body of the Request.\n        params: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples or bytes to send in the query\n            string for the Request.\n        **kwargs: Any\n            Additional arguments to send to the request method.\n\n        Returns\n        -------\n        Any\n        \"\"\"\n        url = APIConfig.make_url(url_path=url_path)\n        json_safe_payload = pydantic_core.to_json(payload) if payload else None\n        json_safe_params = pydantic_core.to_jsonable_python(params)\n        response = self.request(\n            method=method,\n            url=url,\n            params=json_safe_params,\n            content=json_safe_payload,\n            **kwargs,\n        )\n        data = self.process_response(response=response)\n        return data\n\n    async def amake_request(\n        self,\n        method: str,\n        url_path: Union[list[Union[str, int]], str, int],\n        params: Optional[Mapping[str, Any]] = None,\n        payload: Optional[Any] = None,\n        **kwargs: Any,\n    ) -> Any:\n        \"\"\"\n        Make an async HTTP request and `process` its response\n\n        This method is a wrapper around :meth:`.LunchMoney.arequest` that\n        also processes the response and checks for any errors.\n\n        Parameters\n        ----------\n        method: str\n            requests method: GET, OPTIONS, HEAD, POST, PUT,\n            PATCH, or DELETE\n        url_path: Union[List[Union[str, int]], str, int]\n            URL components to make into a URL\n        payload: Optional[Mapping[str, Any]]\n            Data to send in the body of the Request.\n        params: Optional[Mapping[str, Any]]\n            Dictionary, list of tuples or bytes to send in the query\n            string for the Request.\n        **kwargs: Any\n            Additional arguments to send to the request method.\n\n        Returns\n        -------\n        Any\n        \"\"\"\n        url = APIConfig.make_url(url_path=url_path)\n        json_safe_payload = pydantic_core.to_jsonable_python(payload)\n        json_safe_params = pydantic_core.to_jsonable_python(params)\n        response = await self.arequest(\n            method=method,\n            url=url,\n            params=json_safe_params,\n            data=json_safe_payload,\n            **kwargs,\n        )\n        data = self.process_response(response=response)\n        return data\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.async_session","title":"async_session: httpx.AsyncClient cached property","text":"

Lunch Money HTTPX Async Client

Returns:

Type Description AsyncClient"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.session","title":"session: httpx.Client cached property","text":"

Lunch Money HTTPX Client

Returns:

Type Description Client"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.Methods","title":"Methods","text":"

HTTP Request Method Enumerations: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

Source code in lunchable/models/_core.py
class Methods:\n    \"\"\"\n    HTTP Request Method Enumerations: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE\n    \"\"\"\n\n    # This Helper Namespace Organizes and Tracks HTTP Requests by Method\n    GET = \"GET\"\n    OPTIONS = \"OPTIONS\"\n    HEAD = \"HEAD\"\n    POST = \"POST\"\n    PUT = \"PUT\"\n    PATCH = \"PATCH\"\n    DELETE = \"DELETE\"\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.__init__","title":"__init__(access_token=None)","text":"

Initialize a Lunch Money object with an Access Token.

Tries to inherit from the Environment if one isn't provided

Parameters:

Name Type Description Default access_token str | None

Lunchmoney Developer API Access Token

None Source code in lunchable/models/_core.py
def __init__(self, access_token: str | None = None) -> None:\n    \"\"\"\n    Initialize a Lunch Money object with an Access Token.\n\n    Tries to inherit from the Environment if one isn't provided\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n    \"\"\"\n    self.access_token = APIConfig.get_access_token(access_token=access_token)\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str Source code in lunchable/models/_core.py
def __repr__(self) -> str:\n    \"\"\"\n    String Representation\n\n    Returns\n    -------\n    str\n    \"\"\"\n    return \"<LunchMoney: httpx.Client>\"\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.amake_request","title":"amake_request(method, url_path, params=None, payload=None, **kwargs) async","text":"

Make an async HTTP request and process its response

This method is a wrapper around :meth:.LunchMoney.arequest that also processes the response and checks for any errors.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url_path Union[list[Union[str, int]], str, int]

URL components to make into a URL

required payload Optional[Any]

Data to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Any Source code in lunchable/models/_core.py
async def amake_request(\n    self,\n    method: str,\n    url_path: Union[list[Union[str, int]], str, int],\n    params: Optional[Mapping[str, Any]] = None,\n    payload: Optional[Any] = None,\n    **kwargs: Any,\n) -> Any:\n    \"\"\"\n    Make an async HTTP request and `process` its response\n\n    This method is a wrapper around :meth:`.LunchMoney.arequest` that\n    also processes the response and checks for any errors.\n\n    Parameters\n    ----------\n    method: str\n        requests method: GET, OPTIONS, HEAD, POST, PUT,\n        PATCH, or DELETE\n    url_path: Union[List[Union[str, int]], str, int]\n        URL components to make into a URL\n    payload: Optional[Mapping[str, Any]]\n        Data to send in the body of the Request.\n    params: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples or bytes to send in the query\n        string for the Request.\n    **kwargs: Any\n        Additional arguments to send to the request method.\n\n    Returns\n    -------\n    Any\n    \"\"\"\n    url = APIConfig.make_url(url_path=url_path)\n    json_safe_payload = pydantic_core.to_jsonable_python(payload)\n    json_safe_params = pydantic_core.to_jsonable_python(params)\n    response = await self.arequest(\n        method=method,\n        url=url,\n        params=json_safe_params,\n        data=json_safe_payload,\n        **kwargs,\n    )\n    data = self.process_response(response=response)\n    return data\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.arequest","title":"arequest(method, url, *, content=None, data=None, json=None, params=None, **kwargs) async","text":"

Make an async HTTP request

This is a simple method :class:.LunchMoney exposes to make HTTP requests. It has the benefit of using an existing httpx.Client as well as as out of the box auth headers that are used to connect to the Lunch Money Developer API.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url Union[URL, str]

URL for the new Request object.

required content Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]

Content to send in the body of the Request.

None data Optional[Mapping[str, Any]]

Dictionary, list of tuples, bytes, or file-like object to send in the body of the Request.

None json Optional[Any]

A JSON serializable Python object to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Response Source code in lunchable/models/_core.py
async def arequest(\n    self,\n    method: str,\n    url: Union[httpx.URL, str],\n    *,\n    content: Optional[\n        Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\n    ] = None,\n    data: Optional[Mapping[str, Any]] = None,\n    json: Optional[Any] = None,\n    params: Optional[Mapping[str, Any]] = None,\n    **kwargs: Any,\n) -> httpx.Response:\n    \"\"\"\n    Make an async HTTP request\n\n    This is a simple method :class:`.LunchMoney` exposes to make HTTP requests. It\n    has the benefit of using an existing `httpx.Client` as well as as out of the box\n    auth headers that are used to connect to the Lunch Money Developer API.\n\n    Parameters\n    ----------\n    method: str\n        requests method: GET, OPTIONS, HEAD, POST, PUT,\n        PATCH, or DELETE\n    url: Union[httpx.URL, str]\n        URL for the new Request object.\n    content: Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]\n        Content to send in the body of the Request.\n    data: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples, bytes, or file-like object to send\n        in the body of the Request.\n    json: Optional[Any]\n        A JSON serializable Python object to send in the body of the Request.\n    params: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples or bytes to send in the query\n        string for the Request.\n    **kwargs: Any\n        Additional arguments to send to the request method.\n\n    Returns\n    -------\n    httpx.Response\n    \"\"\"\n    response = self.async_session.request(\n        method=method,\n        url=url,\n        content=content,\n        data=data,\n        json=json,\n        params=params,\n        **kwargs,\n    )\n    return await response\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.make_request","title":"make_request(method, url_path, params=None, payload=None, **kwargs)","text":"

Make an HTTP request and process its response

This method is a wrapper around :meth:.LunchMoney.request that also processes the response and checks for any errors.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url_path Union[list[Union[str, int]], str, int]

URL components to make into a URL

required payload Optional[Any]

Data to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Any Source code in lunchable/models/_core.py
def make_request(\n    self,\n    method: str,\n    url_path: Union[list[Union[str, int]], str, int],\n    params: Optional[Mapping[str, Any]] = None,\n    payload: Optional[Any] = None,\n    **kwargs: Any,\n) -> Any:\n    \"\"\"\n    Make an HTTP request and `process` its response\n\n    This method is a wrapper around :meth:`.LunchMoney.request` that\n    also processes the response and checks for any errors.\n\n    Parameters\n    ----------\n    method: str\n        requests method: GET, OPTIONS, HEAD, POST, PUT,\n        PATCH, or DELETE\n    url_path: Union[List[Union[str, int]], str, int]\n        URL components to make into a URL\n    payload: Optional[Mapping[str, Any]]\n        Data to send in the body of the Request.\n    params: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples or bytes to send in the query\n        string for the Request.\n    **kwargs: Any\n        Additional arguments to send to the request method.\n\n    Returns\n    -------\n    Any\n    \"\"\"\n    url = APIConfig.make_url(url_path=url_path)\n    json_safe_payload = pydantic_core.to_json(payload) if payload else None\n    json_safe_params = pydantic_core.to_jsonable_python(params)\n    response = self.request(\n        method=method,\n        url=url,\n        params=json_safe_params,\n        content=json_safe_payload,\n        **kwargs,\n    )\n    data = self.process_response(response=response)\n    return data\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.process_response","title":"process_response(response) classmethod","text":"

Process a Lunch Money response and raise any errors

This includes 200 responses that are actually errors

Parameters:

Name Type Description Default response Response

An HTTPX Response Object

required Source code in lunchable/models/_core.py
@classmethod\ndef process_response(cls, response: httpx.Response) -> Any:\n    \"\"\"\n    Process a Lunch Money response and raise any errors\n\n    This includes 200 responses that are actually errors\n\n    Parameters\n    ----------\n    response: httpx.Response\n        An HTTPX Response Object\n    \"\"\"\n    try:\n        response.raise_for_status()\n    except httpx.HTTPError as he:\n        logger.exception(he)\n        logger.error(response.text)\n        raise LunchMoneyHTTPError(response.text) from he\n    if response.content:\n        returned_data = response.json()\n    else:\n        returned_data = None\n    if isinstance(returned_data, dict) and any(\n        [\"error\" in returned_data.keys(), \"errors\" in returned_data.keys()]\n    ):\n        try:\n            errors = returned_data[\"error\"]\n        except KeyError:\n            errors = returned_data[\"errors\"]\n        logger.exception(errors)\n        raise LunchMoneyHTTPError(errors)\n    return returned_data\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.request","title":"request(method, url, *, content=None, data=None, json=None, params=None, **kwargs)","text":"

Make an HTTP request

This is a simple method :class:.LunchMoney exposes to make HTTP requests. It has the benefit of using an existing httpx.Client as well as as out of the box auth headers that are used to connect to the Lunch Money Developer API.

Parameters:

Name Type Description Default method str

requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE

required url Union[URL, str]

URL for the new Request object.

required content Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]

Content to send in the body of the Request.

None data Optional[Mapping[str, Any]]

Dictionary, list of tuples, bytes, or file-like object to send in the body of the Request.

None json Optional[Any]

A JSON serializable Python object to send in the body of the Request.

None params Optional[Mapping[str, Any]]

Dictionary, list of tuples or bytes to send in the query string for the Request.

None **kwargs Any

Additional arguments to send to the request method.

{}

Returns:

Type Description Response

Examples:

A recent use of this method was to delete a Tag (which isn't available via the Developer API yet)

import lunchable\n\nlunch = lunchable.LunchMoney()\n\n# Get All the Tags\nall_tags = lunch.get_tags()\n# Get All The Null Tags (a list of 1 or zero)\nnull_tags = [tag for tag in all_tags if tag.name in [None, \"\"]]\n\n# Create a Cookie dictionary from a browser session\ncookies = {\"cookie_keys\": \"cookie_values\"}\ndel lunch.session.headers[\"authorization\"]\n\nfor null_tag in null_tags:\n    # use the httpx.client embedded in the class to make a request with cookies\n    response = lunch.request(\n        method=lunch.Methods.DELETE,\n        url=f\"https://api.lunchmoney.app/tags/{null_tag.id}\",\n        cookies=cookies\n    )\n    # raise an error for 4XX responses\n    response.raise_for_status()\n
Source code in lunchable/models/_core.py
def request(\n    self,\n    method: str,\n    url: Union[httpx.URL, str],\n    *,\n    content: Optional[\n        Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\n    ] = None,\n    data: Optional[Mapping[str, Any]] = None,\n    json: Optional[Any] = None,\n    params: Optional[Mapping[str, Any]] = None,\n    **kwargs: Any,\n) -> httpx.Response:\n    \"\"\"\n    Make an HTTP request\n\n    This is a simple method :class:`.LunchMoney` exposes to make HTTP requests. It\n    has the benefit of using an existing `httpx.Client` as well as as out of the box\n    auth headers that are used to connect to the Lunch Money Developer API.\n\n    Parameters\n    ----------\n    method: str\n        requests method: GET, OPTIONS, HEAD, POST, PUT,\n        PATCH, or DELETE\n    url: Union[httpx.URL, str]\n        URL for the new Request object.\n    content: Optional[Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]]\n        Content to send in the body of the Request.\n    data: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples, bytes, or file-like object to send\n        in the body of the Request.\n    json: Optional[Any]\n        A JSON serializable Python object to send in the body of the Request.\n    params: Optional[Mapping[str, Any]]\n        Dictionary, list of tuples or bytes to send in the query\n        string for the Request.\n    **kwargs: Any\n        Additional arguments to send to the request method.\n\n    Returns\n    -------\n    httpx.Response\n\n    Examples\n    --------\n    A recent use of this method was to delete a Tag (which isn't available via the\n    Developer API yet)\n\n    ```python\n    import lunchable\n\n    lunch = lunchable.LunchMoney()\n\n    # Get All the Tags\n    all_tags = lunch.get_tags()\n    # Get All The Null Tags (a list of 1 or zero)\n    null_tags = [tag for tag in all_tags if tag.name in [None, \"\"]]\n\n    # Create a Cookie dictionary from a browser session\n    cookies = {\"cookie_keys\": \"cookie_values\"}\n    del lunch.session.headers[\"authorization\"]\n\n    for null_tag in null_tags:\n        # use the httpx.client embedded in the class to make a request with cookies\n        response = lunch.request(\n            method=lunch.Methods.DELETE,\n            url=f\"https://api.lunchmoney.app/tags/{null_tag.id}\",\n            cookies=cookies\n        )\n        # raise an error for 4XX responses\n        response.raise_for_status()\n    ```\n    \"\"\"\n    response = self.session.request(\n        method=method,\n        url=url,\n        content=content,\n        data=data,\n        json=json,\n        params=params,\n        **kwargs,\n    )\n    return response\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAsyncClient","title":"LunchMoneyAsyncClient","text":"

Bases: AsyncClient

API Async HTTP Client

Source code in lunchable/models/_core.py
class LunchMoneyAsyncClient(httpx.AsyncClient):\n    \"\"\"\n    API Async HTTP Client\n    \"\"\"\n\n    def __init__(self, access_token: str | None = None) -> None:\n        super().__init__()\n        api_headers = APIConfig.get_header(access_token=access_token)\n        self.headers.update(api_headers)\n
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyClient","title":"LunchMoneyClient","text":"

Bases: Client

API HTTP Client

Source code in lunchable/models/_core.py
class LunchMoneyClient(Client):\n    \"\"\"\n    API HTTP Client\n    \"\"\"\n\n    def __init__(self, access_token: str | None = None) -> None:\n        super().__init__()\n        api_headers = APIConfig.get_header(access_token=access_token)\n        self.headers.update(api_headers)\n
"},{"location":"reference/models/_lunchmoney/","title":"_lunchmoney","text":"

Lunch Money Python Client

This Module Leverages Class Inheritance to distribute API Methods Across a series of clients. Ultimately, everything inherits from the lunchable.models.core.LunchMoneyAPIClient class which facilitates interacting with the API.

For example: to see source code on interactions with the \"transactions\" API endpoint you will refer to the TransactionsClient object.

"},{"location":"reference/models/_lunchmoney/#lunchable.models._lunchmoney.LunchMoney","title":"LunchMoney","text":"

Bases: AssetsClient, BudgetsClient, CategoriesClient, CryptoClient, PlaidAccountsClient, RecurringExpensesClient, TagsClient, TransactionsClient, UserClient

Lunch Money Python Client.

This class facilitates with connections to the Lunch Money Developer API. Authenticate with an Access Token. If an access token isn't provided one will attempt to be inherited from a LUNCHMONEY_ACCESS_TOKEN environment variable.

Examples:

from __future__ import annotations\n\nfrom lunchable import LunchMoney\nfrom lunchable.models import TransactionObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions: list[TransactionObject] = lunch.get_transactions()\n
Source code in lunchable/models/_lunchmoney.py
class LunchMoney(\n    AssetsClient,\n    BudgetsClient,\n    CategoriesClient,\n    CryptoClient,\n    PlaidAccountsClient,\n    RecurringExpensesClient,\n    TagsClient,\n    TransactionsClient,\n    UserClient,\n):\n    \"\"\"\n    Lunch Money Python Client.\n\n    This class facilitates with connections to\n    the [Lunch Money Developer API](https://lunchmoney.dev/). Authenticate\n    with an Access Token. If an access token isn't provided one will attempt to\n    be inherited from a `LUNCHMONEY_ACCESS_TOKEN` environment variable.\n\n    Examples\n    --------\n    ```python\n    from __future__ import annotations\n\n    from lunchable import LunchMoney\n    from lunchable.models import TransactionObject\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transactions: list[TransactionObject] = lunch.get_transactions()\n    ```\n    \"\"\"\n\n    def __init__(self, access_token: Optional[str] = None):\n        \"\"\"\n        Initialize a Lunch Money object with an Access Token.\n\n        Tries to inherit from the Environment if one isn't provided\n\n        Parameters\n        ----------\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n        \"\"\"\n        super(LunchMoney, self).__init__(access_token=access_token)\n
"},{"location":"reference/models/_lunchmoney/#lunchable.models._lunchmoney.LunchMoney.__init__","title":"__init__(access_token=None)","text":"

Initialize a Lunch Money object with an Access Token.

Tries to inherit from the Environment if one isn't provided

Parameters:

Name Type Description Default access_token Optional[str]

Lunchmoney Developer API Access Token

None Source code in lunchable/models/_lunchmoney.py
def __init__(self, access_token: Optional[str] = None):\n    \"\"\"\n    Initialize a Lunch Money object with an Access Token.\n\n    Tries to inherit from the Environment if one isn't provided\n\n    Parameters\n    ----------\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n    \"\"\"\n    super(LunchMoney, self).__init__(access_token=access_token)\n
"},{"location":"reference/models/assets/","title":"assets","text":"

Lunch Money - Assets

https://lunchmoney.dev/#assets

"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsClient","title":"AssetsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Assets Interactions

Source code in lunchable/models/assets.py
class AssetsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Assets Interactions\n    \"\"\"\n\n    def get_assets(self) -> List[AssetsObject]:\n        \"\"\"\n        Get Manually Managed Assets\n\n        Get a list of all manually-managed assets associated with the user's account.\n\n        (https://lunchmoney.dev/#assets-object)\n\n        Returns\n        -------\n        List[AssetsObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=[APIConfig.LUNCHMONEY_ASSETS]\n        )\n        assets = response_data.get(APIConfig.LUNCHMONEY_ASSETS)\n        asset_objects = [AssetsObject.model_validate(item) for item in assets]\n        return asset_objects\n\n    def update_asset(\n        self,\n        asset_id: int,\n        type_name: Optional[str] = None,\n        subtype_name: Optional[str] = None,\n        name: Optional[str] = None,\n        balance: Optional[float] = None,\n        balance_as_of: Optional[datetime.datetime] = None,\n        currency: Optional[str] = None,\n        institution_name: Optional[str] = None,\n    ) -> AssetsObject:\n        \"\"\"\n        Update a Single Asset\n\n        Parameters\n        ----------\n        asset_id: int\n            Asset Identifier\n        type_name: Optional[str]\n            Must be one of: cash, credit, investment, other, real estate, loan, vehicle,\n            cryptocurrency, employee compensation\n        subtype_name: Optional[str]\n            Max 25 characters\n        name: Optional[str]\n            Max 45 characters\n        balance: Optional[float]\n            Numeric value of the current balance of the account. Do not include any special\n            characters aside from a decimal point!\n        balance_as_of: Optional[datetime.datetime]\n            Has no effect if balance is not defined. If balance is defined, but balance_as_of\n            is not supplied or is invalid, current timestamp will be used.\n        currency: Optional[str]\n            Three-letter lowercase currency in ISO 4217 format. The code sent must exist in\n            our database. Defaults to asset's currency.\n        institution_name: Optional[str]\n            Max 50 characters\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        payload = _AssetsParamsPut(\n            type_name=type_name,\n            subtype_name=subtype_name,\n            name=name,\n            balance=balance,\n            balance_as_of=balance_as_of,\n            currency=currency,\n            institution_name=institution_name,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[APIConfig.LUNCHMONEY_ASSETS, asset_id],\n            payload=payload,\n        )\n        asset = AssetsObject.model_validate(response_data)\n        return asset\n\n    def insert_asset(\n        self,\n        type_name: str,\n        name: Optional[str] = None,\n        subtype_name: Optional[str] = None,\n        display_name: Optional[str] = None,\n        balance: float = 0.00,\n        balance_as_of: Optional[datetime.datetime] = None,\n        currency: Optional[str] = None,\n        institution_name: Optional[str] = None,\n        closed_on: Optional[datetime.date] = None,\n        exclude_transactions: bool = False,\n    ) -> AssetsObject:\n        \"\"\"\n        Create a single (manually-managed) asset.\n\n        Parameters\n        ----------\n        type_name: Optional[str]\n            Must be one of: cash, credit, investment, other, real estate, loan, vehicle,\n            cryptocurrency, employee compensation\n        name: Optional[str]\n            Max 45 characters\n        subtype_name: Optional[str]\n            Max 25 characters\n        display_name: Optional[str]\n            Display name of the asset (as set by user)\n        balance: float\n            Numeric value of the current balance of the account. Do not include any\n            special characters aside from a decimal point! Defaults to `0.00`\n        balance_as_of: Optional[datetime.datetime]\n            Has no effect if balance is not defined. If balance is defined, but\n            balance_as_of is not supplied or is invalid, current timestamp will be used.\n        currency: Optional[str]\n            Three-letter lowercase currency in ISO 4217 format. The code sent must exist\n            in our database. Defaults to user's primary currency.\n        institution_name: Optional[str]\n            Max 50 characters\n        closed_on: Optional[datetime.date]\n            The date this asset was closed\n        exclude_transactions: bool\n            If true, this asset will not show up as an option for assignment when\n            creating transactions manually. Defaults to False\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        payload = _AssetsParamsPost(\n            type_name=type_name,\n            subtype_name=subtype_name,\n            name=name,\n            display_name=display_name,\n            balance=balance,\n            balance_as_of=balance_as_of,\n            currency=currency,\n            institution_name=institution_name,\n            closed_on=closed_on,\n            exclude_transactions=exclude_transactions,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[APIConfig.LUNCHMONEY_ASSETS],\n            payload=payload,\n        )\n        asset = AssetsObject.model_validate(response_data)\n        return asset\n
"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsClient.get_assets","title":"get_assets()","text":"

Get Manually Managed Assets

Get a list of all manually-managed assets associated with the user's account.

(https://lunchmoney.dev/#assets-object)

Returns:

Type Description List[AssetsObject] Source code in lunchable/models/assets.py
def get_assets(self) -> List[AssetsObject]:\n    \"\"\"\n    Get Manually Managed Assets\n\n    Get a list of all manually-managed assets associated with the user's account.\n\n    (https://lunchmoney.dev/#assets-object)\n\n    Returns\n    -------\n    List[AssetsObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=[APIConfig.LUNCHMONEY_ASSETS]\n    )\n    assets = response_data.get(APIConfig.LUNCHMONEY_ASSETS)\n    asset_objects = [AssetsObject.model_validate(item) for item in assets]\n    return asset_objects\n
"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsClient.insert_asset","title":"insert_asset(type_name, name=None, subtype_name=None, display_name=None, balance=0.0, balance_as_of=None, currency=None, institution_name=None, closed_on=None, exclude_transactions=False)","text":"

Create a single (manually-managed) asset.

Parameters:

Name Type Description Default type_name str

Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation

required name Optional[str]

Max 45 characters

None subtype_name Optional[str]

Max 25 characters

None display_name Optional[str]

Display name of the asset (as set by user)

None balance float

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point! Defaults to 0.00

0.0 balance_as_of Optional[datetime]

Has no effect if balance is not defined. If balance is defined, but balance_as_of is not supplied or is invalid, current timestamp will be used.

None currency Optional[str]

Three-letter lowercase currency in ISO 4217 format. The code sent must exist in our database. Defaults to user's primary currency.

None institution_name Optional[str]

Max 50 characters

None closed_on Optional[date]

The date this asset was closed

None exclude_transactions bool

If true, this asset will not show up as an option for assignment when creating transactions manually. Defaults to False

False

Returns:

Type Description AssetsObject Source code in lunchable/models/assets.py
def insert_asset(\n    self,\n    type_name: str,\n    name: Optional[str] = None,\n    subtype_name: Optional[str] = None,\n    display_name: Optional[str] = None,\n    balance: float = 0.00,\n    balance_as_of: Optional[datetime.datetime] = None,\n    currency: Optional[str] = None,\n    institution_name: Optional[str] = None,\n    closed_on: Optional[datetime.date] = None,\n    exclude_transactions: bool = False,\n) -> AssetsObject:\n    \"\"\"\n    Create a single (manually-managed) asset.\n\n    Parameters\n    ----------\n    type_name: Optional[str]\n        Must be one of: cash, credit, investment, other, real estate, loan, vehicle,\n        cryptocurrency, employee compensation\n    name: Optional[str]\n        Max 45 characters\n    subtype_name: Optional[str]\n        Max 25 characters\n    display_name: Optional[str]\n        Display name of the asset (as set by user)\n    balance: float\n        Numeric value of the current balance of the account. Do not include any\n        special characters aside from a decimal point! Defaults to `0.00`\n    balance_as_of: Optional[datetime.datetime]\n        Has no effect if balance is not defined. If balance is defined, but\n        balance_as_of is not supplied or is invalid, current timestamp will be used.\n    currency: Optional[str]\n        Three-letter lowercase currency in ISO 4217 format. The code sent must exist\n        in our database. Defaults to user's primary currency.\n    institution_name: Optional[str]\n        Max 50 characters\n    closed_on: Optional[datetime.date]\n        The date this asset was closed\n    exclude_transactions: bool\n        If true, this asset will not show up as an option for assignment when\n        creating transactions manually. Defaults to False\n\n    Returns\n    -------\n    AssetsObject\n    \"\"\"\n    payload = _AssetsParamsPost(\n        type_name=type_name,\n        subtype_name=subtype_name,\n        name=name,\n        display_name=display_name,\n        balance=balance,\n        balance_as_of=balance_as_of,\n        currency=currency,\n        institution_name=institution_name,\n        closed_on=closed_on,\n        exclude_transactions=exclude_transactions,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[APIConfig.LUNCHMONEY_ASSETS],\n        payload=payload,\n    )\n    asset = AssetsObject.model_validate(response_data)\n    return asset\n
"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsClient.update_asset","title":"update_asset(asset_id, type_name=None, subtype_name=None, name=None, balance=None, balance_as_of=None, currency=None, institution_name=None)","text":"

Update a Single Asset

Parameters:

Name Type Description Default asset_id int

Asset Identifier

required type_name Optional[str]

Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation

None subtype_name Optional[str]

Max 25 characters

None name Optional[str]

Max 45 characters

None balance Optional[float]

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point!

None balance_as_of Optional[datetime]

Has no effect if balance is not defined. If balance is defined, but balance_as_of is not supplied or is invalid, current timestamp will be used.

None currency Optional[str]

Three-letter lowercase currency in ISO 4217 format. The code sent must exist in our database. Defaults to asset's currency.

None institution_name Optional[str]

Max 50 characters

None

Returns:

Type Description AssetsObject Source code in lunchable/models/assets.py
def update_asset(\n    self,\n    asset_id: int,\n    type_name: Optional[str] = None,\n    subtype_name: Optional[str] = None,\n    name: Optional[str] = None,\n    balance: Optional[float] = None,\n    balance_as_of: Optional[datetime.datetime] = None,\n    currency: Optional[str] = None,\n    institution_name: Optional[str] = None,\n) -> AssetsObject:\n    \"\"\"\n    Update a Single Asset\n\n    Parameters\n    ----------\n    asset_id: int\n        Asset Identifier\n    type_name: Optional[str]\n        Must be one of: cash, credit, investment, other, real estate, loan, vehicle,\n        cryptocurrency, employee compensation\n    subtype_name: Optional[str]\n        Max 25 characters\n    name: Optional[str]\n        Max 45 characters\n    balance: Optional[float]\n        Numeric value of the current balance of the account. Do not include any special\n        characters aside from a decimal point!\n    balance_as_of: Optional[datetime.datetime]\n        Has no effect if balance is not defined. If balance is defined, but balance_as_of\n        is not supplied or is invalid, current timestamp will be used.\n    currency: Optional[str]\n        Three-letter lowercase currency in ISO 4217 format. The code sent must exist in\n        our database. Defaults to asset's currency.\n    institution_name: Optional[str]\n        Max 50 characters\n\n    Returns\n    -------\n    AssetsObject\n    \"\"\"\n    payload = _AssetsParamsPut(\n        type_name=type_name,\n        subtype_name=subtype_name,\n        name=name,\n        balance=balance,\n        balance_as_of=balance_as_of,\n        currency=currency,\n        institution_name=institution_name,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[APIConfig.LUNCHMONEY_ASSETS, asset_id],\n        payload=payload,\n    )\n    asset = AssetsObject.model_validate(response_data)\n    return asset\n
"},{"location":"reference/models/assets/#lunchable.models.assets.AssetsObject","title":"AssetsObject","text":"

Bases: LunchableModel

Manually Managed Asset Objects

Assets in Lunch Money are similar to plaid-accounts except that they are manually managed.

https://lunchmoney.dev/#assets-object

Parameters:

Name Type Description Default id int

Unique identifier for asset

required type_name str

Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan, cryptocurrency, investment, other, credit, real estate]

required subtype_name str | None

Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]

None name str

Name of the asset

required display_name str | None

Display name of the asset (as set by user)

None balance float

Current balance of the asset in numeric format to 4 decimal places

required balance_as_of datetime

Date/time the balance was last updated in ISO 8601 extended format

required closed_on date | None

The date this asset was closed (optional)

None currency str

Three-letter lowercase currency code of the balance in ISO 4217 format

required institution_name str | None

Name of institution holding the asset

None exclude_transactions bool

If true, this asset will not show up as an option for assignment when creating transactions manually

False created_at datetime

Date/time the asset was created in ISO 8601 extended format

required Source code in lunchable/models/assets.py
class AssetsObject(LunchableModel):\n    \"\"\"\n    Manually Managed Asset Objects\n\n    Assets in Lunch Money are similar to `plaid-accounts` except that they are manually managed.\n\n    https://lunchmoney.dev/#assets-object\n    \"\"\"\n\n    _type_name_description = \"\"\"\n    Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan,\n    cryptocurrency, investment, other, credit, real estate]\n    \"\"\"\n    _subtype_name_description = \"\"\"\n    Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]\n    \"\"\"\n    _balance_description = (\n        \"Current balance of the asset in numeric format to 4 decimal places\"\n    )\n    _balance_as_of_description = \"\"\"\n    Date/time the balance was last updated in ISO 8601 extended format\n    \"\"\"\n    _closed_on_description = \"The date this asset was closed (optional)\"\n    _currency_description = (\n        \"Three-letter lowercase currency code of the balance in ISO 4217 format\"\n    )\n    _created_at_description = (\n        \"Date/time the asset was created in ISO 8601 extended format\"\n    )\n    _exclude_transactions_description = (\n        \"If true, this asset will not show up as an \"\n        \"option for assignment when creating \"\n        \"transactions manually\"\n    )\n\n    id: int = Field(description=\"Unique identifier for asset\")\n    type_name: str = Field(description=_type_name_description)\n    subtype_name: Optional[str] = Field(None, description=_subtype_name_description)\n    name: str = Field(description=\"Name of the asset\")\n    display_name: Optional[str] = Field(\n        None, description=\"Display name of the asset (as set by user)\"\n    )\n    balance: float = Field(description=_balance_description)\n    balance_as_of: datetime.datetime = Field(description=_balance_as_of_description)\n    closed_on: Optional[datetime.date] = Field(None, description=_closed_on_description)\n    currency: str = Field(description=_currency_description)\n    institution_name: Optional[str] = Field(\n        None, description=\"Name of institution holding the asset\"\n    )\n    exclude_transactions: bool = Field(\n        default=False, description=_exclude_transactions_description\n    )\n    created_at: datetime.datetime = Field(description=_created_at_description)\n
"},{"location":"reference/models/budgets/","title":"budgets","text":"

Lunch Money - Budgets

https://lunchmoney.dev/#budget

"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetConfigObject","title":"BudgetConfigObject","text":"

Bases: LunchableModel

Budget Configuration Object

Parameters:

Name Type Description Default config_id int required cadence str required amount float | None None currency str | None None to_base float | None None auto_suggest str required Source code in lunchable/models/budgets.py
class BudgetConfigObject(LunchableModel):\n    \"\"\"\n    Budget Configuration Object\n    \"\"\"\n\n    config_id: int\n    cadence: str\n    amount: Optional[float] = None\n    currency: Optional[str] = None\n    to_base: Optional[float] = None\n    auto_suggest: str\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetDataObject","title":"BudgetDataObject","text":"

Bases: LunchableModel

Data Object within a Budget

Parameters:

Name Type Description Default budget_amount float | None None budget_currency str | None None budget_to_base float | None None spending_to_base float 0.0 num_transactions int 0 Source code in lunchable/models/budgets.py
class BudgetDataObject(LunchableModel):\n    \"\"\"\n    Data Object within a Budget\n    \"\"\"\n\n    budget_amount: Optional[float] = None\n    budget_currency: Optional[str] = None\n    budget_to_base: Optional[float] = None\n    spending_to_base: float = 0.00\n    num_transactions: int = 0\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetObject","title":"BudgetObject","text":"

Bases: LunchableModel

Monthly Budget Per Category Object

https://lunchmoney.dev/#budget-object

Parameters:

Name Type Description Default category_name str

Name of the category

required category_id int | None

Unique identifier for category

None category_group_name str | None

Name of the category group, if applicable

None group_id int | None

Unique identifier for category group

None is_group bool | None

If true, this category is a group

None is_income bool

If true, this category is an income category (category properties are set in the app via the Categories page)

required exclude_from_budget bool

If true, this category is excluded from budget (category properties are set in the app via the Categories page)

required exclude_from_totals bool

If true, this category is excluded from totals (category properties are set in the app via the Categories page)

required data Dict[date, BudgetDataObject]

For each month with budget or category spending data, there is a data object with the key set to the month in format YYYY-MM-DD. For properties, see Data object below.

required config BudgetConfigObject | None

Object representing the category's budget suggestion configuration

None Source code in lunchable/models/budgets.py
class BudgetObject(LunchableModel):\n    \"\"\"\n    Monthly Budget Per Category Object\n\n    https://lunchmoney.dev/#budget-object\n    \"\"\"\n\n    _category_group_name_description = \"Name of the category group, if applicable\"\n    _is_income_description = \"\"\"\n    If true, this category is an income category (category properties\n    are set in the app via the Categories page)\n    \"\"\"\n    _exclude_from_budget_description = \"\"\"\n    If true, this category is excluded from budget (category\n    properties are set in the app via the Categories page)\n    \"\"\"\n    _exclude_from_totals_description = \"\"\"\n    If true, this category is excluded from totals (category\n    properties are set in the app via the Categories page)\n    \"\"\"\n    _data_description = \"\"\"\n    For each month with budget or category spending data, there is a data object with the key\n    set to the month in format YYYY-MM-DD. For properties, see Data object below.\n    \"\"\"\n    _config_description = \"\"\"\n    Object representing the category's budget suggestion configuration\n    \"\"\"\n\n    category_name: str = Field(description=\"Name of the category\")\n    category_id: Optional[int] = Field(\n        None, description=\"Unique identifier for category\"\n    )\n    category_group_name: Optional[str] = Field(\n        None, description=_category_group_name_description\n    )\n    group_id: Optional[int] = Field(\n        None, description=\"Unique identifier for category group\"\n    )\n    is_group: Optional[bool] = Field(\n        None, description=\"If true, this category is a group\"\n    )\n    is_income: bool = Field(description=_is_income_description)\n    exclude_from_budget: bool = Field(description=_exclude_from_budget_description)\n    exclude_from_totals: bool = Field(description=_exclude_from_totals_description)\n    data: Dict[datetime.date, BudgetDataObject] = Field(description=_data_description)\n    config: Optional[BudgetConfigObject] = Field(None, description=_config_description)\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetParamsGet","title":"BudgetParamsGet","text":"

Bases: LunchableModel

https://lunchmoney.dev/#get-budget-summary

Parameters:

Name Type Description Default start_date date required end_date date required Source code in lunchable/models/budgets.py
class BudgetParamsGet(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#get-budget-summary\n    \"\"\"\n\n    start_date: datetime.date\n    end_date: datetime.date\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetParamsPut","title":"BudgetParamsPut","text":"

Bases: LunchableModel

https://lunchmoney.dev/#upsert-budget

Parameters:

Name Type Description Default start_date date required category_id int required amount float required currency str | None None Source code in lunchable/models/budgets.py
class BudgetParamsPut(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#upsert-budget\n    \"\"\"\n\n    start_date: datetime.date\n    category_id: int\n    amount: float\n    currency: Optional[str] = None\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetParamsRemove","title":"BudgetParamsRemove","text":"

Bases: LunchableModel

https://lunchmoney.dev/#remove-budget

Parameters:

Name Type Description Default start_date date required category_id int required Source code in lunchable/models/budgets.py
class BudgetParamsRemove(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#remove-budget\n    \"\"\"\n\n    start_date: datetime.date\n    category_id: int\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetsClient","title":"BudgetsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Budget Interactions

Source code in lunchable/models/budgets.py
class BudgetsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Budget Interactions\n    \"\"\"\n\n    def get_budgets(\n        self, start_date: datetime.date, end_date: datetime.date\n    ) -> List[BudgetObject]:\n        \"\"\"\n        Get Monthly Budgets\n\n        Get full details on the budgets for all categories between a certain time\n        period. The budgeted and spending amounts will be an aggregate across this\n        time period. (https://lunchmoney.dev/#plaid-accounts-object)\n\n        Returns\n        -------\n        List[BudgetObject]\n        \"\"\"\n        params = BudgetParamsGet(start_date=start_date, end_date=end_date).model_dump()\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=[APIConfig.LUNCHMONEY_BUDGET],\n            params=params,\n        )\n        budget_objects = [BudgetObject.model_validate(item) for item in response_data]\n        return budget_objects\n\n    def upsert_budget(\n        self,\n        start_date: datetime.date,\n        category_id: int,\n        amount: float,\n        currency: Optional[str] = None,\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"\n        Upsert a Budget for a Category and Date\n\n        Use this endpoint to update an existing budget or insert a new budget for\n        a particular category and date.\n\n        Note: Lunch Money currently only supports monthly budgets, so your date must\n        always be the start of a month (eg. 2021-04-01)\n\n        If this is a sub-category, the response will include the updated category\n        group's budget. This is because setting a sub-category may also update\n        the category group's overall budget.\n\n        https://lunchmoney.dev/#upsert-budget\n\n        Parameters\n        ----------\n        start_date : date\n            Start date for the budget period. Lunch Money currently only supports monthly budgets,\n            so your date must always be the start of a month (eg. 2021-04-01)\n        category_id: int\n            Unique identifier for the category\n        amount: float\n            Amount for budget\n        currency: Optional[str]\n            Currency for the budgeted amount (optional). If empty, will default to your primary\n            currency\n\n        Returns\n        -------\n        Optional[Dict[str, Any]]\n        \"\"\"\n        body = BudgetParamsPut(\n            start_date=start_date,\n            category_id=category_id,\n            amount=amount,\n            currency=currency,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[APIConfig.LUNCHMONEY_BUDGET],\n            payload=body,\n        )\n\n        return response_data[\"category_group\"]\n\n    def remove_budget(self, start_date: datetime.date, category_id: int) -> bool:\n        \"\"\"\n        Unset an Existing Budget for a Particular Category in a Particular Month\n\n        Parameters\n        ----------\n        start_date : date\n            Start date for the budget period. Lunch Money currently only supports monthly budgets,\n            so your date must always be the start of a month (eg. 2021-04-01)\n        category_id: int\n            Unique identifier for the category\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        params = BudgetParamsRemove(\n            start_date=start_date, category_id=category_id\n        ).model_dump()\n        response_data = self.make_request(\n            method=self.Methods.DELETE,\n            url_path=[APIConfig.LUNCHMONEY_BUDGET],\n            params=params,\n        )\n        return response_data\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetsClient.get_budgets","title":"get_budgets(start_date, end_date)","text":"

Get Monthly Budgets

Get full details on the budgets for all categories between a certain time period. The budgeted and spending amounts will be an aggregate across this time period. (https://lunchmoney.dev/#plaid-accounts-object)

Returns:

Type Description List[BudgetObject] Source code in lunchable/models/budgets.py
def get_budgets(\n    self, start_date: datetime.date, end_date: datetime.date\n) -> List[BudgetObject]:\n    \"\"\"\n    Get Monthly Budgets\n\n    Get full details on the budgets for all categories between a certain time\n    period. The budgeted and spending amounts will be an aggregate across this\n    time period. (https://lunchmoney.dev/#plaid-accounts-object)\n\n    Returns\n    -------\n    List[BudgetObject]\n    \"\"\"\n    params = BudgetParamsGet(start_date=start_date, end_date=end_date).model_dump()\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=[APIConfig.LUNCHMONEY_BUDGET],\n        params=params,\n    )\n    budget_objects = [BudgetObject.model_validate(item) for item in response_data]\n    return budget_objects\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetsClient.remove_budget","title":"remove_budget(start_date, category_id)","text":"

Unset an Existing Budget for a Particular Category in a Particular Month

Parameters:

Name Type Description Default start_date date

Start date for the budget period. Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

required category_id int

Unique identifier for the category

required

Returns:

Type Description bool Source code in lunchable/models/budgets.py
def remove_budget(self, start_date: datetime.date, category_id: int) -> bool:\n    \"\"\"\n    Unset an Existing Budget for a Particular Category in a Particular Month\n\n    Parameters\n    ----------\n    start_date : date\n        Start date for the budget period. Lunch Money currently only supports monthly budgets,\n        so your date must always be the start of a month (eg. 2021-04-01)\n    category_id: int\n        Unique identifier for the category\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    params = BudgetParamsRemove(\n        start_date=start_date, category_id=category_id\n    ).model_dump()\n    response_data = self.make_request(\n        method=self.Methods.DELETE,\n        url_path=[APIConfig.LUNCHMONEY_BUDGET],\n        params=params,\n    )\n    return response_data\n
"},{"location":"reference/models/budgets/#lunchable.models.budgets.BudgetsClient.upsert_budget","title":"upsert_budget(start_date, category_id, amount, currency=None)","text":"

Upsert a Budget for a Category and Date

Use this endpoint to update an existing budget or insert a new budget for a particular category and date.

Note: Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

If this is a sub-category, the response will include the updated category group's budget. This is because setting a sub-category may also update the category group's overall budget.

https://lunchmoney.dev/#upsert-budget

Parameters:

Name Type Description Default start_date date

Start date for the budget period. Lunch Money currently only supports monthly budgets, so your date must always be the start of a month (eg. 2021-04-01)

required category_id int

Unique identifier for the category

required amount float

Amount for budget

required currency Optional[str]

Currency for the budgeted amount (optional). If empty, will default to your primary currency

None

Returns:

Type Description Optional[Dict[str, Any]] Source code in lunchable/models/budgets.py
def upsert_budget(\n    self,\n    start_date: datetime.date,\n    category_id: int,\n    amount: float,\n    currency: Optional[str] = None,\n) -> Optional[Dict[str, Any]]:\n    \"\"\"\n    Upsert a Budget for a Category and Date\n\n    Use this endpoint to update an existing budget or insert a new budget for\n    a particular category and date.\n\n    Note: Lunch Money currently only supports monthly budgets, so your date must\n    always be the start of a month (eg. 2021-04-01)\n\n    If this is a sub-category, the response will include the updated category\n    group's budget. This is because setting a sub-category may also update\n    the category group's overall budget.\n\n    https://lunchmoney.dev/#upsert-budget\n\n    Parameters\n    ----------\n    start_date : date\n        Start date for the budget period. Lunch Money currently only supports monthly budgets,\n        so your date must always be the start of a month (eg. 2021-04-01)\n    category_id: int\n        Unique identifier for the category\n    amount: float\n        Amount for budget\n    currency: Optional[str]\n        Currency for the budgeted amount (optional). If empty, will default to your primary\n        currency\n\n    Returns\n    -------\n    Optional[Dict[str, Any]]\n    \"\"\"\n    body = BudgetParamsPut(\n        start_date=start_date,\n        category_id=category_id,\n        amount=amount,\n        currency=currency,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[APIConfig.LUNCHMONEY_BUDGET],\n        payload=body,\n    )\n\n    return response_data[\"category_group\"]\n
"},{"location":"reference/models/categories/","title":"categories","text":"

Lunch Money - Categories

https://lunchmoney.dev/#categories

"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient","title":"CategoriesClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Categories Interactions

Source code in lunchable/models/categories.py
class CategoriesClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Categories Interactions\n    \"\"\"\n\n    def get_categories(self) -> List[CategoriesObject]:\n        \"\"\"\n        Get Spending categories\n\n        Use this endpoint to get a list of all categories associated with the user's account.\n        https://lunchmoney.dev/#get-all-categories\n\n        Returns\n        -------\n        List[CategoriesObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_CATEGORIES\n        )\n        categories = response_data[\"categories\"]\n        category_objects = [\n            CategoriesObject.model_validate(item) for item in categories\n        ]\n        return category_objects\n\n    def insert_category(\n        self,\n        name: str,\n        description: Optional[str] = None,\n        is_income: Optional[bool] = False,\n        exclude_from_budget: Optional[bool] = False,\n        exclude_from_totals: Optional[bool] = False,\n    ) -> int:\n        \"\"\"\n        Create a Spending Category\n\n        Use this to create a single category\n        https://lunchmoney.dev/#create-category\n\n        Parameters\n        ----------\n        name: str\n            Name of category. Must be between 1 and 40 characters.\n        description: Optional[str]\n            Description of category. Must be less than 140 categories. Defaults to None.\n        is_income: Optional[bool]\n            Whether or not transactions in this category should be treated as income.\n            Defaults to False.\n        exclude_from_budget: Optional[bool]\n            Whether or not transactions in this category should be excluded from budgets.\n            Defaults to False.\n        exclude_from_totals: Optional[bool]\n            Whether or not transactions in this category should be excluded from\n            calculated totals. Defaults to False.\n\n        Returns\n        -------\n        int\n            ID of the newly created category\n        \"\"\"\n        category_body = ModelCreateCategory(\n            name=name,\n            description=description,\n            is_income=is_income,\n            exclude_from_budget=exclude_from_budget,\n            exclude_from_totals=exclude_from_totals,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=APIConfig.LUNCHMONEY_CATEGORIES,\n            payload=category_body,\n        )\n        return response_data[\"category_id\"]\n\n    def get_category(self, category_id: int) -> CategoriesObject:\n        \"\"\"\n        Get single category\n\n        Use this endpoint to get hydrated details on a single category. Note that if\n        this category is part of a category group, its properties (is_income,\n        exclude_from_budget, exclude_from_totals) will inherit from the category group.\n\n        https://lunchmoney.dev/#get-single-category\n\n        Parameters\n        ----------\n        category_id : int\n            Id of the Lunch Money Category\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n        )\n        return CategoriesObject.model_validate(response_data)\n\n    def remove_category(self, category_id: int) -> bool:\n        \"\"\"\n        Delete a single category\n\n        Use this endpoint to delete a single category or category group. This will\n        only work if there are no dependencies, such as existing budgets for the\n        category, categorized transactions, categorized recurring items, etc. If\n        there are dependents, this endpoint will return what the dependents are\n        and how many there are.\n\n        https://lunchmoney.dev/#delete-category\n\n        Parameters\n        ----------\n        category_id : int\n            Id of the Lunch Money Category\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.DELETE,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n        )\n        if response_data is not True:\n            raise LunchMoneyError(\n                f\"That Category ({category_id}) has Dependents: \"\n                f\"{json.dumps(response_data, indent=4)}\"\n            )\n        return response_data\n\n    def remove_category_force(self, category_id: int) -> bool:\n        \"\"\"\n        Forcefully delete a single category\n\n        Use this endpoint to force delete a single category or category group and\n        along with it, disassociate the category from any transactions, recurring\n        items, budgets, etc.\n\n        Note: it is best practice to first try the Delete Category endpoint to ensure\n        you don't accidentally delete any data. Disassociation/deletion of the data\n        arising from this endpoint is irreversible!\n\n        https://lunchmoney.dev/#force-delete-category\n\n        Parameters\n        ----------\n        category_id : int\n            Id of the Lunch Money Category\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.DELETE,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id, \"force\"],\n        )\n        return response_data\n\n    def update_category(\n        self,\n        category_id: int,\n        name: Optional[str] = None,\n        description: Optional[str] = None,\n        is_income: Optional[bool] = None,\n        exclude_from_budget: Optional[bool] = None,\n        exclude_from_totals: Optional[bool] = None,\n        group_id: Optional[int] = None,\n    ) -> bool:\n        \"\"\"\n        Update a single category\n\n        Use this endpoint to update the properties for a single category or category group\n\n        https://lunchmoney.dev/#update-category\n\n        Parameters\n        ----------\n        category_id : int\n            Id of the Lunch Money Category\n        name: str\n            Name of category. Must be between 1 and 40 characters.\n        description: Optional[str]\n            Description of category. Must be less than 140 categories. Defaults to None.\n        is_income: Optional[bool]\n            Whether or not transactions in this category should be treated as income.\n            Defaults to False.\n        exclude_from_budget: Optional[bool]\n            Whether or not transactions in this category should be excluded from budgets.\n            Defaults to False.\n        exclude_from_totals: Optional[bool]\n            Whether or not transactions in this category should be excluded from\n            calculated totals. Defaults to False.\n        group_id: Optional[int]\n            For a category, set the group_id to include it in a category group\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        payload = _CategoriesParamsPut(\n            name=name,\n            description=description,\n            is_income=is_income,\n            exclude_from_budget=exclude_from_budget,\n            exclude_from_totals=exclude_from_totals,\n            group_id=group_id,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n            payload=payload,\n        )\n        return response_data\n\n    def insert_category_group(\n        self,\n        name: str,\n        description: Optional[str] = None,\n        is_income: Optional[bool] = False,\n        exclude_from_budget: Optional[bool] = False,\n        exclude_from_totals: Optional[bool] = False,\n        category_ids: Optional[List[int]] = None,\n        new_categories: Optional[List[str]] = None,\n    ) -> int:\n        \"\"\"\n        Create a Spending Category Group\n\n        Use this endpoint to create a single category group\n        https://lunchmoney.dev/#create-category-group\n\n        Parameters\n        ----------\n        name: str\n            Name of category. Must be between 1 and 40 characters.\n        description: Optional[str]\n            Description of category. Must be less than 140 categories. Defaults to None.\n        is_income: Optional[bool]\n            Whether or not transactions in this category should be treated as income.\n            Defaults to False.\n        exclude_from_budget: Optional[bool]\n            Whether or not transactions in this category should be excluded from budgets.\n            Defaults to False.\n        exclude_from_totals: Optional[bool]\n            Whether or not transactions in this category should be excluded from\n            calculated totals. Defaults to False.\n        category_ids: Optional[List[int]]\n            Array of category_id to include in the category group.\n        new_categories: Optional[List[str]]\n            Array of strings representing new categories to create and subsequently\n            include in the category group.\n\n        Returns\n        -------\n        int\n            ID of the newly created category group\n        \"\"\"\n        payload = _CategoriesParamsPost(\n            name=name,\n            description=description,\n            is_income=is_income,\n            exclude_from_budget=exclude_from_budget,\n            exclude_from_totals=exclude_from_totals,\n            category_ids=category_ids,\n            new_categories=new_categories,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[APIConfig.LUNCHMONEY_CATEGORIES, \"group\"],\n            payload=payload,\n        )\n        return response_data[\"category_id\"]\n\n    def insert_into_category_group(\n        self,\n        category_group_id: int,\n        category_ids: Optional[List[int]] = None,\n        new_categories: Optional[List[str]] = None,\n    ) -> CategoriesObject:\n        \"\"\"\n        Add to a Category Group\n\n        Use this endpoint to add categories (either existing or new) to a single\n        category group\n\n        https://lunchmoney.dev/#add-to-category-group\n\n        Parameters\n        ----------\n        category_group_id: int\n            Id of the Lunch Money Category Group\n        category_ids: Optional[List[int]]\n            Array of category_id to include in the category group.\n        new_categories: Optional[List[str]]\n            Array of strings representing new categories to create and subsequently\n            include in the category group.\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        payload = _CategoriesAddParamsPost(\n            category_ids=category_ids, new_categories=new_categories\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[\n                APIConfig.LUNCHMONEY_CATEGORIES,\n                \"group\",\n                category_group_id,\n                \"add\",\n            ],\n            payload=payload,\n        )\n        return CategoriesObject.model_validate(response_data)\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.get_categories","title":"get_categories()","text":"

Get Spending categories

Use this endpoint to get a list of all categories associated with the user's account. https://lunchmoney.dev/#get-all-categories

Returns:

Type Description List[CategoriesObject] Source code in lunchable/models/categories.py
def get_categories(self) -> List[CategoriesObject]:\n    \"\"\"\n    Get Spending categories\n\n    Use this endpoint to get a list of all categories associated with the user's account.\n    https://lunchmoney.dev/#get-all-categories\n\n    Returns\n    -------\n    List[CategoriesObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_CATEGORIES\n    )\n    categories = response_data[\"categories\"]\n    category_objects = [\n        CategoriesObject.model_validate(item) for item in categories\n    ]\n    return category_objects\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.get_category","title":"get_category(category_id)","text":"

Get single category

Use this endpoint to get hydrated details on a single category. Note that if this category is part of a category group, its properties (is_income, exclude_from_budget, exclude_from_totals) will inherit from the category group.

https://lunchmoney.dev/#get-single-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description CategoriesObject Source code in lunchable/models/categories.py
def get_category(self, category_id: int) -> CategoriesObject:\n    \"\"\"\n    Get single category\n\n    Use this endpoint to get hydrated details on a single category. Note that if\n    this category is part of a category group, its properties (is_income,\n    exclude_from_budget, exclude_from_totals) will inherit from the category group.\n\n    https://lunchmoney.dev/#get-single-category\n\n    Parameters\n    ----------\n    category_id : int\n        Id of the Lunch Money Category\n\n    Returns\n    -------\n    CategoriesObject\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n    )\n    return CategoriesObject.model_validate(response_data)\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.insert_category","title":"insert_category(name, description=None, is_income=False, exclude_from_budget=False, exclude_from_totals=False)","text":"

Create a Spending Category

Use this to create a single category https://lunchmoney.dev/#create-category

Parameters:

Name Type Description Default name str

Name of category. Must be between 1 and 40 characters.

required description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

False exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

False exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

False

Returns:

Type Description int

ID of the newly created category

Source code in lunchable/models/categories.py
def insert_category(\n    self,\n    name: str,\n    description: Optional[str] = None,\n    is_income: Optional[bool] = False,\n    exclude_from_budget: Optional[bool] = False,\n    exclude_from_totals: Optional[bool] = False,\n) -> int:\n    \"\"\"\n    Create a Spending Category\n\n    Use this to create a single category\n    https://lunchmoney.dev/#create-category\n\n    Parameters\n    ----------\n    name: str\n        Name of category. Must be between 1 and 40 characters.\n    description: Optional[str]\n        Description of category. Must be less than 140 categories. Defaults to None.\n    is_income: Optional[bool]\n        Whether or not transactions in this category should be treated as income.\n        Defaults to False.\n    exclude_from_budget: Optional[bool]\n        Whether or not transactions in this category should be excluded from budgets.\n        Defaults to False.\n    exclude_from_totals: Optional[bool]\n        Whether or not transactions in this category should be excluded from\n        calculated totals. Defaults to False.\n\n    Returns\n    -------\n    int\n        ID of the newly created category\n    \"\"\"\n    category_body = ModelCreateCategory(\n        name=name,\n        description=description,\n        is_income=is_income,\n        exclude_from_budget=exclude_from_budget,\n        exclude_from_totals=exclude_from_totals,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=APIConfig.LUNCHMONEY_CATEGORIES,\n        payload=category_body,\n    )\n    return response_data[\"category_id\"]\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.insert_category_group","title":"insert_category_group(name, description=None, is_income=False, exclude_from_budget=False, exclude_from_totals=False, category_ids=None, new_categories=None)","text":"

Create a Spending Category Group

Use this endpoint to create a single category group https://lunchmoney.dev/#create-category-group

Parameters:

Name Type Description Default name str

Name of category. Must be between 1 and 40 characters.

required description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

False exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

False exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

False category_ids Optional[List[int]]

Array of category_id to include in the category group.

None new_categories Optional[List[str]]

Array of strings representing new categories to create and subsequently include in the category group.

None

Returns:

Type Description int

ID of the newly created category group

Source code in lunchable/models/categories.py
def insert_category_group(\n    self,\n    name: str,\n    description: Optional[str] = None,\n    is_income: Optional[bool] = False,\n    exclude_from_budget: Optional[bool] = False,\n    exclude_from_totals: Optional[bool] = False,\n    category_ids: Optional[List[int]] = None,\n    new_categories: Optional[List[str]] = None,\n) -> int:\n    \"\"\"\n    Create a Spending Category Group\n\n    Use this endpoint to create a single category group\n    https://lunchmoney.dev/#create-category-group\n\n    Parameters\n    ----------\n    name: str\n        Name of category. Must be between 1 and 40 characters.\n    description: Optional[str]\n        Description of category. Must be less than 140 categories. Defaults to None.\n    is_income: Optional[bool]\n        Whether or not transactions in this category should be treated as income.\n        Defaults to False.\n    exclude_from_budget: Optional[bool]\n        Whether or not transactions in this category should be excluded from budgets.\n        Defaults to False.\n    exclude_from_totals: Optional[bool]\n        Whether or not transactions in this category should be excluded from\n        calculated totals. Defaults to False.\n    category_ids: Optional[List[int]]\n        Array of category_id to include in the category group.\n    new_categories: Optional[List[str]]\n        Array of strings representing new categories to create and subsequently\n        include in the category group.\n\n    Returns\n    -------\n    int\n        ID of the newly created category group\n    \"\"\"\n    payload = _CategoriesParamsPost(\n        name=name,\n        description=description,\n        is_income=is_income,\n        exclude_from_budget=exclude_from_budget,\n        exclude_from_totals=exclude_from_totals,\n        category_ids=category_ids,\n        new_categories=new_categories,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, \"group\"],\n        payload=payload,\n    )\n    return response_data[\"category_id\"]\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.insert_into_category_group","title":"insert_into_category_group(category_group_id, category_ids=None, new_categories=None)","text":"

Add to a Category Group

Use this endpoint to add categories (either existing or new) to a single category group

https://lunchmoney.dev/#add-to-category-group

Parameters:

Name Type Description Default category_group_id int

Id of the Lunch Money Category Group

required category_ids Optional[List[int]]

Array of category_id to include in the category group.

None new_categories Optional[List[str]]

Array of strings representing new categories to create and subsequently include in the category group.

None

Returns:

Type Description CategoriesObject Source code in lunchable/models/categories.py
def insert_into_category_group(\n    self,\n    category_group_id: int,\n    category_ids: Optional[List[int]] = None,\n    new_categories: Optional[List[str]] = None,\n) -> CategoriesObject:\n    \"\"\"\n    Add to a Category Group\n\n    Use this endpoint to add categories (either existing or new) to a single\n    category group\n\n    https://lunchmoney.dev/#add-to-category-group\n\n    Parameters\n    ----------\n    category_group_id: int\n        Id of the Lunch Money Category Group\n    category_ids: Optional[List[int]]\n        Array of category_id to include in the category group.\n    new_categories: Optional[List[str]]\n        Array of strings representing new categories to create and subsequently\n        include in the category group.\n\n    Returns\n    -------\n    CategoriesObject\n    \"\"\"\n    payload = _CategoriesAddParamsPost(\n        category_ids=category_ids, new_categories=new_categories\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[\n            APIConfig.LUNCHMONEY_CATEGORIES,\n            \"group\",\n            category_group_id,\n            \"add\",\n        ],\n        payload=payload,\n    )\n    return CategoriesObject.model_validate(response_data)\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.remove_category","title":"remove_category(category_id)","text":"

Delete a single category

Use this endpoint to delete a single category or category group. This will only work if there are no dependencies, such as existing budgets for the category, categorized transactions, categorized recurring items, etc. If there are dependents, this endpoint will return what the dependents are and how many there are.

https://lunchmoney.dev/#delete-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description bool Source code in lunchable/models/categories.py
def remove_category(self, category_id: int) -> bool:\n    \"\"\"\n    Delete a single category\n\n    Use this endpoint to delete a single category or category group. This will\n    only work if there are no dependencies, such as existing budgets for the\n    category, categorized transactions, categorized recurring items, etc. If\n    there are dependents, this endpoint will return what the dependents are\n    and how many there are.\n\n    https://lunchmoney.dev/#delete-category\n\n    Parameters\n    ----------\n    category_id : int\n        Id of the Lunch Money Category\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.DELETE,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n    )\n    if response_data is not True:\n        raise LunchMoneyError(\n            f\"That Category ({category_id}) has Dependents: \"\n            f\"{json.dumps(response_data, indent=4)}\"\n        )\n    return response_data\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.remove_category_force","title":"remove_category_force(category_id)","text":"

Forcefully delete a single category

Use this endpoint to force delete a single category or category group and along with it, disassociate the category from any transactions, recurring items, budgets, etc.

Note: it is best practice to first try the Delete Category endpoint to ensure you don't accidentally delete any data. Disassociation/deletion of the data arising from this endpoint is irreversible!

https://lunchmoney.dev/#force-delete-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required

Returns:

Type Description bool Source code in lunchable/models/categories.py
def remove_category_force(self, category_id: int) -> bool:\n    \"\"\"\n    Forcefully delete a single category\n\n    Use this endpoint to force delete a single category or category group and\n    along with it, disassociate the category from any transactions, recurring\n    items, budgets, etc.\n\n    Note: it is best practice to first try the Delete Category endpoint to ensure\n    you don't accidentally delete any data. Disassociation/deletion of the data\n    arising from this endpoint is irreversible!\n\n    https://lunchmoney.dev/#force-delete-category\n\n    Parameters\n    ----------\n    category_id : int\n        Id of the Lunch Money Category\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.DELETE,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id, \"force\"],\n    )\n    return response_data\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesClient.update_category","title":"update_category(category_id, name=None, description=None, is_income=None, exclude_from_budget=None, exclude_from_totals=None, group_id=None)","text":"

Update a single category

Use this endpoint to update the properties for a single category or category group

https://lunchmoney.dev/#update-category

Parameters:

Name Type Description Default category_id int

Id of the Lunch Money Category

required name Optional[str]

Name of category. Must be between 1 and 40 characters.

None description Optional[str]

Description of category. Must be less than 140 categories. Defaults to None.

None is_income Optional[bool]

Whether or not transactions in this category should be treated as income. Defaults to False.

None exclude_from_budget Optional[bool]

Whether or not transactions in this category should be excluded from budgets. Defaults to False.

None exclude_from_totals Optional[bool]

Whether or not transactions in this category should be excluded from calculated totals. Defaults to False.

None group_id Optional[int]

For a category, set the group_id to include it in a category group

None

Returns:

Type Description bool Source code in lunchable/models/categories.py
def update_category(\n    self,\n    category_id: int,\n    name: Optional[str] = None,\n    description: Optional[str] = None,\n    is_income: Optional[bool] = None,\n    exclude_from_budget: Optional[bool] = None,\n    exclude_from_totals: Optional[bool] = None,\n    group_id: Optional[int] = None,\n) -> bool:\n    \"\"\"\n    Update a single category\n\n    Use this endpoint to update the properties for a single category or category group\n\n    https://lunchmoney.dev/#update-category\n\n    Parameters\n    ----------\n    category_id : int\n        Id of the Lunch Money Category\n    name: str\n        Name of category. Must be between 1 and 40 characters.\n    description: Optional[str]\n        Description of category. Must be less than 140 categories. Defaults to None.\n    is_income: Optional[bool]\n        Whether or not transactions in this category should be treated as income.\n        Defaults to False.\n    exclude_from_budget: Optional[bool]\n        Whether or not transactions in this category should be excluded from budgets.\n        Defaults to False.\n    exclude_from_totals: Optional[bool]\n        Whether or not transactions in this category should be excluded from\n        calculated totals. Defaults to False.\n    group_id: Optional[int]\n        For a category, set the group_id to include it in a category group\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    payload = _CategoriesParamsPut(\n        name=name,\n        description=description,\n        is_income=is_income,\n        exclude_from_budget=exclude_from_budget,\n        exclude_from_totals=exclude_from_totals,\n        group_id=group_id,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[APIConfig.LUNCHMONEY_CATEGORIES, category_id],\n        payload=payload,\n    )\n    return response_data\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoriesObject","title":"CategoriesObject","text":"

Bases: LunchableModel

Lunch Money Spending Categories

https://lunchmoney.dev/#categories-object

Parameters:

Name Type Description Default id int

A unique identifier for the category.

required name str

The name of the category. Must be between 1 and 40 characters.

required description str | None

The description of the category. Must not exceed 140 characters.

None is_income bool

If true, the transactions in this category will be treated as income.

required exclude_from_budget bool

If true, the transactions in this category will be excluded from the budget.

required exclude_from_totals bool

If true, the transactions in this category will be excluded from totals.

required updated_at datetime | None

The date and time of when the category was last updated (in the ISO 8601 extended format).

None created_at datetime | None

The date and time of when the category was created (in the ISO 8601 extended format).

None is_group bool

If true, the category is a group that can be a parent to other categories.

required group_id int | None

The ID of a category group (or null if the category doesn't belong to a category group).

None children List[CategoryChild] | None

For category groups, this will populate with the categories nested within and include id, name, description and created_at fields.

None Source code in lunchable/models/categories.py
class CategoriesObject(LunchableModel):\n    \"\"\"\n    Lunch Money Spending Categories\n\n    https://lunchmoney.dev/#categories-object\n    \"\"\"\n\n    _name_description = \"The name of the category. Must be between 1 and 40 characters.\"\n    _description_description = (\n        \"The description of the category. Must not exceed 140 characters.\"\n    )\n    _is_income_description = (\n        \"If true, the transactions in this category will be treated as income.\"\n    )\n    _exclude_from_budget_description = \"\"\"\n    If true, the transactions in this category will be excluded from the budget.\n    \"\"\"\n    _exclude_from_totals_description = \"\"\"\n    If true, the transactions in this category will be excluded from totals.\n    \"\"\"\n    _updated_at_description = \"\"\"\n    The date and time of when the category was last updated (in the ISO 8601 extended format).\n    \"\"\"\n    _created_at_description = \"\"\"\n    The date and time of when the category was created (in the ISO 8601 extended format).\n    \"\"\"\n    _is_group_description = \"\"\"\n    If true, the category is a group that can be a parent to other categories.\n    \"\"\"\n    _group_id_description = \"\"\"\n    The ID of a category group (or null if the category doesn't belong to a category group).\n    \"\"\"\n    _children_description = (\n        \"For category groups, this will populate with the \"\n        \"categories nested within and include id, name, \"\n        \"description and created_at fields.\"\n    )\n\n    id: int = Field(description=\"A unique identifier for the category.\")\n    name: str = Field(min_length=1, max_length=40, description=_name_description)\n    description: Optional[str] = Field(\n        None, max_length=140, description=_description_description\n    )\n    is_income: bool = Field(description=_is_income_description)\n    exclude_from_budget: bool = Field(description=_exclude_from_budget_description)\n    exclude_from_totals: bool = Field(description=_exclude_from_totals_description)\n    updated_at: Optional[datetime.datetime] = Field(\n        None, description=_updated_at_description\n    )\n    created_at: Optional[datetime.datetime] = Field(\n        None, description=_created_at_description\n    )\n    is_group: bool = Field(description=_is_group_description)\n    group_id: Optional[int] = Field(None, description=_group_id_description)\n    children: Optional[List[CategoryChild]] = Field(\n        None, description=_children_description\n    )\n
"},{"location":"reference/models/categories/#lunchable.models.categories.CategoryChild","title":"CategoryChild","text":"

Bases: LunchableModel

Child Entry on the Category Object

Parameters:

Name Type Description Default id int required name str required description str | None None created_at datetime | None None Source code in lunchable/models/categories.py
class CategoryChild(LunchableModel):\n    \"\"\"\n    Child Entry on the Category Object\n    \"\"\"\n\n    id: int\n    name: str = Field(min_length=1, max_length=40)\n    description: Optional[str] = Field(None, max_length=140)\n    created_at: Optional[datetime.datetime] = None\n
"},{"location":"reference/models/categories/#lunchable.models.categories.ModelCreateCategory","title":"ModelCreateCategory","text":"

Bases: LunchableModel

https://lunchmoney.dev/#create-category

Parameters:

Name Type Description Default name str required description str | None None is_income bool | None False exclude_from_budget bool | None False exclude_from_totals bool | None False Source code in lunchable/models/categories.py
class ModelCreateCategory(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#create-category\n    \"\"\"\n\n    name: str\n    description: Optional[str] = None\n    is_income: Optional[bool] = False\n    exclude_from_budget: Optional[bool] = False\n    exclude_from_totals: Optional[bool] = False\n
"},{"location":"reference/models/crypto/","title":"crypto","text":"

Lunch Money - Crypto

https://lunchmoney.dev/#crypto

"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoClient","title":"CryptoClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Tag Interactions

Source code in lunchable/models/crypto.py
class CryptoClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Tag Interactions\n    \"\"\"\n\n    def get_crypto(self) -> List[CryptoObject]:\n        \"\"\"\n        Get Crypto Assets\n\n        Use this endpoint to get a list of all cryptocurrency assets associated\n        with the user's account. Both crypto balances from synced and manual\n        accounts will be returned.\n\n        https://lunchmoney.dev/#get-all-crypto\n\n        Returns\n        -------\n        List[CryptoObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_CRYPTO\n        )\n        crypto_data = response_data[\"crypto\"]\n        crypto_objects = [CryptoObject.model_validate(item) for item in crypto_data]\n        return crypto_objects\n\n    def update_crypto(\n        self,\n        crypto_id: int,\n        name: Optional[str] = None,\n        display_name: Optional[str] = None,\n        institution_name: Optional[str] = None,\n        balance: Optional[float] = None,\n        currency: Optional[str] = None,\n    ) -> CryptoObject:\n        \"\"\"\n        Update a Manual Crypto Asset\n\n        Use this endpoint to update a single manually-managed crypto asset (does not include\n        assets received from syncing with your wallet/exchange/etc). These are denoted by\n        source: manual from the GET call above.\n\n        https://lunchmoney.dev/#update-manual-crypto-asset\n\n        Parameters\n        ----------\n        crypto_id: int\n            ID of the crypto asset to update\n        name: Optional[str]\n            Official or full name of the account. Max 45 characters\n        display_name: Optional[str]\n            Display name for the account. Max 25 characters\n        institution_name: Optional[str]\n            Name of provider that holds the account. Max 50 characters\n        balance: Optional[float]\n            Numeric value of the current balance of the account. Do not include any\n            special characters aside from a decimal point!\n        currency: Optional[str]\n            Cryptocurrency that is supported for manual tracking in our database\n\n        Returns\n        -------\n        CryptoObject\n        \"\"\"\n        crypto_body = CryptoParamsPut(\n            name=name,\n            display_name=display_name,\n            institution_name=institution_name,\n            balance=balance,\n            currency=currency,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[\n                APIConfig.LUNCHMONEY_CRYPTO,\n                APIConfig.LUNCHMONEY_CRYPTO_MANUAL,\n                crypto_id,\n            ],\n            payload=crypto_body,\n        )\n        crypto = CryptoObject.model_validate(response_data)\n        return crypto\n
"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoClient.get_crypto","title":"get_crypto()","text":"

Get Crypto Assets

Use this endpoint to get a list of all cryptocurrency assets associated with the user's account. Both crypto balances from synced and manual accounts will be returned.

https://lunchmoney.dev/#get-all-crypto

Returns:

Type Description List[CryptoObject] Source code in lunchable/models/crypto.py
def get_crypto(self) -> List[CryptoObject]:\n    \"\"\"\n    Get Crypto Assets\n\n    Use this endpoint to get a list of all cryptocurrency assets associated\n    with the user's account. Both crypto balances from synced and manual\n    accounts will be returned.\n\n    https://lunchmoney.dev/#get-all-crypto\n\n    Returns\n    -------\n    List[CryptoObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_CRYPTO\n    )\n    crypto_data = response_data[\"crypto\"]\n    crypto_objects = [CryptoObject.model_validate(item) for item in crypto_data]\n    return crypto_objects\n
"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoClient.update_crypto","title":"update_crypto(crypto_id, name=None, display_name=None, institution_name=None, balance=None, currency=None)","text":"

Update a Manual Crypto Asset

Use this endpoint to update a single manually-managed crypto asset (does not include assets received from syncing with your wallet/exchange/etc). These are denoted by source: manual from the GET call above.

https://lunchmoney.dev/#update-manual-crypto-asset

Parameters:

Name Type Description Default crypto_id int

ID of the crypto asset to update

required name Optional[str]

Official or full name of the account. Max 45 characters

None display_name Optional[str]

Display name for the account. Max 25 characters

None institution_name Optional[str]

Name of provider that holds the account. Max 50 characters

None balance Optional[float]

Numeric value of the current balance of the account. Do not include any special characters aside from a decimal point!

None currency Optional[str]

Cryptocurrency that is supported for manual tracking in our database

None

Returns:

Type Description CryptoObject Source code in lunchable/models/crypto.py
def update_crypto(\n    self,\n    crypto_id: int,\n    name: Optional[str] = None,\n    display_name: Optional[str] = None,\n    institution_name: Optional[str] = None,\n    balance: Optional[float] = None,\n    currency: Optional[str] = None,\n) -> CryptoObject:\n    \"\"\"\n    Update a Manual Crypto Asset\n\n    Use this endpoint to update a single manually-managed crypto asset (does not include\n    assets received from syncing with your wallet/exchange/etc). These are denoted by\n    source: manual from the GET call above.\n\n    https://lunchmoney.dev/#update-manual-crypto-asset\n\n    Parameters\n    ----------\n    crypto_id: int\n        ID of the crypto asset to update\n    name: Optional[str]\n        Official or full name of the account. Max 45 characters\n    display_name: Optional[str]\n        Display name for the account. Max 25 characters\n    institution_name: Optional[str]\n        Name of provider that holds the account. Max 50 characters\n    balance: Optional[float]\n        Numeric value of the current balance of the account. Do not include any\n        special characters aside from a decimal point!\n    currency: Optional[str]\n        Cryptocurrency that is supported for manual tracking in our database\n\n    Returns\n    -------\n    CryptoObject\n    \"\"\"\n    crypto_body = CryptoParamsPut(\n        name=name,\n        display_name=display_name,\n        institution_name=institution_name,\n        balance=balance,\n        currency=currency,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[\n            APIConfig.LUNCHMONEY_CRYPTO,\n            APIConfig.LUNCHMONEY_CRYPTO_MANUAL,\n            crypto_id,\n        ],\n        payload=crypto_body,\n    )\n    crypto = CryptoObject.model_validate(response_data)\n    return crypto\n
"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoObject","title":"CryptoObject","text":"

Bases: LunchableModel

Crypto Asset Object

https://lunchmoney.dev/#crypto-object

Parameters:

Name Type Description Default id int

Unique identifier for a manual crypto account (no ID for synced accounts)

required zabo_account_id int | None

Unique identifier for a synced crypto account (no ID for manual accounts, multiple currencies may have the same zabo_account_id)

None source str

synced (this account is synced via a wallet, exchange, etc.) or manual (this account balance is managed manually)

required name str

Name of the crypto asset

required display_name str | None

Display name of the crypto asset (as set by user)

None balance float

Current balance

required balance_as_of datetime | None

Date/time the balance was last updated in ISO 8601 extended format

None currency str | None

Abbreviation for the cryptocurrency

None status str | None

The current status of the crypto account. Either active or in error.

None institution_name str | None

Name of provider holding the asset

None created_at datetime

Date/time the asset was created in ISO 8601 extended format

required Source code in lunchable/models/crypto.py
class CryptoObject(LunchableModel):\n    \"\"\"\n    Crypto Asset Object\n\n    https://lunchmoney.dev/#crypto-object\n    \"\"\"\n\n    _id_description = (\n        \"Unique identifier for a manual crypto account (no ID for synced accounts)\"\n    )\n    _zabo_account_id_description = \"\"\"\n    Unique identifier for a synced crypto account (no ID for manual accounts,\n    multiple currencies may have the same zabo_account_id)\n    \"\"\"\n    _source_description = \"\"\"\n    `synced` (this account is synced via a wallet, exchange, etc.) or `manual` (this account\n    balance is managed manually)\n    \"\"\"\n    _display_name_description = \"Display name of the crypto asset (as set by user)\"\n    _balance_as_of_description = \"\"\"\n    Date/time the balance was last updated in ISO 8601 extended format\n    \"\"\"\n    _status_description = (\n        \"The current status of the crypto account. Either active or in error.\"\n    )\n    _created_at_description = (\n        \"Date/time the asset was created in ISO 8601 extended format\"\n    )\n\n    id: int = Field(description=_id_description)\n    zabo_account_id: Optional[int] = Field(\n        None, description=_zabo_account_id_description\n    )\n    source: str = Field(description=_source_description)\n    name: str = Field(description=\"Name of the crypto asset\")\n    display_name: Optional[str] = Field(None, description=_display_name_description)\n    balance: float = Field(description=\"Current balance\")\n    balance_as_of: Optional[datetime.datetime] = Field(\n        None, description=_balance_as_of_description\n    )\n    currency: Optional[str] = Field(\n        None, description=\"Abbreviation for the cryptocurrency\"\n    )\n    status: Optional[str] = Field(None, description=_status_description)\n    institution_name: Optional[str] = Field(\n        default=None, description=\"Name of provider holding the asset\"\n    )\n    created_at: datetime.datetime = Field(description=_created_at_description)\n
"},{"location":"reference/models/crypto/#lunchable.models.crypto.CryptoParamsPut","title":"CryptoParamsPut","text":"

Bases: LunchableModel

https://lunchmoney.dev/#update-manual-crypto-asset

Parameters:

Name Type Description Default name str | None None display_name str | None None institution_name str | None None balance float | None None currency str | None None Source code in lunchable/models/crypto.py
class CryptoParamsPut(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#update-manual-crypto-asset\n    \"\"\"\n\n    name: Optional[str] = None\n    display_name: Optional[str] = None\n    institution_name: Optional[str] = None\n    balance: Optional[float] = None\n    currency: Optional[str] = None\n
"},{"location":"reference/models/plaid_accounts/","title":"plaid_accounts","text":"

Lunch Money - Plaid Accounts

https://lunchmoney.dev/#plaid-accounts

"},{"location":"reference/models/plaid_accounts/#lunchable.models.plaid_accounts.PlaidAccountObject","title":"PlaidAccountObject","text":"

Bases: LunchableModel

Assets synced from Plaid

Similar to AssetObjects, these accounts are linked to remote sources in Plaid.

https://lunchmoney.dev/#plaid-accounts-object

Parameters:

Name Type Description Default id int

Unique identifier of Plaid account

required date_linked date

Date account was first linked in ISO 8601 extended format

required name str

Name of the account. Can be overridden by the user. Field is originally set by Plaid\")

required type str

Primary type of account. Typically one of: [credit, depository, brokerage, cash, loan, investment]. This field is set by Plaid and cannot be altered.

required subtype str

Optional subtype name of account. This field is set by Plaid and cannot be altered

required mask str | None

Mask (last 3 to 4 digits of account) of account. This field is set by Plaid and cannot be altered

None institution_name str

Name of institution associated with account. This field is set by Plaid and cannot be altered

required status str

Denotes the current status of the account within Lunch Money. Must be one of: active (Account is active and in good state), inactive (Account marked inactive from user. No transactions fetched or balance update for this account), relink (Account needs to be relinked with Plaid), syncing (Account is awaiting first import of transactions), error (Account is in error with Plaid), not found (Account is in error with Plaid), not supported (Account is in error with Plaid)

required last_import datetime | None

Date of last imported transaction in ISO 8601 extended format (not necessarily date of last attempted import)

None balance float | None

Current balance of the account in numeric format to 4 decimal places. This field is set by Plaid and cannot be altered

None currency str

Currency of account balance in ISO 4217 format. This field is set by Plaid and cannot be altered

required balance_last_update datetime

Date balance was last updated in ISO 8601 extended format. This field is set by Plaid and cannot be altered

required limit int | None

Optional credit limit of the account. This field is set by Plaid and cannot be altered

None Source code in lunchable/models/plaid_accounts.py
class PlaidAccountObject(LunchableModel):\n    \"\"\"\n    Assets synced from Plaid\n\n    Similar to AssetObjects, these accounts are linked to remote sources in Plaid.\n\n    https://lunchmoney.dev/#plaid-accounts-object\n    \"\"\"\n\n    _date_linked_description = (\n        \"Date account was first linked in ISO 8601 extended format\"\n    )\n    _name_description = \"\"\"\n    Name of the account. Can be overridden by the user. Field is originally set by Plaid\")\n    \"\"\"\n    _type_description = \"\"\"\n    Primary type of account. Typically one of: [credit, depository, brokerage, cash,\n    loan, investment]. This field is set by Plaid and cannot be altered.\n    \"\"\"\n    _subtype_description = \"\"\"\n    Optional subtype name of account. This field is set by Plaid and cannot be altered\n    \"\"\"\n    _mask_description = \"\"\"\n    Mask (last 3 to 4 digits of account) of account. This field is set by\n    Plaid and cannot be altered\n    \"\"\"\n    _institution_name_description = \"\"\"\n    Name of institution associated with account. This field is set by\n    Plaid and cannot be altered\n    \"\"\"\n    _status_description = \"\"\"\n    Denotes the current status of the account within Lunch Money. Must be one of:\n    active (Account is active and in good state),\n    inactive (Account marked inactive from user. No transactions fetched or\n    balance update for this account),\n    relink (Account needs to be relinked with Plaid),\n    syncing (Account is awaiting first import of transactions),\n    error (Account is in error with Plaid),\n    not found (Account is in error with Plaid),\n    not supported (Account is in error with Plaid)\n    \"\"\"\n    _last_import_description = \"\"\"\n    Date of last imported transaction in ISO 8601 extended format (not necessarily\n    date of last attempted import)\n    \"\"\"\n    _balance_description = \"\"\"\n    Current balance of the account in numeric format to 4 decimal places. This field is\n    set by Plaid and cannot be altered\n    \"\"\"\n    _currency_description = \"\"\"\n    Currency of account balance in ISO 4217 format. This field is set by Plaid\n    and cannot be altered\n    \"\"\"\n    _balance_last_update_description = \"\"\"\n    Date balance was last updated in ISO 8601 extended format. This field is set\n    by Plaid and cannot be altered\n    \"\"\"\n    _limit_description = \"\"\"\n    Optional credit limit of the account. This field is set by Plaid and cannot be altered\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier of Plaid account\")\n    date_linked: datetime.date = Field(description=_date_linked_description)\n    name: str = Field(description=_name_description)\n    type: str = Field(description=_type_description)\n    subtype: str = Field(description=_subtype_description)\n    mask: Optional[str] = Field(None, description=_mask_description)\n    institution_name: str = Field(description=_institution_name_description)\n    status: str = Field(description=_status_description)\n    last_import: Optional[datetime.datetime] = Field(\n        None, description=_last_import_description\n    )\n    balance: Optional[float] = Field(None, description=_balance_description)\n    currency: str = Field(description=_currency_description)\n    balance_last_update: datetime.datetime = Field(\n        description=_balance_last_update_description\n    )\n    limit: Optional[int] = Field(None, description=_limit_description)\n
"},{"location":"reference/models/plaid_accounts/#lunchable.models.plaid_accounts.PlaidAccountsClient","title":"PlaidAccountsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Plaid Accounts Interactions

Source code in lunchable/models/plaid_accounts.py
class PlaidAccountsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Plaid Accounts Interactions\n    \"\"\"\n\n    def get_plaid_accounts(self) -> List[PlaidAccountObject]:\n        \"\"\"\n        Get Plaid Synced Assets\n\n        Get a list of all synced Plaid accounts associated with the user's account.\n\n        Plaid Accounts are individual bank accounts that you have linked to Lunch Money via Plaid.\n        You may link one bank but one bank might contain 4 accounts. Each of these\n        accounts is a Plaid Account. (https://lunchmoney.dev/#plaid-accounts-object)\n\n        Returns\n        -------\n        List[PlaidAccountObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_PLAID_ACCOUNTS\n        )\n        accounts = response_data.get(APIConfig.LUNCHMONEY_PLAID_ACCOUNTS)\n        account_objects = [PlaidAccountObject.model_validate(item) for item in accounts]\n        return account_objects\n
"},{"location":"reference/models/plaid_accounts/#lunchable.models.plaid_accounts.PlaidAccountsClient.get_plaid_accounts","title":"get_plaid_accounts()","text":"

Get Plaid Synced Assets

Get a list of all synced Plaid accounts associated with the user's account.

Plaid Accounts are individual bank accounts that you have linked to Lunch Money via Plaid. You may link one bank but one bank might contain 4 accounts. Each of these accounts is a Plaid Account. (https://lunchmoney.dev/#plaid-accounts-object)

Returns:

Type Description List[PlaidAccountObject] Source code in lunchable/models/plaid_accounts.py
def get_plaid_accounts(self) -> List[PlaidAccountObject]:\n    \"\"\"\n    Get Plaid Synced Assets\n\n    Get a list of all synced Plaid accounts associated with the user's account.\n\n    Plaid Accounts are individual bank accounts that you have linked to Lunch Money via Plaid.\n    You may link one bank but one bank might contain 4 accounts. Each of these\n    accounts is a Plaid Account. (https://lunchmoney.dev/#plaid-accounts-object)\n\n    Returns\n    -------\n    List[PlaidAccountObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_PLAID_ACCOUNTS\n    )\n    accounts = response_data.get(APIConfig.LUNCHMONEY_PLAID_ACCOUNTS)\n    account_objects = [PlaidAccountObject.model_validate(item) for item in accounts]\n    return account_objects\n
"},{"location":"reference/models/recurring_expenses/","title":"recurring_expenses","text":"

Lunch Money - Recurring Expenses

https://lunchmoney.dev/#recurring-expenses

"},{"location":"reference/models/recurring_expenses/#lunchable.models.recurring_expenses.RecurringExpenseParamsGet","title":"RecurringExpenseParamsGet","text":"

Bases: LunchableModel

https://lunchmoney.dev/#get-recurring-expenses

Parameters:

Name Type Description Default start_date date required debit_as_negative bool required Source code in lunchable/models/recurring_expenses.py
class RecurringExpenseParamsGet(LunchableModel):\n    \"\"\"\n    https://lunchmoney.dev/#get-recurring-expenses\n    \"\"\"\n\n    start_date: datetime.date\n    debit_as_negative: bool\n
"},{"location":"reference/models/recurring_expenses/#lunchable.models.recurring_expenses.RecurringExpensesClient","title":"RecurringExpensesClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Recurring Expenses Interactions

Source code in lunchable/models/recurring_expenses.py
class RecurringExpensesClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Recurring Expenses Interactions\n    \"\"\"\n\n    def get_recurring_expenses(\n        self,\n        start_date: Optional[datetime.date] = None,\n        debit_as_negative: bool = False,\n    ) -> List[RecurringExpensesObject]:\n        \"\"\"\n        Get Recurring Expenses\n\n        Retrieve a list of recurring expenses to expect for a specified period.\n\n        Every month, a different set of recurring expenses is expected. This is because recurring\n        expenses can be once a year, twice a year, every 4 months, etc.\n\n        If a recurring expense is listed as \u201ctwice a month\u201d, then that recurring expense will be\n        returned twice, each with a different billing date based on when the system believes that\n        recurring expense transaction is to be expected. If the recurring expense is listed as\n        \u201conce a week\u201d, then that recurring expense will be returned in this list as many times as\n        there are weeks for the specified month.\n\n        In the same vein, if a recurring expense that began last month is set to \u201cEvery 3\n        months\u201d, then that recurring expense will not show up in the results for this month.\n\n        Parameters\n        ----------\n        start_date : Optional[datetime.date]\n            Date to search. By default will return the first day of the current month\n        debit_as_negative: bool\n            Pass in true if you'd like expenses to be returned as negative amounts and credits as\n            positive amounts.\n\n        Returns\n        -------\n        List[RecurringExpensesObject]\n        \"\"\"\n        if start_date is None:\n            start_date = datetime.datetime.now().date().replace(day=1)\n        params = RecurringExpenseParamsGet(\n            start_date=start_date, debit_as_negative=debit_as_negative\n        ).model_dump()\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=[APIConfig.LUNCH_MONEY_RECURRING_EXPENSES],\n            params=params,\n        )\n        recurring_expenses = response_data.get(APIConfig.LUNCH_MONEY_RECURRING_EXPENSES)\n        recurring_expenses_objects = [\n            RecurringExpensesObject.model_validate(item) for item in recurring_expenses\n        ]\n        logger.debug(\n            \"%s RecurringExpensesObjects retrieved\", len(recurring_expenses_objects)\n        )\n        return recurring_expenses_objects\n
"},{"location":"reference/models/recurring_expenses/#lunchable.models.recurring_expenses.RecurringExpensesClient.get_recurring_expenses","title":"get_recurring_expenses(start_date=None, debit_as_negative=False)","text":"

Get Recurring Expenses

Retrieve a list of recurring expenses to expect for a specified period.

Every month, a different set of recurring expenses is expected. This is because recurring expenses can be once a year, twice a year, every 4 months, etc.

If a recurring expense is listed as \u201ctwice a month\u201d, then that recurring expense will be returned twice, each with a different billing date based on when the system believes that recurring expense transaction is to be expected. If the recurring expense is listed as \u201conce a week\u201d, then that recurring expense will be returned in this list as many times as there are weeks for the specified month.

In the same vein, if a recurring expense that began last month is set to \u201cEvery 3 months\u201d, then that recurring expense will not show up in the results for this month.

Parameters:

Name Type Description Default start_date Optional[date]

Date to search. By default will return the first day of the current month

None debit_as_negative bool

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts.

False

Returns:

Type Description List[RecurringExpensesObject] Source code in lunchable/models/recurring_expenses.py
def get_recurring_expenses(\n    self,\n    start_date: Optional[datetime.date] = None,\n    debit_as_negative: bool = False,\n) -> List[RecurringExpensesObject]:\n    \"\"\"\n    Get Recurring Expenses\n\n    Retrieve a list of recurring expenses to expect for a specified period.\n\n    Every month, a different set of recurring expenses is expected. This is because recurring\n    expenses can be once a year, twice a year, every 4 months, etc.\n\n    If a recurring expense is listed as \u201ctwice a month\u201d, then that recurring expense will be\n    returned twice, each with a different billing date based on when the system believes that\n    recurring expense transaction is to be expected. If the recurring expense is listed as\n    \u201conce a week\u201d, then that recurring expense will be returned in this list as many times as\n    there are weeks for the specified month.\n\n    In the same vein, if a recurring expense that began last month is set to \u201cEvery 3\n    months\u201d, then that recurring expense will not show up in the results for this month.\n\n    Parameters\n    ----------\n    start_date : Optional[datetime.date]\n        Date to search. By default will return the first day of the current month\n    debit_as_negative: bool\n        Pass in true if you'd like expenses to be returned as negative amounts and credits as\n        positive amounts.\n\n    Returns\n    -------\n    List[RecurringExpensesObject]\n    \"\"\"\n    if start_date is None:\n        start_date = datetime.datetime.now().date().replace(day=1)\n    params = RecurringExpenseParamsGet(\n        start_date=start_date, debit_as_negative=debit_as_negative\n    ).model_dump()\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=[APIConfig.LUNCH_MONEY_RECURRING_EXPENSES],\n        params=params,\n    )\n    recurring_expenses = response_data.get(APIConfig.LUNCH_MONEY_RECURRING_EXPENSES)\n    recurring_expenses_objects = [\n        RecurringExpensesObject.model_validate(item) for item in recurring_expenses\n    ]\n    logger.debug(\n        \"%s RecurringExpensesObjects retrieved\", len(recurring_expenses_objects)\n    )\n    return recurring_expenses_objects\n
"},{"location":"reference/models/recurring_expenses/#lunchable.models.recurring_expenses.RecurringExpensesObject","title":"RecurringExpensesObject","text":"

Bases: LunchableModel

Recurring Expenses Object

https://lunchmoney.dev/#recurring-expenses-object

Parameters:

Name Type Description Default id int

Unique identifier for recurring expense

required start_date date | None

Denotes when recurring expense starts occurring in ISO 8601 format. If null, then this recurring expense will show up for all time before end_date

None end_date date | None

Denotes when recurring expense stops occurring in ISO 8601 format. If null, then this recurring expense has no set end date and will show up for all months after start_date

None cadence str

One of: [monthly, twice a month, once a week, every 3 months, every 4 months, twice a year, yearly]

required payee str

Payee of the recurring expense

required amount float

Amount of the recurring expense in numeric format to 4 decimal places

required currency str

Three-letter lowercase currency code for the recurring expense in ISO 4217 format

required description str | None

If any, represents the user-entered description of the recurring expense

None billing_date date

Expected billing date for this recurring expense for this month in ISO 8601 format

required type str

\" This can be one of two values: cleared (The recurring expense has been reviewed by the user), suggested (The recurring expense is suggested by the system; the user has yet to review/clear it)

required original_name str | None

If any, represents the original name of the recurring expense as denoted by the transaction that triggered its creation

None source str

This can be one of three values: manual (User created this recurring expense manually from the Recurring Expenses page), transaction (User created this by converting a transaction from the Transactions page), system (Recurring expense was created by the system on transaction import). Some older recurring expenses may not have a source.

required plaid_account_id int | None

If any, denotes the plaid account associated with the creation of this \" recurring expense (see Plaid Accounts)\"

None asset_id int | None

If any, denotes the manually-managed account (i.e. asset) associated with the creation of this recurring expense (see Assets)

None transaction_id int | None

If any, denotes the unique identifier for the associated transaction matching this recurring expense for the current time period

None category_id int | None

If any, denotes the unique identifier for the associated category to this recurring expense

None Source code in lunchable/models/recurring_expenses.py
class RecurringExpensesObject(LunchableModel):\n    \"\"\"\n    Recurring Expenses Object\n\n    https://lunchmoney.dev/#recurring-expenses-object\n    \"\"\"\n\n    _id_description = \"Unique identifier for recurring expense\"\n    _start_date_description = \"\"\"\n    Denotes when recurring expense starts occurring in ISO 8601 format.\n    If null, then this recurring expense will show up for all time\n    before end_date\n    \"\"\"\n    _end_date_description = \"\"\"\n    Denotes when recurring expense stops occurring in ISO 8601 format.\n    If null, then this recurring expense has no set end date and will\n    show up for all months after start_date\n    \"\"\"\n    _cadence_description = \"\"\"\n    One of: [monthly, twice a month, once a week, every 3 months, every 4 months,\n    twice a year, yearly]\n    \"\"\"\n    _amount_description = (\n        \"Amount of the recurring expense in numeric format to 4 decimal places\"\n    )\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code for the recurring expense in ISO 4217 format\n    \"\"\"\n    _description_description = \"\"\"\n    If any, represents the user-entered description of the recurring expense\n    \"\"\"\n    _billing_date_description = \"\"\"\n    Expected billing date for this recurring expense for this month in ISO 8601 format\n    \"\"\"\n    _type_description = \"\"\"\"\n    This can be one of two values: cleared (The recurring expense has been reviewed\n    by the user), suggested (The recurring expense is suggested by the system;\n    the user has yet to review/clear it)\n    \"\"\"\n    _original_name_description = \"\"\"\n    If any, represents the original name of the recurring expense as\n    denoted by the transaction that triggered its creation\n    \"\"\"\n    _source_description = \"\"\"\n    This can be one of three values: manual (User created this recurring expense\n    manually from the Recurring Expenses page), transaction (User created this by\n    converting a transaction from the Transactions page), system (Recurring expense\n    was created by the system on transaction import). Some older recurring expenses\n    may not have a source.\n    \"\"\"\n    _plaid_account_id_description = \"\"\"\n    If any, denotes the plaid account associated with the creation of this \"\n    recurring expense (see Plaid Accounts)\"\n    \"\"\"\n    _asset_id_description = \"\"\"\n    If any, denotes the manually-managed account (i.e. asset) associated with the\n    creation of this recurring expense (see Assets)\n    \"\"\"\n    _transaction_id_description = \"\"\"\n    If any, denotes the unique identifier for the associated transaction matching\n    this recurring expense for the current time period\n    \"\"\"\n    _category_id_description = \"\"\"\n    If any, denotes the unique identifier for the associated category to this recurring expense\n    \"\"\"\n\n    id: int = Field(description=_id_description)\n    start_date: Optional[datetime.date] = Field(\n        None, description=_start_date_description\n    )\n    end_date: Optional[datetime.date] = Field(None, description=_end_date_description)\n    cadence: str = Field(description=_cadence_description)\n    payee: str = Field(description=\"Payee of the recurring expense\")\n    amount: float = Field(description=_amount_description)\n    currency: str = Field(max_length=3, description=_currency_description)\n    description: Optional[str] = Field(None, description=_description_description)\n    billing_date: datetime.date = Field(description=_billing_date_description)\n    type: str = Field(description=_type_description)\n    original_name: Optional[str] = Field(None, description=_original_name_description)\n    source: str = Field(description=_source_description)\n    plaid_account_id: Optional[int] = Field(\n        None, description=_plaid_account_id_description\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    transaction_id: Optional[int] = Field(None, description=_transaction_id_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n
"},{"location":"reference/models/tags/","title":"tags","text":"

Lunch Money - Tags

https://lunchmoney.dev/#tags

"},{"location":"reference/models/tags/#lunchable.models.tags.TagsClient","title":"TagsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Tag Interactions

Source code in lunchable/models/tags.py
class TagsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Tag Interactions\n    \"\"\"\n\n    def get_tags(self) -> List[TagsObject]:\n        \"\"\"\n        Get Spending Tags\n\n        Use this endpoint to get a list of all tags associated with the\n        user's account.\n\n        https://lunchmoney.dev/#get-all-tags\n\n        Returns\n        -------\n        List[TagsObject]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_TAGS\n        )\n        tag_objects = [TagsObject.model_validate(item) for item in response_data]\n        return tag_objects\n
"},{"location":"reference/models/tags/#lunchable.models.tags.TagsClient.get_tags","title":"get_tags()","text":"

Get Spending Tags

Use this endpoint to get a list of all tags associated with the user's account.

https://lunchmoney.dev/#get-all-tags

Returns:

Type Description List[TagsObject] Source code in lunchable/models/tags.py
def get_tags(self) -> List[TagsObject]:\n    \"\"\"\n    Get Spending Tags\n\n    Use this endpoint to get a list of all tags associated with the\n    user's account.\n\n    https://lunchmoney.dev/#get-all-tags\n\n    Returns\n    -------\n    List[TagsObject]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_TAGS\n    )\n    tag_objects = [TagsObject.model_validate(item) for item in response_data]\n    return tag_objects\n
"},{"location":"reference/models/tags/#lunchable.models.tags.TagsObject","title":"TagsObject","text":"

Bases: LunchableModel

Lunchmoney Tags object

https://lunchmoney.dev/#tags-object

Parameters:

Name Type Description Default id int

Unique identifier for tag

required name str

User-defined name of tag

required description str | None

User-defined description of tag

None Source code in lunchable/models/tags.py
class TagsObject(LunchableModel):\n    \"\"\"\n    Lunchmoney Tags object\n\n    https://lunchmoney.dev/#tags-object\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier for tag\")\n    name: str = Field(description=\"User-defined name of tag\", min_length=1)\n    description: Optional[str] = Field(\n        None, description=\"User-defined description of tag\"\n    )\n
"},{"location":"reference/models/transactions/","title":"transactions","text":"

Lunch Money - Transactions

https://lunchmoney.dev/#transactions

"},{"location":"reference/models/transactions/#lunchable.models.transactions.FullStatusEnum","title":"FullStatusEnum","text":"

Bases: str, Enum

Status Options

Source code in lunchable/models/transactions.py
class FullStatusEnum(str, Enum):\n    \"\"\"\n    Status Options\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n    recurring = \"recurring\"\n    recurring_suggested = \"recurring_suggested\"\n    pending = \"pending\"\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionBaseObject","title":"TransactionBaseObject","text":"

Bases: LunchableModel

Base Model For All Transactions to Inherit From

Source code in lunchable/models/transactions.py
class TransactionBaseObject(LunchableModel):\n    \"\"\"\n    Base Model For All Transactions to Inherit From\n    \"\"\"\n\n    pass\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionInsertObject","title":"TransactionInsertObject","text":"

Bases: TransactionBaseObject

Object For Creating New Transactions

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required amount float

Numeric value of amount. i.e. $4.25 should be denoted as 4.25.

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None currency str | None

Three-letter lowercase currency code in ISO 4217 format. The code sent must exist in our database. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account.

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id.

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionInsertObject(TransactionBaseObject):\n    \"\"\"\n    Object For Creating New Transactions\n\n    https://lunchmoney.dev/#insert-transactions\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _amount_description = \"\"\"\n    Numeric value of amount. i.e. $4.25 should be denoted as 4.25.\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated with\n    the same account and must not be a category group.\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code in ISO 4217 format. The code sent must exist\n    in our database. Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account.\n    \"\"\"\n    _recurring_id = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must be associated\n    with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. If recurring_id is provided, the status will\n    automatically be set to recurring or recurring_suggested depending on the type of\n    recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id.\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found, an error\n    will be thrown. Passing in a string will attempt to match by string. If no matching tag\n    name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: datetime.date = Field(description=_date_description)\n    amount: float = Field(description=_amount_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    currency: Optional[str] = Field(\n        None, description=_currency_description, max_length=3\n    )\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(\n        None, description=_external_id_description, max_length=75\n    )\n    tags: Optional[List[Union[str, int]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionInsertObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionObject","title":"TransactionObject","text":"

Bases: TransactionBaseObject

Universal Lunch Money Transaction Object

https://lunchmoney.dev/#transaction-object

Parameters:

Name Type Description Default id int

Unique identifier for transaction

required date date

Date of transaction in ISO 8601 format

required payee str | None

Name of payee If recurring_id is not null, this field will show the payee of associated recurring expense instead of the original transaction payee

None amount float

Amount of the transaction in numeric format to 4 decimal places

required currency str | None

Three-letter lowercase currency code of the transaction in ISO 4217 format

None notes str | None

User-entered transaction notes If recurring_id is not null, this field will be description of associated recurring expense

None category_id int | None

Unique identifier of associated category (see Categories)

None asset_id int | None

Unique identifier of associated manually-managed account (see Assets) Note: plaid_account_id and asset_id cannot both exist for a transaction

None plaid_account_id int | None

Unique identifier of associated Plaid account (see Plaid Accounts) Note: plaid_account_id and asset_id cannot both exist for a transaction

None status str | None

One of the following: cleared: User has reviewed the transaction | uncleared: User has not yet reviewed the transaction | recurring: Transaction is linked to a recurring expense | recurring_suggested: Transaction is listed as a suggested transaction for an existing recurring expense | pending: Imported transaction is marked as pending. This should be a temporary state. User intervention is required to change this to recurring.

None parent_id int | None

Exists if this is a split transaction. Denotes the transaction ID of the original transaction. Note that the parent transaction is not returned in this call.

None is_group bool | None

True if this transaction represents a group of transactions. If so, amount and currency represent the totalled amount of transactions bearing this transaction's id as their group_id. Amount is calculated based on the user's primary currency.

None group_id int | None

Exists if this transaction is part of a group. Denotes the parent's transaction ID

None tags List[TagsObject] | None

Array of Tag objects

None external_id str | None

User-defined external ID for any manually-entered or imported transaction. External ID cannot be accessed or changed for Plaid-imported transactions. External ID must be unique by asset_id. Max 75 characters.

None original_name str | None

The transactions original name before any payee name updates. For synced transactions, this is the raw original payee name from your bank.

None type str | None

(for synced investment transactions only) The transaction type as set by Plaid for investment transactions. Possible values include: buy, sell, cash, transfer and more

None subtype str | None

(for synced investment transactions only) The transaction type as set by Plaid for investment transactions. Possible values include: management fee, withdrawal, dividend, deposit and more

None fees str | None

(for synced investment transactions only) The fees as set by Plaid for investment transactions.

None price str | None

(for synced investment transactions only) The price as set by Plaid for investment transactions.

None quantity str | None

(for synced investment transactions only) The quantity as set by Plaid for investment transactions.

None Source code in lunchable/models/transactions.py
class TransactionObject(TransactionBaseObject):\n    \"\"\"\n    Universal Lunch Money Transaction Object\n\n    https://lunchmoney.dev/#transaction-object\n    \"\"\"\n\n    _amount_description = \"\"\"\n    Amount of the transaction in numeric format to 4 decimal places\n    \"\"\"\n    _payee_description = \"\"\"\n    Name of payee If recurring_id is not null, this field will show the payee\n    of associated recurring expense instead of the original transaction payee\n    \"\"\"\n    _currency_description = \"\"\"\n    Three-letter lowercase currency code of the transaction in ISO 4217 format\n    \"\"\"\n    _notes_description = \"\"\"\n    User-entered transaction notes If recurring_id is not null, this field will\n    be description of associated recurring expense\n    \"\"\"\n    _category_description = \"\"\"\n    Unique identifier of associated category (see Categories)\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier of associated manually-managed account (see Assets)\n    Note: plaid_account_id and asset_id cannot both exist for a transaction\n    \"\"\"\n    _plaid_account_id_description = \"\"\"\n    Unique identifier of associated Plaid account (see Plaid Accounts) Note:\n    plaid_account_id and asset_id cannot both exist for a transaction\n    \"\"\"\n    _status_description = \"\"\"\n    One of the following: cleared: User has reviewed the transaction | uncleared:\n    User has not yet reviewed the transaction | recurring: Transaction is linked\n    to a recurring expense | recurring_suggested: Transaction is listed as a\n    suggested transaction for an existing recurring expense | pending: Imported\n    transaction is marked as pending. This should be a temporary state. User intervention\n    is required to change this to recurring.\n    \"\"\"\n    _parent_id_description = \"\"\"\n    Exists if this is a split transaction. Denotes the transaction ID of the original\n    transaction. Note that the parent transaction is not returned in this call.\n    \"\"\"\n    _is_group_description = \"\"\"\n    True if this transaction represents a group of transactions. If so, amount\n    and currency represent the totalled amount of transactions bearing this\n    transaction's id as their group_id. Amount is calculated based on the\n    user's primary currency.\n    \"\"\"\n    _group_id_description = \"\"\"\n    Exists if this transaction is part of a group. Denotes the parent's transaction ID\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for any manually-entered or imported transaction.\n    External ID cannot be accessed or changed for Plaid-imported transactions.\n    External ID must be unique by asset_id. Max 75 characters.\n    \"\"\"\n    _original_name_description = \"\"\"\n    The transactions original name before any payee name updates. For synced transactions,\n    this is the raw original payee name from your bank.\n    \"\"\"\n    _type_description = \"\"\"\n    (for synced investment transactions only) The transaction type as set by\n    Plaid for investment transactions. Possible values include: buy, sell, cash,\n    transfer and more\n    \"\"\"\n    _subtype_description = \"\"\"\n    (for synced investment transactions only) The transaction type as set by Plaid\n    for investment transactions. Possible values include: management fee, withdrawal,\n    dividend, deposit and more\n    \"\"\"\n    _fees_description = \"\"\"\n    (for synced investment transactions only) The fees as set by Plaid for investment\n    transactions.\n    \"\"\"\n    _price_description = \"\"\"\n    (for synced investment transactions only) The price as set by Plaid for investment\n    transactions.\n    \"\"\"\n    _quantity_description = \"\"\"\n    (for synced investment transactions only) The quantity as set by Plaid for investment\n    transactions.\n    \"\"\"\n\n    id: int = Field(description=\"Unique identifier for transaction\")\n    date: datetime.date = Field(description=\"Date of transaction in ISO 8601 format\")\n    payee: Optional[str] = Field(None, description=_payee_description)\n    amount: float = Field(description=_amount_description)\n    currency: Optional[str] = Field(\n        None, max_length=3, description=_currency_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    category_id: Optional[int] = Field(None, description=_category_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    plaid_account_id: Optional[int] = Field(\n        None, description=_plaid_account_id_description\n    )\n    status: Optional[str] = Field(None, description=_status_description)\n    parent_id: Optional[int] = Field(None, description=_parent_id_description)\n    is_group: Optional[bool] = Field(None, description=_is_group_description)\n    group_id: Optional[int] = Field(None, description=_group_id_description)\n    tags: Optional[List[TagsObject]] = Field(None, description=\"Array of Tag objects\")\n    external_id: Optional[str] = Field(\n        None, max_length=75, description=_external_id_description\n    )\n    original_name: Optional[str] = Field(None, description=_original_name_description)\n    type: Optional[str] = Field(None, description=_type_description)\n    subtype: Optional[str] = Field(None, description=_subtype_description)\n    fees: Optional[str] = Field(None, description=_fees_description)\n    price: Optional[str] = Field(None, description=_price_description)\n    quantity: Optional[str] = Field(None, description=_quantity_description)\n\n    def get_update_object(self) -> TransactionUpdateObject:\n        \"\"\"\n        Return a TransactionUpdateObject\n\n        Return a TransactionUpdateObject to update an expense. Simply\n        change one of the properties and perform an `update_transaction` with\n        your Lunchable object.\n\n        Returns\n        -------\n        TransactionUpdateObject\n        \"\"\"\n        update_dict = self.model_dump()\n        try:\n            TransactionUpdateObject.StatusEnum(self.status)\n        except ValueError:\n            update_dict[\"status\"] = None\n        update_object = TransactionUpdateObject.model_validate(update_dict)\n        if update_object.tags is not None:\n            tags = [] if self.tags is None else self.tags\n            update_object.tags = [tag.name for tag in tags]\n        return update_object\n\n    def get_insert_object(self) -> TransactionInsertObject:\n        \"\"\"\n        Return a TransactionInsertObject\n\n        Return a TransactionInsertObject to update an expense. Simply\n        change some of the properties and perform an `insert_transactions` with\n        your Lunchable object.\n\n        Returns\n        -------\n        TransactionInsertObject\n        \"\"\"\n        insert_dict = self.model_dump()\n        try:\n            TransactionInsertObject.StatusEnum(self.status)\n        except ValueError:\n            insert_dict[\"status\"] = None\n        insert_object = TransactionInsertObject.model_validate(insert_dict)\n        if insert_object.tags is not None:\n            tags = [] if self.tags is None else self.tags\n            insert_object.tags = [tag.name for tag in tags]\n        return insert_object\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionObject.get_insert_object","title":"get_insert_object()","text":"

Return a TransactionInsertObject

Return a TransactionInsertObject to update an expense. Simply change some of the properties and perform an insert_transactions with your Lunchable object.

Returns:

Type Description TransactionInsertObject Source code in lunchable/models/transactions.py
def get_insert_object(self) -> TransactionInsertObject:\n    \"\"\"\n    Return a TransactionInsertObject\n\n    Return a TransactionInsertObject to update an expense. Simply\n    change some of the properties and perform an `insert_transactions` with\n    your Lunchable object.\n\n    Returns\n    -------\n    TransactionInsertObject\n    \"\"\"\n    insert_dict = self.model_dump()\n    try:\n        TransactionInsertObject.StatusEnum(self.status)\n    except ValueError:\n        insert_dict[\"status\"] = None\n    insert_object = TransactionInsertObject.model_validate(insert_dict)\n    if insert_object.tags is not None:\n        tags = [] if self.tags is None else self.tags\n        insert_object.tags = [tag.name for tag in tags]\n    return insert_object\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionObject.get_update_object","title":"get_update_object()","text":"

Return a TransactionUpdateObject

Return a TransactionUpdateObject to update an expense. Simply change one of the properties and perform an update_transaction with your Lunchable object.

Returns:

Type Description TransactionUpdateObject Source code in lunchable/models/transactions.py
def get_update_object(self) -> TransactionUpdateObject:\n    \"\"\"\n    Return a TransactionUpdateObject\n\n    Return a TransactionUpdateObject to update an expense. Simply\n    change one of the properties and perform an `update_transaction` with\n    your Lunchable object.\n\n    Returns\n    -------\n    TransactionUpdateObject\n    \"\"\"\n    update_dict = self.model_dump()\n    try:\n        TransactionUpdateObject.StatusEnum(self.status)\n    except ValueError:\n        update_dict[\"status\"] = None\n    update_object = TransactionUpdateObject.model_validate(update_dict)\n    if update_object.tags is not None:\n        tags = [] if self.tags is None else self.tags\n        update_object.tags = [tag.name for tag in tags]\n    return update_object\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionSplitObject","title":"TransactionSplitObject","text":"

Bases: TransactionBaseObject

Object for Splitting Transactions

https://lunchmoney.dev/#split-object

Parameters:

Name Type Description Default date date

Must be in ISO 8601 format (YYYY-MM-DD).

required category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account.

None notes str | None

Transaction Split Notes.

None amount float

Individual amount of split. Currency will inherit from parent transaction. All amounts must sum up to parent transaction amount.

required Source code in lunchable/models/transactions.py
class TransactionSplitObject(TransactionBaseObject):\n    \"\"\"\n    Object for Splitting Transactions\n\n    https://lunchmoney.dev/#split-object\n    \"\"\"\n\n    _date_description = \"Must be in ISO 8601 format (YYYY-MM-DD).\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account.\n    \"\"\"\n    _notes_description = \"Transaction Split Notes.\"\n    _amount_description = \"\"\"\n    Individual amount of split. Currency will inherit from parent transaction. All\n    amounts must sum up to parent transaction amount.\n    \"\"\"\n\n    date: datetime.date = Field(description=_date_description)\n    category_id: Optional[int] = Field(\n        default=None, description=_category_id_description\n    )\n    notes: Optional[str] = Field(None, description=_notes_description)\n    amount: float = Field(description=_amount_description)\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionUpdateObject","title":"TransactionUpdateObject","text":"

Bases: TransactionBaseObject

Object For Updating Existing Transactions

https://lunchmoney.dev/#update-transaction

Parameters:

Name Type Description Default date date | None

Must be in ISO 8601 format (YYYY-MM-DD).

None category_id int | None

Unique identifier for associated category_id. Category must be associated with the same account and must not be a category group.

None payee str | None

Max 140 characters

None amount float | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None currency str | None

You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id. Defaults to user account's primary currency.

None asset_id int | None

Unique identifier for associated asset (manually-managed account). Asset must be associated with the same account. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None recurring_id int | None

Unique identifier for associated recurring expense. Recurring expense must be associated with the same account.

None notes str | None

Max 350 characters

None status StatusEnum | None

Must be either cleared or uncleared. Defaults to uncleared If recurring_id is provided, the status will automatically be set to recurring or recurring_suggested depending on the type of recurring_id. Defaults to uncleared.

None external_id str | None

User-defined external ID for transaction. Max 75 characters. External IDs must be unique within the same asset_id. You may only update this if this transaction was not created from an automatic import, i.e. if this transaction is not associated with a plaid_account_id

None tags List[Union[str, int]] | None

Passing in a number will attempt to match by ID. If no matching tag ID is found, an error will be thrown. Passing in a string will attempt to match by string. If no matching tag name is found, a new tag will be created.

None Source code in lunchable/models/transactions.py
class TransactionUpdateObject(TransactionBaseObject):\n    \"\"\"\n    Object For Updating Existing Transactions\n\n    https://lunchmoney.dev/#update-transaction\n    \"\"\"\n\n    _date_description = \"\"\"\n    Must be in ISO 8601 format (YYYY-MM-DD).\n    \"\"\"\n    _category_id_description = \"\"\"\n    Unique identifier for associated category_id. Category must be associated\n    with the same account and must not be a category group.\n    \"\"\"\n    _amount_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id\n    \"\"\"\n    _currency_description = \"\"\"\n    You may only update this if this transaction was not created from an automatic\n    import, i.e. if this transaction is not associated with a plaid_account_id.\n    Defaults to user account's primary currency.\n    \"\"\"\n    _asset_id_description = \"\"\"\n    Unique identifier for associated asset (manually-managed account). Asset must be\n    associated with the same account. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _recurring_id_description = \"\"\"\n    Unique identifier for associated recurring expense. Recurring expense must\n    be associated with the same account.\n    \"\"\"\n    _status_description = \"\"\"\n    Must be either cleared or uncleared. Defaults to uncleared If recurring_id is\n    provided, the status will automatically be set to recurring or recurring_suggested\n    depending on the type of recurring_id. Defaults to uncleared.\n    \"\"\"\n    _external_id_description = \"\"\"\n    User-defined external ID for transaction. Max 75 characters. External IDs must be\n    unique within the same asset_id. You may only update this if this transaction was\n    not created from an automatic import, i.e. if this transaction is not associated\n    with a plaid_account_id\n    \"\"\"\n    _tags_description = \"\"\"\n    Passing in a number will attempt to match by ID. If no matching tag ID is found,\n    an error will be thrown. Passing in a string will attempt to match by string.\n    If no matching tag name is found, a new tag will be created.\n    \"\"\"\n\n    class StatusEnum(str, Enum):\n        \"\"\"\n        Status Options, must be \"cleared\" or \"uncleared\"\n        \"\"\"\n\n        cleared = \"cleared\"\n        uncleared = \"uncleared\"\n\n    date: Optional[datetime.date] = Field(None, description=_date_description)\n    category_id: Optional[int] = Field(None, description=_category_id_description)\n    payee: Optional[str] = Field(None, description=\"Max 140 characters\", max_length=140)\n    amount: Optional[float] = Field(None, description=_amount_description)\n    currency: Optional[str] = Field(None, description=_currency_description)\n    asset_id: Optional[int] = Field(None, description=_asset_id_description)\n    recurring_id: Optional[int] = Field(None, description=_recurring_id_description)\n    notes: Optional[str] = Field(None, description=\"Max 350 characters\", max_length=350)\n    status: Optional[StatusEnum] = Field(None, description=_status_description)\n    external_id: Optional[str] = Field(None, description=_external_id_description)\n    tags: Optional[List[Union[int, str]]] = Field(None, description=_tags_description)\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionUpdateObject.StatusEnum","title":"StatusEnum","text":"

Bases: str, Enum

Status Options, must be \"cleared\" or \"uncleared\"

Source code in lunchable/models/transactions.py
class StatusEnum(str, Enum):\n    \"\"\"\n    Status Options, must be \"cleared\" or \"uncleared\"\n    \"\"\"\n\n    cleared = \"cleared\"\n    uncleared = \"uncleared\"\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient","title":"TransactionsClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Transactions Interactions

Source code in lunchable/models/transactions.py
class TransactionsClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Transactions Interactions\n    \"\"\"\n\n    def get_transactions(\n        self,\n        start_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n        end_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n        tag_id: Optional[int] = None,\n        recurring_id: Optional[int] = None,\n        plaid_account_id: Optional[int] = None,\n        category_id: Optional[int] = None,\n        asset_id: Optional[int] = None,\n        group_id: Optional[int] = None,\n        is_group: Optional[bool] = None,\n        status: Optional[str] = None,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        debit_as_negative: Optional[bool] = None,\n        pending: Optional[bool] = None,\n        params: Optional[Dict[str, Any]] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get Transactions Using Criteria\n\n        Use this to retrieve all transactions between a date range. Returns list of Transaction\n        objects. If no query parameters are set, this will return transactions for the\n        current calendar month. If either start_date or end_date are datetime.datetime objects,\n        they will be reduced to dates. If a string is provided, it will be attempted to be parsed\n        as YYYY-MM-DD format.\n\n        Parameters\n        ----------\n        start_date: Optional[Union[datetime.date, datetime.datetime, str]]\n            Denotes the beginning of the time period to fetch transactions for. Defaults\n            to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.\n        end_date: Optional[Union[datetime.date, datetime.datetime, str]]\n            Denotes the end of the time period you'd like to get transactions for.\n            Defaults to end of current month. Required if start_date exists.\n        tag_id: Optional[int]\n            Filter by tag. Only accepts IDs, not names.\n        recurring_id: Optional[int]\n            Filter by recurring expense\n        plaid_account_id: Optional[int]\n            Filter by Plaid account\n        category_id: Optional[int]\n            Filter by category. Will also match category groups.\n        asset_id: Optional[int]\n            Filter by asset\n        group_id: Optional[int]\n            Filter by group_id (if the transaction is part of a specific group)\n        is_group: Optional[bool]\n            Filter by group (returns transaction groups)\n        status: Optional[str]\n            Filter by status (Can be cleared or uncleared. For recurring\n            transactions, use recurring)\n        offset: Optional[int]\n            Sets the offset for the records returned\n        limit: Optional[int]\n            Sets the maximum number of records to return. Note: The server will not\n            respond with any indication that there are more records to be returned.\n            Please check the response length to determine if you should make another\n            call with an offset to fetch more transactions.\n        debit_as_negative: Optional[bool]\n            Pass in true if you'd like expenses to be returned as negative amounts and\n            credits as positive amounts. Defaults to false.\n        pending: Optional[bool]\n            Pass in true if you'd like to include imported transactions with a pending status.\n        params: Optional[dict]\n            Additional Query String Params\n\n        Returns\n        -------\n        List[TransactionObject]\n            A list of transactions\n\n        Examples\n        --------\n        Retrieve a list of\n        [TransactionObject][lunchable.models.transactions.TransactionObject]\n\n        ```python\n        from lunchable import LunchMoney\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n        transactions = lunch.get_transactions(start_date=\"2020-01-01\",\n                                              end_date=\"2020-01-31\")\n        ```\n        \"\"\"\n        search_params = _TransactionParamsGet(\n            tag_id=tag_id,\n            recurring_id=recurring_id,\n            plaid_account_id=plaid_account_id,\n            category_id=category_id,\n            asset_id=asset_id,\n            group_id=group_id,\n            is_group=is_group,\n            status=status,\n            offset=offset,\n            limit=limit,\n            start_date=start_date,\n            end_date=end_date,\n            debit_as_negative=debit_as_negative,\n            pending=pending,\n        ).model_dump(exclude_none=True)\n        search_params.update(params if params is not None else {})\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=APIConfig.LUNCHMONEY_TRANSACTIONS,\n            params=search_params,\n        )\n        transactions = response_data[APIConfig.LUNCHMONEY_TRANSACTIONS]\n        transaction_objects = [\n            TransactionObject.model_validate(item) for item in transactions\n        ]\n        return transaction_objects\n\n    def get_transaction(self, transaction_id: int) -> TransactionObject:\n        \"\"\"\n        Get a Transaction by ID\n\n        Parameters\n        ----------\n        transaction_id: int\n            Lunch Money Transaction ID\n\n        Returns\n        -------\n        TransactionObject\n\n        Examples\n        --------\n        Retrieve a single transaction by its ID\n\n        ```python\n        from lunchable import LunchMoney\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n        transaction = lunch.get_transaction(transaction_id=1234)\n        ```\n\n        The above code returns a\n        [TransactionObject][lunchable.models.transactions.TransactionObject]\n        with ID # 1234 (assuming it exists)\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET,\n            url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, transaction_id],\n        )\n        return TransactionObject.model_validate(response_data)\n\n    ListOrSingleTransactionUpdateObject = Optional[\n        Union[TransactionUpdateObject, TransactionObject]\n    ]\n\n    ListOrSingleTransactionInsertObject = Union[\n        TransactionObject,\n        TransactionInsertObject,\n        List[TransactionObject],\n        List[TransactionInsertObject],\n        List[Union[TransactionObject, TransactionInsertObject]],\n    ]\n\n    def update_transaction(\n        self,\n        transaction_id: int,\n        transaction: ListOrSingleTransactionUpdateObject = None,\n        split: Optional[List[TransactionSplitObject]] = None,\n        debit_as_negative: bool = False,\n        skip_balance_update: bool = True,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Update a Transaction\n\n        Use this endpoint to update a single transaction. You may also use this\n        to split an existing transaction. If a TransactionObject is provided it will be\n        converted into a TransactionUpdateObject.\n\n        PUT https://dev.lunchmoney.app/v1/transactions/:transaction_id\n\n        Parameters\n        ----------\n        transaction_id: int\n            Lunch Money Transaction ID\n        transaction: ListOrSingleTransactionUpdateObject\n            Object to update with\n        split: Optional[List[TransactionSplitObject]]\n            Defines the split of a transaction. You may not split an already-split\n            transaction, recurring transaction, or group transaction.\n        debit_as_negative: bool\n            If true, will assume negative amount values denote expenses and\n            positive amount values denote credits. Defaults to false.\n        skip_balance_update: bool\n            If false, will skip updating balance if an asset_id\n            is present for any of the transactions.\n\n        Returns\n        -------\n        Dict[str, Any]\n\n        Examples\n        --------\n        Update a transaction with a\n        [TransactionUpdateObject][lunchable.models.transactions.TransactionUpdateObject]\n\n        ```python\n        from datetime import datetime\n\n        from lunchable import LunchMoney, TransactionUpdateObject\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n        transaction_note = f\"Updated on {datetime.now()}\"\n        notes_update = TransactionUpdateObject(notes=transaction_note)\n        response = lunch.update_transaction(transaction_id=1234,\n                                            transaction=notes_update)\n        ```\n\n        Update a\n        [TransactionObject][lunchable.models.transactions.TransactionObject]\n        with itself\n\n        ```python\n        from datetime import datetime, timedelta\n\n        from lunchable import LunchMoney\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n        transaction = lunch.get_transaction(transaction_id=1234)\n\n        transaction.notes = f\"Updated on {datetime.now()}\"\n        transaction.date = transaction.date + timedelta(days=1)\n        response = lunch.update_transaction(transaction_id=transaction.id,\n                                            transaction=transaction)\n        ```\n        \"\"\"\n        payload = _TransactionUpdateParamsPut(\n            split=split,\n            debit_as_negative=debit_as_negative,\n            skip_balance_update=skip_balance_update,\n        ).model_dump(exclude_none=True)\n        if transaction is None and split is None:\n            raise LunchMoneyError(\"You must update the transaction or provide a split\")\n        elif transaction is not None:\n            if isinstance(transaction, TransactionObject):\n                transaction = transaction.get_update_object()\n            payload[\"transaction\"] = transaction.model_dump(exclude_unset=True)\n        response_data = self.make_request(\n            method=self.Methods.PUT,\n            url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, transaction_id],\n            payload=payload,\n        )\n        return response_data\n\n    def insert_transactions(\n        self,\n        transactions: ListOrSingleTransactionInsertObject,\n        apply_rules: bool = False,\n        skip_duplicates: bool = True,\n        debit_as_negative: bool = False,\n        check_for_recurring: bool = False,\n        skip_balance_update: bool = True,\n    ) -> List[int]:\n        \"\"\"\n        Create One or Many Lunch Money Transactions\n\n        Use this endpoint to insert many transactions at once. Also accepts\n        a single transaction as well. If a TransactionObject is provided it will be\n        converted into a TransactionInsertObject.\n\n        https://lunchmoney.dev/#insert-transactions\n\n        Parameters\n        ----------\n        transactions: ListOrSingleTransactionTypeObject\n            Transactions to insert. Either a single TransactionInsertObject object or\n            a list of them\n        apply_rules: bool\n            If true, will apply account's existing rules to the inserted transactions.\n            Defaults to false.\n        skip_duplicates: bool\n            If true, the system will automatically dedupe based on transaction date,\n            payee and amount. Note that deduping by external_id will occur regardless\n            of this flag.\n        check_for_recurring: bool\n            if true, will check new transactions for occurrences of new monthly expenses.\n            Defaults to false.\n        debit_as_negative: bool\n            If true, will assume negative amount values denote expenses and\n            positive amount values denote credits. Defaults to false.\n        skip_balance_update: bool\n            If false, will skip updating balance if an asset_id\n            is present for any of the transactions.\n\n        Returns\n        -------\n        List[int]\n\n        Examples\n        --------\n        Create a new transaction with a\n        [TransactionInsertObject][lunchable.models.transactions.TransactionInsertObject]\n\n        ```python\n        from lunchable import LunchMoney, TransactionInsertObject\n\n        lunch = LunchMoney(access_token=\"xxxxxxx\")\n\n        new_transaction = TransactionInsertObject(payee=\"Example Restaurant\",\n                                                  amount=120.00,\n                                                  notes=\"Saturday Dinner\")\n        new_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n        ```\n        \"\"\"\n        insert_objects = []\n        if not isinstance(transactions, list):\n            transactions = [transactions]\n        for item in transactions:\n            if isinstance(item, TransactionObject):\n                insert_objects.append(item.get_insert_object())\n            elif isinstance(item, TransactionInsertObject):\n                insert_objects.append(item)\n            else:\n                raise LunchMoneyError(\n                    \"Only TransactionObjects or TransactionInsertObjects are \"\n                    \"supported by this function.\"\n                )\n        payload = _TransactionInsertParamsPost(\n            transactions=insert_objects,\n            apply_rules=apply_rules,\n            skip_duplicates=skip_duplicates,\n            check_for_recurring=check_for_recurring,\n            debit_as_negative=debit_as_negative,\n            skip_balance_update=skip_balance_update,\n        ).model_dump(exclude_unset=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=APIConfig.LUNCHMONEY_TRANSACTIONS,\n            payload=payload,\n        )\n        ids: List[int] = response_data[\"ids\"] if response_data else []\n        return ids\n\n    def insert_transaction_group(\n        self,\n        date: datetime.date,\n        payee: str,\n        transactions: List[int],\n        category_id: Optional[int] = None,\n        notes: Optional[str] = None,\n        tags: Optional[List[int]] = None,\n    ) -> int:\n        \"\"\"\n        Create a Transaction Group of Two or More Transactions\n\n        Returns the ID of the newly created transaction group\n\n        Parameters\n        ----------\n        date: datetime.date\n            Date for the grouped transaction\n        payee: str\n            Payee name for the grouped transaction\n        category_id: Optional[int]\n            Category for the grouped transaction\n        notes: Optional[str]\n            Notes for the grouped transaction\n        tags: Optional[List[int]]\n            Array of tag IDs for the grouped transaction\n        transactions: Optional[List[int]]\n            Array of transaction IDs to be part of the transaction group\n\n        Returns\n        -------\n        int\n        \"\"\"\n        if len(transactions) < 2:\n            raise LunchMoneyError(\n                \"You must include 2 or more transactions \" \"in the Transaction Group\"\n            )\n        transaction_params = _TransactionGroupParamsPost(\n            date=date,\n            payee=payee,\n            category_id=category_id,\n            notes=notes,\n            tags=tags,\n            transactions=transactions,\n        ).model_dump(exclude_none=True)\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[\n                APIConfig.LUNCHMONEY_TRANSACTIONS,\n                APIConfig.LUNCHMONEY_TRANSACTION_GROUPS,\n            ],\n            payload=transaction_params,\n        )\n        return response_data\n\n    def remove_transaction_group(self, transaction_group_id: int) -> List[int]:\n        \"\"\"\n        Delete a Transaction Group\n\n        Use this method to delete a transaction group. The transactions within the\n        group will not be removed.\n\n        Returns the IDs of the transactions that were part of the deleted group\n\n        https://lunchmoney.dev/#delete-transaction-group\n\n        Parameters\n        ----------\n        transaction_group_id: int\n            Transaction Group Identifier\n\n        Returns\n        -------\n        List[int]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.DELETE,\n            url_path=[\n                APIConfig.LUNCHMONEY_TRANSACTIONS,\n                APIConfig.LUNCHMONEY_TRANSACTION_GROUPS,\n                transaction_group_id,\n            ],\n        )\n        return response_data[\"transactions\"]\n\n    def unsplit_transactions(\n        self, parent_ids: List[int], remove_parents: bool = False\n    ) -> List[int]:\n        \"\"\"\n        Unsplit Transactions\n\n        Use this endpoint to unsplit one or more transactions.\n\n        Returns an array of IDs of deleted transactions\n\n        https://lunchmoney.dev/#unsplit-transactions\n\n        Parameters\n        ----------\n        parent_ids: List[int]\n            Array of transaction IDs to unsplit. If one transaction is unsplittable,\n            no transaction will be unsplit.\n        remove_parents: bool\n            If true, deletes the original parent transaction as well. Note,\n            this is unreversable!\n\n        Returns\n        -------\n        List[int]\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.POST,\n            url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, \"unsplit\"],\n            payload=_TransactionsUnsplitPost(\n                parent_ids=parent_ids, remove_parents=remove_parents\n            ).model_dump(exclude_none=True),\n        )\n        return response_data\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.get_transaction","title":"get_transaction(transaction_id)","text":"

Get a Transaction by ID

Parameters:

Name Type Description Default transaction_id int

Lunch Money Transaction ID

required

Returns:

Type Description TransactionObject

Examples:

Retrieve a single transaction by its ID

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction = lunch.get_transaction(transaction_id=1234)\n

The above code returns a TransactionObject with ID # 1234 (assuming it exists)

Source code in lunchable/models/transactions.py
def get_transaction(self, transaction_id: int) -> TransactionObject:\n    \"\"\"\n    Get a Transaction by ID\n\n    Parameters\n    ----------\n    transaction_id: int\n        Lunch Money Transaction ID\n\n    Returns\n    -------\n    TransactionObject\n\n    Examples\n    --------\n    Retrieve a single transaction by its ID\n\n    ```python\n    from lunchable import LunchMoney\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transaction = lunch.get_transaction(transaction_id=1234)\n    ```\n\n    The above code returns a\n    [TransactionObject][lunchable.models.transactions.TransactionObject]\n    with ID # 1234 (assuming it exists)\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, transaction_id],\n    )\n    return TransactionObject.model_validate(response_data)\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.get_transactions","title":"get_transactions(start_date=None, end_date=None, tag_id=None, recurring_id=None, plaid_account_id=None, category_id=None, asset_id=None, group_id=None, is_group=None, status=None, offset=None, limit=None, debit_as_negative=None, pending=None, params=None)","text":"

Get Transactions Using Criteria

Use this to retrieve all transactions between a date range. Returns list of Transaction objects. If no query parameters are set, this will return transactions for the current calendar month. If either start_date or end_date are datetime.datetime objects, they will be reduced to dates. If a string is provided, it will be attempted to be parsed as YYYY-MM-DD format.

Parameters:

Name Type Description Default start_date Optional[Union[date, datetime, str]]

Denotes the beginning of the time period to fetch transactions for. Defaults to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.

None end_date Optional[Union[date, datetime, str]]

Denotes the end of the time period you'd like to get transactions for. Defaults to end of current month. Required if start_date exists.

None tag_id Optional[int]

Filter by tag. Only accepts IDs, not names.

None recurring_id Optional[int]

Filter by recurring expense

None plaid_account_id Optional[int]

Filter by Plaid account

None category_id Optional[int]

Filter by category. Will also match category groups.

None asset_id Optional[int]

Filter by asset

None group_id Optional[int]

Filter by group_id (if the transaction is part of a specific group)

None is_group Optional[bool]

Filter by group (returns transaction groups)

None status Optional[str]

Filter by status (Can be cleared or uncleared. For recurring transactions, use recurring)

None offset Optional[int]

Sets the offset for the records returned

None limit Optional[int]

Sets the maximum number of records to return. Note: The server will not respond with any indication that there are more records to be returned. Please check the response length to determine if you should make another call with an offset to fetch more transactions.

None debit_as_negative Optional[bool]

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts. Defaults to false.

None pending Optional[bool]

Pass in true if you'd like to include imported transactions with a pending status.

None params Optional[Dict[str, Any]]

Additional Query String Params

None

Returns:

Type Description List[TransactionObject]

A list of transactions

Examples:

Retrieve a list of TransactionObject

from lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransactions = lunch.get_transactions(start_date=\"2020-01-01\",\n                                      end_date=\"2020-01-31\")\n
Source code in lunchable/models/transactions.py
def get_transactions(\n    self,\n    start_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n    end_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n    tag_id: Optional[int] = None,\n    recurring_id: Optional[int] = None,\n    plaid_account_id: Optional[int] = None,\n    category_id: Optional[int] = None,\n    asset_id: Optional[int] = None,\n    group_id: Optional[int] = None,\n    is_group: Optional[bool] = None,\n    status: Optional[str] = None,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    debit_as_negative: Optional[bool] = None,\n    pending: Optional[bool] = None,\n    params: Optional[Dict[str, Any]] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Get Transactions Using Criteria\n\n    Use this to retrieve all transactions between a date range. Returns list of Transaction\n    objects. If no query parameters are set, this will return transactions for the\n    current calendar month. If either start_date or end_date are datetime.datetime objects,\n    they will be reduced to dates. If a string is provided, it will be attempted to be parsed\n    as YYYY-MM-DD format.\n\n    Parameters\n    ----------\n    start_date: Optional[Union[datetime.date, datetime.datetime, str]]\n        Denotes the beginning of the time period to fetch transactions for. Defaults\n        to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.\n    end_date: Optional[Union[datetime.date, datetime.datetime, str]]\n        Denotes the end of the time period you'd like to get transactions for.\n        Defaults to end of current month. Required if start_date exists.\n    tag_id: Optional[int]\n        Filter by tag. Only accepts IDs, not names.\n    recurring_id: Optional[int]\n        Filter by recurring expense\n    plaid_account_id: Optional[int]\n        Filter by Plaid account\n    category_id: Optional[int]\n        Filter by category. Will also match category groups.\n    asset_id: Optional[int]\n        Filter by asset\n    group_id: Optional[int]\n        Filter by group_id (if the transaction is part of a specific group)\n    is_group: Optional[bool]\n        Filter by group (returns transaction groups)\n    status: Optional[str]\n        Filter by status (Can be cleared or uncleared. For recurring\n        transactions, use recurring)\n    offset: Optional[int]\n        Sets the offset for the records returned\n    limit: Optional[int]\n        Sets the maximum number of records to return. Note: The server will not\n        respond with any indication that there are more records to be returned.\n        Please check the response length to determine if you should make another\n        call with an offset to fetch more transactions.\n    debit_as_negative: Optional[bool]\n        Pass in true if you'd like expenses to be returned as negative amounts and\n        credits as positive amounts. Defaults to false.\n    pending: Optional[bool]\n        Pass in true if you'd like to include imported transactions with a pending status.\n    params: Optional[dict]\n        Additional Query String Params\n\n    Returns\n    -------\n    List[TransactionObject]\n        A list of transactions\n\n    Examples\n    --------\n    Retrieve a list of\n    [TransactionObject][lunchable.models.transactions.TransactionObject]\n\n    ```python\n    from lunchable import LunchMoney\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transactions = lunch.get_transactions(start_date=\"2020-01-01\",\n                                          end_date=\"2020-01-31\")\n    ```\n    \"\"\"\n    search_params = _TransactionParamsGet(\n        tag_id=tag_id,\n        recurring_id=recurring_id,\n        plaid_account_id=plaid_account_id,\n        category_id=category_id,\n        asset_id=asset_id,\n        group_id=group_id,\n        is_group=is_group,\n        status=status,\n        offset=offset,\n        limit=limit,\n        start_date=start_date,\n        end_date=end_date,\n        debit_as_negative=debit_as_negative,\n        pending=pending,\n    ).model_dump(exclude_none=True)\n    search_params.update(params if params is not None else {})\n    response_data = self.make_request(\n        method=self.Methods.GET,\n        url_path=APIConfig.LUNCHMONEY_TRANSACTIONS,\n        params=search_params,\n    )\n    transactions = response_data[APIConfig.LUNCHMONEY_TRANSACTIONS]\n    transaction_objects = [\n        TransactionObject.model_validate(item) for item in transactions\n    ]\n    return transaction_objects\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.insert_transaction_group","title":"insert_transaction_group(date, payee, transactions, category_id=None, notes=None, tags=None)","text":"

Create a Transaction Group of Two or More Transactions

Returns the ID of the newly created transaction group

Parameters:

Name Type Description Default date date

Date for the grouped transaction

required payee str

Payee name for the grouped transaction

required category_id Optional[int]

Category for the grouped transaction

None notes Optional[str]

Notes for the grouped transaction

None tags Optional[List[int]]

Array of tag IDs for the grouped transaction

None transactions List[int]

Array of transaction IDs to be part of the transaction group

required

Returns:

Type Description int Source code in lunchable/models/transactions.py
def insert_transaction_group(\n    self,\n    date: datetime.date,\n    payee: str,\n    transactions: List[int],\n    category_id: Optional[int] = None,\n    notes: Optional[str] = None,\n    tags: Optional[List[int]] = None,\n) -> int:\n    \"\"\"\n    Create a Transaction Group of Two or More Transactions\n\n    Returns the ID of the newly created transaction group\n\n    Parameters\n    ----------\n    date: datetime.date\n        Date for the grouped transaction\n    payee: str\n        Payee name for the grouped transaction\n    category_id: Optional[int]\n        Category for the grouped transaction\n    notes: Optional[str]\n        Notes for the grouped transaction\n    tags: Optional[List[int]]\n        Array of tag IDs for the grouped transaction\n    transactions: Optional[List[int]]\n        Array of transaction IDs to be part of the transaction group\n\n    Returns\n    -------\n    int\n    \"\"\"\n    if len(transactions) < 2:\n        raise LunchMoneyError(\n            \"You must include 2 or more transactions \" \"in the Transaction Group\"\n        )\n    transaction_params = _TransactionGroupParamsPost(\n        date=date,\n        payee=payee,\n        category_id=category_id,\n        notes=notes,\n        tags=tags,\n        transactions=transactions,\n    ).model_dump(exclude_none=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[\n            APIConfig.LUNCHMONEY_TRANSACTIONS,\n            APIConfig.LUNCHMONEY_TRANSACTION_GROUPS,\n        ],\n        payload=transaction_params,\n    )\n    return response_data\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.insert_transactions","title":"insert_transactions(transactions, apply_rules=False, skip_duplicates=True, debit_as_negative=False, check_for_recurring=False, skip_balance_update=True)","text":"

Create One or Many Lunch Money Transactions

Use this endpoint to insert many transactions at once. Also accepts a single transaction as well. If a TransactionObject is provided it will be converted into a TransactionInsertObject.

https://lunchmoney.dev/#insert-transactions

Parameters:

Name Type Description Default transactions ListOrSingleTransactionInsertObject

Transactions to insert. Either a single TransactionInsertObject object or a list of them

required apply_rules bool

If true, will apply account's existing rules to the inserted transactions. Defaults to false.

False skip_duplicates bool

If true, the system will automatically dedupe based on transaction date, payee and amount. Note that deduping by external_id will occur regardless of this flag.

True check_for_recurring bool

if true, will check new transactions for occurrences of new monthly expenses. Defaults to false.

False debit_as_negative bool

If true, will assume negative amount values denote expenses and positive amount values denote credits. Defaults to false.

False skip_balance_update bool

If false, will skip updating balance if an asset_id is present for any of the transactions.

True

Returns:

Type Description List[int]

Examples:

Create a new transaction with a TransactionInsertObject

from lunchable import LunchMoney, TransactionInsertObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\n\nnew_transaction = TransactionInsertObject(payee=\"Example Restaurant\",\n                                          amount=120.00,\n                                          notes=\"Saturday Dinner\")\nnew_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n
Source code in lunchable/models/transactions.py
def insert_transactions(\n    self,\n    transactions: ListOrSingleTransactionInsertObject,\n    apply_rules: bool = False,\n    skip_duplicates: bool = True,\n    debit_as_negative: bool = False,\n    check_for_recurring: bool = False,\n    skip_balance_update: bool = True,\n) -> List[int]:\n    \"\"\"\n    Create One or Many Lunch Money Transactions\n\n    Use this endpoint to insert many transactions at once. Also accepts\n    a single transaction as well. If a TransactionObject is provided it will be\n    converted into a TransactionInsertObject.\n\n    https://lunchmoney.dev/#insert-transactions\n\n    Parameters\n    ----------\n    transactions: ListOrSingleTransactionTypeObject\n        Transactions to insert. Either a single TransactionInsertObject object or\n        a list of them\n    apply_rules: bool\n        If true, will apply account's existing rules to the inserted transactions.\n        Defaults to false.\n    skip_duplicates: bool\n        If true, the system will automatically dedupe based on transaction date,\n        payee and amount. Note that deduping by external_id will occur regardless\n        of this flag.\n    check_for_recurring: bool\n        if true, will check new transactions for occurrences of new monthly expenses.\n        Defaults to false.\n    debit_as_negative: bool\n        If true, will assume negative amount values denote expenses and\n        positive amount values denote credits. Defaults to false.\n    skip_balance_update: bool\n        If false, will skip updating balance if an asset_id\n        is present for any of the transactions.\n\n    Returns\n    -------\n    List[int]\n\n    Examples\n    --------\n    Create a new transaction with a\n    [TransactionInsertObject][lunchable.models.transactions.TransactionInsertObject]\n\n    ```python\n    from lunchable import LunchMoney, TransactionInsertObject\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n\n    new_transaction = TransactionInsertObject(payee=\"Example Restaurant\",\n                                              amount=120.00,\n                                              notes=\"Saturday Dinner\")\n    new_transaction_ids = lunch.insert_transactions(transactions=new_transaction)\n    ```\n    \"\"\"\n    insert_objects = []\n    if not isinstance(transactions, list):\n        transactions = [transactions]\n    for item in transactions:\n        if isinstance(item, TransactionObject):\n            insert_objects.append(item.get_insert_object())\n        elif isinstance(item, TransactionInsertObject):\n            insert_objects.append(item)\n        else:\n            raise LunchMoneyError(\n                \"Only TransactionObjects or TransactionInsertObjects are \"\n                \"supported by this function.\"\n            )\n    payload = _TransactionInsertParamsPost(\n        transactions=insert_objects,\n        apply_rules=apply_rules,\n        skip_duplicates=skip_duplicates,\n        check_for_recurring=check_for_recurring,\n        debit_as_negative=debit_as_negative,\n        skip_balance_update=skip_balance_update,\n    ).model_dump(exclude_unset=True)\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=APIConfig.LUNCHMONEY_TRANSACTIONS,\n        payload=payload,\n    )\n    ids: List[int] = response_data[\"ids\"] if response_data else []\n    return ids\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.remove_transaction_group","title":"remove_transaction_group(transaction_group_id)","text":"

Delete a Transaction Group

Use this method to delete a transaction group. The transactions within the group will not be removed.

Returns the IDs of the transactions that were part of the deleted group

https://lunchmoney.dev/#delete-transaction-group

Parameters:

Name Type Description Default transaction_group_id int

Transaction Group Identifier

required

Returns:

Type Description List[int] Source code in lunchable/models/transactions.py
def remove_transaction_group(self, transaction_group_id: int) -> List[int]:\n    \"\"\"\n    Delete a Transaction Group\n\n    Use this method to delete a transaction group. The transactions within the\n    group will not be removed.\n\n    Returns the IDs of the transactions that were part of the deleted group\n\n    https://lunchmoney.dev/#delete-transaction-group\n\n    Parameters\n    ----------\n    transaction_group_id: int\n        Transaction Group Identifier\n\n    Returns\n    -------\n    List[int]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.DELETE,\n        url_path=[\n            APIConfig.LUNCHMONEY_TRANSACTIONS,\n            APIConfig.LUNCHMONEY_TRANSACTION_GROUPS,\n            transaction_group_id,\n        ],\n    )\n    return response_data[\"transactions\"]\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.unsplit_transactions","title":"unsplit_transactions(parent_ids, remove_parents=False)","text":"

Unsplit Transactions

Use this endpoint to unsplit one or more transactions.

Returns an array of IDs of deleted transactions

https://lunchmoney.dev/#unsplit-transactions

Parameters:

Name Type Description Default parent_ids List[int]

Array of transaction IDs to unsplit. If one transaction is unsplittable, no transaction will be unsplit.

required remove_parents bool

If true, deletes the original parent transaction as well. Note, this is unreversable!

False

Returns:

Type Description List[int] Source code in lunchable/models/transactions.py
def unsplit_transactions(\n    self, parent_ids: List[int], remove_parents: bool = False\n) -> List[int]:\n    \"\"\"\n    Unsplit Transactions\n\n    Use this endpoint to unsplit one or more transactions.\n\n    Returns an array of IDs of deleted transactions\n\n    https://lunchmoney.dev/#unsplit-transactions\n\n    Parameters\n    ----------\n    parent_ids: List[int]\n        Array of transaction IDs to unsplit. If one transaction is unsplittable,\n        no transaction will be unsplit.\n    remove_parents: bool\n        If true, deletes the original parent transaction as well. Note,\n        this is unreversable!\n\n    Returns\n    -------\n    List[int]\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.POST,\n        url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, \"unsplit\"],\n        payload=_TransactionsUnsplitPost(\n            parent_ids=parent_ids, remove_parents=remove_parents\n        ).model_dump(exclude_none=True),\n    )\n    return response_data\n
"},{"location":"reference/models/transactions/#lunchable.models.transactions.TransactionsClient.update_transaction","title":"update_transaction(transaction_id, transaction=None, split=None, debit_as_negative=False, skip_balance_update=True)","text":"

Update a Transaction

Use this endpoint to update a single transaction. You may also use this to split an existing transaction. If a TransactionObject is provided it will be converted into a TransactionUpdateObject.

PUT https://dev.lunchmoney.app/v1/transactions/:transaction_id

Parameters:

Name Type Description Default transaction_id int

Lunch Money Transaction ID

required transaction ListOrSingleTransactionUpdateObject

Object to update with

None split Optional[List[TransactionSplitObject]]

Defines the split of a transaction. You may not split an already-split transaction, recurring transaction, or group transaction.

None debit_as_negative bool

If true, will assume negative amount values denote expenses and positive amount values denote credits. Defaults to false.

False skip_balance_update bool

If false, will skip updating balance if an asset_id is present for any of the transactions.

True

Returns:

Type Description Dict[str, Any]

Examples:

Update a transaction with a TransactionUpdateObject

from datetime import datetime\n\nfrom lunchable import LunchMoney, TransactionUpdateObject\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction_note = f\"Updated on {datetime.now()}\"\nnotes_update = TransactionUpdateObject(notes=transaction_note)\nresponse = lunch.update_transaction(transaction_id=1234,\n                                    transaction=notes_update)\n

Update a TransactionObject with itself

from datetime import datetime, timedelta\n\nfrom lunchable import LunchMoney\n\nlunch = LunchMoney(access_token=\"xxxxxxx\")\ntransaction = lunch.get_transaction(transaction_id=1234)\n\ntransaction.notes = f\"Updated on {datetime.now()}\"\ntransaction.date = transaction.date + timedelta(days=1)\nresponse = lunch.update_transaction(transaction_id=transaction.id,\n                                    transaction=transaction)\n
Source code in lunchable/models/transactions.py
def update_transaction(\n    self,\n    transaction_id: int,\n    transaction: ListOrSingleTransactionUpdateObject = None,\n    split: Optional[List[TransactionSplitObject]] = None,\n    debit_as_negative: bool = False,\n    skip_balance_update: bool = True,\n) -> Dict[str, Any]:\n    \"\"\"\n    Update a Transaction\n\n    Use this endpoint to update a single transaction. You may also use this\n    to split an existing transaction. If a TransactionObject is provided it will be\n    converted into a TransactionUpdateObject.\n\n    PUT https://dev.lunchmoney.app/v1/transactions/:transaction_id\n\n    Parameters\n    ----------\n    transaction_id: int\n        Lunch Money Transaction ID\n    transaction: ListOrSingleTransactionUpdateObject\n        Object to update with\n    split: Optional[List[TransactionSplitObject]]\n        Defines the split of a transaction. You may not split an already-split\n        transaction, recurring transaction, or group transaction.\n    debit_as_negative: bool\n        If true, will assume negative amount values denote expenses and\n        positive amount values denote credits. Defaults to false.\n    skip_balance_update: bool\n        If false, will skip updating balance if an asset_id\n        is present for any of the transactions.\n\n    Returns\n    -------\n    Dict[str, Any]\n\n    Examples\n    --------\n    Update a transaction with a\n    [TransactionUpdateObject][lunchable.models.transactions.TransactionUpdateObject]\n\n    ```python\n    from datetime import datetime\n\n    from lunchable import LunchMoney, TransactionUpdateObject\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transaction_note = f\"Updated on {datetime.now()}\"\n    notes_update = TransactionUpdateObject(notes=transaction_note)\n    response = lunch.update_transaction(transaction_id=1234,\n                                        transaction=notes_update)\n    ```\n\n    Update a\n    [TransactionObject][lunchable.models.transactions.TransactionObject]\n    with itself\n\n    ```python\n    from datetime import datetime, timedelta\n\n    from lunchable import LunchMoney\n\n    lunch = LunchMoney(access_token=\"xxxxxxx\")\n    transaction = lunch.get_transaction(transaction_id=1234)\n\n    transaction.notes = f\"Updated on {datetime.now()}\"\n    transaction.date = transaction.date + timedelta(days=1)\n    response = lunch.update_transaction(transaction_id=transaction.id,\n                                        transaction=transaction)\n    ```\n    \"\"\"\n    payload = _TransactionUpdateParamsPut(\n        split=split,\n        debit_as_negative=debit_as_negative,\n        skip_balance_update=skip_balance_update,\n    ).model_dump(exclude_none=True)\n    if transaction is None and split is None:\n        raise LunchMoneyError(\"You must update the transaction or provide a split\")\n    elif transaction is not None:\n        if isinstance(transaction, TransactionObject):\n            transaction = transaction.get_update_object()\n        payload[\"transaction\"] = transaction.model_dump(exclude_unset=True)\n    response_data = self.make_request(\n        method=self.Methods.PUT,\n        url_path=[APIConfig.LUNCHMONEY_TRANSACTIONS, transaction_id],\n        payload=payload,\n    )\n    return response_data\n
"},{"location":"reference/models/user/","title":"user","text":"

Lunch Money - User

https://lunchmoney.dev/#user

"},{"location":"reference/models/user/#lunchable.models.user.UserClient","title":"UserClient","text":"

Bases: LunchMoneyAPIClient

Lunch Money Interactions for Non Finance Operations

Source code in lunchable/models/user.py
class UserClient(LunchMoneyAPIClient):\n    \"\"\"\n    Lunch Money Interactions for Non Finance Operations\n    \"\"\"\n\n    def get_user(self) -> UserObject:\n        \"\"\"\n        Get Personal User Details\n\n        Use this endpoint to get details on the current user.\n\n        https://lunchmoney.dev/#get-user\n\n        Returns\n        -------\n        UserObject\n        \"\"\"\n        response_data = self.make_request(\n            method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_ME\n        )\n        me = UserObject.model_validate(response_data)\n        return me\n
"},{"location":"reference/models/user/#lunchable.models.user.UserClient.get_user","title":"get_user()","text":"

Get Personal User Details

Use this endpoint to get details on the current user.

https://lunchmoney.dev/#get-user

Returns:

Type Description UserObject Source code in lunchable/models/user.py
def get_user(self) -> UserObject:\n    \"\"\"\n    Get Personal User Details\n\n    Use this endpoint to get details on the current user.\n\n    https://lunchmoney.dev/#get-user\n\n    Returns\n    -------\n    UserObject\n    \"\"\"\n    response_data = self.make_request(\n        method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_ME\n    )\n    me = UserObject.model_validate(response_data)\n    return me\n
"},{"location":"reference/models/user/#lunchable.models.user.UserObject","title":"UserObject","text":"

Bases: LunchableModel

The LunchMoney User object

https://lunchmoney.dev/#user-object

Parameters:

Name Type Description Default user_id int

Unique identifier for user

required user_name str

User's' name

required user_email str

User's' Email

required account_id int

Unique identifier for the associated budgeting account

required budget_name str

Name of the associated budgeting account

required api_key_label str | None

User-defined label of the developer API key used. Returns null if nothing has been set.

None Source code in lunchable/models/user.py
class UserObject(LunchableModel):\n    \"\"\"\n    The LunchMoney `User` object\n\n    https://lunchmoney.dev/#user-object\n    \"\"\"\n\n    user_id: int = Field(description=\"Unique identifier for user\")\n    user_name: str = Field(description=\"User's' name\")\n    user_email: str = Field(description=\"User's' Email\")\n    account_id: int = Field(\n        description=\"Unique identifier for the associated budgeting account\"\n    )\n    budget_name: str = Field(description=\"Name of the associated budgeting account\")\n    api_key_label: Optional[str] = Field(\n        None,\n        description=\"User-defined label of the developer API key used. \"\n        \"Returns null if nothing has been set.\",\n    )\n
"},{"location":"reference/plugins/","title":"plugins","text":"

Optional Plugins for LunchMoney

"},{"location":"reference/plugins/#lunchable.plugins.LunchableApp","title":"LunchableApp","text":"

Bases: BaseLunchableApp

Pre-Built Lunchable App

This app comes with a data property which represents all the base data the app should need. Extend the data_models property to items like TransactionObjects to interact with transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableApp(BaseLunchableApp):\n    \"\"\"\n    Pre-Built Lunchable App\n\n    This app comes with a `data` property which represents all the base data\n    the app should need. Extend the `data_models` property to items like\n    `TransactionObject`s to interact with transactions\n    \"\"\"\n\n    @property\n    def lunchable_models(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Which Data Should this app get\n        \"\"\"\n        return []\n\n    @property\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Built-In Models to Populate Most LunchableApp instances\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n        return [\n            LunchableDataModel(\n                model=CategoriesObject, function=self.lunch.get_categories\n            ),\n            LunchableDataModel(\n                model=PlaidAccountObject,\n                function=self.lunch.get_plaid_accounts,\n            ),\n            LunchableDataModel(\n                model=AssetsObject,\n                function=self.lunch.get_assets,\n            ),\n            LunchableDataModel(\n                model=TagsObject,\n                function=self.lunch.get_tags,\n            ),\n            LunchableDataModel(\n                model=UserObject,\n                function=self.lunch.get_user,\n            ),\n            LunchableDataModel(\n                model=CryptoObject,\n                function=self.lunch.get_crypto,\n            ),\n        ]\n
"},{"location":"reference/plugins/#lunchable.plugins.LunchableApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] property","text":"

Built-In Models to Populate Most LunchableApp instances

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/#lunchable.plugins.LunchableApp.lunchable_models","title":"lunchable_models: List[LunchableDataModel] property","text":"

Which Data Should this app get

"},{"location":"reference/plugins/#lunchable.plugins.LunchableTransactionsApp","title":"LunchableTransactionsApp","text":"

Bases: LunchableTransactionsBaseApp

Pre-Built Lunchable App with the last 365 days worth of transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableTransactionsApp(LunchableTransactionsBaseApp):\n    \"\"\"\n    Pre-Built Lunchable App with the last 365 days worth of transactions\n    \"\"\"\n\n    @property\n    def start_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a Start Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year - 100)\n\n    @property\n    def end_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a End Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year + 100)\n
"},{"location":"reference/plugins/#lunchable.plugins.LunchableTransactionsApp.end_date","title":"end_date: datetime.date property","text":"

LunchableTransactionsApp requires a End Date

Returns:

Type Description date"},{"location":"reference/plugins/#lunchable.plugins.LunchableTransactionsApp.start_date","title":"start_date: datetime.date property","text":"

LunchableTransactionsApp requires a Start Date

Returns:

Type Description date"},{"location":"reference/plugins/base/","title":"base","text":"

Base Apps for Plugins

"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableApp","title":"LunchableApp","text":"

Bases: BaseLunchableApp

Pre-Built Lunchable App

This app comes with a data property which represents all the base data the app should need. Extend the data_models property to items like TransactionObjects to interact with transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableApp(BaseLunchableApp):\n    \"\"\"\n    Pre-Built Lunchable App\n\n    This app comes with a `data` property which represents all the base data\n    the app should need. Extend the `data_models` property to items like\n    `TransactionObject`s to interact with transactions\n    \"\"\"\n\n    @property\n    def lunchable_models(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Which Data Should this app get\n        \"\"\"\n        return []\n\n    @property\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Built-In Models to Populate Most LunchableApp instances\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n        return [\n            LunchableDataModel(\n                model=CategoriesObject, function=self.lunch.get_categories\n            ),\n            LunchableDataModel(\n                model=PlaidAccountObject,\n                function=self.lunch.get_plaid_accounts,\n            ),\n            LunchableDataModel(\n                model=AssetsObject,\n                function=self.lunch.get_assets,\n            ),\n            LunchableDataModel(\n                model=TagsObject,\n                function=self.lunch.get_tags,\n            ),\n            LunchableDataModel(\n                model=UserObject,\n                function=self.lunch.get_user,\n            ),\n            LunchableDataModel(\n                model=CryptoObject,\n                function=self.lunch.get_crypto,\n            ),\n        ]\n
"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] property","text":"

Built-In Models to Populate Most LunchableApp instances

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableApp.lunchable_models","title":"lunchable_models: List[LunchableDataModel] property","text":"

Which Data Should this app get

"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableTransactionsApp","title":"LunchableTransactionsApp","text":"

Bases: LunchableTransactionsBaseApp

Pre-Built Lunchable App with the last 365 days worth of transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableTransactionsApp(LunchableTransactionsBaseApp):\n    \"\"\"\n    Pre-Built Lunchable App with the last 365 days worth of transactions\n    \"\"\"\n\n    @property\n    def start_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a Start Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year - 100)\n\n    @property\n    def end_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a End Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year + 100)\n
"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableTransactionsApp.end_date","title":"end_date: datetime.date property","text":"

LunchableTransactionsApp requires a End Date

Returns:

Type Description date"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableTransactionsApp.start_date","title":"start_date: datetime.date property","text":"

LunchableTransactionsApp requires a Start Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/","title":"base_app","text":"

Base App Class

"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp","title":"BaseLunchableApp","text":"

Bases: ABC

Abstract Base Class for Lunchable Apps

Source code in lunchable/plugins/base/base_app.py
class BaseLunchableApp(ABC):\n    \"\"\"\n    Abstract Base Class for Lunchable Apps\n    \"\"\"\n\n    __lunchable_object_mapping__: Dict[str, str] = {\n        PlaidAccountObject.__name__: \"plaid_accounts\",\n        TransactionObject.__name__: \"transactions\",\n        CategoriesObject.__name__: \"categories\",\n        AssetsObject.__name__: \"assets\",\n        TagsObject.__name__: \"tags\",\n        UserObject.__name__: \"user\",\n        CryptoObject.__name__: \"crypto\",\n    }\n\n    @property\n    @abstractmethod\n    def lunchable_models(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Every LunchableApp should define which data objects it depends on\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n\n    @property\n    @abstractmethod\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Every LunchableApp should define which data objects are built-in\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n\n    def __init__(self, cache_time: int = 0, access_token: Optional[str] = None):\n        \"\"\"\n        Lunchable App Initialization\n\n        Parameters\n        ----------\n        cache_time: int\n            Amount of time until the cache should be refreshed\n            (in seconds). Defaults to 0 which always polls for the latest data\n        access_token: Optional[str]\n            Lunchmoney Developer API Access Token\n        \"\"\"\n        self.lunch = LunchMoney(access_token=access_token)\n        self.lunch_data = LunchableDataContainer()\n        self.data_dir = FileConfig.DATA_DIR.joinpath(self.__class__.__name__).joinpath(\n            sha256(self.lunch.access_token.encode(\"utf-8\")).hexdigest()\n        )\n        self.cache_time = cache_time\n        if self.cache_time > 0:\n            self.data_dir.mkdir(exist_ok=True, parents=True)\n\n    def _cache_single_object(\n        self,\n        model: Type[LunchableModelType],\n        function: Callable[[Any], Any],\n        kwargs: Optional[Dict[str, Any]] = None,\n        force: bool = False,\n    ) -> Union[LunchableModelType, List[LunchableModelType]]:\n        \"\"\"\n        Cache a Core Lunchable Data Object\n\n        Parameters\n        ----------\n        model: Type[LunchableModel]\n        function: Callable\n        kwargs: Optional[Dict[str, Any]]\n        force: bool\n\n        Returns\n        -------\n        Any\n        \"\"\"\n        if kwargs is None:\n            kwargs = {}\n        data_file = self.data_dir.joinpath(f\"{model.__name__}.lunch\")\n        if force is True:\n            refresh = True\n        elif self.cache_time > 0 and data_file.exists():\n            modified_time = datetime.datetime.fromtimestamp(\n                data_file.stat().st_mtime, tz=datetime.timezone.utc\n            )\n            current_time = datetime.datetime.now(tz=datetime.timezone.utc)\n            file_age = current_time - modified_time\n            refresh = file_age > datetime.timedelta(seconds=self.cache_time)\n        else:\n            refresh = True\n        if refresh is True:\n            data_objects = function(**kwargs)  # type: ignore[call-arg]\n            if self.cache_time > 0:\n                plain_data: str = json.dumps(data_objects, default=pydantic_encoder)\n                base64_data: bytes = base64.b64encode(plain_data.encode(\"utf-8\"))\n                data_file.write_bytes(base64_data)\n        else:\n            file_text: bytes = data_file.read_bytes()\n            json_body: bytes = base64.b64decode(file_text)\n            json_data: Union[Dict[str, Any], List[Dict[str, Any]]] = json.loads(\n                json_body.decode(\"utf-8\")\n            )\n            if isinstance(json_data, dict):\n                data_objects = model.model_validate(json_data)\n            else:\n                data_objects = [model.model_validate(item) for item in json_data]\n        return data_objects\n\n    def get_latest_cache(\n        self,\n        include: Optional[List[Type[LunchableModel]]] = None,\n        exclude: Optional[List[Type[LunchableModel]]] = None,\n        force: bool = False,\n    ) -> None:\n        \"\"\"\n        Cache the Underlying Data Objects\n\n        Parameters\n        ----------\n        include : Optional[List[Type[LunchableModel]]]\n            Models to refresh cache for (instead of all of them)\n        exclude : Optional[List[Type[LunchableModel]]]\n            Models to skip cache refreshing\n        force: bool\n            Whether to force the cache\n\n        Returns\n        -------\n        None\n        \"\"\"\n        models_to_process = self.lunchable_models + self.__builtin_data_models__\n        if include is not None:\n            new_models_to_process: List[LunchableDataModel] = []\n            data_model_mapping = {item.model: item for item in models_to_process}\n            for model_class in include:\n                new_models_to_process.append(data_model_mapping[model_class])\n            models_to_process = new_models_to_process\n        exclusions = exclude if exclude is not None else []\n        for data_model in models_to_process:\n            if data_model.model in exclusions:\n                continue\n            cache = self._cache_single_object(\n                model=data_model.model,\n                function=data_model.function,\n                kwargs=data_model.kwargs,\n                force=force,\n            )\n            cache_attribute: Union[Dict[int, LunchableModel], LunchableModel]\n            if isinstance(cache, list):\n                cache_attribute = {item.id: item for item in cache}\n            else:\n                cache_attribute = cache\n            setattr(\n                self.lunch_data,\n                self.__lunchable_object_mapping__[data_model.model.__name__],\n                cache_attribute,\n            )\n\n    def refresh_transactions(\n        self,\n        start_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n        end_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n        tag_id: Optional[int] = None,\n        recurring_id: Optional[int] = None,\n        plaid_account_id: Optional[int] = None,\n        category_id: Optional[int] = None,\n        asset_id: Optional[int] = None,\n        group_id: Optional[int] = None,\n        is_group: Optional[bool] = None,\n        status: Optional[str] = None,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        debit_as_negative: Optional[bool] = None,\n        pending: Optional[bool] = None,\n        params: Optional[Dict[str, Any]] = None,\n    ) -> Dict[int, TransactionObject]:\n        \"\"\"\n        Refresh App data with the latest transactions\n\n        Parameters\n        ----------\n        start_date: Optional[Union[datetime.date, datetime.datetime, str]]\n            Denotes the beginning of the time period to fetch transactions for. Defaults\n            to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.\n        end_date: Optional[Union[datetime.date, datetime.datetime, str]]\n            Denotes the end of the time period you'd like to get transactions for.\n            Defaults to end of current month. Required if start_date exists.\n        tag_id: Optional[int]\n            Filter by tag. Only accepts IDs, not names.\n        recurring_id: Optional[int]\n            Filter by recurring expense\n        plaid_account_id: Optional[int]\n            Filter by Plaid account\n        category_id: Optional[int]\n            Filter by category. Will also match category groups.\n        asset_id: Optional[int]\n            Filter by asset\n        group_id: Optional[int]\n            Filter by group_id (if the transaction is part of a specific group)\n        is_group: Optional[bool]\n            Filter by group (returns transaction groups)\n        status: Optional[str]\n            Filter by status (Can be cleared or uncleared. For recurring\n            transactions, use recurring)\n        offset: Optional[int]\n            Sets the offset for the records returned\n        limit: Optional[int]\n            Sets the maximum number of records to return. Note: The server will not\n            respond with any indication that there are more records to be returned.\n            Please check the response length to determine if you should make another\n            call with an offset to fetch more transactions.\n        debit_as_negative: Optional[bool]\n            Pass in true if you'd like expenses to be returned as negative amounts and\n            credits as positive amounts. Defaults to false.\n        pending: Optional[bool]\n            Pass in true if you'd like to include imported transactions with a pending status.\n        params: Optional[dict]\n            Additional Query String Params\n\n        Returns\n        -------\n        Dict[int, TransactionObject]\n        \"\"\"\n        transactions = self.lunch.get_transactions(\n            start_date=start_date, end_date=end_date, status=status\n        )\n        transaction_map = {item.id: item for item in transactions}\n        self.lunch_data.transactions = transaction_map\n        return transaction_map\n\n    def delete_cache(self) -> None:\n        \"\"\"\n        Delete any corresponding cache files\n        \"\"\"\n        if self.data_dir.exists():\n            shutil.rmtree(self.data_dir)\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] abstractmethod property","text":"

Every LunchableApp should define which data objects are built-in

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.lunchable_models","title":"lunchable_models: List[LunchableDataModel] abstractmethod property","text":"

Every LunchableApp should define which data objects it depends on

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.__init__","title":"__init__(cache_time=0, access_token=None)","text":"

Lunchable App Initialization

Parameters:

Name Type Description Default cache_time int

Amount of time until the cache should be refreshed (in seconds). Defaults to 0 which always polls for the latest data

0 access_token Optional[str]

Lunchmoney Developer API Access Token

None Source code in lunchable/plugins/base/base_app.py
def __init__(self, cache_time: int = 0, access_token: Optional[str] = None):\n    \"\"\"\n    Lunchable App Initialization\n\n    Parameters\n    ----------\n    cache_time: int\n        Amount of time until the cache should be refreshed\n        (in seconds). Defaults to 0 which always polls for the latest data\n    access_token: Optional[str]\n        Lunchmoney Developer API Access Token\n    \"\"\"\n    self.lunch = LunchMoney(access_token=access_token)\n    self.lunch_data = LunchableDataContainer()\n    self.data_dir = FileConfig.DATA_DIR.joinpath(self.__class__.__name__).joinpath(\n        sha256(self.lunch.access_token.encode(\"utf-8\")).hexdigest()\n    )\n    self.cache_time = cache_time\n    if self.cache_time > 0:\n        self.data_dir.mkdir(exist_ok=True, parents=True)\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.delete_cache","title":"delete_cache()","text":"

Delete any corresponding cache files

Source code in lunchable/plugins/base/base_app.py
def delete_cache(self) -> None:\n    \"\"\"\n    Delete any corresponding cache files\n    \"\"\"\n    if self.data_dir.exists():\n        shutil.rmtree(self.data_dir)\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.get_latest_cache","title":"get_latest_cache(include=None, exclude=None, force=False)","text":"

Cache the Underlying Data Objects

Parameters:

Name Type Description Default include Optional[List[Type[LunchableModel]]]

Models to refresh cache for (instead of all of them)

None exclude Optional[List[Type[LunchableModel]]]

Models to skip cache refreshing

None force bool

Whether to force the cache

False

Returns:

Type Description None Source code in lunchable/plugins/base/base_app.py
def get_latest_cache(\n    self,\n    include: Optional[List[Type[LunchableModel]]] = None,\n    exclude: Optional[List[Type[LunchableModel]]] = None,\n    force: bool = False,\n) -> None:\n    \"\"\"\n    Cache the Underlying Data Objects\n\n    Parameters\n    ----------\n    include : Optional[List[Type[LunchableModel]]]\n        Models to refresh cache for (instead of all of them)\n    exclude : Optional[List[Type[LunchableModel]]]\n        Models to skip cache refreshing\n    force: bool\n        Whether to force the cache\n\n    Returns\n    -------\n    None\n    \"\"\"\n    models_to_process = self.lunchable_models + self.__builtin_data_models__\n    if include is not None:\n        new_models_to_process: List[LunchableDataModel] = []\n        data_model_mapping = {item.model: item for item in models_to_process}\n        for model_class in include:\n            new_models_to_process.append(data_model_mapping[model_class])\n        models_to_process = new_models_to_process\n    exclusions = exclude if exclude is not None else []\n    for data_model in models_to_process:\n        if data_model.model in exclusions:\n            continue\n        cache = self._cache_single_object(\n            model=data_model.model,\n            function=data_model.function,\n            kwargs=data_model.kwargs,\n            force=force,\n        )\n        cache_attribute: Union[Dict[int, LunchableModel], LunchableModel]\n        if isinstance(cache, list):\n            cache_attribute = {item.id: item for item in cache}\n        else:\n            cache_attribute = cache\n        setattr(\n            self.lunch_data,\n            self.__lunchable_object_mapping__[data_model.model.__name__],\n            cache_attribute,\n        )\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.BaseLunchableApp.refresh_transactions","title":"refresh_transactions(start_date=None, end_date=None, tag_id=None, recurring_id=None, plaid_account_id=None, category_id=None, asset_id=None, group_id=None, is_group=None, status=None, offset=None, limit=None, debit_as_negative=None, pending=None, params=None)","text":"

Refresh App data with the latest transactions

Parameters:

Name Type Description Default start_date Optional[Union[date, datetime, str]]

Denotes the beginning of the time period to fetch transactions for. Defaults to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.

None end_date Optional[Union[date, datetime, str]]

Denotes the end of the time period you'd like to get transactions for. Defaults to end of current month. Required if start_date exists.

None tag_id Optional[int]

Filter by tag. Only accepts IDs, not names.

None recurring_id Optional[int]

Filter by recurring expense

None plaid_account_id Optional[int]

Filter by Plaid account

None category_id Optional[int]

Filter by category. Will also match category groups.

None asset_id Optional[int]

Filter by asset

None group_id Optional[int]

Filter by group_id (if the transaction is part of a specific group)

None is_group Optional[bool]

Filter by group (returns transaction groups)

None status Optional[str]

Filter by status (Can be cleared or uncleared. For recurring transactions, use recurring)

None offset Optional[int]

Sets the offset for the records returned

None limit Optional[int]

Sets the maximum number of records to return. Note: The server will not respond with any indication that there are more records to be returned. Please check the response length to determine if you should make another call with an offset to fetch more transactions.

None debit_as_negative Optional[bool]

Pass in true if you'd like expenses to be returned as negative amounts and credits as positive amounts. Defaults to false.

None pending Optional[bool]

Pass in true if you'd like to include imported transactions with a pending status.

None params Optional[Dict[str, Any]]

Additional Query String Params

None

Returns:

Type Description Dict[int, TransactionObject] Source code in lunchable/plugins/base/base_app.py
def refresh_transactions(\n    self,\n    start_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n    end_date: Optional[Union[datetime.date, datetime.datetime, str]] = None,\n    tag_id: Optional[int] = None,\n    recurring_id: Optional[int] = None,\n    plaid_account_id: Optional[int] = None,\n    category_id: Optional[int] = None,\n    asset_id: Optional[int] = None,\n    group_id: Optional[int] = None,\n    is_group: Optional[bool] = None,\n    status: Optional[str] = None,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    debit_as_negative: Optional[bool] = None,\n    pending: Optional[bool] = None,\n    params: Optional[Dict[str, Any]] = None,\n) -> Dict[int, TransactionObject]:\n    \"\"\"\n    Refresh App data with the latest transactions\n\n    Parameters\n    ----------\n    start_date: Optional[Union[datetime.date, datetime.datetime, str]]\n        Denotes the beginning of the time period to fetch transactions for. Defaults\n        to beginning of current month. Required if end_date exists. Format: YYYY-MM-DD.\n    end_date: Optional[Union[datetime.date, datetime.datetime, str]]\n        Denotes the end of the time period you'd like to get transactions for.\n        Defaults to end of current month. Required if start_date exists.\n    tag_id: Optional[int]\n        Filter by tag. Only accepts IDs, not names.\n    recurring_id: Optional[int]\n        Filter by recurring expense\n    plaid_account_id: Optional[int]\n        Filter by Plaid account\n    category_id: Optional[int]\n        Filter by category. Will also match category groups.\n    asset_id: Optional[int]\n        Filter by asset\n    group_id: Optional[int]\n        Filter by group_id (if the transaction is part of a specific group)\n    is_group: Optional[bool]\n        Filter by group (returns transaction groups)\n    status: Optional[str]\n        Filter by status (Can be cleared or uncleared. For recurring\n        transactions, use recurring)\n    offset: Optional[int]\n        Sets the offset for the records returned\n    limit: Optional[int]\n        Sets the maximum number of records to return. Note: The server will not\n        respond with any indication that there are more records to be returned.\n        Please check the response length to determine if you should make another\n        call with an offset to fetch more transactions.\n    debit_as_negative: Optional[bool]\n        Pass in true if you'd like expenses to be returned as negative amounts and\n        credits as positive amounts. Defaults to false.\n    pending: Optional[bool]\n        Pass in true if you'd like to include imported transactions with a pending status.\n    params: Optional[dict]\n        Additional Query String Params\n\n    Returns\n    -------\n    Dict[int, TransactionObject]\n    \"\"\"\n    transactions = self.lunch.get_transactions(\n        start_date=start_date, end_date=end_date, status=status\n    )\n    transaction_map = {item.id: item for item in transactions}\n    self.lunch_data.transactions = transaction_map\n    return transaction_map\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableApp","title":"LunchableApp","text":"

Bases: BaseLunchableApp

Pre-Built Lunchable App

This app comes with a data property which represents all the base data the app should need. Extend the data_models property to items like TransactionObjects to interact with transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableApp(BaseLunchableApp):\n    \"\"\"\n    Pre-Built Lunchable App\n\n    This app comes with a `data` property which represents all the base data\n    the app should need. Extend the `data_models` property to items like\n    `TransactionObject`s to interact with transactions\n    \"\"\"\n\n    @property\n    def lunchable_models(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Which Data Should this app get\n        \"\"\"\n        return []\n\n    @property\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Built-In Models to Populate Most LunchableApp instances\n\n        Returns\n        -------\n        List[LunchableDataModel]\n        \"\"\"\n        return [\n            LunchableDataModel(\n                model=CategoriesObject, function=self.lunch.get_categories\n            ),\n            LunchableDataModel(\n                model=PlaidAccountObject,\n                function=self.lunch.get_plaid_accounts,\n            ),\n            LunchableDataModel(\n                model=AssetsObject,\n                function=self.lunch.get_assets,\n            ),\n            LunchableDataModel(\n                model=TagsObject,\n                function=self.lunch.get_tags,\n            ),\n            LunchableDataModel(\n                model=UserObject,\n                function=self.lunch.get_user,\n            ),\n            LunchableDataModel(\n                model=CryptoObject,\n                function=self.lunch.get_crypto,\n            ),\n        ]\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] property","text":"

Built-In Models to Populate Most LunchableApp instances

Returns:

Type Description List[LunchableDataModel]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableApp.lunchable_models","title":"lunchable_models: List[LunchableDataModel] property","text":"

Which Data Should this app get

"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer","title":"LunchableDataContainer","text":"

Bases: BaseModel

Data Container for Lunchable App Data

Parameters:

Name Type Description Default plaid_accounts Dict[int, PlaidAccountObject] {} transactions Dict[int, TransactionObject] {} categories Dict[int, CategoriesObject] {} assets Dict[int, AssetsObject] {} tags Dict[int, TagsObject] {} user UserObject UserObject(user_id=0, user_name='', user_email='', account_id=0, budget_name='', api_key_label=None) crypto Dict[int, CryptoObject] {} Source code in lunchable/plugins/base/base_app.py
class LunchableDataContainer(BaseModel):\n    \"\"\"\n    Data Container for Lunchable App Data\n    \"\"\"\n\n    plaid_accounts: Dict[int, PlaidAccountObject] = {}\n    transactions: Dict[int, TransactionObject] = {}\n    categories: Dict[int, CategoriesObject] = {}\n    assets: Dict[int, AssetsObject] = {}\n    tags: Dict[int, TagsObject] = {}\n    user: UserObject = UserObject(\n        user_id=0, user_name=\"\", user_email=\"\", account_id=0, budget_name=\"\"\n    )\n    crypto: Dict[int, CryptoObject] = {}\n\n    @property\n    def asset_map(self) -> Dict[int, Union[PlaidAccountObject, AssetsObject]]:\n        \"\"\"\n        Asset Mapping Across Plaid Accounts and Assets\n\n        Returns\n        -------\n        Dict[int, Union[PlaidAccountObject, AssetsObject]]\n        \"\"\"\n        asset_map: Dict[int, Union[PlaidAccountObject, AssetsObject]] = {}\n        asset_map.update(self.plaid_accounts)\n        asset_map.update(self.assets)\n        return asset_map\n\n    @property\n    def plaid_accounts_list(self) -> List[PlaidAccountObject]:\n        \"\"\"\n        List of Plaid Accounts\n\n        Returns\n        -------\n        List[PlaidAccountObject]\n        \"\"\"\n        return list(self.plaid_accounts.values())\n\n    @property\n    def assets_list(self) -> List[AssetsObject]:\n        \"\"\"\n        List of Assets\n\n        Returns\n        -------\n        List[AssetsObject]\n        \"\"\"\n        return list(self.assets.values())\n\n    @property\n    def transactions_list(self) -> List[TransactionObject]:\n        \"\"\"\n        List of Transactions\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        return list(self.transactions.values())\n\n    @property\n    def categories_list(self) -> List[CategoriesObject]:\n        \"\"\"\n        List of Categories\n\n        Returns\n        -------\n        List[CategoriesObject]\n        \"\"\"\n        return list(self.categories.values())\n\n    @property\n    def tags_list(self) -> List[TagsObject]:\n        \"\"\"\n        List of Tags\n\n        Returns\n        -------\n        List[TagsObject]\n        \"\"\"\n        return list(self.tags.values())\n\n    @property\n    def crypto_list(self) -> List[CryptoObject]:\n        \"\"\"\n        List of Crypto\n\n        Returns\n        -------\n        List[CryptoObject]\n        \"\"\"\n        return list(self.crypto.values())\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.asset_map","title":"asset_map: Dict[int, Union[PlaidAccountObject, AssetsObject]] property","text":"

Asset Mapping Across Plaid Accounts and Assets

Returns:

Type Description Dict[int, Union[PlaidAccountObject, AssetsObject]]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.assets_list","title":"assets_list: List[AssetsObject] property","text":"

List of Assets

Returns:

Type Description List[AssetsObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.categories_list","title":"categories_list: List[CategoriesObject] property","text":"

List of Categories

Returns:

Type Description List[CategoriesObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.crypto_list","title":"crypto_list: List[CryptoObject] property","text":"

List of Crypto

Returns:

Type Description List[CryptoObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.plaid_accounts_list","title":"plaid_accounts_list: List[PlaidAccountObject] property","text":"

List of Plaid Accounts

Returns:

Type Description List[PlaidAccountObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.tags_list","title":"tags_list: List[TagsObject] property","text":"

List of Tags

Returns:

Type Description List[TagsObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataContainer.transactions_list","title":"transactions_list: List[TransactionObject] property","text":"

List of Transactions

Returns:

Type Description List[TransactionObject]"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableDataModel","title":"LunchableDataModel","text":"

Bases: LunchableModel

Core Data Model Defining App Dependencies

Parameters:

Name Type Description Default model Type[LunchableModel] required function Callable[list, Any] required kwargs Dict[str, Any] {} Source code in lunchable/plugins/base/base_app.py
class LunchableDataModel(LunchableModel):\n    \"\"\"\n    Core Data Model Defining App Dependencies\n    \"\"\"\n\n    model: Type[LunchableModel]\n    function: Callable[[Any], Any]\n    kwargs: Dict[str, Any] = {}\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsApp","title":"LunchableTransactionsApp","text":"

Bases: LunchableTransactionsBaseApp

Pre-Built Lunchable App with the last 365 days worth of transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableTransactionsApp(LunchableTransactionsBaseApp):\n    \"\"\"\n    Pre-Built Lunchable App with the last 365 days worth of transactions\n    \"\"\"\n\n    @property\n    def start_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a Start Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year - 100)\n\n    @property\n    def end_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a End Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n        today = datetime.date.today()\n        return today.replace(year=today.year + 100)\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsApp.end_date","title":"end_date: datetime.date property","text":"

LunchableTransactionsApp requires a End Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsApp.start_date","title":"start_date: datetime.date property","text":"

LunchableTransactionsApp requires a Start Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp","title":"LunchableTransactionsBaseApp","text":"

Bases: LunchableApp, ABC

LunchableApp supporting transactions

Source code in lunchable/plugins/base/base_app.py
class LunchableTransactionsBaseApp(LunchableApp, ABC):\n    \"\"\"\n    LunchableApp supporting transactions\n    \"\"\"\n\n    data_models: List[LunchableDataModel] = []\n\n    @property\n    @abstractmethod\n    def start_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a Start Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n\n    @property\n    @abstractmethod\n    def end_date(self) -> datetime.date:\n        \"\"\"\n        LunchableTransactionsApp requires a End Date\n\n        Returns\n        -------\n        datetime.date\n        \"\"\"\n\n    @property\n    def __builtin_data_models__(self) -> List[LunchableDataModel]:\n        \"\"\"\n        Which Data Should this app get\n        \"\"\"\n        return [\n            *super().__builtin_data_models__,\n            LunchableDataModel(\n                model=TransactionObject,\n                function=self.lunch.get_transactions,\n                kwargs={\"start_date\": self.start_date, \"end_date\": self.end_date},\n            ),\n        ]\n\n    def refresh_transactions(  # type: ignore[override]\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> Dict[int, TransactionObject]:\n        \"\"\"\n        Refresh App data with the latest transactions\n\n        Returns\n        -------\n        Dict[int, TransactionObject]\n        \"\"\"\n        transactions = self.lunch.get_transactions(\n            start_date=start_date if start_date is not None else self.start_date,\n            end_date=end_date if end_date is not None else self.end_date,\n        )\n        transaction_map = {item.id: item for item in transactions}\n        self.lunch_data.transactions = transaction_map\n        return transaction_map\n
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp.__builtin_data_models__","title":"__builtin_data_models__: List[LunchableDataModel] property","text":"

Which Data Should this app get

"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp.end_date","title":"end_date: datetime.date abstractmethod property","text":"

LunchableTransactionsApp requires a End Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp.start_date","title":"start_date: datetime.date abstractmethod property","text":"

LunchableTransactionsApp requires a Start Date

Returns:

Type Description date"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp.refresh_transactions","title":"refresh_transactions(start_date=None, end_date=None)","text":"

Refresh App data with the latest transactions

Returns:

Type Description Dict[int, TransactionObject] Source code in lunchable/plugins/base/base_app.py
def refresh_transactions(  # type: ignore[override]\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> Dict[int, TransactionObject]:\n    \"\"\"\n    Refresh App data with the latest transactions\n\n    Returns\n    -------\n    Dict[int, TransactionObject]\n    \"\"\"\n    transactions = self.lunch.get_transactions(\n        start_date=start_date if start_date is not None else self.start_date,\n        end_date=end_date if end_date is not None else self.end_date,\n    )\n    transaction_map = {item.id: item for item in transactions}\n    self.lunch_data.transactions = transaction_map\n    return transaction_map\n
"},{"location":"reference/plugins/base/pandas_app/","title":"pandas_app","text":"

Apps with Pandas Support

"},{"location":"reference/plugins/base/pandas_app/#lunchable.plugins.base.pandas_app.LunchablePandasApp","title":"LunchablePandasApp","text":"

Bases: LunchableApp

LunchableApp with Pandas Super Powers

Source code in lunchable/plugins/base/pandas_app.py
class LunchablePandasApp(LunchableApp):\n    \"\"\"\n    LunchableApp with Pandas Super Powers\n    \"\"\"\n\n    @staticmethod\n    def models_to_df(models: Iterable[LunchableModel]) -> pd.DataFrame:\n        \"\"\"\n        Convert Transactions Array to DataFrame\n\n        Parameters\n        ----------\n        models: List[LunchableModel]\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        if not isinstance(models, list):\n            models = list(models)\n        return pd.DataFrame(\n            [item.model_dump() for item in models],\n            columns=models[0].__fields__.keys(),\n        )\n\n    @staticmethod\n    def df_to_models(\n        df: pd.DataFrame, model_type: Type[LunchableModelType]\n    ) -> List[LunchableModelType]:\n        \"\"\"\n        Convert DataFrame to Transaction Array\n\n        Parameters\n        ----------\n        df: pd.DataFrame\n        model_type: Type[LunchableModel]\n\n        Returns\n        -------\n        List[LunchableModel]\n        \"\"\"\n        array_df = df.copy()\n        array_df = array_df.fillna(np.NaN).replace([np.NaN], [None])\n        model_array = array_df.to_dict(orient=\"records\")\n        return [model_type.model_validate(item) for item in model_array]\n
"},{"location":"reference/plugins/base/pandas_app/#lunchable.plugins.base.pandas_app.LunchablePandasApp.df_to_models","title":"df_to_models(df, model_type) staticmethod","text":"

Convert DataFrame to Transaction Array

Parameters:

Name Type Description Default df DataFrame required model_type Type[LunchableModelType] required

Returns:

Type Description List[LunchableModel] Source code in lunchable/plugins/base/pandas_app.py
@staticmethod\ndef df_to_models(\n    df: pd.DataFrame, model_type: Type[LunchableModelType]\n) -> List[LunchableModelType]:\n    \"\"\"\n    Convert DataFrame to Transaction Array\n\n    Parameters\n    ----------\n    df: pd.DataFrame\n    model_type: Type[LunchableModel]\n\n    Returns\n    -------\n    List[LunchableModel]\n    \"\"\"\n    array_df = df.copy()\n    array_df = array_df.fillna(np.NaN).replace([np.NaN], [None])\n    model_array = array_df.to_dict(orient=\"records\")\n    return [model_type.model_validate(item) for item in model_array]\n
"},{"location":"reference/plugins/base/pandas_app/#lunchable.plugins.base.pandas_app.LunchablePandasApp.models_to_df","title":"models_to_df(models) staticmethod","text":"

Convert Transactions Array to DataFrame

Parameters:

Name Type Description Default models Iterable[LunchableModel] required

Returns:

Type Description DataFrame Source code in lunchable/plugins/base/pandas_app.py
@staticmethod\ndef models_to_df(models: Iterable[LunchableModel]) -> pd.DataFrame:\n    \"\"\"\n    Convert Transactions Array to DataFrame\n\n    Parameters\n    ----------\n    models: List[LunchableModel]\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    if not isinstance(models, list):\n        models = list(models)\n    return pd.DataFrame(\n        [item.model_dump() for item in models],\n        columns=models[0].__fields__.keys(),\n    )\n
"},{"location":"reference/plugins/base/pandas_app/#lunchable.plugins.base.pandas_app.LunchablePandasTransactionsApp","title":"LunchablePandasTransactionsApp","text":"

Bases: LunchableTransactionsApp, LunchablePandasApp

LunchableTransactionsApp with Pandas Super Powers

Source code in lunchable/plugins/base/pandas_app.py
class LunchablePandasTransactionsApp(LunchableTransactionsApp, LunchablePandasApp):\n    \"\"\"\n    LunchableTransactionsApp with Pandas Super Powers\n    \"\"\"\n
"},{"location":"reference/plugins/primelunch/","title":"primelunch","text":"

PrimeLunch Plugin

"},{"location":"reference/plugins/primelunch/primelunch/","title":"primelunch","text":"

PrimeLunch Utils

"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch","title":"PrimeLunch","text":"

Bases: LunchablePandasApp

PrimeLunch: Amazon Notes Updater

Source code in lunchable/plugins/primelunch/primelunch.py
class PrimeLunch(LunchablePandasApp):\n    \"\"\"\n    PrimeLunch: Amazon Notes Updater\n    \"\"\"\n\n    def __init__(\n        self,\n        file_path: Union[str, os.PathLike[str], pathlib.Path],\n        time_window: int = 7,\n        access_token: Optional[str] = None,\n    ) -> None:\n        \"\"\"\n        Initialize and set internal data\n        \"\"\"\n        super().__init__(cache_time=0, access_token=access_token)\n        self.file_path = pathlib.Path(file_path)\n        self.time_window = time_window\n\n    def amazon_to_df(self) -> pd.DataFrame:\n        \"\"\"\n        Read an Amazon Data File to a DataFrame\n\n        This is pretty simple, except duplicate header rows need to be cleaned\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        dt64: np.dtype[datetime64] = np.dtype(\"datetime64[ns]\")\n        expected_columns = {\n            \"order id\": str,\n            \"items\": str,\n            \"to\": str,\n            \"date\": dt64,\n            \"total\": np.float64,\n            \"shipping\": np.float64,\n            \"gift\": np.float64,\n            \"refund\": np.float64,\n            \"payments\": str,\n        }\n        amazon_df = pd.read_csv(\n            self.file_path,\n            usecols=expected_columns.keys(),\n        )\n        header_row_eval = pd.concat(\n            [amazon_df[item] == item for item in expected_columns.keys()], axis=1\n        ).all(axis=1)\n        duplicate_header_rows = np.where(header_row_eval)[0]\n        amazon_df.drop(duplicate_header_rows, axis=0, inplace=True)\n        amazon_df[\"total\"] = (\n            amazon_df[\"total\"].astype(\"string\").str.replace(\",\", \"\").astype(np.float64)\n        )\n        amazon_df = amazon_df.astype(dtype=expected_columns, copy=True, errors=\"raise\")\n        logger.info(\"Amazon Data File loaded: %s\", self.file_path)\n        return amazon_df\n\n    @classmethod\n    def filter_amazon_transactions(cls, df: pd.DataFrame) -> pd.DataFrame:\n        \"\"\"\n        Filter a DataFrame to Amazon Transactions\n\n        Parameters\n        ----------\n        df: pd.DataFrame\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        amazon_transactions = df.copy()\n        amazon_transactions[\"original_name\"] = amazon_transactions[\n            \"original_name\"\n        ].fillna(\"\")\n        amazon_transactions = amazon_transactions[\n            amazon_transactions.payee.str.match(\n                r\"(?i)(Amazon|AMZN|Whole Foods)(\\s?(Prime|Marketplace|MKTP)|\\.\\w+)?\",\n                case=False,\n            )\n            | amazon_transactions.original_name.str.match(\n                r\"(?i)(Amazon|AMZN|Whole Foods)(\\s?(Prime|Marketplace|MKTP)|\\.\\w+)?\",\n                case=False,\n            )\n        ]\n        return amazon_transactions\n\n    @classmethod\n    def deduplicate_matched(cls, df: pd.DataFrame) -> pd.DataFrame:\n        \"\"\"\n        Deduplicate Multiple Connections Made\n\n        Parameters\n        ----------\n        df: pd.DataFrame\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        deduped = df.copy()\n        deduped[\"duplicated\"] = deduped.duplicated(subset=[\"id\"], keep=False)\n        deduped = deduped[deduped[\"duplicated\"] == False]  # noqa:E712\n        return deduped\n\n    @classmethod\n    def _extract_total_from_payments(cls, df: pd.DataFrame) -> pd.DataFrame:\n        \"\"\"\n        Extract the Credit Card Payments from the payments column\n\n        There is quite a bit of data manipulation going on here. We\n        need to extract meaningful credit card transaction info from strings like this:\n\n        Visa ending in 9470: September 11, 2022: $29.57; \\\n        Visa ending in 9470: September 11, 2022: $2.22;\n        \"\"\"\n        extracted = df.copy()\n        extracted[\"new_total\"] = extracted[\"payments\"].str.rstrip(\";\").str.split(\";\")\n        exploded_totals = extracted.explode(\"new_total\", ignore_index=True)\n        exploded_totals = exploded_totals[\n            exploded_totals[\"new_total\"].str.strip() != \"\"\n        ]\n        currency_matcher = r\"(?:[\\\u00a3\\$\\\u20ac]{1}[,\\d]+.?\\d*)\"\n        exploded_totals[\"parsed_total\"] = exploded_totals[\"new_total\"].str.findall(\n            currency_matcher\n        )\n        exploded_totals = exploded_totals.explode(\"parsed_total\", ignore_index=True)\n        exploded_totals[\"parsed_total\"] = exploded_totals[\"parsed_total\"].str.replace(\n            \"[^0-9.]\", \"\", regex=True\n        )\n        exploded_totals[\"parsed_total\"] = exploded_totals[\"parsed_total\"].astype(\n            np.float64\n        )\n        exploded_totals = exploded_totals[~exploded_totals[\"parsed_total\"].isnull()]\n        exploded_totals[\"total\"] = np.where(\n            ~exploded_totals[\"parsed_total\"].isnull(),\n            exploded_totals[\"parsed_total\"],\n            exploded_totals[\"total\"],\n        )\n        return exploded_totals\n\n    @classmethod\n    def _extract_extra_from_orders(cls, df: pd.DataFrame) -> pd.DataFrame:\n        \"\"\"\n        Extract the Credit Card Refunds and Whole Foods Orders\n        \"\"\"\n        refunds = df.copy()\n        refunded_data = refunds[refunds[\"refund\"] > 0].copy()\n        refunded_data[\"total\"] = -refunded_data[\"refund\"]\n        refunded_data[\"items\"] = \"REFUND: \" + refunded_data[\"items\"]\n        complete_amazon_data = pd.concat([refunds, refunded_data], ignore_index=True)\n        complete_amazon_data[\"items\"] = np.where(\n            complete_amazon_data[\"to\"].str.startswith(\"Whole Foods\"),\n            \"Whole Foods Groceries\",\n            complete_amazon_data[\"items\"],\n        )\n        return complete_amazon_data\n\n    @classmethod\n    def merge_transactions(\n        cls, amazon: pd.DataFrame, transactions: pd.DataFrame, time_range: int = 7\n    ) -> pd.DataFrame:\n        \"\"\"\n        Merge Amazon Transactions and LunchMoney Transaction\n\n        Parameters\n        ----------\n        amazon: pd.DataFrame\n        transactions: pd.DataFrame\n        time_range: int\n            Number of days used to connect credit card transactions with\n            Amazon transactions\n\n        Returns\n        -------\n        pd.DataFrame\n        \"\"\"\n        exploded_totals = cls._extract_total_from_payments(df=amazon)\n        complete_amazon_data = cls._extract_extra_from_orders(df=exploded_totals)\n        merged_data = transactions.copy()\n        merged_data = merged_data.merge(\n            complete_amazon_data,\n            how=\"inner\",\n            left_on=[\"amount\"],\n            right_on=[\"total\"],\n            suffixes=(None, \"_amazon\"),\n        )\n        merged_data[\"start_date\"] = merged_data[\"date_amazon\"]\n        merged_data[\"end_date\"] = merged_data[\"date_amazon\"] + datetime.timedelta(\n            days=time_range\n        )\n        merged_data.query(\n            \"start_date <= date <= end_date\",\n            inplace=True,\n        )\n        merged_data[\"notes\"] = merged_data[\"items\"]\n        deduplicated = cls.deduplicate_matched(df=merged_data)\n        logger.info(\"%s Matching Amazon Transactions Identified\", len(deduplicated))\n        return deduplicated[TransactionObject.__fields__.keys()]\n\n    def cache_transactions(\n        self, start_date: datetime.date, end_date: datetime.date\n    ) -> dict[int, TransactionObject]:\n        \"\"\"\n        Cache Transactions to Memory\n\n        Parameters\n        ----------\n        start_date : datetime.date\n        end_date : datetime.date\n\n        Returns\n        -------\n        Dict[int, TransactionObject]\n        \"\"\"\n        end_cache_date = end_date + datetime.timedelta(days=self.time_window)\n        logger.info(\n            \"Fetching LunchMoney transactions between %s and %s\",\n            start_date,\n            end_cache_date,\n        )\n        self.get_latest_cache(include=[CategoriesObject, UserObject])\n        self.refresh_transactions(start_date=start_date, end_date=end_cache_date)\n        logger.info(\n            'Scanning LunchMoney Budget: \"%s\"',\n            html.unescape(self.lunch_data.user.budget_name),\n        )\n        logger.info(\n            \"%s transactions returned from LunchMoney\",\n            len(self.lunch_data.transactions),\n        )\n        return self.lunch_data.transactions\n\n    def print_transaction(\n        self, transaction: TransactionObject, former_transaction: TransactionObject\n    ) -> None:\n        \"\"\"\n        Print a Transaction for interactive input\n        \"\"\"\n        transaction_table = table.Table(show_header=False)\n        notes_table = table.Table(show_header=False)\n        transaction_table.add_row(\"\ud83d\uded2 Transaction ID\", str(former_transaction.id))\n        transaction_table.add_row(\"\ud83c\udfe6 Payee\", former_transaction.payee)\n        transaction_table.add_row(\"\ud83d\udcc5 Date\", str(former_transaction.date))\n        transaction_table.add_row(\n            \"\ud83d\udcb0 Amount\", self.format_currency(amount=former_transaction.amount)\n        )\n        if former_transaction.category_id is not None:\n            transaction_table.add_row(\n                \"\ud83d\udcca Category\",\n                self.lunch_data.categories[former_transaction.category_id].name,\n            )\n        if (\n            former_transaction.original_name is not None\n            and former_transaction.original_name != former_transaction.payee\n        ):\n            transaction_table.add_row(\n                \"\ud83c\udfe6 Original Payee\", former_transaction.original_name\n            )\n        if former_transaction.notes is not None:\n            transaction_table.add_row(\"\ud83d\udcdd Notes\", former_transaction.notes)\n        notes_table.add_row(\n            \"\ud83d\uddd2  Amazon Notes\",\n            transaction.notes.strip(),  # type: ignore[union-attr]\n        )\n        print()\n        print(transaction_table)\n        print(notes_table)\n\n    def update_transaction(\n        self, transaction: TransactionObject, confirm: bool = True\n    ) -> Optional[dict[str, Any]]:\n        \"\"\"\n        Update a Transaction's Notes if they've changed\n\n        Parameters\n        ----------\n        transaction: TransactionObject\n        confirm: bool\n\n        Returns\n        -------\n        Optional[Dict[str, Any]]\n        \"\"\"\n        former_transaction = self.lunch_data.transactions[transaction.id]\n        response = None\n        stripped_notes = transaction.notes.strip()  # type: ignore[union-attr]\n        acceptable_length = min(349, len(stripped_notes))\n        new_notes = stripped_notes[:acceptable_length]\n        if former_transaction.notes != new_notes:\n            confirmation = True\n            if confirm is True:\n                self.print_transaction(\n                    transaction=transaction, former_transaction=former_transaction\n                )\n                confirmation = Confirm.ask(\n                    f\"\\t\u2753 Should we update transaction #{transaction.id}?\"\n                )\n            if confirmation is True:\n                response = self.lunch.update_transaction(\n                    transaction_id=transaction.id,\n                    transaction=TransactionUpdateObject(notes=new_notes),\n                )\n                if confirm is True:\n                    print(f\"\\t\u2705 Transaction #{transaction.id} updated\")\n        return response\n\n    def process_transactions(self, confirm: bool = True) -> None:\n        \"\"\"\n        Run the End-to-End Process\n        \"\"\"\n        logger.info(\n            \"Beginning search to match Amazon and LunchMoney - using %s day window\",\n            self.time_window,\n        )\n        amazon_df = self.amazon_to_df()\n        min_date = amazon_df[\"date\"].min().to_pydatetime().date()\n        max_date = amazon_df[\"date\"].max().to_pydatetime().date()\n        logger.info(\n            \"%s Amazon transactions loaded ranging from %s to %s\",\n            len(amazon_df),\n            min_date,\n            max_date,\n        )\n        self.cache_transactions(start_date=min_date, end_date=max_date)\n        transaction_df = self.models_to_df(\n            models=self.lunch_data.transactions.values(),\n        )\n        amazon_transaction_df = self.filter_amazon_transactions(df=transaction_df)\n        merged_data = self.merge_transactions(\n            transactions=amazon_transaction_df,\n            amazon=amazon_df,\n            time_range=self.time_window,\n        )\n        updated_transactions = self.df_to_models(\n            df=merged_data, model_type=TransactionObject\n        )\n        responses = []\n        for item in updated_transactions:\n            resp = self.update_transaction(transaction=item, confirm=confirm)\n            if resp is not None:\n                responses.append(resp)\n        logger.info(\"%s LunchMoney transactions updated\", len(responses))\n\n    @staticmethod\n    def format_currency(amount: float) -> str:\n        \"\"\"\n        Format currency amounts to be pleasant and human readable\n\n        Parameters\n        ----------\n        amount: float\n            Float Amount to be converted into a string\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if amount < 0:\n            float_string = f\"[bold red]$ ({float(abs(amount)):,.2f})[/bold red]\"\n        else:\n            float_string = f\"[bold green]$ {float(amount):,.2f}[/bold green]\"\n        return float_string\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.__init__","title":"__init__(file_path, time_window=7, access_token=None)","text":"

Initialize and set internal data

Source code in lunchable/plugins/primelunch/primelunch.py
def __init__(\n    self,\n    file_path: Union[str, os.PathLike[str], pathlib.Path],\n    time_window: int = 7,\n    access_token: Optional[str] = None,\n) -> None:\n    \"\"\"\n    Initialize and set internal data\n    \"\"\"\n    super().__init__(cache_time=0, access_token=access_token)\n    self.file_path = pathlib.Path(file_path)\n    self.time_window = time_window\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.amazon_to_df","title":"amazon_to_df()","text":"

Read an Amazon Data File to a DataFrame

This is pretty simple, except duplicate header rows need to be cleaned

Returns:

Type Description DataFrame Source code in lunchable/plugins/primelunch/primelunch.py
def amazon_to_df(self) -> pd.DataFrame:\n    \"\"\"\n    Read an Amazon Data File to a DataFrame\n\n    This is pretty simple, except duplicate header rows need to be cleaned\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    dt64: np.dtype[datetime64] = np.dtype(\"datetime64[ns]\")\n    expected_columns = {\n        \"order id\": str,\n        \"items\": str,\n        \"to\": str,\n        \"date\": dt64,\n        \"total\": np.float64,\n        \"shipping\": np.float64,\n        \"gift\": np.float64,\n        \"refund\": np.float64,\n        \"payments\": str,\n    }\n    amazon_df = pd.read_csv(\n        self.file_path,\n        usecols=expected_columns.keys(),\n    )\n    header_row_eval = pd.concat(\n        [amazon_df[item] == item for item in expected_columns.keys()], axis=1\n    ).all(axis=1)\n    duplicate_header_rows = np.where(header_row_eval)[0]\n    amazon_df.drop(duplicate_header_rows, axis=0, inplace=True)\n    amazon_df[\"total\"] = (\n        amazon_df[\"total\"].astype(\"string\").str.replace(\",\", \"\").astype(np.float64)\n    )\n    amazon_df = amazon_df.astype(dtype=expected_columns, copy=True, errors=\"raise\")\n    logger.info(\"Amazon Data File loaded: %s\", self.file_path)\n    return amazon_df\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.cache_transactions","title":"cache_transactions(start_date, end_date)","text":"

Cache Transactions to Memory

Parameters:

Name Type Description Default start_date date required end_date date required

Returns:

Type Description Dict[int, TransactionObject] Source code in lunchable/plugins/primelunch/primelunch.py
def cache_transactions(\n    self, start_date: datetime.date, end_date: datetime.date\n) -> dict[int, TransactionObject]:\n    \"\"\"\n    Cache Transactions to Memory\n\n    Parameters\n    ----------\n    start_date : datetime.date\n    end_date : datetime.date\n\n    Returns\n    -------\n    Dict[int, TransactionObject]\n    \"\"\"\n    end_cache_date = end_date + datetime.timedelta(days=self.time_window)\n    logger.info(\n        \"Fetching LunchMoney transactions between %s and %s\",\n        start_date,\n        end_cache_date,\n    )\n    self.get_latest_cache(include=[CategoriesObject, UserObject])\n    self.refresh_transactions(start_date=start_date, end_date=end_cache_date)\n    logger.info(\n        'Scanning LunchMoney Budget: \"%s\"',\n        html.unescape(self.lunch_data.user.budget_name),\n    )\n    logger.info(\n        \"%s transactions returned from LunchMoney\",\n        len(self.lunch_data.transactions),\n    )\n    return self.lunch_data.transactions\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.deduplicate_matched","title":"deduplicate_matched(df) classmethod","text":"

Deduplicate Multiple Connections Made

Parameters:

Name Type Description Default df DataFrame required

Returns:

Type Description DataFrame Source code in lunchable/plugins/primelunch/primelunch.py
@classmethod\ndef deduplicate_matched(cls, df: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Deduplicate Multiple Connections Made\n\n    Parameters\n    ----------\n    df: pd.DataFrame\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    deduped = df.copy()\n    deduped[\"duplicated\"] = deduped.duplicated(subset=[\"id\"], keep=False)\n    deduped = deduped[deduped[\"duplicated\"] == False]  # noqa:E712\n    return deduped\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.filter_amazon_transactions","title":"filter_amazon_transactions(df) classmethod","text":"

Filter a DataFrame to Amazon Transactions

Parameters:

Name Type Description Default df DataFrame required

Returns:

Type Description DataFrame Source code in lunchable/plugins/primelunch/primelunch.py
@classmethod\ndef filter_amazon_transactions(cls, df: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Filter a DataFrame to Amazon Transactions\n\n    Parameters\n    ----------\n    df: pd.DataFrame\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    amazon_transactions = df.copy()\n    amazon_transactions[\"original_name\"] = amazon_transactions[\n        \"original_name\"\n    ].fillna(\"\")\n    amazon_transactions = amazon_transactions[\n        amazon_transactions.payee.str.match(\n            r\"(?i)(Amazon|AMZN|Whole Foods)(\\s?(Prime|Marketplace|MKTP)|\\.\\w+)?\",\n            case=False,\n        )\n        | amazon_transactions.original_name.str.match(\n            r\"(?i)(Amazon|AMZN|Whole Foods)(\\s?(Prime|Marketplace|MKTP)|\\.\\w+)?\",\n            case=False,\n        )\n    ]\n    return amazon_transactions\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.format_currency","title":"format_currency(amount) staticmethod","text":"

Format currency amounts to be pleasant and human readable

Parameters:

Name Type Description Default amount float

Float Amount to be converted into a string

required

Returns:

Type Description str Source code in lunchable/plugins/primelunch/primelunch.py
@staticmethod\ndef format_currency(amount: float) -> str:\n    \"\"\"\n    Format currency amounts to be pleasant and human readable\n\n    Parameters\n    ----------\n    amount: float\n        Float Amount to be converted into a string\n\n    Returns\n    -------\n    str\n    \"\"\"\n    if amount < 0:\n        float_string = f\"[bold red]$ ({float(abs(amount)):,.2f})[/bold red]\"\n    else:\n        float_string = f\"[bold green]$ {float(amount):,.2f}[/bold green]\"\n    return float_string\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.merge_transactions","title":"merge_transactions(amazon, transactions, time_range=7) classmethod","text":"

Merge Amazon Transactions and LunchMoney Transaction

Parameters:

Name Type Description Default amazon DataFrame required transactions DataFrame required time_range int

Number of days used to connect credit card transactions with Amazon transactions

7

Returns:

Type Description DataFrame Source code in lunchable/plugins/primelunch/primelunch.py
@classmethod\ndef merge_transactions(\n    cls, amazon: pd.DataFrame, transactions: pd.DataFrame, time_range: int = 7\n) -> pd.DataFrame:\n    \"\"\"\n    Merge Amazon Transactions and LunchMoney Transaction\n\n    Parameters\n    ----------\n    amazon: pd.DataFrame\n    transactions: pd.DataFrame\n    time_range: int\n        Number of days used to connect credit card transactions with\n        Amazon transactions\n\n    Returns\n    -------\n    pd.DataFrame\n    \"\"\"\n    exploded_totals = cls._extract_total_from_payments(df=amazon)\n    complete_amazon_data = cls._extract_extra_from_orders(df=exploded_totals)\n    merged_data = transactions.copy()\n    merged_data = merged_data.merge(\n        complete_amazon_data,\n        how=\"inner\",\n        left_on=[\"amount\"],\n        right_on=[\"total\"],\n        suffixes=(None, \"_amazon\"),\n    )\n    merged_data[\"start_date\"] = merged_data[\"date_amazon\"]\n    merged_data[\"end_date\"] = merged_data[\"date_amazon\"] + datetime.timedelta(\n        days=time_range\n    )\n    merged_data.query(\n        \"start_date <= date <= end_date\",\n        inplace=True,\n    )\n    merged_data[\"notes\"] = merged_data[\"items\"]\n    deduplicated = cls.deduplicate_matched(df=merged_data)\n    logger.info(\"%s Matching Amazon Transactions Identified\", len(deduplicated))\n    return deduplicated[TransactionObject.__fields__.keys()]\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.print_transaction","title":"print_transaction(transaction, former_transaction)","text":"

Print a Transaction for interactive input

Source code in lunchable/plugins/primelunch/primelunch.py
def print_transaction(\n    self, transaction: TransactionObject, former_transaction: TransactionObject\n) -> None:\n    \"\"\"\n    Print a Transaction for interactive input\n    \"\"\"\n    transaction_table = table.Table(show_header=False)\n    notes_table = table.Table(show_header=False)\n    transaction_table.add_row(\"\ud83d\uded2 Transaction ID\", str(former_transaction.id))\n    transaction_table.add_row(\"\ud83c\udfe6 Payee\", former_transaction.payee)\n    transaction_table.add_row(\"\ud83d\udcc5 Date\", str(former_transaction.date))\n    transaction_table.add_row(\n        \"\ud83d\udcb0 Amount\", self.format_currency(amount=former_transaction.amount)\n    )\n    if former_transaction.category_id is not None:\n        transaction_table.add_row(\n            \"\ud83d\udcca Category\",\n            self.lunch_data.categories[former_transaction.category_id].name,\n        )\n    if (\n        former_transaction.original_name is not None\n        and former_transaction.original_name != former_transaction.payee\n    ):\n        transaction_table.add_row(\n            \"\ud83c\udfe6 Original Payee\", former_transaction.original_name\n        )\n    if former_transaction.notes is not None:\n        transaction_table.add_row(\"\ud83d\udcdd Notes\", former_transaction.notes)\n    notes_table.add_row(\n        \"\ud83d\uddd2  Amazon Notes\",\n        transaction.notes.strip(),  # type: ignore[union-attr]\n    )\n    print()\n    print(transaction_table)\n    print(notes_table)\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.process_transactions","title":"process_transactions(confirm=True)","text":"

Run the End-to-End Process

Source code in lunchable/plugins/primelunch/primelunch.py
def process_transactions(self, confirm: bool = True) -> None:\n    \"\"\"\n    Run the End-to-End Process\n    \"\"\"\n    logger.info(\n        \"Beginning search to match Amazon and LunchMoney - using %s day window\",\n        self.time_window,\n    )\n    amazon_df = self.amazon_to_df()\n    min_date = amazon_df[\"date\"].min().to_pydatetime().date()\n    max_date = amazon_df[\"date\"].max().to_pydatetime().date()\n    logger.info(\n        \"%s Amazon transactions loaded ranging from %s to %s\",\n        len(amazon_df),\n        min_date,\n        max_date,\n    )\n    self.cache_transactions(start_date=min_date, end_date=max_date)\n    transaction_df = self.models_to_df(\n        models=self.lunch_data.transactions.values(),\n    )\n    amazon_transaction_df = self.filter_amazon_transactions(df=transaction_df)\n    merged_data = self.merge_transactions(\n        transactions=amazon_transaction_df,\n        amazon=amazon_df,\n        time_range=self.time_window,\n    )\n    updated_transactions = self.df_to_models(\n        df=merged_data, model_type=TransactionObject\n    )\n    responses = []\n    for item in updated_transactions:\n        resp = self.update_transaction(transaction=item, confirm=confirm)\n        if resp is not None:\n            responses.append(resp)\n    logger.info(\"%s LunchMoney transactions updated\", len(responses))\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.PrimeLunch.update_transaction","title":"update_transaction(transaction, confirm=True)","text":"

Update a Transaction's Notes if they've changed

Parameters:

Name Type Description Default transaction TransactionObject required confirm bool True

Returns:

Type Description Optional[Dict[str, Any]] Source code in lunchable/plugins/primelunch/primelunch.py
def update_transaction(\n    self, transaction: TransactionObject, confirm: bool = True\n) -> Optional[dict[str, Any]]:\n    \"\"\"\n    Update a Transaction's Notes if they've changed\n\n    Parameters\n    ----------\n    transaction: TransactionObject\n    confirm: bool\n\n    Returns\n    -------\n    Optional[Dict[str, Any]]\n    \"\"\"\n    former_transaction = self.lunch_data.transactions[transaction.id]\n    response = None\n    stripped_notes = transaction.notes.strip()  # type: ignore[union-attr]\n    acceptable_length = min(349, len(stripped_notes))\n    new_notes = stripped_notes[:acceptable_length]\n    if former_transaction.notes != new_notes:\n        confirmation = True\n        if confirm is True:\n            self.print_transaction(\n                transaction=transaction, former_transaction=former_transaction\n            )\n            confirmation = Confirm.ask(\n                f\"\\t\u2753 Should we update transaction #{transaction.id}?\"\n            )\n        if confirmation is True:\n            response = self.lunch.update_transaction(\n                transaction_id=transaction.id,\n                transaction=TransactionUpdateObject(notes=new_notes),\n            )\n            if confirm is True:\n                print(f\"\\t\u2705 Transaction #{transaction.id} updated\")\n    return response\n
"},{"location":"reference/plugins/primelunch/primelunch/#lunchable.plugins.primelunch.primelunch.run_primelunch","title":"run_primelunch(csv_file, window, update_all, access_token)","text":"

Run the PrimeLunch Update Process

Source code in lunchable/plugins/primelunch/primelunch.py
@click.command(\"run\")\n@click.option(\n    \"-f\",\n    \"--file\",\n    \"csv_file\",\n    type=click.Path(exists=True, resolve_path=True),\n    help=\"File Path of the Amazon Export\",\n    required=True,\n)\n@click.option(\n    \"-w\",\n    \"--window\",\n    \"window\",\n    type=click.INT,\n    help=\"Allowable time window between Amazon transaction date and \"\n    \"credit card transaction date\",\n    default=7,\n)\n@click.option(\n    \"-a\",\n    \"--all\",\n    \"update_all\",\n    is_flag=True,\n    type=click.BOOL,\n    help=\"Whether to skip the confirmation step and simply update all matched \"\n    \"transactions\",\n    default=False,\n)\n@click.option(\n    \"-t\",\n    \"--token\",\n    \"access_token\",\n    type=click.STRING,\n    help=\"LunchMoney Access Token - defaults to the LUNCHMONEY_ACCESS_TOKEN environment variable\",\n    envvar=\"LUNCHMONEY_ACCESS_TOKEN\",\n)\ndef run_primelunch(\n    csv_file: str, window: int, update_all: bool, access_token: str\n) -> None:\n    \"\"\"\n    Run the PrimeLunch Update Process\n    \"\"\"\n    primelunch = PrimeLunch(\n        file_path=csv_file, time_window=window, access_token=access_token\n    )\n    primelunch.process_transactions(confirm=not update_all)\n
"},{"location":"reference/plugins/pushlunch/","title":"pushlunch","text":"

PushLunch lunchable Plugin

"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch","title":"PushLunch","text":"

Bases: LunchableApp

Lunch Money Pushover Notifications via Lunchable

Source code in lunchable/plugins/pushlunch/pushover.py
class PushLunch(LunchableApp):\n    \"\"\"\n    Lunch Money Pushover Notifications via Lunchable\n    \"\"\"\n\n    pushover_endpoint = \"https://api.pushover.net/1/messages.json\"\n\n    def __init__(\n        self,\n        user_key: Optional[str] = None,\n        app_token: Optional[str] = None,\n        lunchmoney_access_token: Optional[str] = None,\n    ):\n        \"\"\"\n        Initialize\n\n        Parameters\n        ----------\n        user_key : Optional[str]\n            Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n            variable if none defined\n        app_token: Optional[str]\n            Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n            variable. If no token available, the official lunchable app token will be provided\n        lunchmoney_access_token: Optional[str]\n            LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable.\n        \"\"\"\n        super().__init__(access_token=lunchmoney_access_token)\n        self.pushover_session = httpx.Client()\n        self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n        _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n        if app_token is None:\n            app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n        token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n        user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n        if user_key in [None, \"\"]:\n            raise PushLunchError(\n                \"You must provide a Pushover User Key or define it with \"\n                \"a `PUSHOVER_USER_KEY` environment variable\"\n            )\n        self._params = {\"user\": user_key, \"token\": token}\n        self.get_latest_cache(\n            include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n        )\n        self.notified_transactions: List[int] = []\n\n    def send_notification(\n        self,\n        message: str,\n        attachment: Optional[object] = None,\n        device: Optional[str] = None,\n        title: Optional[str] = None,\n        url: Optional[str] = None,\n        url_title: Optional[str] = None,\n        priority: Optional[int] = None,\n        sound: Optional[str] = None,\n        timestamp: Optional[str] = None,\n        html: bool = False,\n    ) -> httpx.Response:\n        \"\"\"\n        Send a Pushover Notification\n\n        Parameters\n        ----------\n        message: Optional[str]\n            your message\n        attachment: Optional[object]\n            an image attachment to send with the message; see attachments for more information\n            on how to upload files\n        device: Optional[str]\n            your user's device name to send the message directly to that device, rather than\n            all of the user's devices (multiple devices may be separated by a comma)\n        title: Optional[str]\n            your message's title, otherwise your app's name is used\n        url: Optional[str]\n            a supplementary URL to show with your message\n        url_title: Optional[str]\n            a title for your supplementary URL, otherwise just the URL is shown\n        priority: Optional[int]\n            send as -2 to generate no notification/alert, -1 to always send as a quiet\n            notification, 1 to display as high-priority and bypass the user's quiet hours,\n            or 2 to also require confirmation from the user\n        sound: Optional[str]\n            the name of one of the sounds supported by device clients to override the\n            user's default sound choice\n        timestamp: Optional[str]\n            a Unix timestamp of your message's date and time to display to the user, rather\n            than the time your message is received by our API\n        html: Union[None, 1]\n            Pass 1 if message contains HTML contents\n\n        Returns\n        -------\n        httpx.Response\n        \"\"\"\n        html_param = 1 if html not in [None, False] else None\n        params_dict = {\n            \"message\": message,\n            \"attachment\": attachment,\n            \"device\": device,\n            \"title\": title,\n            \"url\": url,\n            \"url_title\": url_title,\n            \"priority\": priority,\n            \"sound\": sound,\n            \"timestamp\": timestamp,\n            \"html\": html_param,\n        }\n        params: Dict[str, Any] = {\n            key: value for key, value in params_dict.items() if value is not None\n        }\n        params.update(self._params)\n        response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n        response.raise_for_status()\n        return response\n\n    def post_transaction(\n        self, transaction: TransactionObject\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"\n        Post a Lunch Money Transaction as a Pushover Notification\n\n        Assuming the instance of the\n        class hasn't already posted this particular notification\n\n        Parameters\n        ----------\n        transaction: TransactionObject\n\n        Returns\n        -------\n        Dict[str, Any]\n        \"\"\"\n        if transaction.id in self.notified_transactions:\n            return None\n        if transaction.category_id is None:\n            category = \"N/A\"\n        else:\n            category = self.lunch_data.categories[transaction.category_id].name\n        account_id = transaction.plaid_account_id or transaction.asset_id\n        assert account_id is not None\n        account = self.lunch_data.asset_map[account_id]\n        if isinstance(account, AssetsObject):\n            account_name = account.display_name or account.name\n        else:\n            account_name = account.name\n        transaction_formatted = dedent(\n            f\"\"\"\n        <b>Payee:</b> <i>{transaction.payee}</i>\n        <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n        <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n        <b>Category:</b> <i>{category}</i>\n        <b>Account:</b> <i>{account_name}</i>\n        \"\"\"\n        ).strip()\n        if transaction.currency is not None:\n            transaction_formatted += (\n                f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n            )\n        if transaction.status is not None:\n            transaction_formatted += (\n                f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n            )\n        if transaction.notes is not None:\n            note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n            transaction_formatted += f\"\\n{note}\"\n        if transaction.status == \"uncleared\":\n            url = (\n                '<a href=\"https://my.lunchmoney.app/transactions/'\n                f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n                \"<b>Uncleared Transactions from this Period</b></a>\"\n            )\n            transaction_formatted += f\"\\n\\n{url}\"\n\n        response = self.send_notification(\n            message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n        )\n        self.notified_transactions.append(transaction.id)\n        return loads(response.content)\n\n    @classmethod\n    def _format_float(cls, amount: float) -> str:\n        \"\"\"\n        Format Floats to be pleasant and human readable\n\n        Parameters\n        ----------\n        amount: float\n            Float Amount to be converted into a string\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if amount < 0:\n            float_string = f\"$ ({float(amount):,.2f})\".replace(\"-\", \"\")\n        else:\n            float_string = f\"$ {float(amount):,.2f}\"\n        return float_string\n\n    def notify_uncleared_transactions(\n        self, continuous: bool = False, interval: Optional[int] = None\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n        Parameters\n        ----------\n        continuous : bool\n            Whether to continuously check for more uncleared transactions,\n            waiting a fixed amount in between checks.\n        interval: Optional[int]\n            Sleep Interval in Between Tries - only applies if `continuous` is set.\n            Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if interval is None:\n            interval = 60\n        if continuous is True and interval < 5:\n            logger.warning(\n                \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n            )\n            interval = 5\n        if continuous is True:\n            logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n        uncleared_transactions = []\n        continuous_search = True\n\n        while continuous_search is True:\n            found_transactions = len(self.notified_transactions)\n            uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n            for transaction in uncleared_transactions:\n                self.post_transaction(transaction=transaction)\n            if continuous is True:\n                notified = len(self.notified_transactions)\n                new_transactions = notified - found_transactions\n                logger.info(\n                    \"%s new transactions pushed. %s total.\", new_transactions, notified\n                )\n                sleep(interval * 60)\n            else:\n                continuous_search = False\n\n        return uncleared_transactions\n
"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.__init__","title":"__init__(user_key=None, app_token=None, lunchmoney_access_token=None)","text":"

Initialize

Parameters:

Name Type Description Default user_key Optional[str]

Pushover User Key. Will attempt to inherit from PUSHOVER_USER_KEY environment variable if none defined

None app_token Optional[str]

Pushover app token, will attempt to inherit from PUSHOVER_APP_TOKEN environment variable. If no token available, the official lunchable app token will be provided

None lunchmoney_access_token Optional[str]

LunchMoney Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable.

None Source code in lunchable/plugins/pushlunch/pushover.py
def __init__(\n    self,\n    user_key: Optional[str] = None,\n    app_token: Optional[str] = None,\n    lunchmoney_access_token: Optional[str] = None,\n):\n    \"\"\"\n    Initialize\n\n    Parameters\n    ----------\n    user_key : Optional[str]\n        Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n        variable if none defined\n    app_token: Optional[str]\n        Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n        variable. If no token available, the official lunchable app token will be provided\n    lunchmoney_access_token: Optional[str]\n        LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable.\n    \"\"\"\n    super().__init__(access_token=lunchmoney_access_token)\n    self.pushover_session = httpx.Client()\n    self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n    _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n    if app_token is None:\n        app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n    token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n    user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n    if user_key in [None, \"\"]:\n        raise PushLunchError(\n            \"You must provide a Pushover User Key or define it with \"\n            \"a `PUSHOVER_USER_KEY` environment variable\"\n        )\n    self._params = {\"user\": user_key, \"token\": token}\n    self.get_latest_cache(\n        include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n    )\n    self.notified_transactions: List[int] = []\n
"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.notify_uncleared_transactions","title":"notify_uncleared_transactions(continuous=False, interval=None)","text":"

Get the Current Period's Uncleared Transactions and Send a Notification for each

Parameters:

Name Type Description Default continuous bool

Whether to continuously check for more uncleared transactions, waiting a fixed amount in between checks.

False interval Optional[int]

Sleep Interval in Between Tries - only applies if continuous is set. Defaults to 60 (minutes). Cannot be less than 5 (minutes)

None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/pushlunch/pushover.py
def notify_uncleared_transactions(\n    self, continuous: bool = False, interval: Optional[int] = None\n) -> List[TransactionObject]:\n    \"\"\"\n    Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n    Parameters\n    ----------\n    continuous : bool\n        Whether to continuously check for more uncleared transactions,\n        waiting a fixed amount in between checks.\n    interval: Optional[int]\n        Sleep Interval in Between Tries - only applies if `continuous` is set.\n        Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if interval is None:\n        interval = 60\n    if continuous is True and interval < 5:\n        logger.warning(\n            \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n        )\n        interval = 5\n    if continuous is True:\n        logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n    uncleared_transactions = []\n    continuous_search = True\n\n    while continuous_search is True:\n        found_transactions = len(self.notified_transactions)\n        uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n        for transaction in uncleared_transactions:\n            self.post_transaction(transaction=transaction)\n        if continuous is True:\n            notified = len(self.notified_transactions)\n            new_transactions = notified - found_transactions\n            logger.info(\n                \"%s new transactions pushed. %s total.\", new_transactions, notified\n            )\n            sleep(interval * 60)\n        else:\n            continuous_search = False\n\n    return uncleared_transactions\n
"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.post_transaction","title":"post_transaction(transaction)","text":"

Post a Lunch Money Transaction as a Pushover Notification

Assuming the instance of the class hasn't already posted this particular notification

Parameters:

Name Type Description Default transaction TransactionObject required

Returns:

Type Description Dict[str, Any] Source code in lunchable/plugins/pushlunch/pushover.py
def post_transaction(\n    self, transaction: TransactionObject\n) -> Optional[Dict[str, Any]]:\n    \"\"\"\n    Post a Lunch Money Transaction as a Pushover Notification\n\n    Assuming the instance of the\n    class hasn't already posted this particular notification\n\n    Parameters\n    ----------\n    transaction: TransactionObject\n\n    Returns\n    -------\n    Dict[str, Any]\n    \"\"\"\n    if transaction.id in self.notified_transactions:\n        return None\n    if transaction.category_id is None:\n        category = \"N/A\"\n    else:\n        category = self.lunch_data.categories[transaction.category_id].name\n    account_id = transaction.plaid_account_id or transaction.asset_id\n    assert account_id is not None\n    account = self.lunch_data.asset_map[account_id]\n    if isinstance(account, AssetsObject):\n        account_name = account.display_name or account.name\n    else:\n        account_name = account.name\n    transaction_formatted = dedent(\n        f\"\"\"\n    <b>Payee:</b> <i>{transaction.payee}</i>\n    <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n    <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n    <b>Category:</b> <i>{category}</i>\n    <b>Account:</b> <i>{account_name}</i>\n    \"\"\"\n    ).strip()\n    if transaction.currency is not None:\n        transaction_formatted += (\n            f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n        )\n    if transaction.status is not None:\n        transaction_formatted += (\n            f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n        )\n    if transaction.notes is not None:\n        note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n        transaction_formatted += f\"\\n{note}\"\n    if transaction.status == \"uncleared\":\n        url = (\n            '<a href=\"https://my.lunchmoney.app/transactions/'\n            f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n            \"<b>Uncleared Transactions from this Period</b></a>\"\n        )\n        transaction_formatted += f\"\\n\\n{url}\"\n\n    response = self.send_notification(\n        message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n    )\n    self.notified_transactions.append(transaction.id)\n    return loads(response.content)\n
"},{"location":"reference/plugins/pushlunch/#lunchable.plugins.pushlunch.PushLunch.send_notification","title":"send_notification(message, attachment=None, device=None, title=None, url=None, url_title=None, priority=None, sound=None, timestamp=None, html=False)","text":"

Send a Pushover Notification

Parameters:

Name Type Description Default message str

your message

required attachment Optional[object]

an image attachment to send with the message; see attachments for more information on how to upload files

None device Optional[str]

your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma)

None title Optional[str]

your message's title, otherwise your app's name is used

None url Optional[str]

a supplementary URL to show with your message

None url_title Optional[str]

a title for your supplementary URL, otherwise just the URL is shown

None priority Optional[int]

send as -2 to generate no notification/alert, -1 to always send as a quiet notification, 1 to display as high-priority and bypass the user's quiet hours, or 2 to also require confirmation from the user

None sound Optional[str]

the name of one of the sounds supported by device clients to override the user's default sound choice

None timestamp Optional[str]

a Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by our API

None html bool

Pass 1 if message contains HTML contents

False

Returns:

Type Description Response Source code in lunchable/plugins/pushlunch/pushover.py
def send_notification(\n    self,\n    message: str,\n    attachment: Optional[object] = None,\n    device: Optional[str] = None,\n    title: Optional[str] = None,\n    url: Optional[str] = None,\n    url_title: Optional[str] = None,\n    priority: Optional[int] = None,\n    sound: Optional[str] = None,\n    timestamp: Optional[str] = None,\n    html: bool = False,\n) -> httpx.Response:\n    \"\"\"\n    Send a Pushover Notification\n\n    Parameters\n    ----------\n    message: Optional[str]\n        your message\n    attachment: Optional[object]\n        an image attachment to send with the message; see attachments for more information\n        on how to upload files\n    device: Optional[str]\n        your user's device name to send the message directly to that device, rather than\n        all of the user's devices (multiple devices may be separated by a comma)\n    title: Optional[str]\n        your message's title, otherwise your app's name is used\n    url: Optional[str]\n        a supplementary URL to show with your message\n    url_title: Optional[str]\n        a title for your supplementary URL, otherwise just the URL is shown\n    priority: Optional[int]\n        send as -2 to generate no notification/alert, -1 to always send as a quiet\n        notification, 1 to display as high-priority and bypass the user's quiet hours,\n        or 2 to also require confirmation from the user\n    sound: Optional[str]\n        the name of one of the sounds supported by device clients to override the\n        user's default sound choice\n    timestamp: Optional[str]\n        a Unix timestamp of your message's date and time to display to the user, rather\n        than the time your message is received by our API\n    html: Union[None, 1]\n        Pass 1 if message contains HTML contents\n\n    Returns\n    -------\n    httpx.Response\n    \"\"\"\n    html_param = 1 if html not in [None, False] else None\n    params_dict = {\n        \"message\": message,\n        \"attachment\": attachment,\n        \"device\": device,\n        \"title\": title,\n        \"url\": url,\n        \"url_title\": url_title,\n        \"priority\": priority,\n        \"sound\": sound,\n        \"timestamp\": timestamp,\n        \"html\": html_param,\n    }\n    params: Dict[str, Any] = {\n        key: value for key, value in params_dict.items() if value is not None\n    }\n    params.update(self._params)\n    response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n    response.raise_for_status()\n    return response\n
"},{"location":"reference/plugins/pushlunch/pushover/","title":"pushover","text":"

Pushover Notifications via lunchable

"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch","title":"PushLunch","text":"

Bases: LunchableApp

Lunch Money Pushover Notifications via Lunchable

Source code in lunchable/plugins/pushlunch/pushover.py
class PushLunch(LunchableApp):\n    \"\"\"\n    Lunch Money Pushover Notifications via Lunchable\n    \"\"\"\n\n    pushover_endpoint = \"https://api.pushover.net/1/messages.json\"\n\n    def __init__(\n        self,\n        user_key: Optional[str] = None,\n        app_token: Optional[str] = None,\n        lunchmoney_access_token: Optional[str] = None,\n    ):\n        \"\"\"\n        Initialize\n\n        Parameters\n        ----------\n        user_key : Optional[str]\n            Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n            variable if none defined\n        app_token: Optional[str]\n            Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n            variable. If no token available, the official lunchable app token will be provided\n        lunchmoney_access_token: Optional[str]\n            LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable.\n        \"\"\"\n        super().__init__(access_token=lunchmoney_access_token)\n        self.pushover_session = httpx.Client()\n        self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n        _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n        if app_token is None:\n            app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n        token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n        user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n        if user_key in [None, \"\"]:\n            raise PushLunchError(\n                \"You must provide a Pushover User Key or define it with \"\n                \"a `PUSHOVER_USER_KEY` environment variable\"\n            )\n        self._params = {\"user\": user_key, \"token\": token}\n        self.get_latest_cache(\n            include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n        )\n        self.notified_transactions: List[int] = []\n\n    def send_notification(\n        self,\n        message: str,\n        attachment: Optional[object] = None,\n        device: Optional[str] = None,\n        title: Optional[str] = None,\n        url: Optional[str] = None,\n        url_title: Optional[str] = None,\n        priority: Optional[int] = None,\n        sound: Optional[str] = None,\n        timestamp: Optional[str] = None,\n        html: bool = False,\n    ) -> httpx.Response:\n        \"\"\"\n        Send a Pushover Notification\n\n        Parameters\n        ----------\n        message: Optional[str]\n            your message\n        attachment: Optional[object]\n            an image attachment to send with the message; see attachments for more information\n            on how to upload files\n        device: Optional[str]\n            your user's device name to send the message directly to that device, rather than\n            all of the user's devices (multiple devices may be separated by a comma)\n        title: Optional[str]\n            your message's title, otherwise your app's name is used\n        url: Optional[str]\n            a supplementary URL to show with your message\n        url_title: Optional[str]\n            a title for your supplementary URL, otherwise just the URL is shown\n        priority: Optional[int]\n            send as -2 to generate no notification/alert, -1 to always send as a quiet\n            notification, 1 to display as high-priority and bypass the user's quiet hours,\n            or 2 to also require confirmation from the user\n        sound: Optional[str]\n            the name of one of the sounds supported by device clients to override the\n            user's default sound choice\n        timestamp: Optional[str]\n            a Unix timestamp of your message's date and time to display to the user, rather\n            than the time your message is received by our API\n        html: Union[None, 1]\n            Pass 1 if message contains HTML contents\n\n        Returns\n        -------\n        httpx.Response\n        \"\"\"\n        html_param = 1 if html not in [None, False] else None\n        params_dict = {\n            \"message\": message,\n            \"attachment\": attachment,\n            \"device\": device,\n            \"title\": title,\n            \"url\": url,\n            \"url_title\": url_title,\n            \"priority\": priority,\n            \"sound\": sound,\n            \"timestamp\": timestamp,\n            \"html\": html_param,\n        }\n        params: Dict[str, Any] = {\n            key: value for key, value in params_dict.items() if value is not None\n        }\n        params.update(self._params)\n        response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n        response.raise_for_status()\n        return response\n\n    def post_transaction(\n        self, transaction: TransactionObject\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"\n        Post a Lunch Money Transaction as a Pushover Notification\n\n        Assuming the instance of the\n        class hasn't already posted this particular notification\n\n        Parameters\n        ----------\n        transaction: TransactionObject\n\n        Returns\n        -------\n        Dict[str, Any]\n        \"\"\"\n        if transaction.id in self.notified_transactions:\n            return None\n        if transaction.category_id is None:\n            category = \"N/A\"\n        else:\n            category = self.lunch_data.categories[transaction.category_id].name\n        account_id = transaction.plaid_account_id or transaction.asset_id\n        assert account_id is not None\n        account = self.lunch_data.asset_map[account_id]\n        if isinstance(account, AssetsObject):\n            account_name = account.display_name or account.name\n        else:\n            account_name = account.name\n        transaction_formatted = dedent(\n            f\"\"\"\n        <b>Payee:</b> <i>{transaction.payee}</i>\n        <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n        <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n        <b>Category:</b> <i>{category}</i>\n        <b>Account:</b> <i>{account_name}</i>\n        \"\"\"\n        ).strip()\n        if transaction.currency is not None:\n            transaction_formatted += (\n                f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n            )\n        if transaction.status is not None:\n            transaction_formatted += (\n                f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n            )\n        if transaction.notes is not None:\n            note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n            transaction_formatted += f\"\\n{note}\"\n        if transaction.status == \"uncleared\":\n            url = (\n                '<a href=\"https://my.lunchmoney.app/transactions/'\n                f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n                \"<b>Uncleared Transactions from this Period</b></a>\"\n            )\n            transaction_formatted += f\"\\n\\n{url}\"\n\n        response = self.send_notification(\n            message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n        )\n        self.notified_transactions.append(transaction.id)\n        return loads(response.content)\n\n    @classmethod\n    def _format_float(cls, amount: float) -> str:\n        \"\"\"\n        Format Floats to be pleasant and human readable\n\n        Parameters\n        ----------\n        amount: float\n            Float Amount to be converted into a string\n\n        Returns\n        -------\n        str\n        \"\"\"\n        if amount < 0:\n            float_string = f\"$ ({float(amount):,.2f})\".replace(\"-\", \"\")\n        else:\n            float_string = f\"$ {float(amount):,.2f}\"\n        return float_string\n\n    def notify_uncleared_transactions(\n        self, continuous: bool = False, interval: Optional[int] = None\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n        Parameters\n        ----------\n        continuous : bool\n            Whether to continuously check for more uncleared transactions,\n            waiting a fixed amount in between checks.\n        interval: Optional[int]\n            Sleep Interval in Between Tries - only applies if `continuous` is set.\n            Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if interval is None:\n            interval = 60\n        if continuous is True and interval < 5:\n            logger.warning(\n                \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n            )\n            interval = 5\n        if continuous is True:\n            logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n        uncleared_transactions = []\n        continuous_search = True\n\n        while continuous_search is True:\n            found_transactions = len(self.notified_transactions)\n            uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n            for transaction in uncleared_transactions:\n                self.post_transaction(transaction=transaction)\n            if continuous is True:\n                notified = len(self.notified_transactions)\n                new_transactions = notified - found_transactions\n                logger.info(\n                    \"%s new transactions pushed. %s total.\", new_transactions, notified\n                )\n                sleep(interval * 60)\n            else:\n                continuous_search = False\n\n        return uncleared_transactions\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch.__init__","title":"__init__(user_key=None, app_token=None, lunchmoney_access_token=None)","text":"

Initialize

Parameters:

Name Type Description Default user_key Optional[str]

Pushover User Key. Will attempt to inherit from PUSHOVER_USER_KEY environment variable if none defined

None app_token Optional[str]

Pushover app token, will attempt to inherit from PUSHOVER_APP_TOKEN environment variable. If no token available, the official lunchable app token will be provided

None lunchmoney_access_token Optional[str]

LunchMoney Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable.

None Source code in lunchable/plugins/pushlunch/pushover.py
def __init__(\n    self,\n    user_key: Optional[str] = None,\n    app_token: Optional[str] = None,\n    lunchmoney_access_token: Optional[str] = None,\n):\n    \"\"\"\n    Initialize\n\n    Parameters\n    ----------\n    user_key : Optional[str]\n        Pushover User Key. Will attempt to inherit from `PUSHOVER_USER_KEY`  environment\n        variable if none defined\n    app_token: Optional[str]\n        Pushover app token, will attempt to inherit from `PUSHOVER_APP_TOKEN`  environment\n        variable. If no token available, the official lunchable app token will be provided\n    lunchmoney_access_token: Optional[str]\n        LunchMoney Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable.\n    \"\"\"\n    super().__init__(access_token=lunchmoney_access_token)\n    self.pushover_session = httpx.Client()\n    self.pushover_session.headers.update({\"Content-Type\": \"application/json\"})\n\n    _courtesy_token = b\"YXpwMzZ6MjExcWV5OGFvOXNicWF0cmdraXc4aGVz\"\n    if app_token is None:\n        app_token = getenv(\"PUSHOVER_APP_TOKEN\", None)\n    token = app_token or b64decode(_courtesy_token).decode(\"utf-8\")\n    user_key = user_key or getenv(\"PUSHOVER_USER_KEY\", None)\n    if user_key in [None, \"\"]:\n        raise PushLunchError(\n            \"You must provide a Pushover User Key or define it with \"\n            \"a `PUSHOVER_USER_KEY` environment variable\"\n        )\n    self._params = {\"user\": user_key, \"token\": token}\n    self.get_latest_cache(\n        include=[AssetsObject, PlaidAccountObject, CategoriesObject]\n    )\n    self.notified_transactions: List[int] = []\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch.notify_uncleared_transactions","title":"notify_uncleared_transactions(continuous=False, interval=None)","text":"

Get the Current Period's Uncleared Transactions and Send a Notification for each

Parameters:

Name Type Description Default continuous bool

Whether to continuously check for more uncleared transactions, waiting a fixed amount in between checks.

False interval Optional[int]

Sleep Interval in Between Tries - only applies if continuous is set. Defaults to 60 (minutes). Cannot be less than 5 (minutes)

None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/pushlunch/pushover.py
def notify_uncleared_transactions(\n    self, continuous: bool = False, interval: Optional[int] = None\n) -> List[TransactionObject]:\n    \"\"\"\n    Get the Current Period's Uncleared Transactions and Send a Notification for each\n\n    Parameters\n    ----------\n    continuous : bool\n        Whether to continuously check for more uncleared transactions,\n        waiting a fixed amount in between checks.\n    interval: Optional[int]\n        Sleep Interval in Between Tries - only applies if `continuous` is set.\n        Defaults to 60 (minutes). Cannot be less than 5 (minutes)\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if interval is None:\n        interval = 60\n    if continuous is True and interval < 5:\n        logger.warning(\n            \"Check interval cannot be less than 5 minutes. Defaulting to 5.\"\n        )\n        interval = 5\n    if continuous is True:\n        logger.info(\"Continuous Notifications Enabled. Beginning PushLunch.\")\n\n    uncleared_transactions = []\n    continuous_search = True\n\n    while continuous_search is True:\n        found_transactions = len(self.notified_transactions)\n        uncleared_transactions += self.lunch.get_transactions(status=\"uncleared\")\n        for transaction in uncleared_transactions:\n            self.post_transaction(transaction=transaction)\n        if continuous is True:\n            notified = len(self.notified_transactions)\n            new_transactions = notified - found_transactions\n            logger.info(\n                \"%s new transactions pushed. %s total.\", new_transactions, notified\n            )\n            sleep(interval * 60)\n        else:\n            continuous_search = False\n\n    return uncleared_transactions\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch.post_transaction","title":"post_transaction(transaction)","text":"

Post a Lunch Money Transaction as a Pushover Notification

Assuming the instance of the class hasn't already posted this particular notification

Parameters:

Name Type Description Default transaction TransactionObject required

Returns:

Type Description Dict[str, Any] Source code in lunchable/plugins/pushlunch/pushover.py
def post_transaction(\n    self, transaction: TransactionObject\n) -> Optional[Dict[str, Any]]:\n    \"\"\"\n    Post a Lunch Money Transaction as a Pushover Notification\n\n    Assuming the instance of the\n    class hasn't already posted this particular notification\n\n    Parameters\n    ----------\n    transaction: TransactionObject\n\n    Returns\n    -------\n    Dict[str, Any]\n    \"\"\"\n    if transaction.id in self.notified_transactions:\n        return None\n    if transaction.category_id is None:\n        category = \"N/A\"\n    else:\n        category = self.lunch_data.categories[transaction.category_id].name\n    account_id = transaction.plaid_account_id or transaction.asset_id\n    assert account_id is not None\n    account = self.lunch_data.asset_map[account_id]\n    if isinstance(account, AssetsObject):\n        account_name = account.display_name or account.name\n    else:\n        account_name = account.name\n    transaction_formatted = dedent(\n        f\"\"\"\n    <b>Payee:</b> <i>{transaction.payee}</i>\n    <b>Amount:</b> <i>{self._format_float(transaction.amount)}</i>\n    <b>Date:</b> <i>{transaction.date.strftime(\"%A %B %-d, %Y\")}</i>\n    <b>Category:</b> <i>{category}</i>\n    <b>Account:</b> <i>{account_name}</i>\n    \"\"\"\n    ).strip()\n    if transaction.currency is not None:\n        transaction_formatted += (\n            f\"\\n<b>Currency:</b> <i>{transaction.currency.upper()}</i>\"\n        )\n    if transaction.status is not None:\n        transaction_formatted += (\n            f\"\\n<b>Status:</b> <i>{transaction.status.title()}</i>\"\n        )\n    if transaction.notes is not None:\n        note = f\"<b>Notes:</b> <i>{transaction.notes}</i>\"\n        transaction_formatted += f\"\\n{note}\"\n    if transaction.status == \"uncleared\":\n        url = (\n            '<a href=\"https://my.lunchmoney.app/transactions/'\n            f'{transaction.date.year}/{transaction.date.strftime(\"%m\")}?status=unreviewed\">'\n            \"<b>Uncleared Transactions from this Period</b></a>\"\n        )\n        transaction_formatted += f\"\\n\\n{url}\"\n\n    response = self.send_notification(\n        message=transaction_formatted, title=\"Lunch Money Transaction\", html=True\n    )\n    self.notified_transactions.append(transaction.id)\n    return loads(response.content)\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunch.send_notification","title":"send_notification(message, attachment=None, device=None, title=None, url=None, url_title=None, priority=None, sound=None, timestamp=None, html=False)","text":"

Send a Pushover Notification

Parameters:

Name Type Description Default message str

your message

required attachment Optional[object]

an image attachment to send with the message; see attachments for more information on how to upload files

None device Optional[str]

your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma)

None title Optional[str]

your message's title, otherwise your app's name is used

None url Optional[str]

a supplementary URL to show with your message

None url_title Optional[str]

a title for your supplementary URL, otherwise just the URL is shown

None priority Optional[int]

send as -2 to generate no notification/alert, -1 to always send as a quiet notification, 1 to display as high-priority and bypass the user's quiet hours, or 2 to also require confirmation from the user

None sound Optional[str]

the name of one of the sounds supported by device clients to override the user's default sound choice

None timestamp Optional[str]

a Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by our API

None html bool

Pass 1 if message contains HTML contents

False

Returns:

Type Description Response Source code in lunchable/plugins/pushlunch/pushover.py
def send_notification(\n    self,\n    message: str,\n    attachment: Optional[object] = None,\n    device: Optional[str] = None,\n    title: Optional[str] = None,\n    url: Optional[str] = None,\n    url_title: Optional[str] = None,\n    priority: Optional[int] = None,\n    sound: Optional[str] = None,\n    timestamp: Optional[str] = None,\n    html: bool = False,\n) -> httpx.Response:\n    \"\"\"\n    Send a Pushover Notification\n\n    Parameters\n    ----------\n    message: Optional[str]\n        your message\n    attachment: Optional[object]\n        an image attachment to send with the message; see attachments for more information\n        on how to upload files\n    device: Optional[str]\n        your user's device name to send the message directly to that device, rather than\n        all of the user's devices (multiple devices may be separated by a comma)\n    title: Optional[str]\n        your message's title, otherwise your app's name is used\n    url: Optional[str]\n        a supplementary URL to show with your message\n    url_title: Optional[str]\n        a title for your supplementary URL, otherwise just the URL is shown\n    priority: Optional[int]\n        send as -2 to generate no notification/alert, -1 to always send as a quiet\n        notification, 1 to display as high-priority and bypass the user's quiet hours,\n        or 2 to also require confirmation from the user\n    sound: Optional[str]\n        the name of one of the sounds supported by device clients to override the\n        user's default sound choice\n    timestamp: Optional[str]\n        a Unix timestamp of your message's date and time to display to the user, rather\n        than the time your message is received by our API\n    html: Union[None, 1]\n        Pass 1 if message contains HTML contents\n\n    Returns\n    -------\n    httpx.Response\n    \"\"\"\n    html_param = 1 if html not in [None, False] else None\n    params_dict = {\n        \"message\": message,\n        \"attachment\": attachment,\n        \"device\": device,\n        \"title\": title,\n        \"url\": url,\n        \"url_title\": url_title,\n        \"priority\": priority,\n        \"sound\": sound,\n        \"timestamp\": timestamp,\n        \"html\": html_param,\n    }\n    params: Dict[str, Any] = {\n        key: value for key, value in params_dict.items() if value is not None\n    }\n    params.update(self._params)\n    response = self.pushover_session.post(url=self.pushover_endpoint, params=params)\n    response.raise_for_status()\n    return response\n
"},{"location":"reference/plugins/pushlunch/pushover/#lunchable.plugins.pushlunch.pushover.PushLunchError","title":"PushLunchError","text":"

Bases: Exception

PushLunch Exception

Source code in lunchable/plugins/pushlunch/pushover.py
class PushLunchError(Exception):\n    \"\"\"\n    PushLunch Exception\n    \"\"\"\n\n    pass\n
"},{"location":"reference/plugins/splitlunch/","title":"splitlunch","text":"

Splitwise Plugin for Lunchmoney

"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch","title":"SplitLunch","text":"

Bases: Splitwise

Lunchable Plugin For Interacting With Splitwise

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
class SplitLunch(splitwise.Splitwise):\n    \"\"\"\n    Lunchable Plugin For Interacting With Splitwise\n    \"\"\"\n\n    def __init__(\n        self,\n        lunch_money_access_token: Optional[str] = None,\n        financial_partner_id: Optional[int] = None,\n        financial_partner_email: Optional[str] = None,\n        financial_partner_group_id: Optional[int] = None,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n        lunchable_client: Optional[LunchMoney] = None,\n    ):\n        \"\"\"\n        Initialize the Parent Class with some additional properties\n\n        Parameters\n        ----------\n        financial_partner_id: Optional[int]\n            Splitwise User ID of financial partner\n        financial_partner_email: Optional[str]\n            Splitwise linked email address of financial partner\n        financial_partner_group_id: Optional[int]\n            Splitwise Group ID for financial partner transactions\n        consumer_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n            variable\n        consumer_secret: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n            environment variable\n        api_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n            variable.\n        lunch_money_access_token: Optional[str]\n            Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable if not provided.\n        lunchable_client: LunchMoney\n            Instantiated LunchMoney object to use as internal client. One will\n            be created using environment variables otherwise.\n        \"\"\"\n        init_kwargs = self._get_splitwise_init_kwargs(\n            consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n        )\n        super(SplitLunch, self).__init__(**init_kwargs)\n        self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n        self.financial_partner: splitwise.Friend = self.get_friend(\n            friend_id=financial_partner_id, email_address=financial_partner_email\n        )\n        self.financial_group = financial_partner_group_id\n        self.last_check: Optional[datetime.datetime] = None\n        self.lunchable = (\n            LunchMoney(access_token=lunch_money_access_token)\n            if lunchable_client is None\n            else lunchable_client\n        )\n        self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n        self.splitwise_tag = self._none_tag.model_copy()\n        self.splitlunch_tag = self._none_tag.model_copy()\n        self.splitlunch_import_tag = self._none_tag.model_copy()\n        self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n        self._get_splitwise_tags()\n        self.earliest_start_date = datetime.date(1812, 1, 1)\n        today = datetime.date.today()\n        self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n        self.splitwise_asset = self._get_splitwise_asset()\n        self.reimbursement_category = self._get_reimbursement_category()\n\n    def __repr__(self) -> str:\n        \"\"\"\n        String Representation\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return f\"<Splitwise: {self.current_user.email}>\"\n\n    @classmethod\n    def _split_amount(cls, amount: float, splits: int) -> Tuple[float, ...]:\n        \"\"\"\n        Split a money amount into fair shares\n\n        Parameters\n        ----------\n        amount: float\n        splits: int\n\n        Returns\n        -------\n        Tuple[float]\n        \"\"\"\n        try:\n            assert amount == round(amount, 2)\n        except AssertionError as ae:\n            raise SplitLunchError(\n                f\"{amount} caused an error, you must provide a real \" \"spending amount.\"\n            ) from ae\n        equal_shares = round(amount, 2) / splits\n        remainder_dollars = floor(equal_shares)\n        remainder_cents = floor((equal_shares - remainder_dollars) * 100) / 100\n        remainder_left = round(\n            (equal_shares - remainder_dollars - remainder_cents) * splits * 100, 0\n        )\n        owed_amount = remainder_dollars + remainder_cents\n        return_amounts = [owed_amount for _ in range(splits)]\n        for i in range(int(remainder_left)):\n            return_amounts[i] += 0.010\n        shuffle(return_amounts)\n        return tuple([round(item, 2) for item in return_amounts])\n\n    @classmethod\n    def split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n        \"\"\"\n        Split a Transaction into Two\n\n        Split a bill into a tuple of two amounts (and take care\n        of the extra penny if needed)\n\n        Parameters\n        ----------\n        amount: A Currency amount (no more precise than cents)\n\n        Returns\n        -------\n        tuple\n            A tuple is returned with each participant's amount\n        \"\"\"\n        amounts_due = cls._split_amount(amount=amount, splits=2)\n        return amounts_due\n\n    def create_self_paid_expense(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        primary_user_owes, financial_partner_owes = self.split_a_transaction(\n            amount=amount\n        )\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=primary_user_owes)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=financial_partner_owes)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def create_expense_on_behalf_of_partner(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n        This expense will be completely owed by the partner and maked as reimbursed.\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=0.00)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=amount)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def get_friend(\n        self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n    ) -> Optional[splitwise.Friend]:\n        \"\"\"\n        Retrieve a Financial Partner by Email Address\n\n        Parameters\n        ----------\n        email_address: str\n            Email Address of Friend's user in Splitwise\n        friend_id: Optional[int]\n            Splitwise friend ID. Notice the friend ID in the following\n            URL: https://secure.splitwise.com/#/friends/12345678\n\n        Returns\n        -------\n        Optional[splitwise.Friend]\n        \"\"\"\n        friend_list: List[splitwise.Friend] = self.getFriends()\n        if len(friend_list) == 1:\n            return friend_list[0]\n        for friend in friend_list:\n            if friend_id is not None and friend.id == friend_id:\n                return friend\n            elif (\n                email_address is not None\n                and friend.email.lower() == email_address.lower()\n            ):\n                return friend\n        return None\n\n    def get_expenses(\n        self,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        group_id: Optional[int] = None,\n        friendship_id: Optional[int] = None,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        updated_after: Optional[datetime.datetime] = None,\n        updated_before: Optional[datetime.datetime] = None,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Get Splitwise Expenses\n\n        Parameters\n        ----------\n        offset: Optional[int]\n            Number of expenses to be skipped\n        limit: Optional[int]\n            Number of expenses to be returned\n        group_id: Optional[int]\n            GroupID of the expenses\n        friendship_id: Optional[int]\n            FriendshipID of the expenses\n        dated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses later that this date\n        dated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses earlier than this date\n        updated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated after this date\n        updated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated before this date\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        expenses = self.getExpenses(\n            offset=offset,\n            limit=limit,\n            group_id=group_id,\n            friendship_id=friendship_id,\n            dated_after=dated_after,\n            dated_before=dated_before,\n            updated_after=updated_after,\n            updated_before=updated_before,\n        )\n        pydantic_expenses = [\n            self.splitwise_to_pydantic(expense) for expense in expenses\n        ]\n        return pydantic_expenses\n\n    @classmethod\n    def _get_splitwise_init_kwargs(\n        cls,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Get the Splitwise Kwargs\n\n        Parameters\n        ----------\n        consumer_key: Optional[str]\n        consumer_secret: Optional[str]\n        api_key: Optional[str]\n        \"\"\"\n        if consumer_key is None:\n            consumer_key = getenv(\"SPLITWISE_CONSUMER_KEY\")\n        if consumer_secret is None:\n            consumer_secret = getenv(\"SPLITWISE_CONSUMER_SECRET\")\n        if api_key is None:\n            api_key = getenv(\"SPLITWISE_API_KEY\", None)\n        init_kwargs = {\n            \"consumer_key\": consumer_key,\n            \"consumer_secret\": consumer_secret,\n            \"api_key\": api_key,\n        }\n        if consumer_key is None or consumer_secret is None or api_key is None:\n            error_message = (\n                dedent(\n                    \"\"\"\n            You must set your Splitwise credentials explicitly or by assigning\n            the `SPLITWISE_CONSUMER_KEY`, `SPLITWISE_CONSUMER_SECRET`, and the\n            `SPLITWISE_API_KEY`environment variables\n            \"\"\"\n                )\n                .replace(\"\\n\", \" \")\n                .replace(\"  \", \" \")\n            )\n            logger.error(error_message)\n            raise SplitLunchError(error_message)\n        return init_kwargs\n\n    def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n        \"\"\"\n        Convert Splitwise Object to Pydantic\n\n        Parameters\n        ----------\n        expense: splitwise.Expense\n\n        Returns\n        -------\n        SplitLunchExpense\n        \"\"\"\n        financial_impact, self_paid = _get_splitwise_impact(\n            expense=expense, current_user_id=self.current_user.id\n        )\n        expense = SplitLunchExpense(\n            splitwise_id=expense.id,\n            original_amount=expense.cost,\n            financial_impact=financial_impact,\n            self_paid=self_paid,\n            description=expense.description,\n            category=expense.category.name,\n            details=expense.details,\n            payment=expense.payment,\n            date=expense.date,\n            users=[user.id for user in expense.users],\n            created_at=expense.created_at,\n            updated_at=expense.updated_at,\n            deleted_at=expense.deleted_at,\n            deleted=True if expense.deleted_at is not None else False,\n        )\n        return expense\n\n    def _get_splitwise_asset(self) -> Optional[AssetsObject]:\n        \"\"\"\n        Get the Splitwise asset\n\n        Parse a user's Lunch Money accounts and return the manually managed\n        Splitwise account asset object\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        assets = self.lunchable.get_assets()\n        splitwise_assets = []\n        for asset in assets:\n            if (\n                asset.institution_name is not None\n                and \"splitwise\" in asset.institution_name.lower()\n            ):\n                splitwise_assets.append(asset)\n        if len(splitwise_assets) == 0:\n            return None\n        elif len(splitwise_assets) > 1:\n            raise SplitLunchError(\n                \"SplitLunch requires an manually managed Splitwise asset. \"\n                \"Make sure you have a single account where 'Splitwise' \"\n                \"is in the asset's `Institution Name`.\"\n            )\n        else:\n            return splitwise_assets[0]\n\n    def _get_reimbursement_category(self) -> Optional[CategoriesObject]:\n        \"\"\"\n        Get the Reimbusement Category\n\n        Parse a user's Lunch Money categories and return the Reimbursement\n        category\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        categories = self.lunchable.get_categories()\n        reimbursement_list = []\n        for category in categories:\n            if \"reimbursement\" == category.name.strip().lower():\n                reimbursement_list.append(category)\n        if len(reimbursement_list) != 1:\n            return None\n        return reimbursement_list[0]\n\n    def _get_splitwise_tags(self) -> None:\n        \"\"\"\n        Get Lunch Money Tags to Interact with\n\n        Returns\n        -------\n        Dict[str, int]\n        \"\"\"\n        all_tags = self.lunchable.get_tags()\n        for tag in all_tags:\n            if tag.name.lower() == SplitLunchConfig.splitlunch_tag.lower():\n                self.splitlunch_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitwise_tag.lower():\n                self.splitwise_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitlunch_import_tag.lower():\n                self.splitlunch_import_tag = tag\n            elif (\n                tag.name.lower()\n                == SplitLunchConfig.splitlunch_direct_import_tag.lower()\n            ):\n                self.splitlunch_direct_import_tag = tag\n\n    def _raise_nonexistent_tag_error(self, tags: List[str]) -> None:\n        \"\"\"\n        Raise a warning for specific SplitLunch Tags\n\n        tags: List[str]\n            A list of tags to raise the error for\n        \"\"\"\n        if (\n            self.splitlunch_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitwise_tag == self._none_tag\n            and SplitLunchConfig.splitwise_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitwise_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_import_tag}` tag is required. \"\n                f\"This tag is used for creating Splitwise transactions directly from \"\n                f\"Lunch Money transactions. These transactions will be split in half,\"\n                f\"and have one half marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_direct_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_direct_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_direct_import_tag}` tag is \"\n                \"required. This tag is used for creating Splitwise transactions \"\n                \"directly from Lunch Money transactions. These transactions will \"\n                \"be completely owed by your financial partner.\"\n            )\n            raise SplitLunchError(error_message)\n\n    def get_splitlunch_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitlunch\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def get_splitlunch_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitlunch_direct_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(\n            tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n        )\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_direct_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitwise_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitwise\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n        \"\"\"\n        Operate on `SplitLunch` tagged transactions\n\n        Split all transactions with the `SplitLunch` tag in half. One of these\n        new splits will be recategorized to `Reimbursement`. Both new splits will receive\n        the `Splitwise` tag without any preexisting tags.\n        \"\"\"\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        split_transaction_ids = []\n        tagged_objects = self.get_splitlunch_tagged_transactions()\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n            # Generate the First Split\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=transaction.notes,\n                amount=amount_1,\n            )\n            # Generate the second split as a copy, change some properties\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = amount_2\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                \"Splitting transaction: %s -> (%s, %s)\",\n                transaction.amount,\n                amount_1,\n                amount_2,\n            )\n\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            # Tag each of the new transactions generated\n            split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags if transaction.tags is not None else []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag is not None\n                    and tag.name.lower() != self.splitlunch_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return split_transaction_ids\n\n    def make_splitlunch_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchImport` tagged transactions\n\n        Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n        One of these new splits will be recategorized to `Reimbursement`. Both new splits\n        will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n        reapplied.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_self_paid_expense(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=notes,\n                # financial_impact for self-paid transactions will be negative\n                amount=round(\n                    transaction.amount - abs(new_transaction.financial_impact), 2\n                ),\n            )\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = abs(new_transaction.financial_impact)\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                f\"Transaction split by Splitwise: {transaction.amount} -> \"\n                f\"({split_object.amount}, {reimbursement_object.amount})\"\n            )\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": reimbursement_object.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": update_response[\"updated\"],\n                \"split\": update_response[\"split\"],\n            }\n            update_responses.append(formatted_update_response)\n            # Tag each of the new transactions generated\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags or []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return update_responses\n\n    def make_splitlunch_direct_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchDirectImport` tagged transactions\n\n        Send a transaction to Splitwise and then mark the transaction under the\n        `Reimbursement` category. The sum of the transaction will be completely owed\n        by the financial partner.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_expense_on_behalf_of_partner(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            existing_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in existing_tags\n                if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n                tags.append(self.splitwise_tag.name)\n            update = TransactionUpdateObject(\n                category_id=self.reimbursement_category.id, tags=tags, notes=notes\n            )\n            response = self.lunchable.update_transaction(\n                transaction_id=transaction.id, transaction=update\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": transaction.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": response[\"updated\"],\n                \"split\": None,\n            }\n            update_responses.append(formatted_update_response)\n        return update_responses\n\n    def splitwise_to_lunchmoney(\n        self,\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[int]:\n        \"\"\"\n        Ingest Splitwise Expenses into Lunch Money\n\n        This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n        a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n        payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[int]\n            New Lunch Money transaction IDs\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        batch = []\n        new_transaction_ids = []\n        filtered_expenses = self.filter_relevant_splitwise_expenses(\n            expenses=expenses,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        for splitwise_transaction in filtered_expenses:\n            new_lunchmoney_transaction = TransactionInsertObject(\n                date=splitwise_transaction.date.astimezone(tzlocal()),\n                payee=splitwise_transaction.description,\n                amount=splitwise_transaction.financial_impact,\n                asset_id=self.splitwise_asset.id,\n                external_id=splitwise_transaction.splitwise_id,\n            )\n            batch.append(new_lunchmoney_transaction)\n            if len(batch) == 10:\n                new_ids = self.lunchable.insert_transactions(\n                    transactions=batch, apply_rules=True\n                )\n                new_transaction_ids += new_ids\n                batch = []\n        if len(batch) > 0:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n        return new_transaction_ids\n\n    @staticmethod\n    def filter_relevant_splitwise_expenses(\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Filter Expenses in Splitwise into relevant expenses.\n\n        This filtering action is important to understand when seeing why not\n        all transactions from Splitwise end up flowing into Lunch Money.\n\n        1) It filters out deleted expenses\n\n        2) It filters out expenses with a financial impact of 0, implying that the user was not\n        involved in the expense.\n\n        3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n        `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        filtered_expenses = []\n        for splitwise_transaction in expenses:\n            if all(\n                [\n                    splitwise_transaction.deleted is False,\n                    splitwise_transaction.financial_impact != 0.00,\n                    allow_self_paid or (splitwise_transaction.self_paid is False),\n                    allow_payments or (splitwise_transaction.payment is False),\n                ]\n            ):\n                filtered_expenses.append(splitwise_transaction)\n\n        return filtered_expenses\n\n    def get_splitwise_balance(self) -> float:\n        \"\"\"\n        Get the net balance in Splitwise\n\n        Returns\n        -------\n        float\n        \"\"\"\n        groups = self.getGroups()\n        total_balance = 0.00\n        for group in groups:\n            for debt in group.simplified_debts:\n                if debt.fromUser == self.current_user.id:\n                    total_balance -= float(debt.amount)\n                elif debt.toUser == self.current_user.id:\n                    total_balance += float(debt.amount)\n        return total_balance\n\n    def update_splitwise_balance(self) -> AssetsObject:\n        \"\"\"\n        Get and update the Splitwise Asset in Lunch Money\n\n        Returns\n        -------\n        AssetsObject\n            Updated  balance\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        balance = self.get_splitwise_balance()\n        if balance != self.splitwise_asset.balance:\n            updated_asset = self.lunchable.update_asset(\n                asset_id=self.splitwise_asset.id, balance=balance\n            )\n            self.splitwise_asset = updated_asset\n        return self.splitwise_asset\n\n    _deleted_payee = \"[DELETED FROM SPLITWISE]\"\n\n    def get_new_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n    ) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n        \"\"\"\n        Get Splitwise Transactions that don't exist in Lunch Money\n\n        Also return deleted transaction from LunchMoney\n\n        Returns\n        -------\n        Tuple[List[SplitLunchExpense], List[TransactionObject]]\n            New and Deleted Transactions\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        splitlunch_expenses = self.lunchable.get_transactions(\n            asset_id=self.splitwise_asset.id,\n            start_date=datetime.datetime(1800, 1, 1),\n            end_date=datetime.datetime(2300, 12, 31),\n        )\n        splitlunch_ids = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        splitwise_expenses = self.get_expenses(\n            limit=0,\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n        new_ids = splitwise_ids.difference(splitlunch_ids)\n        new_expenses = [\n            expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n        ]\n        deleted_transactions = self.get_deleted_transactions(\n            splitlunch_expenses=splitlunch_expenses,\n            splitwise_transactions=splitwise_expenses,\n        )\n        return new_expenses, deleted_transactions\n\n    def get_deleted_transactions(\n        self,\n        splitlunch_expenses: List[TransactionObject],\n        splitwise_transactions: List[SplitLunchExpense],\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n        Set these transactions to $0.00 and Make a Note\n\n        Parameters\n        ----------\n        splitlunch_expenses: List[TransactionObject]\n        splitwise_transactions: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        existing_transactions = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        deleted_ids = {\n            item.splitwise_id for item in splitwise_transactions if item.deleted is True\n        }\n        untethered_transactions = deleted_ids.intersection(existing_transactions)\n        transactions_to_delete = [\n            tran\n            for tran in splitlunch_expenses\n            if tran.external_id is not None\n            and int(tran.external_id) in untethered_transactions\n            and tran.payee != self._deleted_payee\n        ]\n        return transactions_to_delete\n\n    def refresh_splitwise_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Import New Splitwise Transactions to Lunch Money\n\n        This function get's all transactions from Splitwise, all transactions from\n        your Lunch Money Splitwise account and compares the two.\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        new_transactions, deleted_transactions = self.get_new_transactions(\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        self.splitwise_to_lunchmoney(\n            expenses=new_transactions,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        splitwise_asset = self.update_splitwise_balance()\n        self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n        return {\n            \"balance\": splitwise_asset.balance,\n            \"new\": new_transactions,\n            \"deleted\": deleted_transactions,\n        }\n\n    def handle_deleted_transactions(\n        self,\n        deleted_transactions: List[TransactionObject],\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n        Parameters\n        ----------\n        deleted_transactions: List[TransactionObject]\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        updated_transactions = []\n        for transaction in deleted_transactions:\n            update = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                transaction=TransactionUpdateObject(\n                    amount=0.00,\n                    payee=self._deleted_payee,\n                    notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n                ),\n            )\n            updated_transactions.append(update)\n        return updated_transactions\n\n    def _raise_financial_partner_error(self) -> None:\n        \"\"\"\n        Raise Errors for Financial Partners\n        \"\"\"\n        if self.financial_partner is None:\n            raise SplitLunchError(\n                \"You must designate a financial partner in Splitwise. \"\n                \"This can be done with the partner's Splitwise User ID # \"\n                \"or their email address.\"\n            )\n\n    def _raise_splitwise_asset_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"You must create an asset (aka Account) in Lunch Money with \"\n            \"`Splitwise` in the name. There should only be one account \"\n            \"like this.\"\n        )\n\n    def _raise_category_reimbursement_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"SplitLunch requires a reimbursement Category. \"\n            \"Make sure you have a category entitled `Reimbursement`. \"\n            \"This category will be excluded from budgeting.\"\n            \"Half of split transactions will be created with \"\n            \"this category.\"\n        )\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.__init__","title":"__init__(lunch_money_access_token=None, financial_partner_id=None, financial_partner_email=None, financial_partner_group_id=None, consumer_key=None, consumer_secret=None, api_key=None, lunchable_client=None)","text":"

Initialize the Parent Class with some additional properties

Parameters:

Name Type Description Default financial_partner_id Optional[int]

Splitwise User ID of financial partner

None financial_partner_email Optional[str]

Splitwise linked email address of financial partner

None financial_partner_group_id Optional[int]

Splitwise Group ID for financial partner transactions

None consumer_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_KEY environment variable

None consumer_secret Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_SECRET environment variable

None api_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_API_KEY environment variable.

None lunch_money_access_token Optional[str]

Lunch Money Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable if not provided.

None lunchable_client Optional[LunchMoney]

Instantiated LunchMoney object to use as internal client. One will be created using environment variables otherwise.

None Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __init__(\n    self,\n    lunch_money_access_token: Optional[str] = None,\n    financial_partner_id: Optional[int] = None,\n    financial_partner_email: Optional[str] = None,\n    financial_partner_group_id: Optional[int] = None,\n    consumer_key: Optional[str] = None,\n    consumer_secret: Optional[str] = None,\n    api_key: Optional[str] = None,\n    lunchable_client: Optional[LunchMoney] = None,\n):\n    \"\"\"\n    Initialize the Parent Class with some additional properties\n\n    Parameters\n    ----------\n    financial_partner_id: Optional[int]\n        Splitwise User ID of financial partner\n    financial_partner_email: Optional[str]\n        Splitwise linked email address of financial partner\n    financial_partner_group_id: Optional[int]\n        Splitwise Group ID for financial partner transactions\n    consumer_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n        variable\n    consumer_secret: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n        environment variable\n    api_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n        variable.\n    lunch_money_access_token: Optional[str]\n        Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable if not provided.\n    lunchable_client: LunchMoney\n        Instantiated LunchMoney object to use as internal client. One will\n        be created using environment variables otherwise.\n    \"\"\"\n    init_kwargs = self._get_splitwise_init_kwargs(\n        consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n    )\n    super(SplitLunch, self).__init__(**init_kwargs)\n    self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n    self.financial_partner: splitwise.Friend = self.get_friend(\n        friend_id=financial_partner_id, email_address=financial_partner_email\n    )\n    self.financial_group = financial_partner_group_id\n    self.last_check: Optional[datetime.datetime] = None\n    self.lunchable = (\n        LunchMoney(access_token=lunch_money_access_token)\n        if lunchable_client is None\n        else lunchable_client\n    )\n    self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n    self.splitwise_tag = self._none_tag.model_copy()\n    self.splitlunch_tag = self._none_tag.model_copy()\n    self.splitlunch_import_tag = self._none_tag.model_copy()\n    self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n    self._get_splitwise_tags()\n    self.earliest_start_date = datetime.date(1812, 1, 1)\n    today = datetime.date.today()\n    self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n    self.splitwise_asset = self._get_splitwise_asset()\n    self.reimbursement_category = self._get_reimbursement_category()\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __repr__(self) -> str:\n    \"\"\"\n    String Representation\n\n    Returns\n    -------\n    str\n    \"\"\"\n    return f\"<Splitwise: {self.current_user.email}>\"\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.create_expense_on_behalf_of_partner","title":"create_expense_on_behalf_of_partner(amount, description, date)","text":"

Create and Submit a Splitwise Expense on behalf of your financial partner.

This expense will be completely owed by the partner and maked as reimbursed.

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_expense_on_behalf_of_partner(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n    This expense will be completely owed by the partner and maked as reimbursed.\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=0.00)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=amount)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.create_self_paid_expense","title":"create_self_paid_expense(amount, description, date)","text":"

Create and Submit a Splitwise Expense

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_self_paid_expense(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    primary_user_owes, financial_partner_owes = self.split_a_transaction(\n        amount=amount\n    )\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=primary_user_owes)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=financial_partner_owes)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.filter_relevant_splitwise_expenses","title":"filter_relevant_splitwise_expenses(expenses, allow_self_paid=False, allow_payments=False) staticmethod","text":"

Filter Expenses in Splitwise into relevant expenses.

This filtering action is important to understand when seeing why not all transactions from Splitwise end up flowing into Lunch Money.

1) It filters out deleted expenses

2) It filters out expenses with a financial impact of 0, implying that the user was not involved in the expense.

3) If the --allow-self-paid flag is not provided, it filters out self-paid expenses. A self-paid expense is an expense in Splitwise where you originated the payment. This is excluded because it is assumed that these transactions will have already been imported via a different account.

4) If the --allow-payments flag is not provided, it filters out payments. Payments are excluded because it is assumed that these transactions will have already been imported via a different account.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@staticmethod\ndef filter_relevant_splitwise_expenses(\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Filter Expenses in Splitwise into relevant expenses.\n\n    This filtering action is important to understand when seeing why not\n    all transactions from Splitwise end up flowing into Lunch Money.\n\n    1) It filters out deleted expenses\n\n    2) It filters out expenses with a financial impact of 0, implying that the user was not\n    involved in the expense.\n\n    3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n    `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    filtered_expenses = []\n    for splitwise_transaction in expenses:\n        if all(\n            [\n                splitwise_transaction.deleted is False,\n                splitwise_transaction.financial_impact != 0.00,\n                allow_self_paid or (splitwise_transaction.self_paid is False),\n                allow_payments or (splitwise_transaction.payment is False),\n            ]\n        ):\n            filtered_expenses.append(splitwise_transaction)\n\n    return filtered_expenses\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_deleted_transactions","title":"get_deleted_transactions(splitlunch_expenses, splitwise_transactions)","text":"

Get Splitwise Transactions that exist in Lunch Money but have since been deleted

Set these transactions to $0.00 and Make a Note

Parameters:

Name Type Description Default splitlunch_expenses List[TransactionObject] required splitwise_transactions List[SplitLunchExpense] required

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_deleted_transactions(\n    self,\n    splitlunch_expenses: List[TransactionObject],\n    splitwise_transactions: List[SplitLunchExpense],\n) -> List[TransactionObject]:\n    \"\"\"\n    Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n    Set these transactions to $0.00 and Make a Note\n\n    Parameters\n    ----------\n    splitlunch_expenses: List[TransactionObject]\n    splitwise_transactions: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    existing_transactions = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    deleted_ids = {\n        item.splitwise_id for item in splitwise_transactions if item.deleted is True\n    }\n    untethered_transactions = deleted_ids.intersection(existing_transactions)\n    transactions_to_delete = [\n        tran\n        for tran in splitlunch_expenses\n        if tran.external_id is not None\n        and int(tran.external_id) in untethered_transactions\n        and tran.payee != self._deleted_payee\n    ]\n    return transactions_to_delete\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_expenses","title":"get_expenses(offset=None, limit=None, group_id=None, friendship_id=None, dated_after=None, dated_before=None, updated_after=None, updated_before=None)","text":"

Get Splitwise Expenses

Parameters:

Name Type Description Default offset Optional[int]

Number of expenses to be skipped

None limit Optional[int]

Number of expenses to be returned

None group_id Optional[int]

GroupID of the expenses

None friendship_id Optional[int]

FriendshipID of the expenses

None dated_after Optional[datetime]

ISO 8601 Date time. Return expenses later that this date

None dated_before Optional[datetime]

ISO 8601 Date time. Return expenses earlier than this date

None updated_after Optional[datetime]

ISO 8601 Date time. Return expenses updated after this date

None updated_before Optional[datetime]

ISO 8601 Date time. Return expenses updated before this date

None

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_expenses(\n    self,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    group_id: Optional[int] = None,\n    friendship_id: Optional[int] = None,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    updated_after: Optional[datetime.datetime] = None,\n    updated_before: Optional[datetime.datetime] = None,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Get Splitwise Expenses\n\n    Parameters\n    ----------\n    offset: Optional[int]\n        Number of expenses to be skipped\n    limit: Optional[int]\n        Number of expenses to be returned\n    group_id: Optional[int]\n        GroupID of the expenses\n    friendship_id: Optional[int]\n        FriendshipID of the expenses\n    dated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses later that this date\n    dated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses earlier than this date\n    updated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated after this date\n    updated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated before this date\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    expenses = self.getExpenses(\n        offset=offset,\n        limit=limit,\n        group_id=group_id,\n        friendship_id=friendship_id,\n        dated_after=dated_after,\n        dated_before=dated_before,\n        updated_after=updated_after,\n        updated_before=updated_before,\n    )\n    pydantic_expenses = [\n        self.splitwise_to_pydantic(expense) for expense in expenses\n    ]\n    return pydantic_expenses\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_friend","title":"get_friend(email_address=None, friend_id=None)","text":"

Retrieve a Financial Partner by Email Address

Parameters:

Name Type Description Default email_address Optional[str]

Email Address of Friend's user in Splitwise

None friend_id Optional[int]

Splitwise friend ID. Notice the friend ID in the following URL: https://secure.splitwise.com/#/friends/12345678

None

Returns:

Type Description Optional[Friend] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_friend(\n    self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n) -> Optional[splitwise.Friend]:\n    \"\"\"\n    Retrieve a Financial Partner by Email Address\n\n    Parameters\n    ----------\n    email_address: str\n        Email Address of Friend's user in Splitwise\n    friend_id: Optional[int]\n        Splitwise friend ID. Notice the friend ID in the following\n        URL: https://secure.splitwise.com/#/friends/12345678\n\n    Returns\n    -------\n    Optional[splitwise.Friend]\n    \"\"\"\n    friend_list: List[splitwise.Friend] = self.getFriends()\n    if len(friend_list) == 1:\n        return friend_list[0]\n    for friend in friend_list:\n        if friend_id is not None and friend.id == friend_id:\n            return friend\n        elif (\n            email_address is not None\n            and friend.email.lower() == email_address.lower()\n        ):\n            return friend\n    return None\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_new_transactions","title":"get_new_transactions(dated_after=None, dated_before=None)","text":"

Get Splitwise Transactions that don't exist in Lunch Money

Also return deleted transaction from LunchMoney

Returns:

Type Description Tuple[List[SplitLunchExpense], List[TransactionObject]]

New and Deleted Transactions

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_new_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n    \"\"\"\n    Get Splitwise Transactions that don't exist in Lunch Money\n\n    Also return deleted transaction from LunchMoney\n\n    Returns\n    -------\n    Tuple[List[SplitLunchExpense], List[TransactionObject]]\n        New and Deleted Transactions\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    splitlunch_expenses = self.lunchable.get_transactions(\n        asset_id=self.splitwise_asset.id,\n        start_date=datetime.datetime(1800, 1, 1),\n        end_date=datetime.datetime(2300, 12, 31),\n    )\n    splitlunch_ids = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    splitwise_expenses = self.get_expenses(\n        limit=0,\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n    new_ids = splitwise_ids.difference(splitlunch_ids)\n    new_expenses = [\n        expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n    ]\n    deleted_transactions = self.get_deleted_transactions(\n        splitlunch_expenses=splitlunch_expenses,\n        splitwise_transactions=splitwise_expenses,\n    )\n    return new_expenses, deleted_transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_direct_import_tagged_transactions","title":"get_splitlunch_direct_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchDirectImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_direct_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(\n        tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n    )\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_direct_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_import_tagged_transactions","title":"get_splitlunch_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitlunch_tagged_transactions","title":"get_splitlunch_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitlunch\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitlunch\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitwise_balance","title":"get_splitwise_balance()","text":"

Get the net balance in Splitwise

Returns:

Type Description float Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_balance(self) -> float:\n    \"\"\"\n    Get the net balance in Splitwise\n\n    Returns\n    -------\n    float\n    \"\"\"\n    groups = self.getGroups()\n    total_balance = 0.00\n    for group in groups:\n        for debt in group.simplified_debts:\n            if debt.fromUser == self.current_user.id:\n                total_balance -= float(debt.amount)\n            elif debt.toUser == self.current_user.id:\n                total_balance += float(debt.amount)\n    return total_balance\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.get_splitwise_tagged_transactions","title":"get_splitwise_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitwise\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitwise\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.handle_deleted_transactions","title":"handle_deleted_transactions(deleted_transactions)","text":"

Update Transactions That Exist in Splitwise, but have been deleted in Splitwise

Parameters:

Name Type Description Default deleted_transactions List[TransactionObject] required

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def handle_deleted_transactions(\n    self,\n    deleted_transactions: List[TransactionObject],\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n    Parameters\n    ----------\n    deleted_transactions: List[TransactionObject]\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    updated_transactions = []\n    for transaction in deleted_transactions:\n        update = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            transaction=TransactionUpdateObject(\n                amount=0.00,\n                payee=self._deleted_payee,\n                notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n            ),\n        )\n        updated_transactions.append(update)\n    return updated_transactions\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch","title":"make_splitlunch(tag_transactions=False)","text":"

Operate on SplitLunch tagged transactions

Split all transactions with the SplitLunch tag in half. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without any preexisting tags.

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n    \"\"\"\n    Operate on `SplitLunch` tagged transactions\n\n    Split all transactions with the `SplitLunch` tag in half. One of these\n    new splits will be recategorized to `Reimbursement`. Both new splits will receive\n    the `Splitwise` tag without any preexisting tags.\n    \"\"\"\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    split_transaction_ids = []\n    tagged_objects = self.get_splitlunch_tagged_transactions()\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n        # Generate the First Split\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=transaction.notes,\n            amount=amount_1,\n        )\n        # Generate the second split as a copy, change some properties\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = amount_2\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            \"Splitting transaction: %s -> (%s, %s)\",\n            transaction.amount,\n            amount_1,\n            amount_2,\n        )\n\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        # Tag each of the new transactions generated\n        split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags if transaction.tags is not None else []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag is not None\n                and tag.name.lower() != self.splitlunch_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return split_transaction_ids\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch_direct_import","title":"make_splitlunch_direct_import(tag_transactions=False)","text":"

Operate on SplitLunchDirectImport tagged transactions

Send a transaction to Splitwise and then mark the transaction under the Reimbursement category. The sum of the transaction will be completely owed by the financial partner.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_direct_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchDirectImport` tagged transactions\n\n    Send a transaction to Splitwise and then mark the transaction under the\n    `Reimbursement` category. The sum of the transaction will be completely owed\n    by the financial partner.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_expense_on_behalf_of_partner(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        existing_tags = transaction.tags or []\n        tags = [\n            tag.name\n            for tag in existing_tags\n            if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n        ]\n        if self.splitwise_tag.name not in tags and tag_transactions is True:\n            self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n            tags.append(self.splitwise_tag.name)\n        update = TransactionUpdateObject(\n            category_id=self.reimbursement_category.id, tags=tags, notes=notes\n        )\n        response = self.lunchable.update_transaction(\n            transaction_id=transaction.id, transaction=update\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": transaction.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": response[\"updated\"],\n            \"split\": None,\n        }\n        update_responses.append(formatted_update_response)\n    return update_responses\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.make_splitlunch_import","title":"make_splitlunch_import(tag_transactions=False)","text":"

Operate on SplitLunchImport tagged transactions

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without the SplitLunchImport tag. Any other tags will be reapplied.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchImport` tagged transactions\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Both new splits\n    will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n    reapplied.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_self_paid_expense(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=notes,\n            # financial_impact for self-paid transactions will be negative\n            amount=round(\n                transaction.amount - abs(new_transaction.financial_impact), 2\n            ),\n        )\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = abs(new_transaction.financial_impact)\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            f\"Transaction split by Splitwise: {transaction.amount} -> \"\n            f\"({split_object.amount}, {reimbursement_object.amount})\"\n        )\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": reimbursement_object.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": update_response[\"updated\"],\n            \"split\": update_response[\"split\"],\n        }\n        update_responses.append(formatted_update_response)\n        # Tag each of the new transactions generated\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return update_responses\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.refresh_splitwise_transactions","title":"refresh_splitwise_transactions(dated_after=None, dated_before=None, allow_self_paid=False, allow_payments=False)","text":"

Import New Splitwise Transactions to Lunch Money

This function get's all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two.

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def refresh_splitwise_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> Dict[str, Any]:\n    \"\"\"\n    Import New Splitwise Transactions to Lunch Money\n\n    This function get's all transactions from Splitwise, all transactions from\n    your Lunch Money Splitwise account and compares the two.\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    new_transactions, deleted_transactions = self.get_new_transactions(\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    self.splitwise_to_lunchmoney(\n        expenses=new_transactions,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    splitwise_asset = self.update_splitwise_balance()\n    self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n    return {\n        \"balance\": splitwise_asset.balance,\n        \"new\": new_transactions,\n        \"deleted\": deleted_transactions,\n    }\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.split_a_transaction","title":"split_a_transaction(amount) classmethod","text":"

Split a Transaction into Two

Split a bill into a tuple of two amounts (and take care of the extra penny if needed)

Parameters:

Name Type Description Default amount Union[float, int] required

Returns:

Type Description tuple

A tuple is returned with each participant's amount

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@classmethod\ndef split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n    \"\"\"\n    Split a Transaction into Two\n\n    Split a bill into a tuple of two amounts (and take care\n    of the extra penny if needed)\n\n    Parameters\n    ----------\n    amount: A Currency amount (no more precise than cents)\n\n    Returns\n    -------\n    tuple\n        A tuple is returned with each participant's amount\n    \"\"\"\n    amounts_due = cls._split_amount(amount=amount, splits=2)\n    return amounts_due\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.splitwise_to_lunchmoney","title":"splitwise_to_lunchmoney(expenses, allow_self_paid=False, allow_payments=False)","text":"

Ingest Splitwise Expenses into Lunch Money

This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default, payments and self-paid transactions are also ineligible. Otherwise it will be ignored.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[int]

New Lunch Money transaction IDs

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_lunchmoney(\n    self,\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[int]:\n    \"\"\"\n    Ingest Splitwise Expenses into Lunch Money\n\n    This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n    a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n    payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[int]\n        New Lunch Money transaction IDs\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    batch = []\n    new_transaction_ids = []\n    filtered_expenses = self.filter_relevant_splitwise_expenses(\n        expenses=expenses,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    for splitwise_transaction in filtered_expenses:\n        new_lunchmoney_transaction = TransactionInsertObject(\n            date=splitwise_transaction.date.astimezone(tzlocal()),\n            payee=splitwise_transaction.description,\n            amount=splitwise_transaction.financial_impact,\n            asset_id=self.splitwise_asset.id,\n            external_id=splitwise_transaction.splitwise_id,\n        )\n        batch.append(new_lunchmoney_transaction)\n        if len(batch) == 10:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n            batch = []\n    if len(batch) > 0:\n        new_ids = self.lunchable.insert_transactions(\n            transactions=batch, apply_rules=True\n        )\n        new_transaction_ids += new_ids\n    return new_transaction_ids\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.splitwise_to_pydantic","title":"splitwise_to_pydantic(expense)","text":"

Convert Splitwise Object to Pydantic

Parameters:

Name Type Description Default expense Expense required

Returns:

Type Description SplitLunchExpense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n    \"\"\"\n    Convert Splitwise Object to Pydantic\n\n    Parameters\n    ----------\n    expense: splitwise.Expense\n\n    Returns\n    -------\n    SplitLunchExpense\n    \"\"\"\n    financial_impact, self_paid = _get_splitwise_impact(\n        expense=expense, current_user_id=self.current_user.id\n    )\n    expense = SplitLunchExpense(\n        splitwise_id=expense.id,\n        original_amount=expense.cost,\n        financial_impact=financial_impact,\n        self_paid=self_paid,\n        description=expense.description,\n        category=expense.category.name,\n        details=expense.details,\n        payment=expense.payment,\n        date=expense.date,\n        users=[user.id for user in expense.users],\n        created_at=expense.created_at,\n        updated_at=expense.updated_at,\n        deleted_at=expense.deleted_at,\n        deleted=True if expense.deleted_at is not None else False,\n    )\n    return expense\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunch.update_splitwise_balance","title":"update_splitwise_balance()","text":"

Get and update the Splitwise Asset in Lunch Money

Returns:

Type Description AssetsObject

Updated balance

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def update_splitwise_balance(self) -> AssetsObject:\n    \"\"\"\n    Get and update the Splitwise Asset in Lunch Money\n\n    Returns\n    -------\n    AssetsObject\n        Updated  balance\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    balance = self.get_splitwise_balance()\n    if balance != self.splitwise_asset.balance:\n        updated_asset = self.lunchable.update_asset(\n            asset_id=self.splitwise_asset.id, balance=balance\n        )\n        self.splitwise_asset = updated_asset\n    return self.splitwise_asset\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunchError","title":"SplitLunchError","text":"

Bases: LunchMoneyError

Split Lunch Errors

Source code in lunchable/plugins/splitlunch/exceptions.py
class SplitLunchError(LunchMoneyError):\n    \"\"\"\n    Split Lunch Errors\n    \"\"\"\n
"},{"location":"reference/plugins/splitlunch/#lunchable.plugins.splitlunch.SplitLunchExpense","title":"SplitLunchExpense","text":"

Bases: LunchableModel

SplitLunch Object for Splitwise Expenses

Parameters:

Name Type Description Default splitwise_id int required original_amount float required self_paid bool required financial_impact float required description str required category str required details str | None None payment bool required date datetime required users List[int] required created_at datetime required updated_at datetime required deleted_at datetime | None None deleted bool required Source code in lunchable/plugins/splitlunch/models.py
class SplitLunchExpense(LunchableModel):\n    \"\"\"\n    SplitLunch Object for Splitwise Expenses\n    \"\"\"\n\n    splitwise_id: int\n    original_amount: float\n    self_paid: bool\n    financial_impact: float\n    description: str\n    category: str\n    details: Optional[str] = None\n    payment: bool\n    date: datetime.datetime\n    users: List[int]\n    created_at: datetime.datetime\n    updated_at: datetime.datetime\n    deleted_at: Optional[datetime.datetime] = None\n    deleted: bool\n
"},{"location":"reference/plugins/splitlunch/_config/","title":"_config","text":"

SplitLunch Configuration Helpers

"},{"location":"reference/plugins/splitlunch/_config/#lunchable.plugins.splitlunch._config.SplitLunchConfig","title":"SplitLunchConfig","text":"

Configuration Namespace for SplitLunch

Source code in lunchable/plugins/splitlunch/_config.py
class SplitLunchConfig:\n    \"\"\"\n    Configuration Namespace for SplitLunch\n    \"\"\"\n\n    splitlunch_tag: str = \"SplitLunch\"\n    splitwise_tag: str = \"Splitwise\"\n    splitlunch_import_tag: str = \"SplitLunchImport\"\n    splitlunch_direct_import_tag: str = \"SplitLunchDirectImport\"\n
"},{"location":"reference/plugins/splitlunch/exceptions/","title":"exceptions","text":"

SplitLunch Exceptions

"},{"location":"reference/plugins/splitlunch/exceptions/#lunchable.plugins.splitlunch.exceptions.SplitLunchError","title":"SplitLunchError","text":"

Bases: LunchMoneyError

Split Lunch Errors

Source code in lunchable/plugins/splitlunch/exceptions.py
class SplitLunchError(LunchMoneyError):\n    \"\"\"\n    Split Lunch Errors\n    \"\"\"\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/","title":"lunchmoney_splitwise","text":"

Lunchable Plugin for Splitwise

"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch","title":"SplitLunch","text":"

Bases: Splitwise

Lunchable Plugin For Interacting With Splitwise

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
class SplitLunch(splitwise.Splitwise):\n    \"\"\"\n    Lunchable Plugin For Interacting With Splitwise\n    \"\"\"\n\n    def __init__(\n        self,\n        lunch_money_access_token: Optional[str] = None,\n        financial_partner_id: Optional[int] = None,\n        financial_partner_email: Optional[str] = None,\n        financial_partner_group_id: Optional[int] = None,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n        lunchable_client: Optional[LunchMoney] = None,\n    ):\n        \"\"\"\n        Initialize the Parent Class with some additional properties\n\n        Parameters\n        ----------\n        financial_partner_id: Optional[int]\n            Splitwise User ID of financial partner\n        financial_partner_email: Optional[str]\n            Splitwise linked email address of financial partner\n        financial_partner_group_id: Optional[int]\n            Splitwise Group ID for financial partner transactions\n        consumer_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n            variable\n        consumer_secret: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n            environment variable\n        api_key: Optional[str]\n            Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n            variable.\n        lunch_money_access_token: Optional[str]\n            Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n            environment variable if not provided.\n        lunchable_client: LunchMoney\n            Instantiated LunchMoney object to use as internal client. One will\n            be created using environment variables otherwise.\n        \"\"\"\n        init_kwargs = self._get_splitwise_init_kwargs(\n            consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n        )\n        super(SplitLunch, self).__init__(**init_kwargs)\n        self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n        self.financial_partner: splitwise.Friend = self.get_friend(\n            friend_id=financial_partner_id, email_address=financial_partner_email\n        )\n        self.financial_group = financial_partner_group_id\n        self.last_check: Optional[datetime.datetime] = None\n        self.lunchable = (\n            LunchMoney(access_token=lunch_money_access_token)\n            if lunchable_client is None\n            else lunchable_client\n        )\n        self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n        self.splitwise_tag = self._none_tag.model_copy()\n        self.splitlunch_tag = self._none_tag.model_copy()\n        self.splitlunch_import_tag = self._none_tag.model_copy()\n        self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n        self._get_splitwise_tags()\n        self.earliest_start_date = datetime.date(1812, 1, 1)\n        today = datetime.date.today()\n        self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n        self.splitwise_asset = self._get_splitwise_asset()\n        self.reimbursement_category = self._get_reimbursement_category()\n\n    def __repr__(self) -> str:\n        \"\"\"\n        String Representation\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return f\"<Splitwise: {self.current_user.email}>\"\n\n    @classmethod\n    def _split_amount(cls, amount: float, splits: int) -> Tuple[float, ...]:\n        \"\"\"\n        Split a money amount into fair shares\n\n        Parameters\n        ----------\n        amount: float\n        splits: int\n\n        Returns\n        -------\n        Tuple[float]\n        \"\"\"\n        try:\n            assert amount == round(amount, 2)\n        except AssertionError as ae:\n            raise SplitLunchError(\n                f\"{amount} caused an error, you must provide a real \" \"spending amount.\"\n            ) from ae\n        equal_shares = round(amount, 2) / splits\n        remainder_dollars = floor(equal_shares)\n        remainder_cents = floor((equal_shares - remainder_dollars) * 100) / 100\n        remainder_left = round(\n            (equal_shares - remainder_dollars - remainder_cents) * splits * 100, 0\n        )\n        owed_amount = remainder_dollars + remainder_cents\n        return_amounts = [owed_amount for _ in range(splits)]\n        for i in range(int(remainder_left)):\n            return_amounts[i] += 0.010\n        shuffle(return_amounts)\n        return tuple([round(item, 2) for item in return_amounts])\n\n    @classmethod\n    def split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n        \"\"\"\n        Split a Transaction into Two\n\n        Split a bill into a tuple of two amounts (and take care\n        of the extra penny if needed)\n\n        Parameters\n        ----------\n        amount: A Currency amount (no more precise than cents)\n\n        Returns\n        -------\n        tuple\n            A tuple is returned with each participant's amount\n        \"\"\"\n        amounts_due = cls._split_amount(amount=amount, splits=2)\n        return amounts_due\n\n    def create_self_paid_expense(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        primary_user_owes, financial_partner_owes = self.split_a_transaction(\n            amount=amount\n        )\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=primary_user_owes)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=financial_partner_owes)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def create_expense_on_behalf_of_partner(\n        self, amount: float, description: str, date: datetime.date\n    ) -> SplitLunchExpense:\n        \"\"\"\n        Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n        This expense will be completely owed by the partner and maked as reimbursed.\n\n        Parameters\n        ----------\n        amount: float\n            Transaction Amount\n        description: str\n            Transaction Description\n\n        Returns\n        -------\n        Expense\n        \"\"\"\n        # CREATE THE NEW EXPENSE OBJECT\n        new_expense = splitwise.Expense()\n        new_expense.setDescription(desc=description)\n        new_expense.setDate(date=date)\n        if self.financial_group:\n            new_expense.setGroupId(self.financial_group)\n        # GET AND SET AMOUNTS OWED\n        new_expense.setCost(cost=amount)\n        # CONFIGURE PRIMARY USER\n        primary_user = splitwise.user.ExpenseUser()\n        primary_user.setId(id=self.current_user.id)\n        primary_user.setPaidShare(paid_share=amount)\n        primary_user.setOwedShare(owed_share=0.00)\n        # CONFIGURE SECONDARY USER\n        financial_partner = splitwise.user.ExpenseUser()\n        financial_partner.setId(id=self.financial_partner.id)\n        financial_partner.setPaidShare(paid_share=0.00)\n        financial_partner.setOwedShare(owed_share=amount)\n        # ADD USERS AND REPAYMENTS TO EXPENSE\n        new_expense.addUser(user=primary_user)\n        new_expense.addUser(user=financial_partner)\n        # SUBMIT THE EXPENSE AND GET THE RESPONSE\n        expense_response: splitwise.Expense\n        expense_response, expense_errors = self.createExpense(expense=new_expense)\n        try:\n            assert expense_errors is None\n        except AssertionError as ae:\n            raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n        logger.info(\"Expense Created: %s\", expense_response.id)\n        message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n        self.createComment(expense_id=expense_response.id, content=message)\n        pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n        return pydantic_response\n\n    def get_friend(\n        self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n    ) -> Optional[splitwise.Friend]:\n        \"\"\"\n        Retrieve a Financial Partner by Email Address\n\n        Parameters\n        ----------\n        email_address: str\n            Email Address of Friend's user in Splitwise\n        friend_id: Optional[int]\n            Splitwise friend ID. Notice the friend ID in the following\n            URL: https://secure.splitwise.com/#/friends/12345678\n\n        Returns\n        -------\n        Optional[splitwise.Friend]\n        \"\"\"\n        friend_list: List[splitwise.Friend] = self.getFriends()\n        if len(friend_list) == 1:\n            return friend_list[0]\n        for friend in friend_list:\n            if friend_id is not None and friend.id == friend_id:\n                return friend\n            elif (\n                email_address is not None\n                and friend.email.lower() == email_address.lower()\n            ):\n                return friend\n        return None\n\n    def get_expenses(\n        self,\n        offset: Optional[int] = None,\n        limit: Optional[int] = None,\n        group_id: Optional[int] = None,\n        friendship_id: Optional[int] = None,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        updated_after: Optional[datetime.datetime] = None,\n        updated_before: Optional[datetime.datetime] = None,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Get Splitwise Expenses\n\n        Parameters\n        ----------\n        offset: Optional[int]\n            Number of expenses to be skipped\n        limit: Optional[int]\n            Number of expenses to be returned\n        group_id: Optional[int]\n            GroupID of the expenses\n        friendship_id: Optional[int]\n            FriendshipID of the expenses\n        dated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses later that this date\n        dated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses earlier than this date\n        updated_after: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated after this date\n        updated_before: Optional[datetime.datetime]\n            ISO 8601 Date time. Return expenses updated before this date\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        expenses = self.getExpenses(\n            offset=offset,\n            limit=limit,\n            group_id=group_id,\n            friendship_id=friendship_id,\n            dated_after=dated_after,\n            dated_before=dated_before,\n            updated_after=updated_after,\n            updated_before=updated_before,\n        )\n        pydantic_expenses = [\n            self.splitwise_to_pydantic(expense) for expense in expenses\n        ]\n        return pydantic_expenses\n\n    @classmethod\n    def _get_splitwise_init_kwargs(\n        cls,\n        consumer_key: Optional[str] = None,\n        consumer_secret: Optional[str] = None,\n        api_key: Optional[str] = None,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Get the Splitwise Kwargs\n\n        Parameters\n        ----------\n        consumer_key: Optional[str]\n        consumer_secret: Optional[str]\n        api_key: Optional[str]\n        \"\"\"\n        if consumer_key is None:\n            consumer_key = getenv(\"SPLITWISE_CONSUMER_KEY\")\n        if consumer_secret is None:\n            consumer_secret = getenv(\"SPLITWISE_CONSUMER_SECRET\")\n        if api_key is None:\n            api_key = getenv(\"SPLITWISE_API_KEY\", None)\n        init_kwargs = {\n            \"consumer_key\": consumer_key,\n            \"consumer_secret\": consumer_secret,\n            \"api_key\": api_key,\n        }\n        if consumer_key is None or consumer_secret is None or api_key is None:\n            error_message = (\n                dedent(\n                    \"\"\"\n            You must set your Splitwise credentials explicitly or by assigning\n            the `SPLITWISE_CONSUMER_KEY`, `SPLITWISE_CONSUMER_SECRET`, and the\n            `SPLITWISE_API_KEY`environment variables\n            \"\"\"\n                )\n                .replace(\"\\n\", \" \")\n                .replace(\"  \", \" \")\n            )\n            logger.error(error_message)\n            raise SplitLunchError(error_message)\n        return init_kwargs\n\n    def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n        \"\"\"\n        Convert Splitwise Object to Pydantic\n\n        Parameters\n        ----------\n        expense: splitwise.Expense\n\n        Returns\n        -------\n        SplitLunchExpense\n        \"\"\"\n        financial_impact, self_paid = _get_splitwise_impact(\n            expense=expense, current_user_id=self.current_user.id\n        )\n        expense = SplitLunchExpense(\n            splitwise_id=expense.id,\n            original_amount=expense.cost,\n            financial_impact=financial_impact,\n            self_paid=self_paid,\n            description=expense.description,\n            category=expense.category.name,\n            details=expense.details,\n            payment=expense.payment,\n            date=expense.date,\n            users=[user.id for user in expense.users],\n            created_at=expense.created_at,\n            updated_at=expense.updated_at,\n            deleted_at=expense.deleted_at,\n            deleted=True if expense.deleted_at is not None else False,\n        )\n        return expense\n\n    def _get_splitwise_asset(self) -> Optional[AssetsObject]:\n        \"\"\"\n        Get the Splitwise asset\n\n        Parse a user's Lunch Money accounts and return the manually managed\n        Splitwise account asset object\n\n        Returns\n        -------\n        AssetsObject\n        \"\"\"\n        assets = self.lunchable.get_assets()\n        splitwise_assets = []\n        for asset in assets:\n            if (\n                asset.institution_name is not None\n                and \"splitwise\" in asset.institution_name.lower()\n            ):\n                splitwise_assets.append(asset)\n        if len(splitwise_assets) == 0:\n            return None\n        elif len(splitwise_assets) > 1:\n            raise SplitLunchError(\n                \"SplitLunch requires an manually managed Splitwise asset. \"\n                \"Make sure you have a single account where 'Splitwise' \"\n                \"is in the asset's `Institution Name`.\"\n            )\n        else:\n            return splitwise_assets[0]\n\n    def _get_reimbursement_category(self) -> Optional[CategoriesObject]:\n        \"\"\"\n        Get the Reimbusement Category\n\n        Parse a user's Lunch Money categories and return the Reimbursement\n        category\n\n        Returns\n        -------\n        CategoriesObject\n        \"\"\"\n        categories = self.lunchable.get_categories()\n        reimbursement_list = []\n        for category in categories:\n            if \"reimbursement\" == category.name.strip().lower():\n                reimbursement_list.append(category)\n        if len(reimbursement_list) != 1:\n            return None\n        return reimbursement_list[0]\n\n    def _get_splitwise_tags(self) -> None:\n        \"\"\"\n        Get Lunch Money Tags to Interact with\n\n        Returns\n        -------\n        Dict[str, int]\n        \"\"\"\n        all_tags = self.lunchable.get_tags()\n        for tag in all_tags:\n            if tag.name.lower() == SplitLunchConfig.splitlunch_tag.lower():\n                self.splitlunch_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitwise_tag.lower():\n                self.splitwise_tag = tag\n            elif tag.name.lower() == SplitLunchConfig.splitlunch_import_tag.lower():\n                self.splitlunch_import_tag = tag\n            elif (\n                tag.name.lower()\n                == SplitLunchConfig.splitlunch_direct_import_tag.lower()\n            ):\n                self.splitlunch_direct_import_tag = tag\n\n    def _raise_nonexistent_tag_error(self, tags: List[str]) -> None:\n        \"\"\"\n        Raise a warning for specific SplitLunch Tags\n\n        tags: List[str]\n            A list of tags to raise the error for\n        \"\"\"\n        if (\n            self.splitlunch_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitwise_tag == self._none_tag\n            and SplitLunchConfig.splitwise_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitwise_tag}` tag is required. \"\n                f\"This tag is used for splitting transactions in half and have half \"\n                f\"marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_import_tag}` tag is required. \"\n                f\"This tag is used for creating Splitwise transactions directly from \"\n                f\"Lunch Money transactions. These transactions will be split in half,\"\n                f\"and have one half marked as reimbursed.\"\n            )\n            raise SplitLunchError(error_message)\n        if (\n            self.splitlunch_direct_import_tag == self._none_tag\n            and SplitLunchConfig.splitlunch_direct_import_tag in tags\n        ):\n            error_message = (\n                f\"a `{SplitLunchConfig.splitlunch_direct_import_tag}` tag is \"\n                \"required. This tag is used for creating Splitwise transactions \"\n                \"directly from Lunch Money transactions. These transactions will \"\n                \"be completely owed by your financial partner.\"\n            )\n            raise SplitLunchError(error_message)\n\n    def get_splitlunch_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitlunch\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def get_splitlunch_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitlunch_direct_import_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(\n            tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n        )\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitlunch_direct_import_tag.id,\n            start_date=start_date,\n            end_date=end_date,\n        )\n        return transactions\n\n    def get_splitwise_tagged_transactions(\n        self,\n        start_date: Optional[datetime.date] = None,\n        end_date: Optional[datetime.date] = None,\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Retrieve all transactions with the \"Splitwise\" Tag\n\n        Parameters\n        ----------\n        start_date: Optional[datetime.date]\n        end_date : Optional[datetime.date]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if start_date is None:\n            start_date = self.earliest_start_date\n        if end_date is None:\n            end_date = self.latest_end_date\n        self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n        transactions = self.lunchable.get_transactions(\n            tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n        )\n        return transactions\n\n    def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n        \"\"\"\n        Operate on `SplitLunch` tagged transactions\n\n        Split all transactions with the `SplitLunch` tag in half. One of these\n        new splits will be recategorized to `Reimbursement`. Both new splits will receive\n        the `Splitwise` tag without any preexisting tags.\n        \"\"\"\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        split_transaction_ids = []\n        tagged_objects = self.get_splitlunch_tagged_transactions()\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n            # Generate the First Split\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=transaction.notes,\n                amount=amount_1,\n            )\n            # Generate the second split as a copy, change some properties\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = amount_2\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                \"Splitting transaction: %s -> (%s, %s)\",\n                transaction.amount,\n                amount_1,\n                amount_2,\n            )\n\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            # Tag each of the new transactions generated\n            split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags if transaction.tags is not None else []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag is not None\n                    and tag.name.lower() != self.splitlunch_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return split_transaction_ids\n\n    def make_splitlunch_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchImport` tagged transactions\n\n        Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n        One of these new splits will be recategorized to `Reimbursement`. Both new splits\n        will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n        reapplied.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_self_paid_expense(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            split_object = TransactionSplitObject(\n                date=transaction.date,\n                category_id=transaction.category_id,\n                notes=notes,\n                # financial_impact for self-paid transactions will be negative\n                amount=round(\n                    transaction.amount - abs(new_transaction.financial_impact), 2\n                ),\n            )\n            reimbursement_object = split_object.model_copy()\n            reimbursement_object.amount = abs(new_transaction.financial_impact)\n            reimbursement_object.category_id = self.reimbursement_category.id\n            logger.debug(\n                f\"Transaction split by Splitwise: {transaction.amount} -> \"\n                f\"({split_object.amount}, {reimbursement_object.amount})\"\n            )\n            update_response = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                split=[split_object, reimbursement_object],\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": reimbursement_object.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": update_response[\"updated\"],\n                \"split\": update_response[\"split\"],\n            }\n            update_responses.append(formatted_update_response)\n            # Tag each of the new transactions generated\n            for split_transaction_id in update_response[\"split\"]:\n                update_tags = transaction.tags or []\n                tags = [\n                    tag.name\n                    for tag in update_tags\n                    if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n                ]\n                if self.splitwise_tag.name not in tags and tag_transactions is True:\n                    self._raise_nonexistent_tag_error(\n                        tags=[SplitLunchConfig.splitwise_tag]\n                    )\n                    tags.append(self.splitwise_tag.name)\n                tag_update = TransactionUpdateObject(tags=tags)\n                self.lunchable.update_transaction(\n                    transaction_id=split_transaction_id, transaction=tag_update\n                )\n        return update_responses\n\n    def make_splitlunch_direct_import(\n        self, tag_transactions: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Operate on `SplitLunchDirectImport` tagged transactions\n\n        Send a transaction to Splitwise and then mark the transaction under the\n        `Reimbursement` category. The sum of the transaction will be completely owed\n        by the financial partner.\n\n        Parameters\n        ----------\n        tag_transactions : bool\n            Whether to tag the transactions with the `Splitwise` tag after splitting them.\n            Defaults to False which\n        \"\"\"\n        self._raise_financial_partner_error()\n        if self.reimbursement_category is None:\n            self._raise_category_reimbursement_error()\n            raise ValueError(\"ReimbursementCategory\")\n        tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n        update_responses = []\n        for transaction in tagged_objects:\n            # Split the Original Amount\n            description = str(transaction.payee)\n            if transaction.notes is not None:\n                description = f\"{transaction.payee} - {transaction.notes}\"\n            new_transaction = self.create_expense_on_behalf_of_partner(\n                amount=transaction.amount,\n                description=description,\n                date=transaction.date,\n            )\n            notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n            if transaction.notes is not None:\n                notes = f\"{transaction.notes} || {notes}\"\n            existing_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in existing_tags\n                if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n                tags.append(self.splitwise_tag.name)\n            update = TransactionUpdateObject(\n                category_id=self.reimbursement_category.id, tags=tags, notes=notes\n            )\n            response = self.lunchable.update_transaction(\n                transaction_id=transaction.id, transaction=update\n            )\n            formatted_update_response = {\n                \"original_id\": transaction.id,\n                \"payee\": transaction.payee,\n                \"amount\": transaction.amount,\n                \"reimbursement_amount\": transaction.amount,\n                \"notes\": transaction.notes,\n                \"splitwise_id\": new_transaction.splitwise_id,\n                \"updated\": response[\"updated\"],\n                \"split\": None,\n            }\n            update_responses.append(formatted_update_response)\n        return update_responses\n\n    def splitwise_to_lunchmoney(\n        self,\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[int]:\n        \"\"\"\n        Ingest Splitwise Expenses into Lunch Money\n\n        This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n        a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n        payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[int]\n            New Lunch Money transaction IDs\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        batch = []\n        new_transaction_ids = []\n        filtered_expenses = self.filter_relevant_splitwise_expenses(\n            expenses=expenses,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        for splitwise_transaction in filtered_expenses:\n            new_lunchmoney_transaction = TransactionInsertObject(\n                date=splitwise_transaction.date.astimezone(tzlocal()),\n                payee=splitwise_transaction.description,\n                amount=splitwise_transaction.financial_impact,\n                asset_id=self.splitwise_asset.id,\n                external_id=splitwise_transaction.splitwise_id,\n            )\n            batch.append(new_lunchmoney_transaction)\n            if len(batch) == 10:\n                new_ids = self.lunchable.insert_transactions(\n                    transactions=batch, apply_rules=True\n                )\n                new_transaction_ids += new_ids\n                batch = []\n        if len(batch) > 0:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n        return new_transaction_ids\n\n    @staticmethod\n    def filter_relevant_splitwise_expenses(\n        expenses: List[SplitLunchExpense],\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> List[SplitLunchExpense]:\n        \"\"\"\n        Filter Expenses in Splitwise into relevant expenses.\n\n        This filtering action is important to understand when seeing why not\n        all transactions from Splitwise end up flowing into Lunch Money.\n\n        1) It filters out deleted expenses\n\n        2) It filters out expenses with a financial impact of 0, implying that the user was not\n        involved in the expense.\n\n        3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n        `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n        excluded because it is assumed that these transactions will have already been imported via a\n        different account.\n\n        Parameters\n        ----------\n        expenses: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        filtered_expenses = []\n        for splitwise_transaction in expenses:\n            if all(\n                [\n                    splitwise_transaction.deleted is False,\n                    splitwise_transaction.financial_impact != 0.00,\n                    allow_self_paid or (splitwise_transaction.self_paid is False),\n                    allow_payments or (splitwise_transaction.payment is False),\n                ]\n            ):\n                filtered_expenses.append(splitwise_transaction)\n\n        return filtered_expenses\n\n    def get_splitwise_balance(self) -> float:\n        \"\"\"\n        Get the net balance in Splitwise\n\n        Returns\n        -------\n        float\n        \"\"\"\n        groups = self.getGroups()\n        total_balance = 0.00\n        for group in groups:\n            for debt in group.simplified_debts:\n                if debt.fromUser == self.current_user.id:\n                    total_balance -= float(debt.amount)\n                elif debt.toUser == self.current_user.id:\n                    total_balance += float(debt.amount)\n        return total_balance\n\n    def update_splitwise_balance(self) -> AssetsObject:\n        \"\"\"\n        Get and update the Splitwise Asset in Lunch Money\n\n        Returns\n        -------\n        AssetsObject\n            Updated  balance\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        balance = self.get_splitwise_balance()\n        if balance != self.splitwise_asset.balance:\n            updated_asset = self.lunchable.update_asset(\n                asset_id=self.splitwise_asset.id, balance=balance\n            )\n            self.splitwise_asset = updated_asset\n        return self.splitwise_asset\n\n    _deleted_payee = \"[DELETED FROM SPLITWISE]\"\n\n    def get_new_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n    ) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n        \"\"\"\n        Get Splitwise Transactions that don't exist in Lunch Money\n\n        Also return deleted transaction from LunchMoney\n\n        Returns\n        -------\n        Tuple[List[SplitLunchExpense], List[TransactionObject]]\n            New and Deleted Transactions\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        splitlunch_expenses = self.lunchable.get_transactions(\n            asset_id=self.splitwise_asset.id,\n            start_date=datetime.datetime(1800, 1, 1),\n            end_date=datetime.datetime(2300, 12, 31),\n        )\n        splitlunch_ids = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        splitwise_expenses = self.get_expenses(\n            limit=0,\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n        new_ids = splitwise_ids.difference(splitlunch_ids)\n        new_expenses = [\n            expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n        ]\n        deleted_transactions = self.get_deleted_transactions(\n            splitlunch_expenses=splitlunch_expenses,\n            splitwise_transactions=splitwise_expenses,\n        )\n        return new_expenses, deleted_transactions\n\n    def get_deleted_transactions(\n        self,\n        splitlunch_expenses: List[TransactionObject],\n        splitwise_transactions: List[SplitLunchExpense],\n    ) -> List[TransactionObject]:\n        \"\"\"\n        Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n        Set these transactions to $0.00 and Make a Note\n\n        Parameters\n        ----------\n        splitlunch_expenses: List[TransactionObject]\n        splitwise_transactions: List[SplitLunchExpense]\n\n        Returns\n        -------\n        List[TransactionObject]\n        \"\"\"\n        if self.splitwise_asset is None:\n            self._raise_splitwise_asset_error()\n            raise ValueError(\"SplitwiseAsset\")\n        existing_transactions = {\n            int(item.external_id)\n            for item in splitlunch_expenses\n            if item.external_id is not None\n        }\n        deleted_ids = {\n            item.splitwise_id for item in splitwise_transactions if item.deleted is True\n        }\n        untethered_transactions = deleted_ids.intersection(existing_transactions)\n        transactions_to_delete = [\n            tran\n            for tran in splitlunch_expenses\n            if tran.external_id is not None\n            and int(tran.external_id) in untethered_transactions\n            and tran.payee != self._deleted_payee\n        ]\n        return transactions_to_delete\n\n    def refresh_splitwise_transactions(\n        self,\n        dated_after: Optional[datetime.datetime] = None,\n        dated_before: Optional[datetime.datetime] = None,\n        allow_self_paid: bool = False,\n        allow_payments: bool = False,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Import New Splitwise Transactions to Lunch Money\n\n        This function get's all transactions from Splitwise, all transactions from\n        your Lunch Money Splitwise account and compares the two.\n\n        Returns\n        -------\n        List[SplitLunchExpense]\n        \"\"\"\n        new_transactions, deleted_transactions = self.get_new_transactions(\n            dated_after=dated_after,\n            dated_before=dated_before,\n        )\n        self.splitwise_to_lunchmoney(\n            expenses=new_transactions,\n            allow_self_paid=allow_self_paid,\n            allow_payments=allow_payments,\n        )\n        splitwise_asset = self.update_splitwise_balance()\n        self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n        return {\n            \"balance\": splitwise_asset.balance,\n            \"new\": new_transactions,\n            \"deleted\": deleted_transactions,\n        }\n\n    def handle_deleted_transactions(\n        self,\n        deleted_transactions: List[TransactionObject],\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n        Parameters\n        ----------\n        deleted_transactions: List[TransactionObject]\n\n        Returns\n        -------\n        List[Dict[str, Any]]\n        \"\"\"\n        updated_transactions = []\n        for transaction in deleted_transactions:\n            update = self.lunchable.update_transaction(\n                transaction_id=transaction.id,\n                transaction=TransactionUpdateObject(\n                    amount=0.00,\n                    payee=self._deleted_payee,\n                    notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n                ),\n            )\n            updated_transactions.append(update)\n        return updated_transactions\n\n    def _raise_financial_partner_error(self) -> None:\n        \"\"\"\n        Raise Errors for Financial Partners\n        \"\"\"\n        if self.financial_partner is None:\n            raise SplitLunchError(\n                \"You must designate a financial partner in Splitwise. \"\n                \"This can be done with the partner's Splitwise User ID # \"\n                \"or their email address.\"\n            )\n\n    def _raise_splitwise_asset_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"You must create an asset (aka Account) in Lunch Money with \"\n            \"`Splitwise` in the name. There should only be one account \"\n            \"like this.\"\n        )\n\n    def _raise_category_reimbursement_error(self) -> None:\n        \"\"\"\n        Raise Errors for Splitwise Asset\n        \"\"\"\n        raise SplitLunchError(\n            \"SplitLunch requires a reimbursement Category. \"\n            \"Make sure you have a category entitled `Reimbursement`. \"\n            \"This category will be excluded from budgeting.\"\n            \"Half of split transactions will be created with \"\n            \"this category.\"\n        )\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.__init__","title":"__init__(lunch_money_access_token=None, financial_partner_id=None, financial_partner_email=None, financial_partner_group_id=None, consumer_key=None, consumer_secret=None, api_key=None, lunchable_client=None)","text":"

Initialize the Parent Class with some additional properties

Parameters:

Name Type Description Default financial_partner_id Optional[int]

Splitwise User ID of financial partner

None financial_partner_email Optional[str]

Splitwise linked email address of financial partner

None financial_partner_group_id Optional[int]

Splitwise Group ID for financial partner transactions

None consumer_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_KEY environment variable

None consumer_secret Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_CONSUMER_SECRET environment variable

None api_key Optional[str]

Consumer Key provided by Splitwise. Defaults to SPLITWISE_API_KEY environment variable.

None lunch_money_access_token Optional[str]

Lunch Money Access Token. Will be inherited from LUNCHMONEY_ACCESS_TOKEN environment variable if not provided.

None lunchable_client Optional[LunchMoney]

Instantiated LunchMoney object to use as internal client. One will be created using environment variables otherwise.

None Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __init__(\n    self,\n    lunch_money_access_token: Optional[str] = None,\n    financial_partner_id: Optional[int] = None,\n    financial_partner_email: Optional[str] = None,\n    financial_partner_group_id: Optional[int] = None,\n    consumer_key: Optional[str] = None,\n    consumer_secret: Optional[str] = None,\n    api_key: Optional[str] = None,\n    lunchable_client: Optional[LunchMoney] = None,\n):\n    \"\"\"\n    Initialize the Parent Class with some additional properties\n\n    Parameters\n    ----------\n    financial_partner_id: Optional[int]\n        Splitwise User ID of financial partner\n    financial_partner_email: Optional[str]\n        Splitwise linked email address of financial partner\n    financial_partner_group_id: Optional[int]\n        Splitwise Group ID for financial partner transactions\n    consumer_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_KEY` environment\n        variable\n    consumer_secret: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_CONSUMER_SECRET`\n        environment variable\n    api_key: Optional[str]\n        Consumer Key provided by Splitwise. Defaults to `SPLITWISE_API_KEY` environment\n        variable.\n    lunch_money_access_token: Optional[str]\n        Lunch Money Access Token. Will be inherited from `LUNCHMONEY_ACCESS_TOKEN`\n        environment variable if not provided.\n    lunchable_client: LunchMoney\n        Instantiated LunchMoney object to use as internal client. One will\n        be created using environment variables otherwise.\n    \"\"\"\n    init_kwargs = self._get_splitwise_init_kwargs(\n        consumer_key=consumer_key, consumer_secret=consumer_secret, api_key=api_key\n    )\n    super(SplitLunch, self).__init__(**init_kwargs)\n    self.current_user: splitwise.CurrentUser = self.getCurrentUser()\n    self.financial_partner: splitwise.Friend = self.get_friend(\n        friend_id=financial_partner_id, email_address=financial_partner_email\n    )\n    self.financial_group = financial_partner_group_id\n    self.last_check: Optional[datetime.datetime] = None\n    self.lunchable = (\n        LunchMoney(access_token=lunch_money_access_token)\n        if lunchable_client is None\n        else lunchable_client\n    )\n    self._none_tag = TagsObject(id=0, name=\"SplitLunchPlaceholder\")\n    self.splitwise_tag = self._none_tag.model_copy()\n    self.splitlunch_tag = self._none_tag.model_copy()\n    self.splitlunch_import_tag = self._none_tag.model_copy()\n    self.splitlunch_direct_import_tag = self._none_tag.model_copy()\n    self._get_splitwise_tags()\n    self.earliest_start_date = datetime.date(1812, 1, 1)\n    today = datetime.date.today()\n    self.latest_end_date = datetime.date(today.year + 10, 12, 31)\n    self.splitwise_asset = self._get_splitwise_asset()\n    self.reimbursement_category = self._get_reimbursement_category()\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.__repr__","title":"__repr__()","text":"

String Representation

Returns:

Type Description str Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def __repr__(self) -> str:\n    \"\"\"\n    String Representation\n\n    Returns\n    -------\n    str\n    \"\"\"\n    return f\"<Splitwise: {self.current_user.email}>\"\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.create_expense_on_behalf_of_partner","title":"create_expense_on_behalf_of_partner(amount, description, date)","text":"

Create and Submit a Splitwise Expense on behalf of your financial partner.

This expense will be completely owed by the partner and maked as reimbursed.

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_expense_on_behalf_of_partner(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense on behalf of your financial partner.\n\n    This expense will be completely owed by the partner and maked as reimbursed.\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=0.00)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=amount)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.create_self_paid_expense","title":"create_self_paid_expense(amount, description, date)","text":"

Create and Submit a Splitwise Expense

Parameters:

Name Type Description Default amount float

Transaction Amount

required description str

Transaction Description

required

Returns:

Type Description Expense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def create_self_paid_expense(\n    self, amount: float, description: str, date: datetime.date\n) -> SplitLunchExpense:\n    \"\"\"\n    Create and Submit a Splitwise Expense\n\n    Parameters\n    ----------\n    amount: float\n        Transaction Amount\n    description: str\n        Transaction Description\n\n    Returns\n    -------\n    Expense\n    \"\"\"\n    # CREATE THE NEW EXPENSE OBJECT\n    new_expense = splitwise.Expense()\n    new_expense.setDescription(desc=description)\n    new_expense.setDate(date=date)\n    if self.financial_group:\n        new_expense.setGroupId(self.financial_group)\n    # GET AND SET AMOUNTS OWED\n    primary_user_owes, financial_partner_owes = self.split_a_transaction(\n        amount=amount\n    )\n    new_expense.setCost(cost=amount)\n    # CONFIGURE PRIMARY USER\n    primary_user = splitwise.user.ExpenseUser()\n    primary_user.setId(id=self.current_user.id)\n    primary_user.setPaidShare(paid_share=amount)\n    primary_user.setOwedShare(owed_share=primary_user_owes)\n    # CONFIGURE SECONDARY USER\n    financial_partner = splitwise.user.ExpenseUser()\n    financial_partner.setId(id=self.financial_partner.id)\n    financial_partner.setPaidShare(paid_share=0.00)\n    financial_partner.setOwedShare(owed_share=financial_partner_owes)\n    # ADD USERS AND REPAYMENTS TO EXPENSE\n    new_expense.addUser(user=primary_user)\n    new_expense.addUser(user=financial_partner)\n    # SUBMIT THE EXPENSE AND GET THE RESPONSE\n    expense_response: splitwise.Expense\n    expense_response, expense_errors = self.createExpense(expense=new_expense)\n    try:\n        assert expense_errors is None\n    except AssertionError as ae:\n        raise SplitLunchError(expense_errors[\"base\"][0]) from ae\n    logger.info(\"Expense Created: %s\", expense_response.id)\n    message = f\"Created via SplitLunch: {datetime.datetime.now()}\"\n    self.createComment(expense_id=expense_response.id, content=message)\n    pydantic_response = self.splitwise_to_pydantic(expense=expense_response)\n    return pydantic_response\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.filter_relevant_splitwise_expenses","title":"filter_relevant_splitwise_expenses(expenses, allow_self_paid=False, allow_payments=False) staticmethod","text":"

Filter Expenses in Splitwise into relevant expenses.

This filtering action is important to understand when seeing why not all transactions from Splitwise end up flowing into Lunch Money.

1) It filters out deleted expenses

2) It filters out expenses with a financial impact of 0, implying that the user was not involved in the expense.

3) If the --allow-self-paid flag is not provided, it filters out self-paid expenses. A self-paid expense is an expense in Splitwise where you originated the payment. This is excluded because it is assumed that these transactions will have already been imported via a different account.

4) If the --allow-payments flag is not provided, it filters out payments. Payments are excluded because it is assumed that these transactions will have already been imported via a different account.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@staticmethod\ndef filter_relevant_splitwise_expenses(\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Filter Expenses in Splitwise into relevant expenses.\n\n    This filtering action is important to understand when seeing why not\n    all transactions from Splitwise end up flowing into Lunch Money.\n\n    1) It filters out deleted expenses\n\n    2) It filters out expenses with a financial impact of 0, implying that the user was not\n    involved in the expense.\n\n    3) If the --allow-self-paid flag is not provided, it filters out `self-paid` expenses. A\n    `self-paid` expense is an expense in Splitwise where you originated the payment. This is\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    4) If the --allow-payments flag is not provided, it filters out payments. Payments are\n    excluded because it is assumed that these transactions will have already been imported via a\n    different account.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    filtered_expenses = []\n    for splitwise_transaction in expenses:\n        if all(\n            [\n                splitwise_transaction.deleted is False,\n                splitwise_transaction.financial_impact != 0.00,\n                allow_self_paid or (splitwise_transaction.self_paid is False),\n                allow_payments or (splitwise_transaction.payment is False),\n            ]\n        ):\n            filtered_expenses.append(splitwise_transaction)\n\n    return filtered_expenses\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_deleted_transactions","title":"get_deleted_transactions(splitlunch_expenses, splitwise_transactions)","text":"

Get Splitwise Transactions that exist in Lunch Money but have since been deleted

Set these transactions to $0.00 and Make a Note

Parameters:

Name Type Description Default splitlunch_expenses List[TransactionObject] required splitwise_transactions List[SplitLunchExpense] required

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_deleted_transactions(\n    self,\n    splitlunch_expenses: List[TransactionObject],\n    splitwise_transactions: List[SplitLunchExpense],\n) -> List[TransactionObject]:\n    \"\"\"\n    Get Splitwise Transactions that exist in Lunch Money but have since been deleted\n\n    Set these transactions to $0.00 and Make a Note\n\n    Parameters\n    ----------\n    splitlunch_expenses: List[TransactionObject]\n    splitwise_transactions: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    existing_transactions = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    deleted_ids = {\n        item.splitwise_id for item in splitwise_transactions if item.deleted is True\n    }\n    untethered_transactions = deleted_ids.intersection(existing_transactions)\n    transactions_to_delete = [\n        tran\n        for tran in splitlunch_expenses\n        if tran.external_id is not None\n        and int(tran.external_id) in untethered_transactions\n        and tran.payee != self._deleted_payee\n    ]\n    return transactions_to_delete\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_expenses","title":"get_expenses(offset=None, limit=None, group_id=None, friendship_id=None, dated_after=None, dated_before=None, updated_after=None, updated_before=None)","text":"

Get Splitwise Expenses

Parameters:

Name Type Description Default offset Optional[int]

Number of expenses to be skipped

None limit Optional[int]

Number of expenses to be returned

None group_id Optional[int]

GroupID of the expenses

None friendship_id Optional[int]

FriendshipID of the expenses

None dated_after Optional[datetime]

ISO 8601 Date time. Return expenses later that this date

None dated_before Optional[datetime]

ISO 8601 Date time. Return expenses earlier than this date

None updated_after Optional[datetime]

ISO 8601 Date time. Return expenses updated after this date

None updated_before Optional[datetime]

ISO 8601 Date time. Return expenses updated before this date

None

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_expenses(\n    self,\n    offset: Optional[int] = None,\n    limit: Optional[int] = None,\n    group_id: Optional[int] = None,\n    friendship_id: Optional[int] = None,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    updated_after: Optional[datetime.datetime] = None,\n    updated_before: Optional[datetime.datetime] = None,\n) -> List[SplitLunchExpense]:\n    \"\"\"\n    Get Splitwise Expenses\n\n    Parameters\n    ----------\n    offset: Optional[int]\n        Number of expenses to be skipped\n    limit: Optional[int]\n        Number of expenses to be returned\n    group_id: Optional[int]\n        GroupID of the expenses\n    friendship_id: Optional[int]\n        FriendshipID of the expenses\n    dated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses later that this date\n    dated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses earlier than this date\n    updated_after: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated after this date\n    updated_before: Optional[datetime.datetime]\n        ISO 8601 Date time. Return expenses updated before this date\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    expenses = self.getExpenses(\n        offset=offset,\n        limit=limit,\n        group_id=group_id,\n        friendship_id=friendship_id,\n        dated_after=dated_after,\n        dated_before=dated_before,\n        updated_after=updated_after,\n        updated_before=updated_before,\n    )\n    pydantic_expenses = [\n        self.splitwise_to_pydantic(expense) for expense in expenses\n    ]\n    return pydantic_expenses\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_friend","title":"get_friend(email_address=None, friend_id=None)","text":"

Retrieve a Financial Partner by Email Address

Parameters:

Name Type Description Default email_address Optional[str]

Email Address of Friend's user in Splitwise

None friend_id Optional[int]

Splitwise friend ID. Notice the friend ID in the following URL: https://secure.splitwise.com/#/friends/12345678

None

Returns:

Type Description Optional[Friend] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_friend(\n    self, email_address: Optional[str] = None, friend_id: Optional[int] = None\n) -> Optional[splitwise.Friend]:\n    \"\"\"\n    Retrieve a Financial Partner by Email Address\n\n    Parameters\n    ----------\n    email_address: str\n        Email Address of Friend's user in Splitwise\n    friend_id: Optional[int]\n        Splitwise friend ID. Notice the friend ID in the following\n        URL: https://secure.splitwise.com/#/friends/12345678\n\n    Returns\n    -------\n    Optional[splitwise.Friend]\n    \"\"\"\n    friend_list: List[splitwise.Friend] = self.getFriends()\n    if len(friend_list) == 1:\n        return friend_list[0]\n    for friend in friend_list:\n        if friend_id is not None and friend.id == friend_id:\n            return friend\n        elif (\n            email_address is not None\n            and friend.email.lower() == email_address.lower()\n        ):\n            return friend\n    return None\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_new_transactions","title":"get_new_transactions(dated_after=None, dated_before=None)","text":"

Get Splitwise Transactions that don't exist in Lunch Money

Also return deleted transaction from LunchMoney

Returns:

Type Description Tuple[List[SplitLunchExpense], List[TransactionObject]]

New and Deleted Transactions

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_new_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n) -> Tuple[List[SplitLunchExpense], List[TransactionObject]]:\n    \"\"\"\n    Get Splitwise Transactions that don't exist in Lunch Money\n\n    Also return deleted transaction from LunchMoney\n\n    Returns\n    -------\n    Tuple[List[SplitLunchExpense], List[TransactionObject]]\n        New and Deleted Transactions\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    splitlunch_expenses = self.lunchable.get_transactions(\n        asset_id=self.splitwise_asset.id,\n        start_date=datetime.datetime(1800, 1, 1),\n        end_date=datetime.datetime(2300, 12, 31),\n    )\n    splitlunch_ids = {\n        int(item.external_id)\n        for item in splitlunch_expenses\n        if item.external_id is not None\n    }\n    splitwise_expenses = self.get_expenses(\n        limit=0,\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    splitwise_ids = {item.splitwise_id for item in splitwise_expenses}\n    new_ids = splitwise_ids.difference(splitlunch_ids)\n    new_expenses = [\n        expense for expense in splitwise_expenses if expense.splitwise_id in new_ids\n    ]\n    deleted_transactions = self.get_deleted_transactions(\n        splitlunch_expenses=splitlunch_expenses,\n        splitwise_transactions=splitwise_expenses,\n    )\n    return new_expenses, deleted_transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitlunch_direct_import_tagged_transactions","title":"get_splitlunch_direct_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchDirectImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_direct_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchDirectImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(\n        tags=[SplitLunchConfig.splitlunch_direct_import_tag]\n    )\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_direct_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitlunch_import_tagged_transactions","title":"get_splitlunch_import_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"SplitLunchImport\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_import_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"SplitLunchImport\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_import_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_import_tag.id,\n        start_date=start_date,\n        end_date=end_date,\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitlunch_tagged_transactions","title":"get_splitlunch_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitlunch\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitlunch_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitlunch\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitlunch_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitlunch_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitwise_balance","title":"get_splitwise_balance()","text":"

Get the net balance in Splitwise

Returns:

Type Description float Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_balance(self) -> float:\n    \"\"\"\n    Get the net balance in Splitwise\n\n    Returns\n    -------\n    float\n    \"\"\"\n    groups = self.getGroups()\n    total_balance = 0.00\n    for group in groups:\n        for debt in group.simplified_debts:\n            if debt.fromUser == self.current_user.id:\n                total_balance -= float(debt.amount)\n            elif debt.toUser == self.current_user.id:\n                total_balance += float(debt.amount)\n    return total_balance\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.get_splitwise_tagged_transactions","title":"get_splitwise_tagged_transactions(start_date=None, end_date=None)","text":"

Retrieve all transactions with the \"Splitwise\" Tag

Parameters:

Name Type Description Default start_date Optional[date] None end_date Optional[date] None

Returns:

Type Description List[TransactionObject] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def get_splitwise_tagged_transactions(\n    self,\n    start_date: Optional[datetime.date] = None,\n    end_date: Optional[datetime.date] = None,\n) -> List[TransactionObject]:\n    \"\"\"\n    Retrieve all transactions with the \"Splitwise\" Tag\n\n    Parameters\n    ----------\n    start_date: Optional[datetime.date]\n    end_date : Optional[datetime.date]\n\n    Returns\n    -------\n    List[TransactionObject]\n    \"\"\"\n    if start_date is None:\n        start_date = self.earliest_start_date\n    if end_date is None:\n        end_date = self.latest_end_date\n    self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n    transactions = self.lunchable.get_transactions(\n        tag_id=self.splitwise_tag.id, start_date=start_date, end_date=end_date\n    )\n    return transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.handle_deleted_transactions","title":"handle_deleted_transactions(deleted_transactions)","text":"

Update Transactions That Exist in Splitwise, but have been deleted in Splitwise

Parameters:

Name Type Description Default deleted_transactions List[TransactionObject] required

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def handle_deleted_transactions(\n    self,\n    deleted_transactions: List[TransactionObject],\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Update Transactions That Exist in Splitwise, but have been deleted in Splitwise\n\n    Parameters\n    ----------\n    deleted_transactions: List[TransactionObject]\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    updated_transactions = []\n    for transaction in deleted_transactions:\n        update = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            transaction=TransactionUpdateObject(\n                amount=0.00,\n                payee=self._deleted_payee,\n                notes=f\"{transaction.payee} || {transaction.amount} || {transaction.notes}\",\n            ),\n        )\n        updated_transactions.append(update)\n    return updated_transactions\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.make_splitlunch","title":"make_splitlunch(tag_transactions=False)","text":"

Operate on SplitLunch tagged transactions

Split all transactions with the SplitLunch tag in half. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without any preexisting tags.

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch(self, tag_transactions: bool = False) -> List[Dict[int, Any]]:\n    \"\"\"\n    Operate on `SplitLunch` tagged transactions\n\n    Split all transactions with the `SplitLunch` tag in half. One of these\n    new splits will be recategorized to `Reimbursement`. Both new splits will receive\n    the `Splitwise` tag without any preexisting tags.\n    \"\"\"\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    split_transaction_ids = []\n    tagged_objects = self.get_splitlunch_tagged_transactions()\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        amount_1, amount_2 = self.split_a_transaction(amount=transaction.amount)\n        # Generate the First Split\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=transaction.notes,\n            amount=amount_1,\n        )\n        # Generate the second split as a copy, change some properties\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = amount_2\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            \"Splitting transaction: %s -> (%s, %s)\",\n            transaction.amount,\n            amount_1,\n            amount_2,\n        )\n\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        # Tag each of the new transactions generated\n        split_transaction_ids.append({transaction.id: update_response[\"split\"]})\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags if transaction.tags is not None else []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag is not None\n                and tag.name.lower() != self.splitlunch_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return split_transaction_ids\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.make_splitlunch_direct_import","title":"make_splitlunch_direct_import(tag_transactions=False)","text":"

Operate on SplitLunchDirectImport tagged transactions

Send a transaction to Splitwise and then mark the transaction under the Reimbursement category. The sum of the transaction will be completely owed by the financial partner.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_direct_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchDirectImport` tagged transactions\n\n    Send a transaction to Splitwise and then mark the transaction under the\n    `Reimbursement` category. The sum of the transaction will be completely owed\n    by the financial partner.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_direct_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_expense_on_behalf_of_partner(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        existing_tags = transaction.tags or []\n        tags = [\n            tag.name\n            for tag in existing_tags\n            if tag.name.lower() != self.splitlunch_direct_import_tag.name.lower()\n        ]\n        if self.splitwise_tag.name not in tags and tag_transactions is True:\n            self._raise_nonexistent_tag_error(tags=[SplitLunchConfig.splitwise_tag])\n            tags.append(self.splitwise_tag.name)\n        update = TransactionUpdateObject(\n            category_id=self.reimbursement_category.id, tags=tags, notes=notes\n        )\n        response = self.lunchable.update_transaction(\n            transaction_id=transaction.id, transaction=update\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": transaction.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": response[\"updated\"],\n            \"split\": None,\n        }\n        update_responses.append(formatted_update_response)\n    return update_responses\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.make_splitlunch_import","title":"make_splitlunch_import(tag_transactions=False)","text":"

Operate on SplitLunchImport tagged transactions

Send a transaction to Splitwise and then split the original transaction in Lunch Money. One of these new splits will be recategorized to Reimbursement. Both new splits will receive the Splitwise tag without the SplitLunchImport tag. Any other tags will be reapplied.

Parameters:

Name Type Description Default tag_transactions bool

Whether to tag the transactions with the Splitwise tag after splitting them. Defaults to False which

False

Returns:

Type Description List[Dict[str, Any]] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def make_splitlunch_import(\n    self, tag_transactions: bool = False\n) -> List[Dict[str, Any]]:\n    \"\"\"\n    Operate on `SplitLunchImport` tagged transactions\n\n    Send a transaction to Splitwise and then split the original transaction in Lunch Money.\n    One of these new splits will be recategorized to `Reimbursement`. Both new splits\n    will receive the `Splitwise` tag without the `SplitLunchImport` tag. Any other tags will be\n    reapplied.\n\n    Parameters\n    ----------\n    tag_transactions : bool\n        Whether to tag the transactions with the `Splitwise` tag after splitting them.\n        Defaults to False which\n\n    Returns\n    -------\n    List[Dict[str, Any]]\n    \"\"\"\n    self._raise_financial_partner_error()\n    if self.reimbursement_category is None:\n        self._raise_category_reimbursement_error()\n        raise ValueError(\"ReimbursementCategory\")\n    tagged_objects = self.get_splitlunch_import_tagged_transactions()\n    update_responses = []\n    for transaction in tagged_objects:\n        # Split the Original Amount\n        description = str(transaction.payee)\n        if transaction.notes is not None:\n            description = f\"{transaction.payee} - {transaction.notes}\"\n        new_transaction = self.create_self_paid_expense(\n            amount=transaction.amount,\n            description=description,\n            date=transaction.date,\n        )\n        notes = f\"Splitwise ID: {new_transaction.splitwise_id}\"\n        if transaction.notes is not None:\n            notes = f\"{transaction.notes} || {notes}\"\n        split_object = TransactionSplitObject(\n            date=transaction.date,\n            category_id=transaction.category_id,\n            notes=notes,\n            # financial_impact for self-paid transactions will be negative\n            amount=round(\n                transaction.amount - abs(new_transaction.financial_impact), 2\n            ),\n        )\n        reimbursement_object = split_object.model_copy()\n        reimbursement_object.amount = abs(new_transaction.financial_impact)\n        reimbursement_object.category_id = self.reimbursement_category.id\n        logger.debug(\n            f\"Transaction split by Splitwise: {transaction.amount} -> \"\n            f\"({split_object.amount}, {reimbursement_object.amount})\"\n        )\n        update_response = self.lunchable.update_transaction(\n            transaction_id=transaction.id,\n            split=[split_object, reimbursement_object],\n        )\n        formatted_update_response = {\n            \"original_id\": transaction.id,\n            \"payee\": transaction.payee,\n            \"amount\": transaction.amount,\n            \"reimbursement_amount\": reimbursement_object.amount,\n            \"notes\": transaction.notes,\n            \"splitwise_id\": new_transaction.splitwise_id,\n            \"updated\": update_response[\"updated\"],\n            \"split\": update_response[\"split\"],\n        }\n        update_responses.append(formatted_update_response)\n        # Tag each of the new transactions generated\n        for split_transaction_id in update_response[\"split\"]:\n            update_tags = transaction.tags or []\n            tags = [\n                tag.name\n                for tag in update_tags\n                if tag.name.lower() != self.splitlunch_import_tag.name.lower()\n            ]\n            if self.splitwise_tag.name not in tags and tag_transactions is True:\n                self._raise_nonexistent_tag_error(\n                    tags=[SplitLunchConfig.splitwise_tag]\n                )\n                tags.append(self.splitwise_tag.name)\n            tag_update = TransactionUpdateObject(tags=tags)\n            self.lunchable.update_transaction(\n                transaction_id=split_transaction_id, transaction=tag_update\n            )\n    return update_responses\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.refresh_splitwise_transactions","title":"refresh_splitwise_transactions(dated_after=None, dated_before=None, allow_self_paid=False, allow_payments=False)","text":"

Import New Splitwise Transactions to Lunch Money

This function get's all transactions from Splitwise, all transactions from your Lunch Money Splitwise account and compares the two.

Returns:

Type Description List[SplitLunchExpense] Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def refresh_splitwise_transactions(\n    self,\n    dated_after: Optional[datetime.datetime] = None,\n    dated_before: Optional[datetime.datetime] = None,\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> Dict[str, Any]:\n    \"\"\"\n    Import New Splitwise Transactions to Lunch Money\n\n    This function get's all transactions from Splitwise, all transactions from\n    your Lunch Money Splitwise account and compares the two.\n\n    Returns\n    -------\n    List[SplitLunchExpense]\n    \"\"\"\n    new_transactions, deleted_transactions = self.get_new_transactions(\n        dated_after=dated_after,\n        dated_before=dated_before,\n    )\n    self.splitwise_to_lunchmoney(\n        expenses=new_transactions,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    splitwise_asset = self.update_splitwise_balance()\n    self.handle_deleted_transactions(deleted_transactions=deleted_transactions)\n    return {\n        \"balance\": splitwise_asset.balance,\n        \"new\": new_transactions,\n        \"deleted\": deleted_transactions,\n    }\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.split_a_transaction","title":"split_a_transaction(amount) classmethod","text":"

Split a Transaction into Two

Split a bill into a tuple of two amounts (and take care of the extra penny if needed)

Parameters:

Name Type Description Default amount Union[float, int] required

Returns:

Type Description tuple

A tuple is returned with each participant's amount

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
@classmethod\ndef split_a_transaction(cls, amount: Union[float, int]) -> Tuple[float, ...]:\n    \"\"\"\n    Split a Transaction into Two\n\n    Split a bill into a tuple of two amounts (and take care\n    of the extra penny if needed)\n\n    Parameters\n    ----------\n    amount: A Currency amount (no more precise than cents)\n\n    Returns\n    -------\n    tuple\n        A tuple is returned with each participant's amount\n    \"\"\"\n    amounts_due = cls._split_amount(amount=amount, splits=2)\n    return amounts_due\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.splitwise_to_lunchmoney","title":"splitwise_to_lunchmoney(expenses, allow_self_paid=False, allow_payments=False)","text":"

Ingest Splitwise Expenses into Lunch Money

This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default, payments and self-paid transactions are also ineligible. Otherwise it will be ignored.

Parameters:

Name Type Description Default expenses List[SplitLunchExpense] required

Returns:

Type Description List[int]

New Lunch Money transaction IDs

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_lunchmoney(\n    self,\n    expenses: List[SplitLunchExpense],\n    allow_self_paid: bool = False,\n    allow_payments: bool = False,\n) -> List[int]:\n    \"\"\"\n    Ingest Splitwise Expenses into Lunch Money\n\n    This function inserts splitwise expenses into Lunch Money. If an expense not deleted and has\n    a non-0 impact on the user's splitwise balance it qualifies for ingestion. By default,\n    payments and self-paid transactions are also ineligible. Otherwise it will be ignored.\n\n    Parameters\n    ----------\n    expenses: List[SplitLunchExpense]\n\n    Returns\n    -------\n    List[int]\n        New Lunch Money transaction IDs\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    batch = []\n    new_transaction_ids = []\n    filtered_expenses = self.filter_relevant_splitwise_expenses(\n        expenses=expenses,\n        allow_self_paid=allow_self_paid,\n        allow_payments=allow_payments,\n    )\n    for splitwise_transaction in filtered_expenses:\n        new_lunchmoney_transaction = TransactionInsertObject(\n            date=splitwise_transaction.date.astimezone(tzlocal()),\n            payee=splitwise_transaction.description,\n            amount=splitwise_transaction.financial_impact,\n            asset_id=self.splitwise_asset.id,\n            external_id=splitwise_transaction.splitwise_id,\n        )\n        batch.append(new_lunchmoney_transaction)\n        if len(batch) == 10:\n            new_ids = self.lunchable.insert_transactions(\n                transactions=batch, apply_rules=True\n            )\n            new_transaction_ids += new_ids\n            batch = []\n    if len(batch) > 0:\n        new_ids = self.lunchable.insert_transactions(\n            transactions=batch, apply_rules=True\n        )\n        new_transaction_ids += new_ids\n    return new_transaction_ids\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.splitwise_to_pydantic","title":"splitwise_to_pydantic(expense)","text":"

Convert Splitwise Object to Pydantic

Parameters:

Name Type Description Default expense Expense required

Returns:

Type Description SplitLunchExpense Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def splitwise_to_pydantic(self, expense: splitwise.Expense) -> SplitLunchExpense:\n    \"\"\"\n    Convert Splitwise Object to Pydantic\n\n    Parameters\n    ----------\n    expense: splitwise.Expense\n\n    Returns\n    -------\n    SplitLunchExpense\n    \"\"\"\n    financial_impact, self_paid = _get_splitwise_impact(\n        expense=expense, current_user_id=self.current_user.id\n    )\n    expense = SplitLunchExpense(\n        splitwise_id=expense.id,\n        original_amount=expense.cost,\n        financial_impact=financial_impact,\n        self_paid=self_paid,\n        description=expense.description,\n        category=expense.category.name,\n        details=expense.details,\n        payment=expense.payment,\n        date=expense.date,\n        users=[user.id for user in expense.users],\n        created_at=expense.created_at,\n        updated_at=expense.updated_at,\n        deleted_at=expense.deleted_at,\n        deleted=True if expense.deleted_at is not None else False,\n    )\n    return expense\n
"},{"location":"reference/plugins/splitlunch/lunchmoney_splitwise/#lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch.update_splitwise_balance","title":"update_splitwise_balance()","text":"

Get and update the Splitwise Asset in Lunch Money

Returns:

Type Description AssetsObject

Updated balance

Source code in lunchable/plugins/splitlunch/lunchmoney_splitwise.py
def update_splitwise_balance(self) -> AssetsObject:\n    \"\"\"\n    Get and update the Splitwise Asset in Lunch Money\n\n    Returns\n    -------\n    AssetsObject\n        Updated  balance\n    \"\"\"\n    if self.splitwise_asset is None:\n        self._raise_splitwise_asset_error()\n        raise ValueError(\"SplitwiseAsset\")\n    balance = self.get_splitwise_balance()\n    if balance != self.splitwise_asset.balance:\n        updated_asset = self.lunchable.update_asset(\n            asset_id=self.splitwise_asset.id, balance=balance\n        )\n        self.splitwise_asset = updated_asset\n    return self.splitwise_asset\n
"},{"location":"reference/plugins/splitlunch/models/","title":"models","text":"

SplitLunch Data Models

"},{"location":"reference/plugins/splitlunch/models/#lunchable.plugins.splitlunch.models.SplitLunchExpense","title":"SplitLunchExpense","text":"

Bases: LunchableModel

SplitLunch Object for Splitwise Expenses

Parameters:

Name Type Description Default splitwise_id int required original_amount float required self_paid bool required financial_impact float required description str required category str required details str | None None payment bool required date datetime required users List[int] required created_at datetime required updated_at datetime required deleted_at datetime | None None deleted bool required Source code in lunchable/plugins/splitlunch/models.py
class SplitLunchExpense(LunchableModel):\n    \"\"\"\n    SplitLunch Object for Splitwise Expenses\n    \"\"\"\n\n    splitwise_id: int\n    original_amount: float\n    self_paid: bool\n    financial_impact: float\n    description: str\n    category: str\n    details: Optional[str] = None\n    payment: bool\n    date: datetime.datetime\n    users: List[int]\n    created_at: datetime.datetime\n    updated_at: datetime.datetime\n    deleted_at: Optional[datetime.datetime] = None\n    deleted: bool\n
"}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 3b7735ba..8ffa6d6a 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ