Skip to content

Commit

Permalink
✨ latest api updates
Browse files Browse the repository at this point in the history
  • Loading branch information
juftin authored Dec 22, 2023
1 parent 7ed38c6 commit 1815bf5
Show file tree
Hide file tree
Showing 15 changed files with 414 additions and 72 deletions.
1 change: 1 addition & 0 deletions docs/interacting.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ lunch = LunchMoney(access_token="xxxxxxxxxxx")
| POST | [insert_into_category_group](#lunchable.LunchMoney.insert_into_category_group) | Add to a Category Group |
| POST | [insert_transaction_group](#lunchable.LunchMoney.insert_transaction_group) | Create a Transaction Group of Two or More Transactions |
| POST | [insert_transactions](#lunchable.LunchMoney.insert_transactions) | Create One or Many Lunch Money Transactions |
| POST | [trigger_fetch_from_plaid](#lunchable.LunchMoney.trigger_fetch_from_plaid) | Trigger a Plaid Sync |
| POST | [unsplit_transactions](#lunchable.LunchMoney.unsplit_transactions) | Unsplit Transactions |
| PUT | [upsert_budget](#lunchable.LunchMoney.upsert_budget) | Upsert a Budget for a Category and Date |
| PUT | [update_asset](#lunchable.LunchMoney.update_asset) | Update a Single Asset |
Expand Down
15 changes: 15 additions & 0 deletions lunchable/models/_descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,17 @@ class _CategoriesDescriptions:
For category groups, this will populate with the categories nested
within and include id, name, description and created_at fields.
"""
archived = """
If true, the category is archived and not displayed in relevant
areas of the Lunch Money app.
"""
archived_on = """
The date and time of when the category was last archived
(in the ISO 8601 extended format).
"""
order = """
Numerical ordering of categories
"""


class _CryptoDescriptions:
Expand Down Expand Up @@ -453,3 +464,7 @@ class _TransactionDescriptions:
(for synced investment transactions only) The quantity as set by Plaid for investment
transactions.
"""
to_base = """
The amount converted to the user's primary currency. If the multicurrency
feature is not being used, to_base and amount will be the same.
"""
4 changes: 2 additions & 2 deletions lunchable/models/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class _AssetsParamsPost(LunchableModel):
currency: Optional[str] = None
institution_name: Optional[str] = None
closed_on: Optional[datetime.date] = None
exclude_transactions: bool = False
exclude_transactions: Optional[bool] = None

@field_validator("balance", mode="before")
@classmethod
Expand Down Expand Up @@ -193,7 +193,7 @@ def insert_asset(
currency: Optional[str] = None,
institution_name: Optional[str] = None,
closed_on: Optional[datetime.date] = None,
exclude_transactions: bool = False,
exclude_transactions: Optional[bool] = None,
) -> AssetsObject:
"""
Create a single (manually-managed) asset.
Expand Down
86 changes: 70 additions & 16 deletions lunchable/models/categories.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
https://lunchmoney.dev/#categories
"""

from __future__ import annotations

import datetime
import json
import logging
from typing import List, Optional
from enum import Enum
from typing import List, Optional, Union

from pydantic import Field

Expand All @@ -27,9 +30,11 @@ class ModelCreateCategory(LunchableModel):

name: str
description: Optional[str] = None
is_income: Optional[bool] = False
exclude_from_budget: Optional[bool] = False
exclude_from_totals: Optional[bool] = False
is_income: Optional[bool] = None
exclude_from_budget: Optional[bool] = None
exclude_from_totals: Optional[bool] = None
archived: Optional[bool] = None
group_id: Optional[int] = None


class CategoryChild(LunchableModel):
Expand Down Expand Up @@ -66,6 +71,10 @@ class CategoriesObject(LunchableModel):
exclude_from_totals: bool = Field(
description=_CategoriesDescriptions.exclude_from_totals
)
archived: bool = Field(False, description=_CategoriesDescriptions.archived)
archived_on: Optional[datetime.datetime] = Field(
None, description=_CategoriesDescriptions.archived_on
)
updated_at: Optional[datetime.datetime] = Field(
None, description=_CategoriesDescriptions.updated_at
)
Expand All @@ -74,7 +83,8 @@ class CategoriesObject(LunchableModel):
)
is_group: bool = Field(description=_CategoriesDescriptions.is_group)
group_id: Optional[int] = Field(None, description=_CategoriesDescriptions.group_id)
children: Optional[List[CategoryChild]] = Field(
order: Optional[int] = Field(None, description=_CategoriesDescriptions.order)
children: Optional[List[Union[CategoriesObject, CategoryChild]]] = Field(
None,
description=_CategoriesDescriptions.children,
)
Expand All @@ -90,6 +100,7 @@ class _CategoriesParamsPut(LunchableModel):
is_income: Optional[bool] = None
exclude_from_budget: Optional[bool] = None
exclude_from_totals: Optional[bool] = None
archived: Optional[bool] = None
group_id: Optional[int] = None


Expand All @@ -100,9 +111,9 @@ class _CategoriesParamsPost(LunchableModel):

name: str
description: Optional[str] = None
is_income: Optional[bool] = False
exclude_from_budget: Optional[bool] = False
exclude_from_totals: Optional[bool] = False
is_income: Optional[bool] = None
exclude_from_budget: Optional[bool] = None
exclude_from_totals: Optional[bool] = None
category_ids: Optional[List[int]] = None
new_categories: Optional[List[str]] = None

Expand All @@ -116,24 +127,55 @@ class _CategoriesAddParamsPost(LunchableModel):
new_categories: Optional[List[str]] = None


class CategoriesFormatEnum(str, Enum):
"""
Format Enum
"""

nested: str = "nested"
flattened: str = "flattened"


class _GetCategoriesParams(LunchableModel):
"""
https://lunchmoney.dev/#get-all-categories
"""

format: Optional[CategoriesFormatEnum] = None


class CategoriesClient(LunchMoneyAPIClient):
"""
Lunch Money Categories Interactions
"""

def get_categories(self) -> List[CategoriesObject]:
def get_categories(
self, format: str | CategoriesFormatEnum | None = None
) -> List[CategoriesObject]:
"""
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
Parameters
----------
format: str | CategoriesFormatEnum | None
Can either `flattened` or `nested`. If `flattened`, returns a singular
array of categories, ordered alphabetically. If `nested`, returns
top-level categories (either category groups or categories not part
of a category group) in an array. Subcategories are nested within
the category group under the property `children`. Defaults to None
which will return a `flattened` list of categories.
Returns
-------
List[CategoriesObject]
"""
response_data = self.make_request(
method=self.Methods.GET, url_path=APIConfig.LUNCHMONEY_CATEGORIES
method=self.Methods.GET,
url_path=APIConfig.LUNCHMONEY_CATEGORIES,
params=_GetCategoriesParams(format=format).model_dump(exclude_none=True),
)
categories = response_data["categories"]
category_objects = [
Expand All @@ -145,9 +187,11 @@ def insert_category(
self,
name: str,
description: Optional[str] = None,
is_income: Optional[bool] = False,
exclude_from_budget: Optional[bool] = False,
exclude_from_totals: Optional[bool] = False,
is_income: Optional[bool] = None,
exclude_from_budget: Optional[bool] = None,
exclude_from_totals: Optional[bool] = None,
archived: Optional[bool] = None,
group_id: Optional[int] = None,
) -> int:
"""
Create a Spending Category
Expand All @@ -170,6 +214,10 @@ def insert_category(
exclude_from_totals: Optional[bool]
Whether or not transactions in this category should be excluded from
calculated totals. Defaults to False.
archived: Optional[bool]
Whether or not category should be archived.
group_id: Optional[int]
Assigns the newly-created category to an existing category group.
Returns
-------
Expand All @@ -182,6 +230,8 @@ def insert_category(
is_income=is_income,
exclude_from_budget=exclude_from_budget,
exclude_from_totals=exclude_from_totals,
archived=archived,
group_id=group_id,
).model_dump(exclude_none=True)
response_data = self.make_request(
method=self.Methods.POST,
Expand Down Expand Up @@ -285,6 +335,7 @@ def update_category(
exclude_from_budget: Optional[bool] = None,
exclude_from_totals: Optional[bool] = None,
group_id: Optional[int] = None,
archived: Optional[bool] = None,
) -> bool:
"""
Update a single category
Expand Down Expand Up @@ -312,6 +363,8 @@ def update_category(
calculated totals. Defaults to False.
group_id: Optional[int]
For a category, set the group_id to include it in a category group
archived: Optional[bool]
Whether or not category should be archived.
Returns
-------
Expand All @@ -324,6 +377,7 @@ def update_category(
exclude_from_budget=exclude_from_budget,
exclude_from_totals=exclude_from_totals,
group_id=group_id,
archived=archived,
).model_dump(exclude_none=True)
response_data = self.make_request(
method=self.Methods.PUT,
Expand All @@ -336,9 +390,9 @@ def insert_category_group(
self,
name: str,
description: Optional[str] = None,
is_income: Optional[bool] = False,
exclude_from_budget: Optional[bool] = False,
exclude_from_totals: Optional[bool] = False,
is_income: Optional[bool] = None,
exclude_from_budget: Optional[bool] = None,
exclude_from_totals: Optional[bool] = None,
category_ids: Optional[List[int]] = None,
new_categories: Optional[List[str]] = None,
) -> int:
Expand Down
60 changes: 60 additions & 0 deletions lunchable/models/plaid_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
https://lunchmoney.dev/#plaid-accounts
"""

from __future__ import annotations

import datetime
import logging
from typing import List, Optional
Expand All @@ -18,6 +20,18 @@
logger = logging.getLogger(__name__)


class _PlaidFetchRequest(LunchableModel):
"""
Trigger Fetch from Plaid
https://lunchmoney.dev/#trigger-fetch-from-plaid
"""

start_date: Optional[datetime.date] = None
end_date: Optional[datetime.date] = None
plaid_account_id: Optional[int] = None


class PlaidAccountObject(LunchableModel):
"""
Assets synced from Plaid
Expand Down Expand Up @@ -77,3 +91,49 @@ def get_plaid_accounts(self) -> List[PlaidAccountObject]:
accounts = response_data.get(APIConfig.LUNCHMONEY_PLAID_ACCOUNTS)
account_objects = [PlaidAccountObject.model_validate(item) for item in accounts]
return account_objects

def trigger_fetch_from_plaid(
self,
start_date: Optional[datetime.date] = None,
end_date: Optional[datetime.date] = None,
plaid_account_id: Optional[int] = None,
) -> bool:
"""
Trigger Fetch from Plaid
** This is an experimental endpoint and parameters and/or response may change. **
Use this endpoint to trigger a fetch for latest data from Plaid.
Returns true if there were eligible Plaid accounts to trigger a fetch for. Eligible
accounts are those who last_fetch value is over 1 minute ago. (Although the limit
is every minute, please use this endpoint sparingly!)
Note that fetching from Plaid is a background job. This endpoint simply queues up
the job. You may track the plaid_last_successful_update, last_fetch and last_import
properties to verify the results of the fetch.
Parameters
----------
start_date: Optional[datetime.date]
Start date for fetch (ignored if end_date is null)
end_date: Optional[datetime.date]
End date for fetch (ignored if start_date is null)
plaid_account_id: Optional[int]
Specific ID of a plaid account to fetch. If left empty,
endpoint will trigger a fetch for all eligible accounts
Returns
-------
bool
Returns true if there were eligible Plaid accounts to trigger a fetch for.
"""
fetch_request = _PlaidFetchRequest(
start_date=start_date, end_date=end_date, plaid_account_id=plaid_account_id
)
response: bool = self.make_request(
method=self.Methods.POST,
url_path=[APIConfig.LUNCHMONEY_PLAID_ACCOUNTS, "fetch"],
data=fetch_request.model_dump(exclude_none=True),
)
return response
6 changes: 3 additions & 3 deletions lunchable/models/recurring_expenses.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class RecurringExpenseParamsGet(LunchableModel):
"""

start_date: datetime.date
debit_as_negative: bool
debit_as_negative: Optional[bool] = None


class RecurringExpensesClient(LunchMoneyAPIClient):
Expand All @@ -80,7 +80,7 @@ class RecurringExpensesClient(LunchMoneyAPIClient):
def get_recurring_expenses(
self,
start_date: Optional[datetime.date] = None,
debit_as_negative: bool = False,
debit_as_negative: Optional[bool] = None,
) -> List[RecurringExpensesObject]:
"""
Get Recurring Expenses
Expand Down Expand Up @@ -115,7 +115,7 @@ def get_recurring_expenses(
start_date = datetime.datetime.now().date().replace(day=1)
params = RecurringExpenseParamsGet(
start_date=start_date, debit_as_negative=debit_as_negative
).model_dump()
).model_dump(exclude_none=True)
response_data = self.make_request(
method=self.Methods.GET,
url_path=[APIConfig.LUNCH_MONEY_RECURRING_EXPENSES],
Expand Down
7 changes: 7 additions & 0 deletions lunchable/models/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ class TagsObject(LunchableModel):
description: Optional[str] = Field(
None, description="User-defined description of tag"
)
archived: bool = Field(
False,
description=(
"If true, the tag will not show up when creating or "
"updating transactions in the Lunch Money app"
),
)


class TagsClient(LunchMoneyAPIClient):
Expand Down
Loading

0 comments on commit 1815bf5

Please sign in to comment.