categories.py•11.4 kB
"""Category management tools for YNAB API."""
from typing import Annotated, Any
import ynab
from auth import get_api_client, get_api_configuration
def get_categories(
budget_id: Annotated[
str,
"The ID of the budget to query. Use 'last-used' for the most recently "
"accessed budget, or provide a specific budget ID.",
] = "last-used",
last_knowledge_of_server: Annotated[
int | None,
"The starting server knowledge for delta requests. If provided, only "
"entities that have changed since this value will be included. Use None "
"for full data.",
] = None,
) -> dict[str, Any]:
"""Get all categories for a budget grouped by category group.
Retrieves all categories organized by their category groups. Use this tool to
discover category IDs needed for transaction queries, budgeting operations, and
category-specific analysis.
Args:
budget_id: The ID of the budget to query. Use 'last-used' for the most
recently accessed budget, or provide a specific budget ID.
last_knowledge_of_server: The starting server knowledge for delta requests.
If provided, only entities that have changed since this value will be
included. Use None for full data.
Returns:
dict[str, Any]: Categories grouped by category group with server knowledge
for efficient delta synchronization.
Raises:
Exception: If the API call fails.
"""
configuration = get_api_configuration()
with get_api_client(configuration) as api_client:
api_instance = ynab.CategoriesApi(api_client)
try:
api_response = api_instance.get_categories(
budget_id, last_knowledge_of_server=last_knowledge_of_server
)
return api_response.data.model_dump() # type: ignore[attr-defined]
except Exception as e:
msg = f"Error fetching categories: {e!s}"
raise Exception(msg) from e
def get_category_by_id(
category_id: Annotated[
str,
"The ID of the category to retrieve. Use get_categories to discover "
"available category IDs.",
],
budget_id: Annotated[
str,
"The ID of the budget to query. Use 'last-used' for the most recently "
"accessed budget, or provide a specific budget ID.",
] = "last-used",
) -> dict[str, Any]:
"""Get a single category by its ID.
Retrieves detailed information for a specific category. Amounts (budgeted,
activity, balance, etc.) are specific to the current budget month (UTC).
If you don't know the category ID, use get_categories first to list all
categories and find the ID you need.
Args:
category_id: The ID of the category to retrieve. Use get_categories to
discover available category IDs.
budget_id: The ID of the budget to query. Use 'last-used' for the most
recently accessed budget, or provide a specific budget ID.
Returns:
dict[str, Any]: Category data including budgeted amounts, activity, balance,
and other category metadata for the current month.
Raises:
Exception: If the API call fails or category not found.
"""
configuration = get_api_configuration()
with get_api_client(configuration) as api_client:
api_instance = ynab.CategoriesApi(api_client)
try:
api_response = api_instance.get_category_by_id(budget_id, category_id)
category = api_response.data.category # type: ignore[attr-defined]
return category.model_dump() # type: ignore[attr-defined]
except Exception as e:
msg = f"Error fetching category: {e!s}"
raise Exception(msg) from e
def get_month_category_by_id(
category_id: Annotated[
str,
"The ID of the category to retrieve. Use get_categories to discover "
"available category IDs.",
],
month: Annotated[
str,
"The budget month in ISO format YYYY-MM-DD (e.g., '2024-12-01') or "
"'current' for the current month.",
],
budget_id: Annotated[
str,
"The ID of the budget to query. Use 'last-used' for the most recently "
"accessed budget, or provide a specific budget ID.",
] = "last-used",
) -> dict[str, Any]:
"""Get a single category for a specific budget month.
Retrieves detailed information for a specific category in a specific month.
Amounts (budgeted, activity, balance, etc.) are specific to the specified
budget month (UTC). Use this when you need historical category data or want
to view category information for a month other than the current one.
Args:
category_id: The ID of the category to retrieve. Use get_categories to
discover available category IDs.
month: The budget month in ISO format YYYY-MM-DD (e.g., '2024-12-01') or
'current' for the current month.
budget_id: The ID of the budget to query. Use 'last-used' for the most
recently accessed budget, or provide a specific budget ID.
Returns:
dict[str, Any]: Category data for the specified month, including budgeted
amounts, activity, and balance.
Raises:
Exception: If the API call fails or category not found.
"""
configuration = get_api_configuration()
with get_api_client(configuration) as api_client:
api_instance = ynab.CategoriesApi(api_client)
try:
api_response = api_instance.get_month_category_by_id(
budget_id, month, category_id # type: ignore[arg-type]
)
category = api_response.data.category # type: ignore[attr-defined]
return category.model_dump() # type: ignore[attr-defined]
except Exception as e:
msg = f"Error fetching month category: {e!s}"
raise Exception(msg) from e
def update_category(
category_id: Annotated[
str,
"The ID of the category to update. Use get_categories to discover "
"available category IDs.",
],
budget_id: Annotated[
str,
"The ID of the budget to query. Use 'last-used' for the most recently "
"accessed budget, or provide a specific budget ID.",
] = "last-used",
note: Annotated[
str | None,
"The category note to set. Provide a string to update the note, or None "
"to leave it unchanged.",
] = None,
budgeted: Annotated[
int | None,
"The budgeted amount in milliunits. Provide an integer to update the "
"budgeted amount, or None to leave it unchanged.",
] = None,
) -> dict[str, Any]:
"""Update a category's budgeted amount and/or note for the current month.
Amounts in YNAB are expressed in milliunits: 1000 milliunits = $1.00.
For example, to budget $50.00, use 50000. Negative amounts are not typical
for budgeted amounts. Updates apply to the current budget month.
Args:
category_id: The ID of the category to update. Use get_categories to
discover available category IDs.
budget_id: The ID of the budget to query. Use 'last-used' for the most
recently accessed budget, or provide a specific budget ID.
note: The category note to set. Provide a string to update the note,
or None to leave it unchanged.
budgeted: The budgeted amount in milliunits. Provide an integer to update
the budgeted amount, or None to leave it unchanged.
Returns:
dict[str, Any]: Updated category data including the new budgeted amount
and note.
Raises:
Exception: If the API call fails or category not found.
"""
configuration = get_api_configuration()
with get_api_client(configuration) as api_client:
api_instance = ynab.CategoriesApi(api_client)
try:
# Build category data with only provided fields
category_data: dict[str, Any] = {}
if note is not None:
category_data["note"] = note
if budgeted is not None:
category_data["budgeted"] = budgeted
# Create wrapper
wrapper = ynab.PatchCategoryWrapper(category=category_data) # type: ignore[arg-type]
api_response = api_instance.update_category(budget_id, category_id, wrapper)
return api_response.data.model_dump() # type: ignore[attr-defined]
except Exception as e:
msg = f"Error updating category: {e!s}"
raise Exception(msg) from e
def update_month_category(
category_id: Annotated[
str,
"The ID of the category to update. Use get_categories to discover "
"available category IDs.",
],
month: Annotated[
str,
"The budget month in ISO format YYYY-MM-DD (e.g., '2024-12-01') or "
"'current' for the current month.",
],
budgeted: Annotated[
int,
"The budgeted amount in milliunits. This is the amount you want to "
"allocate to this category for the specified month.",
],
budget_id: Annotated[
str,
"The ID of the budget to query. Use 'last-used' for the most recently "
"accessed budget, or provide a specific budget ID.",
] = "last-used",
) -> dict[str, Any]:
"""Update a category's budgeted amount for a specific month.
Amounts in YNAB are expressed in milliunits: 1000 milliunits = $1.00.
For example, to budget $50.00, use 50000. Negative amounts are not typical
for budgeted amounts.
Only the budgeted amount can be updated for a specific month. Any other
fields will be ignored by the API. Use this when you need to adjust budget
allocations for past or future months.
Args:
category_id: The ID of the category to update. Use get_categories to
discover available category IDs.
month: The budget month in ISO format YYYY-MM-DD (e.g., '2024-12-01') or
'current' for the current month.
budgeted: The budgeted amount in milliunits. This is the amount you want
to allocate to this category for the specified month.
budget_id: The ID of the budget to query. Use 'last-used' for the most
recently accessed budget, or provide a specific budget ID.
Returns:
dict[str, Any]: Updated month category data including the new budgeted
amount for the specified month.
Raises:
Exception: If the API call fails or category not found.
"""
configuration = get_api_configuration()
with get_api_client(configuration) as api_client:
api_instance = ynab.CategoriesApi(api_client)
try:
# Build category data (only budgeted is allowed)
category_data = {"budgeted": budgeted}
# Create wrapper
wrapper = ynab.PatchMonthCategoryWrapper(category=category_data) # type: ignore[arg-type]
api_response = api_instance.update_month_category(
budget_id, month, category_id, wrapper # type: ignore[arg-type]
)
return api_response.data.model_dump() # type: ignore[attr-defined]
except Exception as e:
msg = f"Error updating month category: {e!s}"
raise Exception(msg) from e