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 @@
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
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
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
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
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
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
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.
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
Build the Virtual Environment
hatch env create\n
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
Activate the Virtual Environment
hatch shell\n
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 CLIOutputhatch 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:
default
docs
gen
lint
test
Each of these environments has a set of commands that you can run. To see the commands for a specific environment, run:
hatch CLIOutputhatch 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:
cov
test
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 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. PatchMost 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.
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 DescriptionAsyncClient
"},{"location":"interacting/#lunchable.LunchMoney.session","title":"session: httpx.Client
cached
property
","text":"Lunch Money HTTPX Client
Returns:
Type DescriptionClient
"},{"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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
"},{"location":"interacting/#lunchable.LunchMoney.__repr__","title":"__repr__()
","text":"String Representation
Returns:
Type Descriptionstr
"},{"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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl_path
Union[list[Union[str, int]], str, int]
URL components to make into a URL
requiredpayload
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 DescriptionAny
"},{"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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl
Union[URL, str]
URL for the new Request object.
requiredcontent
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 DescriptionResponse
"},{"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 DescriptionList[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 DescriptionList[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 DescriptionList[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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type DescriptionCategoriesObject
"},{"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 DescriptionList[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 DescriptionList[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 Defaultstart_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 DescriptionList[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 DescriptionList[TagsObject]
"},{"location":"interacting/#lunchable.LunchMoney.get_transaction","title":"get_transaction(transaction_id)
","text":"Get a Transaction by ID
Parameters:
Name Type Description Defaulttransaction_id
int
Lunch Money Transaction ID
requiredReturns:
Type DescriptionTransactionObject
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 Defaultstart_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 DescriptionList[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 DescriptionUserObject
"},{"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 Defaulttype_name
str
Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation
requiredname
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 DescriptionAssetsObject
"},{"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 Defaultname
str
Name of category. Must be between 1 and 40 characters.
requireddescription
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 Descriptionint
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 Defaultname
str
Name of category. Must be between 1 and 40 characters.
requireddescription
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 Descriptionint
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 Defaultcategory_group_id
int
Id of the Lunch Money Category Group
requiredcategory_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 DescriptionCategoriesObject
"},{"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 Defaultdate
date
Date for the grouped transaction
requiredpayee
str
Payee name for the grouped transaction
requiredcategory_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
requiredReturns:
Type Descriptionint
"},{"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 Defaulttransactions
ListOrSingleTransactionInsertObject
Transactions to insert. Either a single TransactionInsertObject object or a list of them
requiredapply_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 DescriptionList[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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl_path
Union[list[Union[str, int]], str, int]
URL components to make into a URL
requiredpayload
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 DescriptionAny
"},{"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 Defaultresponse
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 Defaultstart_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)
requiredcategory_id
int
Unique identifier for the category
requiredReturns:
Type Descriptionbool
"},{"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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type Descriptionbool
"},{"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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type Descriptionbool
"},{"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 Defaulttransaction_group_id
int
Transaction Group Identifier
requiredReturns:
Type DescriptionList[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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl
Union[URL, str]
URL for the new Request object.
requiredcontent
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 DescriptionResponse
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 Defaultparent_ids
List[int]
Array of transaction IDs to unsplit. If one transaction is unsplittable, no transaction will be unsplit.
requiredremove_parents
bool
If true, deletes the original parent transaction as well. Note, this is unreversable!
False
Returns:
Type DescriptionList[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 Defaultasset_id
int
Asset Identifier
requiredtype_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 DescriptionAssetsObject
"},{"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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredname
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 Descriptionbool
"},{"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 Defaultcrypto_id
int
ID of the crypto asset to update
requiredname
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 DescriptionCryptoObject
"},{"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 Defaulttransaction_id
int
Lunch Money Transaction ID
requiredtransaction
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 DescriptionDict[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 Defaultstart_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)
requiredcategory_id
int
Unique identifier for the category
requiredamount
float
Amount for budget
requiredcurrency
Optional[str]
Currency for the budgeted amount (optional). If empty, will default to your primary currency
None
Returns:
Type DescriptionOptional[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 ofTransactionObject
","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 aTransactionUpdateObject
","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:
^
character instead of \\
to escape new lines~/Downloads/amazon_order_history.csv
, on Windows this file is located someplace like C:\\Users\\YourUserName\\Downloads\\amazon_order_history.csv
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.
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 inlunchable/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 Defaultuser_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 Defaultcontinuous
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 DescriptionList[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 Defaulttransaction
TransactionObject
required Returns:
Type DescriptionDict[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 Defaultmessage
str
your message
requiredattachment
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 DescriptionResponse
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.
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.
SplitLunchImport
Reimbursement
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
SplitLunch
Reimbursement
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.
SplitLunchDirectImport
Reimbursement
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.
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 inlunchable/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 Defaultfinancial_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 Descriptionstr
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultsplitlunch_expenses
List[TransactionObject]
required splitwise_transactions
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultoffset
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 DescriptionList[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 Defaultemail_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 DescriptionOptional[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 DescriptionTuple[List[SplitLunchExpense], List[TransactionObject]]
New and Deleted Transactions
Source code inlunchable/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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Descriptionfloat
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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultdeleted_transactions
List[TransactionObject]
required Returns:
Type DescriptionList[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.
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 Defaulttag_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 Defaulttag_transactions
bool
Whether to tag the transactions with the Splitwise
tag after splitting them. Defaults to False which
False
Returns:
Type DescriptionList[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 DescriptionList[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 Defaultamount
Union[float, int]
required Returns:
Type Descriptiontuple
A tuple is returned with each participant's amount
Source code inlunchable/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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[int]
New Lunch Money transaction IDs
Source code inlunchable/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 Defaultexpense
Expense
required Returns:
Type DescriptionSplitLunchExpense
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 DescriptionAssetsObject
Updated balance
Source code inlunchable/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 Defaultaccess_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 inlunchable/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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredamount
float
Numeric value of amount. i.e. $4.25 should be denoted as 4.25.
requiredcategory_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 inlunchable/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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredcategory_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 inlunchable/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 Defaultdate
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 inlunchable/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 Defaultdebug
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 inlunchable/_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 inlunchable/_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 inlunchable/_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
.
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.
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.
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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/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 inlunchable/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 inlunchable/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 inlunchable/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 inlunchable/_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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
Returns:
Type Descriptionstr
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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
Returns:
Type DescriptionDict[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 Defaulturl_path
Union[List[Union[str, int]], str, int]
API Components, if a list join these sequentially
requiredReturns:
Type Descriptionstr
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 inlunchable/_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 inlunchable/_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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
Returns:
Type Descriptionstr
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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
Returns:
Type DescriptionDict[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 Defaulturl_path
Union[List[Union[str, int]], str, int]
API Components, if a list join these sequentially
requiredReturns:
Type Descriptionstr
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 inlunchable/_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 Defaultlog_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 DescriptionTuple[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 Defaultlog_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 Defaultid
int
Unique identifier for asset
requiredtype_name
str
Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan, cryptocurrency, investment, other, credit, real estate]
requiredsubtype_name
str | None
Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]
None
name
str
Name of the asset
requireddisplay_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
requiredbalance_as_of
datetime
Date/time the balance was last updated in ISO 8601 extended format
requiredclosed_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
requiredinstitution_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 inlunchable/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 Defaultcategory_name
str
Name of the category
requiredcategory_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)
requiredexclude_from_budget
bool
If true, this category is excluded from budget (category properties are set in the app via the Categories page)
requiredexclude_from_totals
bool
If true, this category is excluded from totals (category properties are set in the app via the Categories page)
requireddata
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.
requiredconfig
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 Defaultid
int
A unique identifier for the category.
requiredname
str
The name of the category. Must be between 1 and 40 characters.
requireddescription
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.
requiredexclude_from_budget
bool
If true, the transactions in this category will be excluded from the budget.
requiredexclude_from_totals
bool
If true, the transactions in this category will be excluded from totals.
requiredupdated_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.
requiredgroup_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 Defaultid
int
Unique identifier for a manual crypto account (no ID for synced accounts)
requiredzabo_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)
name
str
Name of the crypto asset
requireddisplay_name
str | None
Display name of the crypto asset (as set by user)
None
balance
float
Current balance
requiredbalance_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 inlunchable/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 inlunchable/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 inlunchable/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 Defaultid
int
Unique identifier of Plaid account
requireddate_linked
date
Date account was first linked in ISO 8601 extended format
requiredname
str
Name of the account. Can be overridden by the user. Field is originally set by Plaid\")
requiredtype
str
Primary type of account. Typically one of: [credit, depository, brokerage, cash, loan, investment]. This field is set by Plaid and cannot be altered.
requiredsubtype
str
Optional subtype name of account. This field is set by Plaid and cannot be altered
requiredmask
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
requiredstatus
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)
requiredlast_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
requiredbalance_last_update
datetime
Date balance was last updated in ISO 8601 extended format. This field is set by Plaid and cannot be altered
requiredlimit
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 Defaultid
int
Unique identifier for recurring expense
requiredstart_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]
requiredpayee
str
Payee of the recurring expense
requiredamount
float
Amount of the recurring expense in numeric format to 4 decimal places
requiredcurrency
str
Three-letter lowercase currency code for the recurring expense in ISO 4217 format
requireddescription
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
requiredtype
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)
requiredoriginal_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.
requiredplaid_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 Defaultid
int
Unique identifier for tag
requiredname
str
User-defined name of tag
requireddescription
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 inlunchable/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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredamount
float
Numeric value of amount. i.e. $4.25 should be denoted as 4.25.
requiredcategory_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 inlunchable/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 Defaultid
int
Unique identifier for transaction
requireddate
date
Date of transaction in ISO 8601 format
requiredpayee
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
requiredcurrency
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 DescriptionTransactionInsertObject
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 DescriptionTransactionUpdateObject
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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredcategory_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 inlunchable/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 Defaultdate
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 inlunchable/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 Defaultuser_id
int
Unique identifier for user
requireduser_name
str
User's' name
requireduser_email
str
User's' Email
requiredaccount_id
int
Unique identifier for the associated budgeting account
requiredbudget_name
str
Name of the associated budgeting account
requiredapi_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 inlunchable/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 inlunchable/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 inlunchable/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 DescriptionAsyncClient
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.session","title":"session: httpx.Client
cached
property
","text":"Lunch Money HTTPX Client
Returns:
Type DescriptionClient
"},{"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 inlunchable/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 Defaultaccess_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 Descriptionstr
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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl_path
Union[list[Union[str, int]], str, int]
URL components to make into a URL
requiredpayload
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 DescriptionAny
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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl
Union[URL, str]
URL for the new Request object.
requiredcontent
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 DescriptionResponse
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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl_path
Union[list[Union[str, int]], str, int]
URL components to make into a URL
requiredpayload
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 DescriptionAny
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 Defaultresponse
Response
An HTTPX Response Object
required Source code inlunchable/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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl
Union[URL, str]
URL for the new Request object.
requiredcontent
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 DescriptionResponse
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 inlunchable/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 inlunchable/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 Defaultaccess_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 inlunchable/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 DescriptionList[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 Defaulttype_name
str
Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation
requiredname
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 DescriptionAssetsObject
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 Defaultasset_id
int
Asset Identifier
requiredtype_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 DescriptionAssetsObject
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 Defaultid
int
Unique identifier for asset
requiredtype_name
str
Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan, cryptocurrency, investment, other, credit, real estate]
requiredsubtype_name
str | None
Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]
None
name
str
Name of the asset
requireddisplay_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
requiredbalance_as_of
datetime
Date/time the balance was last updated in ISO 8601 extended format
requiredclosed_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
requiredinstitution_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 inlunchable/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 Defaultconfig_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 Defaultbudget_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 Defaultcategory_name
str
Name of the category
requiredcategory_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)
requiredexclude_from_budget
bool
If true, this category is excluded from budget (category properties are set in the app via the Categories page)
requiredexclude_from_totals
bool
If true, this category is excluded from totals (category properties are set in the app via the Categories page)
requireddata
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.
requiredconfig
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 Defaultstart_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 Defaultstart_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 Defaultstart_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 inlunchable/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 DescriptionList[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 Defaultstart_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)
requiredcategory_id
int
Unique identifier for the category
requiredReturns:
Type Descriptionbool
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 Defaultstart_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)
requiredcategory_id
int
Unique identifier for the category
requiredamount
float
Amount for budget
requiredcurrency
Optional[str]
Currency for the budgeted amount (optional). If empty, will default to your primary currency
None
Returns:
Type DescriptionOptional[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 inlunchable/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 DescriptionList[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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type DescriptionCategoriesObject
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 Defaultname
str
Name of category. Must be between 1 and 40 characters.
requireddescription
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 Descriptionint
ID of the newly created category
Source code inlunchable/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 Defaultname
str
Name of category. Must be between 1 and 40 characters.
requireddescription
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 Descriptionint
ID of the newly created category group
Source code inlunchable/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 Defaultcategory_group_id
int
Id of the Lunch Money Category Group
requiredcategory_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 DescriptionCategoriesObject
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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type Descriptionbool
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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type Descriptionbool
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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredname
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 Descriptionbool
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 Defaultid
int
A unique identifier for the category.
requiredname
str
The name of the category. Must be between 1 and 40 characters.
requireddescription
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.
requiredexclude_from_budget
bool
If true, the transactions in this category will be excluded from the budget.
requiredexclude_from_totals
bool
If true, the transactions in this category will be excluded from totals.
requiredupdated_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.
requiredgroup_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 Defaultid
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 Defaultname
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 inlunchable/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 DescriptionList[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 Defaultcrypto_id
int
ID of the crypto asset to update
requiredname
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 DescriptionCryptoObject
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 Defaultid
int
Unique identifier for a manual crypto account (no ID for synced accounts)
requiredzabo_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)
name
str
Name of the crypto asset
requireddisplay_name
str | None
Display name of the crypto asset (as set by user)
None
balance
float
Current balance
requiredbalance_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 inlunchable/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 Defaultname
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 Defaultid
int
Unique identifier of Plaid account
requireddate_linked
date
Date account was first linked in ISO 8601 extended format
requiredname
str
Name of the account. Can be overridden by the user. Field is originally set by Plaid\")
requiredtype
str
Primary type of account. Typically one of: [credit, depository, brokerage, cash, loan, investment]. This field is set by Plaid and cannot be altered.
requiredsubtype
str
Optional subtype name of account. This field is set by Plaid and cannot be altered
requiredmask
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
requiredstatus
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)
requiredlast_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
requiredbalance_last_update
datetime
Date balance was last updated in ISO 8601 extended format. This field is set by Plaid and cannot be altered
requiredlimit
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 inlunchable/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 DescriptionList[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 Defaultstart_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 inlunchable/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 Defaultstart_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 DescriptionList[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 Defaultid
int
Unique identifier for recurring expense
requiredstart_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]
requiredpayee
str
Payee of the recurring expense
requiredamount
float
Amount of the recurring expense in numeric format to 4 decimal places
requiredcurrency
str
Three-letter lowercase currency code for the recurring expense in ISO 4217 format
requireddescription
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
requiredtype
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)
requiredoriginal_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.
requiredplaid_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 inlunchable/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 DescriptionList[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 Defaultid
int
Unique identifier for tag
requiredname
str
User-defined name of tag
requireddescription
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 inlunchable/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 inlunchable/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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredamount
float
Numeric value of amount. i.e. $4.25 should be denoted as 4.25.
requiredcategory_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 inlunchable/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 Defaultid
int
Unique identifier for transaction
requireddate
date
Date of transaction in ISO 8601 format
requiredpayee
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
requiredcurrency
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 DescriptionTransactionInsertObject
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 DescriptionTransactionUpdateObject
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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredcategory_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 inlunchable/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 Defaultdate
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 inlunchable/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 inlunchable/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 Defaulttransaction_id
int
Lunch Money Transaction ID
requiredReturns:
Type DescriptionTransactionObject
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 inlunchable/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 Defaultstart_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 DescriptionList[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 Defaultdate
date
Date for the grouped transaction
requiredpayee
str
Payee name for the grouped transaction
requiredcategory_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
requiredReturns:
Type Descriptionint
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 Defaulttransactions
ListOrSingleTransactionInsertObject
Transactions to insert. Either a single TransactionInsertObject object or a list of them
requiredapply_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 DescriptionList[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 Defaulttransaction_group_id
int
Transaction Group Identifier
requiredReturns:
Type DescriptionList[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 Defaultparent_ids
List[int]
Array of transaction IDs to unsplit. If one transaction is unsplittable, no transaction will be unsplit.
requiredremove_parents
bool
If true, deletes the original parent transaction as well. Note, this is unreversable!
False
Returns:
Type DescriptionList[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 Defaulttransaction_id
int
Lunch Money Transaction ID
requiredtransaction
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 DescriptionDict[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 inlunchable/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 DescriptionUserObject
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 Defaultuser_id
int
Unique identifier for user
requireduser_name
str
User's' name
requireduser_email
str
User's' Email
requiredaccount_id
int
Unique identifier for the associated budgeting account
requiredbudget_name
str
Name of the associated budgeting account
requiredapi_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 TransactionObject
s to interact with transactions
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 DescriptionList[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 inlunchable/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 Descriptiondate
"},{"location":"reference/plugins/#lunchable.plugins.LunchableTransactionsApp.start_date","title":"start_date: datetime.date
property
","text":"LunchableTransactionsApp requires a Start Date
Returns:
Type Descriptiondate
"},{"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 TransactionObject
s to interact with transactions
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 DescriptionList[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 inlunchable/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 Descriptiondate
"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableTransactionsApp.start_date","title":"start_date: datetime.date
property
","text":"LunchableTransactionsApp requires a Start Date
Returns:
Type Descriptiondate
"},{"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 inlunchable/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 DescriptionList[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 DescriptionList[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 Defaultcache_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 inlunchable/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 Defaultinclude
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 DescriptionNone
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 Defaultstart_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 DescriptionDict[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 TransactionObject
s to interact with transactions
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 DescriptionList[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 Defaultplaid_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 DescriptionDict[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 DescriptionList[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 DescriptionList[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 DescriptionList[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 DescriptionList[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 DescriptionList[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 DescriptionList[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 Defaultmodel
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 inlunchable/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 Descriptiondate
"},{"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 Descriptiondate
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp","title":"LunchableTransactionsBaseApp
","text":" Bases: LunchableApp
, ABC
LunchableApp supporting transactions
Source code inlunchable/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 Descriptiondate
"},{"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 Descriptiondate
"},{"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 DescriptionDict[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 inlunchable/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 Defaultdf
DataFrame
required model_type
Type[LunchableModelType]
required Returns:
Type DescriptionList[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 Defaultmodels
Iterable[LunchableModel]
required Returns:
Type DescriptionDataFrame
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 inlunchable/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 inlunchable/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 inlunchable/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 DescriptionDataFrame
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 Defaultstart_date
date
required end_date
date
required Returns:
Type DescriptionDict[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 Defaultdf
DataFrame
required Returns:
Type DescriptionDataFrame
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 Defaultdf
DataFrame
required Returns:
Type DescriptionDataFrame
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 Defaultamount
float
Float Amount to be converted into a string
requiredReturns:
Type Descriptionstr
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 Defaultamazon
DataFrame
required transactions
DataFrame
required time_range
int
Number of days used to connect credit card transactions with Amazon transactions
7
Returns:
Type DescriptionDataFrame
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 inlunchable/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 inlunchable/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 Defaulttransaction
TransactionObject
required confirm
bool
True
Returns:
Type DescriptionOptional[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 inlunchable/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 inlunchable/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 Defaultuser_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 Defaultcontinuous
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 DescriptionList[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 Defaulttransaction
TransactionObject
required Returns:
Type DescriptionDict[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 Defaultmessage
str
your message
requiredattachment
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 DescriptionResponse
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 inlunchable/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 Defaultuser_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 Defaultcontinuous
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 DescriptionList[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 Defaulttransaction
TransactionObject
required Returns:
Type DescriptionDict[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 Defaultmessage
str
your message
requiredattachment
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 DescriptionResponse
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 inlunchable/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 inlunchable/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 Defaultfinancial_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 Descriptionstr
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultsplitlunch_expenses
List[TransactionObject]
required splitwise_transactions
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultoffset
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 DescriptionList[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 Defaultemail_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 DescriptionOptional[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 DescriptionTuple[List[SplitLunchExpense], List[TransactionObject]]
New and Deleted Transactions
Source code inlunchable/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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Descriptionfloat
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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultdeleted_transactions
List[TransactionObject]
required Returns:
Type DescriptionList[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.
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 Defaulttag_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 Defaulttag_transactions
bool
Whether to tag the transactions with the Splitwise
tag after splitting them. Defaults to False which
False
Returns:
Type DescriptionList[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 DescriptionList[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 Defaultamount
Union[float, int]
required Returns:
Type Descriptiontuple
A tuple is returned with each participant's amount
Source code inlunchable/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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[int]
New Lunch Money transaction IDs
Source code inlunchable/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 Defaultexpense
Expense
required Returns:
Type DescriptionSplitLunchExpense
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 DescriptionAssetsObject
Updated balance
Source code inlunchable/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 inlunchable/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 Defaultsplitwise_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 inlunchable/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 inlunchable/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 inlunchable/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 Defaultfinancial_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 Descriptionstr
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultsplitlunch_expenses
List[TransactionObject]
required splitwise_transactions
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultoffset
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 DescriptionList[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 Defaultemail_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 DescriptionOptional[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 DescriptionTuple[List[SplitLunchExpense], List[TransactionObject]]
New and Deleted Transactions
Source code inlunchable/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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Descriptionfloat
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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultdeleted_transactions
List[TransactionObject]
required Returns:
Type DescriptionList[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.
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 Defaulttag_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 Defaulttag_transactions
bool
Whether to tag the transactions with the Splitwise
tag after splitting them. Defaults to False which
False
Returns:
Type DescriptionList[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 DescriptionList[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 Defaultamount
Union[float, int]
required Returns:
Type Descriptiontuple
A tuple is returned with each participant's amount
Source code inlunchable/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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[int]
New Lunch Money transaction IDs
Source code inlunchable/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 Defaultexpense
Expense
required Returns:
Type DescriptionSplitLunchExpense
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 DescriptionAssetsObject
Updated balance
Source code inlunchable/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 Defaultsplitwise_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
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
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
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
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
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
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.
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
Build the Virtual Environment
hatch env create\n
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
Activate the Virtual Environment
hatch shell\n
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 CLIOutputhatch 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:
default
docs
gen
lint
test
Each of these environments has a set of commands that you can run. To see the commands for a specific environment, run:
hatch CLIOutputhatch 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:
cov
test
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 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. PatchMost 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.
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 DescriptionAsyncClient
"},{"location":"interacting/#lunchable.LunchMoney.session","title":"session: httpx.Client
cached
property
","text":"Lunch Money HTTPX Client
Returns:
Type DescriptionClient
"},{"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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
"},{"location":"interacting/#lunchable.LunchMoney.__repr__","title":"__repr__()
","text":"String Representation
Returns:
Type Descriptionstr
"},{"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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl_path
Union[list[Union[str, int]], str, int]
URL components to make into a URL
requiredpayload
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 DescriptionAny
"},{"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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl
Union[URL, str]
URL for the new Request object.
requiredcontent
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 DescriptionResponse
"},{"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 DescriptionList[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 DescriptionList[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 DescriptionList[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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type DescriptionCategoriesObject
"},{"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 DescriptionList[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 DescriptionList[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 Defaultstart_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 DescriptionList[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 DescriptionList[TagsObject]
"},{"location":"interacting/#lunchable.LunchMoney.get_transaction","title":"get_transaction(transaction_id)
","text":"Get a Transaction by ID
Parameters:
Name Type Description Defaulttransaction_id
int
Lunch Money Transaction ID
requiredReturns:
Type DescriptionTransactionObject
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 Defaultstart_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 DescriptionList[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 DescriptionUserObject
"},{"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 Defaulttype_name
str
Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation
requiredname
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 DescriptionAssetsObject
"},{"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 Defaultname
str
Name of category. Must be between 1 and 40 characters.
requireddescription
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 Descriptionint
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 Defaultname
str
Name of category. Must be between 1 and 40 characters.
requireddescription
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 Descriptionint
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 Defaultcategory_group_id
int
Id of the Lunch Money Category Group
requiredcategory_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 DescriptionCategoriesObject
"},{"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 Defaultdate
date
Date for the grouped transaction
requiredpayee
str
Payee name for the grouped transaction
requiredcategory_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
requiredReturns:
Type Descriptionint
"},{"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 Defaulttransactions
ListOrSingleTransactionInsertObject
Transactions to insert. Either a single TransactionInsertObject object or a list of them
requiredapply_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 DescriptionList[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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl_path
Union[list[Union[str, int]], str, int]
URL components to make into a URL
requiredpayload
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 DescriptionAny
"},{"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 Defaultresponse
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 Defaultstart_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)
requiredcategory_id
int
Unique identifier for the category
requiredReturns:
Type Descriptionbool
"},{"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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type Descriptionbool
"},{"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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type Descriptionbool
"},{"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 Defaulttransaction_group_id
int
Transaction Group Identifier
requiredReturns:
Type DescriptionList[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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl
Union[URL, str]
URL for the new Request object.
requiredcontent
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 DescriptionResponse
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 Defaultparent_ids
List[int]
Array of transaction IDs to unsplit. If one transaction is unsplittable, no transaction will be unsplit.
requiredremove_parents
bool
If true, deletes the original parent transaction as well. Note, this is unreversable!
False
Returns:
Type DescriptionList[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 Defaultasset_id
int
Asset Identifier
requiredtype_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 DescriptionAssetsObject
"},{"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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredname
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 Descriptionbool
"},{"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 Defaultcrypto_id
int
ID of the crypto asset to update
requiredname
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 DescriptionCryptoObject
"},{"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 Defaulttransaction_id
int
Lunch Money Transaction ID
requiredtransaction
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 DescriptionDict[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 Defaultstart_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)
requiredcategory_id
int
Unique identifier for the category
requiredamount
float
Amount for budget
requiredcurrency
Optional[str]
Currency for the budgeted amount (optional). If empty, will default to your primary currency
None
Returns:
Type DescriptionOptional[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 ofTransactionObject
","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 aTransactionUpdateObject
","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:
^
character instead of \\
to escape new lines~/Downloads/amazon_order_history.csv
, on Windows this file is located someplace like C:\\Users\\YourUserName\\Downloads\\amazon_order_history.csv
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.
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 inlunchable/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 Defaultuser_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 Defaultcontinuous
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 DescriptionList[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 Defaulttransaction
TransactionObject
required Returns:
Type DescriptionDict[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 Defaultmessage
str
your message
requiredattachment
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 DescriptionResponse
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.
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.
SplitLunchImport
Reimbursement
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
SplitLunch
Reimbursement
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.
SplitLunchDirectImport
Reimbursement
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.
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 inlunchable/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 Defaultfinancial_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 Descriptionstr
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultsplitlunch_expenses
List[TransactionObject]
required splitwise_transactions
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultoffset
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 DescriptionList[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 Defaultemail_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 DescriptionOptional[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 DescriptionTuple[List[SplitLunchExpense], List[TransactionObject]]
New and Deleted Transactions
Source code inlunchable/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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Descriptionfloat
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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultdeleted_transactions
List[TransactionObject]
required Returns:
Type DescriptionList[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.
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 Defaulttag_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 Defaulttag_transactions
bool
Whether to tag the transactions with the Splitwise
tag after splitting them. Defaults to False which
False
Returns:
Type DescriptionList[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 DescriptionList[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 Defaultamount
Union[float, int]
required Returns:
Type Descriptiontuple
A tuple is returned with each participant's amount
Source code inlunchable/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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[int]
New Lunch Money transaction IDs
Source code inlunchable/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 Defaultexpense
Expense
required Returns:
Type DescriptionSplitLunchExpense
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 DescriptionAssetsObject
Updated balance
Source code inlunchable/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 Defaultaccess_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 inlunchable/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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredamount
float
Numeric value of amount. i.e. $4.25 should be denoted as 4.25.
requiredcategory_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 inlunchable/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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredcategory_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 inlunchable/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 Defaultdate
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 inlunchable/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 Defaultdebug
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 inlunchable/_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 inlunchable/_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 inlunchable/_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
.
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.
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.
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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/_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 inlunchable/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 inlunchable/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 inlunchable/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 inlunchable/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 inlunchable/_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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
Returns:
Type Descriptionstr
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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
Returns:
Type DescriptionDict[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 Defaulturl_path
Union[List[Union[str, int]], str, int]
API Components, if a list join these sequentially
requiredReturns:
Type Descriptionstr
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 inlunchable/_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 inlunchable/_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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
Returns:
Type Descriptionstr
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 Defaultaccess_token
Optional[str]
Lunchmoney Developer API Access Token
None
Returns:
Type DescriptionDict[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 Defaulturl_path
Union[List[Union[str, int]], str, int]
API Components, if a list join these sequentially
requiredReturns:
Type Descriptionstr
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 inlunchable/_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 Defaultlog_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 DescriptionTuple[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 Defaultlog_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 Defaultid
int
Unique identifier for asset
requiredtype_name
str
Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan, cryptocurrency, investment, other, credit, real estate]
requiredsubtype_name
str | None
Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]
None
name
str
Name of the asset
requireddisplay_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
requiredbalance_as_of
datetime
Date/time the balance was last updated in ISO 8601 extended format
requiredclosed_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
requiredinstitution_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 inlunchable/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 Defaultcategory_name
str
Name of the category
requiredcategory_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)
requiredexclude_from_budget
bool
If true, this category is excluded from budget (category properties are set in the app via the Categories page)
requiredexclude_from_totals
bool
If true, this category is excluded from totals (category properties are set in the app via the Categories page)
requireddata
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.
requiredconfig
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 Defaultid
int
A unique identifier for the category.
requiredname
str
The name of the category. Must be between 1 and 40 characters.
requireddescription
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.
requiredexclude_from_budget
bool
If true, the transactions in this category will be excluded from the budget.
requiredexclude_from_totals
bool
If true, the transactions in this category will be excluded from totals.
requiredupdated_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.
requiredgroup_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 Defaultid
int
Unique identifier for a manual crypto account (no ID for synced accounts)
requiredzabo_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)
name
str
Name of the crypto asset
requireddisplay_name
str | None
Display name of the crypto asset (as set by user)
None
balance
float
Current balance
requiredbalance_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 inlunchable/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 inlunchable/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 inlunchable/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 Defaultid
int
Unique identifier of Plaid account
requireddate_linked
date
Date account was first linked in ISO 8601 extended format
requiredname
str
Name of the account. Can be overridden by the user. Field is originally set by Plaid\")
requiredtype
str
Primary type of account. Typically one of: [credit, depository, brokerage, cash, loan, investment]. This field is set by Plaid and cannot be altered.
requiredsubtype
str
Optional subtype name of account. This field is set by Plaid and cannot be altered
requiredmask
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
requiredstatus
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)
requiredlast_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
requiredbalance_last_update
datetime
Date balance was last updated in ISO 8601 extended format. This field is set by Plaid and cannot be altered
requiredlimit
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 Defaultid
int
Unique identifier for recurring expense
requiredstart_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]
requiredpayee
str
Payee of the recurring expense
requiredamount
float
Amount of the recurring expense in numeric format to 4 decimal places
requiredcurrency
str
Three-letter lowercase currency code for the recurring expense in ISO 4217 format
requireddescription
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
requiredtype
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)
requiredoriginal_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.
requiredplaid_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 Defaultid
int
Unique identifier for tag
requiredname
str
User-defined name of tag
requireddescription
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 inlunchable/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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredamount
float
Numeric value of amount. i.e. $4.25 should be denoted as 4.25.
requiredcategory_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 inlunchable/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 Defaultid
int
Unique identifier for transaction
requireddate
date
Date of transaction in ISO 8601 format
requiredpayee
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
requiredcurrency
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 DescriptionTransactionInsertObject
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 DescriptionTransactionUpdateObject
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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredcategory_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 inlunchable/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 Defaultdate
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 inlunchable/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 Defaultuser_id
int
Unique identifier for user
requireduser_name
str
User's' name
requireduser_email
str
User's' Email
requiredaccount_id
int
Unique identifier for the associated budgeting account
requiredbudget_name
str
Name of the associated budgeting account
requiredapi_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 inlunchable/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 inlunchable/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 inlunchable/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 DescriptionAsyncClient
"},{"location":"reference/models/_core/#lunchable.models._core.LunchMoneyAPIClient.session","title":"session: httpx.Client
cached
property
","text":"Lunch Money HTTPX Client
Returns:
Type DescriptionClient
"},{"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 inlunchable/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 Defaultaccess_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 Descriptionstr
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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl_path
Union[list[Union[str, int]], str, int]
URL components to make into a URL
requiredpayload
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 DescriptionAny
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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl
Union[URL, str]
URL for the new Request object.
requiredcontent
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 DescriptionResponse
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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl_path
Union[list[Union[str, int]], str, int]
URL components to make into a URL
requiredpayload
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 DescriptionAny
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 Defaultresponse
Response
An HTTPX Response Object
required Source code inlunchable/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 Defaultmethod
str
requests method: GET, OPTIONS, HEAD, POST, PUT, PATCH, or DELETE
requiredurl
Union[URL, str]
URL for the new Request object.
requiredcontent
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 DescriptionResponse
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 inlunchable/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 inlunchable/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 Defaultaccess_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 inlunchable/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 DescriptionList[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 Defaulttype_name
str
Must be one of: cash, credit, investment, other, real estate, loan, vehicle, cryptocurrency, employee compensation
requiredname
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 DescriptionAssetsObject
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 Defaultasset_id
int
Asset Identifier
requiredtype_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 DescriptionAssetsObject
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 Defaultid
int
Unique identifier for asset
requiredtype_name
str
Primary type of the asset. Must be one of: [employee compensation, cash, vehicle, loan, cryptocurrency, investment, other, credit, real estate]
requiredsubtype_name
str | None
Optional asset subtype. Examples include: [retirement, checking, savings, prepaid credit card]
None
name
str
Name of the asset
requireddisplay_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
requiredbalance_as_of
datetime
Date/time the balance was last updated in ISO 8601 extended format
requiredclosed_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
requiredinstitution_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 inlunchable/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 Defaultconfig_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 Defaultbudget_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 Defaultcategory_name
str
Name of the category
requiredcategory_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)
requiredexclude_from_budget
bool
If true, this category is excluded from budget (category properties are set in the app via the Categories page)
requiredexclude_from_totals
bool
If true, this category is excluded from totals (category properties are set in the app via the Categories page)
requireddata
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.
requiredconfig
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 Defaultstart_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 Defaultstart_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 Defaultstart_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 inlunchable/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 DescriptionList[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 Defaultstart_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)
requiredcategory_id
int
Unique identifier for the category
requiredReturns:
Type Descriptionbool
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 Defaultstart_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)
requiredcategory_id
int
Unique identifier for the category
requiredamount
float
Amount for budget
requiredcurrency
Optional[str]
Currency for the budgeted amount (optional). If empty, will default to your primary currency
None
Returns:
Type DescriptionOptional[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 inlunchable/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 DescriptionList[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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type DescriptionCategoriesObject
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 Defaultname
str
Name of category. Must be between 1 and 40 characters.
requireddescription
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 Descriptionint
ID of the newly created category
Source code inlunchable/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 Defaultname
str
Name of category. Must be between 1 and 40 characters.
requireddescription
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 Descriptionint
ID of the newly created category group
Source code inlunchable/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 Defaultcategory_group_id
int
Id of the Lunch Money Category Group
requiredcategory_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 DescriptionCategoriesObject
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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type Descriptionbool
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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredReturns:
Type Descriptionbool
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 Defaultcategory_id
int
Id of the Lunch Money Category
requiredname
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 Descriptionbool
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 Defaultid
int
A unique identifier for the category.
requiredname
str
The name of the category. Must be between 1 and 40 characters.
requireddescription
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.
requiredexclude_from_budget
bool
If true, the transactions in this category will be excluded from the budget.
requiredexclude_from_totals
bool
If true, the transactions in this category will be excluded from totals.
requiredupdated_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.
requiredgroup_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 Defaultid
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 Defaultname
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 inlunchable/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 DescriptionList[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 Defaultcrypto_id
int
ID of the crypto asset to update
requiredname
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 DescriptionCryptoObject
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 Defaultid
int
Unique identifier for a manual crypto account (no ID for synced accounts)
requiredzabo_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)
name
str
Name of the crypto asset
requireddisplay_name
str | None
Display name of the crypto asset (as set by user)
None
balance
float
Current balance
requiredbalance_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 inlunchable/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 Defaultname
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 Defaultid
int
Unique identifier of Plaid account
requireddate_linked
date
Date account was first linked in ISO 8601 extended format
requiredname
str
Name of the account. Can be overridden by the user. Field is originally set by Plaid\")
requiredtype
str
Primary type of account. Typically one of: [credit, depository, brokerage, cash, loan, investment]. This field is set by Plaid and cannot be altered.
requiredsubtype
str
Optional subtype name of account. This field is set by Plaid and cannot be altered
requiredmask
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
requiredstatus
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)
requiredlast_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
requiredbalance_last_update
datetime
Date balance was last updated in ISO 8601 extended format. This field is set by Plaid and cannot be altered
requiredlimit
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 inlunchable/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 DescriptionList[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 Defaultstart_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 inlunchable/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 Defaultstart_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 DescriptionList[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 Defaultid
int
Unique identifier for recurring expense
requiredstart_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]
requiredpayee
str
Payee of the recurring expense
requiredamount
float
Amount of the recurring expense in numeric format to 4 decimal places
requiredcurrency
str
Three-letter lowercase currency code for the recurring expense in ISO 4217 format
requireddescription
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
requiredtype
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)
requiredoriginal_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.
requiredplaid_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 inlunchable/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 DescriptionList[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 Defaultid
int
Unique identifier for tag
requiredname
str
User-defined name of tag
requireddescription
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 inlunchable/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 inlunchable/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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredamount
float
Numeric value of amount. i.e. $4.25 should be denoted as 4.25.
requiredcategory_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 inlunchable/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 Defaultid
int
Unique identifier for transaction
requireddate
date
Date of transaction in ISO 8601 format
requiredpayee
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
requiredcurrency
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 DescriptionTransactionInsertObject
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 DescriptionTransactionUpdateObject
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 Defaultdate
date
Must be in ISO 8601 format (YYYY-MM-DD).
requiredcategory_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 inlunchable/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 Defaultdate
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 inlunchable/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 inlunchable/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 Defaulttransaction_id
int
Lunch Money Transaction ID
requiredReturns:
Type DescriptionTransactionObject
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 inlunchable/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 Defaultstart_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 DescriptionList[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 Defaultdate
date
Date for the grouped transaction
requiredpayee
str
Payee name for the grouped transaction
requiredcategory_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
requiredReturns:
Type Descriptionint
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 Defaulttransactions
ListOrSingleTransactionInsertObject
Transactions to insert. Either a single TransactionInsertObject object or a list of them
requiredapply_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 DescriptionList[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 Defaulttransaction_group_id
int
Transaction Group Identifier
requiredReturns:
Type DescriptionList[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 Defaultparent_ids
List[int]
Array of transaction IDs to unsplit. If one transaction is unsplittable, no transaction will be unsplit.
requiredremove_parents
bool
If true, deletes the original parent transaction as well. Note, this is unreversable!
False
Returns:
Type DescriptionList[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 Defaulttransaction_id
int
Lunch Money Transaction ID
requiredtransaction
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 DescriptionDict[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 inlunchable/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 DescriptionUserObject
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 Defaultuser_id
int
Unique identifier for user
requireduser_name
str
User's' name
requireduser_email
str
User's' Email
requiredaccount_id
int
Unique identifier for the associated budgeting account
requiredbudget_name
str
Name of the associated budgeting account
requiredapi_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 TransactionObject
s to interact with transactions
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 DescriptionList[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 inlunchable/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 Descriptiondate
"},{"location":"reference/plugins/#lunchable.plugins.LunchableTransactionsApp.start_date","title":"start_date: datetime.date
property
","text":"LunchableTransactionsApp requires a Start Date
Returns:
Type Descriptiondate
"},{"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 TransactionObject
s to interact with transactions
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 DescriptionList[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 inlunchable/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 Descriptiondate
"},{"location":"reference/plugins/base/#lunchable.plugins.base.LunchableTransactionsApp.start_date","title":"start_date: datetime.date
property
","text":"LunchableTransactionsApp requires a Start Date
Returns:
Type Descriptiondate
"},{"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 inlunchable/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 DescriptionList[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 DescriptionList[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 Defaultcache_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 inlunchable/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 Defaultinclude
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 DescriptionNone
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 Defaultstart_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 DescriptionDict[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 TransactionObject
s to interact with transactions
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 DescriptionList[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 Defaultplaid_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 DescriptionDict[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 DescriptionList[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 DescriptionList[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 DescriptionList[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 DescriptionList[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 DescriptionList[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 DescriptionList[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 Defaultmodel
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 inlunchable/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 Descriptiondate
"},{"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 Descriptiondate
"},{"location":"reference/plugins/base/base_app/#lunchable.plugins.base.base_app.LunchableTransactionsBaseApp","title":"LunchableTransactionsBaseApp
","text":" Bases: LunchableApp
, ABC
LunchableApp supporting transactions
Source code inlunchable/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 Descriptiondate
"},{"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 Descriptiondate
"},{"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 DescriptionDict[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 inlunchable/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 Defaultdf
DataFrame
required model_type
Type[LunchableModelType]
required Returns:
Type DescriptionList[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 Defaultmodels
Iterable[LunchableModel]
required Returns:
Type DescriptionDataFrame
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 inlunchable/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 inlunchable/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 inlunchable/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 DescriptionDataFrame
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 Defaultstart_date
date
required end_date
date
required Returns:
Type DescriptionDict[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 Defaultdf
DataFrame
required Returns:
Type DescriptionDataFrame
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 Defaultdf
DataFrame
required Returns:
Type DescriptionDataFrame
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 Defaultamount
float
Float Amount to be converted into a string
requiredReturns:
Type Descriptionstr
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 Defaultamazon
DataFrame
required transactions
DataFrame
required time_range
int
Number of days used to connect credit card transactions with Amazon transactions
7
Returns:
Type DescriptionDataFrame
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 inlunchable/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 inlunchable/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 Defaulttransaction
TransactionObject
required confirm
bool
True
Returns:
Type DescriptionOptional[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 inlunchable/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 inlunchable/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 Defaultuser_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 Defaultcontinuous
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 DescriptionList[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 Defaulttransaction
TransactionObject
required Returns:
Type DescriptionDict[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 Defaultmessage
str
your message
requiredattachment
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 DescriptionResponse
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 inlunchable/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 Defaultuser_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 Defaultcontinuous
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 DescriptionList[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 Defaulttransaction
TransactionObject
required Returns:
Type DescriptionDict[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 Defaultmessage
str
your message
requiredattachment
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 DescriptionResponse
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 inlunchable/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 inlunchable/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 Defaultfinancial_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 Descriptionstr
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultsplitlunch_expenses
List[TransactionObject]
required splitwise_transactions
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultoffset
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 DescriptionList[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 Defaultemail_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 DescriptionOptional[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 DescriptionTuple[List[SplitLunchExpense], List[TransactionObject]]
New and Deleted Transactions
Source code inlunchable/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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Descriptionfloat
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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultdeleted_transactions
List[TransactionObject]
required Returns:
Type DescriptionList[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.
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 Defaulttag_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 Defaulttag_transactions
bool
Whether to tag the transactions with the Splitwise
tag after splitting them. Defaults to False which
False
Returns:
Type DescriptionList[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 DescriptionList[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 Defaultamount
Union[float, int]
required Returns:
Type Descriptiontuple
A tuple is returned with each participant's amount
Source code inlunchable/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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[int]
New Lunch Money transaction IDs
Source code inlunchable/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 Defaultexpense
Expense
required Returns:
Type DescriptionSplitLunchExpense
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 DescriptionAssetsObject
Updated balance
Source code inlunchable/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 inlunchable/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 Defaultsplitwise_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 inlunchable/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 inlunchable/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 inlunchable/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 Defaultfinancial_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 Descriptionstr
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultamount
float
Transaction Amount
requireddescription
str
Transaction Description
requiredReturns:
Type DescriptionExpense
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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultsplitlunch_expenses
List[TransactionObject]
required splitwise_transactions
List[SplitLunchExpense]
required Returns:
Type DescriptionList[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 Defaultoffset
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 DescriptionList[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 Defaultemail_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 DescriptionOptional[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 DescriptionTuple[List[SplitLunchExpense], List[TransactionObject]]
New and Deleted Transactions
Source code inlunchable/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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Descriptionfloat
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 Defaultstart_date
Optional[date]
None
end_date
Optional[date]
None
Returns:
Type DescriptionList[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 Defaultdeleted_transactions
List[TransactionObject]
required Returns:
Type DescriptionList[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.
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 Defaulttag_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 Defaulttag_transactions
bool
Whether to tag the transactions with the Splitwise
tag after splitting them. Defaults to False which
False
Returns:
Type DescriptionList[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 DescriptionList[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 Defaultamount
Union[float, int]
required Returns:
Type Descriptiontuple
A tuple is returned with each participant's amount
Source code inlunchable/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 Defaultexpenses
List[SplitLunchExpense]
required Returns:
Type DescriptionList[int]
New Lunch Money transaction IDs
Source code inlunchable/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 Defaultexpense
Expense
required Returns:
Type DescriptionSplitLunchExpense
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 DescriptionAssetsObject
Updated balance
Source code inlunchable/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 Defaultsplitwise_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